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