1 |
|
2 | const tap = require('tap');
|
3 | const {AudioContext} = require('web-audio-test-api');
|
4 |
|
5 | const AudioEngine = require('../src/AudioEngine');
|
6 |
|
7 |
|
8 | tap.test('SoundPlayer', suite => {
|
9 |
|
10 | let audioContext;
|
11 | let audioEngine;
|
12 | let soundPlayer;
|
13 |
|
14 | const help = {
|
15 | get engineInputs () {
|
16 | return audioEngine.inputNode.toJSON().inputs;
|
17 | }
|
18 | };
|
19 |
|
20 | suite.beforeEach(() => {
|
21 | audioContext = new AudioContext();
|
22 | audioEngine = new AudioEngine(audioContext);
|
23 |
|
24 | audioContext.DECODE_AUDIO_DATA_RESULT = audioContext.createBuffer(2, 8820, 44100);
|
25 | audioContext.DECODE_AUDIO_DATA_FAILED = false;
|
26 | const data = new Uint8Array(0);
|
27 | return audioEngine.decodeSoundPlayer({data}).then(result => {
|
28 | soundPlayer = result;
|
29 | });
|
30 | });
|
31 |
|
32 | suite.afterEach(() => {
|
33 | soundPlayer.dispose();
|
34 | soundPlayer = null;
|
35 | audioEngine = null;
|
36 | audioContext.$reset();
|
37 | audioContext = null;
|
38 | });
|
39 |
|
40 | suite.plan(5);
|
41 |
|
42 | suite.test('play initializes and creates source node', t => {
|
43 | t.plan(3);
|
44 | t.equal(soundPlayer.initialized, false, 'not yet initialized');
|
45 | soundPlayer.play();
|
46 | t.equal(soundPlayer.initialized, true, 'now is initialized');
|
47 | t.deepEqual(soundPlayer.outputNode.toJSON(), {
|
48 | buffer: audioContext.DECODE_AUDIO_DATA_RESULT.toJSON(),
|
49 | inputs: [],
|
50 | loop: false,
|
51 | loopEnd: 0,
|
52 | loopStart: 0,
|
53 | name: 'AudioBufferSourceNode',
|
54 | playbackRate: {
|
55 | inputs: [],
|
56 | value: 1
|
57 | }
|
58 | });
|
59 |
|
60 | t.end();
|
61 | });
|
62 |
|
63 | suite.test('connect', t => {
|
64 | t.plan(1);
|
65 | soundPlayer.play();
|
66 | soundPlayer.connect(audioEngine);
|
67 | t.deepEqual(help.engineInputs, [
|
68 | soundPlayer.outputNode.toJSON()
|
69 | ], 'output node connects to input node');
|
70 | t.end();
|
71 | });
|
72 |
|
73 | suite.test('stop decay', t => {
|
74 | t.plan(5);
|
75 | soundPlayer.play();
|
76 | soundPlayer.connect(audioEngine);
|
77 | const outputNode = soundPlayer.outputNode;
|
78 |
|
79 | audioContext.$processTo(0);
|
80 | soundPlayer.stop();
|
81 | t.equal(soundPlayer.outputNode, null, 'nullify outputNode immediately (taken sound is stopping)');
|
82 | t.deepEqual(help.engineInputs, [{
|
83 | name: 'GainNode',
|
84 | gain: {
|
85 | value: 1,
|
86 | inputs: []
|
87 | },
|
88 | inputs: [outputNode.toJSON()]
|
89 | }], 'output node connects to gain node to input node');
|
90 |
|
91 | audioContext.$processTo(audioEngine.DECAY_DURATION / 2);
|
92 | t.equal(outputNode.$state, 'PLAYING');
|
93 |
|
94 | audioContext.$processTo(audioEngine.DECAY_DURATION + 0.001);
|
95 | t.deepEqual(help.engineInputs, [{
|
96 | name: 'GainNode',
|
97 | gain: {
|
98 | value: 0,
|
99 | inputs: []
|
100 | },
|
101 | inputs: [outputNode.toJSON()]
|
102 | }], 'output node connects to gain node to input node decayed');
|
103 |
|
104 | t.equal(outputNode.$state, 'FINISHED');
|
105 |
|
106 | t.end();
|
107 | });
|
108 |
|
109 | suite.test('play while playing debounces', t => {
|
110 | t.plan(7);
|
111 | const log = [];
|
112 | soundPlayer.connect(audioEngine);
|
113 | soundPlayer.play();
|
114 | t.equal(soundPlayer.isStarting, true, 'player.isStarting');
|
115 | const originalNode = soundPlayer.outputNode;
|
116 |
|
117 | soundPlayer.finished().then(() => log.push('finished first'));
|
118 | soundPlayer.play();
|
119 | soundPlayer.finished().then(() => log.push('finished second'));
|
120 | soundPlayer.play();
|
121 | soundPlayer.finished().then(() => log.push('finished third'));
|
122 | soundPlayer.play();
|
123 | t.equal(originalNode, soundPlayer.outputNode, 'same output node');
|
124 | t.equal(soundPlayer.outputNode.$state, 'PLAYING');
|
125 | return Promise.resolve().then(() => {
|
126 | t.deepEqual(log, ['finished first', 'finished second', 'finished third'], 'finished in order');
|
127 |
|
128 |
|
129 | audioContext.$processTo(audioEngine.DECAY_DURATION - 0.001);
|
130 | soundPlayer.play();
|
131 |
|
132 | t.equal(originalNode, soundPlayer.outputNode, 'same output node');
|
133 |
|
134 |
|
135 |
|
136 | audioContext.$processTo(audioEngine.DECAY_DURATION);
|
137 |
|
138 | t.equal(soundPlayer.isStarting, false, 'player.isStarting now false');
|
139 |
|
140 | soundPlayer.play();
|
141 | t.notEqual(originalNode, soundPlayer.outputNode, 'New output node');
|
142 |
|
143 | t.end();
|
144 | });
|
145 |
|
146 | });
|
147 |
|
148 | suite.test('play while playing', t => {
|
149 | t.plan(15);
|
150 | const log = [];
|
151 | soundPlayer.play();
|
152 | soundPlayer.finished().then(() => log.push('play 1 finished'));
|
153 | soundPlayer.connect(audioEngine);
|
154 | const firstPlayNode = soundPlayer.outputNode;
|
155 |
|
156 |
|
157 | audioContext.$processTo(audioEngine.DECAY_DURATION);
|
158 |
|
159 | return Promise.resolve()
|
160 | .then(() => {
|
161 |
|
162 | t.equal(soundPlayer.outputNode.$state, 'PLAYING');
|
163 |
|
164 | soundPlayer.play();
|
165 | soundPlayer.finished().then(() => log.push('play 2 finished'));
|
166 |
|
167 |
|
168 | return Promise.resolve();
|
169 | })
|
170 | .then(() => {
|
171 |
|
172 | t.equal(log[0], 'play 1 finished');
|
173 | t.notEqual(soundPlayer.outputNode, firstPlayNode, 'created new player node');
|
174 |
|
175 | t.equal(help.engineInputs.length, 2, 'there should be 2 players connected');
|
176 | t.equal(firstPlayNode.$state, 'PLAYING');
|
177 | t.equal(soundPlayer.outputNode.$state, 'PLAYING');
|
178 | t.equal(help.engineInputs[0].gain.value, 1, 'old sound connectect to gain node with volume 1');
|
179 |
|
180 | const {currentTime} = audioContext;
|
181 | audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + 0.001);
|
182 | t.notEqual(help.engineInputs[0].gain.value, 1,
|
183 | 'old sound connected to gain node which will fade');
|
184 |
|
185 | audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + audioEngine.DECAY_DURATION + 0.001);
|
186 | t.equal(soundPlayer.outputNode.$state, 'PLAYING');
|
187 | t.equal(firstPlayNode.$state, 'FINISHED');
|
188 |
|
189 | t.equal(help.engineInputs[0].gain.value, 0, 'faded old sound to 0');
|
190 |
|
191 | t.equal(log.length, 1);
|
192 | audioContext.$processTo(currentTime + audioEngine.DECAY_WAIT + audioEngine.DECAY_DURATION + 0.3);
|
193 |
|
194 |
|
195 | return Promise.resolve();
|
196 | })
|
197 | .then(() => {
|
198 |
|
199 | t.equal(log[1], 'play 2 finished');
|
200 | t.equal(help.engineInputs.length, 1, 'old sound disconneted itself after done');
|
201 | t.equal(log.length, 2);
|
202 |
|
203 | t.end();
|
204 | });
|
205 | });
|
206 |
|
207 | suite.end();
|
208 | });
|