diff --git a/src/entities/song_catalog.h b/src/entities/song_catalog.h index 07c2164..a89d18b 100644 --- a/src/entities/song_catalog.h +++ b/src/entities/song_catalog.h @@ -1,20 +1,30 @@ #pragma once #include +#include #include -inline std::vector get_song_catalog() +/** -1 = pick track by most notes; 0-based index = use that track for the chart. */ +using SongCatalogEntry = std::pair; + +inline std::vector get_song_catalog() { return { - "assets/songs/json/mary.json", - "assets/songs/json/pallettown.json", - "assets/songs/json/tetris.json", - "assets/songs/json/undertale.json", + {"assets/songs/json/mary.json", -1}, + {"assets/songs/json/pallettown.json", -1}, + {"assets/songs/json/tetris.json", -1}, + {"assets/songs/json/undertale.json", 0}, }; } inline std::string get_default_song_path() { auto catalog = get_song_catalog(); - return catalog.empty() ? "" : catalog.front(); + return catalog.empty() ? "" : catalog.front().first; +} + +inline int get_default_track_override() +{ + auto catalog = get_song_catalog(); + return catalog.empty() ? -1 : catalog.front().second; } diff --git a/src/main.cpp b/src/main.cpp index 6ed309a..8d2279c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ int INSTRUMENT_GAMEPAD_INDEX[MAX_INSTRUMENT_TYPES] = {-1, -1, -1, -1}; int INSTRUMENT_PHYSICAL_GAMEPAD[MAX_INSTRUMENT_TYPES] = {-1, -1, -1, -1}; std::string SELECTED_SONG_PATH = get_default_song_path(); +int SELECTED_TRACK_OVERRIDE = get_default_track_override(); Game game; @@ -42,9 +43,9 @@ int main(int argc, char** argv) game.add_scene("ghhb"); auto catalog = get_song_catalog(); - for (const auto& path : catalog) + for (const auto& entry : catalog) { - song_manager->load_song(path, path); + song_manager->load_song(entry.first, entry.first); } std::string default_path = get_default_song_path(); Song& song = song_manager->get_song(default_path); diff --git a/src/samples/ghhb_game.h b/src/samples/ghhb_game.h index 76b12b9..0a87fab 100644 --- a/src/samples/ghhb_game.h +++ b/src/samples/ghhb_game.h @@ -147,50 +147,80 @@ struct Glyph } }; -std::vector chart_from_song(const Song& song) +static size_t pick_track_by_note_count(const Song& song) +{ + size_t best = 0; + size_t best_count = 0; + for (size_t i = 0; i < song.tracks.size(); i++) + { + size_t n = song.tracks[i].notes.size(); + if (n > best_count) + { + best_count = n; + best = i; + } + } + return best; +} + +std::vector chart_from_song(const Song& song, int track_override) { std::vector glyphs; + if (song.tracks.empty()) + return glyphs; + + size_t track_idx; + if (track_override >= 0 && + static_cast(track_override) < song.tracks.size()) + { + track_idx = static_cast(track_override); + } + else + { + track_idx = pick_track_by_note_count(song); + } + const Track& track = song.tracks[track_idx]; int ppq = song.header.ppq > 0 ? song.header.ppq : 480; float bpm = song.header.bpm > 0.0f ? song.header.bpm : 120.0f; float ticks_per_sec = ppq * (bpm / 60.0f); - std::vector> track_note_counts; - for (size_t i = 0; i < song.tracks.size(); i++) - track_note_counts.push_back({i, song.tracks[i].notes.size()}); - - std::sort(track_note_counts.begin(), track_note_counts.end(), - [](const auto& a, const auto& b) { return a.second > b.second; }); - - size_t n_used = std::min(static_cast(MAX_INSTRUMENT_TYPES), track_note_counts.size()); - for (size_t slot = 0; slot < n_used; slot++) + std::vector> timed_notes; + for (const Note& note : track.notes) { - size_t track_idx = track_note_counts[slot].first; - size_t note_count = track_note_counts[slot].second; - const Track& track = song.tracks[track_idx]; - std::printf("Instrument %zu: \"%s\" (family=%s number=%d) %zu notes\n", - slot, track.name.c_str(), track.instrument.family.c_str(), - track.instrument.number, note_count); - int instrument_slot = static_cast(slot); - for (const Note& note : track.notes) - { - if (note.midi < 0 || note.midi > 127) - continue; - float time_sec = note.ticks / ticks_per_sec; - int lane = note.midi % LANE_COUNT; - int octave = note.midi % (LANE_COUNT * OCTAVE_COUNT); - glyphs.push_back(Glyph{time_sec, lane, instrument_slot, octave}); - } + if (note.midi >= 0 && note.midi <= 127) + timed_notes.push_back({note.ticks, ¬e}); } - std::sort(glyphs.begin(), glyphs.end(), - [](const Glyph& a, const Glyph& b) { - if (a.time != b.time) - return a.time < b.time; - return a.lane < b.lane; + std::sort(timed_notes.begin(), timed_notes.end(), + [](const auto& a, const auto& b) { + if (a.first != b.first) + return a.first < b.first; + return a.second->midi < b.second->midi; }); + + int note_index = 0; + for (const auto& pair : timed_notes) + { + const Note& note = *pair.second; + float time_sec = note.ticks / ticks_per_sec; + int lane = note.midi % LANE_COUNT; + int octave = note.midi % (LANE_COUNT * OCTAVE_COUNT); + int instrument_slot = note_index % MAX_INSTRUMENT_TYPES; + glyphs.push_back(Glyph{time_sec, lane, instrument_slot, octave}); + std::printf("glyph track=%zu time=%.2f lane=%d slot=%d\n", + track_idx, time_sec, lane, instrument_slot); + note_index++; + } + + std::printf("Chart: single track \"%s\" (%zu notes), round-robin player assignment%s\n", + track.name.c_str(), glyphs.size(), + (track_override >= 0 && + static_cast(track_override) < song.tracks.size()) + ? " [track override]" + : ""); return glyphs; } -std::vector load_chart(const char* path) +std::vector load_chart(const char* path, int track_override) { std::vector empty; std::FILE* fp = std::fopen(path, "rb"); @@ -203,7 +233,7 @@ std::vector load_chart(const char* path) { Song song = parseSong(doc); std::fclose(fp); - return chart_from_song(song); + return chart_from_song(song, track_override); } std::fclose(fp); return empty; @@ -212,6 +242,7 @@ std::vector load_chart(const char* path) } // namespace extern std::string SELECTED_SONG_PATH; +extern int SELECTED_TRACK_OVERRIDE; extern int INSTRUMENT_GAMEPAD_INDEX[MAX_INSTRUMENT_TYPES]; extern int INSTRUMENT_PHYSICAL_GAMEPAD[MAX_INSTRUMENT_TYPES]; @@ -247,7 +278,7 @@ public: void on_enter() override { - chart = load_chart(SELECTED_SONG_PATH.c_str()); + chart = load_chart(SELECTED_SONG_PATH.c_str(), SELECTED_TRACK_OVERRIDE); float first_note_time = 0.0f; if (!chart.empty()) { diff --git a/src/samples/song_select.h b/src/samples/song_select.h index 828bbef..ae8663b 100644 --- a/src/samples/song_select.h +++ b/src/samples/song_select.h @@ -8,19 +8,23 @@ #include extern std::string SELECTED_SONG_PATH; +extern int SELECTED_TRACK_OVERRIDE; struct SongEntry { std::string name; std::string artist; std::string path; + int track_override; }; -inline std::vector build_song_list(const std::vector& paths) +inline std::vector build_song_list(const std::vector& catalog) { std::vector entries; - for (const std::string& path : paths) + for (const auto& entry : catalog) { + const std::string& path = entry.first; + int track_override = entry.second; std::FILE* fp = std::fopen(path.c_str(), "rb"); if (!fp) continue; @@ -35,7 +39,8 @@ inline std::vector build_song_list(const std::vector& pa std::fclose(fp); Song song = parseSong(doc); entries.push_back( - {song.header.name.empty() ? path : song.header.name, song.header.artist, path}); + {song.header.name.empty() ? path : song.header.name, song.header.artist, path, + track_override}); } while (entries.size() < 4) { @@ -116,6 +121,7 @@ public: if (idx >= 0 && idx < static_cast(songs.size())) { SELECTED_SONG_PATH = songs[idx].path; + SELECTED_TRACK_OVERRIDE = songs[idx].track_override; game->go_to_scene("instrument_select"); } }