UNPKG

waveform-data

Version:

Audio Waveform Data Manipulation API – resample, offset and segment waveform data in JavaScript

843 lines (779 loc) 36.8 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.WaveformData = factory()); })(this, (function () { 'use strict'; /** * Provides access to the waveform data for a single audio channel. */ function WaveformDataChannel(waveformData, channelIndex) { this._waveformData = waveformData; this._channelIndex = channelIndex; } /** * Returns the waveform minimum at the given index position. */ WaveformDataChannel.prototype.min_sample = function (index) { var offset = (index * this._waveformData.channels + this._channelIndex) * 2; return this._waveformData._at(offset); }; /** * Returns the waveform maximum at the given index position. */ WaveformDataChannel.prototype.max_sample = function (index) { var offset = (index * this._waveformData.channels + this._channelIndex) * 2 + 1; return this._waveformData._at(offset); }; /** * Sets the waveform minimum at the given index position. */ WaveformDataChannel.prototype.set_min_sample = function (index, sample) { var offset = (index * this._waveformData.channels + this._channelIndex) * 2; return this._waveformData._set_at(offset, sample); }; /** * Sets the waveform maximum at the given index position. */ WaveformDataChannel.prototype.set_max_sample = function (index, sample) { var offset = (index * this._waveformData.channels + this._channelIndex) * 2 + 1; return this._waveformData._set_at(offset, sample); }; /** * Returns all the waveform minimum values as an array. */ WaveformDataChannel.prototype.min_array = function () { var length = this._waveformData.length; var values = []; for (var i = 0; i < length; i++) { values.push(this.min_sample(i)); } return values; }; /** * Returns all the waveform maximum values as an array. */ WaveformDataChannel.prototype.max_array = function () { var length = this._waveformData.length; var values = []; for (var i = 0; i < length; i++) { values.push(this.max_sample(i)); } return values; }; /** * AudioBuffer-based WaveformData generator * * Adapted from BlockFile::CalcSummary in Audacity, with permission. * See https://github.com/audacity/audacity/blob/ * 1108c1376c09166162335fab4743008cba57c4ee/src/BlockFile.cpp#L198 */ var INT8_MAX = 127; var INT8_MIN = -128; var INT16_MAX = 32767; var INT16_MIN = -32768; function calculateWaveformDataLength(audio_sample_count, scale) { var data_length = Math.floor(audio_sample_count / scale); var samples_remaining = audio_sample_count - data_length * scale; if (samples_remaining > 0) { data_length++; } return data_length; } function generateWaveformData(options) { var scale = options.scale; var amplitude_scale = options.amplitude_scale; var split_channels = options.split_channels; var length = options.length; var sample_rate = options.sample_rate; var channels = options.channels.map(function (channel) { return new Float32Array(channel); }); var output_channels = split_channels ? channels.length : 1; var header_size = 24; var data_length = calculateWaveformDataLength(length, scale); var bytes_per_sample = options.bits === 8 ? 1 : 2; var total_size = header_size + data_length * 2 * bytes_per_sample * output_channels; var buffer = new ArrayBuffer(total_size); var data_view = new DataView(buffer); var scale_counter = 0; var offset = header_size; var min_value = new Array(output_channels); var max_value = new Array(output_channels); for (var channel = 0; channel < output_channels; channel++) { min_value[channel] = Infinity; max_value[channel] = -Infinity; } var range_min = options.bits === 8 ? INT8_MIN : INT16_MIN; var range_max = options.bits === 8 ? INT8_MAX : INT16_MAX; data_view.setInt32(0, 2, true); // Version data_view.setUint32(4, options.bits === 8, true); // Is 8 bit? data_view.setInt32(8, sample_rate, true); // Sample rate data_view.setInt32(12, scale, true); // Scale data_view.setInt32(16, data_length, true); // Length data_view.setInt32(20, output_channels, true); for (var i = 0; i < length; i++) { var sample = 0; if (output_channels === 1) { for (var _channel = 0; _channel < channels.length; ++_channel) { sample += channels[_channel][i]; } sample = Math.floor(range_max * sample * amplitude_scale / channels.length); if (sample < min_value[0]) { min_value[0] = sample; if (min_value[0] < range_min) { min_value[0] = range_min; } } if (sample > max_value[0]) { max_value[0] = sample; if (max_value[0] > range_max) { max_value[0] = range_max; } } } else { for (var _channel2 = 0; _channel2 < output_channels; ++_channel2) { sample = Math.floor(range_max * channels[_channel2][i] * amplitude_scale); if (sample < min_value[_channel2]) { min_value[_channel2] = sample; if (min_value[_channel2] < range_min) { min_value[_channel2] = range_min; } } if (sample > max_value[_channel2]) { max_value[_channel2] = sample; if (max_value[_channel2] > range_max) { max_value[_channel2] = range_max; } } } } if (++scale_counter === scale) { for (var _channel3 = 0; _channel3 < output_channels; _channel3++) { if (options.bits === 8) { data_view.setInt8(offset++, min_value[_channel3]); data_view.setInt8(offset++, max_value[_channel3]); } else { data_view.setInt16(offset, min_value[_channel3], true); data_view.setInt16(offset + 2, max_value[_channel3], true); offset += 4; } min_value[_channel3] = Infinity; max_value[_channel3] = -Infinity; } scale_counter = 0; } } if (scale_counter > 0) { for (var _channel4 = 0; _channel4 < output_channels; _channel4++) { if (options.bits === 8) { data_view.setInt8(offset++, min_value[_channel4]); data_view.setInt8(offset++, max_value[_channel4]); } else { data_view.setInt16(offset, min_value[_channel4], true); data_view.setInt16(offset + 2, max_value[_channel4], true); } } } return buffer; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function isJsonWaveformData(data) { return data && _typeof(data) === 'object' && 'sample_rate' in data && 'samples_per_pixel' in data && 'bits' in data && 'length' in data && 'data' in data; } function isBinaryWaveformData(data) { var isCompatible = data && _typeof(data) === 'object' && 'byteLength' in data; if (isCompatible) { var view = new DataView(data); var version = view.getInt32(0, true); if (version !== 1 && version !== 2) { throw new TypeError('WaveformData.create(): This waveform data version not supported'); } } return isCompatible; } function convertJsonToBinary(data) { var waveformData = data.data; var channels = data.channels || 1; var header_size = 24; // version 2 var bytes_per_sample = data.bits === 8 ? 1 : 2; var expected_length = data.length * 2 * channels; if (waveformData.length !== expected_length) { throw new Error('WaveformData.create(): Length mismatch in JSON waveform data'); } var total_size = header_size + waveformData.length * bytes_per_sample; var array_buffer = new ArrayBuffer(total_size); var data_object = new DataView(array_buffer); data_object.setInt32(0, 2, true); // Version data_object.setUint32(4, data.bits === 8, true); data_object.setInt32(8, data.sample_rate, true); data_object.setInt32(12, data.samples_per_pixel, true); data_object.setInt32(16, data.length, true); data_object.setInt32(20, channels, true); var index = header_size; if (data.bits === 8) { for (var i = 0; i < waveformData.length; i++) { data_object.setInt8(index++, waveformData[i], true); } } else { for (var _i = 0; _i < waveformData.length; _i++) { data_object.setInt16(index, waveformData[_i], true); index += 2; } } return array_buffer; } function isNullOrUndefined(value) { return value === undefined || value === null; } var WorkerClass = null; try { var WorkerThreads = typeof module !== 'undefined' && typeof module.require === 'function' && module.require('worker_threads') || typeof __non_webpack_require__ === 'function' && __non_webpack_require__('worker_threads') || typeof require === 'function' && require('worker_threads'); WorkerClass = WorkerThreads.Worker; } catch(e) {} // eslint-disable-line function decodeBase64$1(base64, enableUnicode) { return Buffer.from(base64, 'base64').toString(enableUnicode ? 'utf16' : 'utf8'); } function createBase64WorkerFactory$2(base64, sourcemapArg, enableUnicodeArg) { var sourcemap = sourcemapArg === undefined ? null : sourcemapArg; var enableUnicode = enableUnicodeArg === undefined ? false : enableUnicodeArg; var source = decodeBase64$1(base64, enableUnicode); var start = source.indexOf('\n', 10) + 1; var body = source.substring(start) + (sourcemap ? '\/\/# sourceMappingURL=' + sourcemap : ''); return function WorkerFactory(options) { return new WorkerClass(body, Object.assign({}, options, { eval: true })); }; } function decodeBase64(base64, enableUnicode) { var binaryString = atob(base64); if (enableUnicode) { var binaryView = new Uint8Array(binaryString.length); for (var i = 0, n = binaryString.length; i < n; ++i) { binaryView[i] = binaryString.charCodeAt(i); } return String.fromCharCode.apply(null, new Uint16Array(binaryView.buffer)); } return binaryString; } function createURL(base64, sourcemapArg, enableUnicodeArg) { var sourcemap = sourcemapArg === undefined ? null : sourcemapArg; var enableUnicode = enableUnicodeArg === undefined ? false : enableUnicodeArg; var source = decodeBase64(base64, enableUnicode); var start = source.indexOf('\n', 10) + 1; var body = source.substring(start) + (sourcemap ? '\/\/# sourceMappingURL=' + sourcemap : ''); var blob = new Blob([body], { type: 'application/javascript' }); return URL.createObjectURL(blob); } function createBase64WorkerFactory$1(base64, sourcemapArg, enableUnicodeArg) { var url; return function WorkerFactory(options) { url = url || createURL(base64, sourcemapArg, enableUnicodeArg); return new Worker(url, options); }; } var kIsNodeJS = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'; function isNodeJS() { return kIsNodeJS; } function createBase64WorkerFactory(base64, sourcemapArg, enableUnicodeArg) { if (isNodeJS()) { return createBase64WorkerFactory$2(base64, sourcemapArg, enableUnicodeArg); } return createBase64WorkerFactory$1(base64, sourcemapArg, enableUnicodeArg); } var WorkerFactory = /*#__PURE__*/createBase64WorkerFactory('Lyogcm9sbHVwLXBsdWdpbi13ZWItd29ya2VyLWxvYWRlciAqLwooZnVuY3Rpb24gKCkgewogICd1c2Ugc3RyaWN0JzsKCiAgLyoqCiAgICogQXVkaW9CdWZmZXItYmFzZWQgV2F2ZWZvcm1EYXRhIGdlbmVyYXRvcgogICAqCiAgICogQWRhcHRlZCBmcm9tIEJsb2NrRmlsZTo6Q2FsY1N1bW1hcnkgaW4gQXVkYWNpdHksIHdpdGggcGVybWlzc2lvbi4KICAgKiBTZWUgaHR0cHM6Ly9naXRodWIuY29tL2F1ZGFjaXR5L2F1ZGFjaXR5L2Jsb2IvCiAgICogICAxMTA4YzEzNzZjMDkxNjYxNjIzMzVmYWI0NzQzMDA4Y2JhNTdjNGVlL3NyYy9CbG9ja0ZpbGUuY3BwI0wxOTgKICAgKi8KCiAgdmFyIElOVDhfTUFYID0gMTI3OwogIHZhciBJTlQ4X01JTiA9IC0xMjg7CiAgdmFyIElOVDE2X01BWCA9IDMyNzY3OwogIHZhciBJTlQxNl9NSU4gPSAtMzI3Njg7CiAgZnVuY3Rpb24gY2FsY3VsYXRlV2F2ZWZvcm1EYXRhTGVuZ3RoKGF1ZGlvX3NhbXBsZV9jb3VudCwgc2NhbGUpIHsKICAgIHZhciBkYXRhX2xlbmd0aCA9IE1hdGguZmxvb3IoYXVkaW9fc2FtcGxlX2NvdW50IC8gc2NhbGUpOwogICAgdmFyIHNhbXBsZXNfcmVtYWluaW5nID0gYXVkaW9fc2FtcGxlX2NvdW50IC0gZGF0YV9sZW5ndGggKiBzY2FsZTsKICAgIGlmIChzYW1wbGVzX3JlbWFpbmluZyA+IDApIHsKICAgICAgZGF0YV9sZW5ndGgrKzsKICAgIH0KICAgIHJldHVybiBkYXRhX2xlbmd0aDsKICB9CiAgZnVuY3Rpb24gZ2VuZXJhdGVXYXZlZm9ybURhdGEob3B0aW9ucykgewogICAgdmFyIHNjYWxlID0gb3B0aW9ucy5zY2FsZTsKICAgIHZhciBhbXBsaXR1ZGVfc2NhbGUgPSBvcHRpb25zLmFtcGxpdHVkZV9zY2FsZTsKICAgIHZhciBzcGxpdF9jaGFubmVscyA9IG9wdGlvbnMuc3BsaXRfY2hhbm5lbHM7CiAgICB2YXIgbGVuZ3RoID0gb3B0aW9ucy5sZW5ndGg7CiAgICB2YXIgc2FtcGxlX3JhdGUgPSBvcHRpb25zLnNhbXBsZV9yYXRlOwogICAgdmFyIGNoYW5uZWxzID0gb3B0aW9ucy5jaGFubmVscy5tYXAoZnVuY3Rpb24gKGNoYW5uZWwpIHsKICAgICAgcmV0dXJuIG5ldyBGbG9hdDMyQXJyYXkoY2hhbm5lbCk7CiAgICB9KTsKICAgIHZhciBvdXRwdXRfY2hhbm5lbHMgPSBzcGxpdF9jaGFubmVscyA/IGNoYW5uZWxzLmxlbmd0aCA6IDE7CiAgICB2YXIgaGVhZGVyX3NpemUgPSAyNDsKICAgIHZhciBkYXRhX2xlbmd0aCA9IGNhbGN1bGF0ZVdhdmVmb3JtRGF0YUxlbmd0aChsZW5ndGgsIHNjYWxlKTsKICAgIHZhciBieXRlc19wZXJfc2FtcGxlID0gb3B0aW9ucy5iaXRzID09PSA4ID8gMSA6IDI7CiAgICB2YXIgdG90YWxfc2l6ZSA9IGhlYWRlcl9zaXplICsgZGF0YV9sZW5ndGggKiAyICogYnl0ZXNfcGVyX3NhbXBsZSAqIG91dHB1dF9jaGFubmVsczsKICAgIHZhciBidWZmZXIgPSBuZXcgQXJyYXlCdWZmZXIodG90YWxfc2l6ZSk7CiAgICB2YXIgZGF0YV92aWV3ID0gbmV3IERhdGFWaWV3KGJ1ZmZlcik7CiAgICB2YXIgc2NhbGVfY291bnRlciA9IDA7CiAgICB2YXIgb2Zmc2V0ID0gaGVhZGVyX3NpemU7CiAgICB2YXIgbWluX3ZhbHVlID0gbmV3IEFycmF5KG91dHB1dF9jaGFubmVscyk7CiAgICB2YXIgbWF4X3ZhbHVlID0gbmV3IEFycmF5KG91dHB1dF9jaGFubmVscyk7CiAgICBmb3IgKHZhciBjaGFubmVsID0gMDsgY2hhbm5lbCA8IG91dHB1dF9jaGFubmVsczsgY2hhbm5lbCsrKSB7CiAgICAgIG1pbl92YWx1ZVtjaGFubmVsXSA9IEluZmluaXR5OwogICAgICBtYXhfdmFsdWVbY2hhbm5lbF0gPSAtSW5maW5pdHk7CiAgICB9CiAgICB2YXIgcmFuZ2VfbWluID0gb3B0aW9ucy5iaXRzID09PSA4ID8gSU5UOF9NSU4gOiBJTlQxNl9NSU47CiAgICB2YXIgcmFuZ2VfbWF4ID0gb3B0aW9ucy5iaXRzID09PSA4ID8gSU5UOF9NQVggOiBJTlQxNl9NQVg7CiAgICBkYXRhX3ZpZXcuc2V0SW50MzIoMCwgMiwgdHJ1ZSk7IC8vIFZlcnNpb24KICAgIGRhdGFfdmlldy5zZXRVaW50MzIoNCwgb3B0aW9ucy5iaXRzID09PSA4LCB0cnVlKTsgLy8gSXMgOCBiaXQ/CiAgICBkYXRhX3ZpZXcuc2V0SW50MzIoOCwgc2FtcGxlX3JhdGUsIHRydWUpOyAvLyBTYW1wbGUgcmF0ZQogICAgZGF0YV92aWV3LnNldEludDMyKDEyLCBzY2FsZSwgdHJ1ZSk7IC8vIFNjYWxlCiAgICBkYXRhX3ZpZXcuc2V0SW50MzIoMTYsIGRhdGFfbGVuZ3RoLCB0cnVlKTsgLy8gTGVuZ3RoCiAgICBkYXRhX3ZpZXcuc2V0SW50MzIoMjAsIG91dHB1dF9jaGFubmVscywgdHJ1ZSk7CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7CiAgICAgIHZhciBzYW1wbGUgPSAwOwogICAgICBpZiAob3V0cHV0X2NoYW5uZWxzID09PSAxKSB7CiAgICAgICAgZm9yICh2YXIgX2NoYW5uZWwgPSAwOyBfY2hhbm5lbCA8IGNoYW5uZWxzLmxlbmd0aDsgKytfY2hhbm5lbCkgewogICAgICAgICAgc2FtcGxlICs9IGNoYW5uZWxzW19jaGFubmVsXVtpXTsKICAgICAgICB9CiAgICAgICAgc2FtcGxlID0gTWF0aC5mbG9vcihyYW5nZV9tYXggKiBzYW1wbGUgKiBhbXBsaXR1ZGVfc2NhbGUgLyBjaGFubmVscy5sZW5ndGgpOwogICAgICAgIGlmIChzYW1wbGUgPCBtaW5fdmFsdWVbMF0pIHsKICAgICAgICAgIG1pbl92YWx1ZVswXSA9IHNhbXBsZTsKICAgICAgICAgIGlmIChtaW5fdmFsdWVbMF0gPCByYW5nZV9taW4pIHsKICAgICAgICAgICAgbWluX3ZhbHVlWzBdID0gcmFuZ2VfbWluOwogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZiAoc2FtcGxlID4gbWF4X3ZhbHVlWzBdKSB7CiAgICAgICAgICBtYXhfdmFsdWVbMF0gPSBzYW1wbGU7CiAgICAgICAgICBpZiAobWF4X3ZhbHVlWzBdID4gcmFuZ2VfbWF4KSB7CiAgICAgICAgICAgIG1heF92YWx1ZVswXSA9IHJhbmdlX21heDsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0gZWxzZSB7CiAgICAgICAgZm9yICh2YXIgX2NoYW5uZWwyID0gMDsgX2NoYW5uZWwyIDwgb3V0cHV0X2NoYW5uZWxzOyArK19jaGFubmVsMikgewogICAgICAgICAgc2FtcGxlID0gTWF0aC5mbG9vcihyYW5nZV9tYXggKiBjaGFubmVsc1tfY2hhbm5lbDJdW2ldICogYW1wbGl0dWRlX3NjYWxlKTsKICAgICAgICAgIGlmIChzYW1wbGUgPCBtaW5fdmFsdWVbX2NoYW5uZWwyXSkgewogICAgICAgICAgICBtaW5fdmFsdWVbX2NoYW5uZWwyXSA9IHNhbXBsZTsKICAgICAgICAgICAgaWYgKG1pbl92YWx1ZVtfY2hhbm5lbDJdIDwgcmFuZ2VfbWluKSB7CiAgICAgICAgICAgICAgbWluX3ZhbHVlW19jaGFubmVsMl0gPSByYW5nZV9taW47CiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICAgIGlmIChzYW1wbGUgPiBtYXhfdmFsdWVbX2NoYW5uZWwyXSkgewogICAgICAgICAgICBtYXhfdmFsdWVbX2NoYW5uZWwyXSA9IHNhbXBsZTsKICAgICAgICAgICAgaWYgKG1heF92YWx1ZVtfY2hhbm5lbDJdID4gcmFuZ2VfbWF4KSB7CiAgICAgICAgICAgICAgbWF4X3ZhbHVlW19jaGFubmVsMl0gPSByYW5nZV9tYXg7CiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgICAgaWYgKCsrc2NhbGVfY291bnRlciA9PT0gc2NhbGUpIHsKICAgICAgICBmb3IgKHZhciBfY2hhbm5lbDMgPSAwOyBfY2hhbm5lbDMgPCBvdXRwdXRfY2hhbm5lbHM7IF9jaGFubmVsMysrKSB7CiAgICAgICAgICBpZiAob3B0aW9ucy5iaXRzID09PSA4KSB7CiAgICAgICAgICAgIGRhdGFfdmlldy5zZXRJbnQ4KG9mZnNldCsrLCBtaW5fdmFsdWVbX2NoYW5uZWwzXSk7CiAgICAgICAgICAgIGRhdGFfdmlldy5zZXRJbnQ4KG9mZnNldCsrLCBtYXhfdmFsdWVbX2NoYW5uZWwzXSk7CiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBkYXRhX3ZpZXcuc2V0SW50MTYob2Zmc2V0LCBtaW5fdmFsdWVbX2NoYW5uZWwzXSwgdHJ1ZSk7CiAgICAgICAgICAgIGRhdGFfdmlldy5zZXRJbnQxNihvZmZzZXQgKyAyLCBtYXhfdmFsdWVbX2NoYW5uZWwzXSwgdHJ1ZSk7CiAgICAgICAgICAgIG9mZnNldCArPSA0OwogICAgICAgICAgfQogICAgICAgICAgbWluX3ZhbHVlW19jaGFubmVsM10gPSBJbmZpbml0eTsKICAgICAgICAgIG1heF92YWx1ZVtfY2hhbm5lbDNdID0gLUluZmluaXR5OwogICAgICAgIH0KICAgICAgICBzY2FsZV9jb3VudGVyID0gMDsKICAgICAgfQogICAgfQogICAgaWYgKHNjYWxlX2NvdW50ZXIgPiAwKSB7CiAgICAgIGZvciAodmFyIF9jaGFubmVsNCA9IDA7IF9jaGFubmVsNCA8IG91dHB1dF9jaGFubmVsczsgX2NoYW5uZWw0KyspIHsKICAgICAgICBpZiAob3B0aW9ucy5iaXRzID09PSA4KSB7CiAgICAgICAgICBkYXRhX3ZpZXcuc2V0SW50OChvZmZzZXQrKywgbWluX3ZhbHVlW19jaGFubmVsNF0pOwogICAgICAgICAgZGF0YV92aWV3LnNldEludDgob2Zmc2V0KyssIG1heF92YWx1ZVtfY2hhbm5lbDRdKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgZGF0YV92aWV3LnNldEludDE2KG9mZnNldCwgbWluX3ZhbHVlW19jaGFubmVsNF0sIHRydWUpOwogICAgICAgICAgZGF0YV92aWV3LnNldEludDE2KG9mZnNldCArIDIsIG1heF92YWx1ZVtfY2hhbm5lbDRdLCB0cnVlKTsKICAgICAgICB9CiAgICAgIH0KICAgIH0KICAgIHJldHVybiBidWZmZXI7CiAgfQoKICBvbm1lc3NhZ2UgPSBmdW5jdGlvbiBvbm1lc3NhZ2UoZXZ0KSB7CiAgICB2YXIgYnVmZmVyID0gZ2VuZXJhdGVXYXZlZm9ybURhdGEoZXZ0LmRhdGEpOwoKICAgIC8vIFRyYW5zZmVyIGJ1ZmZlciB0byB0aGUgY2FsbGluZyB0aHJlYWQKICAgIHRoaXMucG9zdE1lc3NhZ2UoYnVmZmVyLCBbYnVmZmVyXSk7CiAgICB0aGlzLmNsb3NlKCk7CiAgfTsKCn0pKCk7Ci8vIyBzb3VyY2VNYXBwaW5nVVJMPXdhdmVmb3JtLWRhdGEtd29ya2VyLmpzLm1hcAoK', null, false); /* eslint-enable */ /** * Provides access to waveform data. */ function WaveformData(data) { if (isJsonWaveformData(data)) { data = convertJsonToBinary(data); } if (isBinaryWaveformData(data)) { this._data = new DataView(data); this._offset = this._version() === 2 ? 24 : 20; this._channels = []; for (var channel = 0; channel < this.channels; channel++) { this._channels[channel] = new WaveformDataChannel(this, channel); } } else { throw new TypeError('WaveformData.create(): Unknown data format'); } } var defaultOptions = { scale: 512, bits: 8, amplitude_scale: 1.0, split_channels: false, disable_worker: false }; function getOptions(options) { var opts = { scale: options.scale || defaultOptions.scale, bits: options.bits || defaultOptions.bits, amplitude_scale: options.amplitude_scale || defaultOptions.amplitude_scale, split_channels: options.split_channels || defaultOptions.split_channels, disable_worker: options.disable_worker || defaultOptions.disable_worker }; return opts; } function getChannelData(audio_buffer) { var channels = []; for (var i = 0; i < audio_buffer.numberOfChannels; ++i) { channels.push(audio_buffer.getChannelData(i).buffer); } return channels; } function createFromAudioBuffer(audio_buffer, options, callback) { var channels = getChannelData(audio_buffer); if (options.disable_worker) { var buffer = generateWaveformData({ scale: options.scale, bits: options.bits, amplitude_scale: options.amplitude_scale, split_channels: options.split_channels, length: audio_buffer.length, sample_rate: audio_buffer.sampleRate, channels: channels }); callback(undefined, new WaveformData(buffer), audio_buffer); } else { var worker = new WorkerFactory(); worker.onmessage = function (evt) { callback(undefined, new WaveformData(evt.data), audio_buffer); }; worker.postMessage({ scale: options.scale, bits: options.bits, amplitude_scale: options.amplitude_scale, split_channels: options.split_channels, length: audio_buffer.length, sample_rate: audio_buffer.sampleRate, channels: channels }, channels); } } function createFromArrayBuffer(audioContext, audioData, options, callback) { // The following function is a workaround for a Webkit bug where decodeAudioData // invokes the errorCallback with null instead of a DOMException. // See https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata // and http://stackoverflow.com/q/10365335/103396 function errorCallback(error) { if (!error) { error = new DOMException('EncodingError'); } callback(error); // prevent double-calling the callback on errors: callback = function callback() {}; } var promise = audioContext.decodeAudioData(audioData, function (audio_buffer) { createFromAudioBuffer(audio_buffer, options, callback); }, errorCallback); if (promise) { promise.catch(errorCallback); } } /** * Creates and returns a WaveformData instance from the given waveform data. */ WaveformData.create = function create(data) { return new WaveformData(data); }; /** * Creates a WaveformData instance from audio. */ WaveformData.createFromAudio = function (options, callback) { var opts = getOptions(options); if (options.audio_context && options.array_buffer) { return createFromArrayBuffer(options.audio_context, options.array_buffer, opts, callback); } else if (options.audio_buffer) { return createFromAudioBuffer(options.audio_buffer, opts, callback); } else { throw new TypeError( // eslint-disable-next-line 'WaveformData.createFromAudio(): Pass either an AudioContext and ArrayBuffer, or an AudioBuffer object'); } }; function WaveformResampler(options) { this._inputData = options.waveformData; // Scale we want to reach this._output_samples_per_pixel = options.scale; this._scale = this._inputData.scale; // scale we are coming from // The amount of data we want to resample i.e. final zoom want to resample // all data but for intermediate zoom we want to resample subset this._input_buffer_size = this._inputData.length; var input_buffer_length_samples = this._input_buffer_size * this._inputData.scale; var output_buffer_length_samples = Math.ceil(input_buffer_length_samples / this._output_samples_per_pixel); var output_header_size = 24; // version 2 var bytes_per_sample = this._inputData.bits === 8 ? 1 : 2; var total_size = output_header_size + output_buffer_length_samples * 2 * this._inputData.channels * bytes_per_sample; this._output_data = new ArrayBuffer(total_size); this.output_dataview = new DataView(this._output_data); this.output_dataview.setInt32(0, 2, true); // Version this.output_dataview.setUint32(4, this._inputData.bits === 8, true); // Is 8 bit? this.output_dataview.setInt32(8, this._inputData.sample_rate, true); this.output_dataview.setInt32(12, this._output_samples_per_pixel, true); this.output_dataview.setInt32(16, output_buffer_length_samples, true); this.output_dataview.setInt32(20, this._inputData.channels, true); this._outputWaveformData = new WaveformData(this._output_data); this._input_index = 0; this._output_index = 0; var channels = this._inputData.channels; this._min = new Array(channels); this._max = new Array(channels); for (var channel = 0; channel < channels; ++channel) { if (this._input_buffer_size > 0) { this._min[channel] = this._inputData.channel(channel).min_sample(this._input_index); this._max[channel] = this._inputData.channel(channel).max_sample(this._input_index); } else { this._min[channel] = 0; this._max[channel] = 0; } } this._min_value = this._inputData.bits === 8 ? -128 : -32768; this._max_value = this._inputData.bits === 8 ? 127 : 32767; this._where = 0; this._prev_where = 0; this._stop = 0; this._last_input_index = 0; } WaveformResampler.prototype.sample_at_pixel = function (x) { return Math.floor(x * this._output_samples_per_pixel); }; WaveformResampler.prototype.next = function () { var count = 0; var total = 1000; var channels = this._inputData.channels; var channel; while (this._input_index < this._input_buffer_size && count < total) { while (Math.floor(this.sample_at_pixel(this._output_index) / this._scale) === this._input_index) { if (this._output_index > 0) { for (var i = 0; i < channels; ++i) { channel = this._outputWaveformData.channel(i); channel.set_min_sample(this._output_index - 1, this._min[i]); channel.set_max_sample(this._output_index - 1, this._max[i]); } } this._last_input_index = this._input_index; this._output_index++; this._where = this.sample_at_pixel(this._output_index); this._prev_where = this.sample_at_pixel(this._output_index - 1); if (this._where !== this._prev_where) { for (var _i = 0; _i < channels; ++_i) { this._min[_i] = this._max_value; this._max[_i] = this._min_value; } } } this._where = this.sample_at_pixel(this._output_index); this._stop = Math.floor(this._where / this._scale); if (this._stop > this._input_buffer_size) { this._stop = this._input_buffer_size; } while (this._input_index < this._stop) { for (var _i2 = 0; _i2 < channels; ++_i2) { channel = this._inputData.channel(_i2); var value = channel.min_sample(this._input_index); if (value < this._min[_i2]) { this._min[_i2] = value; } value = channel.max_sample(this._input_index); if (value > this._max[_i2]) { this._max[_i2] = value; } } this._input_index++; } count++; } if (this._input_index < this._input_buffer_size) { // More to do return false; } else { // Done if (this._input_index !== this._last_input_index) { for (var _i3 = 0; _i3 < channels; ++_i3) { channel = this._outputWaveformData.channel(_i3); channel.set_min_sample(this._output_index - 1, this._min[_i3]); channel.set_max_sample(this._output_index - 1, this._max[_i3]); } } return true; } }; WaveformResampler.prototype.getOutputData = function () { return this._output_data; }; WaveformData.prototype = { _getResampleOptions: function _getResampleOptions(options) { var opts = {}; opts.scale = options.scale; opts.width = options.width; if (!isNullOrUndefined(opts.width) && (typeof opts.width !== 'number' || opts.width <= 0)) { throw new RangeError('WaveformData.resample(): width should be a positive integer value'); } if (!isNullOrUndefined(opts.scale) && (typeof opts.scale !== 'number' || opts.scale <= 0)) { throw new RangeError('WaveformData.resample(): scale should be a positive integer value'); } if (!opts.scale && !opts.width) { throw new Error('WaveformData.resample(): Missing scale or width option'); } if (opts.width) { // Calculate the target scale for the resampled waveform opts.scale = Math.floor(this.duration * this.sample_rate / opts.width); } if (opts.scale < this.scale) { throw new Error('WaveformData.resample(): Zoom level ' + opts.scale + ' too low, minimum: ' + this.scale); } opts.abortSignal = options.abortSignal; return opts; }, resample: function resample(options) { options = this._getResampleOptions(options); options.waveformData = this; var resampler = new WaveformResampler(options); while (!resampler.next()) { // nothing } return new WaveformData(resampler.getOutputData()); }, /** * Concatenates with one or more other waveforms, returning a new WaveformData object. */ concat: function concat() { var self = this; var otherWaveforms = Array.prototype.slice.call(arguments); // Check that all the supplied waveforms are compatible otherWaveforms.forEach(function (otherWaveform) { if (self.channels !== otherWaveform.channels || self.sample_rate !== otherWaveform.sample_rate || self.bits !== otherWaveform.bits || self.scale !== otherWaveform.scale) { throw new Error('WaveformData.concat(): Waveforms are incompatible'); } }); var combinedBuffer = this._concatBuffers.apply(this, otherWaveforms); return WaveformData.create(combinedBuffer); }, /** * Returns a new ArrayBuffer with the concatenated waveform. * All waveforms must have identical metadata (version, channels, etc) */ _concatBuffers: function _concatBuffers() { var otherWaveforms = Array.prototype.slice.call(arguments); var headerSize = this._offset; var totalSize = headerSize; var totalDataLength = 0; var bufferCollection = [this].concat(otherWaveforms).map(function (w) { return w._data.buffer; }); for (var i = 0; i < bufferCollection.length; i++) { var buffer = bufferCollection[i]; var dataSize = new DataView(buffer).getInt32(16, true); totalSize += buffer.byteLength - headerSize; totalDataLength += dataSize; } var totalBuffer = new ArrayBuffer(totalSize); var sourceHeader = new DataView(bufferCollection[0]); var totalBufferView = new DataView(totalBuffer); // Copy the header from the first chunk for (var _i4 = 0; _i4 < headerSize; _i4++) { totalBufferView.setUint8(_i4, sourceHeader.getUint8(_i4)); } // Rewrite the data-length header item to reflect all of the samples concatenated together totalBufferView.setInt32(16, totalDataLength, true); var offset = 0; var dataOfTotalBuffer = new Uint8Array(totalBuffer, headerSize); for (var _i5 = 0; _i5 < bufferCollection.length; _i5++) { var _buffer = bufferCollection[_i5]; dataOfTotalBuffer.set(new Uint8Array(_buffer, headerSize), offset); offset += _buffer.byteLength - headerSize; } return totalBuffer; }, slice: function slice(options) { var startIndex = 0; var endIndex = 0; if (!isNullOrUndefined(options.startIndex) && !isNullOrUndefined(options.endIndex)) { startIndex = options.startIndex; endIndex = options.endIndex; } else if (!isNullOrUndefined(options.startTime) && !isNullOrUndefined(options.endTime)) { startIndex = this.at_time(options.startTime); endIndex = this.at_time(options.endTime); } if (startIndex < 0) { throw new RangeError('startIndex or startTime must not be negative'); } if (endIndex < 0) { throw new RangeError('endIndex or endTime must not be negative'); } if (startIndex > this.length) { startIndex = this.length; } if (endIndex > this.length) { endIndex = this.length; } if (startIndex > endIndex) { startIndex = endIndex; } var length = endIndex - startIndex; var header_size = 24; // Version 2 var bytes_per_sample = this.bits === 8 ? 1 : 2; var total_size = header_size + length * 2 * this.channels * bytes_per_sample; var output_data = new ArrayBuffer(total_size); var output_dataview = new DataView(output_data); output_dataview.setInt32(0, 2, true); // Version output_dataview.setUint32(4, this.bits === 8, true); // Is 8 bit? output_dataview.setInt32(8, this.sample_rate, true); output_dataview.setInt32(12, this.scale, true); output_dataview.setInt32(16, length, true); output_dataview.setInt32(20, this.channels, true); for (var i = 0; i < length * this.channels * 2; i++) { var sample = this._at(startIndex * this.channels * 2 + i); if (this.bits === 8) { output_dataview.setInt8(header_size + i, sample); } else { output_dataview.setInt16(header_size + i * 2, sample, true); } } return new WaveformData(output_data); }, /** * Returns the data format version number. */ _version: function _version() { return this._data.getInt32(0, true); }, /** * Returns the length of the waveform, in pixels. */ get length() { return this._data.getUint32(16, true); }, /** * Returns the number of bits per sample, either 8 or 16. */ get bits() { var bits = Boolean(this._data.getUint32(4, true)); return bits ? 8 : 16; }, /** * Returns the (approximate) duration of the audio file, in seconds. */ get duration() { return this.length * this.scale / this.sample_rate; }, /** * Returns the number of pixels per second. */ get pixels_per_second() { return this.sample_rate / this.scale; }, /** * Returns the amount of time represented by a single pixel, in seconds. */ get seconds_per_pixel() { return this.scale / this.sample_rate; }, /** * Returns the number of waveform channels. */ get channels() { if (this._version() === 2) { return this._data.getInt32(20, true); } else { return 1; } }, /** * Returns a waveform channel. */ channel: function channel(index) { if (index >= 0 && index < this._channels.length) { return this._channels[index]; } else { throw new RangeError('Invalid channel: ' + index); } }, /** * Returns the number of audio samples per second. */ get sample_rate() { return this._data.getInt32(8, true); }, /** * Returns the number of audio samples per pixel. */ get scale() { return this._data.getInt32(12, true); }, /** * Returns a waveform data value at a specific offset. */ _at: function at_sample(index) { if (this.bits === 8) { return this._data.getInt8(this._offset + index); } else { return this._data.getInt16(this._offset + index * 2, true); } }, /** * Sets a waveform data value at a specific offset. */ _set_at: function set_at(index, sample) { if (this.bits === 8) { return this._data.setInt8(this._offset + index, sample); } else { return this._data.setInt16(this._offset + index * 2, sample, true); } }, /** * Returns the waveform data index position for a given time. */ at_time: function at_time(time) { return Math.floor(time * this.sample_rate / this.scale); }, /** * Returns the time in seconds for a given index. */ time: function time(index) { return index * this.scale / this.sample_rate; }, /** * Returns an object containing the waveform data. */ toJSON: function toJSON() { var waveform = { version: 2, channels: this.channels, sample_rate: this.sample_rate, samples_per_pixel: this.scale, bits: this.bits, length: this.length, data: [] }; for (var i = 0; i < this.length; i++) { for (var channel = 0; channel < this.channels; channel++) { waveform.data.push(this.channel(channel).min_sample(i)); waveform.data.push(this.channel(channel).max_sample(i)); } } return waveform; }, /** * Returns the waveform data in binary format as an ArrayBuffer. */ toArrayBuffer: function toArrayBuffer() { return this._data.buffer; } }; return WaveformData; })); //# sourceMappingURL=waveform-data.js.map