Songs now only play specific tracks from the MIDI, and instruments are ignored (each player still has their own instrument)
This commit is contained in:
parent
e1c6f4d2f9
commit
727fd97f39
@ -1,20 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
inline std::vector<std::string> get_song_catalog()
|
||||
/** -1 = pick track by most notes; 0-based index = use that track for the chart. */
|
||||
using SongCatalogEntry = std::pair<std::string, int>;
|
||||
|
||||
inline std::vector<SongCatalogEntry> 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;
|
||||
}
|
||||
|
||||
@ -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<GHHBScene>("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);
|
||||
|
||||
@ -147,50 +147,80 @@ struct Glyph
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Glyph> 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<Glyph> chart_from_song(const Song& song, int track_override)
|
||||
{
|
||||
std::vector<Glyph> glyphs;
|
||||
if (song.tracks.empty())
|
||||
return glyphs;
|
||||
|
||||
size_t track_idx;
|
||||
if (track_override >= 0 &&
|
||||
static_cast<size_t>(track_override) < song.tracks.size())
|
||||
{
|
||||
track_idx = static_cast<size_t>(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<std::pair<size_t, size_t>> 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<size_t>(MAX_INSTRUMENT_TYPES), track_note_counts.size());
|
||||
for (size_t slot = 0; slot < n_used; slot++)
|
||||
std::vector<std::pair<int, const Note*>> 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<int>(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<size_t>(track_override) < song.tracks.size())
|
||||
? " [track override]"
|
||||
: "");
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
std::vector<Glyph> load_chart(const char* path)
|
||||
std::vector<Glyph> load_chart(const char* path, int track_override)
|
||||
{
|
||||
std::vector<Glyph> empty;
|
||||
std::FILE* fp = std::fopen(path, "rb");
|
||||
@ -203,7 +233,7 @@ std::vector<Glyph> 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<Glyph> 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())
|
||||
{
|
||||
|
||||
@ -8,19 +8,23 @@
|
||||
#include <vector>
|
||||
|
||||
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<SongEntry> build_song_list(const std::vector<std::string>& paths)
|
||||
inline std::vector<SongEntry> build_song_list(const std::vector<SongCatalogEntry>& catalog)
|
||||
{
|
||||
std::vector<SongEntry> 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<SongEntry> build_song_list(const std::vector<std::string>& 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<int>(songs.size()))
|
||||
{
|
||||
SELECTED_SONG_PATH = songs[idx].path;
|
||||
SELECTED_TRACK_OVERRIDE = songs[idx].track_override;
|
||||
game->go_to_scene("instrument_select");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user