UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

215 lines (169 loc) 5.95 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.source.color }); Object.assign(this, shape); this._layer = options.layer; this._view = options.view; this._source = options.source; this._height = options.height; this._url = options.url + '-scaled'; this.sceneFunc(this._sceneFunc); this.hitFunc(this._waveformShapeHitFunc); } WaveformShape.prototype = Object.create(Konva.Shape.prototype); WaveformShape.prototype._sceneFunc = function(context) { var width = this._view.getWidth(); var waveformData = this._layer.getLoadedData(this._url).data; var startPixel = 0, startOffset = 0; if (this._source) { startPixel = this._view.timeToPixels(this._source.mediaStartTime) + Math.max( this._view.getFrameOffset() - this._view.timeToPixels(this._source.startTime), 0 ); startOffset = this._view.timeToPixels(this._source.mediaStartTime); } var endPixel = width; if (this._source) { endPixel = Math.min( this._view.timeToPixels(this._source.mediaEndTime) - Math.max( this._view.timeToPixels(this._source.endTime) - this._view.getFrameOffset() - this._view.getWidth(), 0 ), waveformData.length ); } this._drawWaveform( context, waveformData, startPixel, startOffset, endPixel, this._height ); }; /** * 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._drawWaveform = function(context, waveformData, startPixel, startOffset, endPixel, height) { var channels = waveformData.channels; 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._drawChannel( context, waveformData.channel(i), startPixel, startOffset, endPixel, waveformTop, waveformHeight ); waveformTop += waveformHeight; } }; WaveformShape.prototype._drawChannel = function(context, channel, startPixel, startOffset, endPixel, top, height) { var x, val; var amplitudeScale = this._view.getAmplitudeScale(); context.beginPath(); for (x = Math.floor(startPixel); x < Math.ceil(endPixel); x++) { val = channel.min_sample(x); context.lineTo(x - startOffset + 0.5, top + scaleY(val, height, amplitudeScale) + 0.5); } for (x = Math.ceil(endPixel) - 1; x >= Math.floor(startPixel); x--) { val = channel.max_sample(x); context.lineTo(x - startOffset + 0.5, top + scaleY(val, height, amplitudeScale) + 0.5); } context.closePath(); context.fillShape(this); }; WaveformShape.prototype._waveformShapeHitFunc = function(context) { if (!this._source) { return; } var frameOffset = this._view.getFrameOffset(); var viewWidth = this._view.getWidth(); var startPixels = this._view.timeToPixels(this._source.startTime); var endPixels = this._view.timeToPixels(this._source.endTime); var offsetY = 10; var hitRectHeight = this._height; if (hitRectHeight < 0) { hitRectHeight = 0; } var hitRectLeft = startPixels - frameOffset; var hitRectWidth = endPixels - startPixels; if (hitRectLeft < 0) { hitRectWidth -= -hitRectLeft; hitRectLeft = 0; } if (hitRectLeft + hitRectWidth > viewWidth) { hitRectWidth -= hitRectLeft + hitRectWidth - viewWidth; } context.beginPath(); context.rect(hitRectLeft, offsetY, hitRectWidth, hitRectHeight); context.closePath(); context.fillStrokeShape(this); }; return WaveformShape; });