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