import * as Tone from 'tone'; import { DrumPattern } from '@/types/audio'; import { getRandomPattern, generateRandomPattern } from './patterns'; export class DrumMachine { private kick: Tone.MembraneSynth; private snare: Tone.NoiseSynth; private hihat: Tone.NoiseSynth; private openhat: Tone.NoiseSynth; private sequence: Tone.Sequence | null = null; private pattern: DrumPattern; private output: Tone.Gain; private lowpass: Tone.Filter; constructor(destination: Tone.InputNode) { this.output = new Tone.Gain(0.8); this.lowpass = new Tone.Filter({ frequency: 8000, type: 'lowpass', rolloff: -12, }); // Kick drum - deep and punchy this.kick = new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 6, oscillator: { type: 'sine' }, envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 0.4, }, }); // Snare - filtered noise this.snare = new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.2, sustain: 0, release: 0.1, }, }); const snareFilter = new Tone.Filter({ frequency: 5000, type: 'bandpass', Q: 1, }); this.snare.connect(snareFilter); snareFilter.connect(this.lowpass); // Closed hi-hat - high filtered noise this.hihat = new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.02, }, }); const hihatFilter = new Tone.Filter({ frequency: 10000, type: 'highpass', }); this.hihat.connect(hihatFilter); hihatFilter.connect(this.lowpass); // Open hi-hat this.openhat = new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.3, sustain: 0, release: 0.15, }, }); const openhatFilter = new Tone.Filter({ frequency: 8000, type: 'highpass', }); this.openhat.connect(openhatFilter); openhatFilter.connect(this.lowpass); // Connect kick directly to lowpass this.kick.connect(this.lowpass); // Chain: lowpass -> output -> destination this.lowpass.connect(this.output); this.output.connect(destination); // Initialize with a random pattern this.pattern = getRandomPattern(); } createSequence(onStep?: (step: number) => void): void { if (this.sequence) { this.sequence.dispose(); } const steps = Array.from({ length: 16 }, (_, i) => i); this.sequence = new Tone.Sequence( (time, step) => { if (this.pattern.kick[step]) { this.kick.triggerAttackRelease('C1', '8n', time, 0.8); } if (this.pattern.snare[step]) { this.snare.triggerAttackRelease('8n', time, 0.5); } if (this.pattern.hihat[step]) { this.hihat.triggerAttackRelease('32n', time, 0.3); } if (this.pattern.openhat[step]) { this.openhat.triggerAttackRelease('16n', time, 0.25); } // Call step callback on main thread if (onStep) { Tone.getDraw().schedule(() => { onStep(step); }, time); } }, steps, '16n' ); this.sequence.start(0); } setPattern(pattern: DrumPattern): void { this.pattern = pattern; } randomize(): DrumPattern { this.pattern = Math.random() > 0.5 ? getRandomPattern() : generateRandomPattern(); return this.pattern; } setVolume(volume: number): void { this.output.gain.rampTo(volume, 0.1); } mute(muted: boolean): void { this.output.gain.rampTo(muted ? 0 : 0.8, 0.1); } getPattern(): DrumPattern { return this.pattern; } dispose(): void { this.sequence?.dispose(); this.kick.dispose(); this.snare.dispose(); this.hihat.dispose(); this.openhat.dispose(); this.lowpass.dispose(); this.output.dispose(); } }