Skip to content
Snippets Groups Projects
Commit d1d98337 authored by Paul Kaplan's avatar Paul Kaplan
Browse files

Update docs and tests

parent bb80fa49
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,7 @@ import {
computeChunkedRMS,
encodeAndAddSoundToVM,
downsampleIfNeeded,
backupDownSampler
dropEveryOtherSample
} from '../lib/audio/audio-util.js';
import AudioEffects from '../lib/audio/audio-effects.js';
import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx';
......@@ -138,7 +138,7 @@ class SoundEditor extends React.Component {
});
}
submitNewSamples (samples, sampleRate, skipUndo) {
return downsampleIfNeeded(samples, sampleRate, this.resampleBufferToRate)
return downsampleIfNeeded({samples, sampleRate}, this.resampleBufferToRate)
.then(({samples: newSamples, sampleRate: newSampleRate}) =>
WavEncoder.encode({
sampleRate: newSampleRate,
......@@ -208,9 +208,9 @@ class SoundEditor extends React.Component {
trimEnd: null
});
});
}
handleDeleteInverse () {
// Delete everything outside of the trimmers
const {samples, sampleRate} = this.copyCurrentBuffer();
const sampleCount = samples.length;
const startIndex = Math.floor(this.state.trimStart * sampleCount);
......@@ -331,19 +331,21 @@ class SoundEditor extends React.Component {
const sampleRateRatio = newRate / buffer.sampleRate;
const newLength = sampleRateRatio * buffer.samples.length;
let offlineContext;
if (window.OfflineAudioContext) {
offlineContext = new window.OfflineAudioContext(1, newLength, newRate);
} else if (window.webkitOfflineAudioContext) {
try {
// Try to use either OfflineAudioContext or webkitOfflineAudioContext to resample
// The constructors will throw if trying to resample at an unsupported rate
// (e.g. Safari/webkitOAC does not support lower than 44khz).
try {
if (window.OfflineAudioContext) {
offlineContext = new window.OfflineAudioContext(1, newLength, newRate);
} else if (window.webkitOfflineAudioContext) {
offlineContext = new window.webkitOfflineAudioContext(1, newLength, newRate);
} catch {
if (newRate === (buffer.sampleRate / 2)) {
return resolve(backupDownSampler(buffer, newRate));
}
return reject('Could not resample');
}
} else {
return reject('No offline audio context');
} catch {
// If no OAC available and downsampling by 2, downsample by dropping every other sample.
if (newRate === buffer.sampleRate / 2) {
return resolve(dropEveryOtherSample(buffer));
}
return reject('Could not resample');
}
const source = offlineContext.createBufferSource();
const audioBuffer = offlineContext.createBuffer(1, buffer.samples.length, buffer.sampleRate);
......
import WavEncoder from 'wav-encoder';
import log from '../log.js';
const SOUND_BYTE_LIMIT = 10 * 1000 * 1000; // 10mb
......@@ -60,7 +59,21 @@ const encodeAndAddSoundToVM = function (vm, samples, sampleRate, name, callback)
});
};
const downsampleIfNeeded = (samples, sampleRate, resampler) => {
/**
@typedef SoundBuffer
@type {Object}
@property {Float32Array} samples Array of audio samples
@property {number} sampleRate Audio sample rate
*/
/**
* Downsample the given buffer to try to reduce file size below SOUND_BYTE_LIMIT
* @param {SoundBuffer} buffer - Buffer to resample
* @param {function(SoundBuffer):Promise<SoundBuffer>} resampler - resampler function
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
*/
const downsampleIfNeeded = (buffer, resampler) => {
const {samples, sampleRate} = buffer;
const duration = samples.length / sampleRate;
const encodedByteLength = samples.length * 2; /* bitDepth 16 bit */
// Resolve immediately if already within byte limit
......@@ -76,8 +89,12 @@ const downsampleIfNeeded = (samples, sampleRate, resampler) => {
return Promise.reject('Sound too large to save, refusing to edit');
};
const backupDownSampler = (buffer, newRate) => {
log.warn(`Using backup down sampler for conversion from ${buffer.sampleRate} to ${newRate}`);
/**
* Drop every other sample of an audio buffer as a last-resort way of downsampling.
* @param {SoundBuffer} buffer - Buffer to resample
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
*/
const dropEveryOtherSample = buffer => {
const newLength = Math.floor(buffer.samples.length / 2);
const newSamples = new Float32Array(newLength);
for (let i = 0; i < newLength; i++) {
......@@ -85,7 +102,7 @@ const backupDownSampler = (buffer, newRate) => {
}
return {
samples: newSamples,
sampleRate: newRate
sampleRate: buffer.rate / 2
};
};
......@@ -94,5 +111,5 @@ export {
computeChunkedRMS,
encodeAndAddSoundToVM,
downsampleIfNeeded,
backupDownSampler
dropEveryOtherSample
};
......@@ -2,7 +2,7 @@ import {
computeRMS,
computeChunkedRMS,
downsampleIfNeeded,
backupDownSampler
dropEveryOtherSample
} from '../../../src/lib/audio/audio-util';
describe('computeRMS', () => {
......@@ -60,38 +60,38 @@ describe('downsampleIfNeeded', () => {
const sampleRate = 44100;
test('returns given data when no downsampling needed', async () => {
samples.length = 1;
const res = await downsampleIfNeeded(samples, sampleRate, null);
const res = await downsampleIfNeeded({samples, sampleRate}, null);
expect(res.samples).toEqual(samples);
expect(res.sampleRate).toEqual(sampleRate);
});
test('downsamples to 22050 if that puts it under the limit', async () => {
samples.length = 44100 * 3 * 60;
const resampler = jest.fn(() => 'TEST');
const res = await downsampleIfNeeded(samples, sampleRate, resampler);
const res = await downsampleIfNeeded({samples, sampleRate}, resampler);
expect(resampler).toHaveBeenCalledWith({samples, sampleRate}, 22050);
expect(res).toEqual('TEST');
});
test('fails if resampling would not put it under the limit', async () => {
samples.length = 44100 * 4 * 60;
try {
await downsampleIfNeeded(samples, sampleRate, null);
await downsampleIfNeeded({samples, sampleRate}, null);
} catch (e) {
expect(e).toEqual('Sound too large to save, refusing to edit');
}
});
});
describe('backupDownSampler', () => {
describe('dropEveryOtherSample', () => {
const buffer = {
samples: [1, 0, 1, 0, 1, 0, 1],
samples: [1, 0, 2, 0, 3, 0],
sampleRate: 2
};
test('result is half the length', () => {
const {samples} = backupDownSampler(buffer, 1);
const {samples} = dropEveryOtherSample(buffer, 1);
expect(samples.length).toEqual(Math.floor(buffer.samples.length / 2));
});
test('result contains only even-index items', () => {
const {samples} = backupDownSampler(buffer, 1);
expect(samples.every(v => v === 1)).toBe(true);
const {samples} = dropEveryOtherSample(buffer, 1);
expect(samples).toEqual(new Float32Array([1, 2, 3]));
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment