JSON Score
ci / checks (push) Successful in 39m41s

This commit is contained in:
khodowany
2026-09-07 21:12:22 -05:00
parent 9a3ec7caa5
commit 900604b9fb
15 changed files with 1065 additions and 5634 deletions
+247 -28
View File
@@ -86,10 +86,15 @@ constexpr auto tie_thickness_spaces = std::float32_t{0.13F};
constexpr auto normal_stem_length_spaces = std::float32_t{3.50F};
constexpr auto preamble_vertical_correction_spaces = std::float32_t{0.50F};
constexpr auto stem_head_horizontal_overlap_spaces = std::float32_t{0.06F};
constexpr auto stem_head_vertical_overlap_spaces = std::float32_t{0.20F};
constexpr auto measure_right_guard_spaces = std::float32_t{1.50F};
constexpr auto stem_head_vertical_overlap_spaces = std::float32_t{0.38F};
constexpr auto measure_right_guard_spaces = std::float32_t{2.05F};
constexpr auto chord_above_staff_spaces = std::float32_t{3.65F};
constexpr auto chord_clearance_spaces = std::float32_t{0.45F};
constexpr auto lyric_below_staff_spaces = std::float32_t{6.15F};
constexpr auto lyric_clearance_spaces = std::float32_t{0.75F};
constexpr auto tie_endpoint_gap_spaces = std::float32_t{0.28F};
constexpr auto tie_note_offset_spaces = std::float32_t{0.50F};
constexpr auto tie_arch_spaces = std::float32_t{0.72F};
auto to_raylib(luma::color color) -> Color {
return {
@@ -832,6 +837,26 @@ struct beam_group_layout {
std::float32_t beam_y{};
};
struct laid_out_tie_head {
vamp_zero::score_pitch pitch{};
luma::rect bounds{};
std::float32_t center_y{};
};
struct laid_out_note_ref {
std::size_t system_index{};
std::size_t measure_index{};
std::size_t note_index{};
luma::rect notation_bounds{};
std::bool_t stem_up{true};
std::vector<laid_out_tie_head> heads{};
};
struct laid_out_measure_ref {
std::size_t measure_index{};
luma::rect notation_bounds{};
};
auto layout_staff(
vamp_zero::score_page_layout& layout,
std::float32_t x,
@@ -1679,8 +1704,6 @@ auto layout_note_symbols(
note_layout const& note,
std::float32_t staff_top,
std::float32_t spacing,
std::float32_t next_x,
std::float32_t measure_right_x,
std::size_t system_index,
std::size_t measure_index
) -> void {
@@ -1714,25 +1737,6 @@ auto layout_note_symbols(
note.note_index);
}
}
if (note.source != nullptr && note.source->tie_to_next) {
auto tie_y = note.low_y + spacing * 0.62F;
auto tie_start_x = note.visual_right - spacing * 0.45F;
auto tie_end_x = std::min(next_x - spacing * 0.45F, measure_right_x - spacing * 0.30F);
if (tie_end_x <= tie_start_x + spacing * 0.50F) {
return;
}
add_curve(
layout,
vamp_zero::score_layout_item_role::TIE,
{.x = tie_start_x, .y = tie_y},
{.x = (tie_start_x + tie_end_x) * 0.5F, .y = tie_y + spacing * 0.78F},
{.x = tie_end_x, .y = tie_y},
at_least_one_pixel(scaled(tie_thickness_spaces, spacing)),
score_ink(),
system_index,
measure_index,
note.note_index);
}
}
auto note_x(std::float32_t start_x, std::float32_t width, std::int64_t total_ticks, std::int64_t elapsed_ticks, std::int64_t duration_ticks) -> std::float32_t {
@@ -1837,6 +1841,23 @@ auto measure_notation_top_y(
return top_y;
}
auto measure_notation_bottom_y(std::vector<note_layout> const& notes, std::vector<beam_group_layout> const& beam_groups, std::float32_t staff_top, std::float32_t spacing) -> std::float32_t {
auto bottom_y = staff_top + spacing * 4.0F;
for (auto const& note : notes) {
if (!note.hidden) {
bottom_y = std::max(bottom_y, note.visual_bottom);
}
}
for (auto const& group : beam_groups) {
bottom_y = std::max(bottom_y, group.beam_y + scaled(beam_thickness_spaces, spacing) * 0.5F);
}
return bottom_y;
}
auto lyric_y(std::float32_t staff_top, std::float32_t spacing, std::float32_t notation_bottom_y) -> std::float32_t {
return std::max(staff_top + scaled(lyric_below_staff_spaces, spacing), notation_bottom_y + scaled(lyric_clearance_spaces, spacing));
}
auto chord_symbol_y(std::float32_t staff_top, std::float32_t spacing, std::float32_t notation_top_y) -> std::float32_t {
auto y = staff_top - scaled(chord_above_staff_spaces, spacing);
auto const bottom_limit = notation_top_y - scaled(chord_clearance_spaces, spacing);
@@ -1846,6 +1867,189 @@ auto chord_symbol_y(std::float32_t staff_top, std::float32_t spacing, std::float
return y;
}
auto same_pitch(vamp_zero::score_pitch const& left, vamp_zero::score_pitch const& right) -> std::bool_t {
return normalized_letter(left.letter) == normalized_letter(right.letter)
&& left.octave == right.octave
&& pitch_alteration(left.accidental) == pitch_alteration(right.accidental);
}
auto make_laid_out_note_ref(note_layout const& note, luma::rect notation_bounds, std::size_t system_index, std::size_t measure_index) -> laid_out_note_ref {
auto ref = laid_out_note_ref{
.system_index = system_index,
.measure_index = measure_index,
.note_index = note.note_index,
.notation_bounds = notation_bounds,
.stem_up = note.stem_up,
};
if (note.source == nullptr || note.hidden || note.rest) {
return ref;
}
ref.heads.reserve(note.heads.size());
for (auto const& head : note.heads) {
if (head.source_pitch_index >= note.source->pitches.size()) {
continue;
}
ref.heads.push_back({
.pitch = note.source->pitches[head.source_pitch_index],
.bounds = head.bounds,
.center_y = head.center_y,
});
}
return ref;
}
auto find_note_ref(std::vector<laid_out_note_ref> const& refs, std::size_t measure_index, std::size_t note_index) -> laid_out_note_ref const* {
auto found = std::find_if(refs.begin(), refs.end(), [&](auto const& ref) {
return ref.measure_index == measure_index && ref.note_index == note_index;
});
return found == refs.end() ? nullptr : &*found;
}
auto find_measure_ref(std::vector<laid_out_measure_ref> const& refs, std::size_t measure_index) -> laid_out_measure_ref const* {
auto found = std::find_if(refs.begin(), refs.end(), [&](auto const& ref) {
return ref.measure_index == measure_index;
});
return found == refs.end() ? nullptr : &*found;
}
auto find_tie_head(laid_out_note_ref const& ref, vamp_zero::score_pitch const& pitch) -> laid_out_tie_head const* {
auto found = std::find_if(ref.heads.begin(), ref.heads.end(), [&](auto const& head) {
return same_pitch(head.pitch, pitch);
});
return found == ref.heads.end() ? nullptr : &*found;
}
struct tied_note_destination {
std::size_t measure_index{};
std::size_t note_index{};
};
auto find_tied_destination(vamp_zero::score_document const& score, std::size_t measure_index, std::size_t note_index, vamp_zero::score_pitch const& pitch) -> std::optional<tied_note_destination> {
for (auto current_measure = measure_index; current_measure < score.measures.size(); ++current_measure) {
auto const& measure = score.measures[current_measure];
auto first_note = current_measure == measure_index ? note_index + 1U : 0U;
for (auto current_note = first_note; current_note < measure.notes.size(); ++current_note) {
auto const& note = measure.notes[current_note];
if (note.hidden || note.kind == "rest") {
continue;
}
auto matching_pitch = std::find_if(note.pitches.begin(), note.pitches.end(), [&](auto const& candidate) {
return same_pitch(candidate, pitch);
});
if (matching_pitch == note.pitches.end()) {
return std::nullopt;
}
if (note.tie_from_previous) {
return tied_note_destination{.measure_index = current_measure, .note_index = current_note};
}
return std::nullopt;
}
}
return std::nullopt;
}
auto add_tie_curve(
vamp_zero::score_page_layout& layout,
luma::point from,
luma::point to,
std::bool_t below_note,
std::float32_t spacing,
std::size_t system_index,
std::size_t measure_index,
std::size_t note_index
) -> void {
if (to.x <= from.x + spacing * 0.40F) {
return;
}
auto direction = below_note ? std::float32_t{1.0F} : std::float32_t{-1.0F};
auto control = luma::point{
.x = (from.x + to.x) * 0.5F,
.y = (from.y + to.y) * 0.5F + direction * scaled(tie_arch_spaces, spacing),
};
add_curve(
layout,
vamp_zero::score_layout_item_role::TIE,
from,
control,
to,
at_least_one_pixel(scaled(tie_thickness_spaces, spacing)),
score_ink(),
system_index,
measure_index,
note_index);
}
auto layout_page_ties(
vamp_zero::score_page_layout& layout,
vamp_zero::score_document const& score,
std::vector<laid_out_note_ref> const& note_refs,
std::vector<laid_out_measure_ref> const& measure_refs,
std::float32_t spacing
) -> void {
for (auto measure_index = std::size_t{}; measure_index < score.measures.size(); ++measure_index) {
auto const& measure = score.measures[measure_index];
for (auto note_index = std::size_t{}; note_index < measure.notes.size(); ++note_index) {
auto const& note = measure.notes[note_index];
if (!note.tie_to_next || note.hidden || note.kind == "rest") {
continue;
}
auto const* start_ref = find_note_ref(note_refs, measure_index, note_index);
for (auto const& pitch : note.pitches) {
auto destination = find_tied_destination(score, measure_index, note_index, pitch);
auto const* destination_ref = destination ? find_note_ref(note_refs, destination->measure_index, destination->note_index) : nullptr;
auto const* start_head = start_ref == nullptr ? nullptr : find_tie_head(*start_ref, pitch);
auto const* destination_head = destination_ref == nullptr ? nullptr : find_tie_head(*destination_ref, pitch);
if (start_ref != nullptr && start_head != nullptr && destination_ref != nullptr && destination_head != nullptr && start_ref->system_index == destination_ref->system_index) {
auto below = start_ref->stem_up;
auto y_offset = scaled(tie_note_offset_spaces, spacing) * (below ? 1.0F : -1.0F);
add_tie_curve(
layout,
{.x = start_head->bounds.x + start_head->bounds.width + scaled(tie_endpoint_gap_spaces, spacing), .y = start_head->center_y + y_offset},
{.x = destination_head->bounds.x - scaled(tie_endpoint_gap_spaces, spacing), .y = destination_head->center_y + y_offset},
below,
spacing,
start_ref->system_index,
start_ref->measure_index,
start_ref->note_index);
continue;
}
if (start_ref != nullptr && start_head != nullptr) {
auto below = start_ref->stem_up;
auto y_offset = scaled(tie_note_offset_spaces, spacing) * (below ? 1.0F : -1.0F);
add_tie_curve(
layout,
{.x = start_head->bounds.x + start_head->bounds.width + scaled(tie_endpoint_gap_spaces, spacing), .y = start_head->center_y + y_offset},
{.x = start_ref->notation_bounds.x + start_ref->notation_bounds.width, .y = start_head->center_y + y_offset},
below,
spacing,
start_ref->system_index,
start_ref->measure_index,
start_ref->note_index);
}
if (destination && destination_ref != nullptr && destination_head != nullptr) {
auto const* measure_ref = find_measure_ref(measure_refs, destination->measure_index);
if (measure_ref == nullptr) {
continue;
}
auto below = destination_ref->stem_up;
auto y_offset = scaled(tie_note_offset_spaces, spacing) * (below ? 1.0F : -1.0F);
add_tie_curve(
layout,
{.x = measure_ref->notation_bounds.x, .y = destination_head->center_y + y_offset},
{.x = destination_head->bounds.x - scaled(tie_endpoint_gap_spaces, spacing), .y = destination_head->center_y + y_offset},
below,
spacing,
destination_ref->system_index,
destination_ref->measure_index,
destination_ref->note_index);
}
}
}
}
}
auto layout_measure(
vamp_zero::score_page_layout& layout,
vamp_zero::score_document const& score,
@@ -1855,7 +2059,9 @@ auto layout_measure(
std::size_t system_index,
std::size_t measure_index,
std::float32_t staff_top,
std::float32_t spacing
std::float32_t spacing,
std::vector<laid_out_note_ref>& note_refs,
std::vector<laid_out_measure_ref>& measure_refs
) -> void {
add_text(layout, vamp_zero::score_layout_item_role::MEASURE_NUMBER, bounds.x + spacing * 0.20F, bounds.y + spacing * 0.05F, std::to_string(measure_number), 1, score_muted_ink(), spacing * 4.0F, system_index, measure_index);
@@ -1863,6 +2069,10 @@ auto layout_measure(
auto notation_right_x = bounds.x + bounds.width - scaled(measure_right_guard_spaces, spacing);
auto notation_width = std::max<std::float32_t>(spacing * 3.0F, notation_right_x - notation_x);
auto notation_bounds = luma::rect{.x = notation_x, .y = bounds.y, .width = notation_width, .height = bounds.height};
measure_refs.push_back({
.measure_index = measure_index,
.notation_bounds = notation_bounds,
});
auto total_ticks = measure_ticks(measure);
auto elapsed = std::int64_t{};
auto accidentals = make_accidental_state(effective_key_signature(score, measure));
@@ -1889,7 +2099,12 @@ auto layout_measure(
}
auto const notation_top_y = measure_notation_top_y(measure, notes, beam_groups, staff_top, spacing);
auto const notation_bottom_y = measure_notation_bottom_y(notes, beam_groups, staff_top, spacing);
auto const chord_y = chord_symbol_y(staff_top, spacing, notation_top_y);
auto const lyrics_y = lyric_y(staff_top, spacing, notation_bottom_y);
for (auto const& note : notes) {
note_refs.push_back(make_laid_out_note_ref(note, notation_bounds, system_index, measure_index));
}
if (!measure.section_label.empty()) {
auto section_y = std::min(staff_top - spacing * 4.70F, chord_y - spacing * 0.65F);
add_text(layout, vamp_zero::score_layout_item_role::SECTION_LABEL, bounds.x + spacing * 2.10F, section_y, measure.section_label, 2, score_accent_ink(), bounds.width - spacing * 2.6F, system_index, measure_index);
@@ -1948,14 +2163,13 @@ auto layout_measure(
add_flag(layout, note, spacing, system_index, measure_index);
}
for (auto index = std::size_t{}; index < notes.size(); ++index) {
auto next_x = index + 1U < notes.size() ? notes[index + 1U].x : notes[index].x + spacing * 3.0F;
layout_note_symbols(layout, notes[index], staff_top, spacing, next_x, notation_bounds.x + notation_bounds.width, system_index, measure_index);
layout_note_symbols(layout, notes[index], staff_top, spacing, system_index, measure_index);
if (notes[index].source != nullptr && !notes[index].hidden && !notes[index].source->lyric.empty()) {
add_text(
layout,
vamp_zero::score_layout_item_role::LYRIC,
notes[index].x - spacing,
staff_top + spacing * 5.35F,
lyrics_y,
notes[index].source->lyric,
1,
score_muted_ink(),
@@ -2027,6 +2241,8 @@ auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document c
auto const content_width = std::max<std::float32_t>(spacing * 18.0F, bounds.width - margin * 2.0F);
auto const system_height = (bounds.height - 108.0F) / static_cast<std::float32_t>(vamp_zero::score_systems_per_page);
auto const first_measure = page_index * vamp_zero::score_measures_per_page;
auto note_refs = std::vector<laid_out_note_ref>{};
auto measure_refs = std::vector<laid_out_measure_ref>{};
for (auto system = std::size_t{}; system < vamp_zero::score_systems_per_page; ++system) {
auto const system_y = content_y + static_cast<std::float32_t>(system) * system_height;
@@ -2052,7 +2268,9 @@ auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document c
system,
measure_index,
staff_top,
spacing);
spacing,
note_refs,
measure_refs);
}
auto const bar_x = x + measure_width;
@@ -2069,6 +2287,7 @@ auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document c
}
}
layout_page_ties(layout, score, note_refs, measure_refs, spacing);
add_stroke_rect(layout, vamp_zero::score_layout_item_role::BORDER, bounds, 1.1F, score_border());
return layout;
}