143 lines
4.6 KiB
TypeScript
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),
|
|
};
|
|
}
|
|
}
|