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
@@ -11,7 +11,7 @@ import { JOINT_CONNECTED_KEY } from '../../core/model/joint';
|
||||
import { objectReference } from '../../core/model/objectRefs';
|
||||
import { findAsset, findAssetOf, findScene, type Project } from '../../core/model/Project';
|
||||
import { renderLayerAlong } from '../../core/model/renderLayers';
|
||||
import { soundFileOf } from '../../core/model/sound';
|
||||
import { playedSoundById, playedSoundOf } from '../../core/properties/playedSound';
|
||||
import { hasRenderLayer } from '../../core/properties/PropertySchema';
|
||||
import { resolveAssetProps } from '../../core/properties/resolve';
|
||||
import { loopsOf, volumeOf } from '../../core/properties/sounds';
|
||||
@@ -193,14 +193,14 @@ export function behaviorContext(
|
||||
stopSong: (songId, fadeOut) => run.audio.stopSequence(songVoiceName(songId), false, heldFade(fadeOut)),
|
||||
stopPlayback: (playback, fadeOut) => run.audio.stopPlayback(playback, heldFade(fadeOut)),
|
||||
soundLength: (assetId) => {
|
||||
const file = soundFileOf(project, assetId);
|
||||
const file = playedSoundById(project, assetId);
|
||||
return file ? run.audio.length(file) : 0;
|
||||
},
|
||||
// An id nothing resolves stops nothing, rather than silencing the whole run by accident;
|
||||
// only the empty id means "everything", which is what the node promises.
|
||||
soundTail: (voice) => run.audio.tail(voice),
|
||||
stopSound: (assetId, letRing) => {
|
||||
const file = assetId === '' ? '' : soundFileOf(project, assetId);
|
||||
const file = playedSoundById(project, assetId);
|
||||
if (assetId === '' || file) run.audio.stop(file, letRing);
|
||||
},
|
||||
stopVoice: (voice, letRing) => run.audio.stopVoice(voice, letRing),
|
||||
@@ -270,7 +270,7 @@ function playSound(
|
||||
const fx = fxId ? findAssetOf(project, fxId, 'soundfx') : undefined;
|
||||
const plan = fx && soundFxBuildFor(fx).compiled;
|
||||
return audio.play(
|
||||
asset.file,
|
||||
playedSoundOf(project, asset),
|
||||
level,
|
||||
loop ?? loopsOf(props),
|
||||
owner,
|
||||
@@ -404,7 +404,7 @@ export function playClip(
|
||||
Math.min(dbToGain(SOUND_ITEM_MAX_DB), volumeOf(resolveAssetProps(project, asset)) * clip.volume),
|
||||
);
|
||||
return audio.playClip(
|
||||
asset.file,
|
||||
playedSoundOf(project, asset),
|
||||
{ at: clip.at, offset: clip.offset, duration: clip.duration, volume, bus: clip.bus },
|
||||
clipFx(project, clip.fx, clip.tempo),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user