Fixed overlapping glyph bug. Reverted mary.json to single track.
This commit is contained in:
parent
7654d1323c
commit
c370dbef9a
@ -26,83 +26,246 @@
|
|||||||
{
|
{
|
||||||
"channel": 0,
|
"channel": 0,
|
||||||
"controlChanges": {
|
"controlChanges": {
|
||||||
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}]
|
"1": [
|
||||||
},
|
{
|
||||||
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}],
|
"number": 1,
|
||||||
"instrument": {
|
"ticks": 0,
|
||||||
"family": "pipe",
|
"time": 0,
|
||||||
"number": 73,
|
"value": 0
|
||||||
"name": "flute"
|
}
|
||||||
},
|
|
||||||
"name": "Melody 1",
|
|
||||||
"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}
|
|
||||||
],
|
],
|
||||||
"endOfTrackTicks": 1440
|
"6": [
|
||||||
|
{
|
||||||
|
"number": 6,
|
||||||
|
"ticks": 0,
|
||||||
|
"time": 0,
|
||||||
|
"value": 0.5039370078740157
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"channel": 1,
|
"number": 6,
|
||||||
"controlChanges": {
|
"ticks": 0,
|
||||||
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}]
|
"time": 0,
|
||||||
},
|
"value": 0.5039370078740157
|
||||||
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}],
|
|
||||||
"instrument": {
|
|
||||||
"family": "strings",
|
|
||||||
"number": 40,
|
|
||||||
"name": "violin"
|
|
||||||
},
|
|
||||||
"name": "Melody 2",
|
|
||||||
"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}
|
|
||||||
],
|
|
||||||
"endOfTrackTicks": 3840
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"channel": 2,
|
"number": 6,
|
||||||
"controlChanges": {
|
"ticks": 0,
|
||||||
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}]
|
"time": 0,
|
||||||
},
|
"value": 0.09448818897637795
|
||||||
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}],
|
}
|
||||||
"instrument": {
|
|
||||||
"family": "guitar",
|
|
||||||
"number": 24,
|
|
||||||
"name": "acoustic guitar (nylon)"
|
|
||||||
},
|
|
||||||
"name": "Melody 3",
|
|
||||||
"notes": [
|
|
||||||
{"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": 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}
|
|
||||||
],
|
],
|
||||||
"endOfTrackTicks": 5760
|
"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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"channel": 3,
|
"number": 100,
|
||||||
"controlChanges": {
|
"ticks": 0,
|
||||||
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}]
|
"time": 0,
|
||||||
|
"value": 0.007874015748031496
|
||||||
},
|
},
|
||||||
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}],
|
{
|
||||||
|
"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": {
|
"instrument": {
|
||||||
"family": "chromatic percussion",
|
"family": "piano",
|
||||||
"number": 9,
|
"number": 0,
|
||||||
"name": "glockenspiel"
|
"name": "acoustic grand piano"
|
||||||
},
|
},
|
||||||
"name": "Melody 4",
|
"name": "",
|
||||||
"notes": [
|
"notes": [
|
||||||
{"duration": 0.5, "durationTicks": 480, "midi": 60, "name": "C4", "ticks": 960, "time": 1, "velocity": 0.7874015748031497},
|
{
|
||||||
{"duration": 0.5, "durationTicks": 480, "midi": 64, "name": "E4", "ticks": 5760, "time": 6, "velocity": 0.7874015748031497},
|
"duration": 0.5,
|
||||||
{"duration": 0.5, "durationTicks": 480, "midi": 67, "name": "G4", "ticks": 6240, "time": 6.5, "velocity": 0.7874015748031497},
|
"durationTicks": 480,
|
||||||
{"duration": 1, "durationTicks": 960, "midi": 67, "name": "G4", "ticks": 6720, "time": 7, "velocity": 0.7874015748031497}
|
"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
|
"endOfTrackTicks": 7680
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,9 @@ using SongCatalogEntry = std::pair<std::string, int>;
|
|||||||
inline std::vector<SongCatalogEntry> get_song_catalog()
|
inline std::vector<SongCatalogEntry> get_song_catalog()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{"assets/songs/json/mary.json", -1},
|
{"assets/songs/json/mary.json", 0},
|
||||||
{"assets/songs/json/pallettown.json", -1},
|
{"assets/songs/json/pallettown.json", 0},
|
||||||
{"assets/songs/json/tetris.json", -1},
|
{"assets/songs/json/tetris.json", 0},
|
||||||
{"assets/songs/json/undertale.json", 0},
|
{"assets/songs/json/undertale.json", 0},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ constexpr float SCROLL_PX_PER_SEC = 350.0f;
|
|||||||
constexpr float LEAD_OFFSET_SECONDS = 3.0f;
|
constexpr float LEAD_OFFSET_SECONDS = 3.0f;
|
||||||
constexpr float GLYPH_HEIGHT_FRACTION_OF_LANE = 0.5f;
|
constexpr float GLYPH_HEIGHT_FRACTION_OF_LANE = 0.5f;
|
||||||
constexpr float MIN_SUSTAIN_FALLBACK_SEC = 0.05f;
|
constexpr float MIN_SUSTAIN_FALLBACK_SEC = 0.05f;
|
||||||
|
constexpr float MIN_GLYPH_DURATION_SEC = 0.05f;
|
||||||
|
|
||||||
const int GAMEPAD_BUTTONS[LANE_COUNT] = {
|
const int GAMEPAD_BUTTONS[LANE_COUNT] = {
|
||||||
GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Left
|
GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Left
|
||||||
@ -139,12 +140,13 @@ const char* const INSTRUMENT_LANE_WAV[MAX_INSTRUMENT_TYPES][LANE_COUNT * OCTAVE_
|
|||||||
|
|
||||||
struct Glyph
|
struct Glyph
|
||||||
{
|
{
|
||||||
float time = 0.0f;
|
float time = 0.0f; // Time when the BOTTOM of the glyph hits the line (when note should be played)
|
||||||
float duration_sec = 0.0f;
|
float duration_sec = 0.0f; // Duration extends upward from bottom (top = time + duration)
|
||||||
int lane = 0;
|
int lane = 0;
|
||||||
int instrument_slot = 0;
|
int instrument_slot = 0;
|
||||||
int octave = 0;
|
int octave = 0;
|
||||||
|
|
||||||
|
// Returns Y position of the bottom of the glyph
|
||||||
float y_position(float song_time, float hit_line_y) const
|
float y_position(float song_time, float hit_line_y) const
|
||||||
{
|
{
|
||||||
return hit_line_y - (time - song_time) * SCROLL_PX_PER_SEC;
|
return hit_line_y - (time - song_time) * SCROLL_PX_PER_SEC;
|
||||||
@ -162,6 +164,7 @@ struct PendingSound
|
|||||||
|
|
||||||
struct ActiveSustainedSound
|
struct ActiveSustainedSound
|
||||||
{
|
{
|
||||||
|
float start_time = 0.0f;
|
||||||
float end_time = 0.0f;
|
float end_time = 0.0f;
|
||||||
int lane = 0;
|
int lane = 0;
|
||||||
int instrument_slot = 0;
|
int instrument_slot = 0;
|
||||||
@ -221,12 +224,63 @@ std::vector<Glyph> chart_from_song(const Song& song, int track_override)
|
|||||||
for (const auto& pair : timed_notes)
|
for (const auto& pair : timed_notes)
|
||||||
{
|
{
|
||||||
const Note& note = *pair.second;
|
const Note& note = *pair.second;
|
||||||
float time_sec = note.ticks / ticks_per_sec;
|
float time_sec = note.ticks / ticks_per_sec; // This is now the BOTTOM time
|
||||||
float duration_sec = note.duration_ticks / ticks_per_sec;
|
float duration_sec = note.duration_ticks / ticks_per_sec;
|
||||||
int lane = note.midi % LANE_COUNT;
|
int lane = note.midi % LANE_COUNT;
|
||||||
int octave = note.midi % (LANE_COUNT * OCTAVE_COUNT);
|
int octave = note.midi % (LANE_COUNT * OCTAVE_COUNT);
|
||||||
int instrument_slot = note_index % MAX_INSTRUMENT_TYPES;
|
int instrument_slot = note_index % MAX_INSTRUMENT_TYPES;
|
||||||
|
|
||||||
|
// Log original glyph timing (time is bottom, time + duration is top)
|
||||||
|
float new_bottom_time = time_sec;
|
||||||
|
float new_top_time = time_sec + duration_sec;
|
||||||
|
std::printf("[NEW GLYPH] lane=%d bottom=%.3f top=%.3f dur=%.3f\n",
|
||||||
|
lane, new_bottom_time, new_top_time, duration_sec);
|
||||||
|
|
||||||
|
Glyph* last_in_lane = nullptr;
|
||||||
|
for (size_t i = glyphs.size(); i-- > 0;)
|
||||||
|
{
|
||||||
|
if (glyphs[i].lane == lane)
|
||||||
|
{
|
||||||
|
last_in_lane = &glyphs[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last_in_lane != nullptr)
|
||||||
|
{
|
||||||
|
// Check if existing glyph's top overlaps with new glyph's bottom
|
||||||
|
float existing_bottom = last_in_lane->time;
|
||||||
|
float existing_top = last_in_lane->time + last_in_lane->duration_sec;
|
||||||
|
|
||||||
|
std::printf(" [CHECK] existing lane=%d bottom=%.3f top=%.3f vs new_bottom=%.3f\n",
|
||||||
|
last_in_lane->lane, existing_bottom, existing_top, new_bottom_time);
|
||||||
|
|
||||||
|
if (existing_top > new_bottom_time)
|
||||||
|
{
|
||||||
|
// Truncate existing glyph so its top doesn't go past new glyph's bottom
|
||||||
|
float shortened_duration = new_bottom_time - existing_bottom;
|
||||||
|
|
||||||
|
std::printf(" [OVERLAP DETECTED] shortened_dur=%.3f (was %.3f)\n",
|
||||||
|
shortened_duration, last_in_lane->duration_sec);
|
||||||
|
|
||||||
|
if (shortened_duration < MIN_GLYPH_DURATION_SEC)
|
||||||
|
{
|
||||||
|
std::printf(" [SKIP] shortened duration %.3f < MIN %.3f, skipping new glyph\n",
|
||||||
|
shortened_duration, MIN_GLYPH_DURATION_SEC);
|
||||||
|
note_index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float old_duration = last_in_lane->duration_sec;
|
||||||
|
last_in_lane->duration_sec = shortened_duration;
|
||||||
|
|
||||||
|
std::printf(" [TRUNCATED] lane=%d: duration %.3f -> %.3f (new_top=%.3f)\n",
|
||||||
|
last_in_lane->lane, old_duration, shortened_duration,
|
||||||
|
last_in_lane->time + shortened_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
glyphs.push_back(Glyph{time_sec, duration_sec, lane, instrument_slot, octave});
|
glyphs.push_back(Glyph{time_sec, duration_sec, lane, instrument_slot, octave});
|
||||||
|
std::printf(" [ADDED] glyph count now: %zu\n\n", glyphs.size());
|
||||||
note_index++;
|
note_index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,6 +322,7 @@ public:
|
|||||||
std::vector<Glyph> chart;
|
std::vector<Glyph> chart;
|
||||||
std::vector<Glyph*> spawned;
|
std::vector<Glyph*> spawned;
|
||||||
std::unordered_set<Glyph*> completed_notes;
|
std::unordered_set<Glyph*> completed_notes;
|
||||||
|
std::unordered_set<Glyph*> missed_notes;
|
||||||
std::vector<PendingSound> pending_sounds;
|
std::vector<PendingSound> pending_sounds;
|
||||||
std::vector<ActiveSustainedSound> active_sustained;
|
std::vector<ActiveSustainedSound> active_sustained;
|
||||||
float song_time = 0.0f;
|
float song_time = 0.0f;
|
||||||
@ -283,10 +338,8 @@ public:
|
|||||||
bool note_sounds_loaded[MAX_INSTRUMENT_TYPES][LANE_COUNT * OCTAVE_COUNT] = {{false}};
|
bool note_sounds_loaded[MAX_INSTRUMENT_TYPES][LANE_COUNT * OCTAVE_COUNT] = {{false}};
|
||||||
std::deque<Sound> note_sounds_playing[LANE_COUNT][MAX_INSTRUMENT_TYPES];
|
std::deque<Sound> note_sounds_playing[LANE_COUNT][MAX_INSTRUMENT_TYPES];
|
||||||
static constexpr float PRESS_FLASH_DURATION = 0.12f;
|
static constexpr float PRESS_FLASH_DURATION = 0.12f;
|
||||||
static constexpr float MISS_FLASH_DURATION = 0.15f;
|
|
||||||
float press_flash_timer[LANE_COUNT] = {0};
|
float press_flash_timer[LANE_COUNT] = {0};
|
||||||
float hit_flash_timer[LANE_COUNT] = {0};
|
float hit_flash_timer[LANE_COUNT] = {0};
|
||||||
float miss_flash_timer[LANE_COUNT] = {0};
|
|
||||||
bool game_ended = false;
|
bool game_ended = false;
|
||||||
bool dev_auto_hit_mode = false;
|
bool dev_auto_hit_mode = false;
|
||||||
static constexpr float RESULTS_DELAY_AFTER_LAST_NOTE = 1.0f;
|
static constexpr float RESULTS_DELAY_AFTER_LAST_NOTE = 1.0f;
|
||||||
@ -309,13 +362,13 @@ public:
|
|||||||
dev_auto_hit_mode = false;
|
dev_auto_hit_mode = false;
|
||||||
spawned.clear();
|
spawned.clear();
|
||||||
completed_notes.clear();
|
completed_notes.clear();
|
||||||
|
missed_notes.clear();
|
||||||
pending_sounds.clear();
|
pending_sounds.clear();
|
||||||
active_sustained.clear();
|
active_sustained.clear();
|
||||||
for (int i = 0; i < LANE_COUNT; i++)
|
for (int i = 0; i < LANE_COUNT; i++)
|
||||||
{
|
{
|
||||||
press_flash_timer[i] = 0.0f;
|
press_flash_timer[i] = 0.0f;
|
||||||
hit_flash_timer[i] = 0.0f;
|
hit_flash_timer[i] = 0.0f;
|
||||||
miss_flash_timer[i] = 0.0f;
|
|
||||||
}
|
}
|
||||||
if (music_loaded)
|
if (music_loaded)
|
||||||
{
|
{
|
||||||
@ -419,23 +472,26 @@ public:
|
|||||||
IsGamepadButtonDown(physical_id, GAMEPAD_BUTTONS[lane]);
|
IsGamepadButtonDown(physical_id, GAMEPAD_BUTTONS[lane]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop_playing_released_notes(int lane)
|
void stop_playing_released_notes(int lane, float min_sustain_sec)
|
||||||
{
|
{
|
||||||
for (int slot = 0; slot < MAX_INSTRUMENT_TYPES; slot++)
|
for (int slot = 0; slot < MAX_INSTRUMENT_TYPES; slot++)
|
||||||
{
|
{
|
||||||
if (is_lane_held_by_instrument(lane, slot))
|
if (is_lane_held_by_instrument(lane, slot))
|
||||||
continue;
|
continue;
|
||||||
if (!note_sounds_playing[lane][slot].empty())
|
|
||||||
{
|
|
||||||
StopSound(note_sounds_playing[lane][slot].front());
|
|
||||||
note_sounds_playing[lane][slot].pop_front();
|
|
||||||
auto it = std::find_if(active_sustained.begin(), active_sustained.end(),
|
auto it = std::find_if(active_sustained.begin(), active_sustained.end(),
|
||||||
[lane, slot](const ActiveSustainedSound& a) {
|
[lane, slot](const ActiveSustainedSound& a) {
|
||||||
return a.lane == lane && a.instrument_slot == slot;
|
return a.lane == lane && a.instrument_slot == slot;
|
||||||
});
|
});
|
||||||
if (it != active_sustained.end())
|
if (it == active_sustained.end())
|
||||||
active_sustained.erase(it);
|
continue;
|
||||||
|
if (song_time < it->start_time + min_sustain_sec)
|
||||||
|
continue;
|
||||||
|
if (!note_sounds_playing[lane][slot].empty())
|
||||||
|
{
|
||||||
|
StopSound(note_sounds_playing[lane][slot].front());
|
||||||
|
note_sounds_playing[lane][slot].pop_front();
|
||||||
}
|
}
|
||||||
|
active_sustained.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,15 +539,24 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float glyph_y(const Glyph& n) const
|
// Returns the Y position of the BOTTOM of the glyph
|
||||||
|
// n.time represents when the bottom should reach the hit line
|
||||||
|
float glyph_bottom_y(const Glyph& n) const
|
||||||
{
|
{
|
||||||
return hit_line_y - (n.time + chart_time_offset - song_time) * SCROLL_PX_PER_SEC;
|
return hit_line_y - (n.time + chart_time_offset - song_time) * SCROLL_PX_PER_SEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float glyph_height_px(const Glyph& n, float lane_width_val)
|
||||||
|
{
|
||||||
|
float min_height = lane_width_val * GLYPH_HEIGHT_FRACTION_OF_LANE;
|
||||||
|
float duration_height = n.duration_sec * SCROLL_PX_PER_SEC;
|
||||||
|
return std::max(min_height, duration_height);
|
||||||
|
}
|
||||||
|
|
||||||
bool is_note_hittable(const Glyph& n) const
|
bool is_note_hittable(const Glyph& n) const
|
||||||
{
|
{
|
||||||
float y = glyph_y(n);
|
float bottom_y = glyph_bottom_y(n);
|
||||||
return y >= upper_bar_y - HIT_ZONE_MARGIN && y <= hit_line_y + HIT_ZONE_MARGIN;
|
return bottom_y >= upper_bar_y - HIT_ZONE_MARGIN && bottom_y <= hit_line_y + HIT_ZONE_MARGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void consume_note(Glyph* n)
|
void consume_note(Glyph* n)
|
||||||
@ -500,29 +565,27 @@ public:
|
|||||||
if (it != spawned.end())
|
if (it != spawned.end())
|
||||||
{
|
{
|
||||||
hit_flash_timer[n->lane] = PRESS_FLASH_DURATION;
|
hit_flash_timer[n->lane] = PRESS_FLASH_DURATION;
|
||||||
spawned.erase(it);
|
|
||||||
completed_notes.insert(n);
|
completed_notes.insert(n);
|
||||||
float hit_line_time = n->time + chart_time_offset;
|
// n.time is when the bottom should hit the line
|
||||||
|
float bottom_hits_line_time = n->time + chart_time_offset;
|
||||||
if (note_sounds_loaded[n->instrument_slot][n->octave])
|
if (note_sounds_loaded[n->instrument_slot][n->octave])
|
||||||
pending_sounds.push_back(
|
pending_sounds.push_back(
|
||||||
{hit_line_time, n->duration_sec, n->lane, n->instrument_slot, n->octave});
|
{bottom_hits_line_time, n->duration_sec, n->lane, n->instrument_slot, n->octave});
|
||||||
float y_n = glyph_y(*n);
|
float bottom_y_n = glyph_bottom_y(*n);
|
||||||
for (auto it2 = spawned.begin(); it2 != spawned.end();)
|
for (Glyph* other : spawned)
|
||||||
{
|
{
|
||||||
Glyph* other = *it2;
|
if (other == n)
|
||||||
if (other != n && other->lane == n->lane && other->instrument_slot == n->instrument_slot &&
|
continue;
|
||||||
fabsf(glyph_y(*other) - y_n) <= SIMULTANEOUS_NOTE_Y_TOLERANCE)
|
if (other->lane != n->lane || other->instrument_slot != n->instrument_slot)
|
||||||
|
continue;
|
||||||
|
float bottom_y_other = glyph_bottom_y(*other);
|
||||||
|
if (fabsf(bottom_y_other - bottom_y_n) <= SIMULTANEOUS_NOTE_Y_TOLERANCE)
|
||||||
{
|
{
|
||||||
completed_notes.insert(other);
|
completed_notes.insert(other);
|
||||||
float other_hit_line_time = other->time + chart_time_offset;
|
float other_bottom_hits_line_time = other->time + chart_time_offset;
|
||||||
if (note_sounds_loaded[other->instrument_slot][other->octave])
|
if (note_sounds_loaded[other->instrument_slot][other->octave])
|
||||||
pending_sounds.push_back({other_hit_line_time, other->duration_sec,
|
pending_sounds.push_back({other_bottom_hits_line_time, other->duration_sec,
|
||||||
other->lane, other->instrument_slot, other->octave});
|
other->lane, other->instrument_slot, other->octave});
|
||||||
it2 = spawned.erase(it2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++it2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,6 +605,20 @@ public:
|
|||||||
void update(float delta_time) override
|
void update(float delta_time) override
|
||||||
{
|
{
|
||||||
update_layout();
|
update_layout();
|
||||||
|
{
|
||||||
|
static bool debug_bottom_printed = false;
|
||||||
|
if (!debug_bottom_printed && !chart.empty() && lane_width > 0.0f)
|
||||||
|
{
|
||||||
|
for (const Glyph& g : chart)
|
||||||
|
{
|
||||||
|
float bottom_hits_line_time = g.time + chart_time_offset;
|
||||||
|
float top_hits_line_time = bottom_hits_line_time + g.duration_sec;
|
||||||
|
std::printf("[chart] glyph lane=%d bottom_time=%.3f top_time=%.3f dur=%.3f\n",
|
||||||
|
g.lane, bottom_hits_line_time, top_hits_line_time, g.duration_sec);
|
||||||
|
}
|
||||||
|
debug_bottom_printed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (is_select_pressed())
|
if (is_select_pressed())
|
||||||
{
|
{
|
||||||
dev_auto_hit_mode = !dev_auto_hit_mode;
|
dev_auto_hit_mode = !dev_auto_hit_mode;
|
||||||
@ -565,9 +642,9 @@ public:
|
|||||||
song_time += delta_time;
|
song_time += delta_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
float glyph_height_px = lane_width * GLYPH_HEIGHT_FRACTION_OF_LANE;
|
float min_glyph_height_px = lane_width * GLYPH_HEIGHT_FRACTION_OF_LANE;
|
||||||
float time_per_glyph_height =
|
float time_per_glyph_height =
|
||||||
glyph_height_px > 0.f ? glyph_height_px / SCROLL_PX_PER_SEC : MIN_SUSTAIN_FALLBACK_SEC;
|
min_glyph_height_px > 0.f ? min_glyph_height_px / SCROLL_PX_PER_SEC : MIN_SUSTAIN_FALLBACK_SEC;
|
||||||
for (auto it = pending_sounds.begin(); it != pending_sounds.end();)
|
for (auto it = pending_sounds.begin(); it != pending_sounds.end();)
|
||||||
{
|
{
|
||||||
if (song_time >= it->play_time)
|
if (song_time >= it->play_time)
|
||||||
@ -577,7 +654,7 @@ public:
|
|||||||
note_sounds_playing[it->lane][it->instrument_slot].push_back(s);
|
note_sounds_playing[it->lane][it->instrument_slot].push_back(s);
|
||||||
float sustain_sec = std::max(it->duration_sec, time_per_glyph_height);
|
float sustain_sec = std::max(it->duration_sec, time_per_glyph_height);
|
||||||
active_sustained.push_back(
|
active_sustained.push_back(
|
||||||
{it->play_time + sustain_sec, it->lane, it->instrument_slot});
|
{it->play_time, it->play_time + sustain_sec, it->lane, it->instrument_slot});
|
||||||
it = pending_sounds.erase(it);
|
it = pending_sounds.erase(it);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -634,16 +711,29 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Glyph* n : spawned)
|
||||||
|
{
|
||||||
|
if (completed_notes.count(n) != 0)
|
||||||
|
continue;
|
||||||
|
if (missed_notes.count(n) != 0)
|
||||||
|
continue;
|
||||||
|
float bottom_y = glyph_bottom_y(*n);
|
||||||
|
if (bottom_y > hit_line_y + HIT_ZONE_MARGIN)
|
||||||
|
{
|
||||||
|
missed_notes.insert(n);
|
||||||
|
combo = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = spawned.begin(); it != spawned.end();)
|
for (auto it = spawned.begin(); it != spawned.end();)
|
||||||
{
|
{
|
||||||
Glyph* n = *it;
|
Glyph* n = *it;
|
||||||
float y = glyph_y(*n);
|
float bottom_y = glyph_bottom_y(*n);
|
||||||
if (y > hit_line_y + HIT_ZONE_MARGIN)
|
if (bottom_y > screen_height + 40.0f)
|
||||||
{
|
{
|
||||||
miss_flash_timer[n->lane] = MISS_FLASH_DURATION;
|
completed_notes.erase(n);
|
||||||
completed_notes.insert(n);
|
missed_notes.erase(n);
|
||||||
it = spawned.erase(it);
|
it = spawned.erase(it);
|
||||||
combo = 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -663,11 +753,6 @@ public:
|
|||||||
{
|
{
|
||||||
hit_flash_timer[lane] = 0.0f;
|
hit_flash_timer[lane] = 0.0f;
|
||||||
}
|
}
|
||||||
miss_flash_timer[lane] -= delta_time;
|
|
||||||
if (miss_flash_timer[lane] < 0.0f)
|
|
||||||
{
|
|
||||||
miss_flash_timer[lane] = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev_auto_hit_mode)
|
if (dev_auto_hit_mode)
|
||||||
@ -675,6 +760,8 @@ public:
|
|||||||
std::vector<Glyph*> to_consume;
|
std::vector<Glyph*> to_consume;
|
||||||
for (Glyph* n : spawned)
|
for (Glyph* n : spawned)
|
||||||
{
|
{
|
||||||
|
if (completed_notes.count(n) != 0)
|
||||||
|
continue;
|
||||||
if (is_note_hittable(*n))
|
if (is_note_hittable(*n))
|
||||||
{
|
{
|
||||||
to_consume.push_back(n);
|
to_consume.push_back(n);
|
||||||
@ -689,7 +776,7 @@ public:
|
|||||||
{
|
{
|
||||||
for (int lane = 0; lane < LANE_COUNT; lane++)
|
for (int lane = 0; lane < LANE_COUNT; lane++)
|
||||||
{
|
{
|
||||||
stop_playing_released_notes(lane);
|
stop_playing_released_notes(lane, time_per_glyph_height);
|
||||||
bool pressed = is_lane_pressed(lane);
|
bool pressed = is_lane_pressed(lane);
|
||||||
if (pressed)
|
if (pressed)
|
||||||
press_flash_timer[lane] = PRESS_FLASH_DURATION;
|
press_flash_timer[lane] = PRESS_FLASH_DURATION;
|
||||||
@ -713,8 +800,8 @@ public:
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float y = glyph_y(*n);
|
float bottom_y = glyph_bottom_y(*n);
|
||||||
float d = fabsf(y - hit_line_y);
|
float d = fabsf(bottom_y - hit_line_y);
|
||||||
if (d < best_dist)
|
if (d < best_dist)
|
||||||
{
|
{
|
||||||
best_dist = d;
|
best_dist = d;
|
||||||
@ -781,15 +868,6 @@ public:
|
|||||||
static_cast<int>(RECEPTOR_HEIGHT),
|
static_cast<int>(RECEPTOR_HEIGHT),
|
||||||
Color{255, 255, 255, static_cast<unsigned char>(alpha)});
|
Color{255, 255, 255, static_cast<unsigned char>(alpha)});
|
||||||
}
|
}
|
||||||
if (miss_flash_timer[lane] > 0.0f)
|
|
||||||
{
|
|
||||||
float alpha = 200.0f * (miss_flash_timer[lane] / MISS_FLASH_DURATION);
|
|
||||||
DrawRectangle(static_cast<int>(lane * lane_width),
|
|
||||||
static_cast<int>(upper_bar_y),
|
|
||||||
static_cast<int>(lane_width),
|
|
||||||
static_cast<int>(RECEPTOR_HEIGHT),
|
|
||||||
Color{255, 80, 80, static_cast<unsigned char>(alpha)});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DrawLineEx(Vector2{0, upper_bar_y}, Vector2{screen_width, upper_bar_y}, 3.0f, WHITE);
|
DrawLineEx(Vector2{0, upper_bar_y}, Vector2{screen_width, upper_bar_y}, 3.0f, WHITE);
|
||||||
DrawLineEx(Vector2{0, hit_line_y}, Vector2{screen_width, hit_line_y}, 3.0f, WHITE);
|
DrawLineEx(Vector2{0, hit_line_y}, Vector2{screen_width, hit_line_y}, 3.0f, WHITE);
|
||||||
@ -806,11 +884,14 @@ public:
|
|||||||
button_label_font_size, 1, Color{220, 220, 240, 255});
|
button_label_font_size, 1, Color{220, 220, 240, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Color MISSED_GLYPH_COLOR = {140, 140, 140, 255};
|
||||||
std::vector<std::vector<Glyph*>> by_lane(static_cast<size_t>(LANE_COUNT));
|
std::vector<std::vector<Glyph*>> by_lane(static_cast<size_t>(LANE_COUNT));
|
||||||
for (Glyph* n : spawned)
|
for (Glyph* n : spawned)
|
||||||
{
|
{
|
||||||
float y = glyph_y(*n);
|
float height = glyph_height_px(*n, lane_width);
|
||||||
if (y < -40.0f || y > screen_height + 40.0f)
|
float bottom = glyph_bottom_y(*n);
|
||||||
|
float top = bottom - height;
|
||||||
|
if (bottom < -40.0f || top > screen_height + 40.0f)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -819,16 +900,23 @@ public:
|
|||||||
for (int lane = 0; lane < LANE_COUNT; lane++)
|
for (int lane = 0; lane < LANE_COUNT; lane++)
|
||||||
{
|
{
|
||||||
std::vector<Glyph*>& list = by_lane[static_cast<size_t>(lane)];
|
std::vector<Glyph*>& list = by_lane[static_cast<size_t>(lane)];
|
||||||
|
// Sort by top Y (which is bottom - height, i.e., earlier in time)
|
||||||
std::sort(list.begin(), list.end(),
|
std::sort(list.begin(), list.end(),
|
||||||
[this](Glyph* a, Glyph* b) { return glyph_y(*a) < glyph_y(*b); });
|
[this](Glyph* a, Glyph* b) {
|
||||||
|
float top_a = glyph_bottom_y(*a) - glyph_height_px(*a, lane_width);
|
||||||
|
float top_b = glyph_bottom_y(*b) - glyph_height_px(*b, lane_width);
|
||||||
|
return top_a < top_b;
|
||||||
|
});
|
||||||
float left_base = lane * lane_width;
|
float left_base = lane * lane_width;
|
||||||
for (size_t i = 0; i < list.size();)
|
for (size_t i = 0; i < list.size();)
|
||||||
{
|
{
|
||||||
size_t j = i;
|
size_t j = i;
|
||||||
float y0 = glyph_y(*list[i]);
|
float top_y0 = glyph_bottom_y(*list[i]) - glyph_height_px(*list[i], lane_width);
|
||||||
while (j < list.size() &&
|
while (j < list.size())
|
||||||
glyph_y(*list[j]) - y0 <= SIMULTANEOUS_NOTE_Y_TOLERANCE)
|
|
||||||
{
|
{
|
||||||
|
float top_y_j = glyph_bottom_y(*list[j]) - glyph_height_px(*list[j], lane_width);
|
||||||
|
if (top_y_j - top_y0 > SIMULTANEOUS_NOTE_Y_TOLERANCE)
|
||||||
|
break;
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
std::vector<Glyph*> group(list.begin() + static_cast<std::ptrdiff_t>(i),
|
std::vector<Glyph*> group(list.begin() + static_cast<std::ptrdiff_t>(i),
|
||||||
@ -844,16 +932,17 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
float slice_width = lane_width / static_cast<float>(instrument_count);
|
float slice_width = lane_width / static_cast<float>(instrument_count);
|
||||||
float glyph_height = lane_width * GLYPH_HEIGHT_FRACTION_OF_LANE;
|
|
||||||
int column = 0;
|
int column = 0;
|
||||||
for (size_t g = 0; g < group.size();)
|
for (size_t g = 0; g < group.size();)
|
||||||
{
|
{
|
||||||
int slot = group[g]->instrument_slot;
|
int slot = group[g]->instrument_slot;
|
||||||
float left = left_base + column * slice_width;
|
float left = left_base + column * slice_width;
|
||||||
Glyph* n = group[g];
|
Glyph* n = group[g];
|
||||||
float y = glyph_y(*n);
|
float glyph_height = glyph_height_px(*n, lane_width);
|
||||||
float top = y - glyph_height / 2.0f;
|
float bottom = glyph_bottom_y(*n);
|
||||||
Color fill = INSTRUMENT_COLORS[n->instrument_slot];
|
float top = bottom - glyph_height;
|
||||||
|
bool missed = missed_notes.count(n) != 0;
|
||||||
|
Color fill = missed ? MISSED_GLYPH_COLOR : INSTRUMENT_COLORS[n->instrument_slot];
|
||||||
Color edge = Color{
|
Color edge = Color{
|
||||||
static_cast<unsigned char>(std::min(255, fill.r + 35)),
|
static_cast<unsigned char>(std::min(255, fill.r + 35)),
|
||||||
static_cast<unsigned char>(std::min(255, fill.g + 50)),
|
static_cast<unsigned char>(std::min(255, fill.g + 50)),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user