import * as Tone from 'tone'; import { BassType, Genre } from '@/types/audio'; import { getRandomBassPattern } from './patterns'; export class BassEngine { private synth: Tone.MonoSynth | null = null; private sequence: Tone.Sequence | null = null; private pattern: (string | null)[]; private output: Tone.Gain; private filter: Tone.Filter; private currentType: BassType = 'synth'; private genre: Genre = 'hiphop'; constructor(destination: Tone.InputNode) { this.output = new Tone.Gain(0.6); this.filter = new Tone.Filter({ frequency: 800, type: 'lowpass', rolloff: -24, }); this.filter.connect(this.output); this.output.connect(destination); this.pattern = getRandomBassPattern(this.genre); this.createSynth('synth'); } private createSynth(type: BassType): void { if (this.synth) { this.synth.dispose(); } const configs: Record> = { synth: { oscillator: { type: 'sawtooth' }, envelope: { attack: 0.01, decay: 0.3, sustain: 0.4, release: 0.2 }, filterEnvelope: { attack: 0.01, decay: 0.2, sustain: 0.5, release: 0.2, baseFrequency: 200, octaves: 2 }, }, sub: { oscillator: { type: 'sine' }, envelope: { attack: 0.02, decay: 0.5, sustain: 0.8, release: 0.4 }, filterEnvelope: { attack: 0.01, decay: 0.1, sustain: 1, release: 0.1, baseFrequency: 100, octaves: 1 }, }, electric: { oscillator: { type: 'triangle' }, envelope: { attack: 0.005, decay: 0.2, sustain: 0.3, release: 0.15 }, filterEnvelope: { attack: 0.01, decay: 0.15, sustain: 0.4, release: 0.2, baseFrequency: 300, octaves: 2.5 }, }, upright: { oscillator: { type: 'triangle' }, envelope: { attack: 0.02, decay: 0.4, sustain: 0.2, release: 0.3 }, filterEnvelope: { attack: 0.02, decay: 0.3, sustain: 0.3, release: 0.3, baseFrequency: 250, octaves: 1.5 }, }, }; this.synth = new Tone.MonoSynth(configs[type]); this.synth.volume.value = -6; this.synth.connect(this.filter); this.currentType = type; } setInstrument(type: BassType): void { this.createSynth(type); } setGenre(genre: Genre): void { this.genre = genre; this.pattern = getRandomBassPattern(genre); if (this.sequence) { this.createSequence(); } } createSequence(): void { if (this.sequence) { this.sequence.dispose(); } const steps = Array.from({ length: 16 }, (_, i) => i); this.sequence = new Tone.Sequence( (time, step) => { const note = this.pattern[step]; if (note && this.synth) { this.synth.triggerAttackRelease(note, '8n', time, 0.7); } }, steps, '16n' ); this.sequence.start(0); } randomize(): void { this.pattern = getRandomBassPattern(this.genre); if (this.sequence) { this.createSequence(); } } setVolume(volume: number): void { this.output.gain.rampTo(volume, 0.1); } mute(muted: boolean): void { this.output.gain.rampTo(muted ? 0 : 0.6, 0.1); } dispose(): void { this.sequence?.dispose(); this.synth?.dispose(); this.filter.dispose(); this.output.dispose(); } }