Put a sound in a room, a hall or a cave with a Reverb node (#328)
A sound FX had no space around it. The Reverb node folds the sound with an impulse response: one it makes itself, or a sound asset as the answer of a real space. It works on a voice and on a bus. - Type: Room, Hall, Cave and Plate have fixed numbers. Custom shows Decay (up to 10 s), Damping and Pre-delay. Sound shows the pick of a sound asset and Pre-delay. The rows are derived ports, the sound pick an idle param, so they only appear for their type. - The made impulse response is seeded noise per side, 60 dB down at Decay, through a low-pass closing from 20 kHz toward 500 Hz as far as Damping says. It is made once per rate, decay and damping, the last 16 are kept. - A sound as impulse response: the sound FX compilers now get the project, so a node can turn a sound asset into its file. The build key holds that file, so a cut or swapped sound builds again. The instrument compiler does the same for a Reverb inside an instrument. - The library loads that file with the voice, a sequence or before a bus builds its chain. A missing file leaves the reverb silent. - Mix blends dry and reverb and is the only number that moves while the sound plays, as the issue said. The voice rings for pre-delay plus decay, or as long as its sound. - The open point from #325 about sends on project buses is #351, not part of this. Tests: - Unit reverb (new) 4, soundfx 57 (4 new), sound-library 104 (2 new), green. - Counter-checks, each turns one test red: the impulse file not loaded with the voice, the tail without the length of the sound. - e2e 34 (melektaus-atx): ALL PASSED, 81 checks (1 new). Chrome offline: silent during the pre-delay, 35.9 dB fall over 0.6 s of a 1 s decay (36 expected), the dry half at 0.25 for mix 0.5. A sound with one sample at 0.2 s gives the impulse back 0.25 s later and nowhere else. - Gate (melektaus-atx) over the end of this series #318 to #328: CHECK EXIT 0, 267 files, 4134 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
071f194326
commit
d349251a01
14 files changed
+544
-47
No files matched your search
@@ -18,7 +18,7 @@ import { findAssetOf, type Project } from '../model/Project';
|
||||
import { playedSoundOf } from '../properties/playedSound';
|
||||
import { resolveAssetProps } from '../properties/resolve';
|
||||
import { volumeOf } from '../properties/sounds';
|
||||
import { soundFxBuildFor } from '../soundfx/compile';
|
||||
import { soundFxBuildFor, soundFxKey } from '../soundfx/compile';
|
||||
import {
|
||||
type FxAmount,
|
||||
type FxChipPlan,
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
SOUND_FX_OUTPUT,
|
||||
SoundFxMix,
|
||||
type SoundFxNodeDef,
|
||||
type SoundFxProject,
|
||||
SoundFxReverb,
|
||||
withPitch,
|
||||
withStages,
|
||||
} from '../soundfx/nodes';
|
||||
@@ -83,13 +85,13 @@ export function instrumentSourcesOf(project: Project): InstrumentSources {
|
||||
},
|
||||
soundFx: (assetId) => {
|
||||
const asset = findAssetOf(project, assetId, 'soundfx');
|
||||
return asset ? soundFxBuildFor(asset).compiled : null;
|
||||
return asset ? soundFxBuildFor(asset, project).compiled : null;
|
||||
},
|
||||
key: (assetId) => {
|
||||
const sound = findAssetOf(project, assetId, 'sound');
|
||||
if (sound) return `${playedSoundOf(project, sound)}@${volumeOf(resolveAssetProps(project, sound))}`;
|
||||
const fx = findAssetOf(project, assetId, 'soundfx');
|
||||
return fx ? graphFingerprint(fx.graph) : '';
|
||||
return fx ? soundFxKey(fx, project) : '';
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -130,6 +132,7 @@ export function instrumentParams(graph: Graph): readonly DeclaredPort[] {
|
||||
* are numbers here, `velocity` and the parameters stay open for every voice to fill in.
|
||||
*/
|
||||
export function compileInstrument(graph: Graph, note: number, sources: InstrumentSources): InstrumentBuild {
|
||||
const project: SoundFxProject = { soundFile: (assetId) => sources.sound(assetId)?.file ?? '' };
|
||||
const walk = graphEmitter<Def, Value>(graph, {
|
||||
isDef: isInstrumentDef,
|
||||
noun: 'instrument',
|
||||
@@ -224,10 +227,12 @@ export function compileInstrument(graph: Graph, note: number, sources: Instrumen
|
||||
const [a] = plansAt('a');
|
||||
const [b] = plansAt('b');
|
||||
if (!a || !b) return { out: { plans: a ? [a] : b ? [b] : [] } };
|
||||
return { out: { plans: [def.emit({ a, b }, amounts, node.params)] } };
|
||||
return { out: { plans: [def.emit({ a, b }, amounts, node.params, project)] } };
|
||||
}
|
||||
const [arriving] = plansAt('in');
|
||||
return { out: arriving ? { plans: [def.emit({ in: arriving }, amounts, node.params)] } : SILENT };
|
||||
return {
|
||||
out: arriving ? { plans: [def.emit({ in: arriving }, amounts, node.params, project)] } : SILENT,
|
||||
};
|
||||
}
|
||||
|
||||
/** A Sample's sound, or null after saying why it has none – also where its trigger is off. */
|
||||
@@ -383,7 +388,12 @@ export function instrumentBuildFor(
|
||||
sources: InstrumentSources,
|
||||
): InstrumentBuild {
|
||||
const named = asset.graph.nodes
|
||||
.map((node) => textOf(node.params[node.type === INSTRUMENT_SOUND_FX ? 'fx' : 'sound'] ?? ''))
|
||||
.flatMap((node) =>
|
||||
node.type === INSTRUMENT_SOUND_FX
|
||||
? [node.params.fx]
|
||||
: [node.params.sound, ...(node.type === SoundFxReverb.type ? [node.params.impulse] : [])],
|
||||
)
|
||||
.map((id) => textOf(id ?? ''))
|
||||
.filter((id) => id !== '');
|
||||
const fingerprint = `${graphFingerprint(asset.graph)}|${named.map((id) => sources.key(id)).join('|')}`;
|
||||
let cached = builds.get(asset.id);
|
||||
|
||||
Reference in new issue
Block a user