#include #include #include #include #include #include #include #include #include namespace { auto pitch(std::char_t letter, std::string accidental, std::int32_t octave = 4) -> vamp_zero::score_pitch { return { .letter = letter, .octave = octave, .accidental = std::move(accidental), }; } auto note( std::initializer_list pitches, std::bool_t tie_from_previous = false, std::string notehead = {}, std::bool_t hidden = false ) -> vamp_zero::score_note { return { .kind = "note", .hidden = hidden, .duration_ticks = 4096, .display_ticks = 4096, .notehead = std::move(notehead), .tie_from_previous = tie_from_previous, .pitches = std::vector{pitches}, }; } auto rest(std::initializer_list pitches) -> vamp_zero::score_note { return { .kind = "rest", .duration_ticks = 4096, .display_ticks = 4096, .pitches = std::vector{pitches}, }; } auto mark_name(vamp_zero::score_accidental_mark mark) -> std::string { switch (mark) { case vamp_zero::score_accidental_mark::NONE: return "none"; case vamp_zero::score_accidental_mark::FLAT: return "flat"; case vamp_zero::score_accidental_mark::NATURAL: return "natural"; case vamp_zero::score_accidental_mark::SHARP: return "sharp"; } return {}; } auto mark_names(std::vector const& marks) -> std::vector { auto names = std::vector{}; names.reserve(marks.size()); for (auto mark : marks) { names.push_back(mark_name(mark)); } return names; } void expect_marks(std::vector const& marks, std::initializer_list expected) { auto actual_names = mark_names(marks); BOOST_REQUIRE_EQUAL(actual_names.size(), expected.size()); auto index = std::size_t{}; for (auto name : expected) { BOOST_TEST(actual_names[index] == std::string{name}); ++index; } } } BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) { auto parsed = vamp_zero::parse_score_document(R"json( { "version": 1, "metadata": { "title": "Standard Tune", "composer": "Composer", "key_signature": "Eb", "time_signature": "3/4" }, "staves": [ { "id": "staff-lead", "name": "Lead", "clef": "treble", "role": "lead", "measures": [ { "id": "measure-1", "time_signature": "3/4", "key_signature": "Eb", "voices": [ { "id": "voice-1", "notes": [ { "id": "note-1", "kind": "note", "duration_ticks": 4096, "pitches": [ { "letter": "b", "octave": 4, "accidental": "b" } ] } ] } ] } ] } ] } )json", "standard-tune.json"); BOOST_REQUIRE(static_cast(parsed)); BOOST_TEST(parsed->title == "Standard Tune"); BOOST_TEST(parsed->composer == "Composer"); BOOST_TEST(parsed->key_signature == "Eb"); BOOST_TEST(parsed->time_signature == "3/4"); BOOST_TEST(parsed->clef == "treble"); BOOST_TEST(parsed->part == "Lead"); BOOST_REQUIRE_EQUAL(parsed->measures.size(), 1U); BOOST_TEST(parsed->measures.front().key_signature == "Eb"); BOOST_TEST(parsed->measures.front().time_signature == "3/4"); } BOOST_AUTO_TEST_CASE(accidentals_are_suppressed_when_the_key_signature_already_covers_the_pitch) { auto c_major = vamp_zero::score_measure{ .key_signature = "C", .notes = { note({pitch('c', "n")}), note({pitch('d', "n")}), note({pitch('e', "n")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(c_major, "C"), {"none", "none", "none"}); auto f_major = vamp_zero::score_measure{ .key_signature = "F", .notes = { note({pitch('b', "b")}), note({pitch('e', "n")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(f_major, "C"), {"none", "none"}); auto d_major = vamp_zero::score_measure{ .key_signature = "D", .notes = { note({pitch('f', "#")}), note({pitch('c', "#")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(d_major, "C"), {"none", "none"}); } BOOST_AUTO_TEST_CASE(accidentals_follow_measure_state_after_the_first_printed_change) { auto measure = vamp_zero::score_measure{ .key_signature = "F", .notes = { note({pitch('b', "b")}), note({pitch('b', "n")}), note({pitch('b', "n")}), note({pitch('b', "b")}), note({pitch('b', "b")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(measure, "C"), {"none", "natural", "none", "flat", "none"}); } BOOST_AUTO_TEST_CASE(measure_state_resets_for_each_measure) { auto first = vamp_zero::score_measure{ .key_signature = "C", .notes = { note({pitch('f', "#")}), note({pitch('f', "#")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(first, "C"), {"sharp", "none"}); auto second = vamp_zero::score_measure{ .key_signature = "C", .notes = { note({pitch('f', "#")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(second, "C"), {"sharp"}); } BOOST_AUTO_TEST_CASE(tied_rests_hidden_and_slash_notes_do_not_emit_accidentals) { auto measure = vamp_zero::score_measure{ .key_signature = "C", .notes = { note({pitch('f', "#")}, true), rest({pitch('b', "b")}), note({pitch('c', "#")}, false, "slash"), note({pitch('d', "#")}, false, {}, true), note({pitch('e', "b")}), }, }; expect_marks(vamp_zero::notated_accidentals_for_measure(measure, "C"), {"none", "flat"}); }