From c8df8ca7e4a1854c4564b64ac319bce7284ed8dc Mon Sep 17 00:00:00 2001 From: chousemp3 Date: Wed, 3 Sep 2025 07:50:49 -0700 Subject: [PATCH] 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. --- gtk2_ardour/midi_view.cc | 44 ++++--------------- libs/ardour/ardour/strum.h | 51 ++++++++++++++++++++++ libs/ardour/strum.cc | 86 ++++++++++++++++++++++++++++++++++++++ libs/ardour/wscript | 1 + 4 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 libs/ardour/ardour/strum.h create mode 100644 libs/ardour/strum.cc diff --git a/gtk2_ardour/midi_view.cc b/gtk2_ardour/midi_view.cc index 28f9afad2a..62659c448f 100644 --- a/gtk2_ardour/midi_view.cc +++ b/gtk2_ardour/midi_view.cc @@ -47,6 +47,7 @@ #include "ardour/operations.h" #include "ardour/quantize.h" #include "ardour/session.h" +#include "ardour/strum.h" #include "evoral/Parameter.h" #include "evoral/Event.h" @@ -5371,44 +5372,17 @@ MidiView::strum_notes (bool forward, bool fine) return; } - start_note_diff_command (_("Strum")); + ARDOUR::Strum strum(forward, fine); - Notes notes; - selection_as_notelist (notes, false); + PBD::Command* cmd = _editing_context.apply_midi_note_edit_op_to_region (strum, *this); - if (notes.size() < 2) { - abort_note_diff(); - return; + if (cmd) { + _editing_context.begin_reversible_command (strum.name ()); + (*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 diff --git a/libs/ardour/ardour/strum.h b/libs/ardour/ardour/strum.h new file mode 100644 index 0000000000..a927565aee --- /dev/null +++ b/libs/ardour/ardour/strum.h @@ -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::NotePtr NotePtr; + typedef Evoral::Sequence::Notes Notes; + + Strum (bool forward, bool fine); + + PBD::Command* operator() (std::shared_ptr model, + Temporal::Beats position, + std::vector& seqs); + + std::string name () const { return std::string ("strum"); } + +private: + bool _forward; + bool _fine; +}; + +} /* namespace */ diff --git a/libs/ardour/strum.cc b/libs/ardour/strum.cc new file mode 100644 index 0000000000..a8863a069b --- /dev/null +++ b/libs/ardour/strum.cc @@ -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 model, + Temporal::Beats position, + std::vector& 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::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 */ diff --git a/libs/ardour/wscript b/libs/ardour/wscript index a7cdc52db6..f3b2b40958 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -250,6 +250,7 @@ libardour_sources = [ 'stripable.cc', # 'step_sequencer.cc', 'strip_silence.cc', + 'strum.cc', 'surround_pannable.cc', 'surround_return.cc', 'surround_send.cc',