Skip to content

Commit

Permalink
patternist: implement legato
Browse files Browse the repository at this point in the history
  • Loading branch information
jcelerier committed Jan 4, 2025
1 parent e577c8a commit 8c5030c
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 43 deletions.
62 changes: 52 additions & 10 deletions src/plugins/score-plugin-midi/Patternist/PatternExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -60,35 +69,68 @@ class pattern_node : public ossia::nonowning_graph_node

last = current;
auto& mess = out.target<ossia::midi_port>()->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;
}
}
}

for(Lane& lane : pattern.lanes)
{
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);
Expand Down
65 changes: 59 additions & 6 deletions src/plugins/score-plugin-midi/Patternist/PatternModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ W_OBJECT_IMPL(Patternist::ProcessModel)

namespace Patternist
{
static std::vector<Patternist::Note> fromInts(std::initializer_list<int> e)
{
std::vector<Patternist::Note> 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<Process::ProcessModel>& id, QObject* parent)
: Process::
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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 <>
Expand Down
10 changes: 9 additions & 1 deletion src/plugins/score-plugin-midi/Patternist/PatternModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ class ValueOutlet;
}
namespace Patternist
{

enum class Note : uint8_t
{
Rest,
Note,
Legato
};

struct Lane
{
std::vector<bool> pattern;
std::vector<Patternist::Note> pattern;
uint8_t note{};

bool operator==(const Lane& other) const noexcept
Expand Down
22 changes: 20 additions & 2 deletions src/plugins/score-plugin-midi/Patternist/PatternParsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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())
Expand Down
18 changes: 15 additions & 3 deletions src/plugins/score-plugin-midi/Patternist/PatternPresenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<UpdatePattern>(layer, layer.currentPattern(), cur);
Expand Down
42 changes: 21 additions & 21 deletions src/plugins/score-plugin-midi/Patternist/PatternView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down

0 comments on commit 8c5030c

Please sign in to comment.