diff --git a/libs/ardour/step_sequencer.cc b/libs/ardour/step_sequencer.cc new file mode 100644 index 0000000000..e21d933f6a --- /dev/null +++ b/libs/ardour/step_sequencer.cc @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2018 Paul Davis + * + * 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, 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 + +#include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_state_tracker.h" +#include "ardour/step_sequencer.h" +#include "ardour/tempo.h" + +using namespace ARDOUR; +using namespace std; + +static int notenum = 60; + +Step::Step (StepSequence &s, Temporal::Beats const & b) + : _sequence (s) + , _enabled (true) + , _nominal_beat (b) + , _skipped (false) + , _mode (AbsolutePitch) +{ + std::cerr << "step @ " << b << std::endl; + + for (size_t n = 0; n < _notes_per_step; ++n) { + _notes[n].number = -1; + } + + /* XXX HACK XXXX */ + _notes[0].number = notenum++; + + for (size_t n = 0; n < _parameters_per_step; ++n) { + _parameters[n].parameter = -1; + } +} + +Step::~Step () +{ +} + +StepSequencer& +Step::sequencer() const +{ + return _sequence.sequencer(); +} + +void +Step::set_mode (Mode m) +{ + _mode = m; +} + +void +Step::set_beat (Temporal::Beats const & b) +{ + _nominal_beat = b; +} + +void +Step::set_note (double note, double velocity, int32_t duration, int n) +{ + assert (n < _notes_per_step); + _notes[n].number = note; + _notes[n].velocity = velocity; + _notes[n].duration = duration; +} + +void +Step::set_chord (size_t note_cnt, double* notes) +{ +} + +void +Step::set_parameter (int number, double value, int n) +{ + assert (n < _parameters_per_step); + _parameters[n].parameter = number; + _parameters[n].value = value; +} + +void +Step::set_enabled (bool yn) +{ + _enabled = yn; +} + +bool +Step::run (MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) +{ + for (size_t n = 0; n < _notes_per_step; ++n) { + check_parameter (n, buf, running, start_sample, end_sample); + } + + for (size_t n = 0; n < _notes_per_step; ++n) { + check_note (n, buf, running, start_sample, end_sample, tracker); + } + + if (running) { + + samplepos_t scheduled_samples = sequencer().tempo_map().sample_at_beat (_scheduled_beat.to_double()); + + if (scheduled_samples >= start_sample && scheduled_samples < end_sample) { + /* this step was covered by the run() range, so update its next + * scheduled time. + */ + _scheduled_beat += sequencer().duration(); + } + + } + + return true; +} + +void +Step::check_parameter (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample) +{ +} + +void +Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) +{ + Note& note (_notes[n]); + + if (note.number < 0) { + /* note not set .. ignore */ + return; + } + + /* figure out when this note would sound */ + + Temporal::Beats note_on_time = _scheduled_beat; + + note_on_time += note.offset; + + if (running && !note.on) { + + /* don't play silent notes */ + + if (note.velocity == 0) { + return; + } + + samplepos_t on_samples = sequencer().tempo_map().sample_at_beat (note_on_time.to_double()); + + if (on_samples >= start_sample && on_samples < end_sample) { + + uint8_t mbuf[3]; + + /* prepare 3 MIDI bytes for note on */ + + mbuf[0] = 0x90 | _sequence.channel(); + + switch (_mode) { + case AbsolutePitch: + mbuf[1] = note.number; + break; + case RelativePitch: + mbuf[1] = _sequence.root() + note.interval; + break; + } + + mbuf[2] = (uint8_t) floor (note.velocity * 127.0); + + /* Put it into the MIDI buffer */ + buf.write (on_samples - start_sample, Evoral::MIDI_EVENT, 3, mbuf); + tracker.add (mbuf[1], _sequence.channel()); + + /* keep track (even though other things will at different levels */ + + note.on = true; + + /* compute note off time based on our duration */ + + note.off_at = note_on_time; + + if (note.duration == 1) { + note.off_at += Temporal::Beats (0, _sequence.step_size().to_ticks() - 1); + } else { + note.off_at += Temporal::Beats (0, _sequence.step_size().to_ticks() / note.duration); + } + } + } + + if (note.on) { + + samplepos_t off_samples = sequencer().tempo_map().sample_at_beat (note.off_at.to_double()); + + if (off_samples >= start_sample && off_samples < end_sample) { + + uint8_t mbuf[3]; + + /* prepare 3 MIDI bytes for note off */ + + mbuf[0] = 0x80 | _sequence.channel(); + + switch (_mode) { + case AbsolutePitch: + mbuf[1] = note.number; + break; + case RelativePitch: + mbuf[1] = _sequence.root() + note.interval; + break; + } + + mbuf[2] = note.velocity; + + buf.write (off_samples - start_sample, Evoral::MIDI_EVENT, 3, mbuf); + tracker.remove (mbuf[1], _sequence.channel()); + + /* record keeping */ + + note.on = false; + note.off_at = Temporal::Beats(); + } + } +} + +void +Step::set_timeline_offset (Temporal::Beats const & start, Temporal::Beats const & offset) +{ + timeline_offset = offset; + + if (_nominal_beat < offset) { + _scheduled_beat = start + _nominal_beat + sequencer().duration(); /* schedule into the next loop iteration */ + } else { + _scheduled_beat = start + _nominal_beat; /* schedule into the current loop iteration */ + } + + /* MIDI state tracker will deal with any stuck notes */ + for (size_t n = 0; n < _notes_per_step; ++n) { + _notes[n].on = false; + _notes[n].off_at = Temporal::Beats(); + } +} + +/**/ + +StepSequence::StepSequence (StepSequencer& s, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size) + : _sequencer (s) + , _start (0) + , _end (nsteps) + , _channel (0) + , _step_size (step_size) + , _bar_size (bar_size) + , _root (64) + , _mode (MusicalMode::IonianMajor) +{ + Temporal::Beats beats; + + for (size_t s = 0; s < nsteps; ++s) { + _steps.push_back (new Step (*this, beats)); + beats += step_size; + } + + end_beat = beats; +} + +StepSequence::~StepSequence () +{ + for (Steps::iterator i = _steps.begin(); i != _steps.end(); ++i) { + delete *i; + } +} + +void +StepSequence::startup (Temporal::Beats const & start, Temporal::Beats const & offset) +{ + for (Steps::iterator i = _steps.begin(); i != _steps.end(); ++i) { + (*i)->set_timeline_offset (start, offset); + } +} + +void +StepSequence::reset () +{ +} + +void +StepSequence::set_channel (int c) +{ + _channel = c; +} + +Temporal::Beats +StepSequence::wrap (Temporal::Beats const & b) const +{ + if (b < end_beat) { + return b; + } + + return b - end_beat; +} + + +bool +StepSequence::run (MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) +{ + for (Steps::iterator s = _steps.begin(); s != _steps.end(); ++s) { + (*s)->run (buf, running, start_sample, end_sample, tracker); + } + return true; +} + +void +StepSequence::adjust_step_pitch (int step, int amt) +{ + if (step >= _steps.size()) { + return; + } + + _steps[step]->_notes[0].number += amt; + + if (_steps[step]->_notes[0].number > 127.0) { + _steps[step]->_notes[0].number = 127.0; + } + + if (_steps[step]->_notes[0].number < 0.0) { + _steps[step]->_notes[0].number = 0.0; + } +} + +/**/ + +StepSequencer::StepSequencer (TempoMap& tmap, size_t nseqs, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size) + : _tempo_map (tmap) + , _step_size (step_size) + , _start (0) + , _end (nsteps) +{ + for (size_t n = 0; n < nseqs; ++n) { + _sequences.push_back (new StepSequence (*this, nsteps, step_size, bar_size)); + } +} + +StepSequencer::~StepSequencer () +{ + for (StepSequences::iterator i = _sequences.begin(); i != _sequences.end(); ++i) { + delete *i; + } +} + +bool +StepSequencer::run (MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker) +{ + Glib::Threads::Mutex::Lock lm (_sequence_lock); + + for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) { + (*s)->run (buf, running, start_sample, end_sample, tracker); + } + + return true; +} + +void +StepSequencer::sync () +{ +} + +void +StepSequencer::reset () +{ + { + Glib::Threads::Mutex::Lock lm1 (_sequence_lock); + for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) { + (*s)->reset (); + } + } +} + +Temporal::Beats +StepSequencer::duration() const +{ + return _step_size * (_end - _start) ; +} + +void +StepSequencer::startup (Temporal::Beats const & start, Temporal::Beats const & offset) +{ + { + Glib::Threads::Mutex::Lock lm1 (_sequence_lock); + for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) { + (*s)->startup (start, offset); + } + } + +} + +void +StepSequencer::adjust_step_pitch (int seq, int step, int amt) +{ + _sequences.front()->adjust_step_pitch (step, amt); +}