UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

215 lines (175 loc) 5.59 kB
/** * @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; });