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',