forked from averyfelts/Lofi_Generator
- 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>
139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import * as Tone from 'tone';
|
|
import { AmbientType } from '@/types/audio';
|
|
|
|
export class AmbientLayer {
|
|
private noise1: Tone.Noise | null = null;
|
|
private noise2: Tone.Noise | null = null;
|
|
private filter1: Tone.Filter | null = null;
|
|
private filter2: Tone.Filter | null = null;
|
|
private gain1: Tone.Gain | null = null;
|
|
private gain2: Tone.Gain | null = null;
|
|
private output: Tone.Gain;
|
|
private lfo: Tone.LFO | null = null;
|
|
private currentType: AmbientType = 'rain';
|
|
private isPlaying = false;
|
|
|
|
constructor(destination: Tone.InputNode) {
|
|
this.output = new Tone.Gain(0.4);
|
|
this.output.connect(destination);
|
|
this.createAmbient('rain');
|
|
}
|
|
|
|
private createAmbient(type: AmbientType): void {
|
|
// Dispose existing
|
|
this.noise1?.dispose();
|
|
this.noise2?.dispose();
|
|
this.filter1?.dispose();
|
|
this.filter2?.dispose();
|
|
this.gain1?.dispose();
|
|
this.gain2?.dispose();
|
|
this.lfo?.dispose();
|
|
|
|
const configs: Record<AmbientType, {
|
|
noise1: { type: 'white' | 'pink' | 'brown'; filter: number; filterType: BiquadFilterType; gain: number };
|
|
noise2: { type: 'white' | 'pink' | 'brown'; filter: number; filterType: BiquadFilterType; gain: number };
|
|
lfoFreq: number;
|
|
}> = {
|
|
rain: {
|
|
noise1: { type: 'pink', filter: 3000, filterType: 'lowpass', gain: 0.15 },
|
|
noise2: { type: 'brown', filter: 1500, filterType: 'bandpass', gain: 0.1 },
|
|
lfoFreq: 0.1,
|
|
},
|
|
vinyl: {
|
|
noise1: { type: 'brown', filter: 2000, filterType: 'bandpass', gain: 0.12 },
|
|
noise2: { type: 'white', filter: 800, filterType: 'lowpass', gain: 0.05 },
|
|
lfoFreq: 0.05,
|
|
},
|
|
nature: {
|
|
noise1: { type: 'pink', filter: 4000, filterType: 'lowpass', gain: 0.1 },
|
|
noise2: { type: 'brown', filter: 500, filterType: 'lowpass', gain: 0.08 },
|
|
lfoFreq: 0.08,
|
|
},
|
|
space: {
|
|
noise1: { type: 'pink', filter: 1000, filterType: 'lowpass', gain: 0.12 },
|
|
noise2: { type: 'brown', filter: 300, filterType: 'lowpass', gain: 0.1 },
|
|
lfoFreq: 0.02,
|
|
},
|
|
};
|
|
|
|
const config = configs[type];
|
|
|
|
// Primary noise layer
|
|
this.noise1 = new Tone.Noise(config.noise1.type);
|
|
this.filter1 = new Tone.Filter({
|
|
frequency: config.noise1.filter,
|
|
type: config.noise1.filterType,
|
|
});
|
|
this.gain1 = new Tone.Gain(config.noise1.gain);
|
|
this.noise1.connect(this.filter1);
|
|
this.filter1.connect(this.gain1);
|
|
this.gain1.connect(this.output);
|
|
|
|
// Secondary noise layer
|
|
this.noise2 = new Tone.Noise(config.noise2.type);
|
|
this.filter2 = new Tone.Filter({
|
|
frequency: config.noise2.filter,
|
|
type: config.noise2.filterType,
|
|
});
|
|
this.gain2 = new Tone.Gain(config.noise2.gain);
|
|
this.noise2.connect(this.filter2);
|
|
this.filter2.connect(this.gain2);
|
|
this.gain2.connect(this.output);
|
|
|
|
// LFO for subtle variation
|
|
this.lfo = new Tone.LFO({
|
|
frequency: config.lfoFreq,
|
|
min: config.noise1.gain * 0.7,
|
|
max: config.noise1.gain * 1.2,
|
|
});
|
|
this.lfo.connect(this.gain1.gain);
|
|
|
|
this.currentType = type;
|
|
|
|
// Restart if was playing
|
|
if (this.isPlaying) {
|
|
this.noise1.start();
|
|
this.noise2.start();
|
|
this.lfo.start();
|
|
}
|
|
}
|
|
|
|
setInstrument(type: AmbientType): void {
|
|
this.createAmbient(type);
|
|
}
|
|
|
|
start(): void {
|
|
this.noise1?.start();
|
|
this.noise2?.start();
|
|
this.lfo?.start();
|
|
this.isPlaying = true;
|
|
}
|
|
|
|
stop(): void {
|
|
this.noise1?.stop();
|
|
this.noise2?.stop();
|
|
this.lfo?.stop();
|
|
this.isPlaying = false;
|
|
}
|
|
|
|
setVolume(volume: number): void {
|
|
this.output.gain.rampTo(volume, 0.1);
|
|
}
|
|
|
|
mute(muted: boolean): void {
|
|
this.output.gain.rampTo(muted ? 0 : 0.4, 0.1);
|
|
}
|
|
|
|
dispose(): void {
|
|
this.stop();
|
|
this.noise1?.dispose();
|
|
this.noise2?.dispose();
|
|
this.filter1?.dispose();
|
|
this.filter2?.dispose();
|
|
this.gain1?.dispose();
|
|
this.gain2?.dispose();
|
|
this.lfo?.dispose();
|
|
this.output.dispose();
|
|
}
|
|
}
|