jam-tracks / frontend /src /components /ChordDisplay.jsx
Mina Emadi
Move player runtime to browser-only Node app
aad4714
Raw
History Blame Contribute Delete
1.84 kB
import React, { useMemo } from 'react'
const KEY_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
export function transposeChord(chord, semitones) {
if (!chord || chord === 'N.C.' || semitones === 0) return chord
let root, suffix
if (chord.length >= 2 && chord[1] === '#') {
root = chord.slice(0, 2)
suffix = chord.slice(2)
} else {
root = chord[0]
suffix = chord.slice(1)
}
const idx = KEY_NAMES.indexOf(root)
if (idx === -1) return chord
return KEY_NAMES[(idx + semitones + 120) % 12] + suffix
}
function ChordDisplay({ chordsData, semitones = 0 }) {
const chords = chordsData?.chords || []
// Build unique chord list
const uniqueChords = useMemo(() => {
const seen = new Set()
return chords
.filter(c => c.chord !== 'N.C.')
.map(c => transposeChord(c.chord, semitones))
.filter(c => {
if (seen.has(c)) return false
seen.add(c)
return true
})
}, [chords, semitones])
if (!chordsData || uniqueChords.length === 0) return null
return (
<div className="card rounded-xl p-5 animate-fade-in">
<div className="flex items-center justify-between mb-3">
<h2 className="h-display text-xl text-white tracking-wider">Chords in this Song</h2>
<span className="text-[10px] text-gray-400 uppercase tracking-wider">
{chordsData.chord_source === 'midi' ? 'MIDI' : 'Audio'}
</span>
</div>
<div className="flex flex-wrap gap-2">
{uniqueChords.map((chord, i) => (
<span
key={i}
className="px-3 py-1.5 rounded-lg bg-surface-elevated border border-white/[0.06] text-sm font-semibold text-gray-300"
>
{chord}
</span>
))}
</div>
</div>
)
}
export default React.memo(ChordDisplay)