+424
-47
@@ -1,13 +1,16 @@
|
||||
#include <vamp_zero/score.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <raylib.h>
|
||||
@@ -52,6 +55,13 @@ struct notehead_instance {
|
||||
luma::point origin{};
|
||||
};
|
||||
|
||||
struct key_signature_position {
|
||||
std::char_t letter{};
|
||||
std::int32_t octave{};
|
||||
};
|
||||
|
||||
constexpr auto glyph_g_clef = std::int32_t{0xe050};
|
||||
constexpr auto glyph_f_clef = std::int32_t{0xe062};
|
||||
constexpr auto glyph_notehead_whole = std::int32_t{0xe0a2};
|
||||
constexpr auto glyph_notehead_half = std::int32_t{0xe0a3};
|
||||
constexpr auto glyph_notehead_black = std::int32_t{0xe0a4};
|
||||
@@ -66,6 +76,7 @@ constexpr auto glyph_augmentation_dot = std::int32_t{0xe1e7};
|
||||
constexpr auto glyph_accidental_flat = std::int32_t{0xe260};
|
||||
constexpr auto glyph_accidental_natural = std::int32_t{0xe261};
|
||||
constexpr auto glyph_accidental_sharp = std::int32_t{0xe262};
|
||||
constexpr auto glyph_time_sig_0 = std::int32_t{0xe080};
|
||||
|
||||
auto to_raylib(luma::color color) -> Color {
|
||||
return {
|
||||
@@ -104,6 +115,8 @@ auto music_font(std::float32_t size) -> Font {
|
||||
}
|
||||
|
||||
auto codepoints = std::vector<std::int32_t>{
|
||||
glyph_g_clef,
|
||||
glyph_f_clef,
|
||||
glyph_notehead_whole,
|
||||
glyph_notehead_half,
|
||||
glyph_notehead_black,
|
||||
@@ -118,6 +131,16 @@ auto music_font(std::float32_t size) -> Font {
|
||||
glyph_accidental_flat,
|
||||
glyph_accidental_natural,
|
||||
glyph_accidental_sharp,
|
||||
glyph_time_sig_0,
|
||||
glyph_time_sig_0 + 1,
|
||||
glyph_time_sig_0 + 2,
|
||||
glyph_time_sig_0 + 3,
|
||||
glyph_time_sig_0 + 4,
|
||||
glyph_time_sig_0 + 5,
|
||||
glyph_time_sig_0 + 6,
|
||||
glyph_time_sig_0 + 7,
|
||||
glyph_time_sig_0 + 8,
|
||||
glyph_time_sig_0 + 9,
|
||||
};
|
||||
auto path = std::string{vamp_style::score_music_font_path};
|
||||
auto loaded = LoadFontEx(path.c_str(), key, codepoints.data(), static_cast<std::int32_t>(codepoints.size()));
|
||||
@@ -130,6 +153,10 @@ auto music_font(std::float32_t size) -> Font {
|
||||
|
||||
auto glyph_box(std::int32_t codepoint) -> smufl_box {
|
||||
switch (codepoint) {
|
||||
case glyph_g_clef:
|
||||
return {.west = 0.0F, .south = -2.236F, .east = 2.656F, .north = 4.036F};
|
||||
case glyph_f_clef:
|
||||
return {.west = 0.0F, .south = -1.984F, .east = 3.104F, .north = 0.864F};
|
||||
case glyph_notehead_whole:
|
||||
return {.west = 0.0F, .south = -0.744684F, .east = 1.520666F, .north = 0.66F};
|
||||
case glyph_notehead_half:
|
||||
@@ -158,6 +185,26 @@ auto glyph_box(std::int32_t codepoint) -> smufl_box {
|
||||
return {.west = 0.0F, .south = -1.824F, .east = 0.854F, .north = 1.848F};
|
||||
case glyph_accidental_sharp:
|
||||
return {.west = -0.088F, .south = -1.536F, .east = 1.56F, .north = 1.532F};
|
||||
case glyph_time_sig_0:
|
||||
return {.west = 0.08F, .south = -1.432638F, .east = 2.052F, .north = 1.436F};
|
||||
case glyph_time_sig_0 + 1:
|
||||
return {.west = 0.08F, .south = -1.456F, .east = 1.132F, .north = 1.464F};
|
||||
case glyph_time_sig_0 + 2:
|
||||
return {.west = 0.08F, .south = -1.514881F, .east = 2.641855F, .north = 1.528F};
|
||||
case glyph_time_sig_0 + 3:
|
||||
return {.west = 0.079297F, .south = -1.568F, .east = 2.16F, .north = 1.568F};
|
||||
case glyph_time_sig_0 + 4:
|
||||
return {.west = 0.08F, .south = -1.958364F, .east = 2.532314F, .north = 1.964742F};
|
||||
case glyph_time_sig_0 + 5:
|
||||
return {.west = 0.078655F, .south = -1.556F, .east = 2.332F, .north = 1.556F};
|
||||
case glyph_time_sig_0 + 6:
|
||||
return {.west = 0.08F, .south = -1.452F, .east = 2.26F, .north = 1.452F};
|
||||
case glyph_time_sig_0 + 7:
|
||||
return {.west = 0.081447F, .south = -1.38F, .east = 2.368F, .north = 1.38F};
|
||||
case glyph_time_sig_0 + 8:
|
||||
return {.west = 0.08F, .south = -1.712F, .east = 2.016604F, .north = 1.712F};
|
||||
case glyph_time_sig_0 + 9:
|
||||
return {.west = 0.08F, .south = -1.684F, .east = 1.976F, .north = 1.684F};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@@ -192,26 +239,22 @@ auto draw_music_origin(std::int32_t codepoint, luma::point origin, std::float32_
|
||||
DrawTextEx(font, text.c_str(), {.x = draw_x, .y = draw_y}, font_size, 0.0F, to_raylib(color));
|
||||
}
|
||||
|
||||
auto origin_for_center(std::int32_t codepoint, std::float32_t center_x, std::float32_t center_y, std::float32_t staff_spacing) -> luma::point {
|
||||
auto origin_for_centered_baseline(std::int32_t codepoint, std::float32_t center_x, std::float32_t baseline_y, std::float32_t staff_spacing) -> luma::point {
|
||||
return {
|
||||
.x = center_x - glyph_center_x(codepoint) * staff_spacing,
|
||||
.y = center_y + glyph_center_y(codepoint) * staff_spacing,
|
||||
.y = baseline_y,
|
||||
};
|
||||
}
|
||||
|
||||
auto draw_music_centered(std::int32_t codepoint, std::float32_t center_x, std::float32_t center_y, std::float32_t staff_spacing, luma::color color) -> void {
|
||||
draw_music_origin(codepoint, origin_for_center(codepoint, center_x, center_y, staff_spacing), staff_spacing, color);
|
||||
auto draw_music_centered(std::int32_t codepoint, std::float32_t center_x, std::float32_t baseline_y, std::float32_t staff_spacing, luma::color color) -> void {
|
||||
draw_music_origin(codepoint, origin_for_centered_baseline(codepoint, center_x, baseline_y, staff_spacing), staff_spacing, color);
|
||||
}
|
||||
|
||||
auto draw_music_centered_at_origin_y(std::int32_t codepoint, std::float32_t center_x, std::float32_t origin_y, std::float32_t staff_spacing, luma::color color) -> void {
|
||||
draw_music_origin(codepoint, {.x = center_x - glyph_center_x(codepoint) * staff_spacing, .y = origin_y}, staff_spacing, color);
|
||||
}
|
||||
|
||||
auto draw_music_left_of(std::int32_t codepoint, std::float32_t right_x, std::float32_t center_y, std::float32_t staff_spacing, luma::color color) -> void {
|
||||
auto draw_music_left_of(std::int32_t codepoint, std::float32_t right_x, std::float32_t baseline_y, std::float32_t staff_spacing, luma::color color) -> void {
|
||||
auto box = glyph_box(codepoint);
|
||||
auto origin = luma::point{
|
||||
.x = right_x - box.east * staff_spacing,
|
||||
.y = center_y + glyph_center_y(codepoint) * staff_spacing,
|
||||
.y = baseline_y,
|
||||
};
|
||||
draw_music_origin(codepoint, origin, staff_spacing, color);
|
||||
}
|
||||
@@ -359,19 +402,185 @@ auto pitch_y(vamp_zero::score_pitch const& pitch, std::float32_t staff_top, std:
|
||||
return bottom_line - static_cast<std::float32_t>(pitch_index - e4_index) * (staff_spacing * 0.5F);
|
||||
}
|
||||
|
||||
auto accidental_glyph(std::string const& accidental) -> std::optional<std::int32_t> {
|
||||
if (accidental == "#" || accidental == "sharp") {
|
||||
return glyph_accidental_sharp;
|
||||
}
|
||||
if (accidental == "b" || accidental == "flat") {
|
||||
auto accidental_glyph(vamp_zero::score_accidental_mark mark) -> std::optional<std::int32_t> {
|
||||
switch (mark) {
|
||||
case vamp_zero::score_accidental_mark::FLAT:
|
||||
return glyph_accidental_flat;
|
||||
}
|
||||
if (accidental == "natural" || accidental == "n") {
|
||||
case vamp_zero::score_accidental_mark::NATURAL:
|
||||
return glyph_accidental_natural;
|
||||
case vamp_zero::score_accidental_mark::SHARP:
|
||||
return glyph_accidental_sharp;
|
||||
case vamp_zero::score_accidental_mark::NONE:
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto normalized_letter(std::char_t letter) -> std::char_t {
|
||||
if (letter >= 'A' && letter <= 'Z') {
|
||||
return static_cast<std::char_t>(letter + ('a' - 'A'));
|
||||
}
|
||||
return letter;
|
||||
}
|
||||
|
||||
auto pitch_alteration(std::string_view accidental) -> std::int32_t {
|
||||
if (accidental == "#" || accidental == "sharp") {
|
||||
return 1;
|
||||
}
|
||||
if (accidental == "b" || accidental == "flat") {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto alteration_mark(std::int32_t alteration) -> vamp_zero::score_accidental_mark {
|
||||
if (alteration < 0) {
|
||||
return vamp_zero::score_accidental_mark::FLAT;
|
||||
}
|
||||
if (alteration > 0) {
|
||||
return vamp_zero::score_accidental_mark::SHARP;
|
||||
}
|
||||
return vamp_zero::score_accidental_mark::NATURAL;
|
||||
}
|
||||
|
||||
auto normalized_key(std::string_view key_signature) -> std::string {
|
||||
auto key = std::string{key_signature};
|
||||
key.erase(std::remove(key.begin(), key.end(), ' '), key.end());
|
||||
if (key.empty()) {
|
||||
return "C";
|
||||
}
|
||||
key.front() = static_cast<std::char_t>(key.front() >= 'a' && key.front() <= 'z' ? key.front() - ('a' - 'A') : key.front());
|
||||
for (auto index = std::size_t{1}; index < key.size(); ++index) {
|
||||
if (key[index] >= 'A' && key[index] <= 'Z') {
|
||||
key[index] = static_cast<std::char_t>(key[index] + ('a' - 'A'));
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
auto key_signature_count(std::string_view key_signature) -> std::int32_t {
|
||||
auto key = normalized_key(key_signature);
|
||||
if (key == "C") {
|
||||
return 0;
|
||||
}
|
||||
if (key == "G") {
|
||||
return 1;
|
||||
}
|
||||
if (key == "D") {
|
||||
return 2;
|
||||
}
|
||||
if (key == "A") {
|
||||
return 3;
|
||||
}
|
||||
if (key == "E") {
|
||||
return 4;
|
||||
}
|
||||
if (key == "B") {
|
||||
return 5;
|
||||
}
|
||||
if (key == "F#" || key == "Gb") {
|
||||
return key == "F#" ? 6 : -6;
|
||||
}
|
||||
if (key == "C#") {
|
||||
return 7;
|
||||
}
|
||||
if (key == "F") {
|
||||
return -1;
|
||||
}
|
||||
if (key == "Bb") {
|
||||
return -2;
|
||||
}
|
||||
if (key == "Eb") {
|
||||
return -3;
|
||||
}
|
||||
if (key == "Ab") {
|
||||
return -4;
|
||||
}
|
||||
if (key == "Db") {
|
||||
return -5;
|
||||
}
|
||||
if (key == "Cb") {
|
||||
return -7;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto key_signature_alterations(std::string_view key_signature) -> std::array<std::int32_t, 7> {
|
||||
auto alterations = std::array<std::int32_t, 7>{};
|
||||
constexpr auto sharp_order = std::array<std::char_t, 7>{'f', 'c', 'g', 'd', 'a', 'e', 'b'};
|
||||
constexpr auto flat_order = std::array<std::char_t, 7>{'b', 'e', 'a', 'd', 'g', 'c', 'f'};
|
||||
auto count = key_signature_count(key_signature);
|
||||
if (count > 0) {
|
||||
for (auto index = std::int32_t{}; index < count; ++index) {
|
||||
alterations[static_cast<std::size_t>(letter_index(sharp_order[static_cast<std::size_t>(index)]))] = 1;
|
||||
}
|
||||
} else if (count < 0) {
|
||||
for (auto index = std::int32_t{}; index < -count; ++index) {
|
||||
alterations[static_cast<std::size_t>(letter_index(flat_order[static_cast<std::size_t>(index)]))] = -1;
|
||||
}
|
||||
}
|
||||
return alterations;
|
||||
}
|
||||
|
||||
struct accidental_state {
|
||||
std::array<std::int32_t, 7> key_alterations{};
|
||||
std::map<std::pair<std::int32_t, std::int32_t>, std::int32_t> measure_alterations{};
|
||||
};
|
||||
|
||||
auto make_accidental_state(std::string_view key_signature) -> accidental_state {
|
||||
return {
|
||||
.key_alterations = key_signature_alterations(key_signature),
|
||||
};
|
||||
}
|
||||
|
||||
auto notated_accidental_for_pitch(accidental_state& state, vamp_zero::score_pitch const& pitch, std::bool_t tied_from_previous) -> vamp_zero::score_accidental_mark {
|
||||
if (tied_from_previous) {
|
||||
return vamp_zero::score_accidental_mark::NONE;
|
||||
}
|
||||
auto const letter = letter_index(normalized_letter(pitch.letter));
|
||||
auto const desired = pitch_alteration(pitch.accidental);
|
||||
auto const key = std::pair{letter, pitch.octave};
|
||||
auto const found = state.measure_alterations.find(key);
|
||||
auto const current = found == state.measure_alterations.end() ? state.key_alterations[static_cast<std::size_t>(letter)] : found->second;
|
||||
if (desired == current) {
|
||||
return vamp_zero::score_accidental_mark::NONE;
|
||||
}
|
||||
state.measure_alterations[key] = desired;
|
||||
return alteration_mark(desired);
|
||||
}
|
||||
|
||||
auto notated_accidentals_for_note(accidental_state& state, vamp_zero::score_note const& note, std::bool_t slash) -> std::vector<vamp_zero::score_accidental_mark> {
|
||||
auto marks = std::vector<vamp_zero::score_accidental_mark>{};
|
||||
if (note.hidden || note.kind == "rest" || slash) {
|
||||
return marks;
|
||||
}
|
||||
marks.reserve(note.pitches.size());
|
||||
for (auto const& pitch : note.pitches) {
|
||||
marks.push_back(notated_accidental_for_pitch(state, pitch, note.tie_from_previous));
|
||||
}
|
||||
return marks;
|
||||
}
|
||||
|
||||
auto effective_key_signature(vamp_zero::score_document const& score, vamp_zero::score_measure const& measure) -> std::string_view {
|
||||
if (!measure.key_signature.empty()) {
|
||||
return measure.key_signature;
|
||||
}
|
||||
if (!score.key_signature.empty()) {
|
||||
return score.key_signature;
|
||||
}
|
||||
return "C";
|
||||
}
|
||||
|
||||
auto effective_time_signature(vamp_zero::score_document const& score, vamp_zero::score_measure const* measure) -> std::string_view {
|
||||
if (measure != nullptr && !measure->time_signature.empty()) {
|
||||
return measure->time_signature;
|
||||
}
|
||||
if (!score.time_signature.empty()) {
|
||||
return score.time_signature;
|
||||
}
|
||||
return "4/4";
|
||||
}
|
||||
|
||||
auto draw_curve(luma::draw_context& context, luma::point start, luma::point control, luma::point end, std::float32_t thickness, luma::color color) -> void {
|
||||
constexpr auto segments = std::int32_t{10};
|
||||
auto previous = start;
|
||||
@@ -395,6 +604,142 @@ auto draw_staff(luma::draw_context& context, std::float32_t x, std::float32_t y,
|
||||
}
|
||||
}
|
||||
|
||||
auto normalized_clef(std::string_view clef) -> std::string {
|
||||
auto value = std::string{clef};
|
||||
for (auto& ch : value) {
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
ch = static_cast<std::char_t>(ch + ('a' - 'A'));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
auto draw_clef(luma::draw_context&, std::string_view clef, std::float32_t x, std::float32_t staff_top, std::float32_t spacing, luma::color ink) -> std::float32_t {
|
||||
auto name = normalized_clef(clef);
|
||||
auto codepoint = name == "bass" || name == "f" ? glyph_f_clef : glyph_g_clef;
|
||||
auto baseline_y = codepoint == glyph_f_clef ? staff_top + spacing : staff_top + spacing * 3.0F;
|
||||
draw_music_origin(codepoint, {.x = x, .y = baseline_y}, spacing, ink);
|
||||
return glyph_width(codepoint, spacing) + spacing * 0.95F;
|
||||
}
|
||||
|
||||
auto key_signature_pitch(std::int32_t count, std::size_t index) -> vamp_zero::score_pitch {
|
||||
constexpr auto sharp_positions = std::array<key_signature_position, 7>{{
|
||||
{.letter = 'f', .octave = 5},
|
||||
{.letter = 'c', .octave = 5},
|
||||
{.letter = 'g', .octave = 5},
|
||||
{.letter = 'd', .octave = 5},
|
||||
{.letter = 'a', .octave = 4},
|
||||
{.letter = 'e', .octave = 5},
|
||||
{.letter = 'b', .octave = 4},
|
||||
}};
|
||||
constexpr auto flat_positions = std::array<key_signature_position, 7>{{
|
||||
{.letter = 'b', .octave = 4},
|
||||
{.letter = 'e', .octave = 5},
|
||||
{.letter = 'a', .octave = 4},
|
||||
{.letter = 'd', .octave = 5},
|
||||
{.letter = 'g', .octave = 4},
|
||||
{.letter = 'c', .octave = 5},
|
||||
{.letter = 'f', .octave = 4},
|
||||
}};
|
||||
auto const position = count > 0 ? sharp_positions[index] : flat_positions[index];
|
||||
return {
|
||||
.letter = position.letter,
|
||||
.octave = position.octave,
|
||||
};
|
||||
}
|
||||
|
||||
auto draw_key_signature(luma::draw_context&, std::string_view key_signature, std::float32_t x, std::float32_t staff_top, std::float32_t spacing, luma::color ink) -> std::float32_t {
|
||||
auto count = key_signature_count(key_signature);
|
||||
if (count == 0) {
|
||||
return 0.0F;
|
||||
}
|
||||
auto const glyph = count > 0 ? glyph_accidental_sharp : glyph_accidental_flat;
|
||||
auto const step = glyph_width(glyph, spacing) * (count > 0 ? 0.72F : 0.86F);
|
||||
auto current_x = x;
|
||||
auto const total = static_cast<std::size_t>(std::abs(count));
|
||||
for (auto index = std::size_t{}; index < total; ++index) {
|
||||
auto pitch = key_signature_pitch(count, index);
|
||||
draw_music_origin(glyph, {.x = current_x, .y = pitch_y(pitch, staff_top, spacing)}, spacing, ink);
|
||||
current_x += step;
|
||||
}
|
||||
return current_x - x + spacing * 0.45F;
|
||||
}
|
||||
|
||||
auto time_signature_digit(std::char_t digit) -> std::optional<std::int32_t> {
|
||||
if (digit < '0' || digit > '9') {
|
||||
return std::nullopt;
|
||||
}
|
||||
return glyph_time_sig_0 + static_cast<std::int32_t>(digit - '0');
|
||||
}
|
||||
|
||||
auto time_signature_run_width(std::string_view text, std::float32_t spacing) -> std::float32_t {
|
||||
auto width = std::float32_t{};
|
||||
for (auto digit : text) {
|
||||
auto codepoint = time_signature_digit(digit);
|
||||
if (!codepoint) {
|
||||
continue;
|
||||
}
|
||||
if (width > 0.0F) {
|
||||
width += spacing * 0.16F;
|
||||
}
|
||||
width += glyph_width(*codepoint, spacing);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
auto draw_time_signature_run(std::string_view text, std::float32_t x, std::float32_t baseline_y, std::float32_t spacing, luma::color ink) -> void {
|
||||
auto current_x = x;
|
||||
for (auto digit : text) {
|
||||
auto codepoint = time_signature_digit(digit);
|
||||
if (!codepoint) {
|
||||
continue;
|
||||
}
|
||||
draw_music_origin(*codepoint, {.x = current_x, .y = baseline_y}, spacing, ink);
|
||||
current_x += glyph_width(*codepoint, spacing) + spacing * 0.16F;
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_time_signature(luma::draw_context&, std::string_view time_signature, std::float32_t x, std::float32_t staff_top, std::float32_t spacing, luma::color ink) -> std::float32_t {
|
||||
auto slash = time_signature.find('/');
|
||||
if (slash == std::string_view::npos) {
|
||||
return 0.0F;
|
||||
}
|
||||
auto numerator = time_signature.substr(0, slash);
|
||||
auto denominator = time_signature.substr(slash + 1U);
|
||||
auto numerator_width = time_signature_run_width(numerator, spacing);
|
||||
auto denominator_width = time_signature_run_width(denominator, spacing);
|
||||
auto width = std::max(numerator_width, denominator_width);
|
||||
if (width <= 0.0F) {
|
||||
return 0.0F;
|
||||
}
|
||||
draw_time_signature_run(numerator, x + (width - numerator_width) * 0.5F, staff_top + spacing, spacing, ink);
|
||||
draw_time_signature_run(denominator, x + (width - denominator_width) * 0.5F, staff_top + spacing * 3.0F, spacing, ink);
|
||||
return width + spacing * 0.95F;
|
||||
}
|
||||
|
||||
auto draw_system_preamble(
|
||||
luma::draw_context& context,
|
||||
vamp_zero::score_document const& score,
|
||||
vamp_zero::score_measure const* first_measure,
|
||||
std::float32_t x,
|
||||
std::float32_t staff_top,
|
||||
std::float32_t spacing,
|
||||
std::bool_t show_time_signature
|
||||
) -> std::float32_t {
|
||||
auto ink = vamp_zero::to_luma(vamp_style::paper_ink);
|
||||
auto current_x = x + spacing * 0.35F;
|
||||
current_x += draw_clef(context, score.clef, current_x, staff_top, spacing, ink);
|
||||
auto key_width = draw_key_signature(context, first_measure == nullptr ? score.key_signature : effective_key_signature(score, *first_measure), current_x, staff_top, spacing, ink);
|
||||
current_x += key_width;
|
||||
if (key_width == 0.0F) {
|
||||
current_x += spacing * 0.35F;
|
||||
}
|
||||
if (show_time_signature) {
|
||||
current_x += draw_time_signature(context, effective_time_signature(score, first_measure), current_x, staff_top, spacing, ink);
|
||||
}
|
||||
return current_x + spacing * 0.25F;
|
||||
}
|
||||
|
||||
auto draw_duration_dot(luma::draw_context& context, std::float32_t x, std::float32_t y, std::float32_t spacing, luma::color ink) -> void {
|
||||
static_cast<void>(context);
|
||||
draw_music_centered(glyph_augmentation_dot, x, y, spacing, ink);
|
||||
@@ -450,7 +795,7 @@ auto draw_rest(luma::draw_context& context, duration_shape duration, std::float3
|
||||
if (centered) {
|
||||
draw_music_centered(codepoint, x, center, spacing, ink);
|
||||
} else {
|
||||
draw_music_centered_at_origin_y(codepoint, x, origin_y, spacing, ink);
|
||||
draw_music_centered(codepoint, x, origin_y, spacing, ink);
|
||||
}
|
||||
if (duration.dots != 0) {
|
||||
draw_duration_dot(context, x + glyph_width(codepoint, spacing) * 0.5F + spacing, center, spacing, ink);
|
||||
@@ -475,12 +820,21 @@ auto notehead_codepoint(duration_shape duration, std::bool_t slash) -> std::int3
|
||||
|
||||
auto draw_notehead(duration_shape duration, std::bool_t slash, std::float32_t x, std::float32_t y, std::float32_t spacing, luma::color ink) -> notehead_instance {
|
||||
auto codepoint = notehead_codepoint(duration, slash);
|
||||
auto origin = origin_for_center(codepoint, x, y, spacing);
|
||||
auto origin = origin_for_centered_baseline(codepoint, x, y, spacing);
|
||||
draw_music_origin(codepoint, origin, spacing, ink);
|
||||
return {.center_y = y, .origin = origin};
|
||||
}
|
||||
|
||||
auto draw_note(luma::draw_context& context, vamp_zero::score_note const& note, std::float32_t x, std::float32_t staff_top, std::float32_t spacing, std::float32_t next_x, std::bool_t slash) -> void {
|
||||
auto draw_note(
|
||||
luma::draw_context& context,
|
||||
vamp_zero::score_note const& note,
|
||||
std::float32_t x,
|
||||
std::float32_t staff_top,
|
||||
std::float32_t spacing,
|
||||
std::float32_t next_x,
|
||||
std::bool_t slash,
|
||||
std::vector<vamp_zero::score_accidental_mark> const& accidentals
|
||||
) -> void {
|
||||
if (note.hidden) {
|
||||
return;
|
||||
}
|
||||
@@ -508,11 +862,13 @@ auto draw_note(luma::draw_context& context, vamp_zero::score_note const& note, s
|
||||
low_y = std::max(low_y, yy);
|
||||
high_y = std::min(high_y, yy);
|
||||
}
|
||||
for (auto const& pitch : note.pitches) {
|
||||
for (auto pitch_index = std::size_t{}; pitch_index < note.pitches.size(); ++pitch_index) {
|
||||
auto const& pitch = note.pitches[pitch_index];
|
||||
auto yy = std::clamp(pitch_y(pitch, staff_top, spacing), staff_top - spacing * 2.0F, bottom + spacing * 2.0F);
|
||||
draw_ledger_lines(context, x, yy, staff_top, spacing, ink);
|
||||
if (auto accidental = accidental_glyph(pitch.accidental)) {
|
||||
auto head_origin = origin_for_center(notehead_codepoint(duration, false), x, yy, spacing);
|
||||
auto mark = pitch_index < accidentals.size() ? accidentals[pitch_index] : vamp_zero::score_accidental_mark::NONE;
|
||||
if (auto accidental = accidental_glyph(mark)) {
|
||||
auto head_origin = origin_for_centered_baseline(notehead_codepoint(duration, false), x, yy, spacing);
|
||||
auto head_left = head_origin.x + glyph_box(notehead_codepoint(duration, false)).west * spacing;
|
||||
draw_music_left_of(*accidental, head_left - spacing * 0.65F, yy, spacing, ink);
|
||||
}
|
||||
@@ -524,7 +880,7 @@ auto draw_note(luma::draw_context& context, vamp_zero::score_note const& note, s
|
||||
if (has_stem(duration) && !heads.empty()) {
|
||||
auto stem_up = (low_y + high_y) * 0.5F >= center;
|
||||
if (stem_up) {
|
||||
auto base_origin = origin_for_center(notehead, x, low_y, spacing);
|
||||
auto base_origin = origin_for_centered_baseline(notehead, x, low_y, spacing);
|
||||
auto base = anchor_point(base_origin, stem_up_anchor(notehead), spacing);
|
||||
auto stem_top = std::min(high_y, center) - spacing * 3.6F;
|
||||
context.stroke_line(base, {.x = base.x, .y = stem_top}, 1.6F, ink);
|
||||
@@ -532,7 +888,7 @@ auto draw_note(luma::draw_context& context, vamp_zero::score_note const& note, s
|
||||
draw_eighth_flag(true, base.x, stem_top, spacing, ink);
|
||||
}
|
||||
} else {
|
||||
auto base_origin = origin_for_center(notehead, x, high_y, spacing);
|
||||
auto base_origin = origin_for_centered_baseline(notehead, x, high_y, spacing);
|
||||
auto base = anchor_point(base_origin, stem_down_anchor(notehead), spacing);
|
||||
auto stem_bottom = std::max(low_y, center) + spacing * 3.6F;
|
||||
context.stroke_line(base, {.x = base.x, .y = stem_bottom}, 1.6F, ink);
|
||||
@@ -543,7 +899,7 @@ auto draw_note(luma::draw_context& context, vamp_zero::score_note const& note, s
|
||||
}
|
||||
|
||||
if (duration.dots != 0) {
|
||||
auto dot_origin = origin_for_center(notehead, x, low_y, spacing);
|
||||
auto dot_origin = origin_for_centered_baseline(notehead, x, low_y, spacing);
|
||||
auto dot_x = dot_origin.x + glyph_box(notehead).east * spacing + spacing * 0.85F;
|
||||
draw_duration_dot(context, dot_x, dot_y_for(low_y, staff_top, spacing), spacing, ink);
|
||||
}
|
||||
@@ -558,22 +914,26 @@ auto note_x(std::float32_t start_x, std::float32_t width, std::int64_t total_tic
|
||||
return start_x + width * ratio;
|
||||
}
|
||||
|
||||
auto draw_measure(luma::draw_context& context, vamp_zero::score_measure const& measure, luma::rect bounds, std::size_t measure_number) -> void {
|
||||
constexpr auto spacing = std::float32_t{8.0F};
|
||||
auto staff_top = bounds.y + 52.0F;
|
||||
draw_text(context, bounds.x + 5.0F, bounds.y + 1.0F, std::to_string(measure_number), 1, vamp_style::muted, 42.0F);
|
||||
auto draw_measure(
|
||||
luma::draw_context& context,
|
||||
vamp_zero::score_document const& score,
|
||||
vamp_zero::score_measure const& measure,
|
||||
luma::rect bounds,
|
||||
std::size_t measure_number,
|
||||
std::float32_t staff_top,
|
||||
std::float32_t spacing
|
||||
) -> void {
|
||||
draw_text(context, bounds.x + spacing * 0.2F, bounds.y + spacing * 0.05F, std::to_string(measure_number), 1, vamp_style::muted, spacing * 4.0F);
|
||||
if (!measure.section_label.empty()) {
|
||||
draw_text(context, bounds.x + 28.0F, bounds.y - 2.0F, measure.section_label, 2, vamp_style::selection, bounds.width - 32.0F);
|
||||
}
|
||||
if (!measure.time_signature.empty() && measure_number == 1U) {
|
||||
draw_text(context, bounds.x + 8.0F, staff_top + 2.0F, measure.time_signature, 1, vamp_style::paper_ink, 42.0F);
|
||||
draw_text(context, bounds.x + spacing * 2.3F, bounds.y - spacing * 0.25F, measure.section_label, 2, vamp_style::selection, bounds.width - spacing * 2.7F);
|
||||
}
|
||||
|
||||
auto notation_x = bounds.x + (measure_number == 1U ? 43.0F : 24.0F);
|
||||
auto notation_width = std::max(24.0F, bounds.width - (notation_x - bounds.x) - 16.0F);
|
||||
auto notation_x = bounds.x + spacing * 1.4F;
|
||||
auto notation_width = std::max(spacing * 3.0F, bounds.width - (notation_x - bounds.x) - spacing);
|
||||
auto total_ticks = measure_ticks(measure);
|
||||
auto elapsed = std::int64_t{};
|
||||
auto previous_chord = std::string{};
|
||||
auto accidentals = make_accidental_state(effective_key_signature(score, measure));
|
||||
for (auto index = std::size_t{}; index < measure.notes.size(); ++index) {
|
||||
auto const& note = measure.notes[index];
|
||||
auto duration_ticks = spacing_ticks(note);
|
||||
@@ -581,12 +941,14 @@ auto draw_measure(luma::draw_context& context, vamp_zero::score_measure const& m
|
||||
auto next_duration = index + 1U < measure.notes.size() ? spacing_ticks(measure.notes[index + 1U]) : duration_ticks;
|
||||
auto next_x = note_x(notation_x, notation_width, total_ticks, elapsed + duration_ticks, next_duration);
|
||||
if (!note.hidden && !note.chord_symbol.empty() && note.chord_symbol != previous_chord) {
|
||||
draw_text(context, x - 18.0F, bounds.y + 24.0F, note.chord_symbol, 2, vamp_style::paper_ink, std::min(92.0F, bounds.width - (x - bounds.x)));
|
||||
draw_text(context, x - spacing * 1.5F, bounds.y + spacing * 2.0F, note.chord_symbol, 2, vamp_style::paper_ink, std::min(spacing * 10.0F, bounds.width - (x - bounds.x)));
|
||||
previous_chord = note.chord_symbol;
|
||||
}
|
||||
draw_note(context, note, x, staff_top, spacing, next_x, note.notehead == "slash");
|
||||
auto const slash = note.notehead == "slash";
|
||||
auto accidental_marks = notated_accidentals_for_note(accidentals, note, slash);
|
||||
draw_note(context, note, x, staff_top, spacing, next_x, slash, accidental_marks);
|
||||
if (!note.hidden && !note.lyric.empty()) {
|
||||
draw_text(context, x - 12.0F, staff_top + 46.0F, note.lyric, 1, vamp_style::muted, 66.0F);
|
||||
draw_text(context, x - spacing, staff_top + spacing * 5.4F, note.lyric, 1, vamp_style::muted, spacing * 8.0F);
|
||||
}
|
||||
elapsed += duration_ticks;
|
||||
}
|
||||
@@ -594,11 +956,22 @@ auto draw_measure(luma::draw_context& context, vamp_zero::score_measure const& m
|
||||
|
||||
}
|
||||
|
||||
auto vamp_zero::notated_accidentals_for_measure(vamp_zero::score_measure const& measure, std::string_view inherited_key_signature) -> std::vector<vamp_zero::score_accidental_mark> {
|
||||
auto state = make_accidental_state(measure.key_signature.empty() ? inherited_key_signature : measure.key_signature);
|
||||
auto accidentals = std::vector<vamp_zero::score_accidental_mark>{};
|
||||
for (auto const& note : measure.notes) {
|
||||
auto marks = notated_accidentals_for_note(state, note, note.notehead == "slash");
|
||||
accidentals.insert(accidentals.end(), marks.begin(), marks.end());
|
||||
}
|
||||
return accidentals;
|
||||
}
|
||||
|
||||
auto vamp_zero::draw_score_page(luma::draw_context& context, luma::rect bounds, vamp_zero::score_document const& score, std::size_t page_index) -> void {
|
||||
context.fill_rect(bounds, vamp_zero::to_luma(vamp_style::paper));
|
||||
context.stroke_rect(bounds, 2.0F, vamp_zero::to_luma(vamp_style::rule));
|
||||
context.push_clip(bounds);
|
||||
|
||||
constexpr auto spacing = std::float32_t{12.0F};
|
||||
auto margin = std::float32_t{28.0F};
|
||||
auto header_y = bounds.y + 16.0F;
|
||||
draw_text(context, bounds.x + margin, header_y, score.title, 3, vamp_style::paper_ink, bounds.width * 0.55F);
|
||||
@@ -610,22 +983,26 @@ auto vamp_zero::draw_score_page(luma::draw_context& context, luma::rect bounds,
|
||||
auto content_y = bounds.y + 84.0F;
|
||||
auto content_width = bounds.width - margin * 2.0F;
|
||||
auto system_height = (bounds.height - 108.0F) / static_cast<std::float32_t>(vamp_zero::score_systems_per_page);
|
||||
auto measure_width = content_width / static_cast<std::float32_t>(vamp_zero::score_measures_per_system);
|
||||
auto first_measure = page_index * vamp_zero::score_measures_per_page;
|
||||
|
||||
for (auto system = std::size_t{}; system < vamp_zero::score_systems_per_page; ++system) {
|
||||
auto system_y = content_y + static_cast<std::float32_t>(system) * system_height;
|
||||
auto staff_top = system_y + 52.0F;
|
||||
draw_staff(context, content_x, staff_top, content_width, 8.0F);
|
||||
auto staff_top = system_y + spacing * 4.25F;
|
||||
auto system_first_measure = first_measure + system * vamp_zero::score_measures_per_system;
|
||||
auto const* first_system_measure = system_first_measure < score.measures.size() ? &score.measures[system_first_measure] : nullptr;
|
||||
draw_staff(context, content_x, staff_top, content_width, spacing);
|
||||
auto measure_start_x = draw_system_preamble(context, score, first_system_measure, content_x, staff_top, spacing, page_index == 0U && system == 0U);
|
||||
auto measure_area_width = std::max(spacing * 12.0F, content_x + content_width - measure_start_x);
|
||||
auto measure_width = measure_area_width / static_cast<std::float32_t>(vamp_zero::score_measures_per_system);
|
||||
for (auto measure = std::size_t{}; measure < vamp_zero::score_measures_per_system; ++measure) {
|
||||
auto measure_index = first_measure + system * vamp_zero::score_measures_per_system + measure;
|
||||
auto x = content_x + static_cast<std::float32_t>(measure) * measure_width;
|
||||
context.stroke_line({.x = x, .y = staff_top}, {.x = x, .y = staff_top + 32.0F}, 1.2F, vamp_zero::to_luma(vamp_style::paper_ink));
|
||||
auto measure_index = system_first_measure + measure;
|
||||
auto x = measure_start_x + static_cast<std::float32_t>(measure) * measure_width;
|
||||
context.stroke_line({.x = x, .y = staff_top}, {.x = x, .y = staff_top + spacing * 4.0F}, 1.2F, vamp_zero::to_luma(vamp_style::paper_ink));
|
||||
if (measure_index < score.measures.size()) {
|
||||
draw_measure(context, score.measures[measure_index], {.x = x, .y = system_y, .width = measure_width, .height = system_height}, measure_index + 1U);
|
||||
draw_measure(context, score, score.measures[measure_index], {.x = x, .y = system_y, .width = measure_width, .height = system_height}, measure_index + 1U, staff_top, spacing);
|
||||
}
|
||||
}
|
||||
context.stroke_line({.x = content_x + content_width, .y = staff_top}, {.x = content_x + content_width, .y = staff_top + 32.0F}, 2.0F, vamp_zero::to_luma(vamp_style::paper_ink));
|
||||
context.stroke_line({.x = content_x + content_width, .y = staff_top}, {.x = content_x + content_width, .y = staff_top + spacing * 4.0F}, 2.0F, vamp_zero::to_luma(vamp_style::paper_ink));
|
||||
}
|
||||
|
||||
context.pop_clip();
|
||||
|
||||
Reference in New Issue
Block a user