Lofi_Generator/lib/audio/ambientLayer.ts
Avery Felts 0f17775f3f Add multi-genre support and expanded instrument options
- 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>
2026-01-20 17:57:12 -07:00

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();
}
}