Files
rsgp/src/lib/audio/engines/FormantPopDrum.ts

143 lines
4.6 KiB
TypeScript

import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
import type { PitchLock } from './base/SynthEngine';
interface FormantPopDrumParams {
formant1Freq: number;
formant1Width: number;
formant2Freq: number;
formant2Width: number;
noiseDecay: number;
ampAttack: number;
ampDecay: number;
brightness: number;
stereoSpread: number;
}
export class FormantPopDrum extends CsoundEngine<FormantPopDrumParams> {
getName(): string {
return 'Formant Pop Drum';
}
getDescription(): string {
return 'Short noise burst through dual bandpass filters creating marimba-like or wooden drum tones';
}
getType() {
return 'generative' as const;
}
getCategory() {
return 'Percussion' as const;
}
protected getOrchestra(): string {
return `
instr 1
iF1Freq chnget "formant1Freq"
iF1Width chnget "formant1Width"
iF2Freq chnget "formant2Freq"
iF2Width chnget "formant2Width"
iNoiseDecay chnget "noiseDecay"
iAmpAttack chnget "ampAttack"
iAmpDecay chnget "ampDecay"
iBrightness chnget "brightness"
iStereoSpread chnget "stereoSpread"
idur = p3
iNoiseDecayTime = iNoiseDecay * idur
iAmpAttackTime = iAmpAttack * idur
iAmpDecayTime = iAmpDecay * idur
; Declick envelope for noise (very short to avoid clicks)
kDeclickEnv linseg 0, 0.001, 1, iNoiseDecayTime, 0, idur - iNoiseDecayTime - 0.001, 0
; Generate random noise
aNoise noise 1, 0
; Apply declick envelope to noise
aNoiseEnv = aNoise * kDeclickEnv
; First bandpass filter (formant 1)
aFormant1 butterbp aNoiseEnv, iF1Freq, iF1Width
; Second bandpass filter (formant 2)
aFormant2 butterbp aNoiseEnv, iF2Freq, iF2Width
; Mix formants with brightness control
aMix = aFormant1 * (1 - iBrightness * 0.5) + aFormant2 * iBrightness
; Amplitude envelope (exponential decay)
kAmpEnv expseg 0.001, iAmpAttackTime, 1, iAmpDecayTime, 0.001, idur - iAmpAttackTime - iAmpDecayTime, 0.001
; Apply amplitude envelope
aOut = aMix * kAmpEnv
; Stereo output with slight frequency offset for right channel
iF1FreqR = iF1Freq * (1 + iStereoSpread * 0.02)
iF2FreqR = iF2Freq * (1 + iStereoSpread * 0.02)
aFormant1R butterbp aNoiseEnv, iF1FreqR, iF1Width
aFormant2R butterbp aNoiseEnv, iF2FreqR, iF2Width
aMixR = aFormant1R * (1 - iBrightness * 0.5) + aFormant2R * iBrightness
aOutR = aMixR * kAmpEnv
outs aOut, aOutR
endin
`;
}
protected getParametersForCsound(params: FormantPopDrumParams): CsoundParameter[] {
return [
{ channelName: 'formant1Freq', value: params.formant1Freq },
{ channelName: 'formant1Width', value: params.formant1Width },
{ channelName: 'formant2Freq', value: params.formant2Freq },
{ channelName: 'formant2Width', value: params.formant2Width },
{ channelName: 'noiseDecay', value: params.noiseDecay },
{ channelName: 'ampAttack', value: params.ampAttack },
{ channelName: 'ampDecay', value: params.ampDecay },
{ channelName: 'brightness', value: params.brightness },
{ channelName: 'stereoSpread', value: params.stereoSpread },
];
}
randomParams(pitchLock?: PitchLock): FormantPopDrumParams {
const formant1FreqChoices = [200, 250, 300, 400, 500, 600, 800, 1000];
const formant1Freq = pitchLock?.enabled
? pitchLock.frequency
: this.randomChoice(formant1FreqChoices) * this.randomRange(0.9, 1.1);
return {
formant1Freq,
formant1Width: this.randomRange(30, 120),
formant2Freq: formant1Freq * this.randomRange(1.5, 3.5),
formant2Width: this.randomRange(40, 150),
noiseDecay: this.randomRange(0.05, 0.3),
ampAttack: this.randomRange(0.001, 0.02),
ampDecay: this.randomRange(0.1, 0.6),
brightness: this.randomRange(0.2, 0.8),
stereoSpread: this.randomRange(0, 0.5),
};
}
mutateParams(
params: FormantPopDrumParams,
mutationAmount: number = 0.15,
pitchLock?: PitchLock
): FormantPopDrumParams {
const formant1Freq = pitchLock?.enabled ? pitchLock.frequency : params.formant1Freq;
return {
formant1Freq,
formant1Width: this.mutateValue(params.formant1Width, mutationAmount, 20, 200),
formant2Freq: this.mutateValue(params.formant2Freq, mutationAmount, 300, 4000),
formant2Width: this.mutateValue(params.formant2Width, mutationAmount, 30, 250),
noiseDecay: this.mutateValue(params.noiseDecay, mutationAmount, 0.02, 0.5),
ampAttack: this.mutateValue(params.ampAttack, mutationAmount, 0.001, 0.05),
ampDecay: this.mutateValue(params.ampDecay, mutationAmount, 0.05, 0.8),
brightness: this.mutateValue(params.brightness, mutationAmount, 0, 1),
stereoSpread: this.mutateValue(params.stereoSpread, mutationAmount, 0, 1),
};
}
}