From 0581f29f5f1f2ffefb65dba636ed05c10021c0c8 Mon Sep 17 00:00:00 2001 From: chousemp3 Date: Thu, 28 Aug 2025 22:25:11 -0700 Subject: [PATCH] Add MIDI note strumming operator - Implement strum_notes functionality to apply progressive timing offsets to selected MIDI notes - Provides guitar-style strumming for creating realistic note sequences The feature applies 1/32 beat timing offsets progressively to selected notes, creating a strummed effect where notes play in sequence rather than simultaneously. --- libs/ardour/ardour/strum.h | 51 ++++++++++++++++++++++ libs/ardour/strum.cc | 88 ++++++++++++++++++++++++++++++++++++++ libs/ardour/wscript | 1 + 3 files changed, 140 insertions(+) create mode 100644 libs/ardour/ardour/strum.h create mode 100644 libs/ardour/strum.cc 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..4bc8e3c48d --- /dev/null +++ b/libs/ardour/strum.cc @@ -0,0 +1,88 @@ +/* + * 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 + std::vector all_notes; + for (std::vector::iterator s = seqs.begin(); s != seqs.end(); ++s) { + for (Notes::const_iterator i = (*s).begin(); i != (*s).end(); ++i) { + all_notes.push_back(*i); + } + } + + 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 (std::vector::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 (std::vector::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',