From f5121bc2addc2602933bf311424c682042006324 Mon Sep 17 00:00:00 2001 From: Joseph DiMaria Date: Sat, 31 Jan 2026 02:34:17 -0800 Subject: [PATCH] Mary had a little lamb - proper bpm and duration parsing --- assets/songs/json/mary.json | 272 ++++++++++++++++++++++++++++++++++++ src/entities/song.h | 21 +++ src/main.cpp | 5 +- 3 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 assets/songs/json/mary.json diff --git a/assets/songs/json/mary.json b/assets/songs/json/mary.json new file mode 100644 index 0000000..34ffd7e --- /dev/null +++ b/assets/songs/json/mary.json @@ -0,0 +1,272 @@ +{ + "header": { + "keySignatures": [], + "meta": [], + "name": "", + "ppq": 480, + "tempos": [ + { + "bpm": 120, + "ticks": 0 + } + ], + "timeSignatures": [ + { + "ticks": 0, + "timeSignature": [ + 4, + 4 + ], + "measures": 0 + } + ] + }, + "tracks": [ + { + "channel": 0, + "controlChanges": { + "1": [ + { + "number": 1, + "ticks": 0, + "time": 0, + "value": 0 + } + ], + "6": [ + { + "number": 6, + "ticks": 0, + "time": 0, + "value": 0.5039370078740157 + }, + { + "number": 6, + "ticks": 0, + "time": 0, + "value": 0.5039370078740157 + }, + { + "number": 6, + "ticks": 0, + "time": 0, + "value": 0.09448818897637795 + } + ], + "7": [ + { + "number": 7, + "ticks": 0, + "time": 0, + "value": 0.7874015748031497 + } + ], + "10": [ + { + "number": 10, + "ticks": 0, + "time": 0, + "value": 0.5039370078740157 + } + ], + "11": [ + { + "number": 11, + "ticks": 0, + "time": 0, + "value": 1 + } + ], + "38": [ + { + "number": 38, + "ticks": 0, + "time": 0, + "value": 0 + } + ], + "100": [ + { + "number": 100, + "ticks": 0, + "time": 0, + "value": 0.015748031496062992 + }, + { + "number": 100, + "ticks": 0, + "time": 0, + "value": 0.007874015748031496 + }, + { + "number": 100, + "ticks": 0, + "time": 0, + "value": 0 + } + ], + "101": [ + { + "number": 101, + "ticks": 0, + "time": 0, + "value": 0 + }, + { + "number": 101, + "ticks": 0, + "time": 0, + "value": 0 + }, + { + "number": 101, + "ticks": 0, + "time": 0, + "value": 0 + } + ], + "121": [ + { + "number": 121, + "ticks": 0, + "time": 0, + "value": 0 + } + ] + }, + "pitchBends": [ + { + "ticks": 0, + "time": 0, + "value": 0 + } + ], + "instrument": { + "family": "piano", + "number": 0, + "name": "acoustic grand piano" + }, + "name": "", + "notes": [ + { + "duration": 0.5, + "durationTicks": 480, + "midi": 64, + "name": "E4", + "ticks": 0, + "time": 0, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 62, + "name": "D4", + "ticks": 480, + "time": 0.5, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 60, + "name": "C4", + "ticks": 960, + "time": 1, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 62, + "name": "D4", + "ticks": 1440, + "time": 1.5, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 64, + "name": "E4", + "ticks": 1920, + "time": 2, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 64, + "name": "E4", + "ticks": 2400, + "time": 2.5, + "velocity": 0.7874015748031497 + }, + { + "duration": 1, + "durationTicks": 960, + "midi": 64, + "name": "E4", + "ticks": 2880, + "time": 3, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 62, + "name": "D4", + "ticks": 3840, + "time": 4, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 62, + "name": "D4", + "ticks": 4320, + "time": 4.5, + "velocity": 0.7874015748031497 + }, + { + "duration": 1, + "durationTicks": 960, + "midi": 62, + "name": "D4", + "ticks": 4800, + "time": 5, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 64, + "name": "E4", + "ticks": 5760, + "time": 6, + "velocity": 0.7874015748031497 + }, + { + "duration": 0.5, + "durationTicks": 480, + "midi": 67, + "name": "G4", + "ticks": 6240, + "time": 6.5, + "velocity": 0.7874015748031497 + }, + { + "duration": 1, + "durationTicks": 960, + "midi": 67, + "name": "G4", + "ticks": 6720, + "time": 7, + "velocity": 0.7874015748031497 + } + ], + "endOfTrackTicks": 7680 + } + ] +} \ No newline at end of file diff --git a/src/entities/song.h b/src/entities/song.h index 8e03953..2018745 100644 --- a/src/entities/song.h +++ b/src/entities/song.h @@ -10,7 +10,9 @@ struct Note { std::string name; int midi; int duration_ticks; + int duration_ms; // Duration of the note in milliseconds int ticks; + int position_ms; // How many milliseconds into the song the note occurs }; struct Instrument { @@ -26,6 +28,9 @@ struct Track { struct Header { std::string name; + int ppq; + float bpm; + double tempo; }; struct Song { @@ -33,6 +38,13 @@ struct Song { std::vector tracks; }; +int ticksToMilliseconds(int ticks, double tempo, int ppq) { + double microseconds = (ticks * tempo) / ppq; + + // Convert to milliseconds with rounding + return static_cast(microseconds / 1000.0 + 0.5); +} + Song parseSong(const rapidjson::Document& doc) { assert(doc.IsObject()); @@ -42,6 +54,13 @@ Song parseSong(const rapidjson::Document& doc) { const auto& h = doc["header"].GetObject(); Header header; header.name = h["name"].GetString(); + header.ppq = h["ppq"].GetInt(); + printf("header has tempos: %d\n", h.HasMember("tempos")); + if (h.HasMember("tempos") && h["tempos"].IsArray()) { + const auto& tempos = h["tempos"].GetArray(); + header.bpm = tempos[0]["bpm"].GetFloat(); + header.tempo = 60000000.0 / header.bpm; + } song.header = header; } @@ -66,7 +85,9 @@ Song parseSong(const rapidjson::Document& doc) { note.name = n["name"].GetString(); note.midi = n["midi"].GetInt(); note.duration_ticks = n["durationTicks"].GetInt(); + note.duration_ms = ticksToMilliseconds(note.duration_ticks, song.header.tempo, song.header.ppq); note.ticks = n["ticks"].GetInt(); + note.position_ms = ticksToMilliseconds(note.ticks, song.header.tempo, song.header.ppq); track.notes.push_back(note); } } diff --git a/src/main.cpp b/src/main.cpp index 36af4c9..cf4db39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,8 +32,11 @@ int main(int argc, char** argv) game.add_scene("title"); game.add_scene("ghhb"); - Song& song = song_manager->load_song("tetris", "assets/songs/json/tetris.json"); + Song& song = song_manager->load_song("mary_had_a_lil_lamb", "assets/songs/json/mary.json"); printf("Song name: %s\n", song.header.name.c_str()); + printf("Song bpm: %f\n", song.header.bpm); + printf("Song tempo: %f\n", song.header.tempo); + printf("First note duration: %d\n", song.tracks[0].notes[0].duration_ms); #ifdef __EMSCRIPTEN__