/** * The Sound tab's view (#343): seconds across, zoomed about the pointer, and the stretch of the * file between two handles – how a drag moves it and what the asset then stores. */ import { describe, expect, it } from 'vitest'; import { clampWave, draggedSpan, edgeAt, fitWave, rulerLabel, rulerStep, secondsToX, spanOf, spanPeak, storedCut, wavePeaks, xToSeconds, zoomWave, } from '../../src/editor/sound/waveView'; describe('the wave view', () => { it('zooms about the pointer and fits the file with a margin either side', () => { const view = { panX: 10, pxPerSecond: 100 }; const zoomed = zoomWave(view, 2, 210); expect(xToSeconds(zoomed, 210)).toBeCloseTo(xToSeconds(view, 210)); expect(zoomed.pxPerSecond).toBe(200); const fitted = fitWave(4, 832, 16); expect(secondsToX(fitted, 0)).toBe(16); expect(secondsToX(fitted, 4)).toBe(816); }); it('keeps either end of the file from going further in than half the width', () => { expect(clampWave({ panX: 900, pxPerSecond: 100 }, 20, 1000).panX).toBe(500); expect(clampWave({ panX: -1600, pxPerSecond: 100 }, 20, 1000).panX).toBe(-1500); expect(clampWave({ panX: -900, pxPerSecond: 100 }, 20, 1000).panX).toBe(-900); // Two seconds fit: they stay on screen. expect(clampWave({ panX: 20, pxPerSecond: 100 }, 2, 1000).panX).toBe(20); expect(clampWave({ panX: -50, pxPerSecond: 100 }, 2, 1000).panX).toBe(0); }); it('labels the ruler in steps of 1, 2 or 5, with the decimals the step needs', () => { expect(rulerStep(100, 70)).toBe(1); expect(rulerStep(40, 70)).toBe(2); expect(rulerStep(1000, 70)).toBeCloseTo(0.1); expect(rulerStep(10, 70)).toBe(10); expect(rulerLabel(1.5, 0.5)).toBe('1.5s'); expect(rulerLabel(2, 1)).toBe('2s'); expect(rulerLabel(0.25, 0.05)).toBe('0.25s'); expect(rulerLabel(75, 5)).toBe('1:15'); }); }); describe('the cut span', () => { const view = { panX: 0, pxPerSecond: 100 }; it('reads an end of 0 or past the file as its end', () => { expect(spanOf(0, 0, 3)).toEqual({ start: 0, end: 3 }); expect(spanOf(1, 9, 3)).toEqual({ start: 1, end: 3 }); expect(spanOf(2.5, 1, 3)).toEqual({ start: 1, end: 1 }); }); it('finds the handle in reach, the nearer one where both are', () => { const span = { start: 1, end: 2 }; expect(edgeAt(view, span, 103)).toBe('start'); expect(edgeAt(view, span, 197)).toBe('end'); expect(edgeAt(view, span, 150)).toBeNull(); expect(edgeAt(view, { start: 1, end: 1.04 }, 103)).toBe('end'); }); it('moves one edge, never past the other, or spans a new stretch inside the file', () => { const span = { start: 1, end: 2 }; expect(draggedSpan(span, 'start', 1, 0.5, 3, 0.001)).toEqual({ start: 0.5, end: 2 }); expect(draggedSpan(span, 'start', 1, 2.5, 3, 0.001)).toEqual({ start: 1.999, end: 2 }); expect(draggedSpan(span, 'end', 2, 9, 3, 0.001)).toEqual({ start: 1, end: 3 }); expect(draggedSpan(span, null, 2.5, 0.5, 3, 0.001)).toEqual({ start: 0.5, end: 2.5 }); expect(draggedSpan(span, null, -1, 1, 3, 0.001)).toEqual({ start: 0, end: 1 }); }); it('stores the ends of the file as 0, everything else to the sample', () => { expect(storedCut({ start: 0, end: 3 }, 3, 48000)).toEqual({ start: 0, end: 0 }); expect(storedCut({ start: 0.000001, end: 2.999999 }, 3, 48000)).toEqual({ start: 0, end: 0 }); expect(storedCut({ start: 1.2345678, end: 2 }, 3, 48000)).toEqual({ start: 1.234563, end: 2 }); }); }); describe('the wave peaks', () => { it('reads the lowest and highest sample of any stretch, over the blocks and the samples at its ends', () => { const left = new Float32Array(1000); const right = new Float32Array(1000); left[10] = 0.5; right[600] = -0.75; left[999] = 0.9; const peaks = wavePeaks([left, right]); expect(peaks.max).toHaveLength(4); expect(spanPeak([left, right], peaks, 0, 1000)).toEqual({ min: -0.75, max: Math.fround(0.9) }); expect(spanPeak([left, right], peaks, 5, 20)).toEqual({ min: 0, max: 0.5 }); expect(spanPeak([left, right], peaks, 11, 599)).toEqual({ min: 0, max: 0 }); expect(spanPeak([left, right], peaks, 300.2, 300.4)).toEqual({ min: 0, max: 0 }); }); });