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

72 lines
2.0 KiB
TypeScript

'use client';
import { Toggle } from '@/components/ui/toggle';
import { VolumeControl } from './VolumeControl';
import { Volume2, VolumeX, Drum, Music, Cloud } from 'lucide-react';
import { LayerName } from '@/types/audio';
interface LayerMixerProps {
volumes: {
drums: number;
chords: number;
ambient: number;
};
muted: {
drums: boolean;
chords: boolean;
ambient: boolean;
};
onVolumeChange: (layer: LayerName, volume: number) => void;
onToggleMute: (layer: LayerName) => void;
}
const layers: { name: LayerName; label: string; icon: React.ReactNode }[] = [
{ name: 'drums', label: 'Drums', icon: <Drum className="h-4 w-4" /> },
{ name: 'chords', label: 'Chords', icon: <Music className="h-4 w-4" /> },
{ name: 'ambient', label: 'Ambient', icon: <Cloud className="h-4 w-4" /> },
];
export function LayerMixer({
volumes,
muted,
onVolumeChange,
onToggleMute,
}: LayerMixerProps) {
return (
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Layers
</h3>
<div className="space-y-4">
{layers.map(({ name, label, icon }) => (
<div key={name} className="flex items-center gap-3">
<Toggle
pressed={!muted[name]}
onPressedChange={() => onToggleMute(name)}
size="sm"
className="shrink-0"
aria-label={`Toggle ${label}`}
>
{muted[name] ? (
<VolumeX className="h-4 w-4" />
) : (
<Volume2 className="h-4 w-4" />
)}
</Toggle>
<div className="flex items-center gap-2 shrink-0 w-20">
{icon}
<span className="text-sm">{label}</span>
</div>
<VolumeControl
label=""
value={volumes[name]}
onChange={(v) => onVolumeChange(name, v)}
className="flex-1"
/>
</div>
))}
</div>
</div>
);
}