UNPKG

12.3 kBPlain TextView Raw
1import { expect } from "chai";
2import { atTime, Offline } from "../../test/helper/Offline.js";
3import { ToneAudioBuffer } from "../core/context/ToneAudioBuffer.js";
4import { getContext } from "../core/Global.js";
5import { Player } from "./buffer/Player.js";
6import { Oscillator } from "./oscillator/Oscillator.js";
7
8describe("Source", () => {
9 it("can be started and stopped", () => {
10 const source = new Oscillator();
11 source.start(0);
12 source.stop(1);
13 source.dispose();
14 });
15
16 it("can be constructed with an options object", () => {
17 const source = new Oscillator({
18 volume: -20,
19 });
20 expect(source.volume.value).to.be.closeTo(-20, 0.1);
21 source.dispose();
22 });
23
24 it("can be muted in the constructor options", () => {
25 const source = new Oscillator({
26 mute: true,
27 });
28 expect(source.mute).to.be.true;
29 source.dispose();
30 });
31
32 it("can set the volume", () => {
33 const source = new Oscillator();
34 source.volume.value = -8;
35 expect(source.volume.value).to.be.closeTo(-8, 0.1);
36 source.dispose();
37 });
38
39 it("can mute and unmute the source", () => {
40 const source = new Oscillator();
41 source.volume.value = -8;
42 source.mute = true;
43 expect(source.mute).to.be.true;
44 expect(source.volume.value).to.equal(-Infinity);
45 source.mute = false;
46 // returns the volume to what it was
47 expect(source.volume.value).to.be.closeTo(-8, 0.1);
48 source.dispose();
49 });
50
51 it("can get and set values with an object", () => {
52 const source = new Oscillator();
53 source.set({ volume: -10 });
54 expect(source.get().volume).to.be.closeTo(-10, 0.1);
55 source.dispose();
56 });
57
58 it("is initally stopped", () => {
59 const source = new Oscillator();
60 expect(source.state).to.equal("stopped");
61 source.dispose();
62 });
63
64 it("cannot be scheduled to stop/start twice in a row", () => {
65 return Offline(() => {
66 const source = new Oscillator();
67 source.start(0).start(1);
68 source.stop(2).stop(3);
69 source.dispose();
70 });
71 });
72
73 it("can be scheduled with multiple starts/stops", () => {
74 return Offline(() => {
75 const source = new Oscillator();
76 source.start(0).stop(0.5).start(0.75).stop(1).start(1.25).stop(1.5);
77 return [
78 atTime(0.1, () => {
79 expect(source.state).to.equal("started");
80 }),
81 atTime(0.5, () => {
82 expect(source.state).to.equal("stopped");
83 }),
84 atTime(0.8, () => {
85 expect(source.state).to.equal("started");
86 }),
87 atTime(1, () => {
88 expect(source.state).to.equal("stopped");
89 }),
90 atTime(1.25, () => {
91 expect(source.state).to.equal("started");
92 }),
93 atTime(1.6, () => {
94 expect(source.state).to.equal("stopped");
95 }),
96 ];
97 }, 2);
98 });
99
100 it("clamps start time to the currentTime", (done) => {
101 const source = new Oscillator();
102 expect(source.state).to.equal("stopped");
103 source.start(0);
104 setTimeout(() => {
105 expect(source.state).to.equal("started");
106 source.dispose();
107 done();
108 }, 10);
109 });
110
111 it("clamps stop time to the currentTime", (done) => {
112 const source = new Oscillator();
113 expect(source.state).to.equal("stopped");
114 source.start(0);
115 setTimeout(() => {
116 expect(source.state).to.equal("started");
117 source.stop(0);
118 setTimeout(() => {
119 expect(source.state).to.equal("stopped");
120 source.dispose();
121 done();
122 }, 10);
123 }, 10);
124 });
125
126 it("correctly returns the scheduled play state", () => {
127 return Offline(() => {
128 const source = new Oscillator();
129 expect(source.state).to.equal("stopped");
130 source.start(0).stop(0.5);
131
132 return (time) => {
133 if (time >= 0 && time < 0.5) {
134 expect(source.state).to.equal("started");
135 } else if (time > 0.5) {
136 expect(source.state).to.equal("stopped");
137 }
138 };
139 }, 0.6);
140 });
141
142 it("start needs to be greater than the previous start time", () => {
143 return Offline(() => {
144 const source = new Oscillator();
145 source.start(0);
146 expect(() => {
147 source.start(0);
148 }).to.throw(Error);
149 source.dispose();
150 });
151 });
152
153 context("sync", () => {
154 const ramp = new Float32Array(getContext().sampleRate);
155 ramp.forEach((val, index) => {
156 ramp[index] = index / getContext().sampleRate;
157 });
158 const rampBuffer = ToneAudioBuffer.fromArray(ramp);
159
160 it("can sync its start to the transport", () => {
161 return Offline(({ transport }) => {
162 const source = new Oscillator();
163 source.sync().start(0);
164 expect(source.state).to.equal("stopped");
165 transport.start(source.now());
166 expect(source.state).to.equal("started");
167 source.dispose();
168 transport.stop();
169 });
170 });
171
172 it("calling sync multiple times has no affect", () => {
173 return Offline(({ transport }) => {
174 const source = new Oscillator();
175 source.sync().sync().start(0);
176 expect(source.state).to.equal("stopped");
177 transport.start(source.now());
178 expect(source.state).to.equal("started");
179 source.dispose();
180 transport.stop();
181 });
182 });
183
184 it("can unsync after it was synced", () => {
185 return Offline(({ transport }) => {
186 const source = new Oscillator();
187 source.sync().start(0);
188 source.unsync();
189 transport.start();
190 expect(source.state).to.equal("stopped");
191 });
192 });
193
194 it("calling unsync multiple times has no affect", () => {
195 return Offline(({ transport }) => {
196 const source = new Oscillator();
197 source.sync().start(0);
198 source.unsync().unsync();
199 transport.start();
200 expect(source.state).to.equal("stopped");
201 });
202 });
203
204 it("can sync its stop to the transport", () => {
205 return Offline(({ transport }) => {
206 const source = new Oscillator();
207 source.sync().start(0);
208 expect(source.state).to.equal("stopped");
209 transport.start(0).stop(0.4);
210 expect(source.state).to.equal("started");
211
212 return (time) => {
213 if (time > 0.4) {
214 expect(source.state).to.equal("stopped");
215 }
216 };
217 }, 0.5);
218 });
219
220 it("can schedule multiple starts/stops", () => {
221 return Offline(({ transport }) => {
222 const source = new Oscillator();
223 source.sync().start(0.1).stop(0.2).start(0.3);
224 transport.start(0).stop(0.4);
225 expect(source.state).to.equal("stopped");
226
227 return (time) => {
228 if (time > 0.1 && time < 0.19) {
229 expect(source.state).to.equal("started");
230 } else if (time > 0.2 && time < 0.29) {
231 expect(source.state).to.equal("stopped");
232 } else if (time > 0.3 && time < 0.39) {
233 expect(source.state).to.equal("started");
234 } else if (time > 0.4) {
235 expect(source.state).to.equal("stopped");
236 }
237 };
238 }, 0.6);
239 });
240
241 it.skip("can sync schedule multiple starts", () => {
242 return Offline(({ transport }) => {
243 const buff = ToneAudioBuffer.fromArray(
244 new Float32Array(1024).map((v) => 1)
245 );
246 const source = new Player(buff);
247 source.sync().start(0.1).start(0.3);
248 transport.start(0);
249 expect(source.state).to.equal("stopped");
250
251 return [
252 atTime(0.11, () => {
253 expect(source.state).to.equal("started");
254 }),
255 atTime(0.31, () => {
256 expect(source.state).to.equal("started");
257 }),
258 ];
259 }, 0.6);
260 });
261
262 it("has correct offset when the transport is started with an offset", () => {
263 return Offline(({ transport }) => {
264 const source = new Oscillator();
265 source.sync().start(0.3).stop(0.4);
266 transport.start(0, 0.1);
267 expect(source.state).to.equal("stopped");
268
269 return (time) => {
270 if (time > 0.21 && time < 0.29) {
271 expect(source.state).to.equal("started");
272 } else if (time > 0.31) {
273 expect(source.state).to.equal("stopped");
274 }
275 };
276 }, 0.5);
277 });
278
279 it("can start with an offset after the start time of the source", () => {
280 return Offline(({ transport }) => {
281 const source = new Oscillator();
282 source.sync().start(0);
283 transport.start(0, 0.1);
284 expect(source.state).to.equal("started");
285 source.dispose();
286 }, 0.1);
287 });
288
289 it("can sync its start to the transport after a delay", () => {
290 return Offline(({ transport }) => {
291 const source = new Oscillator();
292 source.sync().start(0.3);
293 transport.start(0).stop(0.4);
294 expect(source.state).to.equal("stopped");
295
296 return (time) => {
297 if (time > 0.3 && time < 0.39) {
298 expect(source.state).to.equal("started");
299 } else if (time > 0.4) {
300 expect(source.state).to.equal("stopped");
301 }
302 };
303 }, 0.6);
304 });
305
306 it("correct state when the transport position is changed", () => {
307 return Offline(({ transport }) => {
308 const source = new Oscillator();
309 source.sync().start(0.3).stop(0.4);
310 transport.start(0).stop(0.4);
311 expect(source.state).to.equal("stopped");
312 transport.seconds = 0.305;
313 expect(source.state).to.equal("started");
314 transport.seconds = 0.405;
315 return atTime(0.01, () => {
316 expect(source.state).to.equal("stopped");
317 });
318 }, 0.1);
319 });
320
321 it("gives the correct offset on time on start/stop events", () => {
322 return Offline(({ transport }) => {
323 const source = new Player(rampBuffer).toDestination();
324 source.sync().start(0.2, 0.1).stop(0.3);
325 transport.start(0.2);
326 }, 0.7).then((output) => {
327 expect(output.getValueAtTime(0.41)).to.be.closeTo(0.1, 0.01);
328 expect(output.getValueAtTime(0.45)).to.be.closeTo(0.15, 0.001);
329 expect(output.getValueAtTime(0.5)).to.be.equal(0);
330 });
331 });
332
333 it("gives the correct offset on time on start/stop events when started with an offset", () => {
334 return Offline(({ transport }) => {
335 const source = new Player(rampBuffer).toDestination();
336 source.sync().start(0.2, 0.1).stop(0.4);
337 transport.start(0.2, 0.1);
338 }, 0.7).then((output) => {
339 expect(output.getValueAtTime(0.21)).to.be.closeTo(0.0, 0.01);
340 expect(output.getValueAtTime(0.31)).to.be.closeTo(0.1, 0.01);
341 expect(output.getValueAtTime(0.41)).to.be.closeTo(0.2, 0.01);
342 expect(output.getValueAtTime(0.45)).to.be.closeTo(0.25, 0.01);
343 expect(output.getValueAtTime(0.51)).to.be.equal(0);
344 });
345 });
346
347 it("gives the correct offset on time on start/stop events invoked with an transport offset that's in the middle of the event", () => {
348 return Offline(({ transport }) => {
349 const source = new Player(rampBuffer).toDestination();
350 source.sync().start(0.2, 0.1).stop(0.4);
351 transport.start(0, 0.3);
352 }, 0.7).then((output) => {
353 expect(output.getValueAtTime(0.01)).to.be.closeTo(0.2, 0.01);
354 expect(output.getValueAtTime(0.05)).to.be.closeTo(0.25, 0.01);
355 expect(output.getValueAtTime(0.11)).to.be.equal(0);
356 });
357 });
358
359 it("gives the correct duration when invoked with an transport offset that's in the middle of the event", () => {
360 return Offline(({ transport }) => {
361 const source = new Player(rampBuffer).toDestination();
362 source.sync().start(0.2, 0.1, 0.3);
363 transport.start(0, 0.3);
364 }, 0.7).then((output) => {
365 expect(output.getValueAtTime(0.01)).to.be.closeTo(0.2, 0.01);
366 expect(output.getValueAtTime(0.1)).to.be.closeTo(0.3, 0.01);
367 expect(output.getValueAtTime(0.199)).to.be.closeTo(0.4, 0.01);
368 expect(output.getValueAtTime(0.31)).to.be.equal(0);
369 });
370 });
371
372 it("stops at the right time when transport.stop is invoked before the scheduled stop", () => {
373 return Offline(({ transport }) => {
374 const source = new Player(rampBuffer).toDestination();
375 source.sync().start(0.2).stop(0.4);
376 transport.start(0).stop(0.3);
377 }, 0.7).then((output) => {
378 expect(output.getValueAtTime(0.2)).to.be.closeTo(0.0, 0.01);
379 expect(output.getValueAtTime(0.25)).to.be.closeTo(0.05, 0.01);
380 expect(output.getValueAtTime(0.31)).to.be.equal(0);
381 });
382 });
383
384 it("invokes the right methods and offsets when the transport is seeked", () => {
385 return Offline(({ transport }) => {
386 const source = new Player(rampBuffer).toDestination();
387 source.sync().start(0.2);
388 transport.start(0, 0.3);
389
390 return atTime(0.1, () => {
391 // seek forward in time
392 transport.seconds = 0.1;
393 });
394 }, 0.7).then((output) => {
395 expect(output.getValueAtTime(0.01)).to.be.closeTo(0.1, 0.01);
396 expect(output.getValueAtTime(0.05)).to.be.closeTo(0.15, 0.01);
397 expect(output.getValueAtTime(0.11)).to.be.closeTo(0.0, 0.01);
398 expect(output.getValueAtTime(0.21)).to.be.closeTo(0.0, 0.01);
399 expect(output.getValueAtTime(0.25)).to.be.closeTo(0.05, 0.01);
400 expect(output.getValueAtTime(0.3)).to.be.closeTo(0.1, 0.01);
401 });
402 });
403 });
404});