Cut a sound to a stretch of its file in its own Sound tab (#343)
A sound asset now opens in a tab that shows its file as a waveform. Two handles set where the sound starts and ends in the file, and that stretch is the sample: Play sound, a song item and an instrument's Sample all play it and nothing else. The file itself is never changed. - The asset has three new rows: Start (s), End (s) and Edge fade (ms). End 0 is the end of the file, the fade is 10 ms by default. Older assets have no values and play the whole file as before. - Every way of playing asks playedSoundOf() for a name instead of the file: the file, or <file>#<start>~<end>~<fade> for a cut. - SoundLibrary makes the cut from the decoded file the first time that name plays and ramps each edge inside the file over the fade. Length and peaks of a cut are read from the file without a copy. - Cut buffers can be made again at any time, so the library lets the oldest go past 64 Mi samples. A voice holds its buffers itself. - The tab draws min/max per pixel from 256-sample block peaks, and the samples as a line when zoomed in far. Wheel zooms, the middle button pans, as in the music tabs. Wave, cut and ruler sit under a mask, so a handle panned out of view does not draw over the panels beside. - Dragging a handle moves that edge, dragging elsewhere spans a new stretch. One drag is one undo step for both edges. Whole file puts the handles back, Play or Space plays the stretch with a playhead. - The inspector preview and the Sound FX preview play the cut too, and the song lanes draw and repeat it. Tests: - Unit: sounds 20, wave-view 8 (new), sound-library 68, plus 258 in the touched areas (instrument, song, pattern, inspector, tabs) - e2e 34 on melektaus-atx: ALL PASSED, 57 checks (7 new) - Gate on melektaus-atx: CHECK EXIT 0, 253 files, 3936 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2fdf97f7de
commit
f1f4a57589
24 files changed
+1442
-53
No files matched your search
@@ -0,0 +1,44 @@
|
||||
import { findAssetOf } from '../../core/model/Project';
|
||||
import { playedSoundOf } from '../../core/properties/playedSound';
|
||||
import { soundLibrary } from '../../viewport/SoundLibrary';
|
||||
import { centreOf, type HookContext } from './hookHelpers';
|
||||
|
||||
/** The Sound tab (#343). */
|
||||
export function soundHooks({ state, editor }: HookContext) {
|
||||
const { soundEditor } = editor;
|
||||
return {
|
||||
soundTab: {
|
||||
current: () => soundEditor.currentAssetId,
|
||||
info: () => soundEditor.shownInfo,
|
||||
rect: () => soundEditor.canvasRect(),
|
||||
/** Where a second of the file is on screen, at the middle of the wave. */
|
||||
at: (seconds: number) => soundEditor.canvas.secondsToScreen(seconds),
|
||||
/** The stretch between the handles, in seconds; null while the file is not there. */
|
||||
span: () => soundEditor.canvas.shownSpan(),
|
||||
duration: () => soundEditor.canvas.fileDuration(),
|
||||
cursor: () => soundEditor.canvas.cursor,
|
||||
playhead: () => soundEditor.canvas.playhead,
|
||||
playing: () => soundEditor.isPlaying,
|
||||
playButton: () => ({ ...centreOf(soundEditor.playButton), text: soundEditor.playButton.text }),
|
||||
wholeButton: () => ({ ...centreOf(soundEditor.wholeButton), enabled: soundEditor.wholeButton.enabled }),
|
||||
fadeField: () => ({ ...centreOf(soundEditor.fadeField), text: soundEditor.fadeField.value }),
|
||||
/** What a sound asset is played by: its file, or the name of its cut. */
|
||||
played: (assetId: string) => {
|
||||
const asset = findAssetOf(state.project, assetId, 'sound');
|
||||
return asset ? playedSoundOf(state.project, asset) : null;
|
||||
},
|
||||
/** The samples a name plays, cut and faded: their count, rate, and the first and last few of channel 0. */
|
||||
samples: async (name: string) => {
|
||||
const buffer = await soundLibrary.load(name);
|
||||
if (!buffer) return null;
|
||||
const data = buffer.getChannelData(0);
|
||||
return {
|
||||
length: buffer.length,
|
||||
rate: buffer.sampleRate,
|
||||
head: Array.from(data.slice(0, 4)),
|
||||
tail: Array.from(data.slice(-4)),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user