Both were asked for in #367 "for later". The node of a Parametric EQ now shows its curve in small, and its tab shows what passes the node while it is seen. #375, the curve in the node: - NodeDef has an optional `picture`: a height, and shapes made from the settings as plain point lists (guide, area, line). It is general, the Parametric EQ is its first user. - NodeView draws it in a box below the rows, again only when the box or the settings change. A drag in the EQ tab changes them, so the node follows it live. - Without a band the box stays, flat on its 0 dB line, so the node does not grow at the first band. - The axes of the curve, `eqCurvePoints` and `fillShapes` move from editor/soundfx/eqView to core/soundfx/parametricEq. Tab and node draw the sum from the same function. #374, the spectrum: - A node marked `tapped` gets a `tap` stage in front of its own stages and one behind them when compiled, in a sound FX and in an instrument alike. Its id is asset and node, since a copied asset keeps its node ids. - A tap is a gain of 1. SpectrumTaps (new, viewport/spectrum.ts) holds the tap of every voice and bus while it plays. - While the EQ tab is seen, in the tab row or in a window, the taps of that node feed one analyser per side (8192 points). Nothing is made before a tap of the node plays, so no AudioContext is opened early. - The tab draws both behind the curve, on a scale of 0 to -96 dBFS: the sound coming in faint and grey, the sound going on in the colour of a sound port. The analyser smooths it, and once the sound has gone the sum still hands it silence, so it falls away. - Once the tab is not seen, Editor.tick lets the analysers go, like the meters of the Song tab. - A tap does nothing to the sound, so `stats().last.stages` leaves it out, and a bypassed EQ keeps its taps. Decided on my own: - Every sound passing the node counts: Play of the sound FX tab, an instrument's keys, a song, a run. The issue proposed that. - The spectrum has no labels of its own. Its scale is in the wiki. Tests: Gate (melektaus-atx) 275 files, 4247 tests (11 new), EXIT 0 e2e 34 (melektaus-atx) ALL PASSED, 98 checks (2 new), twice Counter-checks, each put back by copy and compared with cmp: - The node's curve drawn without its bands: 2 unit tests fail. - The spectrum without the loudest bin between two points: 1 fails. - SpectrumTaps.stop leaving the taps linked: 1 fails. - Taps that never feed the analyser: e2e 34 fails in exactly the spectrum check (frames 0). Run in the container only. The screenshots of the second run were looked at: the blip as one peak behind the curve, and the dip at 4 kHz in the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
25084f8576
commit
687377682c
19 files changed
+802
-99
No files matched your search
@@ -49,6 +49,7 @@ import {
|
||||
releaseEnvelope,
|
||||
stopProcessor,
|
||||
} from './fxNodes';
|
||||
import { type Spectrum, SpectrumTaps } from './spectrum';
|
||||
import { wake } from './wake';
|
||||
|
||||
/** A voice is no louder than a sound item of a song turned all the way up. */
|
||||
@@ -305,6 +306,8 @@ export class SoundLibrary implements AudioSink {
|
||||
private readonly ownerStops = new Map<string, number>();
|
||||
/** The tracks of the songs played so far (#334), by name. */
|
||||
private readonly buses = new Map<string, Bus>();
|
||||
/** The taps of the voices and buses playing now, and the node listened to (#374). */
|
||||
private readonly spectra = new SpectrumTaps();
|
||||
private readonly peaked = new Map<string, Float32Array>();
|
||||
/**
|
||||
* The patterns and songs a game plays, each on a scheduler of its own. What it asks after are its
|
||||
@@ -531,6 +534,27 @@ export class SoundLibrary implements AudioSink {
|
||||
for (const bus of this.buses.values()) this.dropMeter(bus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to the sound in front of the node `id` (`tapId`) and behind it (#374): a Parametric
|
||||
* EQ's tab while it is seen. `spectrum` reads it, `unlisten` lets it go.
|
||||
*/
|
||||
listen(id: string): void {
|
||||
this.spectra.listen(id);
|
||||
}
|
||||
|
||||
unlisten(): void {
|
||||
this.spectra.stop();
|
||||
}
|
||||
|
||||
spectrum(): Spectrum | null {
|
||||
return this.spectra.read();
|
||||
}
|
||||
|
||||
/** The node listened to, the taps playing, and how many of them feed an analyser (test hook). */
|
||||
spectrumTaps(): { listened: string; held: number; feeding: number } {
|
||||
return this.spectra.info();
|
||||
}
|
||||
|
||||
/** How many buses are tapped for a meter (test hook). */
|
||||
metered(): number {
|
||||
return [...this.buses.values()].filter((bus) => bus.meter).length;
|
||||
@@ -865,6 +889,7 @@ export class SoundLibrary implements AudioSink {
|
||||
tail = stageMade.output;
|
||||
knobs.push(...stageMade.knobs);
|
||||
if (stageMade.stop) made.stops.push(stageMade.stop);
|
||||
if (stage.kind === 'tap') made.stops.push(this.spectra.hold(stage, stageMade.output));
|
||||
nodes.push(stageMade.input, stageMade.output);
|
||||
}
|
||||
return { out: tail, latency };
|
||||
@@ -1763,8 +1788,11 @@ export class SoundLibrary implements AudioSink {
|
||||
tail = made.output;
|
||||
knobs.push(...made.knobs);
|
||||
if (made.stop) wiring.drivers.push({ start: () => {}, stop: made.stop });
|
||||
if (stage.kind === 'tap')
|
||||
wiring.drivers.push({ start: () => {}, stop: this.spectra.hold(stage, made.output) });
|
||||
}
|
||||
wiring.stages += chain.stages.length;
|
||||
// A tap does nothing to the sound: the stages counted are those that do.
|
||||
wiring.stages += chain.stages.filter((stage) => stage.kind !== 'tap').length;
|
||||
return tail;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user