Files
rsgp/src/lib/audio/engines/Sub8.ts
2025-10-13 17:35:47 +02:00

136 lines
4.1 KiB
TypeScript

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<Sub8Params> {
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)
};
}
}