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.
This commit is contained in:
chousemp3 2025-08-28 22:25:11 -07:00 committed by Robin Gareus
parent 4dbacf0cb6
commit 0581f29f5f
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04
3 changed files with 140 additions and 0 deletions

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 */

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

@ -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<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
std::vector<NotePtr> all_notes;
for (std::vector<Notes>::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<NotePtr>::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<NotePtr>::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',
# 'step_sequencer.cc',
'strip_silence.cc',
'strum.cc',
'surround_pannable.cc',
'surround_return.cc',
'surround_send.cc',