import * as Tone from 'tone'; import { ChordProgression, ChordType, Genre } from '@/types/audio'; import { getRandomProgression } from './patterns'; export class ChordEngine { private synth: Tone.PolySynth | null = null; 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; private currentType: ChordType = 'pad'; private genre: Genre = 'hiphop'; constructor(destination: Tone.InputNode) { this.output = new Tone.Gain(0.6); this.filter = new Tone.Filter({ frequency: 2000, type: 'lowpass', rolloff: -24, }); this.reverb = new Tone.Reverb({ decay: 3, wet: 0.4, }); this.chorus = new Tone.Chorus({ frequency: 0.5, delayTime: 3.5, depth: 0.5, wet: 0.3, }).start(); this.filter.connect(this.chorus); this.chorus.connect(this.reverb); this.reverb.connect(this.output); this.output.connect(destination); this.progression = getRandomProgression(this.genre); this.createSynth('pad'); } private createSynth(type: ChordType): void { if (this.synth) { this.synth.dispose(); } const configs: Record = { pad: { synth: Tone.FMSynth, options: { 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 }, }, }, strings: { synth: Tone.Synth, options: { oscillator: { type: 'sawtooth' }, envelope: { attack: 0.4, decay: 0.3, sustain: 0.9, release: 2 }, }, }, organ: { synth: Tone.Synth, options: { oscillator: { type: 'sine' }, envelope: { attack: 0.01, decay: 0.1, sustain: 0.9, release: 0.3 }, }, }, synth: { synth: Tone.Synth, options: { oscillator: { type: 'fatsawtooth', spread: 30, count: 3 }, envelope: { attack: 0.1, decay: 0.2, sustain: 0.6, release: 0.8 }, }, }, }; const config = configs[type]; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.synth = new Tone.PolySynth(config.synth as any, config.options); this.synth.volume.value = -12; this.synth.connect(this.filter); this.currentType = type; } setInstrument(type: ChordType): void { this.createSynth(type); } setGenre(genre: Genre): void { this.genre = genre; this.progression = getRandomProgression(genre); if (this.sequence) { this.createSequence(); } } 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]; if (this.synth) { 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; if (this.sequence) { this.createSequence(); } } randomize(): ChordProgression { this.progression = getRandomProgression(this.genre); 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(); } }