diff --git a/src/plugins/score-plugin-midi/Patternist/PatternExecutor.cpp b/src/plugins/score-plugin-midi/Patternist/PatternExecutor.cpp index 7c9a763a95..2de68804c4 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternExecutor.cpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternExecutor.cpp @@ -36,6 +36,14 @@ class pattern_node : public ossia::nonowning_graph_node std::string label() const noexcept override { return "pattern_node"; } + bool legato(int note) const noexcept + { + for(const Lane& lane : pattern.lanes) + if(lane.note == note) + return lane.pattern[current] == Note::Legato; + return false; + } + void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override { using namespace ossia; @@ -49,6 +57,7 @@ class pattern_node : public ossia::nonowning_graph_node mess.push_back(libremidi::channel_events::note_off(channel, note, 0)); mess.back().timestamp = 0; } + in_flight.clear(); return; } @@ -60,20 +69,53 @@ class pattern_node : public ossia::nonowning_graph_node last = current; auto& mess = out.target()->messages; - for(uint8_t note : in_flight) + + for(auto it = in_flight.begin(); it != in_flight.end();) { - mess.push_back(libremidi::channel_events::note_off(channel, note, 0)); - mess.back().timestamp = date; + uint8_t note = *it; + if(!legato(note)) + { + mess.push_back(libremidi::channel_events::note_off(channel, note, 0)); + mess.back().timestamp = date; + it = in_flight.erase(it); + } + else + { + ++it; + } } - in_flight.clear(); for(Lane& lane : pattern.lanes) { - if(lane.note <= 127 && lane.pattern[current]) + if(lane.note <= 127) { - mess.push_back(libremidi::channel_events::note_on(channel, lane.note, 64)); - mess.back().timestamp = date; - in_flight.insert(lane.note); + switch(lane.pattern[current]) + { + case Note::Note: + mess.push_back( + libremidi::channel_events::note_on(channel, lane.note, 100)); + mess.back().timestamp = date; + in_flight.insert(lane.note); + break; + case Note::Legato: + if(!in_flight.contains(lane.note)) + { + mess.push_back( + libremidi::channel_events::note_on(channel, lane.note, 100)); + mess.back().timestamp = date; + in_flight.insert(lane.note); + } + break; + case Note::Rest: + if(in_flight.contains(lane.note)) + { + mess.push_back( + libremidi::channel_events::note_off(channel, lane.note, 0)); + mess.back().timestamp = date; + in_flight.erase(lane.note); + } + break; + } } } @@ -81,14 +123,14 @@ class pattern_node : public ossia::nonowning_graph_node { if(lane.note == 255) { - if(lane.pattern[current]) + if(lane.pattern[current] != Note::Rest) accent_out->write_value(1., date); else accent_out->write_value(0., date); } else if(lane.note == 254) { - if(lane.pattern[current]) + if(lane.pattern[current] != Note::Rest) slide_out->write_value(1., date); else slide_out->write_value(0., date); diff --git a/src/plugins/score-plugin-midi/Patternist/PatternModel.cpp b/src/plugins/score-plugin-midi/Patternist/PatternModel.cpp index f027b222bc..042cca9afc 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternModel.cpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternModel.cpp @@ -17,6 +17,24 @@ W_OBJECT_IMPL(Patternist::ProcessModel) namespace Patternist { +static std::vector fromInts(std::initializer_list e) +{ + std::vector l; + for(int v : e) + switch(v) + { + case 0: + l.push_back(Note::Rest); + break; + case 1: + l.push_back(Note::Note); + break; + case 2: + l.push_back(Note::Legato); + break; + } + return l; +} ProcessModel::ProcessModel( const TimeVal& duration, const Id& id, QObject* parent) : Process:: @@ -27,8 +45,10 @@ ProcessModel::ProcessModel( { Pattern pattern; pattern.length = 4; - pattern.lanes.push_back(Lane{{1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0}, 64}); - pattern.lanes.push_back(Lane{{0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}, 32}); + pattern.lanes.push_back( + Lane{fromInts({0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}), 38}); + pattern.lanes.push_back( + Lane{fromInts({1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0}), 36}); m_patterns.push_back(pattern); metadata().setInstanceName(*this); init(); @@ -76,7 +96,7 @@ void ProcessModel::setCurrentPattern(int n) { auto pattern = m_patterns[m_currentPattern]; for(auto& lane : pattern.lanes) - std::fill(lane.pattern.begin(), lane.pattern.end(), false); + std::fill(lane.pattern.begin(), lane.pattern.end(), Note::Rest); while(n >= std::ssize(m_patterns)) m_patterns.push_back(pattern); @@ -151,8 +171,21 @@ void JSONReader::read(const Patternist::Lane& proc) std::string str; str.reserve(proc.pattern.size()); - for(bool b : proc.pattern) - str.push_back(b ? 'X' : '.'); + for(enum Patternist::Note n : proc.pattern) + { + switch(n) + { + case Patternist::Note::Rest: + str.push_back('-'); + break; + case Patternist::Note::Note: + str.push_back('1'); + break; + case Patternist::Note::Legato: + str.push_back('2'); + break; + } + } obj["Pattern"] = str; stream.EndObject(); @@ -163,7 +196,27 @@ void JSONWriter::write(Patternist::Lane& proc) { proc.note = obj["Note"].toInt(); for(char c : obj["Pattern"].toStdString()) - proc.pattern.push_back(c == 'X'); + { + switch(c) + { + default: + case '0': + case '-': + case '.': + proc.pattern.push_back(Patternist::Note::Rest); + break; + case '1': + case 'x': + case 'X': + case 'f': + case 'F': + proc.pattern.push_back(Patternist::Note::Note); + break; + case '2': + proc.pattern.push_back(Patternist::Note::Legato); + break; + } + } } template <> diff --git a/src/plugins/score-plugin-midi/Patternist/PatternModel.hpp b/src/plugins/score-plugin-midi/Patternist/PatternModel.hpp index b71f73f17b..b1c064dc5e 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternModel.hpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternModel.hpp @@ -13,9 +13,17 @@ class ValueOutlet; } namespace Patternist { + +enum class Note : uint8_t +{ + Rest, + Note, + Legato +}; + struct Lane { - std::vector pattern; + std::vector pattern; uint8_t note{}; bool operator==(const Lane& other) const noexcept diff --git a/src/plugins/score-plugin-midi/Patternist/PatternParsing.cpp b/src/plugins/score-plugin-midi/Patternist/PatternParsing.cpp index 311f8ae009..f40302dc67 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternParsing.cpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternParsing.cpp @@ -16,7 +16,7 @@ namespace Patternist { Pattern parsePattern(const QByteArray& data) noexcept { - static const QRegularExpression exp{QStringLiteral("[0-9][0-9] [-xX]+")}; + static const QRegularExpression exp{QStringLiteral("[0-9][0-9] [-xXfF012]+")}; Pattern p; for(QString lane : data.split('\n')) @@ -47,7 +47,25 @@ Pattern parsePattern(const QByteArray& data) noexcept p.length = split[1].size(); for(int i = 0; i < p.length; i++) { - lane.pattern.push_back(split[1][i] == '-' ? false : true); + char c = split[1][i].toLatin1(); + switch(c) + { + default: + case '-': + case '0': + lane.pattern.push_back(Note::Rest); + break; + case '1': + case 'x': + case 'X': + case 'f': + case 'F': + lane.pattern.push_back(Note::Note); + break; + case '2': + lane.pattern.push_back(Note::Legato); + break; + } } if(!lane.pattern.empty()) diff --git a/src/plugins/score-plugin-midi/Patternist/PatternPresenter.cpp b/src/plugins/score-plugin-midi/Patternist/PatternPresenter.cpp index 97a795265a..a0a53dd420 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternPresenter.cpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternPresenter.cpp @@ -23,9 +23,21 @@ Presenter::Presenter( connect(m_view, &View::toggled, this, [&](int lane, int index) { auto cur = layer.patterns()[layer.currentPattern()]; - bool b = cur.lanes[lane].pattern[index]; - - cur.lanes[lane].pattern[index] = !b; + const auto& l = cur.lanes[lane]; + auto b = l.pattern[index]; + + switch(b) + { + case Note::Rest: + cur.lanes[lane].pattern[index] = Note::Note; + break; + case Note::Note: + cur.lanes[lane].pattern[index] = l.note < 128 ? Note::Legato : Note::Rest; + break; + case Note::Legato: + cur.lanes[lane].pattern[index] = Note::Rest; + break; + } CommandDispatcher<> disp{m_context.context.commandStack}; disp.submit(layer, layer.currentPattern(), cur); diff --git a/src/plugins/score-plugin-midi/Patternist/PatternView.cpp b/src/plugins/score-plugin-midi/Patternist/PatternView.cpp index ec74eb7cab..f40cc19e46 100644 --- a/src/plugins/score-plugin-midi/Patternist/PatternView.cpp +++ b/src/plugins/score-plugin-midi/Patternist/PatternView.cpp @@ -99,34 +99,34 @@ void View::paint_impl(QPainter* painter) const { auto& l = cur_p.lanes[lane]; - // Draw the filled patterns for(int i = 0; i < cur_p.length; i++) { const QRectF rect{ x0 + i * (box_side + box_spacing), y0 + lane_height * lane, box_side, box_side}; + const QRectF legatoRect{ + x0 + i * (box_side + box_spacing) - box_spacing, y0 + lane_height * lane + 2, + box_spacing, box_side - 4}; - if(l.pattern[i]) + switch(l.pattern[i]) { - painter->setBrush( - (i != m_execPosition) ? style.Base4 : style.Base4.lighter.brush); - painter->drawRect(rect); - } - } - - // Draw the empty patterns - for(int i = 0; i < cur_p.length; i++) - { - const QRectF rect{ - x0 + i * (box_side + box_spacing), y0 + lane_height * lane, box_side, - box_side}; - - if(!l.pattern[i]) - { - painter->setBrush( - (i != m_execPosition) ? style.Emphasis2.main.brush - : style.Emphasis2.lighter.brush); - painter->drawRect(rect); + case Note::Rest: + painter->setBrush( + (i != m_execPosition) ? style.Emphasis2.main.brush + : style.Emphasis2.lighter.brush); + painter->drawRect(rect); + break; + case Note::Legato: + painter->setBrush( + (i != m_execPosition) ? style.Base4 : style.Base4.lighter.brush); + painter->drawRect(legatoRect); + painter->drawRect(rect); + break; + case Note::Note: + painter->setBrush( + (i != m_execPosition) ? style.Base4 : style.Base4.lighter.brush); + painter->drawRect(rect); + break; } } }