Move strum operation from GUI to libardour as MidiOperator

- Create new Strum class in libardour inheriting from MidiOperator
- Add strum.cc to build configuration (wscript)
- Refactor MidiView::strum_notes() to use new ARDOUR::Strum operator
- Follow same pattern as quantize, transpose, and legatize operations
- Preserve original behavior while improving architecture

This addresses feedback to move strum logic from GUI layer to proper
business logic layer, making it consistent with other MIDI operators
and enabling reuse throughout the codebase.
This commit is contained in:
chousemp3 2025-09-03 07:50:49 -07:00
parent 51e3a94aa8
commit c8df8ca7e4
4 changed files with 147 additions and 35 deletions

View file

@ -47,6 +47,7 @@
#include "ardour/operations.h" #include "ardour/operations.h"
#include "ardour/quantize.h" #include "ardour/quantize.h"
#include "ardour/session.h" #include "ardour/session.h"
#include "ardour/strum.h"
#include "evoral/Parameter.h" #include "evoral/Parameter.h"
#include "evoral/Event.h" #include "evoral/Event.h"
@ -5371,44 +5372,17 @@ MidiView::strum_notes (bool forward, bool fine)
return; return;
} }
start_note_diff_command (_("Strum")); ARDOUR::Strum strum(forward, fine);
Notes notes; PBD::Command* cmd = _editing_context.apply_midi_note_edit_op_to_region (strum, *this);
selection_as_notelist (notes, false);
if (notes.size() < 2) { if (cmd) {
abort_note_diff(); _editing_context.begin_reversible_command (strum.name ());
return; (*cmd)();
_editing_context.add_command (cmd);
_editing_context.commit_reversible_command ();
_editing_context.session()->set_dirty ();
} }
Temporal::Beats total_offset;
Temporal::Beats offset;
if (fine) {
offset = Temporal::Beats::ticks (Temporal::ticks_per_beat / 128);
} else {
offset = Temporal::Beats::ticks (Temporal::ticks_per_beat / 32);
}
if (forward) {
for (auto const & n : notes) {
NoteBase* cne = find_canvas_note (n);
if (cne) {
change_note_time (cne, total_offset, true);
total_offset += offset;
}
}
} else { // backward
for (auto it = notes.rbegin(); it != notes.rend(); ++it) {
NoteBase* cne = find_canvas_note (*it);
if (cne) {
change_note_time (cne, total_offset, true);
total_offset += offset;
}
}
}
apply_note_diff ();
} }
void void

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2025
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "ardour/libardour_visibility.h"
#include "ardour/midi_model.h"
#include "ardour/midi_operator.h"
namespace ARDOUR {
/** Strum notes (add progressive timing offset to notes).
*
* This operator applies a progressive timing offset to selected notes,
* creating a strumming effect where notes are offset by a specified
* amount in either forward or backward direction.
*/
class LIBARDOUR_API Strum : public MidiOperator {
public:
typedef Evoral::Sequence<Temporal::Beats>::NotePtr NotePtr;
typedef Evoral::Sequence<Temporal::Beats>::Notes Notes;
Strum (bool forward, bool fine);
PBD::Command* operator() (std::shared_ptr<ARDOUR::MidiModel> model,
Temporal::Beats position,
std::vector<Notes>& seqs);
std::string name () const { return std::string ("strum"); }
private:
bool _forward;
bool _fine;
};
} /* namespace */

86
libs/ardour/strum.cc Normal file
View file

@ -0,0 +1,86 @@
/*
* Copyright (C) 2025
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ardour/strum.h"
#include "ardour/midi_model.h"
namespace ARDOUR {
Strum::Strum(bool forward, bool fine)
: _forward(forward)
, _fine(fine)
{}
PBD::Command*
Strum::operator()(std::shared_ptr<ARDOUR::MidiModel> model,
Temporal::Beats position,
std::vector<Strum::Notes>& seqs)
{
typedef MidiModel::NoteDiffCommand Command;
Command* cmd = new Command(model, name());
if (seqs.empty()) {
return cmd;
}
// Get all notes from all sequences and sort them by start time
Notes all_notes;
for (std::vector<Notes>::iterator s = seqs.begin(); s != seqs.end(); ++s) {
all_notes.insert(all_notes.end(), (*s).begin(), (*s).end());
}
if (all_notes.size() < 2) {
return cmd;
}
// Sort notes by start time
std::sort(all_notes.begin(), all_notes.end(),
[](const NotePtr& a, const NotePtr& b) {
return a->time() < b->time();
});
Temporal::Beats total_offset;
Temporal::Beats offset;
if (_fine) {
offset = Temporal::Beats::ticks(Temporal::ticks_per_beat / 128);
} else {
offset = Temporal::Beats::ticks(Temporal::ticks_per_beat / 32);
}
if (_forward) {
for (Notes::const_iterator i = all_notes.begin(); i != all_notes.end(); ++i) {
const NotePtr note = *i;
Temporal::Beats new_start = note->time() + total_offset;
cmd->change(note, MidiModel::NoteDiffCommand::StartTime, new_start);
total_offset += offset;
}
} else { // backward
for (Notes::const_reverse_iterator i = all_notes.rbegin(); i != all_notes.rend(); ++i) {
const NotePtr note = *i;
Temporal::Beats new_start = note->time() + total_offset;
cmd->change(note, MidiModel::NoteDiffCommand::StartTime, new_start);
total_offset += offset;
}
}
return cmd;
}
} /* namespace */

View file

@ -250,6 +250,7 @@ libardour_sources = [
'stripable.cc', 'stripable.cc',
# 'step_sequencer.cc', # 'step_sequencer.cc',
'strip_silence.cc', 'strip_silence.cc',
'strum.cc',
'surround_pannable.cc', 'surround_pannable.cc',
'surround_return.cc', 'surround_return.cc',
'surround_send.cc', 'surround_send.cc',