forked from averyfelts/Lofi_Generator
work in progress implementation of: - mixer with channel strips, faders, pan knobs, level meters - timeline with ruler, playhead, sections, keyframe tracks - pattern and progression pickers for drums/chords - automation lanes and mute tracks - loop bracket for loop region selection - export modal placeholder known issues: - drum pattern changes don't update audio engine - timeline keyframes not connected to scheduler - some UI bugs remain this is a checkpoint commit for further iteration
109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useMemo } from 'react';
|
|
import { Card } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Shuffle } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { drumPatterns } from '@/lib/audio/patterns';
|
|
|
|
interface PatternPickerProps {
|
|
selectedIndex: number | null;
|
|
onSelect: (index: number) => void;
|
|
onRandom: () => void;
|
|
onClose: () => void;
|
|
className?: string;
|
|
}
|
|
|
|
const patternNames = [
|
|
'Classic Boom Bap',
|
|
'Laid Back Groove',
|
|
'Minimal Chill',
|
|
'Jazzy Swing',
|
|
'Deep Pocket',
|
|
];
|
|
|
|
function PatternPreview({ pattern }: { pattern: typeof drumPatterns[0] }) {
|
|
return (
|
|
<div className="grid grid-cols-16 gap-px w-full h-8 bg-muted/30 rounded">
|
|
{Array.from({ length: 16 }, (_, i) => {
|
|
const hasKick = pattern.kick[i];
|
|
const hasSnare = pattern.snare[i];
|
|
const hasHihat = pattern.hihat[i];
|
|
const hasOpenhat = pattern.openhat[i];
|
|
|
|
return (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
'flex flex-col gap-px justify-center',
|
|
i % 4 === 0 && 'bg-muted/20'
|
|
)}
|
|
>
|
|
{hasKick && <div className="w-full h-1.5 bg-lofi-orange/80 rounded-sm" />}
|
|
{hasSnare && <div className="w-full h-1.5 bg-lofi-pink/80 rounded-sm" />}
|
|
{(hasHihat || hasOpenhat) && (
|
|
<div
|
|
className={cn(
|
|
'w-full h-1 rounded-sm',
|
|
hasOpenhat ? 'bg-yellow-500/60' : 'bg-muted-foreground/40'
|
|
)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PatternPicker({
|
|
selectedIndex,
|
|
onSelect,
|
|
onRandom,
|
|
onClose,
|
|
className,
|
|
}: PatternPickerProps) {
|
|
return (
|
|
<Card
|
|
className={cn(
|
|
'absolute z-50 p-3 w-64 bg-card border border-border shadow-xl',
|
|
className
|
|
)}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-xs font-medium text-muted-foreground uppercase">
|
|
Drum Patterns
|
|
</span>
|
|
<Button variant="ghost" size="sm" className="h-6 px-2" onClick={onRandom}>
|
|
<Shuffle className="h-3 w-3 mr-1" />
|
|
Random
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{drumPatterns.map((pattern, i) => (
|
|
<button
|
|
key={i}
|
|
className={cn(
|
|
'w-full p-2 rounded-md border transition-colors text-left',
|
|
selectedIndex === i
|
|
? 'border-primary bg-primary/10'
|
|
: 'border-border/50 hover:border-border hover:bg-muted/30'
|
|
)}
|
|
onClick={() => onSelect(i)}
|
|
>
|
|
<span className="text-xs font-medium block mb-1">{patternNames[i]}</span>
|
|
<PatternPreview pattern={pattern} />
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<Button variant="outline" size="sm" className="w-full mt-2" onClick={onClose}>
|
|
Close
|
|
</Button>
|
|
</Card>
|
|
);
|
|
}
|