@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -17,11 +20,75 @@ enum class score_accidental_mark {
|
|||||||
SHARP,
|
SHARP,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class score_layout_item_kind {
|
||||||
|
FILL_RECT,
|
||||||
|
STROKE_RECT,
|
||||||
|
STROKE_LINE,
|
||||||
|
CURVE,
|
||||||
|
GLYPH,
|
||||||
|
TEXT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class score_layout_item_role {
|
||||||
|
PAPER,
|
||||||
|
BORDER,
|
||||||
|
STAFF_LINE,
|
||||||
|
BARLINE,
|
||||||
|
FINAL_BARLINE,
|
||||||
|
CLEF,
|
||||||
|
KEY_SIGNATURE,
|
||||||
|
TIME_SIGNATURE,
|
||||||
|
NOTEHEAD,
|
||||||
|
REST,
|
||||||
|
ACCIDENTAL,
|
||||||
|
STEM,
|
||||||
|
FLAG,
|
||||||
|
BEAM,
|
||||||
|
DOT,
|
||||||
|
TIE,
|
||||||
|
TUPLET,
|
||||||
|
TUPLET_BRACKET,
|
||||||
|
TITLE,
|
||||||
|
COMPOSER,
|
||||||
|
PART,
|
||||||
|
PAGE_NUMBER,
|
||||||
|
MEASURE_NUMBER,
|
||||||
|
SECTION_LABEL,
|
||||||
|
CHORD_SYMBOL,
|
||||||
|
LYRIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct score_layout_item {
|
||||||
|
vamp_zero::score_layout_item_kind kind{};
|
||||||
|
vamp_zero::score_layout_item_role role{};
|
||||||
|
luma::rect bounds{};
|
||||||
|
luma::point origin{};
|
||||||
|
luma::point from{};
|
||||||
|
luma::point control{};
|
||||||
|
luma::point to{};
|
||||||
|
luma::color color{};
|
||||||
|
std::float32_t thickness{};
|
||||||
|
std::float32_t staff_spacing{};
|
||||||
|
std::int32_t codepoint{};
|
||||||
|
std::int32_t text_scale{};
|
||||||
|
std::size_t system_index{std::numeric_limits<std::size_t>::max()};
|
||||||
|
std::size_t measure_index{std::numeric_limits<std::size_t>::max()};
|
||||||
|
std::size_t note_index{std::numeric_limits<std::size_t>::max()};
|
||||||
|
std::string text{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct score_page_layout {
|
||||||
|
luma::rect bounds{};
|
||||||
|
std::float32_t staff_spacing{};
|
||||||
|
std::vector<vamp_zero::score_layout_item> items{};
|
||||||
|
};
|
||||||
|
|
||||||
constexpr auto score_measures_per_system = std::size_t{4};
|
constexpr auto score_measures_per_system = std::size_t{4};
|
||||||
constexpr auto score_systems_per_page = std::size_t{3};
|
constexpr auto score_systems_per_page = std::size_t{3};
|
||||||
constexpr auto score_measures_per_page = score_measures_per_system * score_systems_per_page;
|
constexpr auto score_measures_per_page = score_measures_per_system * score_systems_per_page;
|
||||||
|
|
||||||
auto notated_accidentals_for_measure(vamp_zero::score_measure const& measure, std::string_view inherited_key_signature) -> std::vector<vamp_zero::score_accidental_mark>;
|
auto notated_accidentals_for_measure(vamp_zero::score_measure const& measure, std::string_view inherited_key_signature) -> std::vector<vamp_zero::score_accidental_mark>;
|
||||||
|
auto layout_score_page(luma::rect bounds, vamp_zero::score_document const& score, std::size_t page_index) -> vamp_zero::score_page_layout;
|
||||||
auto draw_score_page(luma::draw_context& context, luma::rect bounds, vamp_zero::score_document const& score, std::size_t page_index) -> void;
|
auto draw_score_page(luma::draw_context& context, luma::rect bounds, vamp_zero::score_document const& score, std::size_t page_index) -> void;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,15 +32,23 @@ struct score_note {
|
|||||||
std::string lyric{};
|
std::string lyric{};
|
||||||
std::bool_t tie_to_next{};
|
std::bool_t tie_to_next{};
|
||||||
std::bool_t tie_from_previous{};
|
std::bool_t tie_from_previous{};
|
||||||
|
std::string tuplet_id{};
|
||||||
std::vector<vamp_zero::score_pitch> pitches{};
|
std::vector<vamp_zero::score_pitch> pitches{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct score_tuplet {
|
||||||
|
std::string id{};
|
||||||
|
std::int32_t note_count{};
|
||||||
|
std::int32_t space_count{};
|
||||||
|
};
|
||||||
|
|
||||||
struct score_measure {
|
struct score_measure {
|
||||||
std::string id{};
|
std::string id{};
|
||||||
std::string section_label{};
|
std::string section_label{};
|
||||||
std::string time_signature{};
|
std::string time_signature{};
|
||||||
std::string key_signature{};
|
std::string key_signature{};
|
||||||
std::vector<vamp_zero::score_note> notes{};
|
std::vector<vamp_zero::score_note> notes{};
|
||||||
|
std::vector<vamp_zero::score_tuplet> tuplets{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct score_document {
|
struct score_document {
|
||||||
|
|||||||
@@ -169,8 +169,9 @@ auto parse_note(boost::json::value const& value) -> std::expected<vamp_zero::sco
|
|||||||
auto lyric = optional_string_value(**object, "lyric", {});
|
auto lyric = optional_string_value(**object, "lyric", {});
|
||||||
auto tie_to_next = optional_bool_value(**object, "tie_to_next", false);
|
auto tie_to_next = optional_bool_value(**object, "tie_to_next", false);
|
||||||
auto tie_from_previous = optional_bool_value(**object, "tie_from_previous", false);
|
auto tie_from_previous = optional_bool_value(**object, "tie_from_previous", false);
|
||||||
if (!id || !kind || !hidden || !duration || !display || !notehead || !chord || !lyric || !tie_to_next || !tie_from_previous) {
|
auto tuplet_id = optional_string_value(**object, "tuplet_id", {});
|
||||||
return std::unexpected(!id ? id.error() : (!kind ? kind.error() : (!hidden ? hidden.error() : (!duration ? duration.error() : (!display ? display.error() : (!notehead ? notehead.error() : (!chord ? chord.error() : (!lyric ? lyric.error() : (!tie_to_next ? tie_to_next.error() : tie_from_previous.error())))))))));
|
if (!id || !kind || !hidden || !duration || !display || !notehead || !chord || !lyric || !tie_to_next || !tie_from_previous || !tuplet_id) {
|
||||||
|
return std::unexpected(!id ? id.error() : (!kind ? kind.error() : (!hidden ? hidden.error() : (!duration ? duration.error() : (!display ? display.error() : (!notehead ? notehead.error() : (!chord ? chord.error() : (!lyric ? lyric.error() : (!tie_to_next ? tie_to_next.error() : (!tie_from_previous ? tie_from_previous.error() : tuplet_id.error()))))))))));
|
||||||
}
|
}
|
||||||
auto note = vamp_zero::score_note{
|
auto note = vamp_zero::score_note{
|
||||||
.id = std::move(*id),
|
.id = std::move(*id),
|
||||||
@@ -183,6 +184,7 @@ auto parse_note(boost::json::value const& value) -> std::expected<vamp_zero::sco
|
|||||||
.lyric = std::move(*lyric),
|
.lyric = std::move(*lyric),
|
||||||
.tie_to_next = *tie_to_next,
|
.tie_to_next = *tie_to_next,
|
||||||
.tie_from_previous = *tie_from_previous,
|
.tie_from_previous = *tie_from_previous,
|
||||||
|
.tuplet_id = std::move(*tuplet_id),
|
||||||
};
|
};
|
||||||
if (auto found = (*object)->find("pitches"); found != (*object)->end()) {
|
if (auto found = (*object)->find("pitches"); found != (*object)->end()) {
|
||||||
if (!found->value().is_array()) {
|
if (!found->value().is_array()) {
|
||||||
@@ -199,6 +201,24 @@ auto parse_note(boost::json::value const& value) -> std::expected<vamp_zero::sco
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto parse_tuplet(boost::json::value const& value) -> std::expected<vamp_zero::score_tuplet, modulus::error> {
|
||||||
|
auto object = require_object(value, "tuplet");
|
||||||
|
if (!object) {
|
||||||
|
return std::unexpected(object.error());
|
||||||
|
}
|
||||||
|
auto id = required_string(**object, "id");
|
||||||
|
auto note_count = optional_int64_value(**object, "note_count", 0);
|
||||||
|
auto space_count = optional_int64_value(**object, "space_count", 0);
|
||||||
|
if (!id || !note_count || !space_count) {
|
||||||
|
return std::unexpected(!id ? id.error() : (!note_count ? note_count.error() : space_count.error()));
|
||||||
|
}
|
||||||
|
return vamp_zero::score_tuplet{
|
||||||
|
.id = std::move(*id),
|
||||||
|
.note_count = static_cast<std::int32_t>(*note_count),
|
||||||
|
.space_count = static_cast<std::int32_t>(*space_count),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
auto parse_measure(boost::json::value const& value) -> std::expected<vamp_zero::score_measure, modulus::error> {
|
auto parse_measure(boost::json::value const& value) -> std::expected<vamp_zero::score_measure, modulus::error> {
|
||||||
auto object = require_object(value, "measure");
|
auto object = require_object(value, "measure");
|
||||||
if (!object) {
|
if (!object) {
|
||||||
@@ -237,6 +257,18 @@ auto parse_measure(boost::json::value const& value) -> std::expected<vamp_zero::
|
|||||||
}
|
}
|
||||||
measure.notes.push_back(std::move(*note));
|
measure.notes.push_back(std::move(*note));
|
||||||
}
|
}
|
||||||
|
if (auto tuplets_value = (*voice)->find("tuplets"); tuplets_value != (*voice)->end()) {
|
||||||
|
if (!tuplets_value->value().is_array()) {
|
||||||
|
return std::unexpected(invalid("tuplets must be an array"));
|
||||||
|
}
|
||||||
|
for (auto const& tuplet_value : tuplets_value->value().as_array()) {
|
||||||
|
auto tuplet = parse_tuplet(tuplet_value);
|
||||||
|
if (!tuplet) {
|
||||||
|
return std::unexpected(tuplet.error());
|
||||||
|
}
|
||||||
|
measure.tuplets.push_back(std::move(*tuplet));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return measure;
|
return measure;
|
||||||
}
|
}
|
||||||
|
|||||||
+1166
-235
File diff suppressed because it is too large
Load Diff
@@ -416,7 +416,7 @@ public:
|
|||||||
.x = area.x,
|
.x = area.x,
|
||||||
.y = area.y + 80.0F,
|
.y = area.y + 80.0F,
|
||||||
.width = area.width,
|
.width = area.width,
|
||||||
.height = std::max(0.0F, area.height - 80.0F),
|
.height = std::max<std::float32_t>(0.0F, area.height - 80.0F),
|
||||||
};
|
};
|
||||||
if (state_.app.mode == vamp_zero::app_mode::MAIN_MENU) {
|
if (state_.app.mode == vamp_zero::app_mode::MAIN_MENU) {
|
||||||
draw_main_menu(context, content, state_.app);
|
draw_main_menu(context, content, state_.app);
|
||||||
|
|||||||
+187
@@ -1,6 +1,11 @@
|
|||||||
#include <test.hpp>
|
#include <test.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -12,6 +17,8 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto layout_any_index = std::numeric_limits<std::size_t>::max();
|
||||||
|
|
||||||
auto pitch(std::char_t letter, std::string accidental, std::int32_t octave = 4) -> vamp_zero::score_pitch {
|
auto pitch(std::char_t letter, std::string accidental, std::int32_t octave = 4) -> vamp_zero::score_pitch {
|
||||||
return {
|
return {
|
||||||
.letter = letter,
|
.letter = letter,
|
||||||
@@ -37,6 +44,13 @@ auto note(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto note_with_ticks(std::initializer_list<vamp_zero::score_pitch> pitches, std::int64_t ticks) -> vamp_zero::score_note {
|
||||||
|
auto value = note(pitches);
|
||||||
|
value.duration_ticks = ticks;
|
||||||
|
value.display_ticks = ticks;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
auto rest(std::initializer_list<vamp_zero::score_pitch> pitches) -> vamp_zero::score_note {
|
auto rest(std::initializer_list<vamp_zero::score_pitch> pitches) -> vamp_zero::score_note {
|
||||||
return {
|
return {
|
||||||
.kind = "rest",
|
.kind = "rest",
|
||||||
@@ -79,6 +93,71 @@ void expect_marks(std::vector<vamp_zero::score_accidental_mark> const& marks, st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto score_with_measure(vamp_zero::score_measure measure, std::string key_signature = "C", std::string time_signature = "4/4", std::string clef = "treble") -> vamp_zero::score_document {
|
||||||
|
if (measure.key_signature.empty()) {
|
||||||
|
measure.key_signature = key_signature;
|
||||||
|
}
|
||||||
|
if (measure.time_signature.empty()) {
|
||||||
|
measure.time_signature = time_signature;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
.title = "Layout Tune",
|
||||||
|
.composer = "Composer",
|
||||||
|
.key_signature = std::move(key_signature),
|
||||||
|
.time_signature = std::move(time_signature),
|
||||||
|
.clef = std::move(clef),
|
||||||
|
.part = "Lead",
|
||||||
|
.measures = {std::move(measure)},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto score_with_notes(std::vector<vamp_zero::score_note> notes, std::string key_signature = "C", std::string time_signature = "4/4", std::string clef = "treble") -> vamp_zero::score_document {
|
||||||
|
auto measure = vamp_zero::score_measure{
|
||||||
|
.time_signature = time_signature,
|
||||||
|
.key_signature = key_signature,
|
||||||
|
.notes = std::move(notes),
|
||||||
|
};
|
||||||
|
return score_with_measure(std::move(measure), std::move(key_signature), std::move(time_signature), std::move(clef));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto layout_items(
|
||||||
|
vamp_zero::score_page_layout const& layout,
|
||||||
|
vamp_zero::score_layout_item_role role,
|
||||||
|
std::size_t system_index = layout_any_index,
|
||||||
|
std::size_t measure_index = layout_any_index,
|
||||||
|
std::size_t note_index = layout_any_index
|
||||||
|
) -> std::vector<vamp_zero::score_layout_item const*> {
|
||||||
|
auto result = std::vector<vamp_zero::score_layout_item const*>{};
|
||||||
|
for (auto const& item : layout.items) {
|
||||||
|
if (item.role != role) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (system_index != layout_any_index && item.system_index != system_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (measure_index != layout_any_index && item.measure_index != measure_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (note_index != layout_any_index && item.note_index != note_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push_back(&item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto center_x(vamp_zero::score_layout_item const& item) -> std::float32_t {
|
||||||
|
return item.bounds.x + item.bounds.width * 0.5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto center_y(vamp_zero::score_layout_item const& item) -> std::float32_t {
|
||||||
|
return item.bounds.y + item.bounds.height * 0.5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto near(std::float32_t left, std::float32_t right) -> std::bool_t {
|
||||||
|
return std::abs(left - right) < 0.02F;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
||||||
@@ -110,6 +189,7 @@ BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
|||||||
"id": "note-1",
|
"id": "note-1",
|
||||||
"kind": "note",
|
"kind": "note",
|
||||||
"duration_ticks": 4096,
|
"duration_ticks": 4096,
|
||||||
|
"tuplet_id": "triplet-1",
|
||||||
"pitches": [
|
"pitches": [
|
||||||
{
|
{
|
||||||
"letter": "b",
|
"letter": "b",
|
||||||
@@ -118,6 +198,13 @@ BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"tuplets": [
|
||||||
|
{
|
||||||
|
"id": "triplet-1",
|
||||||
|
"note_count": 3,
|
||||||
|
"space_count": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -138,6 +225,12 @@ BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
|||||||
BOOST_REQUIRE_EQUAL(parsed->measures.size(), 1U);
|
BOOST_REQUIRE_EQUAL(parsed->measures.size(), 1U);
|
||||||
BOOST_TEST(parsed->measures.front().key_signature == "Eb");
|
BOOST_TEST(parsed->measures.front().key_signature == "Eb");
|
||||||
BOOST_TEST(parsed->measures.front().time_signature == "3/4");
|
BOOST_TEST(parsed->measures.front().time_signature == "3/4");
|
||||||
|
BOOST_REQUIRE_EQUAL(parsed->measures.front().notes.size(), 1U);
|
||||||
|
BOOST_TEST(parsed->measures.front().notes.front().tuplet_id == "triplet-1");
|
||||||
|
BOOST_REQUIRE_EQUAL(parsed->measures.front().tuplets.size(), 1U);
|
||||||
|
BOOST_TEST(parsed->measures.front().tuplets.front().id == "triplet-1");
|
||||||
|
BOOST_TEST(parsed->measures.front().tuplets.front().note_count == 3);
|
||||||
|
BOOST_TEST(parsed->measures.front().tuplets.front().space_count == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(accidentals_are_suppressed_when_the_key_signature_already_covers_the_pitch) {
|
BOOST_AUTO_TEST_CASE(accidentals_are_suppressed_when_the_key_signature_already_covers_the_pitch) {
|
||||||
@@ -216,3 +309,97 @@ BOOST_AUTO_TEST_CASE(tied_rests_hidden_and_slash_notes_do_not_emit_accidentals)
|
|||||||
};
|
};
|
||||||
expect_marks(vamp_zero::notated_accidentals_for_measure(measure, "C"), {"none", "flat"});
|
expect_marks(vamp_zero::notated_accidentals_for_measure(measure, "C"), {"none", "flat"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(score_layout_orders_preamble_on_staff_baselines) {
|
||||||
|
auto score = score_with_notes({note({pitch('e', "n")})}, "F", "4/4", "treble");
|
||||||
|
auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U);
|
||||||
|
|
||||||
|
auto clefs = layout_items(layout, vamp_zero::score_layout_item_role::CLEF, 0U);
|
||||||
|
auto keys = layout_items(layout, vamp_zero::score_layout_item_role::KEY_SIGNATURE, 0U);
|
||||||
|
auto times = layout_items(layout, vamp_zero::score_layout_item_role::TIME_SIGNATURE, 0U);
|
||||||
|
auto staff_lines = layout_items(layout, vamp_zero::score_layout_item_role::STAFF_LINE, 0U);
|
||||||
|
BOOST_REQUIRE_EQUAL(clefs.size(), 1U);
|
||||||
|
BOOST_REQUIRE(!keys.empty());
|
||||||
|
BOOST_REQUIRE(!times.empty());
|
||||||
|
BOOST_REQUIRE_GE(staff_lines.size(), 5U);
|
||||||
|
|
||||||
|
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_AUTO_TEST_CASE(score_layout_attaches_stems_to_notehead_anchors) {
|
||||||
|
auto score = score_with_notes({note({pitch('d', "n")})});
|
||||||
|
auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U);
|
||||||
|
|
||||||
|
auto heads = layout_items(layout, vamp_zero::score_layout_item_role::NOTEHEAD, 0U, 0U, 0U);
|
||||||
|
auto stems = layout_items(layout, vamp_zero::score_layout_item_role::STEM, 0U, 0U, 0U);
|
||||||
|
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()->to.y < stems.front()->from.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(score_layout_displaces_chord_seconds_and_columns_accidentals) {
|
||||||
|
auto score = score_with_notes({note({pitch('c', "#"), pitch('d', "#")})});
|
||||||
|
auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U);
|
||||||
|
|
||||||
|
auto heads = layout_items(layout, vamp_zero::score_layout_item_role::NOTEHEAD, 0U, 0U, 0U);
|
||||||
|
auto accidentals = layout_items(layout, vamp_zero::score_layout_item_role::ACCIDENTAL, 0U, 0U, 0U);
|
||||||
|
BOOST_REQUIRE_EQUAL(heads.size(), 2U);
|
||||||
|
BOOST_REQUIRE_EQUAL(accidentals.size(), 2U);
|
||||||
|
|
||||||
|
auto const* lower_head = *std::max_element(heads.begin(), heads.end(), [](auto const* left, auto const* right) {
|
||||||
|
return center_y(*left) < center_y(*right);
|
||||||
|
});
|
||||||
|
auto const* upper_head = *std::min_element(heads.begin(), heads.end(), [](auto const* left, auto const* right) {
|
||||||
|
return center_y(*left) < center_y(*right);
|
||||||
|
});
|
||||||
|
BOOST_TEST(center_x(*upper_head) > center_x(*lower_head) + layout.staff_spacing * 0.30F);
|
||||||
|
|
||||||
|
auto const* leftmost_head = *std::min_element(heads.begin(), heads.end(), [](auto const* left, auto const* right) {
|
||||||
|
return left->bounds.x < right->bounds.x;
|
||||||
|
});
|
||||||
|
auto const* leftmost_accidental = *std::min_element(accidentals.begin(), accidentals.end(), [](auto const* left, auto const* right) {
|
||||||
|
return left->bounds.x < right->bounds.x;
|
||||||
|
});
|
||||||
|
auto const* rightmost_accidental = *std::max_element(accidentals.begin(), accidentals.end(), [](auto const* left, auto const* right) {
|
||||||
|
return left->bounds.x < right->bounds.x;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto const* accidental : accidentals) {
|
||||||
|
BOOST_TEST(accidental->bounds.x + accidental->bounds.width < leftmost_head->bounds.x);
|
||||||
|
}
|
||||||
|
BOOST_TEST(rightmost_accidental->bounds.x - leftmost_accidental->bounds.x > layout.staff_spacing * 0.50F);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(score_layout_beams_eighth_tuplets_and_labels_them) {
|
||||||
|
auto first = note_with_ticks({pitch('c', "n")}, 2048);
|
||||||
|
auto second = note_with_ticks({pitch('d', "n")}, 2048);
|
||||||
|
auto third = note_with_ticks({pitch('e', "n")}, 2048);
|
||||||
|
first.tuplet_id = "triplet-1";
|
||||||
|
second.tuplet_id = "triplet-1";
|
||||||
|
third.tuplet_id = "triplet-1";
|
||||||
|
|
||||||
|
auto score = score_with_measure({
|
||||||
|
.time_signature = "4/4",
|
||||||
|
.key_signature = "C",
|
||||||
|
.notes = {std::move(first), std::move(second), std::move(third)},
|
||||||
|
.tuplets = {{
|
||||||
|
.id = "triplet-1",
|
||||||
|
.note_count = 3,
|
||||||
|
.space_count = 2,
|
||||||
|
}},
|
||||||
|
});
|
||||||
|
auto layout = vamp_zero::layout_score_page({.x = 0.0F, .y = 0.0F, .width = 976.0F, .height = 484.0F}, score, 0U);
|
||||||
|
|
||||||
|
auto beams = layout_items(layout, vamp_zero::score_layout_item_role::BEAM, 0U, 0U);
|
||||||
|
auto flags = layout_items(layout, vamp_zero::score_layout_item_role::FLAG, 0U, 0U);
|
||||||
|
auto tuplets = layout_items(layout, vamp_zero::score_layout_item_role::TUPLET, 0U, 0U);
|
||||||
|
BOOST_REQUIRE_EQUAL(beams.size(), 1U);
|
||||||
|
BOOST_TEST(flags.empty());
|
||||||
|
BOOST_REQUIRE_EQUAL(tuplets.size(), 1U);
|
||||||
|
BOOST_TEST(tuplets.front()->text == "3");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user