@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
215 lines (175 loc) • 5.59 kB
JavaScript
/**
* @file
*
* Defines the {@link WaveformShape} class.
*
* @module waveform-shape
*/
define(['../utils', 'konva'], function(Utils, Konva) {
'use strict';
/**
* Scales the waveform data for drawing on a canvas context.
*
* @param {Number} amplitude The waveform data point amplitude.
* @param {Number} height The height of the waveform, in pixels.
* @param {Number} scale Amplitude scaling factor.
* @returns {Number} The scaled waveform data point.
*/
function scaleY(amplitude, height, scale) {
var range = 256;
var offset = 128;
var scaledAmplitude = (amplitude * scale + offset) * height / range;
return height - Utils.clamp(height - scaledAmplitude, 0, height);
}
/**
* Waveform shape options.
*
* @typedef {Object} WaveformShapeOptions
* @global
* @property {String} color Waveform color.
* @property {WaveformOverview|WaveformZoomView} view The view object
* that contains the waveform shape.
* @property {Segment?} segment If given, render a waveform image
* covering the segment's time range. Otherwise, render the entire
* waveform duration.
*/
/**
* Creates a Konva.Shape object that renders a waveform image.
*
* @class
* @alias WaveformShape
*
* @param {WaveformShapeOptions} options
*/
function WaveformShape(options) {
const shape = new Konva.Shape({
fill: options.color,
listening: false
});
Object.assign(this, shape);
this._view = options.view;
this._height = options.height;
this._waveformDataFunc = options.waveformDataFunc;
this.sceneFunc(function(context) {
var waveformPoints = this._waveformDataFunc ? this._waveformDataFunc() : null;
this._sceneFunc(context, waveformPoints);
});
}
WaveformShape.prototype = Object.create(Konva.Shape.prototype);
WaveformShape.prototype._sceneFunc = function(context, waveformPoints) {
if (!waveformPoints) {
return;
}
var xPoints = [];
var minByChannel = [];
var maxByChannel = [];
var channels = 0;
this._forEachWaveformPoint(waveformPoints, function(point) {
if (!point || !point.min || !point.max) {
return;
}
if (channels === 0) {
channels = point.min.length;
for (var c = 0; c < channels; c++) {
minByChannel[c] = [];
maxByChannel[c] = [];
}
}
xPoints.push(point.x);
for (var i = 0; i < channels; i++) {
minByChannel[i].push(point.min[i]);
maxByChannel[i].push(point.max[i]);
}
});
if (channels === 0 || xPoints.length === 0) {
return;
}
this._drawWaveformFromPoints(context, xPoints, minByChannel, maxByChannel, channels, this._height);
};
WaveformShape.prototype._forEachWaveformPoint = function(waveformPoints, callback) {
if (!waveformPoints) {
return;
}
// Support iterators ({ next() }) without requiring Symbol.iterator.
if (typeof waveformPoints.next === 'function') {
while (true) {
var result = waveformPoints.next();
if (!result || result.done) {
break;
}
callback(result.value);
}
return;
}
// Support ES6 iterables if available.
if (typeof Symbol !== 'undefined' && waveformPoints[Symbol.iterator]) {
var iterator = waveformPoints[Symbol.iterator]();
while (true) {
var iterResult = iterator.next();
if (iterResult.done) {
break;
}
callback(iterResult.value);
}
return;
}
// Fallback for arrays.
if (Array.isArray(waveformPoints)) {
for (var i = 0; i < waveformPoints.length; i++) {
callback(waveformPoints[i]);
}
}
};
/**
* Draws a waveform on a canvas context.
*
* @param {Konva.Context} context The canvas context to draw on.
* @param {WaveformData} waveformData The waveform data to draw.
* @param {Number} frameOffset The start position of the waveform shown
* in the view, in pixels.
* @param {Number} startPixels The start position of the waveform to draw,
* in pixels.
* @param {Number} endPixels The end position of the waveform to draw,
* in pixels.
* @param {Number} width The width of the waveform area, in pixels.
* @param {Number} height The height of the waveform area, in pixels.
*/
WaveformShape.prototype._drawWaveformFromPoints = function(
context,
xPoints,
minByChannel,
maxByChannel,
channels,
height
) {
var waveformTop = 0;
var waveformHeight = Math.floor(height / channels);
for (var i = 0; i < channels; i++) {
if (i === channels - 1) {
waveformHeight = height - (channels - 1) * waveformHeight;
}
this._drawChannelFromPoints(
context,
xPoints,
minByChannel[i],
maxByChannel[i],
waveformTop,
waveformHeight
);
waveformTop += waveformHeight;
}
};
WaveformShape.prototype._drawChannelFromPoints = function(context, xPoints, minValues, maxValues, top, height) {
var amplitudeScale = this._view.getAmplitudeScale();
context.beginPath();
for (var i = 0; i < xPoints.length; i++) {
context.lineTo(xPoints[i], top + scaleY(minValues[i], height, amplitudeScale) + 0.5);
}
for (var j = xPoints.length - 1; j >= 0; j--) {
context.lineTo(xPoints[j], top + scaleY(maxValues[j], height, amplitudeScale) + 0.5);
}
context.closePath();
context.fillShape(this);
};
return WaveformShape;
});