import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine'; import type { PitchLock } from './base/SynthEngine'; interface Sub8Params { frequency: number; filterCutoff: number; filterEnvAmount: number; resonance: number; saturation: number; osc1Level: number; osc2Level: number; osc3Level: number; attack: number; decay: number; sustain: number; release: number; } export class Sub8 extends CsoundEngine { getName(): string { return 'Sub8'; } getDescription(): string { return 'Multi-oscillator subtractive synth with diode ladder filter and saturation'; } getType(): 'generative' | 'sample' | 'input' { return 'generative'; } getCategory(): 'Additive' | 'Subtractive' | 'FM' | 'Percussion' | 'Noise' | 'Physical' | 'Modulation' | 'Experimental' | 'Utility' { return 'Subtractive'; } getOrchestra(): string { return ` instr 1 ifreq = chnget("frequency") iamp = 0.5 ifilterCutoff = chnget("filterCutoff") ifilterEnvAmount = chnget("filterEnvAmount") ireso = chnget("resonance") isat = chnget("saturation") iosc1Level = chnget("osc1Level") iosc2Level = chnget("osc2Level") iosc3Level = chnget("osc3Level") iatt = chnget("attack") * p3 idec = chnget("decay") * p3 isus = chnget("sustain") irel = chnget("release") * p3 ; Three detuned oscillators asig1 = vco2(iamp * iosc1Level, ifreq, 10) asig2 = vco2(iamp * iosc2Level, ifreq * 2, 10) asig3 = vco2(iamp * iosc3Level, ifreq * 3.5, 12) asig = asig1 + asig2 + asig3 ; Saturation asig = tanh(asig * isat) / tanh(isat) ; Filter envelope aenv = madsr(iatt, idec, isus, irel) kcutoff = ifilterCutoff + (aenv * ifilterEnvAmount) kcutoff = limit(kcutoff, 20, 18000) ; Diode ladder filter asig = diode_ladder(asig, kcutoff, ireso) ; Final amplitude envelope asig = asig * aenv * 0.7 outs asig, asig endin `; } getParametersForCsound(params: Sub8Params): CsoundParameter[] { return [ { channelName: 'frequency', value: params.frequency }, { channelName: 'filterCutoff', value: params.filterCutoff }, { channelName: 'filterEnvAmount', value: params.filterEnvAmount }, { channelName: 'resonance', value: params.resonance }, { channelName: 'saturation', value: params.saturation }, { channelName: 'osc1Level', value: params.osc1Level }, { channelName: 'osc2Level', value: params.osc2Level }, { channelName: 'osc3Level', value: params.osc3Level }, { channelName: 'attack', value: params.attack }, { channelName: 'decay', value: params.decay }, { channelName: 'sustain', value: params.sustain }, { channelName: 'release', value: params.release } ]; } randomParams(pitchLock?: PitchLock): Sub8Params { const frequency = pitchLock?.enabled ? pitchLock.frequency : 55 * Math.pow(2, Math.random() * 5); return { frequency, filterCutoff: 200 + Math.random() * 3800, filterEnvAmount: Math.random() * 6000, resonance: 0.5 + Math.random() * 14.5, saturation: 1 + Math.random() * 9, osc1Level: 0.5 + Math.random() * 0.5, osc2Level: Math.random() * 0.5, osc3Level: Math.random() * 0.3, attack: Math.random() * 0.1, decay: 0.05 + Math.random() * 0.3, sustain: 0.2 + Math.random() * 0.6, release: 0.05 + Math.random() * 0.4 }; } mutateParams(params: Sub8Params, mutationAmount = 0.2, pitchLock?: PitchLock): Sub8Params { const mutate = (value: number, min: number, max: number) => { const change = (Math.random() - 0.5) * 2 * mutationAmount * (max - min); return Math.max(min, Math.min(max, value + change)); }; return { frequency: pitchLock?.enabled ? pitchLock.frequency : mutate(params.frequency, 55, 55 * Math.pow(2, 5)), filterCutoff: mutate(params.filterCutoff, 200, 4000), filterEnvAmount: mutate(params.filterEnvAmount, 0, 6000), resonance: mutate(params.resonance, 0.5, 15), saturation: mutate(params.saturation, 1, 10), osc1Level: mutate(params.osc1Level, 0.5, 1), osc2Level: mutate(params.osc2Level, 0, 0.5), osc3Level: mutate(params.osc3Level, 0, 0.3), attack: mutate(params.attack, 0, 0.1), decay: mutate(params.decay, 0.05, 0.35), sustain: mutate(params.sustain, 0.2, 0.8), release: mutate(params.release, 0.05, 0.45) }; } }