Lofi_Generator/components/timeline/PatternPicker.tsx
Nicholai d3158e4c6a feat(timeline-mixer): WIP timeline and mixer components
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
2026-01-20 18:22:10 -07:00

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>
);
}