diff --git a/doc/API.md b/doc/API.md index 6e68938..0b20848 100644 --- a/doc/API.md +++ b/doc/API.md @@ -16,6 +16,7 @@ * [.channel](#waveformDatachannelindex) * [.resample](#waveformDataresampleoptions) * [.concat](#waveformDataconcatwaveforms) + * [.slice](#waveformDatasliceoptions) * [.toJSON](#waveformDatatojson) * [.toArrayBuffer](#waveformDatatoarraybuffer) * [WaveformDataChannel](#waveformdatachannel) @@ -468,6 +469,38 @@ console.log(wave3.length); // -> 100 console.log(combinedResult.length); // -> 900 ``` +### waveformData.slice(options) + +Returns a subset of the waveform data between a given start and end point. + +#### Arguments + +| Name | Type | +| --------- | ------------------------------------ | +| `options` | An object containing either `startIndex` and `endIndex` values, which give the start and end indexes in the waveform data, or `startTime` and `endTime`, which give the start and end times (in seconds) | + +#### Example + +Return the waveform between index 100 and index 200: + +```javascript +const waveform = WaveformData.create(buffer); + +const slice = waveform.slice({ startIndex: 100, endIndex: 200 }); + +console.log(slice.length); // -> 100 +``` + +Return the waveform between 1.0 and 2.0 seconds: + +```javascript +const waveform = WaveformData.create(buffer); + +const slice = waveform.slice({ startTime: 1.0, endTime: 2.0 }); + +console.log(slice.length); // -> 86 +``` + ## waveformData.toJSON() Returns an object containing the waveform data. diff --git a/src/waveform-data.js b/src/waveform-data.js index d70a776..eb33ada 100644 --- a/src/waveform-data.js +++ b/src/waveform-data.js @@ -423,6 +423,70 @@ WaveformData.prototype = { return totalBuffer; }, + slice: function(options) { + var startIndex = 0; + var endIndex = 0; + + if (options.startIndex != null && options.endIndex != null) { + startIndex = options.startIndex; + endIndex = options.endIndex; + } + else if (options.startTime != null && options.endTime != null) { + 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. */ diff --git a/test/unit/waveform-data.js b/test/unit/waveform-data.js index ba81a55..6ad6c22 100644 --- a/test/unit/waveform-data.js +++ b/test/unit/waveform-data.js @@ -55,7 +55,7 @@ export default function waveformDataTests(WaveformData) { context("with " + bits + "-bit " + format + " data", function() { beforeEach(function() { const data = format === "binary" ? getBinaryData({ channels: 1, bits: bits }) - : getJSONData({ channels: 1, bits: bits }); + : getJSONData({ channels: 1, bits: bits }); instance = WaveformData.create(data); }); @@ -284,6 +284,96 @@ export default function waveformDataTests(WaveformData) { expect(result.duration).to.equal(expectations.duration * 3); }); }); + + describe(".slice()", function() { + context("with valid startIndex and endIndex", function() { + it("should return a subset of the waveform", function() { + var extract = instance.slice({ startIndex: 1, endIndex: 4 }); + + expect(extract.length).to.equal(3); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([-10, 0, -5]); + expect(extract.channel(0).max_array()).to.deep.equal([10, 0, 7]); + }); + }); + + context("with endIndex beyond the length of the waveform", function() { + it("should limit to the end of the waveform", function() { + var extract = instance.slice({ startIndex: 1, endIndex: 12 }); + + expect(extract.length).to.equal(9); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([-10, 0, -5, -5, 0, 0, 0, 0, -2]); + expect(extract.channel(0).max_array()).to.deep.equal([10, 0, 7, 7, 0, 0, 0, 0, 2]); + }); + }); + + context("with startIndex equal to endIndex", function() { + it("should return a zero length waveform", function() { + var extract = instance.slice({ startIndex: 1, endIndex: 1 }); + + expect(extract.length).to.equal(0); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([]); + expect(extract.channel(0).max_array()).to.deep.equal([]); + }); + }); + + context("with startIndex greater than endIndex", function() { + it("should return a zero length waveform", function() { + var extract = instance.slice({ startIndex: 1, endIndex: 1 }); + + expect(extract.length).to.equal(0); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([]); + expect(extract.channel(0).max_array()).to.deep.equal([]); + }); + }); + + context("with startIndex and endIndex beyond the length of the waveform", function() { + it("should return a zero length waveform", function() { + var extract = instance.slice({ startIndex: 10, endIndex: 12 }); + + expect(extract.length).to.equal(0); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([]); + expect(extract.channel(0).max_array()).to.deep.equal([]); + }); + }); + + context("with negative startIndex", function() { + it("should throw RangeError", function() { + expect(function() { + instance.slice({ startIndex: -1, endIndex: 4 }); + }).to.throw(RangeError); + }); + }); + + context("with negative endIndex", function() { + it("should throw RangeError", function() { + expect(function() { + instance.slice({ startIndex: 1, endIndex: -1 }); + }).to.throw(RangeError); + }); + }); + + context("with valid startTime and endTime", function() { + it("should return a subset of the waveform", function() { + var extract = instance.slice({ startTime: instance.time(1), endTime: instance.time(4) }); + + expect(extract.length).to.equal(3); + expect(extract.channels).to.equal(instance.channels); + expect(extract.bits).to.equal(instance.bits); + expect(extract.channel(0).min_array()).to.deep.equal([-10, 0, -5]); + expect(extract.channel(0).max_array()).to.deep.equal([10, 0, 7]); + }); + }); + }); }); }); }); @@ -295,7 +385,7 @@ export default function waveformDataTests(WaveformData) { context("with " + bits + "-bit " + format + " data", function() { beforeEach(function() { const data = format === "binary" ? getBinaryData({ channels: 2, bits: bits }) - : getJSONData({ channels: 2, bits: bits }); + : getJSONData({ channels: 2, bits: bits }); instance = WaveformData.create(data); });