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
+10
-1
@@ -84,6 +84,7 @@ import { copySelection } from './clipboard';
|
||||
import { PixelArtEditor } from './pixelart/PixelArtEditor';
|
||||
import { VectorArtEditor } from './vectorart/VectorArtEditor';
|
||||
import { CurveEditor } from './curve/CurveEditor';
|
||||
import { SoundEditor } from './sound/SoundEditor';
|
||||
import { TextureEditor } from './texture/TextureEditor';
|
||||
import { installFileImport } from './fileImport';
|
||||
import type { PluginManager } from './plugins/loader';
|
||||
@@ -97,6 +98,7 @@ export type CentreTab =
|
||||
| 'statemachine'
|
||||
| 'shader'
|
||||
| 'particles'
|
||||
| 'sound'
|
||||
| 'soundfx'
|
||||
| 'instrument'
|
||||
| 'pattern'
|
||||
@@ -150,6 +152,7 @@ const PANE_ORDER = [
|
||||
'statemachine',
|
||||
'shader',
|
||||
'particles',
|
||||
'sound',
|
||||
'soundfx',
|
||||
'instrument',
|
||||
'pattern',
|
||||
@@ -173,6 +176,7 @@ const TAB_LABELS: Record<CentreTab, string> = {
|
||||
statemachine: 'State machine',
|
||||
shader: 'Shader',
|
||||
particles: 'Particles',
|
||||
sound: 'Sound',
|
||||
soundfx: 'Sound FX',
|
||||
instrument: 'Instrument',
|
||||
pattern: 'Pattern',
|
||||
@@ -225,6 +229,7 @@ export class Editor implements PlayHost {
|
||||
readonly stateMachineEditor: StateMachineEditor;
|
||||
readonly shaderEditor: ShaderEditor;
|
||||
readonly particlesEditor: ParticlesEditor;
|
||||
readonly soundEditor: SoundEditor;
|
||||
readonly soundFxEditor: SoundFxEditor;
|
||||
readonly instrumentEditor: InstrumentEditor;
|
||||
readonly patternEditor: PatternEditor;
|
||||
@@ -314,6 +319,7 @@ export class Editor implements PlayHost {
|
||||
this.stateMachineEditor = new StateMachineEditor(state);
|
||||
this.shaderEditor = new ShaderEditor(state);
|
||||
this.particlesEditor = new ParticlesEditor(state);
|
||||
this.soundEditor = new SoundEditor(state);
|
||||
this.soundFxEditor = new SoundFxEditor(state);
|
||||
this.instrumentEditor = new InstrumentEditor(state);
|
||||
this.patternEditor = new PatternEditor(state);
|
||||
@@ -426,6 +432,7 @@ export class Editor implements PlayHost {
|
||||
statemachine: this.stateMachineEditor,
|
||||
shader: this.shaderEditor,
|
||||
particles: this.particlesEditor,
|
||||
sound: this.soundEditor,
|
||||
soundfx: this.soundFxEditor,
|
||||
instrument: this.instrumentEditor,
|
||||
pattern: this.patternEditor,
|
||||
@@ -1023,6 +1030,7 @@ export class Editor implements PlayHost {
|
||||
if (this.tab === 'curve') this.curveEditor.tick(dt);
|
||||
if (this.tab === 'pattern') this.patternEditor.tick();
|
||||
if (this.tab === 'song') this.songEditor.tick();
|
||||
if (this.tab === 'sound') this.soundEditor.tick();
|
||||
// Play repaints the machine tab itself (see `PlaySession.tick`); an editor run has no session
|
||||
// to do it, and its states move just the same.
|
||||
if (this.tab === 'statemachine' && this.editorRun.isRunning) this.stateMachineEditor.canvas.redraw();
|
||||
@@ -1057,7 +1065,8 @@ export class Editor implements PlayHost {
|
||||
(this.tab === 'tile' && this.tileEditor.isPlaying) ||
|
||||
(this.tab === 'curve' && this.curveEditor.isPlaying) ||
|
||||
(this.tab === 'pattern' && this.patternEditor.isPlaying) ||
|
||||
(this.tab === 'song' && this.songEditor.isPlaying)
|
||||
(this.tab === 'song' && this.songEditor.isPlaying) ||
|
||||
(this.tab === 'sound' && this.soundEditor.isPlaying)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user