Files
feuerwerk/tests/editor/wave-view.test.ts
T
adminandClaude Opus 5.5 852dce5142 Shrink a clip with the wave and drop it with a click beside (#357)
Zoomed out, the wave left room above and below it, but the bright file,
the picked clip and its handles still took the whole height. And a click
in the wave beside the picked clip kept it picked, so only the strip
could take it back to the sound.

- fileReach in waveView.ts is how far up and down the file reaches:
  full scale and its inset, never past the area. At level 1 it is the
  whole height, zoomed in too.
- The bright file, the shade outside the clip and the two handles are
  drawn within that reach.
- A click over the wave beside the picked clip picks the sound. Inside
  the clip or on a handle nothing changes, and a drag still spans the
  clip anew.

Tests:
- Unit wave-view: 11 green (1 new), a counter-check red
- e2e 34 (melektaus-atx): ALL PASSED, 91 checks (1 new)
- Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4195 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:13:09 +02:00

136 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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,
fileReach,
fitWave,
levelMarks,
MAX_LEVEL,
MIN_LEVEL,
rulerLabel,
rulerStep,
secondsToX,
spanOf,
spanPeak,
storedCut,
wavePeaks,
xToSeconds,
zoomLevel,
zoomWave,
} from '../../src/editor/sound/waveView';
describe('the height of the wave (#357)', () => {
it('zooms between a quarter and sixteen times full scale', () => {
expect(zoomLevel(1, 2)).toBe(2);
expect(zoomLevel(1, 0.5)).toBe(0.5);
expect(zoomLevel(0.3, 0.5)).toBe(MIN_LEVEL);
expect(zoomLevel(12, 2)).toBe(MAX_LEVEL);
});
it('marks every 6 dB that lies inside the area and not too close to the middle', () => {
// Level 1, full scale 100 px from the middle: 0 dB at 100, -6 at 50, -12 at 25, -18 at 12.6.
const marks = levelMarks(1, 100, 110, 14);
expect(marks.map((m) => m.db)).toEqual([0, -6, -12]);
expect(marks[1]!.offset).toBeCloseTo(50.1, 1);
// Zoomed out, -6 dB lies 12.5 px from the middle and is left out.
expect(levelMarks(0.25, 100, 110, 14).map((m) => m.db)).toEqual([0]);
// Zoomed in, 0 dB lies past the edge and the quiet ones show.
const loud = levelMarks(16, 100, 110, 14).map((m) => m.db);
expect(loud[0]).toBe(-24);
expect(loud).not.toContain(0);
});
it('lets the file and its clip reach as high as full scale zoomed out, never past the area', () => {
// Full scale 100 px from the middle at level 1, 6 px inset, 106 px of room.
expect(fileReach(1, 100, 6, 106)).toBe(106);
expect(fileReach(0.25, 100, 6, 106)).toBe(31);
expect(fileReach(4, 100, 6, 106)).toBe(106);
});
});
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 });
});
});