Give sounds, instruments, patterns and songs a sound FX (#352)
A sound FX could only be picked where something was played: on a Play
node, a track or an item of a song. A sound that should always sound
through the same effect needed that pick everywhere it was used. Now
the asset says it once, and every place that plays it follows.
- New row Sound FX on a sound, an instrument, a pattern and a song,
right before Bus. A sound goes through it on every voice, an
instrument on every note after its graph, a pattern on all its
notes together and a song on its output.
- The fx input of Play sound, Play note, Play pattern and Play song
and the Sound FX of a song item now read "From the asset" while
empty. None is a pick of its own, stored as `#none` like `#master`.
A pick replaces the FX of the asset, it does not come on top.
- A pattern item's Instrument reads "From the asset" instead of None.
The meaning is unchanged.
- A double-click on a sound item opens the sound, as one on a pattern
item opens the pattern.
- The tabs play through the FX of their asset. The Pattern and Song
tab put it on a bus of their own between the output and the bus of
the asset. The keys of the Pattern tab play there too.
- Notes of a pattern or song go through the FX of their instrument.
Its chain is loaded with the other needs of the sequence.
Decided without asking:
- Old items and Play nodes with an empty fx now mean "From the
asset". No asset has an FX yet, so nothing sounds different.
- The label is "From the asset" as on Loop and Bus, not "From asset".
- A sound used as a Sample, as the impulse of a Reverb or as the test
sound of the Sound FX tab plays without its own FX.
Findings:
- In e2e, `__gm.inspectorField('Sound FX')` and `('Instrument')` find
the group of the Assets panel first. The check asks for the last
match.
Tests:
- Unit asset-fx (new): 8 green. Six counter-checks each turned red.
- Field lists of sound, pattern and song: Sound FX added.
- e2e 65 (melektaus-atx): ALL PASSED, 46 checks (3 new). Counter-check
without the FX bus of the tab: that check red.
- Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4142 tests.
- Not checked: how it sounds, the Sound, Instrument and Pattern tab
in the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d349251a01
commit
a295d233ed
26 files changed
+521
-60
No files matched your search
@@ -11,11 +11,11 @@ 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 { pickedFx } from '../../core/properties/assetFx';
|
||||
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';
|
||||
import { soundFxBuildFor } from '../../core/soundfx/compile';
|
||||
import { instrumentBuildFor, instrumentSourcesOf, throughScopedFx } from '../../core/instrument/compile';
|
||||
import { VELOCITY_PARAM } from '../../core/instrument/nodes';
|
||||
import { heldNote } from '../../core/instrument/notes';
|
||||
@@ -190,7 +190,7 @@ export function behaviorContext(
|
||||
detached ? '' : entity.id,
|
||||
params,
|
||||
{ tempo, bus: instrumentBus(project, instrumentId, bus) },
|
||||
fxVoiceOf(project, fx, tempo, fxParams),
|
||||
fxVoiceOf(project, instrumentFx(project, instrumentId, fx), tempo, fxParams),
|
||||
),
|
||||
playPattern: (patternId, volume, loop, detached, fx, fxParams, bus) =>
|
||||
playPattern(run, patternId, volume, loop, detached ? '' : entity.id, fx, fxParams, bus),
|
||||
@@ -287,18 +287,22 @@ function playSound(
|
||||
if (!asset?.file) return 0;
|
||||
const props = resolveAssetProps(project, asset);
|
||||
const level = Math.max(0, Math.min(1, volumeOf(props) * volume));
|
||||
const fx = fxId ? findAssetOf(project, fxId, 'soundfx') : undefined;
|
||||
const plan = fx && soundFxBuildFor(fx, project).compiled;
|
||||
const fx = fxVoiceOf(project, pickedFx(fxId, props), tempo, fxParams);
|
||||
return audio.play(
|
||||
playedSoundOf(project, asset),
|
||||
level,
|
||||
loop ?? loopsOf(props),
|
||||
owner,
|
||||
plan ? { plan, params: fxParams, tempo } : DRY_VOICE,
|
||||
fx ?? DRY_VOICE,
|
||||
playedBus(project, bus, props),
|
||||
);
|
||||
}
|
||||
|
||||
/** The sound FX a note of that instrument goes through, by the pick of a node (#352); '' for none. */
|
||||
export function instrumentFx(project: Project, instrumentId: string, picked: string): string {
|
||||
return pickedFx(picked, findAssetOf(project, instrumentId, 'instrument')?.props ?? {});
|
||||
}
|
||||
|
||||
/** The bus of the mix a note of that instrument is heard on, by the pick of a node (#325). */
|
||||
export function instrumentBus(project: Project, instrumentId: string, picked: string): string {
|
||||
return playedBus(project, picked, findAssetOf(project, instrumentId, 'instrument')?.props ?? {});
|
||||
@@ -375,7 +379,7 @@ function playPattern(
|
||||
const level = heldLevel(volume);
|
||||
const length = patternLength(asset);
|
||||
const tempo = patternTempo(asset.props);
|
||||
const fx = fxVoiceOf(project, fxId, tempo, fxParams);
|
||||
const fx = fxVoiceOf(project, pickedFx(fxId, asset.props), tempo, fxParams);
|
||||
return audio.playSequence(
|
||||
patternVoiceName(patternId),
|
||||
{
|
||||
@@ -413,7 +417,7 @@ function playSong(
|
||||
if (!song) return 0;
|
||||
const sequence = songSequence(project, song);
|
||||
const tempo = songTempo(song.props);
|
||||
const fx = fxVoiceOf(project, fxId, tempo, fxParams);
|
||||
const fx = fxVoiceOf(project, pickedFx(fxId, song.props), tempo, fxParams);
|
||||
return audio.playSequence(
|
||||
songVoiceName(songId),
|
||||
{
|
||||
@@ -434,7 +438,10 @@ function playSong(
|
||||
);
|
||||
}
|
||||
|
||||
/** A note the scheduler planned (#332), struck through its instrument at its volume, on its bus. */
|
||||
/**
|
||||
* A note the scheduler planned (#332), struck through its instrument and the sound FX that names
|
||||
* (#352) at its volume, on its bus.
|
||||
*/
|
||||
export function strikeNote(
|
||||
sources: Pick<ContextSources, 'project' | 'audio'>,
|
||||
note: ScheduledNote,
|
||||
@@ -447,7 +454,8 @@ export function strikeNote(
|
||||
duration: note.duration,
|
||||
};
|
||||
const timing = { at: note.at, tempo: note.tempo, ...(note.bus && { bus: note.bus }) };
|
||||
return playNote(sources, note.instrument, struck, owner, {}, timing);
|
||||
const fx = fxVoiceOf(sources.project, instrumentFx(sources.project, note.instrument, ''), note.tempo);
|
||||
return playNote(sources, note.instrument, struck, owner, {}, timing, fx);
|
||||
}
|
||||
|
||||
/** A clip of a song (#334): its sound at its own volume times the item's, through its sound FX. */
|
||||
|
||||
Reference in new issue
Block a user