Lofi_Generator/components/timeline/ProgressionPicker.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

92 lines
2.6 KiB
TypeScript

'use client';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Shuffle } from 'lucide-react';
import { cn } from '@/lib/utils';
import { chordProgressions } from '@/lib/audio/patterns';
interface ProgressionPickerProps {
selectedIndex: number | null;
onSelect: (index: number) => void;
onRandom: () => void;
onClose: () => void;
className?: string;
}
function chordToName(notes: string[]): string {
if (notes.length === 0) return '';
const noteMap: Record<string, string> = {
'C': 'C', 'D': 'D', 'E': 'E', 'F': 'F', 'G': 'G', 'A': 'A', 'B': 'B',
};
const root = notes[0].replace(/[0-9]/g, '');
const rootName = root.replace('#', '♯').replace('b', '♭');
if (notes.some((n) => n.includes('b') || n.includes('Eb') || n.includes('Bb'))) {
return `${rootName}m7`;
}
return `${rootName}maj7`;
}
export function ProgressionPicker({
selectedIndex,
onSelect,
onRandom,
onClose,
className,
}: ProgressionPickerProps) {
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">
Chord Progressions
</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">
{chordProgressions.map((prog, 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">{prog.name}</span>
<div className="flex gap-1 text-[10px] text-muted-foreground font-mono">
{prog.chords.map((chord, j) => (
<span
key={j}
className="px-1.5 py-0.5 bg-muted/50 rounded"
>
{chordToName(chord)}
</span>
))}
</div>
</button>
))}
</div>
<Button variant="outline" size="sm" className="w-full mt-2" onClick={onClose}>
Close
</Button>
</Card>
);
}