- Add 4 genres: Hip Hop, Classical, Trap, Pop with unique patterns - Add new instrument layers: Bass, Brass, Piano - Each layer now has 4 instrument variations to choose from - Add genre-specific drum patterns, chord progressions, and melodies - Add duration control (1-10 minutes) - Rename app to "Beat Generator" with modern gradient header - Redesign UI with 2-column instrument grid layout - Add color-coded accent for each instrument section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
118 lines
3.2 KiB
TypeScript
118 lines
3.2 KiB
TypeScript
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<BassType, Partial<Tone.MonoSynthOptions>> = {
|
|
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();
|
|
}
|
|
}
|