mirror of
https://github.com/Tonejs/Tone.js
synced 2024-12-28 12:33:12 +00:00
360 lines
11 KiB
TypeScript
360 lines
11 KiB
TypeScript
import { expect } from "chai";
|
|
import "test/helper/ToneAudioBuffer";
|
|
import { getContext } from "../Global";
|
|
import { ToneAudioBuffer } from "./ToneAudioBuffer";
|
|
|
|
const testFile = "./audio/sine.wav";
|
|
|
|
describe("ToneAudioBuffer", () => {
|
|
|
|
context("basic", () => {
|
|
|
|
it("can be created and disposed", () => {
|
|
const buff = new ToneAudioBuffer(testFile);
|
|
buff.dispose();
|
|
});
|
|
|
|
it("loads a file from a url string", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, (buff) => {
|
|
expect(buff).to.be.instanceof(ToneAudioBuffer);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("has a duration", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, () => {
|
|
expect(buffer.duration).to.be.closeTo(3, 0.01);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("can be constructed with no arguments", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
expect(buffer.length).to.equal(0);
|
|
expect(buffer.duration).to.equal(0);
|
|
expect(buffer.numberOfChannels).to.equal(0);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can get the number of channels", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, () => {
|
|
expect(buffer.numberOfChannels).to.be.equal(1);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("can get the length of the buffer", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, () => {
|
|
expect(buffer.length).to.be.a("number");
|
|
expect(buffer.length).to.be.above(130000);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("can be constructed with an options object", done => {
|
|
const buffer = new ToneAudioBuffer({
|
|
onload: () => {
|
|
buffer.dispose();
|
|
done();
|
|
},
|
|
reverse: true,
|
|
url: testFile,
|
|
});
|
|
expect(buffer.reverse).to.equal(true);
|
|
});
|
|
|
|
it("takes an AudioBuffer in the constructor method", async () => {
|
|
const audioBuffer = await ToneAudioBuffer.load(testFile);
|
|
const buffer = new ToneAudioBuffer({
|
|
url: audioBuffer,
|
|
});
|
|
const testOne = new ToneAudioBuffer(buffer.get());
|
|
expect(testOne.get()).to.equal(buffer.get());
|
|
testOne.dispose();
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("takes a loaded ToneAudioBuffer in the constructor method", async () => {
|
|
const audioBuffer = await ToneAudioBuffer.fromUrl(testFile);
|
|
const buffer = new ToneAudioBuffer({
|
|
url: audioBuffer,
|
|
});
|
|
const testOne = new ToneAudioBuffer(buffer);
|
|
expect(testOne.get()).to.equal(buffer.get());
|
|
testOne.dispose();
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("takes an unloaded Tone.ToneAudioBuffer in the constructor method", done => {
|
|
const unloadedToneAudioBuffer = new ToneAudioBuffer(testFile);
|
|
const buffer = new ToneAudioBuffer({
|
|
onload(): void {
|
|
const testOne = new ToneAudioBuffer(buffer);
|
|
expect(unloadedToneAudioBuffer.get()).to.equal(buffer.get());
|
|
unloadedToneAudioBuffer.dispose();
|
|
buffer.dispose();
|
|
done();
|
|
},
|
|
url: unloadedToneAudioBuffer,
|
|
});
|
|
});
|
|
|
|
it("takes Tone.ToneAudioBuffer in the set method", done => {
|
|
const buffer = new ToneAudioBuffer({
|
|
url: testFile,
|
|
onload(): void {
|
|
const testOne = new ToneAudioBuffer(testFile);
|
|
testOne.set(buffer);
|
|
expect(testOne.get()).to.equal(buffer.get());
|
|
testOne.dispose();
|
|
buffer.dispose();
|
|
done();
|
|
},
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
context("loading", () => {
|
|
|
|
it("invokes the error callback if there is a problem with the file", done => {
|
|
const buffer = new ToneAudioBuffer("nosuchfile.wav", () => {
|
|
throw new Error("shouldn't invoke this function");
|
|
}, e => {
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("invokes the error callback on static .load method", async () => {
|
|
let hadError = false;
|
|
try {
|
|
await ToneAudioBuffer.load("nosuchfile.wav");
|
|
} catch (e) {
|
|
hadError = true;
|
|
}
|
|
expect(hadError).to.equal(true);
|
|
});
|
|
|
|
it("can load a file with fallback extensions", async () => {
|
|
const buffer = await ToneAudioBuffer.load("./audio/sine.[nope|nada|wav]");
|
|
expect(buffer).to.exist;
|
|
});
|
|
|
|
it("takes the first supported format when multiple extensions are provided", async () => {
|
|
const buffer = await ToneAudioBuffer.load("./audio/sine.[wav|nope]");
|
|
expect(buffer).to.exist;
|
|
});
|
|
|
|
it("instance .load method returns Promise", done => {
|
|
const promise = (new ToneAudioBuffer()).load(testFile);
|
|
expect(promise).to.have.property("then");
|
|
promise.then((buff) => {
|
|
expect(buff).to.be.instanceOf(ToneAudioBuffer);
|
|
done();
|
|
});
|
|
promise.catch(() => {
|
|
throw new Error("shouldn't invoke this function");
|
|
});
|
|
});
|
|
|
|
it("invokes the error callback if the file is corrupt", done => {
|
|
const buffer = new ToneAudioBuffer("./audio/corrupt.wav", () => {
|
|
throw new Error("shouldn't invoke this function");
|
|
}, e => {
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
context("buffer manipulation", () => {
|
|
|
|
it("returns an empty array if there is no channel data", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
expect(buffer.getChannelData(0).length).to.equal(0);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can get the channel data as an array", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, () => {
|
|
expect(buffer.getChannelData(0)).to.be.an.instanceOf(Float32Array);
|
|
expect(buffer.getChannelData(0).length).to.be.above(130000);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("can reverse a buffer", done => {
|
|
const buffer = new ToneAudioBuffer(testFile, () => {
|
|
const buffArray = buffer.get() as AudioBuffer;
|
|
const lastSample = buffArray[buffArray.length - 1];
|
|
buffer.reverse = true;
|
|
expect((buffer.get() as AudioBuffer)[0]).to.equal(lastSample);
|
|
// setting reverse again has no effect
|
|
buffer.reverse = true;
|
|
expect((buffer.get() as AudioBuffer)[0]).to.equal(lastSample);
|
|
buffer.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it("can convert from an array", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
const arr = new Float32Array(0.5 * buffer.sampleRate);
|
|
arr[0] = 0.5;
|
|
buffer.fromArray(arr);
|
|
expect(buffer.duration).to.equal(0.5);
|
|
expect(buffer.numberOfChannels).to.equal(1);
|
|
// test the first sample of the first channel to see if it's the same
|
|
expect(buffer.toArray(0)[0]).to.equal(0.5);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can create a buffer from an array using the static method", () => {
|
|
const arr = new Float32Array(0.5 * getContext().sampleRate);
|
|
arr[0] = 0.5;
|
|
const buffer = ToneAudioBuffer.fromArray(arr);
|
|
expect(buffer.duration).to.equal(0.5);
|
|
expect(buffer.numberOfChannels).to.equal(1);
|
|
// test the first sample of the first channel to see if it's the same
|
|
expect(buffer.toArray(0)[0]).to.equal(0.5);
|
|
// should return the same thing without the channel argument as well
|
|
expect(buffer.toArray()[0]).to.equal(0.5);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can convert from a multidimentional array", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
const arr = [new Float32Array(0.5 * buffer.sampleRate), new Float32Array(0.5 * buffer.sampleRate)];
|
|
arr[0][0] = 0.5;
|
|
buffer.fromArray(arr);
|
|
expect(buffer.duration).to.equal(0.5);
|
|
expect(buffer.numberOfChannels).to.equal(2);
|
|
expect(buffer.toArray(0)[0]).to.equal(0.5);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can convert to and from an array", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
const arr = [new Float32Array(0.5 * buffer.sampleRate), new Float32Array(0.5 * buffer.sampleRate)];
|
|
arr[0][0] = 0.5;
|
|
buffer.fromArray(arr);
|
|
expect(buffer.toArray(0)[0]).to.equal(0.5);
|
|
expect(buffer.toArray()[0][0]).to.equal(0.5);
|
|
// with a selected channel
|
|
expect(buffer.toArray(0)[0]).to.equal(0.5);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can slice a portion of the array", async () => {
|
|
const buffer = await ToneAudioBuffer.fromUrl(testFile);
|
|
// original duration
|
|
expect(buffer.duration).to.be.closeTo(3, 0.01);
|
|
const sliced1 = buffer.slice(0, 1);
|
|
// confirm they have the same values
|
|
const offset = Math.floor(buffer.sampleRate * 0.9);
|
|
// does not modify the original
|
|
expect(buffer.duration).to.be.closeTo(3, 0.01);
|
|
expect(sliced1.duration).to.be.closeTo(1, 0.01);
|
|
const sliced2 = sliced1.slice(0.5);
|
|
expect(sliced2.duration).to.be.closeTo(0.5, 0.01);
|
|
const sliced3 = buffer.slice(2);
|
|
expect(sliced3.toArray(0)[Math.floor(0.5 * buffer.sampleRate) + 1])
|
|
.to.equal(buffer.toArray(0)[Math.floor(2.5 * buffer.sampleRate) + 1]);
|
|
buffer.dispose();
|
|
sliced1.dispose();
|
|
sliced2.dispose();
|
|
sliced3.dispose();
|
|
});
|
|
|
|
it("slice can extend the buffer also", async () => {
|
|
const buffer = await ToneAudioBuffer.fromUrl(testFile);
|
|
// original duration
|
|
expect(buffer.duration).to.be.closeTo(3, 0.01);
|
|
const sliced = buffer.slice(0, 4);
|
|
expect(sliced.duration).to.be.closeTo(4, 0.01);
|
|
buffer.dispose();
|
|
sliced.dispose();
|
|
});
|
|
|
|
it("can convert a buffer to mono", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
const arr = [new Float32Array(0.5 * buffer.sampleRate), new Float32Array(0.5 * buffer.sampleRate)];
|
|
arr[0][0] = 0.5;
|
|
buffer.fromArray(arr);
|
|
expect(buffer.duration).to.equal(0.5);
|
|
expect(buffer.numberOfChannels).to.equal(2);
|
|
buffer.toMono();
|
|
expect(buffer.numberOfChannels).to.equal(1);
|
|
// should have averaged the two first samples
|
|
expect(buffer.toArray()[0]).to.equal(0.25);
|
|
buffer.dispose();
|
|
});
|
|
|
|
it("can use just the second channel of a buffer when making mono", () => {
|
|
const buffer = new ToneAudioBuffer();
|
|
const arr = [new Float32Array(0.5 * buffer.sampleRate), new Float32Array(0.5 * buffer.sampleRate)];
|
|
arr[0][0] = 0.5;
|
|
buffer.fromArray(arr);
|
|
expect(buffer.duration).to.equal(0.5);
|
|
expect(buffer.numberOfChannels).to.equal(2);
|
|
buffer.toMono(1);
|
|
expect(buffer.numberOfChannels).to.equal(1);
|
|
// should have averaged the two first samples
|
|
expect(buffer.toArray()[0]).to.equal(0);
|
|
buffer.dispose();
|
|
});
|
|
});
|
|
|
|
context("static methods", () => {
|
|
|
|
it("Test if the browser supports the given type", () => {
|
|
expect(ToneAudioBuffer.supportsType("test.wav")).to.equal(true);
|
|
expect(ToneAudioBuffer.supportsType("wav")).to.equal(true);
|
|
expect(ToneAudioBuffer.supportsType("path/to/test.wav")).to.equal(true);
|
|
expect(ToneAudioBuffer.supportsType("path/to/test.nope")).to.equal(false);
|
|
});
|
|
|
|
it("can be constructed with ToneAudioBuffer.fromUrl", done => {
|
|
ToneAudioBuffer.fromUrl("nosuchfile.wav").then(() => {
|
|
throw new Error("shouldn't invoke this function");
|
|
}).catch(() => {
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
context("ToneAudioBuffer.loaded()", () => {
|
|
it("returns a promise", () => {
|
|
expect(ToneAudioBuffer.loaded()).to.have.property("then");
|
|
});
|
|
|
|
it("is invoked when all the buffers are loaded", async () => {
|
|
const buff0 = new ToneAudioBuffer(testFile);
|
|
const buff1 = new ToneAudioBuffer(testFile);
|
|
await ToneAudioBuffer.loaded();
|
|
expect(buff0.loaded).to.equal(true);
|
|
expect(buff1.loaded).to.equal(true);
|
|
});
|
|
|
|
it("can be setup before the urls", async () => {
|
|
const loadedPromise = ToneAudioBuffer.loaded();
|
|
const buff0 = new ToneAudioBuffer(testFile);
|
|
const buff1 = new ToneAudioBuffer(testFile);
|
|
await loadedPromise;
|
|
expect(buff0.loaded).to.equal(true);
|
|
expect(buff1.loaded).to.equal(true);
|
|
});
|
|
|
|
it("invokes loaded even if there is an error", () => {
|
|
ToneAudioBuffer.fromUrl(testFile);
|
|
ToneAudioBuffer.fromUrl("nosuchfile.wav");
|
|
return ToneAudioBuffer.loaded();
|
|
});
|
|
});
|
|
});
|