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), }; } }