import * as Tone from 'tone'; import { ChordProgression } from '@/types/audio'; import { getRandomProgression } from './patterns'; export class ChordEngine { private synth: Tone.PolySynth; private sequence: Tone.Sequence | null = null; private progression: ChordProgression; private output: Tone.Gain; private filter: Tone.Filter; private reverb: Tone.Reverb; private chorus: Tone.Chorus; constructor(destination: Tone.InputNode) { this.output = new Tone.Gain(0.6); // Warm lofi filter this.filter = new Tone.Filter({ frequency: 2000, type: 'lowpass', rolloff: -24, }); // Dreamy reverb this.reverb = new Tone.Reverb({ decay: 3, wet: 0.4, }); // Subtle chorus for width this.chorus = new Tone.Chorus({ frequency: 0.5, delayTime: 3.5, depth: 0.5, wet: 0.3, }).start(); // FM Synth for warm, evolving pad sound this.synth = new Tone.PolySynth(Tone.FMSynth, { harmonicity: 2, modulationIndex: 1.5, oscillator: { type: 'sine', }, envelope: { attack: 0.3, decay: 0.3, sustain: 0.8, release: 1.5, }, modulation: { type: 'sine', }, modulationEnvelope: { attack: 0.5, decay: 0.2, sustain: 0.5, release: 0.5, }, }); // Lower the overall synth volume to prevent clipping this.synth.volume.value = -12; // Chain: synth -> filter -> chorus -> reverb -> output -> destination this.synth.connect(this.filter); this.filter.connect(this.chorus); this.chorus.connect(this.reverb); this.reverb.connect(this.output); this.output.connect(destination); // Initialize with a random progression this.progression = getRandomProgression(); } createSequence(): void { if (this.sequence) { this.sequence.dispose(); } const steps = Array.from({ length: this.progression.chords.length }, (_, i) => i); this.sequence = new Tone.Sequence( (time, step) => { const chord = this.progression.chords[step]; const duration = this.progression.durations[step]; // Release previous notes and play new chord this.synth.releaseAll(time); this.synth.triggerAttackRelease(chord, duration, time, 0.5); }, steps, '2n' ); this.sequence.start(0); } setProgression(progression: ChordProgression): void { this.progression = progression; // Recreate sequence with new progression if (this.sequence) { this.createSequence(); } } randomize(): ChordProgression { this.progression = getRandomProgression(); if (this.sequence) { this.createSequence(); } return this.progression; } 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); } setFilterFrequency(freq: number): void { this.filter.frequency.rampTo(freq, 0.1); } getProgression(): ChordProgression { return this.progression; } dispose(): void { this.sequence?.dispose(); this.synth.dispose(); this.filter.dispose(); this.reverb.dispose(); this.chorus.dispose(); this.output.dispose(); } }