Source code

Revision control

Copy as Markdown

Other Tools

// Use a power of two to eliminate round-off converting from frames to time.
let sampleRate = 32768;
// How many grains to play.
let numberOfTests = 100;
// Duration of each grain to be played. Make a whole number of frames
let duration = Math.floor(0.01 * sampleRate) / sampleRate;
// A little extra bit of silence between grain boundaries. Must be a whole
// number of frames.
let grainGap = Math.floor(0.005 * sampleRate) / sampleRate;
// Time step between the start of each grain. We need to add a little
// bit of silence so we can detect grain boundaries
let timeStep = duration + grainGap;
// Time step between the start for each grain. Must be a whole number of
// frames.
let grainOffsetStep = Math.floor(0.001 * sampleRate) / sampleRate;
// How long to render to cover all of the grains.
let renderTime = (numberOfTests + 1) * timeStep;
let context;
let renderedData;
// Create a buffer containing the data that we want. The function f
// returns the desired value at sample frame k.
function createSignalBuffer(context, f) {
// Make sure the buffer has enough data for all of the possible
// grain offsets and durations. The additional 1 is for any
// round-off errors.
let signalLength =
Math.floor(1 + sampleRate * (numberOfTests * grainOffsetStep + duration));
let buffer = context.createBuffer(2, signalLength, sampleRate);
let data = buffer.getChannelData(0);
for (let k = 0; k < signalLength; ++k) {
data[k] = f(k);
}
return buffer;
}
// From the data array, find the start and end sample frame for each
// grain. This depends on the data having 0's between grain, and
// that the grain is always strictly non-zero.
function findStartAndEndSamples(data) {
let nSamples = data.length;
let startTime = [];
let endTime = [];
let lookForStart = true;
// Look through the rendered data to find the start and stop
// times of each grain.
for (let k = 0; k < nSamples; ++k) {
if (lookForStart) {
// Find a non-zero point and record the start. We're not
// concerned with the value in this test, only that the
// grain started here.
if (renderedData[k]) {
startTime.push(k);
lookForStart = false;
}
} else {
// Find a zero and record the end of the grain.
if (!renderedData[k]) {
endTime.push(k);
lookForStart = true;
}
}
}
return {start: startTime, end: endTime};
}
function playGrain(context, source, time, offset, duration) {
let bufferSource = context.createBufferSource();
bufferSource.buffer = source;
bufferSource.connect(context.destination);
bufferSource.start(time, offset, duration);
}
// Play out all grains. Returns a object containing two arrays, one
// for the start time and one for the grain offset time.
function playAllGrains(context, source, numberOfNotes) {
let startTimes = new Array(numberOfNotes);
let offsets = new Array(numberOfNotes);
for (let k = 0; k < numberOfNotes; ++k) {
let timeOffset = k * timeStep;
let grainOffset = k * grainOffsetStep;
playGrain(context, source, timeOffset, grainOffset, duration);
startTimes[k] = timeOffset;
offsets[k] = grainOffset;
}
return {startTimes: startTimes, grainOffsetTimes: offsets};
}
// Verify that the start and end frames for each grain match our
// expected start and end frames.
function verifyStartAndEndFrames(startEndFrames, should) {
let startFrames = startEndFrames.start;
let endFrames = startEndFrames.end;
// Count of how many grains started at the incorrect time.
let errorCountStart = 0;
// Count of how many grains ended at the incorrect time.
let errorCountEnd = 0;
should(
startFrames.length == endFrames.length, 'Found all grain starts and ends')
.beTrue();
should(startFrames.length, 'Number of start frames').beEqualTo(numberOfTests);
should(endFrames.length, 'Number of end frames').beEqualTo(numberOfTests);
// Examine the start and stop times to see if they match our
// expectations.
for (let k = 0; k < startFrames.length; ++k) {
let expectedStart = timeToSampleFrame(k * timeStep, sampleRate);
// The end point is the duration.
let expectedEnd = expectedStart +
grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate);
if (startFrames[k] != expectedStart)
++errorCountStart;
if (endFrames[k] != expectedEnd)
++errorCountEnd;
should([startFrames[k], endFrames[k]], 'Pulse ' + k + ' boundary')
.beEqualToArray([expectedStart, expectedEnd]);
}
// Check that all the grains started or ended at the correct time.
if (!errorCountStart) {
should(
startFrames.length, 'Number of grains that started at the correct time')
.beEqualTo(numberOfTests);
} else {
should(
errorCountStart,
'Number of grains out of ' + numberOfTests +
'that started at the wrong time')
.beEqualTo(0);
}
if (!errorCountEnd) {
should(endFrames.length, 'Number of grains that ended at the correct time')
.beEqualTo(numberOfTests);
} else {
should(
errorCountEnd,
'Number of grains out of ' + numberOfTests +
' that ended at the wrong time')
.beEqualTo(0);
}
}