From b6f69f033eea820a77740c9dcdef849cf783f8a5 Mon Sep 17 00:00:00 2001 From: Brandon Taylor Date: Fri, 8 Nov 2024 18:00:07 +0000 Subject: [PATCH] abstract note --- src/abstract_rational/AbstractRational.cpp | 20 +- src/abstract_rational/AbstractRational.hpp | 7 +- src/abstract_rational/interval/Interval.cpp | 11 - src/abstract_rational/interval/Interval.hpp | 1 - src/abstract_rational/rational/Rational.cpp | 9 - src/abstract_rational/rational/Rational.hpp | 2 - src/named/Named.cpp | 14 -- src/named/Named.hpp | 55 +---- .../PercussionInstrument.cpp | 13 ++ .../PercussionInstrument.hpp | 5 + src/named/program/instrument/Instrument.cpp | 13 ++ src/named/program/instrument/Instrument.hpp | 5 + .../program/percussion_set/PercussionSet.cpp | 13 ++ .../program/percussion_set/PercussionSet.hpp | 5 + src/other/justly.cpp | 9 +- src/other/other.cpp | 49 ----- src/other/other.hpp | 24 --- src/row/CMakeLists.txt | 3 +- src/row/Row.cpp | 9 +- src/row/Row.hpp | 51 +++-- src/row/RowsModel.hpp | 26 +-- src/row/chord/Chord.cpp | 58 +++--- src/row/chord/Chord.hpp | 5 +- src/row/note/CMakeLists.txt | 7 + src/row/note/Note.cpp | 11 + src/row/note/Note.hpp | 54 +++++ .../{ => note}/pitched_note/CMakeLists.txt | 0 .../{ => note}/pitched_note/PitchedNote.cpp | 62 ++++-- .../{ => note}/pitched_note/PitchedNote.hpp | 27 ++- .../pitched_note/PitchedNotesModel.cpp | 6 +- .../pitched_note/PitchedNotesModel.hpp | 7 +- .../{ => note}/unpitched_note/CMakeLists.txt | 0 .../unpitched_note/UnpitchedNote.cpp | 61 +++--- .../unpitched_note/UnpitchedNote.hpp | 26 ++- src/song/EditChildrenOrBack.cpp | 2 +- src/song/Player.cpp | 189 ++---------------- src/song/Player.hpp | 102 +++++++++- src/song/SetStartingDouble.cpp | 16 ++ src/song/Song.cpp | 17 -- src/song/Song.hpp | 5 - src/song/SongEditor.cpp | 142 ++++++++++++- src/song/SongEditor.hpp | 4 +- 42 files changed, 596 insertions(+), 549 deletions(-) create mode 100644 src/row/note/CMakeLists.txt create mode 100644 src/row/note/Note.cpp create mode 100644 src/row/note/Note.hpp rename src/row/{ => note}/pitched_note/CMakeLists.txt (100%) rename src/row/{ => note}/pitched_note/PitchedNote.cpp (72%) rename src/row/{ => note}/pitched_note/PitchedNote.hpp (50%) rename src/row/{ => note}/pitched_note/PitchedNotesModel.cpp (70%) rename src/row/{ => note}/pitched_note/PitchedNotesModel.hpp (70%) rename src/row/{ => note}/unpitched_note/CMakeLists.txt (100%) rename src/row/{ => note}/unpitched_note/UnpitchedNote.cpp (75%) rename src/row/{ => note}/unpitched_note/UnpitchedNote.hpp (51%) diff --git a/src/abstract_rational/AbstractRational.cpp b/src/abstract_rational/AbstractRational.cpp index f1f125a5..666e63bf 100644 --- a/src/abstract_rational/AbstractRational.cpp +++ b/src/abstract_rational/AbstractRational.cpp @@ -4,8 +4,6 @@ #include #include -#include "other/other.hpp" - AbstractRational::AbstractRational(int numerator_input, int denominator_input) : numerator(numerator_input), denominator(denominator_input){}; @@ -32,17 +30,6 @@ void AbstractRational::to_json(nlohmann::json &json_rational) const { add_int_to_json(json_rational, "denominator", denominator, 1); } -auto get_numerator_schema() -> nlohmann::json { - return get_number_schema("integer", "numerator", 1, - MAX_RATIONAL_NUMERATOR); -} - -auto get_denominator_schema() -> nlohmann::json { - return get_number_schema("integer", "denominator", 1, - MAX_RATIONAL_DENOMINATOR); - -} - void add_abstract_rational_to_json(nlohmann::json &json_row, const AbstractRational &rational, const char *column_name) { @@ -52,3 +39,10 @@ void add_abstract_rational_to_json(nlohmann::json &json_row, json_row[column_name] = std::move(json_rational); } } + +void add_int_to_json(nlohmann::json &json_object, const char *field_name, + int value, int default_value) { + if (value != default_value) { + json_object[field_name] = value; + } +} \ No newline at end of file diff --git a/src/abstract_rational/AbstractRational.hpp b/src/abstract_rational/AbstractRational.hpp index 8122eae2..de3a599c 100644 --- a/src/abstract_rational/AbstractRational.hpp +++ b/src/abstract_rational/AbstractRational.hpp @@ -23,10 +23,6 @@ struct AbstractRational { virtual void to_json(nlohmann::json &json_rational) const; }; -[[nodiscard]] auto get_numerator_schema() -> nlohmann::json; - -[[nodiscard]] auto get_denominator_schema() -> nlohmann::json; - void add_abstract_rational_to_json(nlohmann::json &json_row, const AbstractRational &rational, const char *column_name); @@ -39,3 +35,6 @@ auto json_field_to_abstract_rational(const nlohmann::json &json_row, } return {}; } + +void add_int_to_json(nlohmann::json &json_object, const char *field_name, + int value, int default_value); diff --git a/src/abstract_rational/interval/Interval.cpp b/src/abstract_rational/interval/Interval.cpp index e87cf842..78716ef1 100644 --- a/src/abstract_rational/interval/Interval.cpp +++ b/src/abstract_rational/interval/Interval.cpp @@ -2,8 +2,6 @@ #include -#include "other/other.hpp" - static const auto OCTAVE_RATIO = 2.0; Interval::Interval(const nlohmann::json &json_rational) @@ -26,15 +24,6 @@ auto Interval::to_double() const -> double { return AbstractRational::to_double() * pow(OCTAVE_RATIO, octave); } -auto get_interval_schema() -> nlohmann::json { - return get_object_schema( - "an interval", - nlohmann::json({{"numerator", get_numerator_schema()}, - {"denominator", get_denominator_schema()}, - {"octave", get_number_schema("integer", "octave", - -MAX_OCTAVE, MAX_OCTAVE)}})); -} - void Interval::to_json(nlohmann::json &json_interval) const { AbstractRational::to_json(json_interval); add_int_to_json(json_interval, "octave", octave, 0); diff --git a/src/abstract_rational/interval/Interval.hpp b/src/abstract_rational/interval/Interval.hpp index ac25cbbb..da59eeb0 100644 --- a/src/abstract_rational/interval/Interval.hpp +++ b/src/abstract_rational/interval/Interval.hpp @@ -23,4 +23,3 @@ struct Interval : public AbstractRational { Q_DECLARE_METATYPE(Interval); -[[nodiscard]] auto get_interval_schema() -> nlohmann::json; diff --git a/src/abstract_rational/rational/Rational.cpp b/src/abstract_rational/rational/Rational.cpp index 4dac900e..c047ca75 100644 --- a/src/abstract_rational/rational/Rational.cpp +++ b/src/abstract_rational/rational/Rational.cpp @@ -2,8 +2,6 @@ #include -#include "other/other.hpp" - Rational::Rational(const nlohmann::json &json_rational) : AbstractRational(json_rational) {} @@ -15,10 +13,3 @@ auto Rational::operator==(const Rational &other_rational) const -> bool { denominator == other_rational.denominator; } -auto get_rational_schema(const char *description) -> nlohmann::json { - return get_object_schema( - description, - nlohmann::json( - {{"numerator", get_numerator_schema()}, - {"denominator", get_denominator_schema()}})); -} diff --git a/src/abstract_rational/rational/Rational.hpp b/src/abstract_rational/rational/Rational.hpp index a470487e..f66135d5 100644 --- a/src/abstract_rational/rational/Rational.hpp +++ b/src/abstract_rational/rational/Rational.hpp @@ -16,5 +16,3 @@ struct Rational : public AbstractRational { Q_DECLARE_METATYPE(Rational); -[[nodiscard]] auto -get_rational_schema(const char *description) -> nlohmann::json; diff --git a/src/named/Named.cpp b/src/named/Named.cpp index 15c35b8f..cdd8f6d5 100644 --- a/src/named/Named.cpp +++ b/src/named/Named.cpp @@ -3,17 +3,3 @@ Named::Named(const char* name_input) : name(name_input) { } - -auto get_name_or_empty(const Named *named_pointer) -> QString { - if (named_pointer == nullptr) { - return ""; - } - return named_pointer->name; -} - -void add_named_to_json(nlohmann::json &json_row, const Named *named_pointer, - const char *column_name) { - if (named_pointer != nullptr) { - json_row[column_name] = named_pointer->name.toStdString(); - } -} \ No newline at end of file diff --git a/src/named/Named.hpp b/src/named/Named.hpp index 4c4ce0cc..1c28fa16 100644 --- a/src/named/Named.hpp +++ b/src/named/Named.hpp @@ -1,17 +1,8 @@ #pragma once -#include -#include #include #include #include -#include -#include -#include -#include -#include - -class QWidget; // a subnamed should have the following method: // static auto SubNamed::get_all_nameds() -> const QList&; @@ -20,10 +11,8 @@ struct Named { explicit Named(const char *name_input); }; -[[nodiscard]] auto get_name_or_empty(const Named *named_pointer) -> QString; - template SubNamed> -[[nodiscard]] static auto get_by_name(const QString &name) -> const SubNamed & { +[[nodiscard]] static auto get_by_name(const QString& name) -> const SubNamed & { const auto &all_nameds = SubNamed::get_all_nameds(); const auto named_pointer = std::find_if(all_nameds.cbegin(), all_nameds.cend(), @@ -32,45 +21,3 @@ template SubNamed> return *named_pointer; } -template SubNamed> -auto substitute_named_for(QWidget &parent, const SubNamed *sub_named_pointer, - const SubNamed *current_sub_named_pointer, - const char *default_one, const char *error_type, - const QString &message) -> const SubNamed & { - if (sub_named_pointer == nullptr) { - sub_named_pointer = current_sub_named_pointer; - }; - if (sub_named_pointer == nullptr) { - QMessageBox::warning(&parent, QObject::tr(error_type), message); - sub_named_pointer = &get_by_name(default_one); - } - return *sub_named_pointer; -} - -template SubNamed> -[[nodiscard]] auto -json_field_to_named_pointer(const nlohmann::json &json_row, - const char *field_name) -> const SubNamed * { - if (json_row.contains(field_name)) { - const auto &json_named = json_row[field_name]; - Q_ASSERT(json_named.is_string()); - return &get_by_name( - QString::fromStdString(json_named.get())); - }; - return nullptr; -} - -void add_named_to_json(nlohmann::json &json_row, const Named *named_pointer, - const char *column_name); - -template SubNamed> -auto get_named_schema(const char *description) -> nlohmann::json { - std::vector names; - const auto &all_nameds = SubNamed::get_all_nameds(); - std::transform(all_nameds.cbegin(), all_nameds.cend(), - std::back_inserter(names), - [](const SubNamed &item) { return item.name.toStdString(); }); - return nlohmann::json({{"type", "string"}, - {"description", description}, - {"enum", std::move(names)}}); -}; diff --git a/src/named/percussion_instrument/PercussionInstrument.cpp b/src/named/percussion_instrument/PercussionInstrument.cpp index edb205d9..bee06290 100644 --- a/src/named/percussion_instrument/PercussionInstrument.cpp +++ b/src/named/percussion_instrument/PercussionInstrument.cpp @@ -49,3 +49,16 @@ auto PercussionInstrument::get_all_nameds() }); return all_percussions; } + +auto PercussionInstrument::get_field_name() -> const char* { + return "percussion_instrument"; +}; +auto PercussionInstrument::get_type_name() -> const char* { + return "percussion instrument"; +}; +auto PercussionInstrument::get_missing_error() -> const char* { + return "Percussion instrument error"; +}; +auto PercussionInstrument::get_default() -> const char* { + return "Tambourine"; +}; \ No newline at end of file diff --git a/src/named/percussion_instrument/PercussionInstrument.hpp b/src/named/percussion_instrument/PercussionInstrument.hpp index 3794cbda..2b490a01 100644 --- a/src/named/percussion_instrument/PercussionInstrument.hpp +++ b/src/named/percussion_instrument/PercussionInstrument.hpp @@ -12,6 +12,11 @@ struct PercussionInstrument : public Named { PercussionInstrument(const char* name, short midi_number); [[nodiscard]] static auto get_all_nameds() -> const QList &; + + [[nodiscard]] static auto get_field_name() -> const char*; + [[nodiscard]] static auto get_type_name() -> const char*; + [[nodiscard]] static auto get_missing_error() -> const char*; + [[nodiscard]] static auto get_default() -> const char*; }; Q_DECLARE_METATYPE(const PercussionInstrument *); diff --git a/src/named/program/instrument/Instrument.cpp b/src/named/program/instrument/Instrument.cpp index e55a3b61..d75838ac 100644 --- a/src/named/program/instrument/Instrument.cpp +++ b/src/named/program/instrument/Instrument.cpp @@ -7,3 +7,16 @@ auto Instrument::get_all_nameds() -> const QList & { static const auto all_instruments = get_programs(false); return all_instruments; } + +auto Instrument::get_field_name() -> const char* { + return "instrument"; +}; +auto Instrument::get_type_name() -> const char* { + return "instrument"; +}; +auto Instrument::get_missing_error() -> const char* { + return "Instrument error"; +}; +auto Instrument::get_default() -> const char* { + return "Marimba"; +}; diff --git a/src/named/program/instrument/Instrument.hpp b/src/named/program/instrument/Instrument.hpp index 7aa92097..d4a85567 100644 --- a/src/named/program/instrument/Instrument.hpp +++ b/src/named/program/instrument/Instrument.hpp @@ -10,6 +10,11 @@ template class QList; struct Instrument : public Program { Instrument(const char* name, short bank_number, short preset_number); [[nodiscard]] static auto get_all_nameds() -> const QList &; + + [[nodiscard]] static auto get_field_name() -> const char*; + [[nodiscard]] static auto get_type_name() -> const char*; + [[nodiscard]] static auto get_missing_error() -> const char*; + [[nodiscard]] static auto get_default() -> const char*; }; Q_DECLARE_METATYPE(const Instrument *); diff --git a/src/named/program/percussion_set/PercussionSet.cpp b/src/named/program/percussion_set/PercussionSet.cpp index 6895c90d..ec889993 100644 --- a/src/named/program/percussion_set/PercussionSet.cpp +++ b/src/named/program/percussion_set/PercussionSet.cpp @@ -8,3 +8,16 @@ auto PercussionSet::get_all_nameds() -> const QList & { static const auto all_percussion_sets = get_programs(true); return all_percussion_sets; } + +auto PercussionSet::get_field_name() -> const char* { + return "percussion_set"; +}; +auto PercussionSet::get_type_name() -> const char* { + return "percussion set"; +}; +auto PercussionSet::get_missing_error() -> const char* { + return "Percussion set error"; +}; +auto PercussionSet::get_default() -> const char* { + return "Standard"; +}; diff --git a/src/named/program/percussion_set/PercussionSet.hpp b/src/named/program/percussion_set/PercussionSet.hpp index 64c4b18e..b4a412cf 100644 --- a/src/named/program/percussion_set/PercussionSet.hpp +++ b/src/named/program/percussion_set/PercussionSet.hpp @@ -10,6 +10,11 @@ template class QList; struct PercussionSet : public Program { PercussionSet(const char* name, short bank_number, short preset_number); [[nodiscard]] static auto get_all_nameds() -> const QList &; + + [[nodiscard]] static auto get_field_name() -> const char*; + [[nodiscard]] static auto get_type_name() -> const char*; + [[nodiscard]] static auto get_missing_error() -> const char*; + [[nodiscard]] static auto get_default() -> const char*; }; Q_DECLARE_METATYPE(const PercussionSet *); diff --git a/src/other/justly.cpp b/src/other/justly.cpp index 2121258d..5687308a 100644 --- a/src/other/justly.cpp +++ b/src/other/justly.cpp @@ -39,11 +39,18 @@ #include "other/other.hpp" #include "row/RowsModel.hpp" #include "row/chord/ChordsModel.hpp" -#include "row/pitched_note/PitchedNotesModel.hpp" +#include "row/note/pitched_note/PitchedNotesModel.hpp" #include "song/Player.hpp" #include "song/Song.hpp" #include "song/SongEditor.hpp" +static auto get_name_or_empty(const Named *named_pointer) -> QString { + if (named_pointer == nullptr) { + return ""; + } + return named_pointer->name; +} + void set_up() { QApplication::setApplicationDisplayName("Justly"); diff --git a/src/other/other.cpp b/src/other/other.cpp index dcb40ec6..6eff59fc 100644 --- a/src/other/other.cpp +++ b/src/other/other.cpp @@ -1,55 +1,6 @@ #include "other/other.hpp" -#include -#include #include -#include - -auto get_words_schema() -> nlohmann::json { - return nlohmann::json({{"type", "string"}, {"description", "the words"}}); -} - -auto get_number_schema(const char *type, const char *description, int minimum, - int maximum) -> nlohmann::json { - return nlohmann::json({{"type", type}, - {"description", description}, - {"minimum", minimum}, - {"maximum", maximum}}); -} - -auto get_array_schema(const char *description, - const nlohmann::json &item_json) -> nlohmann::json { - return nlohmann::json( - {{"type", "array"}, {"description", description}, {"items", item_json}}); -}; - -auto get_object_schema(const char *description, - const nlohmann::json &properties_json) - -> nlohmann::json { - return nlohmann::json({{"type", "object"}, - {"description", description}, - {"properties", properties_json}}); -}; - -void add_int_to_json(nlohmann::json &json_object, const char *field_name, - int value, int default_value) { - if (value != default_value) { - json_object[field_name] = value; - } -} - -void add_words_to_json(nlohmann::json &json_row, const QString &words) { - if (!words.isEmpty()) { - json_row["words"] = words.toStdString().c_str(); - } -} - - -void add_note_location(QTextStream &stream, int chord_number, int note_number, - const char *note_type) { - stream << QObject::tr(" for chord ") << chord_number + 1 << QObject::tr(", ") - << QObject::tr(note_type) << QObject::tr(" note ") << note_number + 1; -} void prevent_compression(QWidget &widget) { widget.setMinimumSize(widget.minimumSizeHint()); diff --git a/src/other/other.hpp b/src/other/other.hpp index 25337339..8ec76847 100644 --- a/src/other/other.hpp +++ b/src/other/other.hpp @@ -1,12 +1,8 @@ #pragma once -#include #include #include -#include - -class QTextStream; class QWidget; template @@ -28,24 +24,4 @@ auto variant_to(const QVariant &variant) -> SubType { return variant.value(); } -[[nodiscard]] auto get_words_schema() -> nlohmann::json; - -[[nodiscard]] auto get_number_schema(const char *type, const char *description, - int minimum, - int maximum) -> nlohmann::json; - -[[nodiscard]] auto -get_array_schema(const char *description, - const nlohmann::json &item_json) -> nlohmann::json; -[[nodiscard]] auto -get_object_schema(const char *description, - const nlohmann::json &properties_json) -> nlohmann::json; - -void add_int_to_json(nlohmann::json &json_object, const char *field_name, - int value, int default_value); -void add_words_to_json(nlohmann::json &json_row, const QString &words); - -void add_note_location(QTextStream &stream, int chord_number, int note_number, - const char *note_type); - void prevent_compression(QWidget &widget); diff --git a/src/row/CMakeLists.txt b/src/row/CMakeLists.txt index 4b835f3e..aa92173d 100644 --- a/src/row/CMakeLists.txt +++ b/src/row/CMakeLists.txt @@ -1,6 +1,5 @@ add_subdirectory(chord) -add_subdirectory(pitched_note) -add_subdirectory(unpitched_note) +add_subdirectory(note) target_sources(JustlyLibrary PRIVATE "InsertRow.hpp" diff --git a/src/row/Row.cpp b/src/row/Row.cpp index 25ff10ee..d7c4b89a 100644 --- a/src/row/Row.cpp +++ b/src/row/Row.cpp @@ -1,7 +1,6 @@ #include "row/Row.hpp" #include "abstract_rational/AbstractRational.hpp" -#include "other/other.hpp" static auto json_field_to_words(const nlohmann::json &json_row) -> QString { if (json_row.contains("words")) { @@ -27,4 +26,10 @@ Row::Row(const nlohmann::json &json_chord) auto Row::is_column_editable(int /*column_number*/) -> bool { return true; -}; \ No newline at end of file +} + +void add_words_to_json(nlohmann::json &json_row, const QString &words) { + if (!words.isEmpty()) { + json_row["words"] = words.toStdString().c_str(); + } +} diff --git a/src/row/Row.hpp b/src/row/Row.hpp index 55dc2631..65375b63 100644 --- a/src/row/Row.hpp +++ b/src/row/Row.hpp @@ -1,18 +1,23 @@ #pragma once -#include #include #include +#include #include #include #include +#include #include #include "abstract_rational/rational/Rational.hpp" +#include "named/Named.hpp" + +template class QList; // In addition to the following, a sub-row should have the following methods: // SubRow::SubRow(const nlohmann::json& json_row); -// [[nodiscard]] static auto is_column_editable(int column_number) -> bool; (optional) +// [[nodiscard]] static auto is_column_editable(int column_number) -> bool; +// (optional) // [[nodiscard]] static auto get_column_name(int column_number) -> QString; // [[nodiscard]] static auto get_number_of_columns() -> int; // void SubRow::copy_columns_from(const SubRow &template_row, int left_column, @@ -50,21 +55,8 @@ void json_to_rows(QList &rows, const nlohmann::json &json_rows) { } template SubRow> -auto json_field_to_rows(nlohmann::json json_object, - const char *field) -> QList { - if (json_object.contains(field)) { - QList rows; - const auto &json_rows = json_object[field]; - json_to_rows(rows, json_rows); - return rows; - } - return {}; -} - -template SubRow> -static void add_rows_to_json(nlohmann::json &json_chord, - const QList &rows, - const char *field_name) { +void add_rows_to_json(nlohmann::json &json_chord, const QList &rows, + const char *field_name) { if (!rows.empty()) { nlohmann::json json_rows; std::transform( @@ -72,4 +64,27 @@ static void add_rows_to_json(nlohmann::json &json_chord, [](const SubRow &row) -> nlohmann::json { return row.to_json(); }); json_chord[field_name] = std::move(json_rows); } -} \ No newline at end of file +} + +void add_words_to_json(nlohmann::json &json_row, const QString &words); + +template SubNamed> +void add_named_to_json(nlohmann::json &json_row, + const SubNamed *named_pointer) { + if (named_pointer != nullptr) { + json_row[SubNamed::get_field_name()] = named_pointer->name.toStdString(); + } +} + +template SubNamed> +[[nodiscard]] auto json_field_to_named_pointer(const nlohmann::json &json_row) + -> const SubNamed * { + const char *field_name = SubNamed::get_field_name(); + if (json_row.contains(field_name)) { + const auto &json_named = json_row[field_name]; + Q_ASSERT(json_named.is_string()); + return &get_by_name( + QString::fromStdString(json_named.get())); + }; + return nullptr; +} diff --git a/src/row/RowsModel.hpp b/src/row/RowsModel.hpp index 1f66b2db..378723fe 100644 --- a/src/row/RowsModel.hpp +++ b/src/row/RowsModel.hpp @@ -28,7 +28,7 @@ struct RowsModel : public QAbstractTableModel { [[nodiscard]] auto rowCount(const QModelIndex & /*parent_index*/) const -> int override { - return static_cast(get_const_rows(*this).size()); + return static_cast(get_const_reference(rows_pointer).size()); } [[nodiscard]] auto @@ -83,7 +83,7 @@ struct RowsModel : public QAbstractTableModel { return {}; } - return get_const_rows(*this).at(row_number).get_data(index.column()); + return get_const_reference(rows_pointer).at(row_number).get_data(index.column()); } [[nodiscard]] auto setData(const QModelIndex &index, @@ -101,7 +101,7 @@ struct RowsModel : public QAbstractTableModel { // don't inline these functions because they use protected methods void set_cell(int row_number, int column_number, const QVariant &new_value) { - get_rows(*this)[row_number].set_data(column_number, new_value); + get_reference(rows_pointer)[row_number].set_data(column_number, new_value); dataChanged(index(row_number, column_number), index(row_number, column_number), {Qt::DisplayRole, Qt::EditRole}); @@ -109,7 +109,7 @@ struct RowsModel : public QAbstractTableModel { void set_cells(int first_row_number, const QList &new_rows, int left_column, int right_column) { - auto &rows = get_rows(*this); + auto &rows = get_reference(rows_pointer); auto number_of_new_rows = new_rows.size(); for (auto replace_number = 0; replace_number < number_of_new_rows; replace_number++) { @@ -122,7 +122,7 @@ struct RowsModel : public QAbstractTableModel { } void insert_json_rows(int first_row_number, const nlohmann::json &json_rows) { - auto &rows = get_rows(*this); + auto &rows = get_reference(rows_pointer); beginInsertRows(QModelIndex(), first_row_number, first_row_number + static_cast(json_rows.size()) - 1); json_to_rows(rows, json_rows); @@ -130,7 +130,7 @@ struct RowsModel : public QAbstractTableModel { } void insert_rows(int first_row_number, const QList &new_rows) { - auto &rows = get_rows(*this); + auto &rows = get_reference(rows_pointer); beginInsertRows(QModelIndex(), first_row_number, first_row_number + new_rows.size() - 1); std::copy(new_rows.cbegin(), new_rows.cend(), @@ -140,13 +140,13 @@ struct RowsModel : public QAbstractTableModel { void insert_row(int row_number, const SubRow &new_row) { beginInsertRows(QModelIndex(), row_number, row_number); - auto &rows = get_rows(*this); + auto &rows = get_reference(rows_pointer); rows.insert(rows.begin() + row_number, new_row); endInsertRows(); } void remove_rows(int first_row_number, int number_of_rows) { - auto &rows = get_rows(*this); + auto &rows = get_reference(rows_pointer); beginRemoveRows(QModelIndex(), first_row_number, first_row_number + number_of_rows - 1); rows.erase(rows.begin() + first_row_number, @@ -161,13 +161,3 @@ struct RowsModel : public QAbstractTableModel { } }; -template SubRow> -[[nodiscard]] auto get_rows(RowsModel &rows_model) -> QList & { - return get_reference(rows_model.rows_pointer); -}; - -template SubRow> -[[nodiscard]] auto -get_const_rows(const RowsModel &rows_model) -> const QList & { - return get_const_reference(rows_model.rows_pointer); -}; diff --git a/src/row/chord/Chord.cpp b/src/row/chord/Chord.cpp index 61494e28..d72e046c 100644 --- a/src/row/chord/Chord.cpp +++ b/src/row/chord/Chord.cpp @@ -2,29 +2,41 @@ #include #include +#include #include #include "abstract_rational/AbstractRational.hpp" #include "abstract_rational/interval/Interval.hpp" #include "abstract_rational/rational/Rational.hpp" #include "justly/ChordColumn.hpp" -#include "named/Named.hpp" #include "named/percussion_instrument/PercussionInstrument.hpp" #include "named/program/instrument/Instrument.hpp" #include "named/program/percussion_set/PercussionSet.hpp" #include "other/other.hpp" -#include "row/pitched_note/PitchedNote.hpp" -#include "row/unpitched_note/UnpitchedNote.hpp" +#include "row/note/pitched_note/PitchedNote.hpp" +#include "row/note/unpitched_note/UnpitchedNote.hpp" + +template SubRow> +static auto json_field_to_rows(nlohmann::json json_object, + const char *field) -> QList { + if (json_object.contains(field)) { + QList rows; + const auto &json_rows = json_object[field]; + json_to_rows(rows, json_rows); + return rows; + } + return {}; +} Chord::Chord(const nlohmann::json &json_chord) : Row(json_chord), instrument_pointer( - json_field_to_named_pointer(json_chord, "instrument")), + json_field_to_named_pointer(json_chord)), percussion_set_pointer(json_field_to_named_pointer( - json_chord, "percussion_set")), + json_chord)), percussion_instrument_pointer( json_field_to_named_pointer( - json_chord, "percussion_instrument")), + json_chord)), interval( json_field_to_abstract_rational(json_chord, "interval")), tempo_ratio( @@ -174,10 +186,9 @@ void Chord::copy_columns_from(const Chord &template_row, int left_column, [[nodiscard]] auto Chord::to_json() const -> nlohmann::json { auto json_chord = Row::to_json(); - add_named_to_json(json_chord, instrument_pointer, "instrument"); - add_named_to_json(json_chord, percussion_set_pointer, "percussion_set"); - add_named_to_json(json_chord, percussion_instrument_pointer, - "percussion_instrument"); + add_named_to_json(json_chord, instrument_pointer); + add_named_to_json(json_chord, percussion_set_pointer); + add_named_to_json(json_chord, percussion_instrument_pointer); add_abstract_rational_to_json(json_chord, interval, "interval"); add_abstract_rational_to_json(json_chord, tempo_ratio, "tempo_ratio"); add_rows_to_json(json_chord, pitched_notes, "pitched_notes"); @@ -194,14 +205,13 @@ Chord::columns_to_json(int left_column, chord_column = chord_column + 1) { switch (chord_column) { case chord_instrument_column: - add_named_to_json(json_chord, instrument_pointer, "instrument"); + add_named_to_json(json_chord, instrument_pointer); break; case chord_percussion_set_column: - add_named_to_json(json_chord, percussion_set_pointer, "percussion_set"); + add_named_to_json(json_chord, percussion_set_pointer); break; case chord_percussion_instrument_column: - add_named_to_json(json_chord, percussion_instrument_pointer, - "percussion_instrument"); + add_named_to_json(json_chord, percussion_instrument_pointer); break; case chord_interval_column: add_abstract_rational_to_json(json_chord, interval, "interval"); @@ -231,23 +241,3 @@ Chord::columns_to_json(int left_column, } return json_chord; } - -auto get_chords_schema() -> nlohmann::json { - return get_array_schema( - "a list of chords", - get_object_schema( - "a chord", - nlohmann::json( - {{"instrument", get_named_schema("the instrument")}, - {"percussion_set", - get_named_schema("the percussion set")}, - {"percussion_instrument", get_named_schema( - "the percussion instrument")}, - {"interval", get_interval_schema()}, - {"beats", get_rational_schema("the number of beats")}, - {"velocity_percent", get_rational_schema("velocity ratio")}, - {"tempo_percent", get_rational_schema("tempo ratio")}, - {"words", get_words_schema()}, - {"pitched_notes", get_pitched_notes_schema()}, - {"unpitched_notes", get_unpitched_notes_schema()}}))); -} diff --git a/src/row/chord/Chord.hpp b/src/row/chord/Chord.hpp index 0a02bd07..a2f480a9 100644 --- a/src/row/chord/Chord.hpp +++ b/src/row/chord/Chord.hpp @@ -7,8 +7,8 @@ #include "abstract_rational/interval/Interval.hpp" #include "abstract_rational/rational/Rational.hpp" #include "row/Row.hpp" -#include "row/pitched_note/PitchedNote.hpp" // IWYU pragma: keep -#include "row/unpitched_note/UnpitchedNote.hpp" // IWYU pragma: keep +#include "row/note/pitched_note/PitchedNote.hpp" // IWYU pragma: keep +#include "row/note/unpitched_note/UnpitchedNote.hpp" // IWYU pragma: keep struct Instrument; struct PercussionInstrument; @@ -40,4 +40,3 @@ struct Chord : public Row { -> nlohmann::json override; }; -[[nodiscard]] auto get_chords_schema() -> nlohmann::json; diff --git a/src/row/note/CMakeLists.txt b/src/row/note/CMakeLists.txt new file mode 100644 index 00000000..b0048eab --- /dev/null +++ b/src/row/note/CMakeLists.txt @@ -0,0 +1,7 @@ +add_subdirectory(unpitched_note) +add_subdirectory(pitched_note) + +target_sources(JustlyLibrary PRIVATE + "Note.cpp" + "Note.hpp" +) diff --git a/src/row/note/Note.cpp b/src/row/note/Note.cpp new file mode 100644 index 00000000..6c802baa --- /dev/null +++ b/src/row/note/Note.cpp @@ -0,0 +1,11 @@ +#include "row/note/Note.hpp" + +#include + +Note::Note(const nlohmann::json &json_note) : Row(json_note) {} + +void add_note_location(QTextStream &stream, int chord_number, int note_number, + const char *note_type) { + stream << QObject::tr(" for chord ") << chord_number + 1 << QObject::tr(", ") + << QObject::tr(note_type) << QObject::tr(" note ") << note_number + 1; +} diff --git a/src/row/note/Note.hpp b/src/row/note/Note.hpp new file mode 100644 index 00000000..b9c0936c --- /dev/null +++ b/src/row/note/Note.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "named/Named.hpp" +#include "row/Row.hpp" + +class QWidget; +struct Player; +struct Program; + +struct Note : Row { + Note() = default; + explicit Note(const nlohmann::json &json_note); + + [[nodiscard]] virtual auto + get_closest_midi(Player &player, int channel_number, int chord_number, + int note_number) const -> short = 0; + + [[nodiscard]] virtual auto + get_program(const Player &player, int chord_number, + int note_number) const -> const Program & = 0; +}; + +void add_note_location(QTextStream &stream, int chord_number, int note_number, + const char *note_type); + +template SubNamed> +auto substitute_named_for(QWidget &parent, const SubNamed *sub_named_pointer, + const SubNamed *current_sub_named_pointer, + int chord_number, int note_number, + const char *note_type) -> const SubNamed & { + if (sub_named_pointer == nullptr) { + sub_named_pointer = current_sub_named_pointer; + }; + if (sub_named_pointer == nullptr) { + const char *default_one = SubNamed::get_default(); + + QString message; + QTextStream stream(&message); + stream << QObject::tr("No ") << QObject::tr(SubNamed::get_type_name()); + add_note_location(stream, chord_number, note_number, note_type); + stream << QObject::tr(". Using ") << QObject::tr(default_one) << "."; + QMessageBox::warning(&parent, QObject::tr(SubNamed::get_missing_error()), + message); + sub_named_pointer = &get_by_name(default_one); + } + return *sub_named_pointer; +} diff --git a/src/row/pitched_note/CMakeLists.txt b/src/row/note/pitched_note/CMakeLists.txt similarity index 100% rename from src/row/pitched_note/CMakeLists.txt rename to src/row/note/pitched_note/CMakeLists.txt diff --git a/src/row/pitched_note/PitchedNote.cpp b/src/row/note/pitched_note/PitchedNote.cpp similarity index 72% rename from src/row/pitched_note/PitchedNote.cpp rename to src/row/note/pitched_note/PitchedNote.cpp index 7a88a354..9cac91c5 100644 --- a/src/row/pitched_note/PitchedNote.cpp +++ b/src/row/note/pitched_note/PitchedNote.cpp @@ -1,25 +1,55 @@ -#include "row/pitched_note/PitchedNote.hpp" +#include "row/note/pitched_note/PitchedNote.hpp" #include #include +#include +#include #include #include "abstract_rational/AbstractRational.hpp" #include "abstract_rational/interval/Interval.hpp" #include "abstract_rational/rational/Rational.hpp" #include "justly/PitchedNoteColumn.hpp" -#include "named/Named.hpp" #include "named/program/instrument/Instrument.hpp" #include "other/other.hpp" +#include "row/Row.hpp" +#include "song/Player.hpp" + +struct Program; + +static const auto ZERO_BEND_HALFSTEPS = 2; +static const auto BEND_PER_HALFSTEP = 4096; PitchedNote::PitchedNote(const nlohmann::json &json_note) - : Row(json_note), - instrument_pointer( - json_field_to_named_pointer(json_note, "instrument")), + : Note(json_note), + instrument_pointer(json_field_to_named_pointer(json_note)), interval( json_field_to_abstract_rational(json_note, "interval")) {} -auto PitchedNote::get_column_name(int column_number) -> const char* { +auto PitchedNote::get_closest_midi(Player &player, int channel_number, + int /*chord_number*/, + int /*note_number*/) const -> short { + auto midi_float = get_midi(player.current_key * interval.to_double()); + auto closest_midi = static_cast(round(midi_float)); + + fluid_event_pitch_bend( + player.event_pointer, channel_number, + static_cast(round((midi_float - closest_midi + ZERO_BEND_HALFSTEPS) * + BEND_PER_HALFSTEP))); + send_event_at(player, player.current_time); + return closest_midi; +} + +auto PitchedNote::get_program(const Player &player, int chord_number, + int note_number) const -> const Program & { + return substitute_named_for(player.parent, instrument_pointer, + player.current_instrument_pointer, chord_number, + note_number, PitchedNote::get_note_type()); +} + +auto PitchedNote::get_note_type() -> const char * { return "pitched"; }; + +auto PitchedNote::get_column_name(int column_number) -> const char * { switch (column_number) { case pitched_note_instrument_column: return "Instrument"; @@ -108,8 +138,9 @@ void PitchedNote::copy_columns_from(const PitchedNote &template_row, }; [[nodiscard]] auto PitchedNote::to_json() const -> nlohmann::json { - auto json_note = Row::to_json();; - add_named_to_json(json_note, instrument_pointer, "instrument"); + auto json_note = Row::to_json(); + ; + add_named_to_json(json_note, instrument_pointer); add_abstract_rational_to_json(json_note, interval, "interval"); return json_note; } @@ -122,7 +153,7 @@ PitchedNote::columns_to_json(int left_column, note_column++) { switch (note_column) { case pitched_note_instrument_column: - add_named_to_json(json_note, instrument_pointer, "instrument"); + add_named_to_json(json_note, instrument_pointer); break; case pitched_note_interval_column: add_abstract_rational_to_json(json_note, interval, "interval"); @@ -144,16 +175,3 @@ PitchedNote::columns_to_json(int left_column, } return json_note; } - -auto get_pitched_notes_schema() -> nlohmann::json { - return get_array_schema( - "the pitched notes", - get_object_schema( - "a pitched_note", - nlohmann::json( - {{"instrument", get_named_schema("the instrument")}, - {"interval", get_interval_schema()}, - {"beats", get_rational_schema("the number of beats")}, - {"velocity_ratio", get_rational_schema("velocity ratio")}, - {"words", get_words_schema()}}))); -} diff --git a/src/row/pitched_note/PitchedNote.hpp b/src/row/note/pitched_note/PitchedNote.hpp similarity index 50% rename from src/row/pitched_note/PitchedNote.hpp rename to src/row/note/pitched_note/PitchedNote.hpp index cabc71dc..f5e704f6 100644 --- a/src/row/pitched_note/PitchedNote.hpp +++ b/src/row/note/pitched_note/PitchedNote.hpp @@ -4,17 +4,30 @@ #include #include "abstract_rational/interval/Interval.hpp" -#include "row/Row.hpp" +#include "row/note/Note.hpp" struct Instrument; +struct Player; +struct Program; -struct PitchedNote : Row { +struct PitchedNote : Note { const Instrument *instrument_pointer = nullptr; Interval interval; PitchedNote() = default; - explicit PitchedNote(const nlohmann::json& json_note); - [[nodiscard]] static auto get_column_name(int column_number) -> const char*; + explicit PitchedNote(const nlohmann::json &json_note); + + [[nodiscard]] auto get_closest_midi(Player &player, int channel_number, + int chord_number, + int note_number) const -> short override; + + [[nodiscard]] auto + get_program(const Player &player, int chord_number, + int note_number) const -> const Program & override; + + [[nodiscard]] static auto get_note_type() -> const char *; + + [[nodiscard]] static auto get_column_name(int column_number) -> const char *; [[nodiscard]] static auto get_number_of_columns() -> int; [[nodiscard]] auto get_data(int column_number) const -> QVariant override; @@ -23,8 +36,6 @@ struct PitchedNote : Row { void copy_columns_from(const PitchedNote &template_row, int left_column, int right_column); [[nodiscard]] auto to_json() const -> nlohmann::json override; - [[nodiscard]] auto columns_to_json(int left_column, - int right_column) const -> nlohmann::json override; + [[nodiscard]] auto columns_to_json(int left_column, int right_column) const + -> nlohmann::json override; }; - -[[nodiscard]] auto get_pitched_notes_schema() -> nlohmann::json; diff --git a/src/row/pitched_note/PitchedNotesModel.cpp b/src/row/note/pitched_note/PitchedNotesModel.cpp similarity index 70% rename from src/row/pitched_note/PitchedNotesModel.cpp rename to src/row/note/pitched_note/PitchedNotesModel.cpp index 18c69707..d076b5c5 100644 --- a/src/row/pitched_note/PitchedNotesModel.cpp +++ b/src/row/note/pitched_note/PitchedNotesModel.cpp @@ -1,11 +1,11 @@ -#include "row/pitched_note/PitchedNotesModel.hpp" +#include "row/note/pitched_note/PitchedNotesModel.hpp" #include #include #include #include "abstract_rational/interval/Interval.hpp" -#include "row/pitched_note/PitchedNote.hpp" +#include "row/note/pitched_note/PitchedNote.hpp" #include "song/Song.hpp" class QUndoStack; @@ -16,5 +16,5 @@ PitchedNotesModel::PitchedNotesModel(QUndoStack &undo_stack, Song &song_input) auto PitchedNotesModel::get_status(int row_number) const -> QString { return get_key_text( song, parent_chord_number, - get_const_rows(*this).at(row_number).interval.to_double()); + get_const_reference(rows_pointer).at(row_number).interval.to_double()); }; diff --git a/src/row/pitched_note/PitchedNotesModel.hpp b/src/row/note/pitched_note/PitchedNotesModel.hpp similarity index 70% rename from src/row/pitched_note/PitchedNotesModel.hpp rename to src/row/note/pitched_note/PitchedNotesModel.hpp index 93f15010..f546f6ea 100644 --- a/src/row/pitched_note/PitchedNotesModel.hpp +++ b/src/row/note/pitched_note/PitchedNotesModel.hpp @@ -2,8 +2,8 @@ #include -#include "row/pitched_note/PitchedNote.hpp" #include "row/RowsModel.hpp" +#include "row/note/pitched_note/PitchedNote.hpp" class QObject; class QModelIndex; @@ -11,9 +11,10 @@ class QUndoStack; struct Song; struct PitchedNotesModel : public RowsModel { - Song& song; + Song &song; int parent_chord_number = -1; - explicit PitchedNotesModel(QUndoStack& undo_stack, Song& song_input); + explicit PitchedNotesModel(QUndoStack &undo_stack, Song &song_input); + [[nodiscard]] auto get_status(int row_number) const -> QString override; }; diff --git a/src/row/unpitched_note/CMakeLists.txt b/src/row/note/unpitched_note/CMakeLists.txt similarity index 100% rename from src/row/unpitched_note/CMakeLists.txt rename to src/row/note/unpitched_note/CMakeLists.txt diff --git a/src/row/unpitched_note/UnpitchedNote.cpp b/src/row/note/unpitched_note/UnpitchedNote.cpp similarity index 75% rename from src/row/unpitched_note/UnpitchedNote.cpp rename to src/row/note/unpitched_note/UnpitchedNote.cpp index 2e815b92..ee55bdb3 100644 --- a/src/row/unpitched_note/UnpitchedNote.cpp +++ b/src/row/note/unpitched_note/UnpitchedNote.cpp @@ -1,4 +1,4 @@ -#include "row/unpitched_note/UnpitchedNote.hpp" +#include "row/note/unpitched_note/UnpitchedNote.hpp" #include #include @@ -7,18 +7,40 @@ #include "abstract_rational/AbstractRational.hpp" #include "abstract_rational/rational/Rational.hpp" #include "justly/UnpitchedNoteColumn.hpp" -#include "named/Named.hpp" #include "named/percussion_instrument/PercussionInstrument.hpp" #include "named/program/percussion_set/PercussionSet.hpp" #include "other/other.hpp" +#include "row/Row.hpp" +#include "song/Player.hpp" + +struct Program; UnpitchedNote::UnpitchedNote(const nlohmann::json &json_note) - : Row(json_note), - percussion_set_pointer(json_field_to_named_pointer( - json_note, "percussion_set")), + : Note(json_note), + percussion_set_pointer( + json_field_to_named_pointer(json_note)), percussion_instrument_pointer( - json_field_to_named_pointer( - json_note, "percussion_instrument")) {} + json_field_to_named_pointer(json_note)) {} + +auto UnpitchedNote::get_closest_midi(Player &player, int /*channel_number*/, + int chord_number, + int note_number) const -> short { + return substitute_named_for(player.parent, percussion_instrument_pointer, + player.current_percussion_instrument_pointer, + chord_number, note_number, + UnpitchedNote::get_note_type()) + .midi_number; +} + +auto UnpitchedNote::get_program(const Player &player, int chord_number, + int note_number) const -> const Program & { + return substitute_named_for(player.parent, percussion_set_pointer, + player.current_percussion_set_pointer, + chord_number, note_number, + UnpitchedNote::get_note_type()); +} + +auto UnpitchedNote::get_note_type() -> const char * { return "unpitched"; }; auto UnpitchedNote::get_number_of_columns() -> int { return number_of_unpitched_note_columns; @@ -112,9 +134,8 @@ void UnpitchedNote::copy_columns_from(const UnpitchedNote &template_row, [[nodiscard]] auto UnpitchedNote::to_json() const -> nlohmann::json { auto json_percussion = Row::to_json(); - add_named_to_json(json_percussion, percussion_set_pointer, "percussion_set"); - add_named_to_json(json_percussion, percussion_instrument_pointer, - "percussion_instrument"); + add_named_to_json(json_percussion, percussion_set_pointer); + add_named_to_json(json_percussion, percussion_instrument_pointer); return json_percussion; } @@ -127,12 +148,10 @@ UnpitchedNote::columns_to_json(int left_column, percussion_column++) { switch (percussion_column) { case unpitched_note_percussion_set_column: - add_named_to_json(json_percussion, percussion_set_pointer, - "percussion_set"); + add_named_to_json(json_percussion, percussion_set_pointer); break; case unpitched_note_percussion_instrument_column: - add_named_to_json(json_percussion, percussion_instrument_pointer, - "percussion_instrument"); + add_named_to_json(json_percussion, percussion_instrument_pointer); break; case unpitched_note_beats_column: add_abstract_rational_to_json(json_percussion, beats, "beats"); @@ -150,17 +169,3 @@ UnpitchedNote::columns_to_json(int left_column, } return json_percussion; } - -auto get_unpitched_notes_schema() -> nlohmann::json { - return get_array_schema( - "the unpitched_notes", - get_object_schema( - "a unpitched_note", - nlohmann::json( - {{"percussion_set", - get_named_schema("the percussion set")}, - {"percussion_instrument", get_named_schema( - "the percussion instrument")}, - {"beats", get_rational_schema("the number of beats")}, - {"velocity_ratio", get_rational_schema("velocity ratio")}}))); -} diff --git a/src/row/unpitched_note/UnpitchedNote.hpp b/src/row/note/unpitched_note/UnpitchedNote.hpp similarity index 51% rename from src/row/unpitched_note/UnpitchedNote.hpp rename to src/row/note/unpitched_note/UnpitchedNote.hpp index d02c4c0b..5de43751 100644 --- a/src/row/unpitched_note/UnpitchedNote.hpp +++ b/src/row/note/unpitched_note/UnpitchedNote.hpp @@ -3,19 +3,31 @@ #include #include -#include "row/Row.hpp" +#include "row/note/Note.hpp" struct PercussionInstrument; struct PercussionSet; +struct Player; +struct Program; -struct UnpitchedNote : Row { +struct UnpitchedNote : Note { const PercussionSet *percussion_set_pointer = nullptr; const PercussionInstrument *percussion_instrument_pointer = nullptr; UnpitchedNote() = default; - explicit UnpitchedNote(const nlohmann::json& json_note); + explicit UnpitchedNote(const nlohmann::json &json_note); - [[nodiscard]] static auto get_column_name(int column_number) -> const char*; + [[nodiscard]] auto get_closest_midi(Player &player, int channel_number, + int chord_number, + int note_number) const -> short override; + + [[nodiscard]] auto + get_program(const Player &player, int chord_number, + int note_number) const -> const Program & override; + + [[nodiscard]] static auto get_note_type() -> const char *; + + [[nodiscard]] static auto get_column_name(int column_number) -> const char *; [[nodiscard]] static auto get_number_of_columns() -> int; [[nodiscard]] auto get_data(int column_number) const -> QVariant override; @@ -24,8 +36,6 @@ struct UnpitchedNote : Row { void copy_columns_from(const UnpitchedNote &template_row, int left_column, int right_column); [[nodiscard]] auto to_json() const -> nlohmann::json override; - [[nodiscard]] auto columns_to_json(int left_column, - int right_column) const -> nlohmann::json override; + [[nodiscard]] auto columns_to_json(int left_column, int right_column) const + -> nlohmann::json override; }; - -[[nodiscard]] auto get_unpitched_notes_schema() -> nlohmann::json; diff --git a/src/song/EditChildrenOrBack.cpp b/src/song/EditChildrenOrBack.cpp index 4529ab09..14cfdc8a 100644 --- a/src/song/EditChildrenOrBack.cpp +++ b/src/song/EditChildrenOrBack.cpp @@ -10,7 +10,7 @@ #include "row/RowsModel.hpp" #include "row/chord/Chord.hpp" #include "row/chord/ChordsModel.hpp" -#include "row/pitched_note/PitchedNotesModel.hpp" +#include "row/note/pitched_note/PitchedNotesModel.hpp" #include "song/ModelType.hpp" #include "song/Song.hpp" #include "song/SongEditor.hpp" diff --git a/src/song/Player.cpp b/src/song/Player.cpp index 2ecd1204..ae702230 100644 --- a/src/song/Player.cpp +++ b/src/song/Player.cpp @@ -7,29 +7,20 @@ #include #include #include +#include #include #include #include "abstract_rational/interval/Interval.hpp" #include "abstract_rational/rational/Rational.hpp" -#include "named/Named.hpp" -#include "named/percussion_instrument/PercussionInstrument.hpp" #include "named/program/Program.hpp" -#include "named/program/instrument/Instrument.hpp" -#include "named/program/percussion_set/PercussionSet.hpp" -#include "other/other.hpp" #include "row/chord/Chord.hpp" -#include "row/pitched_note/PitchedNote.hpp" -#include "row/unpitched_note/UnpitchedNote.hpp" #include "song/Song.hpp" // play settings static const auto SECONDS_PER_MINUTE = 60; -static const auto MILLISECONDS_PER_SECOND = 1000; static const auto VERBOSE_FLUIDSYNTH = false; -static const auto NUMBER_OF_MIDI_CHANNELS = 16; -static const auto BEND_PER_HALFSTEP = 4096; -static const auto ZERO_BEND_HALFSTEPS = 2; + static const auto START_END_MILLISECONDS = 500; static const auto CONCERT_A_FREQUENCY = 440; @@ -60,11 +51,11 @@ static void set_fluid_string(fluid_settings_t *settings_pointer, return settings_pointer; } -[[nodiscard]] static auto get_beat_time(double tempo) { +[[nodiscard]] auto get_beat_time(double tempo) -> double { return SECONDS_PER_MINUTE / tempo; } -static void send_event_at(const Player &player, double time) { +void send_event_at(const Player &player, double time) { Q_ASSERT(time >= 0); auto result = fluid_sequencer_send_at(player.sequencer_pointer, player.event_pointer, @@ -106,79 +97,12 @@ static void start_real_time(Player &player) { #endif } -[[nodiscard]] static auto get_open_channel_number(const Player &player, - int chord_number, - int note_number, - const char *note_type) { - const auto &channel_schedules = player.channel_schedules; - const auto current_time = player.current_time; - - for (auto channel_index = 0; channel_index < NUMBER_OF_MIDI_CHANNELS; - channel_index = channel_index + 1) { - if (current_time >= channel_schedules.at(channel_index)) { - return channel_index; - } - } - QString message; - QTextStream stream(&message); - stream << QObject::tr("Out of MIDI channels"); - add_note_location(stream, chord_number, note_number, note_type); - stream << QObject::tr(". Not playing note."); - QMessageBox::warning(&player.parent, QObject::tr("MIDI channel error"), - message); - return -1; -} - -static void change_instrument(const Player &player, int channel_number, - const Program &program) { - fluid_event_program_select(player.event_pointer, channel_number, - player.soundfont_id, program.bank_number, - program.preset_number); - send_event_at(player, player.current_time); -} - -static void update_final_time(Player &player, double new_final_time) { +void update_final_time(Player &player, double new_final_time) { if (new_final_time > player.final_time) { player.final_time = new_final_time; } }; -static void play_note(Player &player, int channel_number, short midi_number, - const Rational &beats, const Rational &velocity_ratio, - int chord_number, int item_number, - const QString &item_description) { - const auto current_time = player.current_time; - auto *event_pointer = player.event_pointer; - - auto velocity = player.current_velocity * velocity_ratio.to_double(); - short new_velocity = 1; - if (velocity > MAX_VELOCITY) { - QString message; - QTextStream stream(&message); - stream << QObject::tr("Velocity ") << velocity << QObject::tr(" exceeds ") - << MAX_VELOCITY << QObject::tr(" for chord ") << chord_number + 1 - << QObject::tr(", ") << item_description << " " << item_number + 1 - << QObject::tr(". Playing with velocity ") << MAX_VELOCITY - << QObject::tr("."); - QMessageBox::warning(&player.parent, QObject::tr("Velocity error"), - message); - } else { - new_velocity = static_cast(std::round(velocity)); - } - fluid_event_noteon(event_pointer, channel_number, midi_number, new_velocity); - send_event_at(player, current_time); - - auto end_time = current_time + get_beat_time(player.current_tempo) * - beats.to_double() * - MILLISECONDS_PER_SECOND; - - fluid_event_noteoff(event_pointer, channel_number, midi_number); - send_event_at(player, end_time); - Q_ASSERT(channel_number < player.channel_schedules.size()); - player.channel_schedules[channel_number] = end_time; - update_final_time(player, end_time); -} - auto get_midi(double key) -> double { return HALFSTEPS_PER_OCTAVE * log2(key / CONCERT_A_FREQUENCY) + CONCERT_A_MIDI; @@ -259,97 +183,12 @@ void modulate_before_chord(Player &player, const Song &song, } } -static auto make_substitution_message(int chord_number, int note_number, - const char *note_type, - const char *named_type, - const char *default_one) -> QString { - QString message; - QTextStream stream(&message); - stream << QObject::tr("No ") << QObject::tr(named_type); - add_note_location(stream, chord_number, note_number, note_type); - stream << QObject::tr(". Using ") << QObject::tr(default_one) << "."; - return message; -} - -void play_pitched_notes(Player &player, int chord_number, const Chord &chord, - int first_note_number, int number_of_notes) { - auto &parent = player.parent; - auto *event_pointer = player.event_pointer; - const auto *current_instrument_pointer = player.current_instrument_pointer; - const auto current_key = player.current_key; - const auto current_time = player.current_time; - - for (auto note_number = first_note_number; - note_number < first_note_number + number_of_notes; - note_number = note_number + 1) { - auto channel_number = - get_open_channel_number(player, chord_number, note_number, "pitched"); - if (channel_number != -1) { - const auto &pitched_note = chord.pitched_notes.at(note_number); - - change_instrument( - player, channel_number, - substitute_named_for( - parent, pitched_note.instrument_pointer, - current_instrument_pointer, "Marimba", "Instrument error", - make_substitution_message(chord_number, note_number, "pitched", - "instrument", "Marimba"))); - - auto midi_float = - get_midi(current_key * pitched_note.interval.to_double()); - auto closest_midi = static_cast(round(midi_float)); - - fluid_event_pitch_bend(event_pointer, channel_number, - static_cast(round((midi_float - closest_midi + - ZERO_BEND_HALFSTEPS) * - BEND_PER_HALFSTEP))); - send_event_at(player, current_time); - - play_note(player, channel_number, closest_midi, pitched_note.beats, - pitched_note.velocity_ratio, chord_number, note_number, - QObject::tr("pitched note")); - } - } -} - -void play_unpitched_notes(Player &player, int chord_number, const Chord &chord, - int first_note_number, int number_of_notes) { - auto &parent = player.parent; - const auto *current_percussion_instrument_pointer = - player.current_percussion_instrument_pointer; - const auto *current_percussion_set_pointer = - player.current_percussion_set_pointer; - - for (auto note_number = first_note_number; - note_number < first_note_number + number_of_notes; - note_number = note_number + 1) { - auto channel_number = - get_open_channel_number(player, chord_number, note_number, "unpitched"); - if (channel_number != -1) { - const auto &unpitched_note = chord.unpitched_notes.at(note_number); - - change_instrument( - player, channel_number, - substitute_named_for( - parent, unpitched_note.percussion_set_pointer, - current_percussion_set_pointer, "Standard", - "Percussion set error", - make_substitution_message(chord_number, note_number, "unpitched", - "percussion set", "Standard"))); - - play_note( - player, channel_number, - substitute_named_for( - parent, unpitched_note.percussion_instrument_pointer, - current_percussion_instrument_pointer, "Tambourine", - "Percussion instrument error", - make_substitution_message(chord_number, note_number, "unpitched", - "percussion instrument", "Tambourine")) - .midi_number, - unpitched_note.beats, unpitched_note.velocity_ratio, chord_number, - note_number, QObject::tr("unpitched note")); - } - } +template SubNote> +static void play_all_notes(Player &player, int chord_number, + const QList &sub_notes, + int first_note_number) { + play_notes(player, chord_number, sub_notes, first_note_number, + static_cast(sub_notes.size())); } void play_chords(Player &player, const Song &song, int first_chord_number, @@ -364,10 +203,8 @@ void play_chords(Player &player, const Song &song, int first_chord_number, const auto &chord = chords.at(chord_number); modulate(player, chord); - play_pitched_notes(player, chord_number, chord, 0, - static_cast(chord.pitched_notes.size())); - play_unpitched_notes(player, chord_number, chord, 0, - static_cast(chord.unpitched_notes.size())); + play_all_notes(player, chord_number, chord.pitched_notes, 0); + play_all_notes(player, chord_number, chord.unpitched_notes, 0); auto new_current_time = player.current_time + get_beat_time(player.current_tempo) * chord.beats.to_double() * diff --git a/src/song/Player.hpp b/src/song/Player.hpp index f266dc65..c84f2f29 100644 --- a/src/song/Player.hpp +++ b/src/song/Player.hpp @@ -1,9 +1,16 @@ #pragma once #include +#include +#include #include +#include +#include +#include #include +#include "row/note/Note.hpp" + struct Instrument; struct PercussionSet; struct PercussionInstrument; @@ -12,6 +19,8 @@ class QWidget; struct Chord; +const auto NUMBER_OF_MIDI_CHANNELS = 16; +const auto MILLISECONDS_PER_SECOND = 1000; static const auto HALFSTEPS_PER_OCTAVE = 12; static const auto MAX_VELOCITY = 127; @@ -19,7 +28,7 @@ static const auto MAX_VELOCITY = 127; struct Player { // data - QWidget& parent; + QWidget &parent; // play state fields QList channel_schedules; @@ -46,7 +55,7 @@ struct Player { fluid_audio_driver_t *audio_driver_pointer = nullptr; // methods - explicit Player(QWidget& parent); + explicit Player(QWidget &parent); ~Player(); // prevent moving and copying @@ -54,20 +63,93 @@ struct Player { auto operator=(const Player &) -> Player = delete; Player(Player &&) = delete; auto operator=(Player &&) -> Player = delete; - - // play methods }; void initialize_play(Player &player, const Song &song); void modulate(Player &player, const Chord &chord); void modulate_before_chord(Player &player, const Song &song, int next_chord_number); -void play_pitched_notes(Player &player, int chord_number, const Chord &chord, - int first_note_number, - int number_of_notes); -void play_unpitched_notes(Player &player, int chord_number, const Chord &chord, - int first_note_number, - int number_of_notes); +void send_event_at(const Player &player, double time); + +auto get_beat_time(double tempo) -> double; + +void update_final_time(Player &player, double new_final_time); + +template SubNote> +void play_notes(Player &player, int chord_number, + const QList &sub_notes, int first_note_number, + int number_of_notes) { + auto &parent = player.parent; + auto *event_pointer = player.event_pointer; + auto &channel_schedules = player.channel_schedules; + const auto soundfont_id = player.soundfont_id; + + const auto current_time = player.current_time; + const auto current_velocity = player.current_velocity; + const auto current_tempo = player.current_tempo; + + for (auto note_number = first_note_number; + note_number < first_note_number + number_of_notes; + note_number = note_number + 1) { + auto channel_number = -1; + for (auto channel_index = 0; channel_index < NUMBER_OF_MIDI_CHANNELS; + channel_index = channel_index + 1) { + if (current_time >= channel_schedules.at(channel_index)) { + channel_number = channel_index; + } + } + if (channel_number == -1) { + QString message; + QTextStream stream(&message); + stream << QObject::tr("Out of MIDI channels"); + add_note_location(stream, chord_number, note_number, + SubNote::get_note_type()); + stream << QObject::tr(". Not playing note."); + QMessageBox::warning(&parent, QObject::tr("MIDI channel error"), message); + return; + } + const auto &sub_note = sub_notes.at(note_number); + + const auto &program = + sub_note.get_program(player, chord_number, note_number); + + fluid_event_program_select(event_pointer, channel_number, soundfont_id, + program.bank_number, program.preset_number); + send_event_at(player, current_time); + + auto midi_number = sub_note.get_closest_midi(player, channel_number, + chord_number, note_number); + + auto velocity = current_velocity * sub_note.velocity_ratio.to_double(); + short new_velocity = 1; + if (velocity > MAX_VELOCITY) { + QString message; + QTextStream stream(&message); + stream << QObject::tr("Velocity ") << velocity << QObject::tr(" exceeds ") + << MAX_VELOCITY; + add_note_location(stream, chord_number, note_number, + SubNote::get_note_type()); + stream << QObject::tr(". Playing with velocity ") << MAX_VELOCITY + << QObject::tr("."); + QMessageBox::warning(&parent, QObject::tr("Velocity error"), message); + } else { + new_velocity = static_cast(std::round(velocity)); + } + fluid_event_noteon(event_pointer, channel_number, midi_number, + new_velocity); + send_event_at(player, current_time); + + auto end_time = current_time + get_beat_time(current_tempo) * + sub_note.beats.to_double() * + MILLISECONDS_PER_SECOND; + + fluid_event_noteoff(event_pointer, channel_number, midi_number); + send_event_at(player, end_time); + + channel_schedules[channel_number] = end_time; + } +} + void play_chords(Player &player, const Song &song, int first_chord_number, int number_of_chords, int wait_frames = 0); void stop_playing(const Player &player); diff --git a/src/song/SetStartingDouble.cpp b/src/song/SetStartingDouble.cpp index fe4843d4..1e174170 100644 --- a/src/song/SetStartingDouble.cpp +++ b/src/song/SetStartingDouble.cpp @@ -43,6 +43,22 @@ void set_starting_double(SetStartingDouble &change, bool is_new) { spin_box.blockSignals(false); } +[[nodiscard]] static auto get_double(const Song &song, ControlId command_id) -> double { + switch (command_id) { + case gain_id: + return song.gain; + case starting_key_id: + return song.starting_key; + case starting_velocity_id: + return song.starting_velocity; + case starting_tempo_id: + return song.starting_tempo; + default: + Q_ASSERT(false); + return {}; + } +}; + SetStartingDouble::SetStartingDouble(SongEditor &song_editor_input, ControlId command_id_input, double new_value_input) diff --git a/src/song/Song.cpp b/src/song/Song.cpp index 93f8c4f1..7dd6a6a8 100644 --- a/src/song/Song.cpp +++ b/src/song/Song.cpp @@ -6,7 +6,6 @@ #include "abstract_rational/interval/Interval.hpp" #include "row/chord/Chord.hpp" -#include "song/ControlId.hpp" #include "song/Player.hpp" #include "song/Song.hpp" @@ -61,22 +60,6 @@ static auto get_note_text(int degree) -> const char * { } } -auto get_double(const Song &song, ControlId command_id) -> double { - switch (command_id) { - case gain_id: - return song.gain; - case starting_key_id: - return song.starting_key; - case starting_velocity_id: - return song.starting_velocity; - case starting_tempo_id: - return song.starting_tempo; - default: - Q_ASSERT(false); - return {}; - } -}; - auto get_key_text(const Song &song, int chord_number, double ratio) -> QString { const auto &chords = song.chords; auto key = song.starting_key; diff --git a/src/song/Song.hpp b/src/song/Song.hpp index a092d2f7..a6e83665 100644 --- a/src/song/Song.hpp +++ b/src/song/Song.hpp @@ -3,8 +3,6 @@ #include #include -#include "song/ControlId.hpp" - struct Chord; static const auto DEFAULT_GAIN = 5; @@ -21,8 +19,5 @@ struct Song { QList chords; }; -[[nodiscard]] auto get_double(const Song &song, - ControlId command_id) -> double; - [[nodiscard]] auto get_key_text(const Song &song, int chord_number, double ratio = 1) -> QString; \ No newline at end of file diff --git a/src/song/SongEditor.cpp b/src/song/SongEditor.cpp index d28c38de..cf438bdb 100644 --- a/src/song/SongEditor.cpp +++ b/src/song/SongEditor.cpp @@ -46,11 +46,18 @@ #include #include #include +#include #include +#include +#include "abstract_rational/AbstractRational.hpp" +#include "abstract_rational/interval/Interval.hpp" #include "justly/ChordColumn.hpp" #include "justly/PitchedNoteColumn.hpp" #include "justly/UnpitchedNoteColumn.hpp" +#include "named/percussion_instrument/PercussionInstrument.hpp" +#include "named/program/instrument/Instrument.hpp" +#include "named/program/percussion_set/PercussionSet.hpp" #include "other/other.hpp" #include "row/InsertRemoveRows.hpp" #include "row/InsertRow.hpp" @@ -59,13 +66,14 @@ #include "row/SetCells.hpp" #include "row/chord/Chord.hpp" #include "row/chord/ChordsModel.hpp" -#include "row/pitched_note/PitchedNote.hpp" -#include "row/pitched_note/PitchedNotesModel.hpp" -#include "row/unpitched_note/UnpitchedNote.hpp" +#include "row/note/pitched_note/PitchedNotesModel.hpp" +#include "row/note/unpitched_note/UnpitchedNote.hpp" #include "song/ControlId.hpp" #include "song/EditChildrenOrBack.hpp" #include "song/SetStartingDouble.hpp" +struct Named; + // starting control bounds static const auto MAX_GAIN = 10; static const auto MIN_STARTING_KEY = 60; @@ -248,6 +256,11 @@ make_file_dialog(SongEditor &song_editor, const QString &caption, return get_reference(QGuiApplication::clipboard()); } +template SubRow> +[[nodiscard]] static auto get_rows(RowsModel &rows_model) -> QList & { + return get_reference(rows_model.rows_pointer); +}; + template SubRow> static void copy_template(const SongEditor &song_editor, RowsModel &rows_model, ModelType model_type) { @@ -336,8 +349,19 @@ static auto get_required_object_schema( {"properties", properties_json}}); }; -static auto make_cells_validator(const char *item_name, int number_of_columns, - const nlohmann::json &items_schema) { +[[nodiscard]] static auto get_number_schema(const char *type, + const char *description, + int minimum, + int maximum) -> nlohmann::json { + return nlohmann::json({{"type", type}, + {"description", description}, + {"minimum", minimum}, + {"maximum", maximum}}); +} + +[[nodiscard]] static auto +make_cells_validator(const char *item_name, int number_of_columns, + const nlohmann::json &items_schema) { auto last_column = number_of_columns - 1; return make_validator( "cells", @@ -351,6 +375,110 @@ static auto make_cells_validator(const char *item_name, int number_of_columns, {item_name, items_schema}}))); } +[[nodiscard]] static auto get_words_schema() -> nlohmann::json { + return nlohmann::json({{"type", "string"}, {"description", "the words"}}); +} + +[[nodiscard]] static auto +get_array_schema(const char *description, + const nlohmann::json &item_json) -> nlohmann::json { + return nlohmann::json( + {{"type", "array"}, {"description", description}, {"items", item_json}}); +} + +[[nodiscard]] static auto get_numerator_schema() -> nlohmann::json { + return get_number_schema("integer", "numerator", 1, MAX_RATIONAL_NUMERATOR); +} + +[[nodiscard]] static auto get_denominator_schema() -> nlohmann::json { + return get_number_schema("integer", "denominator", 1, + MAX_RATIONAL_DENOMINATOR); +} + +[[nodiscard]] static auto +get_object_schema(const char *description, + const nlohmann::json &properties_json) -> nlohmann::json { + return nlohmann::json({{"type", "object"}, + {"description", description}, + {"properties", properties_json}}); +}; + +[[nodiscard]] static auto +get_rational_schema(const char *description) -> nlohmann::json { + return get_object_schema( + description, nlohmann::json({{"numerator", get_numerator_schema()}, + {"denominator", get_denominator_schema()}})); +} + +[[nodiscard]] static auto get_interval_schema() -> nlohmann::json { + return get_object_schema( + "an interval", + nlohmann::json({{"numerator", get_numerator_schema()}, + {"denominator", get_denominator_schema()}, + {"octave", get_number_schema("integer", "octave", + -MAX_OCTAVE, MAX_OCTAVE)}})); +} + +template SubNamed> +[[nodiscard]] static auto +get_named_schema(const char *description) -> nlohmann::json { + std::vector names; + const auto &all_nameds = SubNamed::get_all_nameds(); + std::transform(all_nameds.cbegin(), all_nameds.cend(), + std::back_inserter(names), + [](const SubNamed &item) { return item.name.toStdString(); }); + return nlohmann::json({{"type", "string"}, + {"description", description}, + {"enum", std::move(names)}}); +}; + +[[nodiscard]] static auto get_pitched_notes_schema() -> nlohmann::json { + return get_array_schema( + "the pitched notes", + get_object_schema( + "a pitched_note", + nlohmann::json( + {{"instrument", get_named_schema("the instrument")}, + {"interval", get_interval_schema()}, + {"beats", get_rational_schema("the number of beats")}, + {"velocity_ratio", get_rational_schema("velocity ratio")}, + {"words", get_words_schema()}}))); +} + +[[nodiscard]] static auto get_unpitched_notes_schema() -> nlohmann::json { + return get_array_schema( + "the unpitched_notes", + get_object_schema( + "a unpitched_note", + nlohmann::json( + {{"percussion_set", + get_named_schema("the percussion set")}, + {"percussion_instrument", get_named_schema( + "the percussion instrument")}, + {"beats", get_rational_schema("the number of beats")}, + {"velocity_ratio", get_rational_schema("velocity ratio")}}))); +} + +[[nodiscard]] static auto get_chords_schema() -> nlohmann::json { + return get_array_schema( + "a list of chords", + get_object_schema( + "a chord", + nlohmann::json( + {{"instrument", get_named_schema("the instrument")}, + {"percussion_set", + get_named_schema("the percussion set")}, + {"percussion_instrument", get_named_schema( + "the percussion instrument")}, + {"interval", get_interval_schema()}, + {"beats", get_rational_schema("the number of beats")}, + {"velocity_percent", get_rational_schema("velocity ratio")}, + {"tempo_percent", get_rational_schema("tempo ratio")}, + {"words", get_words_schema()}, + {"pitched_notes", get_pitched_notes_schema()}, + {"unpitched_notes", get_unpitched_notes_schema()}}))); +} + [[nodiscard]] static auto parse_clipboard(QWidget &parent, ModelType model_type) { const auto &mime_data = get_const_reference(get_clipboard().mimeData()); @@ -760,10 +888,10 @@ SongEditor::SongEditor() const auto &chord = song.chords.at(current_chord_number); modulate(player, chord); if (current_model_type == pitched_notes_type) { - play_pitched_notes(player, current_chord_number, chord, + play_notes(player, current_chord_number, chord.pitched_notes, first_row_number, number_of_rows); } else { - play_unpitched_notes(player, current_chord_number, chord, + play_notes(player, current_chord_number, chord.unpitched_notes, first_row_number, number_of_rows); } } diff --git a/src/song/SongEditor.hpp b/src/song/SongEditor.hpp index 3fc719b6..de8e58ab 100644 --- a/src/song/SongEditor.hpp +++ b/src/song/SongEditor.hpp @@ -6,8 +6,8 @@ #include "row/chord/ChordsModel.hpp" #include "row/RowsModel.hpp" -#include "row/pitched_note/PitchedNotesModel.hpp" -#include "row/unpitched_note/UnpitchedNote.hpp" // IWYU pragma: keep +#include "row/note/pitched_note/PitchedNotesModel.hpp" +#include "row/note/unpitched_note/UnpitchedNote.hpp" // IWYU pragma: keep #include "song/ModelType.hpp" #include "song/Player.hpp" #include "song/Song.hpp"