Avery Felts 5ed84192d5 Implement lofi hip hop generator with Tone.js
- Set up Next.js project with shadcn/ui and Tailwind CSS
- Created audio engine with MembraneSynth drums, FMSynth chords, and ambient noise layers
- Implemented 16-step drum sequencer with boom bap patterns
- Added jazz chord progressions (ii-V-I, minor key, neo soul)
- Built React hook for audio state management
- Created UI components: transport controls, volume sliders, layer mixer, beat visualizer
- Applied lofi-themed dark color scheme with oklch colors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 17:29:28 -07:00

111 lines
3.5 KiB
TypeScript

'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { TransportControls } from './TransportControls';
import { VolumeControl } from './VolumeControl';
import { LayerMixer } from './LayerMixer';
import { Visualizer } from './Visualizer';
import { useAudioEngine } from '@/hooks/useAudioEngine';
import { Slider } from '@/components/ui/slider';
import { Label } from '@/components/ui/label';
export function LofiGenerator() {
const {
state,
currentStep,
togglePlayback,
generateNewBeat,
setMasterVolume,
setLayerVolume,
toggleMute,
setBpm,
setSwing,
} = useAudioEngine();
return (
<div className="min-h-screen flex items-center justify-center p-4">
<Card className="w-full max-w-md bg-card/80 backdrop-blur-sm border-border/50">
<CardHeader className="text-center pb-2">
<CardTitle className="text-2xl font-light tracking-wide">
lofi generator
</CardTitle>
<p className="text-sm text-muted-foreground">
beats to relax/study to
</p>
</CardHeader>
<CardContent className="space-y-6">
{/* Visualizer */}
<Visualizer currentStep={currentStep} isPlaying={state.isPlaying} />
{/* Transport Controls */}
<div className="flex justify-center">
<TransportControls
isPlaying={state.isPlaying}
isInitialized={state.isInitialized}
onTogglePlayback={togglePlayback}
onGenerateNewBeat={generateNewBeat}
/>
</div>
{/* Master Volume */}
<div className="pt-2">
<VolumeControl
label="Master Volume"
value={state.volumes.master}
onChange={setMasterVolume}
/>
</div>
{/* BPM and Swing Controls */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-sm text-muted-foreground">BPM</Label>
<span className="text-xs text-muted-foreground font-mono">
{state.bpm}
</span>
</div>
<Slider
value={[state.bpm]}
onValueChange={([v]) => setBpm(v)}
min={60}
max={100}
step={1}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-sm text-muted-foreground">Swing</Label>
<span className="text-xs text-muted-foreground font-mono">
{Math.round(state.swing * 100)}%
</span>
</div>
<Slider
value={[state.swing]}
onValueChange={([v]) => setSwing(v)}
min={0}
max={0.5}
step={0.01}
/>
</div>
</div>
{/* Layer Mixer */}
<LayerMixer
volumes={state.volumes}
muted={state.muted}
onVolumeChange={setLayerVolume}
onToggleMute={toggleMute}
/>
{/* Footer */}
<p className="text-center text-xs text-muted-foreground/60 pt-4">
Click play to start the audio engine
</p>
</CardContent>
</Card>
</div>
);
}