forked from averyfelts/Lofi_Generator
- 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>
111 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|