Added simultaneous notes.

This commit is contained in:
Gordon Weeks 2026-01-31 11:55:05 -08:00
parent 0be0ff963e
commit 82feac5d5b
2 changed files with 53 additions and 12 deletions

View File

@ -55,6 +55,9 @@
}, },
"name": "Melody 2", "name": "Melody 2",
"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, "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": 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": 1920, "time": 2, "velocity": 0.7874015748031497},
{"duration": 0.5, "durationTicks": 480, "midi": 64, "name": "E4", "ticks": 2400, "time": 2.5, "velocity": 0.7874015748031497}, {"duration": 0.5, "durationTicks": 480, "midi": 64, "name": "E4", "ticks": 2400, "time": 2.5, "velocity": 0.7874015748031497},
@ -75,6 +78,8 @@
}, },
"name": "Melody 3", "name": "Melody 3",
"notes": [ "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": 3840, "time": 4, "velocity": 0.7874015748031497},
{"duration": 0.5, "durationTicks": 480, "midi": 62, "name": "D4", "ticks": 4320, "time": 4.5, "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": 1, "durationTicks": 960, "midi": 62, "name": "D4", "ticks": 4800, "time": 5, "velocity": 0.7874015748031497}
@ -94,6 +99,7 @@
}, },
"name": "Melody 4", "name": "Melody 4",
"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, "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, "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} {"duration": 1, "durationTicks": 960, "midi": 67, "name": "G4", "ticks": 6720, "time": 7, "velocity": 0.7874015748031497}

View File

@ -17,6 +17,7 @@ constexpr int MAX_GAMEPADS = 4;
constexpr int MAX_INSTRUMENT_TYPES = MAX_GAMEPADS; constexpr int MAX_INSTRUMENT_TYPES = MAX_GAMEPADS;
constexpr float RECEPTOR_HEIGHT = 150.0f; constexpr float RECEPTOR_HEIGHT = 150.0f;
constexpr float HIT_ZONE_MARGIN = 20.0f; constexpr float HIT_ZONE_MARGIN = 20.0f;
constexpr float SIMULTANEOUS_NOTE_Y_TOLERANCE = 2.0f;
constexpr float SCROLL_PX_PER_SEC = 350.0f; constexpr float SCROLL_PX_PER_SEC = 350.0f;
constexpr float LEAD_OFFSET_SECONDS = 3.0f; constexpr float LEAD_OFFSET_SECONDS = 3.0f;
@ -469,6 +470,10 @@ public:
{ {
continue; continue;
} }
if (!lane_pressed_by_instrument_owner(lane, n->instrument_slot))
{
continue;
}
float y = glyph_y(*n); float y = glyph_y(*n);
float d = fabsf(y - hit_line_y); float d = fabsf(y - hit_line_y);
if (d < best_dist) if (d < best_dist)
@ -479,12 +484,9 @@ public:
} }
if (best != nullptr) if (best != nullptr)
{ {
if (lane_pressed_by_instrument_owner(lane, best->instrument_slot)) consume_note(best);
{
consume_note(best);
}
} }
else if (is_lane_pressed(lane)) else
{ {
combo = 0; combo = 0;
score = std::max(0, score - 25); score = std::max(0, score - 25);
@ -564,6 +566,7 @@ public:
button_label_font_size, 1, Color{220, 220, 240, 255}); button_label_font_size, 1, Color{220, 220, 240, 255});
} }
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 y = glyph_y(*n);
@ -571,13 +574,45 @@ public:
{ {
continue; continue;
} }
float cx = lane_center_x(n->lane); by_lane[static_cast<size_t>(n->lane)].push_back(n);
Color fill = INSTRUMENT_COLORS[n->instrument_slot]; }
Color edge = Color{static_cast<unsigned char>(std::min(255, fill.r + 35)), for (int lane = 0; lane < LANE_COUNT; lane++)
static_cast<unsigned char>(std::min(255, fill.g + 50)), {
static_cast<unsigned char>(std::min(255, fill.b + 50)), 255}; std::vector<Glyph*>& list = by_lane[static_cast<size_t>(lane)];
DrawCircle(static_cast<int>(cx), static_cast<int>(y), 22, fill); std::sort(list.begin(), list.end(),
DrawCircleLines(static_cast<int>(cx), static_cast<int>(y), 22, edge); [this](Glyph* a, Glyph* b) { return glyph_y(*a) < glyph_y(*b); });
float left_base = lane * lane_width;
for (size_t i = 0; i < list.size();)
{
size_t j = i;
float y0 = glyph_y(*list[i]);
while (j < list.size() &&
glyph_y(*list[j]) - y0 <= SIMULTANEOUS_NOTE_Y_TOLERANCE)
{
j++;
}
int group_count = static_cast<int>(j - i);
float slice_width = lane_width / static_cast<float>(group_count);
float glyph_height = lane_width / 2.0f;
for (int k = 0; k < group_count; k++)
{
Glyph* n = list[i + static_cast<size_t>(k)];
float y = glyph_y(*n);
float left = left_base + k * slice_width;
float top = y - glyph_height / 2.0f;
Color fill = INSTRUMENT_COLORS[n->instrument_slot];
Color edge = Color{
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.b + 50)), 255};
DrawRectangle(static_cast<int>(left), static_cast<int>(top),
static_cast<int>(slice_width),
static_cast<int>(glyph_height), fill);
DrawRectangleLinesEx(
Rectangle{left, top, slice_width, glyph_height}, 2.0f, edge);
}
i = j;
}
} }
std::string score_text = "Score: " + std::to_string(score) + " Combo: " + std::to_string(combo); std::string score_text = "Score: " + std::to_string(score) + " Combo: " + std::to_string(combo);