diff --git a/README.md b/README.md index da440b3..879b5ea 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,10 @@ dotnet build -p:CultLibRoot= # or set CULTLIB_ROOT= ``` Without an override the sibling `..\CultLib` checkout is used and must itself be at that revision. + +**Stores are one-way, and rolling back can lose data.** Since the pin move (#35) list elements carry ids, and a store holding any element id is stamped with a newer header. What an older build does with it depends on the store kind: + +- **Single-file stores** (header v3 with ids): the patch catalog (`patches/aquasynth-patch-cultcache.cc`), the speech training store and the daemon store. Pins before `cultlib-unity-v2.0.0` / `caching-unity-v2.0.0`, including NuGet `cultlib-v1.0.32`, read these without complaint, silently drop the ids, and can rewrite elements without them. Only v2.0.0 and later refuse by name. +- **Directory stores** (header v5 with ids): the IPA trial results store (`ipa-trial-results.cc`, `UseDirectoryStore`). These are refused by name from `cultlib-unity-v1.0.57` / `caching-unity-v1.2.0`. + +So: take a copy of any store before the upgrade and restore from it if you roll back, never write with the new pin and then read with an old one, and upgrade every reader before any writer. Stores written before the move still open; ids are minted on load. diff --git a/src/AquaSynth.Core/FaustExport.cs b/src/AquaSynth.Core/FaustExport.cs index ca98116..ca70c36 100644 --- a/src/AquaSynth.Core/FaustExport.cs +++ b/src/AquaSynth.Core/FaustExport.cs @@ -29,12 +29,10 @@ public static partial class FaustEmitter if (patch.Repeat is { } repeat) { var interval = parameters.Expression("/patch/repeat", repeat.IntervalSeconds); - source.AppendLine($"age = time - floor(time / {interval}) * {interval};"); - } - else - { - source.AppendLine("age = time;"); + // sfxr's repeat restarts the pitch slide and the arpeggio, not the envelope: age keeps running, the voices wrap their pitch age. + source.AppendLine($"repeat_interval = {interval};"); } + source.AppendLine("age = time;"); source.AppendLine("clip01(x) = min(1.0, max(0.0, x));"); source.AppendLine("wrap01(x) = x - floor(x);"); source.AppendLine("slew(rate,x) = x : *(1.0 - exp((0.0 - max(0.0001, rate)) / ma.SR)) : + ~ *(exp((0.0 - max(0.0001, rate)) / ma.SR));"); @@ -325,6 +323,9 @@ public static partial class FaustEmitter ? $"{voiceAge} = wrap01(age / {F(voice.Note.TriggerIntervalSeconds)}) * {F(voice.Note.TriggerIntervalSeconds)};" : $"{voiceAge} = age;"); + var pitchAge = patch.Repeat is null ? voiceAge : $"{name}_pitch_age"; + if (patch.Repeat is not null) source.AppendLine($"{pitchAge} = {voiceAge} - floor({voiceAge} / repeat_interval) * repeat_interval;"); + var minFreq = parameters.Expression(OwnerField(ownerPath, "pitch/min_freq"), voice.Pitch.MinFrequencyHz); var noteFreq = NoteFrequencyExpression(source, patch.Playback, voice.Note, name, OwnerField(ownerPath, "note/frequency"), parameters); var noteGate = NoteGateExpression(source, patch.Playback, voice.Note, name, OwnerField(ownerPath, "note/gate"), parameters); @@ -333,7 +334,7 @@ public static partial class FaustEmitter var vibratoDepth = parameters.Expression(OwnerField(ownerPath, "pitch/vibrato"), voice.Pitch.VibratoDepth); var vibratoHz = parameters.Expression(OwnerField(ownerPath, "pitch/vibrato_hz"), voice.Pitch.VibratoHz); var vibratoDelay = parameters.Expression(OwnerField(ownerPath, "pitch/vibrato_delay"), voice.Pitch.VibratoDelaySeconds); - var baseFreq = $"max({minFreq}, {noteFreq} * pow(2.0, {pitchRamp} * {voiceAge} + 0.5 * {pitchDelta} * {voiceAge} * {voiceAge}))"; + var baseFreq = $"max({minFreq}, {noteFreq} * pow(2.0, {pitchRamp} * {pitchAge} + 0.5 * {pitchDelta} * {pitchAge} * {pitchAge}))"; var hasVibrato = voice.Pitch.VibratoDepth != 0 && voice.Pitch.VibratoHz > 0 || parameters.IsBound(OwnerField(ownerPath, "pitch/vibrato")) || parameters.IsBound(OwnerField(ownerPath, "pitch/vibrato_hz")); @@ -342,7 +343,7 @@ public static partial class FaustEmitter : ""; var arpeggio = voice.Arpeggio is null ? "1.0" - : $"select2({voiceAge} < {parameters.Expression(OwnerField(ownerPath, "arpeggio/delay"), voice.Arpeggio.DelaySeconds)}, {parameters.Expression(OwnerField(ownerPath, "arpeggio/multiplier"), voice.Arpeggio.Multiplier)}, 1.0)"; + : $"select2({pitchAge} < {parameters.Expression(OwnerField(ownerPath, "arpeggio/delay"), voice.Arpeggio.DelaySeconds)}, {parameters.Expression(OwnerField(ownerPath, "arpeggio/multiplier"), voice.Arpeggio.Multiplier)}, 1.0)"; var frequency = $"(({baseFreq}){vibrato}) * {arpeggio} * pow(2.0, patch_mod_pitch + {pitch})"; var dutyExpression = $"clip01({parameters.Expression(OwnerField(ownerPath, "osc/duty"), voice.Oscillator.Duty)} + {parameters.Expression(OwnerField(ownerPath, "duty/ramp"), voice.Duty.RampPerSecond)} * {voiceAge} + patch_mod_duty + {duty})"; var fmIndex = $"max(0.0, {parameters.Expression(OwnerField(ownerPath, "fm/index"), voice.Fm.Index)} + patch_mod_fm_index + {fmIndexMod}) * {FmDecay(parameters.Expression(OwnerField(ownerPath, "fm/decay"), voice.Fm.IndexDecaySeconds), voice.Fm.IndexDecaySeconds, parameters.IsBound(OwnerField(ownerPath, "fm/decay")), voiceAge)}"; diff --git a/tests/AquaSynth.Dsl.Tests/RepeatLoweringTests.cs b/tests/AquaSynth.Dsl.Tests/RepeatLoweringTests.cs new file mode 100644 index 0000000..3e1c65e --- /dev/null +++ b/tests/AquaSynth.Dsl.Tests/RepeatLoweringTests.cs @@ -0,0 +1,44 @@ +using AquaSynth.Dsl; + +namespace AquaSynth.Dsl.Tests; + +// sfxr's repeat restarts the pitch slide and the arpeggio; it does not restart the envelope. Before this, `patch repeat` wrapped the +// whole age, so a repeat shorter than sustain+decay never reached the release (the 2026-11-11 explosion/golf/powerup finding). +public sealed class RepeatLoweringTests +{ + private const string Script = """ + patch repeat=0.11315 + voice + wave=sine + freq=57.5946 + sustain=0.1306122449 + decay=0.1777777778 + pitch_ramp=-0.208544 + arp_delay=0.05 + arp_mult=1.5 + """; + + [Fact] + public void RepeatWrapsPitchAndArpeggioAgeButNotTheEnvelope() + { + var source = FaustEmitter.EmitScript(Script).Source; + + Assert.Contains("age = time;", source); + Assert.DoesNotContain("floor(time /", source); + Assert.Contains("voice_0_pitch_age = voice_0_age - floor(voice_0_age / repeat_interval) * repeat_interval;", source); + Assert.Contains("0.208544", source); + Assert.Contains("* voice_0_pitch_age", source); + Assert.Contains("select2(voice_0_pitch_age <", source); + Assert.Contains("oneshot_adsr_at(voice_0_age,", source); + Assert.DoesNotContain("oneshot_adsr_at(voice_0_pitch_age", source); + } + + [Fact] + public void WithoutRepeatThereIsNoPitchAge() + { + var source = FaustEmitter.EmitScript(Script.Replace("patch repeat=0.11315\n", "")).Source; + + Assert.DoesNotContain("pitch_age", source); + Assert.DoesNotContain("repeat_interval", source); + } +} diff --git a/tools/aqua-feel/SHELF.md b/tools/aqua-feel/SHELF.md index 1727e78..b57964f 100644 --- a/tools/aqua-feel/SHELF.md +++ b/tools/aqua-feel/SHELF.md @@ -85,18 +85,18 @@ Sustained patches such as the thruster keep sounding until stopped; the render c | `patch/advanced-glass-creature` | start | none | decays | 3 s | unheard | | `patch/advanced-human-larynx-graph` | start | curves/breath_phrase/depth 0..1 (default 0.9); curves/fold_onset/depth 0..1 (default 0.8); curves/lip_vowel/depth 0..1 (default 0.65); human/constriction/diameter 0.02..3 (default 0.95); human/constriction/index 0..6 (default 4.8); human/fold/opening 0..1 (default 0.16); human/fold/tension 0..1 (default 0.58); human/lip/opening 0..2 (default 1.2); human/subglottal 0..1.5 (default 0.78); human/tongue/diameter 0.05..3 (default 1.25); human/tongue/index 0..6 (default 3.3) | decays | 3 s | unheard | | `patch/advanced-machine-breath` | start | none | decays | 3 s | unheard | -| `patch/advanced-ritual-sequence` | start | none | sustained | 3 s | unheard | +| `patch/advanced-ritual-sequence` | start | none | decays | 3 s | unheard | | `patch/advanced-subtractive-drums` | start | none | decays | 3 s | unheard | | `patch/advanced-syrinx-voice` | start | curves/beak_phrase/depth 0..1 (default 0.72); curves/left_phrase_opening/depth 0..1 (default 0.85); curves/left_phrase_pressure/depth 0..1 (default 0.92); curves/right_phrase_opening/depth 0..1 (default 0.85); curves/right_phrase_pressure/depth 0..1 (default 0.92); voice/beak/opening 0..1.5 (default 0.88); voice/left/opening 0..1 (default 0.23); voice/left/pressure 0..1.5 (default 0.78); voice/left/tension 0..1 (default 0.43); voice/right/opening 0..1 (default 0.19); voice/right/pressure 0..1.5 (default 0.66); voice/right/tension 0..1 (default 0.5) | decays | 3 s | unheard | | `patch/bfxr-coin-spark` | start | none | decays | 3 s | unheard | | `patch/bfxr-portal-chirp` | start | none | decays | 3 s | unheard | | `patch/bfxr-shield-pop` | start | none | decays | 3 s | unheard | -| `patch/bfxr-ui-bloom` | start | none | sustained | 3 s | unheard | +| `patch/bfxr-ui-bloom` | start | none | decays | 3 s | unheard | | `patch/dx7-algorithm-32-additive-organ` | start | macro/drawbar 0..1 (default 0.78); macro/shimmer 0..0.3 (default 0.08) | decays | 3 s | unheard | | `patch/dx7-algorithm-8-bright-pair` | start | macro/brightness 0..5 (default 2.6); macro/strike 0.02..0.6 (default 0.18) | decays | 3 s | unheard | | `patch/dx7-public-domain-mc-mm-5-3` | start | none | decays | 3 s | unheard | | `patch/dx7-public-domain-prc-synth1-calibrated` | start | none | decays | 3 s | unheard | -| `patch/examples-language-tour` | start | none | sustained | 3 s | unheard | +| `patch/examples-language-tour` | start | none | decays | 3 s | unheard | | `patch/fm-bell-bell` | start | none | decays | 3 s | unheard | | `patch/fm-bell-chime` | start | none | decays | 3 s | unheard | | `patch/fm-bell-coin` | start | none | decays | 3 s | unheard | @@ -108,7 +108,7 @@ Sustained patches such as the thruster keep sounding until stopped; the render c | `patch/sfxr-jump` | start | none | decays | 3 s | unheard | | `patch/sfxr-laser` | start | none | decays | 3 s | unheard | | `patch/sfxr-pickup` | start | none | decays | 3 s | unheard | -| `patch/sfxr-powerup` | start | none | sustained | 3 s | unheard | +| `patch/sfxr-powerup` | start | none | decays | 3 s | unheard | | `patch/wobble-bass-growl` | start | none | decays | 3 s | unheard | | `patch/wobble-bass-neuro` | start | none | decays | 3 s | unheard | | `patch/wobble-bass-talker` | start | none | decays | 3 s | unheard | diff --git a/tools/aqua-feel/shelf.json b/tools/aqua-feel/shelf.json index c6e9e7e..6d8297f 100644 --- a/tools/aqua-feel/shelf.json +++ b/tools/aqua-feel/shelf.json @@ -859,7 +859,7 @@ "kind": "start", "detail": "plays from the moment it is started; no gate, trigger or audio-feature input. Sliders, where listed, are live while it plays." }, - "behaviour": "sustained: still sounding at the end of the render window, runs until stopped", + "behaviour": "decays: dies away inside the render window", "duration_s": 3, "duration_note": "the render window, not the effect's length", "render": { @@ -1106,7 +1106,7 @@ "kind": "start", "detail": "plays from the moment it is started; no gate, trigger or audio-feature input. Sliders, where listed, are live while it plays." }, - "behaviour": "sustained: still sounding at the end of the render window, runs until stopped", + "behaviour": "decays: dies away inside the render window", "duration_s": 3, "duration_note": "the render window, not the effect's length", "render": { @@ -1265,7 +1265,7 @@ "kind": "start", "detail": "plays from the moment it is started; no gate, trigger or audio-feature input. Sliders, where listed, are live while it plays." }, - "behaviour": "sustained: still sounding at the end of the render window, runs until stopped", + "behaviour": "decays: dies away inside the render window", "duration_s": 3, "duration_note": "the render window, not the effect's length", "render": { @@ -1565,7 +1565,7 @@ "kind": "start", "detail": "plays from the moment it is started; no gate, trigger or audio-feature input. Sliders, where listed, are live while it plays." }, - "behaviour": "sustained: still sounding at the end of the render window, runs until stopped", + "behaviour": "decays: dies away inside the render window", "duration_s": 3, "duration_note": "the render window, not the effect's length", "render": { diff --git a/tools/aqua-playground/lowered/advanced-ritual-sequence.dsp b/tools/aqua-playground/lowered/advanced-ritual-sequence.dsp index b59626c..5c07841 100644 --- a/tools/aqua-playground/lowered/advanced-ritual-sequence.dsp +++ b/tools/aqua-playground/lowered/advanced-ritual-sequence.dsp @@ -3,7 +3,8 @@ declare name "advanced-ritual-sequence"; declare options "[nvoices:1]"; time = ba.time / ma.SR; -age = time - floor(time / 0.42) * 0.42; +repeat_interval = 0.42; +age = time; clip01(x) = min(1.0, max(0.0, x)); wrap01(x) = x - floor(x); slew(rate,x) = x : *(1.0 - exp((0.0 - max(0.0001, rate)) / ma.SR)) : + ~ *(exp((0.0 - max(0.0001, rate)) / ma.SR)); @@ -41,8 +42,9 @@ patch_mod_formant_mix = 0.0; patch_mod_fm_index = 0 + 0.75 * lfo_tri(2.38, 0); voice_0_age = age; -voice_0_freq = ((max(20, 196 * pow(2.0, 0 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age)))) * select2(voice_0_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0); -voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 196 * pow(2.0, 0 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age)))) * select2(voice_0_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 196 * pow(2.0, 0 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age)))) * select2(voice_0_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_0_age / max(0.32, 0.0001))) / ma.PI)); +voice_0_pitch_age = voice_0_age - floor(voice_0_age / repeat_interval) * repeat_interval; +voice_0_freq = ((max(20, 196 * pow(2.0, 0 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age)))) * select2(voice_0_pitch_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0); +voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 196 * pow(2.0, 0 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age)))) * select2(voice_0_pitch_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 196 * pow(2.0, 0 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age)))) * select2(voice_0_pitch_age < 0.065, 1.4983, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_0_age / max(0.32, 0.0001))) / ma.PI)); voice_0_colored = (voice_0_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_0_driven = ma.tanh(voice_0_colored * (1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0); voice_0_folded = voice_0_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_0_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -52,8 +54,9 @@ voice_0_formants = voice_0_phased; voice_0 = ((voice_0_phased * (1.0 - clip01(0 + patch_mod_formant_mix + 0.0)) + voice_0_formants * clip01(0 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_0_age, 0.001, 0, 0.8474576, 0.5, 0.045) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.1534); voice_1_age = age; -voice_1_freq = ((max(20, 293.66 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * select2(voice_1_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0); -voice_1_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 293.66 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * select2(voice_1_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 293.66 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * select2(voice_1_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_1_age / max(0.32, 0.0001))) / ma.PI)); +voice_1_pitch_age = voice_1_age - floor(voice_1_age / repeat_interval) * repeat_interval; +voice_1_freq = ((max(20, 293.66 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * select2(voice_1_pitch_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0); +voice_1_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 293.66 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * select2(voice_1_pitch_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 293.66 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * select2(voice_1_pitch_age < 0.065, 1.3348, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_1_age / max(0.32, 0.0001))) / ma.PI)); voice_1_colored = (voice_1_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_1_driven = ma.tanh(voice_1_colored * (1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0); voice_1_folded = voice_1_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_1_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -63,8 +66,9 @@ voice_1_formants = voice_1_phased; voice_1 = ((voice_1_phased * (1.0 - clip01(0 + patch_mod_formant_mix + 0.0)) + voice_1_formants * clip01(0 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_1_age, 0.001, 0, 0.8474576, 0.38, 0.035) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.09); voice_2_age = age; -voice_2_freq = ((max(20, 392 * pow(2.0, 0 * voice_2_age + 0.5 * 0 * voice_2_age * voice_2_age)))) * select2(voice_2_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0); -voice_2_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 392 * pow(2.0, 0 * voice_2_age + 0.5 * 0 * voice_2_age * voice_2_age)))) * select2(voice_2_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 392 * pow(2.0, 0 * voice_2_age + 0.5 * 0 * voice_2_age * voice_2_age)))) * select2(voice_2_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_2_age / max(0.32, 0.0001))) / ma.PI)); +voice_2_pitch_age = voice_2_age - floor(voice_2_age / repeat_interval) * repeat_interval; +voice_2_freq = ((max(20, 392 * pow(2.0, 0 * voice_2_pitch_age + 0.5 * 0 * voice_2_pitch_age * voice_2_pitch_age)))) * select2(voice_2_pitch_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0); +voice_2_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 392 * pow(2.0, 0 * voice_2_pitch_age + 0.5 * 0 * voice_2_pitch_age * voice_2_pitch_age)))) * select2(voice_2_pitch_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0 + sin(2.0 * ma.PI * os.phasor(1.0, ((max(20, 392 * pow(2.0, 0 * voice_2_pitch_age + 0.5 * 0 * voice_2_pitch_age * voice_2_pitch_age)))) * select2(voice_2_pitch_age < 0.065, 1.2599, 1.0) * pow(2.0, patch_mod_pitch + 0.0) * max(0.0, 2))) * (max(0.0, 1.8 + patch_mod_fm_index + 0.0) * exp(-voice_2_age / max(0.32, 0.0001))) / ma.PI)); voice_2_colored = (voice_2_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_2_driven = ma.tanh(voice_2_colored * (1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0); voice_2_folded = voice_2_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_2_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -74,7 +78,8 @@ voice_2_formants = voice_2_phased; voice_2 = ((voice_2_phased * (1.0 - clip01(0 + patch_mod_formant_mix + 0.0)) + voice_2_formants * clip01(0 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_2_age, 0.001, 0, 0.8474576, 0.32, 0.028) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.07); voice_3_age = age; -voice_3_freq = ((max(20, 3100 * pow(2.0, 0 * voice_3_age + 0.5 * 0 * voice_3_age * voice_3_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_3_pitch_age = voice_3_age - floor(voice_3_age / repeat_interval) * repeat_interval; +voice_3_freq = ((max(20, 3100 * pow(2.0, 0 * voice_3_pitch_age + 0.5 * 0 * voice_3_pitch_age * voice_3_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); voice_3_osc = no.noise; voice_3_colored = (voice_3_osc * (1.0 - clip01(1 + patch_mod_noise + 0.0)) + no.noise * clip01(1 + patch_mod_noise + 0.0)); voice_3_driven = ma.tanh(voice_3_colored * (1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.1 + patch_mod_drive + 0.0) * 12.0); diff --git a/tools/aqua-playground/lowered/bfxr-ui-bloom.dsp b/tools/aqua-playground/lowered/bfxr-ui-bloom.dsp index 2e48f40..c4236af 100644 --- a/tools/aqua-playground/lowered/bfxr-ui-bloom.dsp +++ b/tools/aqua-playground/lowered/bfxr-ui-bloom.dsp @@ -3,7 +3,8 @@ declare name "bfxr-ui-bloom"; declare options "[nvoices:1]"; time = ba.time / ma.SR; -age = time - floor(time / 0.18) * 0.18; +repeat_interval = 0.18; +age = time; clip01(x) = min(1.0, max(0.0, x)); wrap01(x) = x - floor(x); slew(rate,x) = x : *(1.0 - exp((0.0 - max(0.0001, rate)) / ma.SR)) : + ~ *(exp((0.0 - max(0.0001, rate)) / ma.SR)); @@ -41,8 +42,9 @@ patch_mod_formant_mix = 0.0; patch_mod_fm_index = 0.0; voice_0_age = age; -voice_0_freq = ((max(20, 392 * pow(2.0, 0.18 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age)))) * select2(voice_0_age < 0.08, 1.25, 1.0) * pow(2.0, patch_mod_pitch + 0.0); -voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 392 * pow(2.0, 0.18 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age)))) * select2(voice_0_age < 0.08, 1.25, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0)); +voice_0_pitch_age = voice_0_age - floor(voice_0_age / repeat_interval) * repeat_interval; +voice_0_freq = ((max(20, 392 * pow(2.0, 0.18 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age)))) * select2(voice_0_pitch_age < 0.08, 1.25, 1.0) * pow(2.0, patch_mod_pitch + 0.0); +voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 392 * pow(2.0, 0.18 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age)))) * select2(voice_0_pitch_age < 0.08, 1.25, 1.0) * pow(2.0, patch_mod_pitch + 0.0)) + 0)); voice_0_colored = (voice_0_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_0_driven = ma.tanh(voice_0_colored * (1.0 + clip01(0.06 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.06 + patch_mod_drive + 0.0) * 12.0); voice_0_folded = voice_0_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_0_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -52,8 +54,9 @@ voice_0_formants = voice_0_phased; voice_0 = ((voice_0_phased * (1.0 - clip01(0 + patch_mod_formant_mix + 0.0)) + voice_0_formants * clip01(0 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_0_age, 0, 0, 0.8196721, 0.5, 0.05) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.1464); voice_1_age = age; -voice_1_freq = ((max(20, 588 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); -voice_1_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 588 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); +voice_1_pitch_age = voice_1_age - floor(voice_1_age / repeat_interval) * repeat_interval; +voice_1_freq = ((max(20, 588 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_1_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 588 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); voice_1_colored = (voice_1_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_1_driven = ma.tanh(voice_1_colored * (1.0 + clip01(0.06 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.06 + patch_mod_drive + 0.0) * 12.0); voice_1_folded = voice_1_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_1_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); diff --git a/tools/aqua-playground/lowered/examples-language-tour.dsp b/tools/aqua-playground/lowered/examples-language-tour.dsp index afced2b..f9f5a5f 100644 --- a/tools/aqua-playground/lowered/examples-language-tour.dsp +++ b/tools/aqua-playground/lowered/examples-language-tour.dsp @@ -3,7 +3,8 @@ declare name "examples-language-tour"; declare options "[nvoices:1]"; time = ba.time / ma.SR; -age = time - floor(time / 0.4739614) * 0.4739614; +repeat_interval = 0.4739614; +age = time; clip01(x) = min(1.0, max(0.0, x)); wrap01(x) = x - floor(x); slew(rate,x) = x : *(1.0 - exp((0.0 - max(0.0001, rate)) / ma.SR)) : + ~ *(exp((0.0 - max(0.0001, rate)) / ma.SR)); @@ -41,8 +42,9 @@ patch_mod_formant_mix = 0 + 0.08 * lfo_sin(5, 0) + 0 + 0.12 * lfo_tri(2, 0); patch_mod_fm_index = 0.0; voice_0_age = age; -voice_0_freq = ((max(20, 220 * pow(2.0, 0 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 5) * 0.02))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); -voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 220 * pow(2.0, 0 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 5) * 0.02))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); +voice_0_pitch_age = voice_0_age - floor(voice_0_age / repeat_interval) * repeat_interval; +voice_0_freq = ((max(20, 220 * pow(2.0, 0 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 5) * 0.02))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 220 * pow(2.0, 0 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 5) * 0.02))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); voice_0_colored = (voice_0_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_0_driven = ma.tanh(voice_0_colored * (1.0 + clip01(0 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0 + patch_mod_drive + 0.0) * 12.0); voice_0_folded = voice_0_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_0_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -52,8 +54,9 @@ voice_0_formants = ((voice_0_phased : fi.resonbp(620, 6.888889, 1.0)) * 1 + (voi voice_0 = ((voice_0_phased * (1.0 - clip01(0.45 + patch_mod_formant_mix + 0.0)) + voice_0_formants * clip01(0.45 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_0_age, 0.002, 0, 1, 0.2, 0.03) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.12); voice_1_age = age; -voice_1_freq = ((max(20, 440 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); -voice_1_osc = 1.0 - 4.0 * abs(wrap01(os.phasor(1.0, ((max(20, 440 * pow(2.0, 0 * voice_1_age + 0.5 * 0 * voice_1_age * voice_1_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0) - 0.5); +voice_1_pitch_age = voice_1_age - floor(voice_1_age / repeat_interval) * repeat_interval; +voice_1_freq = ((max(20, 440 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_1_osc = 1.0 - 4.0 * abs(wrap01(os.phasor(1.0, ((max(20, 440 * pow(2.0, 0 * voice_1_pitch_age + 0.5 * 0 * voice_1_pitch_age * voice_1_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0) - 0.5); voice_1_colored = (voice_1_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_1_driven = ma.tanh(voice_1_colored * (1.0 + clip01(0 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0 + patch_mod_drive + 0.0) * 12.0); voice_1_folded = voice_1_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_1_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); @@ -63,8 +66,9 @@ voice_1_formants = voice_1_phased; voice_1 = ((voice_1_phased * (1.0 - clip01(0 + patch_mod_formant_mix + 0.0)) + voice_1_formants * clip01(0 + patch_mod_formant_mix + 0.0)) * oneshot_adsr_at(voice_1_age, 0, 0, 1, 0.18, 0.02) * max(0.0, 1.0 + patch_mod_gain + 0.0) * 0.04); voice_2_age = age; -voice_2_freq = ((max(20, 230.3403 * pow(2.0, 0.7460228 * voice_2_age + 0.5 * 0.00000008 * voice_2_age * voice_2_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); -voice_2_osc = 2.0 * wrap01(os.phasor(1.0, ((max(20, 230.3403 * pow(2.0, 0.7460228 * voice_2_age + 0.5 * 0.00000008 * voice_2_age * voice_2_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0) - 1.0; +voice_2_pitch_age = voice_2_age - floor(voice_2_age / repeat_interval) * repeat_interval; +voice_2_freq = ((max(20, 230.3403 * pow(2.0, 0.7460228 * voice_2_pitch_age + 0.5 * 0.00000008 * voice_2_pitch_age * voice_2_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_2_osc = 2.0 * wrap01(os.phasor(1.0, ((max(20, 230.3403 * pow(2.0, 0.7460228 * voice_2_pitch_age + 0.5 * 0.00000008 * voice_2_pitch_age * voice_2_pitch_age)))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0) - 1.0; voice_2_colored = (voice_2_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_2_driven = ma.tanh(voice_2_colored * (1.0 + clip01(0.1212819 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.1212819 + patch_mod_drive + 0.0) * 12.0); voice_2_folded = voice_2_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_2_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0); diff --git a/tools/aqua-playground/lowered/sfxr-powerup.dsp b/tools/aqua-playground/lowered/sfxr-powerup.dsp index cf8bf37..0494b9a 100644 --- a/tools/aqua-playground/lowered/sfxr-powerup.dsp +++ b/tools/aqua-playground/lowered/sfxr-powerup.dsp @@ -3,7 +3,8 @@ declare name "sfxr-powerup"; declare options "[nvoices:1]"; time = ba.time / ma.SR; -age = time - floor(time / 0.11315) * 0.11315; +repeat_interval = 0.11315; +age = time; clip01(x) = min(1.0, max(0.0, x)); wrap01(x) = x - floor(x); slew(rate,x) = x : *(1.0 - exp((0.0 - max(0.0001, rate)) / ma.SR)) : + ~ *(exp((0.0 - max(0.0001, rate)) / ma.SR)); @@ -41,8 +42,9 @@ patch_mod_formant_mix = 0.0; patch_mod_fm_index = 0.0; voice_0_age = age; -voice_0_freq = ((max(20, 57.5946 * pow(2.0, -0.208544 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 3.9602) * 0.09))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); -voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 57.5946 * pow(2.0, -0.208544 * voice_0_age + 0.5 * 0 * voice_0_age * voice_0_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 3.9602) * 0.09))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); +voice_0_pitch_age = voice_0_age - floor(voice_0_age / repeat_interval) * repeat_interval; +voice_0_freq = ((max(20, 57.5946 * pow(2.0, -0.208544 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 3.9602) * 0.09))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0); +voice_0_osc = sin(2.0 * ma.PI * wrap01(os.phasor(1.0, ((max(20, 57.5946 * pow(2.0, -0.208544 * voice_0_pitch_age + 0.5 * 0 * voice_0_pitch_age * voice_0_pitch_age))) * (1.0 + select2(voice_0_age < 0, 0.0, sin(2.0 * ma.PI * (voice_0_age - 0) * 3.9602) * 0.09))) * 1.0 * pow(2.0, patch_mod_pitch + 0.0)) + 0)); voice_0_colored = (voice_0_osc * (1.0 - clip01(0 + patch_mod_noise + 0.0)) + no.noise * clip01(0 + patch_mod_noise + 0.0)); voice_0_driven = ma.tanh(voice_0_colored * (1.0 + clip01(0.12 + patch_mod_drive + 0.0) * 12.0)) / ma.tanh(1.0 + clip01(0.12 + patch_mod_drive + 0.0) * 12.0); voice_0_folded = voice_0_driven * (1.0 - clip01(0 + patch_mod_fold + 0.0)) + fold(voice_0_driven * (1.0 + clip01(0 + patch_mod_fold + 0.0) * 3.5)) * clip01(0 + patch_mod_fold + 0.0);