feat(app): add types, mock data, and transpose utility
This commit is contained in:
144
app/app/lib/mock.ts
Normal file
144
app/app/lib/mock.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import type { Song, SongSummary } from "./types";
|
||||
|
||||
const OCEAN: Song = {
|
||||
meta: {
|
||||
title: "A Drop In The Ocean",
|
||||
artist: "Ron Pope",
|
||||
capo: null,
|
||||
original_key: "Em",
|
||||
tuning: null,
|
||||
tempo: null,
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
kind: "chorus",
|
||||
label: "Chorus",
|
||||
lines: [
|
||||
{
|
||||
text: "A drop in the ocean,",
|
||||
chords: [{ offset: 0, chord: "Em" }, { offset: 12, chord: "C" }],
|
||||
},
|
||||
{
|
||||
text: "A change in the weather,",
|
||||
chords: [{ offset: 2, chord: "G" }, { offset: 16, chord: "D" }],
|
||||
},
|
||||
{
|
||||
text: "I was praying that you and me might end up together.",
|
||||
chords: [
|
||||
{ offset: 6, chord: "Em" },
|
||||
{ offset: 17, chord: "C" },
|
||||
{ offset: 33, chord: "G" },
|
||||
{ offset: 44, chord: "D" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "verse",
|
||||
label: "Verse",
|
||||
lines: [
|
||||
{
|
||||
text: "I don't wanna waste the weekend,",
|
||||
chords: [{ offset: 0, chord: "C" }, { offset: 15, chord: "G" }],
|
||||
},
|
||||
{
|
||||
text: "If you don't love me, pretend",
|
||||
chords: [{ offset: 3, chord: "D" }, { offset: 21, chord: "Em" }],
|
||||
},
|
||||
{
|
||||
text: "A few more hours, then it's time to go.",
|
||||
chords: [{ offset: 2, chord: "C" }, { offset: 18, chord: "G" }, { offset: 30, chord: "D" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "bridge",
|
||||
label: "Bridge",
|
||||
lines: [
|
||||
{
|
||||
text: "Still I can't let you be,",
|
||||
chords: [{ offset: 0, chord: "Am" }, { offset: 11, chord: "G" }, { offset: 13, chord: "D" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const NAKED: Song = {
|
||||
meta: {
|
||||
title: "Naked",
|
||||
artist: "James Arthur",
|
||||
capo: null,
|
||||
original_key: "G",
|
||||
tuning: null,
|
||||
tempo: null,
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
kind: "chorus",
|
||||
label: "Chorus",
|
||||
lines: [
|
||||
{
|
||||
text: "I'm not going to wait until you're done",
|
||||
chords: [{ offset: 0, chord: "G" }, { offset: 18, chord: "Bm" }],
|
||||
},
|
||||
{
|
||||
text: "Pretending you don't need anyone",
|
||||
chords: [{ offset: 16, chord: "Em" }, { offset: 27, chord: "C" }],
|
||||
},
|
||||
{
|
||||
text: "I'm standing here naked",
|
||||
chords: [{ offset: 19, chord: "G" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "verse",
|
||||
label: "Verse",
|
||||
lines: [
|
||||
{
|
||||
text: "I lay awake thinking of all I wasted",
|
||||
chords: [{ offset: 0, chord: "G" }, { offset: 22, chord: "Bm" }],
|
||||
},
|
||||
{
|
||||
text: "All of the time we had I took for granted",
|
||||
chords: [{ offset: 11, chord: "Em" }, { offset: 32, chord: "C" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const SONGS_MAP: Record<string, Song> = {
|
||||
"song-ocean": OCEAN,
|
||||
"song-naked": NAKED,
|
||||
};
|
||||
|
||||
function previewChords(song: Song): string[] {
|
||||
const seen = new Set<string>();
|
||||
const result: string[] = [];
|
||||
for (const section of song.sections) {
|
||||
for (const line of section.lines) {
|
||||
for (const cp of line.chords) {
|
||||
if (!seen.has(cp.chord)) {
|
||||
seen.add(cp.chord);
|
||||
result.push(cp.chord);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.length >= 5) break;
|
||||
}
|
||||
return result.slice(0, 5);
|
||||
}
|
||||
|
||||
export const MOCK_SONGS: SongSummary[] = Object.entries(SONGS_MAP).map(
|
||||
([id, song]) => ({
|
||||
id,
|
||||
meta: song.meta,
|
||||
preview_chords: previewChords(song),
|
||||
})
|
||||
);
|
||||
|
||||
export function getMockSong(id: string): Song | null {
|
||||
return SONGS_MAP[id] ?? null;
|
||||
}
|
||||
34
app/app/lib/transpose.ts
Normal file
34
app/app/lib/transpose.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Song } from "./types";
|
||||
|
||||
const NOTES_SHARP = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
||||
const NOTES_FLAT = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
|
||||
|
||||
function transposeChord(chord: string, semitones: number): string {
|
||||
const match = chord.match(/^([A-G][#b]?)(.*)/);
|
||||
if (!match) return chord;
|
||||
const [, root, descriptor] = match;
|
||||
const idx = NOTES_SHARP.indexOf(root) !== -1
|
||||
? NOTES_SHARP.indexOf(root)
|
||||
: NOTES_FLAT.indexOf(root);
|
||||
if (idx === -1) return chord;
|
||||
const newIdx = ((idx + semitones) % 12 + 12) % 12;
|
||||
const notes = semitones >= 0 ? NOTES_SHARP : NOTES_FLAT;
|
||||
return notes[newIdx] + descriptor;
|
||||
}
|
||||
|
||||
export function transposeSong(song: Song, semitones: number): Song {
|
||||
if (semitones === 0) return song;
|
||||
return {
|
||||
...song,
|
||||
sections: song.sections.map((section) => ({
|
||||
...section,
|
||||
lines: section.lines.map((line) => ({
|
||||
...line,
|
||||
chords: line.chords.map((cp) => ({
|
||||
...cp,
|
||||
chord: transposeChord(cp.chord, semitones),
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
37
app/app/lib/types.ts
Normal file
37
app/app/lib/types.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export interface ChordPosition {
|
||||
offset: number;
|
||||
chord: string;
|
||||
}
|
||||
|
||||
export interface LyricLine {
|
||||
text: string;
|
||||
chords: ChordPosition[];
|
||||
}
|
||||
|
||||
export interface Section {
|
||||
kind: string;
|
||||
label: string | null;
|
||||
lines: LyricLine[];
|
||||
}
|
||||
|
||||
export interface SongMeta {
|
||||
title: string;
|
||||
artist: string;
|
||||
capo: number | null;
|
||||
original_key: string | null;
|
||||
tuning: string | null;
|
||||
tempo: number | null;
|
||||
}
|
||||
|
||||
export interface Song {
|
||||
meta: SongMeta;
|
||||
sections: Section[];
|
||||
}
|
||||
|
||||
// Trimmed version used in the library grid
|
||||
export interface SongSummary {
|
||||
id: string;
|
||||
meta: SongMeta;
|
||||
// First 5 unique chord names from the song, in order of appearance
|
||||
preview_chords: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user