import { computeRMS, computeChunkedRMS, downsampleIfNeeded, dropEveryOtherSample } from '../../../src/lib/audio/audio-util'; describe('computeRMS', () => { test('returns 0 when given no samples', () => { expect(computeRMS([])).toEqual(0); }); test('returns the RMS scaled by the given unity value and square rooted', () => { const unity = 0.5; const samples = [3, 2, 1]; expect(computeRMS(samples, unity)).toEqual( Math.sqrt(Math.sqrt(((3 * 3) + (2 * 2) + (1 * 1)) / 3) / 0.5) ); }); test('uses a default unity value of 0.55', () => { const samples = [1, 1, 1]; // raw rms is 1, scaled to (1 / 0.55) and square rooted expect(computeRMS(samples)).toEqual(Math.sqrt(1 / 0.55)); }); }); describe('computeChunkedRMS', () => { test('computes the rms for each chunk based on chunk size', () => { const samples = [2, 1, 3, 2, 5]; const chunkedLevels = computeChunkedRMS(samples, 2); // chunked to [2, 0], [3, 0], [5] // rms scaled with default unity of 0.55 expect(chunkedLevels.length).toEqual(3); expect(chunkedLevels).toEqual([ Math.sqrt(Math.sqrt(((2 * 2) + (1 * 1)) / 2) / 0.55), Math.sqrt(Math.sqrt(((3 * 3) + (2 * 2)) / 2) / 0.55), Math.sqrt(Math.sqrt((5 * 5) / 1) / 0.55) ]); }); test('chunk size larger than sample size creates single chunk', () => { const samples = [1, 1, 1]; const chunkedLevels = computeChunkedRMS(samples, 7); // chunked to [1, 1, 1] // rms scaled with default unity of 0.55 expect(chunkedLevels.length).toEqual(1); expect(chunkedLevels).toEqual([Math.sqrt(1 / 0.55)]); }); test('chunk size as multiple is handled correctly', () => { const samples = [1, 1, 1, 1, 1, 1]; const chunkedLevels = computeChunkedRMS(samples, 3); // chunked to [1, 1, 1], [1, 1, 1] // rms scaled with default unity of 0.55 expect(chunkedLevels.length).toEqual(2); expect(chunkedLevels).toEqual([Math.sqrt(1 / 0.55), Math.sqrt(1 / 0.55)]); }); }); describe('downsampleIfNeeded', () => { const samples = {length: 1}; const sampleRate = 44100; test('returns given data when no downsampling needed', async () => { samples.length = 1; 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); expect(resampler).toHaveBeenCalledWith({samples, sampleRate}, 22050); expect(res).toEqual('TEST'); }); // TW: We allow resampling even if it would exceed the limit because our GUI handles this better. test.skip('fails if resampling would not put it under the limit', async () => { samples.length = 44100 * 4 * 60; try { await downsampleIfNeeded({samples, sampleRate}, null); } catch (e) { expect(e.message).toEqual('Sound too large to save, refusing to edit'); } }); }); describe('dropEveryOtherSample', () => { const buffer = { samples: [1, 0, 2, 0, 3, 0], sampleRate: 2 }; test('result is half the length', () => { const {samples} = dropEveryOtherSample(buffer); expect(samples.length).toEqual(Math.floor(buffer.samples.length / 2)); }); test('result contains only even-index items', () => { const {samples} = dropEveryOtherSample(buffer); expect(samples).toEqual(new Float32Array([1, 2, 3])); }); test('result sampleRate is given sampleRate / 2', () => { const {sampleRate} = dropEveryOtherSample(buffer); expect(sampleRate).toEqual(buffer.sampleRate / 2); }); });