From fb92c3ae2aed50da6974142790e353857f840e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 13 Oct 2025 14:23:27 +0200 Subject: [PATCH] new interesting generator --- src/lib/audio/engines/CombResonator.ts | 350 +++++++++++++++++++++++++ src/lib/audio/engines/registry.ts | 2 + 2 files changed, 352 insertions(+) create mode 100644 src/lib/audio/engines/CombResonator.ts diff --git a/src/lib/audio/engines/CombResonator.ts b/src/lib/audio/engines/CombResonator.ts new file mode 100644 index 0000000..f52d277 --- /dev/null +++ b/src/lib/audio/engines/CombResonator.ts @@ -0,0 +1,350 @@ +import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine'; +import type { PitchLock } from './base/SynthEngine'; + +enum SourceType { + Saw = 0, + Pulse = 1, + NoiseBurst = 2, + Triangle = 3, +} + +interface CombParams { + feedback: number; + brightness: number; +} + +interface ResonatorParams { + frequency: number; + resonance: number; + envAmount: number; + attack: number; + decay: number; + sustain: number; + release: number; +} + +interface DelayParams { + time1: number; + time2: number; + feedback: number; + filterFreq: number; + filterSweep: number; + mix: number; +} + +export interface CombResonatorParams { + baseFreq: number; + sourceType: SourceType; + sourceDecay: number; + comb: CombParams; + resonator: ResonatorParams; + delay: DelayParams; + stereoWidth: number; +} + +export class CombResonator extends CsoundEngine { + getName(): string { + return 'CombRes'; + } + + getDescription(): string { + return 'Comb filter and resonator for metallic and bell-like sounds'; + } + + getType() { + return 'generative' as const; + } + + getCategory() { + return 'Subtractive' as const; + } + + protected getOrchestra(): string { + return ` +instr 1 + ibasefreq chnget "basefreq" + isourcetype chnget "sourcetype" + isourcedecay chnget "sourcedecay" + icombfeedback chnget "comb_feedback" + icombbright chnget "comb_brightness" + iresfreq chnget "res_frequency" + iresres chnget "res_resonance" + iresenvamt chnget "res_envamt" + iresattack chnget "res_attack" + iresdecay chnget "res_decay" + iressustain chnget "res_sustain" + iresrelease chnget "res_release" + ideltime1 chnget "delay_time1" + ideltime2 chnget "delay_time2" + idelfeedback chnget "delay_feedback" + ifilterfreq chnget "delay_filterfreq" + ifiltersweep chnget "delay_filtersweep" + idelmix chnget "delay_mix" + istereo chnget "stereowidth" + + idur = p3 + + ; Convert ratios to time values + iresatt = iresattack * idur + iresdec = iresdecay * idur + iresrel = iresrelease * idur + isrcdec = isourcedecay * idur + idt1 = ideltime1 * idur + idt2 = ideltime2 * idur + + ; Stereo detuning + idetune = 1 + (istereo * 0.003) + ifreqL = ibasefreq / idetune + ifreqR = ibasefreq * idetune + + ; Source envelope - sharp attack, exponential decay + ksrcenv expon 1, isrcdec, 0.001 + + ; Generate source signals with more amplitude + if isourcetype == 0 then + ; Saw wave with harmonics + asrcL vco2 ksrcenv * 0.8, ifreqL, 0 + asrcR vco2 ksrcenv * 0.8, ifreqR, 0 + elseif isourcetype == 1 then + ; Pulse wave + asrcL vco2 ksrcenv * 0.8, ifreqL, 2, 0.3 + asrcR vco2 ksrcenv * 0.8, ifreqR, 2, 0.3 + elseif isourcetype == 2 then + ; Noise burst with pitch + anoise1 noise ksrcenv * 0.6, 0 + anoise2 noise ksrcenv * 0.6, 0 + asrcL butterbp anoise1, ifreqL, ifreqL * 2 + asrcR butterbp anoise2, ifreqR, ifreqR * 2 + else + ; Triangle wave + asrcL vco2 ksrcenv * 0.8, ifreqL, 12 + asrcR vco2 ksrcenv * 0.8, ifreqR, 12 + endif + + ; Direct path for immediate sound (no delay) + adirectL = asrcL * 0.3 + adirectR = asrcR * 0.3 + + ; Comb filter using vcomb for better feedback control + idelaytimeL = 1 / ifreqL + idelaytimeR = 1 / ifreqR + ilooptime = 0.1 + + acombL vcomb asrcL, idelaytimeL, ilooptime, icombfeedback + acombR vcomb asrcR, idelaytimeR, ilooptime, icombfeedback + + ; Damping filter + adampl tone acombL, ibasefreq * icombbright + adampR tone acombR, ibasefreq * icombbright + + ; Resonator envelope for filter sweep + kresenv madsr iresatt, iresdec, iressustain, iresrel + kresfreq = iresfreq + (kresenv * iresenvamt * ibasefreq * 4) + kresfreq = limit(kresfreq, 80, 18000) + + ; Multiple resonators for richer tone + kbw = kresfreq / iresres + ares1L butterbp adampl, kresfreq, kbw + ares1R butterbp adampR, kresfreq, kbw + + ares2L butterbp adampl, kresfreq * 1.5, kbw * 1.2 + ares2R butterbp adampR, kresfreq * 1.5, kbw * 1.2 + + ; Mix resonators + aresL = ares1L + (ares2L * 0.5) + aresR = ares1R + (ares2R * 0.5) + + ; Dry signal (this plays immediately) + adryL = (adampl * 0.2) + (aresL * 0.8) + adryR = (adampR * 0.2) + (aresR * 0.8) + + ; Direct signal envelope: loud at start, fades out quickly + kdirectenv linseg 1, idur * 0.05, 0, idur * 0.95, 0 + + ; Crossfade envelope: start with dry signal, fade in delays + kdryenv linseg 0.6, idur * 0.15, 0.3, idur * 0.85, 0.2 + kwetenv linseg 0, idur * 0.1, 1, idur * 0.9, 1 + + ; Extreme filter sweep envelopes with multiple segments + kfilter1 linseg ifilterfreq, idur * 0.2, ifilterfreq + (ifiltersweep * 12000), idur * 0.3, ifilterfreq - (ifiltersweep * 5000), idur * 0.5, ifilterfreq + (ifiltersweep * 15000) + kfilter1 = limit(kfilter1, 250, 19000) + + kfilter2 expseg ifilterfreq * 2, idur * 0.15, ifilterfreq * 0.3 + 200, idur * 0.35, ifilterfreq * 3 + 100, idur * 0.5, ifilterfreq + (ifiltersweep * 10000) + 100 + kfilter2 = limit(kfilter2, 200, 18000) + + kfilter3 linseg ifilterfreq * 0.5, idur * 0.25, ifilterfreq + (ifiltersweep * 8000), idur * 0.25, ifilterfreq * 2, idur * 0.5, ifilterfreq - (ifiltersweep * 3000) + kfilter3 = limit(kfilter3, 300, 16000) + + kfilter4 expon ifilterfreq + 100, idur, ifilterfreq + (ifiltersweep * 14000) + 100 + kfilter4 = limit(kfilter4, 350, 17000) + + ; LFO for delay time modulation + klfo1 lfo 0.03, 3 + (ifiltersweep * 2) + klfo2 lfo 0.04, 5 - (ifiltersweep * 1.5) + + ; Multi-tap delay line 1 (Left -> Right) with modulation + abuf1 delayr idt1 * 1.1 + kdt1a = (idt1 * 0.2) + klfo1 + kdt1b = (idt1 * 0.45) - klfo2 + kdt1c = (idt1 * 0.75) + (klfo1 * 0.5) + kdt1d = idt1 + adel1a deltap3 kdt1a + adel1b deltap3 kdt1b + adel1c deltap3 kdt1c + adel1d deltap3 kdt1d + delayw adryL + (adel1d * idelfeedback * 0.95) + + afilt1a butterbp adel1a, kfilter1, kfilter1 * 0.15 + afilt1b butterbp adel1b, kfilter2, kfilter2 * 0.2 + afilt1c butterbp adel1c, kfilter3, kfilter3 * 0.25 + afilt1d butterbp adel1d, kfilter4, kfilter4 * 0.18 + + adelR = (afilt1a * 0.6) + (afilt1b * 0.8) + (afilt1c * 0.9) + (afilt1d * 0.7) + + ; Multi-tap delay line 2 (Right -> Left) with modulation + abuf2 delayr idt2 * 1.1 + kdt2a = (idt2 * 0.18) - klfo2 + kdt2b = (idt2 * 0.42) + klfo1 + kdt2c = (idt2 * 0.7) - (klfo2 * 0.5) + kdt2d = idt2 + adel2a deltap3 kdt2a + adel2b deltap3 kdt2b + adel2c deltap3 kdt2c + adel2d deltap3 kdt2d + delayw adryR + (adel2d * idelfeedback * 0.95) + + afilt2a butterbp adel2a, kfilter1 * 1.4, kfilter1 * 0.12 + afilt2b butterbp adel2b, kfilter2 * 0.7, kfilter2 * 0.22 + afilt2c butterbp adel2c, kfilter3 * 1.2, kfilter3 * 0.16 + afilt2d butterbp adel2d, kfilter4 * 0.9, kfilter4 * 0.2 + + adelL = (afilt2a * 0.7) + (afilt2b * 0.6) + (afilt2c * 0.85) + (afilt2d * 0.8) + + ; Additional chaotic resonant delays + abuf3 delayr idt1 * 1.6 + kdt3 = (idt1 * 1.4) + (klfo1 * 2) + adel3 deltap3 kdt3 + delayw (adryL + adryR) * 0.5 + (adel3 * idelfeedback * 0.85) + afilt3 butterbp adel3, kfilter2 * 0.6, kfilter2 * 0.1 + + abuf4 delayr idt2 * 1.8 + kdt4 = (idt2 * 1.6) - (klfo2 * 2) + adel4 deltap3 kdt4 + delayw (adryR + adryL) * 0.5 + (adel4 * idelfeedback * 0.8) + afilt4 butterbp adel4, kfilter3 * 1.3, kfilter3 * 0.12 + + abuf5 delayr idt1 * 2.2 + kdt5 = (idt1 * 2.0) + klfo1 + klfo2 + adel5 deltap3 kdt5 + delayw (adelL + adelR) * 0.4 + (adel5 * idelfeedback * 0.75) + afilt5 butterbp adel5, kfilter4 * 0.8, kfilter4 * 0.08 + + ; Mix: direct signal (immediate), dry (soon after), delays (build up) + amixL = (adirectL * kdirectenv) + (adryL * kdryenv) + ((adelL * 1.2 + afilt3 * 0.8 + afilt4 * 0.7 + afilt5 * 0.6) * idelmix * kwetenv) + amixR = (adirectR * kdirectenv) + (adryR * kdryenv) + ((adelR * 1.2 + afilt4 * 0.8 + afilt3 * 0.7 + afilt5 * 0.6) * idelmix * kwetenv) + + outs amixL, amixR +endin +`; + } + + protected getParametersForCsound(params: CombResonatorParams): CsoundParameter[] { + return [ + { channelName: 'basefreq', value: params.baseFreq }, + { channelName: 'sourcetype', value: params.sourceType }, + { channelName: 'sourcedecay', value: params.sourceDecay }, + { channelName: 'comb_feedback', value: params.comb.feedback }, + { channelName: 'comb_brightness', value: params.comb.brightness }, + { channelName: 'res_frequency', value: params.resonator.frequency }, + { channelName: 'res_resonance', value: params.resonator.resonance }, + { channelName: 'res_envamt', value: params.resonator.envAmount }, + { channelName: 'res_attack', value: params.resonator.attack }, + { channelName: 'res_decay', value: params.resonator.decay }, + { channelName: 'res_sustain', value: params.resonator.sustain }, + { channelName: 'res_release', value: params.resonator.release }, + { channelName: 'delay_time1', value: params.delay.time1 }, + { channelName: 'delay_time2', value: params.delay.time2 }, + { channelName: 'delay_feedback', value: params.delay.feedback }, + { channelName: 'delay_filterfreq', value: params.delay.filterFreq }, + { channelName: 'delay_filtersweep', value: params.delay.filterSweep }, + { channelName: 'delay_mix', value: params.delay.mix }, + { channelName: 'stereowidth', value: params.stereoWidth }, + ]; + } + + randomParams(pitchLock?: PitchLock): CombResonatorParams { + let baseFreq: number; + if (pitchLock?.enabled) { + baseFreq = pitchLock.frequency; + } else { + const baseFreqChoices = [82.4, 110, 146.8, 220, 293.7, 440, 587.3, 880]; + baseFreq = this.randomChoice(baseFreqChoices) * this.randomRange(0.97, 1.03); + } + + return { + baseFreq, + sourceType: this.randomInt(0, 3) as SourceType, + sourceDecay: this.randomRange(0.02, 0.15), + comb: { + feedback: this.randomRange(0.7, 0.95), + brightness: this.randomRange(3, 20), + }, + resonator: { + frequency: baseFreq * this.randomChoice([1, 1.3, 1.6, 2, 2.4, 3, 3.5, 4, 5]), + resonance: this.randomRange(8, 80), + envAmount: this.randomRange(0.3, 2.0), + attack: this.randomRange(0.001, 0.05), + decay: this.randomRange(0.15, 0.6), + sustain: this.randomRange(0.05, 0.4), + release: this.randomRange(0.3, 0.8), + }, + delay: { + time1: this.randomRange(0.08, 0.35), + time2: this.randomRange(0.1, 0.4), + feedback: this.randomRange(0.82, 0.98), + filterFreq: this.randomRange(400, 5000), + filterSweep: this.randomRange(-1.2, 2.0), + mix: this.randomRange(0.6, 1.0), + }, + stereoWidth: this.randomRange(0.2, 0.8), + }; + } + + mutateParams( + params: CombResonatorParams, + mutationAmount: number = 0.15, + pitchLock?: PitchLock + ): CombResonatorParams { + const baseFreq = pitchLock?.enabled ? pitchLock.frequency : params.baseFreq; + + return { + baseFreq, + sourceType: Math.random() < 0.15 ? (this.randomInt(0, 3) as SourceType) : params.sourceType, + sourceDecay: this.mutateValue(params.sourceDecay, mutationAmount, 0.02, 0.5), + comb: { + feedback: this.mutateValue(params.comb.feedback, mutationAmount, 0.3, 0.95), + brightness: this.mutateValue(params.comb.brightness, mutationAmount, 1, 20), + }, + resonator: { + frequency: Math.random() < 0.2 + ? baseFreq * this.randomChoice([1, 1.5, 2, 2.5, 3, 3.5, 4]) + : this.mutateValue(params.resonator.frequency, mutationAmount, baseFreq * 0.5, baseFreq * 6), + resonance: this.mutateValue(params.resonator.resonance, mutationAmount, 3, 80), + envAmount: this.mutateValue(params.resonator.envAmount, mutationAmount, 0, 2), + attack: this.mutateValue(params.resonator.attack, mutationAmount, 0.001, 0.2), + decay: this.mutateValue(params.resonator.decay, mutationAmount, 0.05, 0.6), + sustain: this.mutateValue(params.resonator.sustain, mutationAmount, 0, 0.7), + release: this.mutateValue(params.resonator.release, mutationAmount, 0.1, 0.9), + }, + delay: { + time1: this.mutateValue(params.delay.time1, mutationAmount, 0.05, 0.5), + time2: this.mutateValue(params.delay.time2, mutationAmount, 0.08, 0.55), + feedback: this.mutateValue(params.delay.feedback, mutationAmount, 0.75, 0.99), + filterFreq: this.mutateValue(params.delay.filterFreq, mutationAmount, 350, 7000), + filterSweep: this.mutateValue(params.delay.filterSweep, mutationAmount, -1.5, 2.5), + mix: this.mutateValue(params.delay.mix, mutationAmount, 0.5, 1.0), + }, + stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 1), + }; + } +} diff --git a/src/lib/audio/engines/registry.ts b/src/lib/audio/engines/registry.ts index 0bc39a0..84d3bb5 100644 --- a/src/lib/audio/engines/registry.ts +++ b/src/lib/audio/engines/registry.ts @@ -25,6 +25,7 @@ import { FMTomTom } from './FMTomTom'; import { RingCymbal } from './RingCymbal'; import { AdditiveBass } from './AdditiveBass'; import { FeedbackSnare } from './FeedbackSnare'; +import { CombResonator } from './CombResonator'; export const engines: SynthEngine[] = [ new Sample(), @@ -52,5 +53,6 @@ export const engines: SynthEngine[] = [ new ParticleNoise(), new DustNoise(), new SubtractiveThreeOsc(), + new CombResonator(), new MassiveAdditive(), ];