Cut a sound to a stretch of its file in its own Sound tab (#343)
game-maker / gate (push) Successful in 3m32s
game-maker / suite (push) Failing after 18m20s

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:
adminandClaude Opus 5.5 committed 2026-10-06 21:42:21 +02:00
1 parent 2fdf97f7de
commit f1f4a57589
24 files changed
+1442 -53

No files matched your search

+10 -1
View File
@@ -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)
);
}