scratch-audio/test/SoundPlayer.js

209 lines
7 KiB
JavaScript
Raw Normal View History

2018-06-19 15:09:03 -04:00
/* global Uint8Array Promise */
2018-06-19 13:37:00 -04:00
const tap = require('tap');
const {AudioContext} = require('web-audio-test-api');
const AudioEngine = require('../src/AudioEngine');
tap.test('SoundPlayer', suite => {
let audioContext;
let audioEngine;
let soundPlayer;
2018-06-19 15:09:03 -04:00
const help = {
get engineInputs () {
return audioEngine.inputNode.toJSON().inputs;
}
};
2018-06-20 15:13:05 -04:00
suite.beforeEach(() => {
2018-06-19 13:37:00 -04:00
audioContext = new AudioContext();
audioEngine = new AudioEngine(audioContext);
// sound will be 0.2 seconds long
audioContext.DECODE_AUDIO_DATA_RESULT = audioContext.createBuffer(2, 8820, 44100);
2018-06-19 15:09:03 -04:00
audioContext.DECODE_AUDIO_DATA_FAILED = false;
const data = new Uint8Array(0);
2018-06-20 15:13:05 -04:00
return audioEngine.decodeSoundPlayer({data}).then(result => {
soundPlayer = result;
});
2018-06-19 13:37:00 -04:00
});
suite.afterEach(() => {
soundPlayer.dispose();
soundPlayer = null;
audioEngine = null;
audioContext.$reset();
audioContext = null;
});
2018-06-21 15:05:08 -04:00
suite.plan(5);
2018-06-19 13:37:00 -04:00
2018-06-21 11:25:27 -04:00
suite.test('play initializes and creates source node', t => {
2018-06-19 13:37:00 -04:00
t.plan(3);
t.equal(soundPlayer.initialized, false, 'not yet initialized');
soundPlayer.play();
t.equal(soundPlayer.initialized, true, 'now is initialized');
t.deepEqual(soundPlayer.outputNode.toJSON(), {
2018-06-21 11:25:27 -04:00
buffer: audioContext.DECODE_AUDIO_DATA_RESULT.toJSON(),
2018-06-19 13:37:00 -04:00
inputs: [],
loop: false,
loopEnd: 0,
loopStart: 0,
name: 'AudioBufferSourceNode',
playbackRate: {
inputs: [],
value: 1
}
});
t.end();
});
suite.test('connect', t => {
t.plan(1);
soundPlayer.play();
soundPlayer.connect(audioEngine);
2018-06-21 11:25:27 -04:00
t.deepEqual(help.engineInputs, [
2018-06-19 13:37:00 -04:00
soundPlayer.outputNode.toJSON()
], 'output node connects to input node');
t.end();
});
suite.test('stop decay', t => {
t.plan(5);
2018-06-19 13:37:00 -04:00
soundPlayer.play();
soundPlayer.connect(audioEngine);
const outputNode = soundPlayer.outputNode;
2018-06-19 13:37:00 -04:00
audioContext.$processTo(0);
soundPlayer.stop();
t.equal(soundPlayer.outputNode, null, 'nullify outputNode immediately (taken sound is stopping)');
2018-06-21 11:25:27 -04:00
t.deepEqual(help.engineInputs, [{
2018-06-19 13:37:00 -04:00
name: 'GainNode',
gain: {
value: 1,
inputs: []
},
inputs: [outputNode.toJSON()]
2018-06-19 13:37:00 -04:00
}], 'output node connects to gain node to input node');
audioContext.$processTo(audioEngine.DECAY_DURATION / 2);
t.equal(outputNode.$state, 'PLAYING');
2018-06-19 13:37:00 -04:00
audioContext.$processTo(audioEngine.DECAY_DURATION + 0.001);
2018-06-21 11:25:27 -04:00
t.deepEqual(help.engineInputs, [{
2018-06-19 13:37:00 -04:00
name: 'GainNode',
gain: {
value: 0,
inputs: []
},
inputs: [outputNode.toJSON()]
2018-06-19 13:37:00 -04:00
}], 'output node connects to gain node to input node decayed');
t.equal(outputNode.$state, 'FINISHED');
2018-06-19 13:37:00 -04:00
t.end();
});
2018-06-21 15:05:08 -04:00
suite.test('play while playing debounces', t => {
t.plan(7);
const log = [];
soundPlayer.connect(audioEngine);
soundPlayer.play();
t.equal(soundPlayer.isStarting, true, 'player.isStarting');
const originalNode = soundPlayer.outputNode;
// the second play should still "finish" this play
soundPlayer.finished().then(() => log.push('finished first'));
soundPlayer.play();
soundPlayer.finished().then(() => log.push('finished second'));
soundPlayer.play();
soundPlayer.finished().then(() => log.push('finished third'));
soundPlayer.play();
t.equal(originalNode, soundPlayer.outputNode, 'same output node');
t.equal(soundPlayer.outputNode.$state, 'PLAYING');
return Promise.resolve().then(() => {
t.deepEqual(log, ['finished first', 'finished second', 'finished third'], 'finished in order');
// fast forward to one ms before decay time
audioContext.$processTo(audioEngine.DECAY_DURATION - 0.001);
2018-06-21 15:05:08 -04:00
soundPlayer.play();
t.equal(originalNode, soundPlayer.outputNode, 'same output node');
// now at DECAY_DURATION, we should meet a new player as the old one is taken/stopped
audioContext.$processTo(audioEngine.DECAY_DURATION);
2018-06-21 15:05:08 -04:00
t.equal(soundPlayer.isStarting, false, 'player.isStarting now false');
soundPlayer.play();
t.notEqual(originalNode, soundPlayer.outputNode, 'New output node');
t.end();
});
});
2018-06-20 14:51:48 -04:00
suite.test('play while playing', t => {
t.plan(15);
2018-06-19 15:09:03 -04:00
const log = [];
soundPlayer.play();
soundPlayer.finished().then(() => log.push('play 1 finished'));
soundPlayer.connect(audioEngine);
2018-06-20 15:13:05 -04:00
const firstPlayNode = soundPlayer.outputNode;
2018-06-21 15:05:08 -04:00
// go past debounce time and play again
audioContext.$processTo(audioEngine.DECAY_DURATION);
2018-06-19 15:09:03 -04:00
2018-06-20 14:51:48 -04:00
return Promise.resolve()
2023-12-15 17:44:01 -05:00
.then(() => {
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(soundPlayer.outputNode.$state, 'PLAYING');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
soundPlayer.play();
soundPlayer.finished().then(() => log.push('play 2 finished'));
2018-06-20 14:51:48 -04:00
2023-12-15 17:44:01 -05:00
// wait for a micro-task loop to fire our previous events
return Promise.resolve();
})
.then(() => {
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(log[0], 'play 1 finished');
t.notEqual(soundPlayer.outputNode, firstPlayNode, 'created new player node');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(help.engineInputs.length, 2, 'there should be 2 players connected');
t.equal(firstPlayNode.$state, 'PLAYING');
t.equal(soundPlayer.outputNode.$state, 'PLAYING');
t.equal(help.engineInputs[0].gain.value, 1, 'old sound connectect to gain node with volume 1');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
const {currentTime} = audioContext;
audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + 0.001);
t.notEqual(help.engineInputs[0].gain.value, 1,
'old sound connected to gain node which will fade');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + audioEngine.DECAY_DURATION + 0.001);
t.equal(soundPlayer.outputNode.$state, 'PLAYING');
t.equal(firstPlayNode.$state, 'FINISHED');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(help.engineInputs[0].gain.value, 0, 'faded old sound to 0');
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(log.length, 1);
audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + audioEngine.DECAY_DURATION + 0.3);
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
// wait for a micro-task loop to fire our previous events
return Promise.resolve();
})
.then(() => {
2018-06-19 15:09:03 -04:00
2023-12-15 17:44:01 -05:00
t.equal(log[1], 'play 2 finished');
t.equal(help.engineInputs.length, 1, 'old sound disconneted itself after done');
t.equal(log.length, 2);
2018-06-20 14:51:48 -04:00
2023-12-15 17:44:01 -05:00
t.end();
});
2018-06-19 15:09:03 -04:00
});
2018-06-19 13:37:00 -04:00
suite.end();
});