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

284 lines
10 KiB
TypeScript

'use client';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Slider } from '@/components/ui/slider';
import { Label } from '@/components/ui/label';
import { LayerBox } from './LayerBox';
import { Visualizer } from './Visualizer';
import { useAudioEngine } from '@/hooks/useAudioEngine';
import {
Play,
Pause,
Shuffle,
Drum,
Music,
Cloud,
Guitar,
Waves,
Piano,
} from 'lucide-react';
import { Genre, GENRE_CONFIG, INSTRUMENT_OPTIONS } from '@/types/audio';
export function LofiGenerator() {
const {
state,
currentStep,
togglePlayback,
generateNewBeat,
setMasterVolume,
setLayerVolume,
toggleMute,
setGenre,
setDuration,
setInstrument,
setBpm,
setSwing,
} = useAudioEngine();
const genres: { value: Genre; label: string }[] = [
{ value: 'hiphop', label: 'Hip Hop' },
{ value: 'classical', label: 'Classical' },
{ value: 'trap', label: 'Trap' },
{ value: 'pop', label: 'Pop' },
];
return (
<div className="min-h-screen flex flex-col items-center p-4 pt-8">
{/* Modern Header */}
<header className="w-full max-w-2xl mb-8">
<div className="flex items-center justify-center gap-3 mb-2">
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-lofi-orange via-lofi-pink to-lofi-purple flex items-center justify-center">
<Waves className="h-5 w-5 text-white" />
</div>
<h1 className="text-3xl font-bold tracking-tight bg-gradient-to-r from-lofi-orange via-lofi-pink to-lofi-purple bg-clip-text text-transparent">
Beat Generator
</h1>
</div>
<p className="text-center text-sm text-muted-foreground">
Create custom beats across multiple genres
</p>
</header>
<Card className="w-full max-w-2xl bg-card/80 backdrop-blur-sm border-border/50">
<CardContent className="p-6 space-y-6">
{/* Top Controls: Duration & Genre */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs text-muted-foreground uppercase tracking-wider">
Duration
</Label>
<div className="flex items-center gap-2">
<Slider
value={[state.duration]}
onValueChange={([v]) => setDuration(v)}
min={1}
max={10}
step={1}
className="flex-1"
/>
<span className="text-sm font-mono w-12 text-right">
{state.duration} min
</span>
</div>
</div>
<div className="space-y-2">
<Label className="text-xs text-muted-foreground uppercase tracking-wider">
Genre
</Label>
<Select value={state.genre} onValueChange={(v) => setGenre(v as Genre)}>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{genres.map((g) => (
<SelectItem key={g.value} value={g.value}>
{g.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* Transport Controls */}
<div className="flex items-center justify-center gap-4">
<Button
size="lg"
onClick={togglePlayback}
className="h-14 w-14 rounded-full bg-gradient-to-br from-lofi-orange to-lofi-pink hover:opacity-90"
>
{state.isPlaying ? (
<Pause className="h-6 w-6" />
) : (
<Play className="h-6 w-6 ml-1" />
)}
</Button>
<Button
variant="secondary"
size="lg"
onClick={generateNewBeat}
className="gap-2"
>
<Shuffle className="h-4 w-4" />
Generate
</Button>
</div>
{/* Visualizer */}
<Visualizer currentStep={currentStep} isPlaying={state.isPlaying} />
{/* Master Controls */}
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs text-muted-foreground">Master</Label>
<span className="text-xs text-muted-foreground font-mono">
{Math.round(state.volumes.master * 100)}%
</span>
</div>
<Slider
value={[state.volumes.master]}
onValueChange={([v]) => setMasterVolume(v)}
min={0}
max={1}
step={0.01}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs 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={180}
step={1}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs 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>
{/* Instrument Layers */}
<div className="space-y-3">
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Instruments
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<LayerBox
title="Drums"
icon={<Drum className="h-4 w-4" />}
volume={state.volumes.drums}
muted={state.muted.drums}
instrument={state.instruments.drums}
instrumentOptions={INSTRUMENT_OPTIONS.drums}
onVolumeChange={(v) => setLayerVolume('drums', v)}
onToggleMute={() => toggleMute('drums')}
onInstrumentChange={(v) => setInstrument('drums', v)}
accentColor="orange"
/>
<LayerBox
title="Bass"
icon={<Guitar className="h-4 w-4" />}
volume={state.volumes.bass}
muted={state.muted.bass}
instrument={state.instruments.bass}
instrumentOptions={INSTRUMENT_OPTIONS.bass}
onVolumeChange={(v) => setLayerVolume('bass', v)}
onToggleMute={() => toggleMute('bass')}
onInstrumentChange={(v) => setInstrument('bass', v)}
accentColor="blue"
/>
<LayerBox
title="Piano"
icon={<Piano className="h-4 w-4" />}
volume={state.volumes.piano}
muted={state.muted.piano}
instrument={state.instruments.piano}
instrumentOptions={INSTRUMENT_OPTIONS.piano}
onVolumeChange={(v) => setLayerVolume('piano', v)}
onToggleMute={() => toggleMute('piano')}
onInstrumentChange={(v) => setInstrument('piano', v)}
accentColor="green"
/>
<LayerBox
title="Brass"
icon={<Music className="h-4 w-4" />}
volume={state.volumes.brass}
muted={state.muted.brass}
instrument={state.instruments.brass}
instrumentOptions={INSTRUMENT_OPTIONS.brass}
onVolumeChange={(v) => setLayerVolume('brass', v)}
onToggleMute={() => toggleMute('brass')}
onInstrumentChange={(v) => setInstrument('brass', v)}
accentColor="yellow"
/>
<LayerBox
title="Chords"
icon={<Waves className="h-4 w-4" />}
volume={state.volumes.chords}
muted={state.muted.chords}
instrument={state.instruments.chords}
instrumentOptions={INSTRUMENT_OPTIONS.chords}
onVolumeChange={(v) => setLayerVolume('chords', v)}
onToggleMute={() => toggleMute('chords')}
onInstrumentChange={(v) => setInstrument('chords', v)}
accentColor="pink"
/>
<LayerBox
title="Ambient"
icon={<Cloud className="h-4 w-4" />}
volume={state.volumes.ambient}
muted={state.muted.ambient}
instrument={state.instruments.ambient}
instrumentOptions={INSTRUMENT_OPTIONS.ambient}
onVolumeChange={(v) => setLayerVolume('ambient', v)}
onToggleMute={() => toggleMute('ambient')}
onInstrumentChange={(v) => setInstrument('ambient', v)}
accentColor="purple"
/>
</div>
</div>
{/* Footer */}
<p className="text-center text-xs text-muted-foreground/60 pt-2">
Click play to initialize audio engine Genre: {GENRE_CONFIG[state.genre].name}
</p>
</CardContent>
</Card>
</div>
);
}