- 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>
72 lines
2.0 KiB
TypeScript
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>
|
|
);
|
|
}
|