note-grain-on-testing.js (6904B)
1 // Use a power of two to eliminate round-off converting from frames to time. 2 let sampleRate = 32768; 3 4 // How many grains to play. 5 let numberOfTests = 100; 6 7 // Duration of each grain to be played. Make a whole number of frames 8 let duration = Math.floor(0.01 * sampleRate) / sampleRate; 9 10 // A little extra bit of silence between grain boundaries. Must be a whole 11 // number of frames. 12 let grainGap = Math.floor(0.005 * sampleRate) / sampleRate; 13 14 // Time step between the start of each grain. We need to add a little 15 // bit of silence so we can detect grain boundaries 16 let timeStep = duration + grainGap; 17 18 // Time step between the start for each grain. Must be a whole number of 19 // frames. 20 let grainOffsetStep = Math.floor(0.001 * sampleRate) / sampleRate; 21 22 // How long to render to cover all of the grains. 23 let renderTime = (numberOfTests + 1) * timeStep; 24 25 let context; 26 let renderedData; 27 28 // Create a buffer containing the data that we want. The function f 29 // returns the desired value at sample frame k. 30 function createSignalBuffer(context, f) { 31 // Make sure the buffer has enough data for all of the possible 32 // grain offsets and durations. The additional 1 is for any 33 // round-off errors. 34 let signalLength = 35 Math.floor(1 + sampleRate * (numberOfTests * grainOffsetStep + duration)); 36 37 let buffer = context.createBuffer(2, signalLength, sampleRate); 38 let data = buffer.getChannelData(0); 39 40 for (let k = 0; k < signalLength; ++k) { 41 data[k] = f(k); 42 } 43 44 return buffer; 45 } 46 47 // From the data array, find the start and end sample frame for each 48 // grain. This depends on the data having 0's between grain, and 49 // that the grain is always strictly non-zero. 50 function findStartAndEndSamples(data) { 51 let nSamples = data.length; 52 53 let startTime = []; 54 let endTime = []; 55 let lookForStart = true; 56 57 // Look through the rendered data to find the start and stop 58 // times of each grain. 59 for (let k = 0; k < nSamples; ++k) { 60 if (lookForStart) { 61 // Find a non-zero point and record the start. We're not 62 // concerned with the value in this test, only that the 63 // grain started here. 64 if (renderedData[k]) { 65 startTime.push(k); 66 lookForStart = false; 67 } 68 } else { 69 // Find a zero and record the end of the grain. 70 if (!renderedData[k]) { 71 endTime.push(k); 72 lookForStart = true; 73 } 74 } 75 } 76 77 return {start: startTime, end: endTime}; 78 } 79 80 function playGrain(context, source, time, offset, duration) { 81 let bufferSource = context.createBufferSource(); 82 83 bufferSource.buffer = source; 84 bufferSource.connect(context.destination); 85 bufferSource.start(time, offset, duration); 86 } 87 88 // Play out all grains. Returns a object containing two arrays, one 89 // for the start time and one for the grain offset time. 90 function playAllGrains(context, source, numberOfNotes) { 91 let startTimes = new Array(numberOfNotes); 92 let offsets = new Array(numberOfNotes); 93 94 for (let k = 0; k < numberOfNotes; ++k) { 95 let timeOffset = k * timeStep; 96 let grainOffset = k * grainOffsetStep; 97 98 playGrain(context, source, timeOffset, grainOffset, duration); 99 startTimes[k] = timeOffset; 100 offsets[k] = grainOffset; 101 } 102 103 return {startTimes: startTimes, grainOffsetTimes: offsets}; 104 } 105 106 // Verify that the start and end frames for each grain match our 107 // expected start and end frames. 108 function verifyStartAndEndFrames(startEndFrames, should) { 109 let startFrames = startEndFrames.start; 110 let endFrames = startEndFrames.end; 111 112 // Count of how many grains started at the incorrect time. 113 let errorCountStart = 0; 114 115 // Count of how many grains ended at the incorrect time. 116 let errorCountEnd = 0; 117 118 should( 119 startFrames.length == endFrames.length, 'Found all grain starts and ends') 120 .beTrue(); 121 122 should(startFrames.length, 'Number of start frames').beEqualTo(numberOfTests); 123 should(endFrames.length, 'Number of end frames').beEqualTo(numberOfTests); 124 125 // Examine the start and stop times to see if they match our 126 // expectations. 127 for (let k = 0; k < startFrames.length; ++k) { 128 let expectedStart = timeToSampleFrame(k * timeStep, sampleRate); 129 // The end point is the duration. 130 let expectedEnd = expectedStart + 131 grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate); 132 133 if (startFrames[k] != expectedStart) { 134 ++errorCountStart; 135 } 136 if (endFrames[k] != expectedEnd) { 137 ++errorCountEnd; 138 } 139 140 should([startFrames[k], endFrames[k]], 'Pulse ' + k + ' boundary') 141 .beEqualToArray([expectedStart, expectedEnd]); 142 } 143 144 // Check that all the grains started or ended at the correct time. 145 if (!errorCountStart) { 146 should( 147 startFrames.length, 'Number of grains that started at the correct time') 148 .beEqualTo(numberOfTests); 149 } else { 150 should( 151 errorCountStart, 152 'Number of grains out of ' + numberOfTests + 153 'that started at the wrong time') 154 .beEqualTo(0); 155 } 156 157 if (!errorCountEnd) { 158 should(endFrames.length, 'Number of grains that ended at the correct time') 159 .beEqualTo(numberOfTests); 160 } else { 161 should( 162 errorCountEnd, 163 'Number of grains out of ' + numberOfTests + 164 ' that ended at the wrong time') 165 .beEqualTo(0); 166 } 167 } 168 169 function verifyStartAndEndFrames_W3CTH(startEndFrames) { 170 const startFrames = startEndFrames.start; 171 const endFrames = startEndFrames.end; 172 173 let errorCountStart = 0; 174 let errorCountEnd = 0; 175 176 assert_equals( 177 startFrames.length, endFrames.length, 178 'Found all grain starts and ends'); 179 assert_equals(startFrames.length, numberOfTests, 'Number of start frames'); 180 assert_equals(endFrames.length, numberOfTests, 'Number of end frames'); 181 182 for (let k = 0; k < startFrames.length; ++k) { 183 const expectedStart = timeToSampleFrame(k * timeStep, sampleRate); 184 const expectedEnd = 185 expectedStart + grainLengthInSampleFrames( 186 k * grainOffsetStep, duration, sampleRate); 187 188 if (startFrames[k] !== expectedStart) { 189 ++errorCountStart; 190 } 191 if (endFrames[k] !== expectedEnd) { 192 ++errorCountEnd; 193 } 194 195 assert_array_equals( 196 [startFrames[k], endFrames[k]], 197 [expectedStart, expectedEnd], 198 'Pulse ' + k + ' boundary'); 199 } 200 201 if (!errorCountStart) { 202 assert_equals( 203 startFrames.length, numberOfTests, 204 'Number of grains that started at the correct time'); 205 } else { 206 assert_equals( 207 errorCountStart, 0, 208 `Number of grains out of ${numberOfTests} that ` + 209 `started at the wrong time`); 210 } 211 212 if (!errorCountEnd) { 213 assert_equals( 214 endFrames.length, numberOfTests, 215 'Number of grains that ended at the correct time'); 216 } else { 217 assert_equals( 218 errorCountEnd, 0, 219 `Number of grains out of ${numberOfTests} that ` + 220 `ended at the wrong time`); 221 } 222 }