Skip to content

Commit

Permalink
Introduce relativeNormalization option for split channels rendering (k…
Browse files Browse the repository at this point in the history
…atspaugh#2108)

* Extract absolute maximum as a utility function

* Implement relativeNormalization option for split channels

* assign default parameters for splitChannelsOptions

* Improve example documentation for relativeNormalization

* fix typo

* document purpose of overallAbsMax

* Capitalize normalizedMax parameter documentation

* rewrap long comment

* version tag relativeNormalization property

* unit tests for absMax utility module

* add relativeNormalization to changelog

* corrected version references to 4.3.0

* improve absMax documentation

* mark version for SplitChannelOptions typedef with @SInCE instead of @Version
  • Loading branch information
Claudiohbsantos committed Nov 19, 2020
1 parent f62ad10 commit 3c09bb8
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 10 deletions.
6 changes: 4 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
wavesurfer.js changelog
=======================

Next (unreleased)
-----------------
4.3.0 (unreleased)
------------------

- Add `relativeNormalization` option to maintain proportionality between waveforms when `splitChannels` and `normalize` are `true` (#2108)

4.2.0 (20.10.2020)
------------------
Expand Down
3 changes: 2 additions & 1 deletion example/split-channels/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ window.onload = function() {
waveColor: 'purple'
}
},
filterChannels: []
filterChannels: [],
relativeNormalization: true
}
});

Expand Down
3 changes: 3 additions & 0 deletions example/split-channels/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ <h4>splitChannelOptions</h4>
<p>
<code>filterChannels</code> - array - Array of channel numbers. Channels included in the array will not be drawn.
</p>
<p>
<code>relativeNormalization</code> - boolean - When <code>normalize</code> and <code>splitChannels</code> are both true the channels will be normalized individually or proportionally to each other. Defaults to <code>false</code> (each channel will be normalized in isolation).
</p>


<div class="footer row">
Expand Down
15 changes: 15 additions & 0 deletions spec/util.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ describe('util:', function() {
expect(WaveSurfer.util.max([])).toEqual(-Infinity);
});

/** @test {absMax} */
it('absMax returns largest absolute number in the provided array when largest is positive', function() {
expect(WaveSurfer.util.absMax([0, 1, 1.1, 100, -1])).toEqual(100);
});

/** @test {absMax} */
it('absMax returns largest absolute number in the provided array when largest is negative', function() {
expect(WaveSurfer.util.absMax([0, 1, -101, 1.1, 100, -1])).toEqual(101);
});

/** @test {absMax} */
it('absMax returns -Infinity for an empty array', function() {
expect(WaveSurfer.util.absMax([])).toEqual(-Infinity);
});

/** @test {style} */
it('style applies a map of styles to an element', function() {
var el = {
Expand Down
16 changes: 11 additions & 5 deletions src/drawer.multicanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,10 @@ export default class MultiCanvas extends Drawer {
* rendered
* @param {function} fn The render function to call, e.g. `drawWave`
* @param {number} drawIndex The index of the current channel after filtering.
* @param {number?} normalizedMax Maximum modulation value across channels for use with relativeNormalization. Ignored when undefined
* @returns {void}
*/
prepareDraw(peaks, channelIndex, start, end, fn, drawIndex) {
prepareDraw(peaks, channelIndex, start, end, fn, drawIndex, normalizedMax) {
return util.frame(() => {
// Split channels and call this function with the channelIndex set
if (peaks[0] instanceof Array) {
Expand All @@ -474,8 +475,15 @@ export default class MultiCanvas extends Drawer {
);
}

let overallAbsMax;
if (this.params.splitChannelsOptions && this.params.splitChannelsOptions.relativeNormalization) {
// calculate maximum peak across channels to use for normalization
overallAbsMax = util.max(channels.map((channelPeaks => util.absMax(channelPeaks))));
}


return channels.forEach((channelPeaks, i) =>
this.prepareDraw(channelPeaks, i, start, end, fn, filteredChannels.indexOf(channelPeaks))
this.prepareDraw(channelPeaks, i, start, end, fn, filteredChannels.indexOf(channelPeaks), overallAbsMax)
);
}
peaks = channels[0];
Expand All @@ -491,9 +499,7 @@ export default class MultiCanvas extends Drawer {
// set
let absmax = 1 / this.params.barHeight;
if (this.params.normalize) {
const max = util.max(peaks);
const min = util.min(peaks);
absmax = -min > max ? -min : max;
absmax = normalizedMax === undefined ? util.absMax(peaks) : normalizedMax;
}

// Bar wave draws the bottom only as a reflection of the top,
Expand Down
16 changes: 16 additions & 0 deletions src/util/absMax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import utilMax from './max';
import utilmin from './min';

/**
* Get the largest absolute value in an array
*
* @param {Array} values Array of numbers
* @returns {Number} Largest number found
* @example console.log(max([-3, 2, 1]), max([-3, 2, 4])); // logs 3 4
* @since 4.3.0
*/
export default function absMax(values) {
const max = utilMax(values);
const min = utilmin(values);
return -min > max ? -min : max;
}
1 change: 1 addition & 0 deletions src/util/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as getId } from './get-id';
export { default as max } from './max';
export { default as min } from './min';
export { default as absMax } from './absMax';
export { default as Observer } from './observer';
export { default as style } from './style';
export { default as requestAnimationFrame } from './request-animation-frame';
Expand Down
32 changes: 30 additions & 2 deletions src/wavesurfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import MediaElementWebAudio from './mediaelement-webaudio';
* skipForward() and skipBackward() methods.
* @property {boolean} splitChannels=false Render with separate waveforms for
* the channels of the audio
* @property {SplitChannelOptions} splitChannelsOptions={} Options for splitChannel rendering
* @property {string} waveColor='#999' The fill color of the waveform after the
* cursor.
* @property {object} xhr={} XHR options. For example:
Expand Down Expand Up @@ -149,6 +150,28 @@ import MediaElementWebAudio from './mediaelement-webaudio';
* the dependency specified in extends. Returns the plugin class.
*/

/**
* @typedef {Object} SplitChannelOptions
* @desc parameters applied when splitChannels option is true
* @property {boolean} overlay=false determines whether channels are rendered on top of each other or on separate tracks
* @property {object} channelColors={} object describing color for each channel. Example:
* {
* 0: {
* progressColor: 'green',
* waveColor: 'pink'
* },
* 1: {
* progressColor: 'orange',
* waveColor: 'purple'
* }
* }
* @property {number[]} filterChannels=[] indexes of channels to be hidden from rendering
* @property {boolean} relativeNormalization=false determines whether
* normalization is done per channel or maintains proportionality between
* channels. Only applied when normalize and splitChannels are both true.
* @since 4.3.0
*/

/**
* @interface PluginClass
*
Expand Down Expand Up @@ -267,7 +290,8 @@ export default class WaveSurfer extends util.Observer {
splitChannelsOptions: {
overlay: false,
channelColors: {},
filterChannels: []
filterChannels: [],
relativeNormalization: false
},
waveColor: '#999',
xhr: {}
Expand Down Expand Up @@ -338,7 +362,11 @@ export default class WaveSurfer extends util.Observer {
* @private
*/
this.params = Object.assign({}, this.defaultParams, params);

this.params.splitChannelsOptions = Object.assign(
{},
this.defaultParams.splitChannelsOptions,
params.splitChannelsOptions
);
/** @private */
this.container =
'string' == typeof params.container
Expand Down

0 comments on commit 3c09bb8

Please sign in to comment.