136 lines
4.1 KiB
TypeScript
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)
|
|
};
|
|
}
|
|
}
|