import * as Tone from 'tone'; import { DrumMachine } from './drumMachine'; import { ChordEngine } from './chordEngine'; import { AmbientLayer } from './ambientLayer'; import { BassEngine } from './bassEngine'; import { BrassEngine } from './brassEngine'; import { PianoEngine } from './pianoEngine'; import { EngineState, AudioEngineCallbacks, LayerName, Genre, GENRE_CONFIG, DrumKit, BassType, BrassType, PianoType, ChordType, AmbientType, } from '@/types/audio'; class AudioEngine { private static instance: AudioEngine | null = null; private drumMachine: DrumMachine | null = null; private chordEngine: ChordEngine | null = null; private ambientLayer: AmbientLayer | null = null; private bassEngine: BassEngine | null = null; private brassEngine: BrassEngine | null = null; private pianoEngine: PianoEngine | null = null; private masterGain: Tone.Gain | null = null; private masterCompressor: Tone.Compressor | null = null; private masterLimiter: Tone.Limiter | null = null; private masterReverb: Tone.Reverb | null = null; private callbacks: AudioEngineCallbacks = {}; private state: EngineState = { isPlaying: false, isInitialized: false, bpm: 90, swing: 0.15, currentStep: 0, genre: 'hiphop', duration: 3, instruments: { drums: 'acoustic', bass: 'synth', brass: 'trumpet', piano: 'grand', chords: 'pad', ambient: 'rain', }, volumes: { master: 0.8, drums: 0.8, bass: 0.6, brass: 0.4, piano: 0.5, chords: 0.6, ambient: 0.4, }, muted: { drums: false, bass: false, brass: true, piano: true, chords: false, ambient: false, }, }; private constructor() {} static getInstance(): AudioEngine { if (!AudioEngine.instance) { AudioEngine.instance = new AudioEngine(); } return AudioEngine.instance; } async initialize(): Promise { if (this.state.isInitialized) return; await Tone.start(); const genreConfig = GENRE_CONFIG[this.state.genre]; Tone.getTransport().bpm.value = genreConfig.bpm; Tone.getTransport().swing = genreConfig.swing; Tone.getTransport().swingSubdivision = '16n'; // Master chain this.masterGain = new Tone.Gain(this.state.volumes.master); this.masterCompressor = new Tone.Compressor({ threshold: -20, ratio: 4, attack: 0.003, release: 0.25, }); this.masterLimiter = new Tone.Limiter(-1); this.masterReverb = new Tone.Reverb({ decay: 2, wet: 0.15, }); this.masterGain.connect(this.masterReverb); this.masterReverb.connect(this.masterCompressor); this.masterCompressor.connect(this.masterLimiter); this.masterLimiter.toDestination(); // Initialize all layers this.drumMachine = new DrumMachine(this.masterGain); this.chordEngine = new ChordEngine(this.masterGain); this.ambientLayer = new AmbientLayer(this.masterGain); this.bassEngine = new BassEngine(this.masterGain); this.brassEngine = new BrassEngine(this.masterGain); this.pianoEngine = new PianoEngine(this.masterGain); // Set initial genre for all engines this.drumMachine.setGenre(this.state.genre); this.chordEngine.setGenre(this.state.genre); this.bassEngine.setGenre(this.state.genre); this.brassEngine.setGenre(this.state.genre); this.pianoEngine.setGenre(this.state.genre); // Apply initial mute states this.brassEngine.mute(this.state.muted.brass); this.pianoEngine.mute(this.state.muted.piano); // Create sequences this.drumMachine.createSequence((step) => { this.state.currentStep = step; this.callbacks.onStepChange?.(step); }); this.chordEngine.createSequence(); this.bassEngine.createSequence(); this.brassEngine.createSequence(); this.pianoEngine.createSequence(); this.state.bpm = genreConfig.bpm; this.state.swing = genreConfig.swing; this.state.isInitialized = true; this.notifyStateChange(); } setCallbacks(callbacks: AudioEngineCallbacks): void { this.callbacks = callbacks; } private notifyStateChange(): void { this.callbacks.onStateChange?.({ ...this.state }); } async play(): Promise { if (!this.state.isInitialized) { await this.initialize(); } this.ambientLayer?.start(); Tone.getTransport().start(); this.state.isPlaying = true; this.notifyStateChange(); } pause(): void { Tone.getTransport().pause(); this.ambientLayer?.stop(); this.state.isPlaying = false; this.notifyStateChange(); } stop(): void { Tone.getTransport().stop(); this.ambientLayer?.stop(); this.state.isPlaying = false; this.state.currentStep = 0; this.notifyStateChange(); } generateNewBeat(): void { this.drumMachine?.randomize(); this.chordEngine?.randomize(); this.bassEngine?.randomize(); this.brassEngine?.randomize(); this.pianoEngine?.randomize(); this.notifyStateChange(); } setGenre(genre: Genre): void { this.state.genre = genre; const genreConfig = GENRE_CONFIG[genre]; // Update BPM and swing for genre this.state.bpm = genreConfig.bpm; this.state.swing = genreConfig.swing; Tone.getTransport().bpm.value = genreConfig.bpm; Tone.getTransport().swing = genreConfig.swing; // Update all engines with new genre this.drumMachine?.setGenre(genre); this.chordEngine?.setGenre(genre); this.bassEngine?.setGenre(genre); this.brassEngine?.setGenre(genre); this.pianoEngine?.setGenre(genre); this.notifyStateChange(); } setDuration(minutes: number): void { this.state.duration = Math.max(1, Math.min(10, minutes)); this.notifyStateChange(); } setInstrument(layer: LayerName, instrument: string): void { switch (layer) { case 'drums': this.state.instruments.drums = instrument as DrumKit; this.drumMachine?.setInstrument(instrument as DrumKit); break; case 'bass': this.state.instruments.bass = instrument as BassType; this.bassEngine?.setInstrument(instrument as BassType); break; case 'brass': this.state.instruments.brass = instrument as BrassType; this.brassEngine?.setInstrument(instrument as BrassType); break; case 'piano': this.state.instruments.piano = instrument as PianoType; this.pianoEngine?.setInstrument(instrument as PianoType); break; case 'chords': this.state.instruments.chords = instrument as ChordType; this.chordEngine?.setInstrument(instrument as ChordType); break; case 'ambient': this.state.instruments.ambient = instrument as AmbientType; this.ambientLayer?.setInstrument(instrument as AmbientType); break; } this.notifyStateChange(); } setBpm(bpm: number): void { this.state.bpm = Math.max(60, Math.min(180, bpm)); Tone.getTransport().bpm.value = this.state.bpm; this.notifyStateChange(); } setSwing(swing: number): void { this.state.swing = Math.max(0, Math.min(0.5, swing)); Tone.getTransport().swing = this.state.swing; this.notifyStateChange(); } setMasterVolume(volume: number): void { this.state.volumes.master = Math.max(0, Math.min(1, volume)); this.masterGain?.gain.rampTo(this.state.volumes.master, 0.1); this.notifyStateChange(); } setLayerVolume(layer: LayerName, volume: number): void { const normalizedVolume = Math.max(0, Math.min(1, volume)); this.state.volumes[layer] = normalizedVolume; switch (layer) { case 'drums': this.drumMachine?.setVolume(normalizedVolume); break; case 'bass': this.bassEngine?.setVolume(normalizedVolume); break; case 'brass': this.brassEngine?.setVolume(normalizedVolume); break; case 'piano': this.pianoEngine?.setVolume(normalizedVolume); break; case 'chords': this.chordEngine?.setVolume(normalizedVolume); break; case 'ambient': this.ambientLayer?.setVolume(normalizedVolume); break; } this.notifyStateChange(); } toggleMute(layer: LayerName): void { this.state.muted[layer] = !this.state.muted[layer]; switch (layer) { case 'drums': this.drumMachine?.mute(this.state.muted[layer]); break; case 'bass': this.bassEngine?.mute(this.state.muted[layer]); break; case 'brass': this.brassEngine?.mute(this.state.muted[layer]); break; case 'piano': this.pianoEngine?.mute(this.state.muted[layer]); break; case 'chords': this.chordEngine?.mute(this.state.muted[layer]); break; case 'ambient': this.ambientLayer?.mute(this.state.muted[layer]); break; } this.notifyStateChange(); } setMuted(layer: LayerName, muted: boolean): void { this.state.muted[layer] = muted; switch (layer) { case 'drums': this.drumMachine?.mute(muted); break; case 'bass': this.bassEngine?.mute(muted); break; case 'brass': this.brassEngine?.mute(muted); break; case 'piano': this.pianoEngine?.mute(muted); break; case 'chords': this.chordEngine?.mute(muted); break; case 'ambient': this.ambientLayer?.mute(muted); break; } this.notifyStateChange(); } getState(): EngineState { return { ...this.state }; } dispose(): void { this.stop(); this.drumMachine?.dispose(); this.chordEngine?.dispose(); this.ambientLayer?.dispose(); this.bassEngine?.dispose(); this.brassEngine?.dispose(); this.pianoEngine?.dispose(); this.masterGain?.dispose(); this.masterCompressor?.dispose(); this.masterLimiter?.dispose(); this.masterReverb?.dispose(); this.drumMachine = null; this.chordEngine = null; this.ambientLayer = null; this.bassEngine = null; this.brassEngine = null; this.pianoEngine = null; this.masterGain = null; this.masterCompressor = null; this.masterLimiter = null; this.masterReverb = null; this.state.isInitialized = false; this.state.isPlaying = false; AudioEngine.instance = null; } } export const audioEngine = AudioEngine.getInstance(); export default audioEngine;