Fixed overlapping glyph bug. Reverted mary.json to single track.

This commit is contained in:
Gordon Weeks 2026-01-31 16:30:35 -08:00
parent 7654d1323c
commit c370dbef9a
3 changed files with 393 additions and 141 deletions

View File

@ -26,83 +26,246 @@
{ {
"channel": 0, "channel": 0,
"controlChanges": { "controlChanges": {
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}] "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}], "pitchBends": [
"instrument": { {
"family": "pipe", "ticks": 0,
"number": 73, "time": 0,
"name": "flute" "value": 0
}, }
"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
},
{
"channel": 1,
"controlChanges": {
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}]
},
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}],
"instrument": { "instrument": {
"family": "strings", "family": "piano",
"number": 40, "number": 0,
"name": "violin" "name": "acoustic grand piano"
}, },
"name": "Melody 2", "name": "",
"notes": [ "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,
{"duration": 0.5, "durationTicks": 480, "midi": 60, "name": "C4", "ticks": 960, "time": 1, "velocity": 0.7874015748031497}, "durationTicks": 480,
{"duration": 0.5, "durationTicks": 480, "midi": 62, "name": "D4", "ticks": 1440, "time": 1.5, "velocity": 0.7874015748031497}, "midi": 64,
{"duration": 0.5, "durationTicks": 480, "midi": 64, "name": "E4", "ticks": 1920, "time": 2, "velocity": 0.7874015748031497}, "name": "E4",
{"duration": 0.5, "durationTicks": 480, "midi": 64, "name": "E4", "ticks": 2400, "time": 2.5, "velocity": 0.7874015748031497}, "ticks": 0,
{"duration": 1, "durationTicks": 960, "midi": 64, "name": "E4", "ticks": 2880, "time": 3, "velocity": 0.7874015748031497} "time": 0,
], "velocity": 0.7874015748031497
"endOfTrackTicks": 3840 },
}, {
{ "duration": 0.5,
"channel": 2, "durationTicks": 480,
"controlChanges": { "midi": 62,
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}] "name": "D4",
}, "ticks": 480,
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}], "time": 0.5,
"instrument": { "velocity": 0.7874015748031497
"family": "guitar", },
"number": 24, {
"name": "acoustic guitar (nylon)" "duration": 0.5,
}, "durationTicks": 480,
"name": "Melody 3", "midi": 60,
"notes": [ "name": "C4",
{"duration": 0.5, "durationTicks": 480, "midi": 62, "name": "D4", "ticks": 480, "time": 0.5, "velocity": 0.7874015748031497}, "ticks": 960,
{"duration": 0.5, "durationTicks": 480, "midi": 60, "name": "C4", "ticks": 960, "time": 1, "velocity": 0.7874015748031497}, "time": 1,
{"duration": 0.5, "durationTicks": 480, "midi": 62, "name": "D4", "ticks": 3840, "time": 4, "velocity": 0.7874015748031497}, "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,
"endOfTrackTicks": 5760 "durationTicks": 480,
}, "midi": 62,
{ "name": "D4",
"channel": 3, "ticks": 1440,
"controlChanges": { "time": 1.5,
"7": [{"number": 7, "ticks": 0, "time": 0, "value": 0.7874015748031497}] "velocity": 0.7874015748031497
}, },
"pitchBends": [{"ticks": 0, "time": 0, "value": 0}], {
"instrument": { "duration": 0.5,
"family": "chromatic percussion", "durationTicks": 480,
"number": 9, "midi": 64,
"name": "glockenspiel" "name": "E4",
}, "ticks": 1920,
"name": "Melody 4", "time": 2,
"notes": [ "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": 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": 0.5,
{"duration": 1, "durationTicks": 960, "midi": 67, "name": "G4", "ticks": 6720, "time": 7, "velocity": 0.7874015748031497} "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
} }

View File

@ -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},
}; };
} }

View File

@ -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;
auto it = std::find_if(active_sustained.begin(), active_sustained.end(),
[lane, slot](const ActiveSustainedSound& a) {
return a.lane == lane && a.instrument_slot == slot;
});
if (it == active_sustained.end())
continue;
if (song_time < it->start_time + min_sustain_sec)
continue;
if (!note_sounds_playing[lane][slot].empty()) if (!note_sounds_playing[lane][slot].empty())
{ {
StopSound(note_sounds_playing[lane][slot].front()); StopSound(note_sounds_playing[lane][slot].front());
note_sounds_playing[lane][slot].pop_front(); note_sounds_playing[lane][slot].pop_front();
auto it = std::find_if(active_sustained.begin(), active_sustained.end(),
[lane, slot](const ActiveSustainedSound& a) {
return a.lane == lane && a.instrument_slot == slot;
});
if (it != active_sustained.end())
active_sustained.erase(it);
} }
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)),