1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
function createTestBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(1, sampleFrameLength, context.sampleRate);
let channelData = audioBuffer.getChannelData(0);
// Create a simple linear ramp starting at zero, with each value in the buffer
// equal to its index position.
for (let i = 0; i < sampleFrameLength; ++i)
channelData[i] = i;
return audioBuffer;
}
function checkSingleTest(renderedBuffer, i, should) {
let renderedData = renderedBuffer.getChannelData(0);
let offsetFrame = i * testSpacingFrames;
let test = tests[i];
let expected = test.expected;
let description;
if (test.description) {
description = test.description;
} else {
// No description given, so create a basic one from the given test
// parameters.
description =
'loop from ' + test.loopStartFrame + ' -> ' + test.loopEndFrame;
if (test.offsetFrame)
description += ' with offset ' + test.offsetFrame;
if (test.playbackRate && test.playbackRate != 1)
description += ' with playbackRate of ' + test.playbackRate;
}
let framesToTest;
if (test.renderFrames)
framesToTest = test.renderFrames;
else if (test.durationFrames)
framesToTest = test.durationFrames;
// Verify that the output matches
let prefix = 'Case ' + i + ': ';
should(
renderedData.slice(offsetFrame, offsetFrame + framesToTest),
prefix + description)
.beEqualToArray(expected);
// Verify that we get all zeroes after the buffer (or duration) has passed.
should(
renderedData.slice(
offsetFrame + framesToTest, offsetFrame + testSpacingFrames),
prefix + description + ': tail')
.beConstantValueOf(0);
}
function checkAllTests(renderedBuffer, should) {
for (let i = 0; i < tests.length; ++i)
checkSingleTest(renderedBuffer, i, should);
}
// Create the actual result by modulating playbackRate or detune AudioParam of
// ABSN. |modTarget| is a string of AudioParam name, |modOffset| is the offset
// (anchor) point of modulation, and |modRange| is the range of modulation.
//
// createSawtoothWithModulation(context, 'detune', 440, 1200);
//
// The above will perform a modulation on detune within the range of
// [1200, -1200] around the sawtooth waveform on 440Hz.
function createSawtoothWithModulation(context, modTarget, modOffset, modRange) {
let lfo = context.createOscillator();
let amp = context.createGain();
// Create a sawtooth generator with the signal range of [0, 1].
let phasor = context.createBufferSource();
let phasorBuffer = context.createBuffer(1, sampleRate, sampleRate);
let phasorArray = phasorBuffer.getChannelData(0);
let phase = 0, phaseStep = 1 / sampleRate;
for (let i = 0; i < phasorArray.length; i++) {
phasorArray[i] = phase % 1.0;
phase += phaseStep;
}
phasor.buffer = phasorBuffer;
phasor.loop = true;
// 1Hz for audible (human-perceivable) parameter modulation by LFO.
lfo.frequency.value = 1.0;
amp.gain.value = modRange;
phasor.playbackRate.value = modOffset;
// The oscillator output should be amplified accordingly to drive the
// modulation within the desired range.
lfo.connect(amp);
amp.connect(phasor[modTarget]);
phasor.connect(context.destination);
lfo.start();
phasor.start();
}
|