| 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(); |
| } |