Swing the volume or the pitch with Tremolo and Vibrato (#320)
Alarms, sirens and wobbly retro sounds need a volume or a pitch that swings. Two new nodes do that, on a voice and on a bus alike. - Tremolo: Rate (0 to 20 Hz), Depth (0 to 100 %), Shape (Sine, Triangle, Square, Saw). The volume swings between full and 1 - depth. - Vibrato: Rate (0.5 to 20 Hz), Depth (0 to 4 semitones), Shape. The shape is the curve of the pitch, a Square jumps between two pitches. - As the issue decided: two nodes, phase 0 on the first sample of the voice, no fade-in time. Both run on the FX worklet, the classes are in lfo.ts and tested in Node. - Deviation from the issue text, as in the plan: the Vibrato is a delay that swings, not playbackRate. A bus has no playbackRate, and the sound keeps its length. - The delay swings around a center taken from the start values. The center counts as the latency of the node, so a voice starts that much earlier and a Mix and a bus make up for it. - Known limit, in the node text and the wiki: a depth turned up or a rate turned down after the start asks for a delay below 0. The lower half of the swing then flattens. Finding: the first e2e run was red at the edge of a window. The phase is summed up, so the square flips one sample later than 2400 steps of 10/48000 suggest. The check keeps 10 samples away from the edges. Tests: - Unit lfo (new) 5, soundfx 53 (2 new), sound-library 102 (1 new), green. - Counter-checks, each turns one test red: the swing turned over (up and down swapped), the start time not passed to the worklet. - e2e 34 (melektaus-atx): ALL PASSED, 80 checks (1 new). Chrome offline: Tremolo square silent before its voice, then 0.5 and 0 for 50 ms each. Vibrato square at 5 Hz, 1 semitone: 1059.5 Hz and 940.5 Hz. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d3a4cb7d0e
commit
071f194326
8 files changed
+542
-9
No files matched your search
@@ -18,7 +18,7 @@ import {
|
||||
import type { MixBus } from '../../src/core/music/mixer';
|
||||
import type { SequencePlay } from '../../src/core/music/playbacks';
|
||||
import type { ScheduledNote } from '../../src/core/music/Scheduler';
|
||||
import type { PitchOptions } from '../../src/viewport/fxWorklet';
|
||||
import type { LfoOptions, PitchOptions } from '../../src/viewport/fxWorklet';
|
||||
import { saturationCurve } from '../../src/core/soundfx/saturation';
|
||||
import { SoundLibrary, stageNodes } from '../../src/viewport/SoundLibrary';
|
||||
|
||||
@@ -341,7 +341,7 @@ beforeEach(() => {
|
||||
? 'crush'
|
||||
: name === 'chip-player'
|
||||
? 'chip'
|
||||
: name === 'phaser' || name === 'flanger' || name === 'limiter'
|
||||
: name === 'phaser' || name === 'flanger' || name === 'limiter' || name === 'lfo'
|
||||
? name
|
||||
: name === 'pitch-shifter'
|
||||
? 'shifter'
|
||||
@@ -2385,4 +2385,47 @@ describe('the stages of #318 to #328', () => {
|
||||
[0.75, 'value'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('starts the LFO of a Vibrato with its voice and delays around its center, the voice that much earlier (#320)', async () => {
|
||||
const library = new SoundLibrary();
|
||||
const vibrato: FxStagePlan = {
|
||||
kind: 'vibrato',
|
||||
shape: 'square',
|
||||
rate: { value: 0.5 },
|
||||
depth: { value: 4 },
|
||||
};
|
||||
library.playNote(
|
||||
'instrument:i',
|
||||
[
|
||||
{
|
||||
pitch: [],
|
||||
stages: [vibrato],
|
||||
source: {
|
||||
file: FILE,
|
||||
volume: 1,
|
||||
loop: false,
|
||||
loopStart: 0,
|
||||
loopEnd: 0,
|
||||
gate: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
{},
|
||||
1,
|
||||
'',
|
||||
0,
|
||||
{ at: 2 },
|
||||
);
|
||||
await settle();
|
||||
await arrive();
|
||||
const center = ((2 ** (4 / 12) - 1) * 0.25) / 0.5;
|
||||
const options = worklets[0]!.options.processorOptions as unknown as LfoOptions & { vibrato: boolean };
|
||||
expect(options).toMatchObject({ shape: 2, startAt: 2 - center, vibrato: true });
|
||||
expect(options.center).toBeCloseTo(center, 4);
|
||||
expect(sources[0]!.startedAt).toBeCloseTo(2 - center, 4);
|
||||
expect([...worklets[0]!.parameters.entries()].map(([name, p]) => [name, p.value])).toEqual([
|
||||
['rate', 0.5],
|
||||
['depth', 4],
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user