import * as Tone from 'tone'; import { PianoType, Genre } from '@/types/audio'; import { getRandomPianoPattern } from './patterns'; export class PianoEngine { private synth: Tone.PolySynth | null = null; private sequence: Tone.Sequence | null = null; private pattern: (string[] | null)[]; private output: Tone.Gain; private filter: Tone.Filter; private reverb: Tone.Reverb; private currentType: PianoType = 'grand'; private genre: Genre = 'hiphop'; private currentVolume: number = 0.5; private isMuted: boolean = false; constructor(destination: Tone.InputNode) { this.output = new Tone.Gain(this.currentVolume); this.filter = new Tone.Filter({ frequency: 5000, type: 'lowpass', rolloff: -12, }); this.reverb = new Tone.Reverb({ decay: 2.5, wet: 0.25, }); this.filter.connect(this.reverb); this.reverb.connect(this.output); this.output.connect(destination); this.pattern = getRandomPianoPattern(this.genre); this.createSynth('grand'); } private createSynth(type: PianoType): void { if (this.synth) { this.synth.dispose(); } const configs: Record = { grand: { synth: Tone.Synth, options: { oscillator: { type: 'triangle' }, envelope: { attack: 0.005, decay: 0.8, sustain: 0.2, release: 1.2 }, }, }, electric: { synth: Tone.Synth, options: { oscillator: { type: 'sine' }, envelope: { attack: 0.01, decay: 0.5, sustain: 0.3, release: 0.8 }, }, }, rhodes: { synth: Tone.FMSynth, options: { harmonicity: 3, modulationIndex: 1, oscillator: { type: 'sine' }, envelope: { attack: 0.01, decay: 0.6, sustain: 0.3, release: 1 }, modulation: { type: 'sine' }, modulationEnvelope: { attack: 0.01, decay: 0.3, sustain: 0.5, release: 0.5 }, }, }, synth: { synth: Tone.Synth, options: { oscillator: { type: 'sawtooth' }, envelope: { attack: 0.02, decay: 0.3, sustain: 0.4, release: 0.5 }, }, }, }; const config = configs[type]; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.synth = new Tone.PolySynth(config.synth as any, config.options as any); this.synth.volume.value = -10; this.synth.connect(this.filter); this.currentType = type; } setInstrument(type: PianoType): void { this.createSynth(type); } setGenre(genre: Genre): void { this.genre = genre; this.pattern = getRandomPianoPattern(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 notes = this.pattern[step]; if (notes && this.synth) { this.synth.triggerAttackRelease(notes, '8n', time, 0.5); } }, steps, '16n' ); this.sequence.start(0); } randomize(): void { this.pattern = getRandomPianoPattern(this.genre); if (this.sequence) { this.createSequence(); } } setVolume(volume: number): void { this.currentVolume = volume; if (!this.isMuted) { this.output.gain.rampTo(volume, 0.1); } } mute(muted: boolean): void { this.isMuted = muted; this.output.gain.rampTo(muted ? 0 : this.currentVolume, 0.1); } dispose(): void { this.sequence?.dispose(); this.synth?.dispose(); this.filter.dispose(); this.reverb.dispose(); this.output.dispose(); } }