diff --git a/cmake/pico.cmake b/cmake/pico.cmake index 6021806..9c4d831 100644 --- a/cmake/pico.cmake +++ b/cmake/pico.cmake @@ -37,6 +37,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) pico_sdk_init() +set(VAMP_MODULUS_INCLUDE_DIR "" CACHE PATH "Path to the Modulus public include directory") +if(NOT VAMP_MODULUS_INCLUDE_DIR) + set(VAMP_MODULUS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../modulus/include") +endif() +get_filename_component(VAMP_MODULUS_INCLUDE_DIR "${VAMP_MODULUS_INCLUDE_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +if(NOT EXISTS "${VAMP_MODULUS_INCLUDE_DIR}/modulus/types.hpp") + message(FATAL_ERROR "VAMP_MODULUS_INCLUDE_DIR must contain modulus/types.hpp: ${VAMP_MODULUS_INCLUDE_DIR}") +endif() + add_executable(product-vamp-pico2w src/vamp_style.cpp src/vamp_style/font.cpp @@ -48,6 +57,7 @@ add_executable(product-vamp-pico2w ) target_include_directories(product-vamp-pico2w PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${VAMP_MODULUS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/vamp_pico/capi ) target_compile_definitions(product-vamp-pico2w PRIVATE diff --git a/cmake/zero.cmake b/cmake/zero.cmake index ffa0d61..fa3da73 100644 --- a/cmake/zero.cmake +++ b/cmake/zero.cmake @@ -33,6 +33,9 @@ target_include_directories(vamp-style PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_compile_features(vamp-style PUBLIC cxx_std_17) +target_link_libraries(vamp-style PUBLIC + modulus::modulus +) set(VAMP_PROTOCOL_SOURCES src/vamp_protocol/page_control.cpp @@ -43,6 +46,9 @@ target_include_directories(vamp-protocol PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_compile_features(vamp-protocol PUBLIC cxx_std_17) +target_link_libraries(vamp-protocol PUBLIC + modulus::modulus +) set(VAMP_CORE_SOURCES src/vamp_zero/app.cpp @@ -205,6 +211,7 @@ if(VAMP_BUILD_PICO_FIRMWARE) "${VAMP_PICO_WIFI_PASSPHRASE}" "${VAMP_ZERO_HOST}" "${VAMP_ZERO_PORT}" + "${modulus_INCLUDE_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS package/scripts/build_pico_firmware.sh diff --git a/include/types.hpp b/include/types.hpp deleted file mode 100644 index 62490a1..0000000 --- a/include/types.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -#if defined(__has_include) -#if __has_include() -#include -#endif -#endif - -namespace std { - -using bool_t = decltype(true); -using char_t = decltype('x'); -using uchar_t = std::uint8_t; - -#if !defined(__STDCPP_FLOAT32_T__) -using float32_t = decltype(0.0F); -#endif - -#if !defined(__STDCPP_FLOAT64_T__) -using float64_t = decltype(0.0); -#endif - -} diff --git a/include/vamp_pico/display.hpp b/include/vamp_pico/display.hpp index 4c52ef3..5244247 100644 --- a/include/vamp_pico/display.hpp +++ b/include/vamp_pico/display.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include namespace vamp_pico { diff --git a/include/vamp_pico/http_client.hpp b/include/vamp_pico/http_client.hpp index fa6367b..c56a514 100644 --- a/include/vamp_pico/http_client.hpp +++ b/include/vamp_pico/http_client.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace vamp_pico { diff --git a/include/vamp_pico/touch.hpp b/include/vamp_pico/touch.hpp index c375e17..2dea371 100644 --- a/include/vamp_pico/touch.hpp +++ b/include/vamp_pico/touch.hpp @@ -2,7 +2,7 @@ #include -#include +#include namespace vamp_pico { diff --git a/include/vamp_protocol/page_control.hpp b/include/vamp_protocol/page_control.hpp index d231d03..002072d 100644 --- a/include/vamp_protocol/page_control.hpp +++ b/include/vamp_protocol/page_control.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace vamp_protocol { diff --git a/include/vamp_style.hpp b/include/vamp_style.hpp index 787f435..90e233c 100644 --- a/include/vamp_style.hpp +++ b/include/vamp_style.hpp @@ -2,7 +2,7 @@ #include -#include +#include namespace vamp_style { diff --git a/include/vamp_style/font.hpp b/include/vamp_style/font.hpp index 446215f..daaea48 100644 --- a/include/vamp_style/font.hpp +++ b/include/vamp_style/font.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace vamp_style { diff --git a/include/vamp_zero/app.hpp b/include/vamp_zero/app.hpp index a2d0b2b..fedc83b 100644 --- a/include/vamp_zero/app.hpp +++ b/include/vamp_zero/app.hpp @@ -6,7 +6,7 @@ #include -#include +#include namespace vamp_zero { diff --git a/include/vamp_zero/config.hpp b/include/vamp_zero/config.hpp index 110717c..624b495 100644 --- a/include/vamp_zero/config.hpp +++ b/include/vamp_zero/config.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace vamp_zero { diff --git a/include/vamp_zero/types.hpp b/include/vamp_zero/types.hpp index 437f44a..ac8a23b 100644 --- a/include/vamp_zero/types.hpp +++ b/include/vamp_zero/types.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace vamp_zero { diff --git a/package/scripts/build_pico_firmware.sh b/package/scripts/build_pico_firmware.sh index 0b21e74..e44c981 100755 --- a/package/scripts/build_pico_firmware.sh +++ b/package/scripts/build_pico_firmware.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -if [ "$#" -ne 7 ]; then - echo "usage: build_pico_firmware.sh BUILD_DIR OUTPUT_UF2 SDK_VERSION WIFI_SSID WIFI_PASSPHRASE ZERO_HOST ZERO_PORT" >&2 +if [ "$#" -lt 7 ] || [ "$#" -gt 8 ]; then + echo "usage: build_pico_firmware.sh BUILD_DIR OUTPUT_UF2 SDK_VERSION WIFI_SSID WIFI_PASSPHRASE ZERO_HOST ZERO_PORT [MODULUS_INCLUDE_DIR]" >&2 exit 2 fi @@ -14,6 +14,7 @@ wifi_ssid=$4 wifi_passphrase=$5 zero_host=$6 zero_port=$7 +modulus_include_dir=${8:-} pico_sdk_path=${VAMP_PICO_SDK_PATH:-${PICO_SDK_PATH:-}} sdk_args=( @@ -27,6 +28,13 @@ if [ -n "$pico_sdk_path" ]; then -DPICO_SDK_FETCH_FROM_GIT=OFF ) fi +modulus_args=() +if [ -n "$modulus_include_dir" ]; then + modulus_include_dir=$(cd "$modulus_include_dir" && pwd) + modulus_args=( + "-DVAMP_MODULUS_INCLUDE_DIR=${modulus_include_dir}" + ) +fi mkdir -p "$(dirname "$output_uf2")" @@ -58,6 +66,7 @@ cmake \ -DPICO_BOARD=pico2_w \ -DPICO_GCC_TRIPLE=arm-none-eabi \ "${sdk_args[@]}" \ + "${modulus_args[@]}" \ "-DVAMP_PICO_WIFI_SSID=${wifi_ssid}" \ "-DVAMP_PICO_WIFI_PASSPHRASE=${wifi_passphrase}" \ "-DVAMP_ZERO_HOST=${zero_host}" \ diff --git a/src/vamp_zero/score.cpp b/src/vamp_zero/score.cpp index 868777b..6e977a1 100644 --- a/src/vamp_zero/score.cpp +++ b/src/vamp_zero/score.cpp @@ -84,6 +84,12 @@ constexpr auto thin_barline_thickness_spaces = std::float32_t{0.16F}; constexpr auto thick_barline_thickness_spaces = std::float32_t{0.50F}; 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 chord_above_staff_spaces = std::float32_t{3.65F}; +constexpr auto chord_clearance_spaces = std::float32_t{0.45F}; auto to_raylib(luma::color color) -> Color { return { @@ -810,6 +816,8 @@ struct note_layout { std::float32_t high_y{}; std::float32_t visual_left{}; std::float32_t visual_right{}; + std::float32_t visual_top{}; + std::float32_t visual_bottom{}; std::float32_t stem_x{}; std::float32_t stem_base_y{}; std::float32_t stem_tip_y{}; @@ -851,7 +859,7 @@ auto layout_clef(vamp_zero::score_page_layout& layout, std::string_view clef, st auto reference_pitch = codepoint == glyph_f_clef ? vamp_zero::score_pitch{.letter = 'f', .octave = 3} : vamp_zero::score_pitch{.letter = 'g', .octave = 4}; - auto baseline_y = pitch_y(reference_pitch, staff_top, spacing, clef); + auto baseline_y = pitch_y(reference_pitch, staff_top, spacing, clef) + scaled(preamble_vertical_correction_spaces, spacing); auto origin = origin_for_left_baseline(codepoint, x, baseline_y, spacing); add_glyph(layout, vamp_zero::score_layout_item_role::CLEF, codepoint, origin, spacing, score_ink(), system_index); return glyph_width(codepoint, spacing) + spacing * 0.90F; @@ -924,7 +932,7 @@ auto layout_key_signature( auto const total = static_cast(std::abs(count)); for (auto index = std::size_t{}; index < total; ++index) { auto pitch = key_signature_pitch(count, index, clef); - auto baseline_y = pitch_y(pitch, staff_top, spacing, clef); + auto baseline_y = pitch_y(pitch, staff_top, spacing, clef) + scaled(preamble_vertical_correction_spaces, spacing); add_glyph( layout, vamp_zero::score_layout_item_role::KEY_SIGNATURE, @@ -1006,8 +1014,8 @@ auto layout_time_signature( if (width <= 0.0F) { return 0.0F; } - layout_time_signature_run(layout, numerator, x + (width - numerator_width) * 0.5F, staff_top + spacing, spacing, system_index); - layout_time_signature_run(layout, denominator, x + (width - denominator_width) * 0.5F, staff_top + spacing * 3.0F, spacing, system_index); + layout_time_signature_run(layout, numerator, x + (width - numerator_width) * 0.5F, staff_top + spacing * 1.5F, spacing, system_index); + layout_time_signature_run(layout, denominator, x + (width - denominator_width) * 0.5F, staff_top + spacing * 3.5F, spacing, system_index); return width + spacing * 0.95F; } @@ -1140,13 +1148,18 @@ auto update_note_visual_extents(note_layout& note, std::float32_t spacing) -> vo if (note.hidden) { note.visual_left = note.x; note.visual_right = note.x; + note.visual_top = note.low_y; + note.visual_bottom = note.low_y; return; } if (note.rest) { auto codepoint = rest_codepoint(note.duration); - auto half_width = glyph_width(codepoint, spacing) * 0.5F; - note.visual_left = note.x - half_width; - note.visual_right = note.x + half_width; + auto origin = origin_for_centered_baseline(codepoint, note.x, note.low_y, spacing); + auto bounds = glyph_bounds(codepoint, origin, spacing); + note.visual_left = bounds.x; + note.visual_right = bounds.x + bounds.width; + note.visual_top = bounds.y; + note.visual_bottom = bounds.y + bounds.height; if (note.duration.dots != 0) { note.visual_right += spacing * 1.35F; } @@ -1154,9 +1167,13 @@ auto update_note_visual_extents(note_layout& note, std::float32_t spacing) -> vo } note.visual_left = note.heads.empty() ? note.x : note.heads.front().bounds.x; note.visual_right = note.heads.empty() ? note.x : note.heads.front().bounds.x + note.heads.front().bounds.width; + note.visual_top = note.heads.empty() ? note.high_y : note.heads.front().bounds.y; + note.visual_bottom = note.heads.empty() ? note.low_y : note.heads.front().bounds.y + note.heads.front().bounds.height; for (auto const& head : note.heads) { note.visual_left = std::min(note.visual_left, head.bounds.x); note.visual_right = std::max(note.visual_right, head.bounds.x + head.bounds.width); + note.visual_top = std::min(note.visual_top, head.bounds.y); + note.visual_bottom = std::max(note.visual_bottom, head.bounds.y + head.bounds.height); } if (note.duration.dots != 0) { note.visual_right += spacing * 1.40F; @@ -1261,8 +1278,12 @@ auto update_stem_geometry(note_layout& note, std::float32_t staff_top, std::floa auto anchor = note.stem_up ? stem_up_anchor(note.notehead) : stem_down_anchor(note.notehead); auto base = anchor_point(head->origin, anchor, spacing); auto center = staff_top + spacing * 2.0F; - note.stem_x = base.x; - note.stem_base_y = base.y; + note.stem_x = note.stem_up + ? base.x - scaled(stem_head_horizontal_overlap_spaces, spacing) + : base.x + scaled(stem_head_horizontal_overlap_spaces, spacing); + note.stem_base_y = note.stem_up + ? base.y + scaled(stem_head_vertical_overlap_spaces, spacing) + : base.y - scaled(stem_head_vertical_overlap_spaces, spacing); note.stem_tip_y = note.stem_up ? std::min(note.high_y, center) - scaled(normal_stem_length_spaces, spacing) : std::max(note.low_y, center) + scaled(normal_stem_length_spaces, spacing); @@ -1416,6 +1437,24 @@ auto fit_note_spacing(std::vector& notes, luma::rect bounds, std::f } } +auto keep_notes_inside_right_guard(std::vector& notes, luma::rect bounds, std::float32_t spacing) -> void { + auto last_right = std::numeric_limits::lowest(); + for (auto const& note : notes) { + if (!note.hidden) { + last_right = std::max(last_right, note.visual_right); + } + } + auto const right_limit = bounds.x + bounds.width - spacing * 0.25F; + if (last_right <= right_limit) { + return; + } + for (auto& note : notes) { + if (!note.hidden) { + shift_note_x(note, right_limit - last_right, spacing); + } + } +} + auto eligible_for_beam(note_layout const& note) -> std::bool_t { return !note.hidden && !note.rest && has_stem(note.duration) && note.duration.base == duration_base::EIGHTH; } @@ -1560,6 +1599,55 @@ auto add_flag(vamp_zero::score_page_layout& layout, note_layout const& note, std add_glyph(layout, vamp_zero::score_layout_item_role::FLAG, codepoint, origin, spacing, score_ink(), system_index, measure_index, note.note_index); } +auto include_visual_bounds(note_layout& note, luma::rect bounds) -> void { + note.visual_left = std::min(note.visual_left, bounds.x); + note.visual_right = std::max(note.visual_right, bounds.x + bounds.width); + note.visual_top = std::min(note.visual_top, bounds.y); + note.visual_bottom = std::max(note.visual_bottom, bounds.y + bounds.height); +} + +auto flag_bounds(note_layout const& note, std::float32_t spacing) -> luma::rect { + auto codepoint = note.stem_up ? glyph_flag_eighth_up : glyph_flag_eighth_down; + auto anchor = note.stem_up ? smufl_anchor{.x = 0.0F, .y = -0.176F} : smufl_anchor{.x = 0.0F, .y = 0.176F}; + auto origin = luma::point{ + .x = note.stem_x - anchor.x * spacing, + .y = note.stem_tip_y + anchor.y * spacing, + }; + return glyph_bounds(codepoint, origin, spacing); +} + +auto refresh_note_visual_extents(note_layout& note, std::float32_t spacing) -> void { + update_note_visual_extents(note, spacing); + if (note.hidden || note.rest) { + return; + } + + auto accidental_extent = note_accidental_extent(note, spacing); + if (accidental_extent > 0.0F) { + note.visual_left -= accidental_extent; + } + for (auto const& head : note.heads) { + if (auto glyph = accidental_glyph(head.accidental)) { + auto box = glyph_box(*glyph); + note.visual_top = std::min(note.visual_top, head.center_y - box.north * spacing); + note.visual_bottom = std::max(note.visual_bottom, head.center_y - box.south * spacing); + } + } + + if (has_stem(note.duration) && note.stem_x != 0.0F) { + auto half_stem = at_least_one_pixel(scaled(stem_thickness_spaces, spacing)) * 0.5F; + include_visual_bounds(note, { + .x = note.stem_x - half_stem, + .y = std::min(note.stem_base_y, note.stem_tip_y), + .width = half_stem * 2.0F, + .height = std::abs(note.stem_tip_y - note.stem_base_y), + }); + } + if (has_flag(note.duration) && !note.beamed && note.stem_x != 0.0F) { + include_visual_bounds(note, flag_bounds(note, spacing)); + } +} + auto layout_rest( vamp_zero::score_page_layout& layout, note_layout const& note, @@ -1592,6 +1680,7 @@ auto layout_note_symbols( 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 { @@ -1627,12 +1716,17 @@ auto layout_note_symbols( } 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 = note.visual_right - spacing * 0.45F, .y = tie_y}, - {.x = (note.x + next_x) * 0.5F, .y = tie_y + spacing * 0.78F}, - {.x = next_x - spacing * 0.45F, .y = tie_y}, + {.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, @@ -1703,6 +1797,55 @@ auto layout_tuplets( } } +auto measure_notation_top_y( + vamp_zero::score_measure const& measure, + std::vector const& notes, + std::vector const& beam_groups, + std::float32_t staff_top, + std::float32_t spacing +) -> std::float32_t { + auto top_y = staff_top; + for (auto const& note : notes) { + if (!note.hidden) { + top_y = std::min(top_y, note.visual_top); + } + } + for (auto const& group : beam_groups) { + top_y = std::min(top_y, group.beam_y - scaled(beam_thickness_spaces, spacing) * 0.5F); + } + for (auto const& tuplet : measure.tuplets) { + if (tuplet.id.empty() || tuplet.note_count <= 0) { + continue; + } + auto first = invalid_layout_index; + auto last = invalid_layout_index; + for (auto index = std::size_t{}; index < notes.size(); ++index) { + if (notes[index].source != nullptr && notes[index].source->tuplet_id == tuplet.id) { + first = std::min(first, index); + last = index; + } + } + if (first == invalid_layout_index || last == invalid_layout_index || first > last) { + continue; + } + auto tuplet_top_y = staff_top; + for (auto index = first; index <= last; ++index) { + tuplet_top_y = std::min(tuplet_top_y, notes[index].beamed ? notes[index].stem_tip_y : notes[index].visual_top); + } + top_y = std::min(top_y, tuplet_top_y - spacing * 1.80F); + } + return top_y; +} + +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); + if (y + text_line_height(2) > bottom_limit) { + y = bottom_limit - text_line_height(2); + } + return y; +} + auto layout_measure( vamp_zero::score_page_layout& layout, vamp_zero::score_document const& score, @@ -1715,12 +1858,11 @@ auto layout_measure( std::float32_t spacing ) -> 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); - if (!measure.section_label.empty()) { - add_text(layout, vamp_zero::score_layout_item_role::SECTION_LABEL, bounds.x + spacing * 2.10F, staff_top - spacing * 4.05F, measure.section_label, 2, score_accent_ink(), bounds.width - spacing * 2.6F, system_index, measure_index); - } auto notation_x = bounds.x + spacing * 1.15F; - auto notation_width = std::max(spacing * 3.0F, bounds.width - (notation_x - bounds.x) - spacing * 0.80F); + auto notation_right_x = bounds.x + bounds.width - scaled(measure_right_guard_spaces, spacing); + auto notation_width = std::max(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}; auto total_ticks = measure_ticks(measure); auto elapsed = std::int64_t{}; auto accidentals = make_accidental_state(effective_key_signature(score, measure)); @@ -1735,8 +1877,23 @@ auto layout_measure( notes.push_back(build_note_layout(score, note, index, elapsed, duration_ticks, x, staff_top, spacing, accidental_marks)); elapsed += duration_ticks; } - fit_note_spacing(notes, {.x = notation_x, .y = bounds.y, .width = notation_width, .height = bounds.height}, spacing); + fit_note_spacing(notes, notation_bounds, spacing); auto beam_groups = make_beam_groups(notes, staff_top, spacing); + for (auto& note : notes) { + refresh_note_visual_extents(note, spacing); + } + fit_note_spacing(notes, notation_bounds, spacing); + keep_notes_inside_right_guard(notes, notation_bounds, spacing); + for (auto& note : notes) { + refresh_note_visual_extents(note, spacing); + } + + auto const notation_top_y = measure_notation_top_y(measure, notes, beam_groups, staff_top, spacing); + auto const chord_y = chord_symbol_y(staff_top, spacing, notation_top_y); + 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); + } auto previous_chord = std::string{}; for (auto const& note : notes) { @@ -1747,7 +1904,7 @@ auto layout_measure( layout, vamp_zero::score_layout_item_role::CHORD_SYMBOL, note.x - spacing * 1.15F, - staff_top - spacing * 2.45F, + chord_y, note.source->chord_symbol, 2, score_ink(), @@ -1792,7 +1949,7 @@ auto layout_measure( } 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, system_index, measure_index); + layout_note_symbols(layout, notes[index], staff_top, spacing, next_x, notation_bounds.x + notation_bounds.width, system_index, measure_index); if (notes[index].source != nullptr && !notes[index].hidden && !notes[index].source->lyric.empty()) { add_text( layout, diff --git a/tests/test.cpp b/tests/test.cpp index 347a331..195ef42 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -154,6 +154,10 @@ auto center_y(vamp_zero::score_layout_item const& item) -> std::float32_t { return item.bounds.y + item.bounds.height * 0.5F; } +auto right_edge(vamp_zero::score_layout_item const& item) -> std::float32_t { + return item.bounds.x + item.bounds.width; +} + auto near(std::float32_t left, std::float32_t right) -> std::bool_t { return std::abs(left - right) < 0.02F; } @@ -325,7 +329,11 @@ BOOST_AUTO_TEST_CASE(score_layout_orders_preamble_on_staff_baselines) { BOOST_TEST(clefs.front()->bounds.x < keys.front()->bounds.x); BOOST_TEST(keys.front()->bounds.x < times.front()->bounds.x); - BOOST_TEST(near(clefs.front()->origin.y, staff_lines.front()->from.y + layout.staff_spacing * 3.0F)); + BOOST_TEST(near(clefs.front()->origin.y, staff_lines.front()->from.y + layout.staff_spacing * 3.5F)); + BOOST_TEST(near(keys.front()->origin.y, staff_lines.front()->from.y + layout.staff_spacing * 2.5F)); + BOOST_REQUIRE_GE(times.size(), 2U); + BOOST_TEST(near(times[0]->origin.y, staff_lines.front()->from.y + layout.staff_spacing * 1.5F)); + BOOST_TEST(near(times[1]->origin.y, staff_lines.front()->from.y + layout.staff_spacing * 3.5F)); } BOOST_AUTO_TEST_CASE(score_layout_attaches_stems_to_notehead_anchors) { @@ -337,8 +345,10 @@ BOOST_AUTO_TEST_CASE(score_layout_attaches_stems_to_notehead_anchors) { BOOST_REQUIRE_EQUAL(heads.size(), 1U); BOOST_REQUIRE_EQUAL(stems.size(), 1U); - BOOST_TEST(near(stems.front()->from.x, heads.front()->origin.x + layout.staff_spacing * 1.336F)); - BOOST_TEST(near(stems.front()->from.y, heads.front()->origin.y - layout.staff_spacing * 0.288F)); + BOOST_TEST(stems.front()->from.x < heads.front()->bounds.x + heads.front()->bounds.width); + BOOST_TEST(stems.front()->from.x > heads.front()->bounds.x + heads.front()->bounds.width - layout.staff_spacing * 0.25F); + BOOST_TEST(stems.front()->from.y > heads.front()->bounds.y); + BOOST_TEST(stems.front()->from.y < heads.front()->bounds.y + heads.front()->bounds.height); BOOST_TEST(stems.front()->to.y < stems.front()->from.y); } @@ -403,3 +413,66 @@ BOOST_AUTO_TEST_CASE(score_layout_beams_eighth_tuplets_and_labels_them) { BOOST_REQUIRE_EQUAL(tuplets.size(), 1U); BOOST_TEST(tuplets.front()->text == "3"); } + +BOOST_AUTO_TEST_CASE(score_layout_keeps_dense_notation_inside_measure_guard) { + auto first = note_with_ticks({pitch('c', "#")}, 2048); + auto second = note_with_ticks({pitch('d', "#")}, 2048); + auto third = note_with_ticks({pitch('e', "n")}, 6144); + auto fourth = note_with_ticks({pitch('f', "#")}, 4096); + auto fifth = note_with_ticks({pitch('g', "#")}, 2048); + fourth.tie_to_next = true; + + auto score = score_with_measure({ + .time_signature = "4/4", + .key_signature = "C", + .notes = {std::move(first), std::move(second), std::move(third), std::move(fourth), std::move(fifth)}, + }); + auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U); + + auto barlines = layout_items(layout, vamp_zero::score_layout_item_role::FINAL_BARLINE, 0U, 0U); + BOOST_REQUIRE_EQUAL(barlines.size(), 1U); + auto guarded_right = barlines.front()->from.x - layout.staff_spacing; + auto roles = { + vamp_zero::score_layout_item_role::NOTEHEAD, + vamp_zero::score_layout_item_role::ACCIDENTAL, + vamp_zero::score_layout_item_role::STEM, + vamp_zero::score_layout_item_role::FLAG, + vamp_zero::score_layout_item_role::BEAM, + vamp_zero::score_layout_item_role::DOT, + vamp_zero::score_layout_item_role::TIE, + }; + for (auto role : roles) { + for (auto const* item : layout_items(layout, role, 0U, 0U)) { + BOOST_TEST(right_edge(*item) <= guarded_right); + } + } +} + +BOOST_AUTO_TEST_CASE(score_layout_raises_chord_symbols_above_high_notation) { + auto high = note({pitch('c', "n", 6)}); + high.chord_symbol = "Cmaj7"; + auto score = score_with_notes({std::move(high)}); + auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U); + + auto chords = layout_items(layout, vamp_zero::score_layout_item_role::CHORD_SYMBOL, 0U, 0U); + BOOST_REQUIRE_EQUAL(chords.size(), 1U); + auto top_y = std::numeric_limits::max(); + auto roles = { + vamp_zero::score_layout_item_role::NOTEHEAD, + vamp_zero::score_layout_item_role::ACCIDENTAL, + vamp_zero::score_layout_item_role::STEM, + vamp_zero::score_layout_item_role::FLAG, + vamp_zero::score_layout_item_role::BEAM, + vamp_zero::score_layout_item_role::DOT, + vamp_zero::score_layout_item_role::TUPLET, + vamp_zero::score_layout_item_role::TUPLET_BRACKET, + vamp_zero::score_layout_item_role::TIE, + }; + for (auto role : roles) { + for (auto const* item : layout_items(layout, role, 0U, 0U)) { + top_y = std::min(top_y, item->bounds.y); + } + } + BOOST_TEST(right_edge(*chords.front()) <= layout.bounds.x + layout.bounds.width); + BOOST_TEST(chords.front()->bounds.y + chords.front()->bounds.height <= top_y - layout.staff_spacing * 0.40F); +}