From d682e61b996f0f9fd610d69dd82fee5bc92d67d7 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Fri, 2 Nov 2018 09:59:35 -0400 Subject: [PATCH] step sequencer now follows tempo map precisely; beatbox loses some functionality (for now) --- libs/ardour/ardour/beatbox.h | 1 + libs/ardour/ardour/step_sequencer.h | 120 +++++------ libs/ardour/beatbox.cc | 301 +++------------------------- 3 files changed, 94 insertions(+), 328 deletions(-) diff --git a/libs/ardour/ardour/beatbox.h b/libs/ardour/ardour/beatbox.h index ff27e47e8f..998334ea1d 100644 --- a/libs/ardour/ardour/beatbox.h +++ b/libs/ardour/ardour/beatbox.h @@ -95,6 +95,7 @@ class BeatBox : public ARDOUR::Processor { int _meter_beat_type; superclock_t superclock_cnt; superclock_t last_start; + superclock_t last_end; Timecode::BBT_Time last_time; int _sample_rate; diff --git a/libs/ardour/ardour/step_sequencer.h b/libs/ardour/ardour/step_sequencer.h index c2b29cbd02..39ce9e5f87 100644 --- a/libs/ardour/ardour/step_sequencer.h +++ b/libs/ardour/ardour/step_sequencer.h @@ -25,7 +25,7 @@ #include #include "temporal/types.h" -#include "temporal/bbt_time.h" +#include "temporal/beats.h" #include "ardour/mode.h" #include "ardour/types.h" @@ -33,9 +33,13 @@ namespace ARDOUR { class MidiBuffer; - +class MidiStateTracker; class StepSequencer; class StepSequence; +class TempoMap; + +typedef std::pair BeatPosition; +typedef std::vector BeatPositions; class Step { public: @@ -44,10 +48,10 @@ class Step { RelativePitch }; - Step (StepSequence&, Timecode::BBT_Time const & nominal_on); + Step (StepSequence&, Temporal::Beats const & beat); ~Step (); - void set_note (double note, double velocity = 0.5, double duration = 0.9, int n = 0); + void set_note (double note, double velocity = 0.5, int32_t duration = 1, int n = 0); void set_chord (size_t note_cnt, double* notes); void set_parameter (int number, double value, int n = 0); @@ -56,11 +60,10 @@ class Step { double note (size_t n = 0) const { return _notes[n].number; } double velocity (size_t n = 0) const { return _notes[n].velocity; } - Timecode::BBT_Time beat_duration (size_t n = 0) const; - double duration (size_t n = 0) const { return _notes[n].duration; } + int32_t duration (size_t n = 0) const { return _notes[n].duration; } - void set_offset (Timecode::BBT_Time const &, size_t n = 0); - Timecode::BBT_Time offset (size_t n = 0) const { return _notes[n].offset; } + void set_offset (Temporal::Beats const &, size_t n = 0); + Temporal::Beats offset (size_t n = 0) const { return _notes[n].offset; } int parameter (size_t n = 0) const { return _parameters[n].parameter; } int parameter_value (size_t n = 0) const { return _parameters[n].value; } @@ -71,16 +74,22 @@ class Step { void set_repeat (size_t r); size_t repeat() const { return _repeat; } - void set_nominal_on (Timecode::BBT_Time const &); - bool run (MidiBuffer& buf, Timecode::BBT_Time const & start, Timecode::BBT_Time const & end, samplecnt_t beat_samples); + void set_beat (Temporal::Beats const & beat); + Temporal::Beats beat () const { return _nominal_beat; } + + bool run (MidiBuffer& buf, bool running, samplepos_t, samplepos_t, MidiStateTracker&); bool skipped() const { return _skipped; } void set_skipped (bool); + void set_timeline_offset (Temporal::Beats const &, Temporal::Beats const &); + private: StepSequence& _sequence; bool _enabled; - Timecode::BBT_Time _nominal_on; + Temporal::Beats timeline_offset; + Temporal::Beats _nominal_beat; + Temporal::Beats _scheduled_beat; bool _skipped; Mode _mode; @@ -95,14 +104,13 @@ class Step { double interval; /* semitones */ }; double velocity; - double duration; - Timecode::BBT_Time offset; + int32_t duration; + Temporal::Beats offset; bool on; - Timecode::BBT_Time off_at; + Temporal::Beats off_at; - Note () : number (-1), on (false) {} - Note (double n, double v, double d, Timecode::BBT_Time o) - : number (n), velocity (v), duration (d), offset (o), on (false) {} + Note () : number (-1), velocity (0.5), duration (1), on (false) {} + Note (double n, double v, double d, Temporal::Beats const & o) : number (n), velocity (v), duration (d), offset (o), on (false) {} }; static const int _notes_per_step = 5; @@ -112,9 +120,11 @@ class Step { ParameterValue _parameters[_parameters_per_step]; size_t _repeat; - void check_note (size_t n, MidiBuffer& buf, Timecode::BBT_Time const & start, Timecode::BBT_Time const & end, ARDOUR::samplecnt_t beat_samples); - void check_parameter (size_t n, MidiBuffer& buf, Timecode::BBT_Time const & start, Timecode::BBT_Time const & end, ARDOUR::samplecnt_t beat_samples); + void check_note (size_t n, MidiBuffer& buf, bool, samplepos_t, samplepos_t, MidiStateTracker&); + void check_parameter (size_t n, MidiBuffer& buf, bool, samplepos_t, samplepos_t); + + StepSequencer& sequencer() const; }; class StepSequence @@ -127,15 +137,21 @@ class StepSequence rd_random = 3 }; - StepSequence (size_t numsteps, StepSequencer &myseq); + StepSequence (StepSequencer &myseq, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size); ~StepSequence (); + void startup (Temporal::Beats const & start, Temporal::Beats const & offset); + + Temporal::Beats bar_size() const { return _bar_size; } + double root() const { return _root; } void set_root (double n); int channel() const { return _channel; } void set_channel (int); + Temporal::Beats wrap (Temporal::Beats const &) const; + MusicalMode mode() const { return _mode; } void set_mode (MusicalMode m); @@ -146,13 +162,14 @@ class StepSequence void set_end_step (size_t); void set_start_and_end_step (size_t, size_t); - void set_beat_divisor (size_t); - size_t beat_divisor () const { return _beat_divisor; } + void set_step_size (Temporal::Beats const &); + Temporal::Beats step_size () const { return _step_size; } void reset (); - void set_tempo (double quarters_per_minute, int sr); - bool run (MidiBuffer& buf, Timecode::BBT_Time const & start, Timecode::BBT_Time const & end); + bool run (MidiBuffer& buf, bool running, samplepos_t, samplepos_t, MidiStateTracker&); + + StepSequencer& sequencer() const { return _sequencer; } private: StepSequencer& _sequencer; @@ -160,63 +177,52 @@ class StepSequence typedef std::vector Steps; Steps _steps; - size_t _start; - size_t _end; + size_t _start; /* step count */ + size_t _end; /* step count */ + int _channel; /* MIDI channel */ + + Temporal::Beats _step_size; + Temporal::Beats _bar_size; + Temporal::Beats end_beat; + double _root; MusicalMode _mode; - size_t _beat_divisor; - int _channel; - samplecnt_t _beat_samples; }; class StepSequencer { public: - enum State { - Running, - Halted, - Paused, - }; - - StepSequencer (size_t nseqs, size_t nsteps); + StepSequencer (TempoMap&, size_t nseqs, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size); ~StepSequencer (); + Temporal::Beats duration() const; + + void startup (Temporal::Beats const & start, Temporal::Beats const & offset); + + Temporal::Beats step_size () const { return _step_size; } + void set_step_size (Temporal::Beats const &); + void set_start_step (size_t); void set_end_step (size_t); void set_start_and_end_step (size_t, size_t); - bool running() const { return _state == Running; } - bool halted() const { return _state == Halted; } - bool paused() const { return _state == Paused; } - - void start (); - void halt (); /* stop everything, reset */ - void play (); - void pause (); - void toggle_pause (); void sync (); /* return all rows to start step */ void reset (); /* return entire state to default */ - double tempo() const; /* quarters per minute, not beats per minute */ - void set_tempo (double, int sr); + bool run (MidiBuffer& buf, bool running, samplepos_t, samplepos_t, MidiStateTracker&); - bool run (MidiBuffer& buf, Timecode::BBT_Time const & start, Timecode::BBT_Time const & end); + TempoMap& tempo_map() const { return _tempo_map; } private: Glib::Threads::Mutex _sequence_lock; - Glib::Threads::Mutex _state_lock; typedef std::vector StepSequences; StepSequences _sequences; - State _state; - Timecode::BBT_Time _target_start; - Timecode::BBT_Time _target_end; - double _target_tempo; - - Timecode::BBT_Time _start; - Timecode::BBT_Time _end; - double _tempo; + TempoMap& _tempo_map; + Temporal::Beats _step_size; + int32_t _start; + int32_t _end; }; } /* namespace */ diff --git a/libs/ardour/beatbox.cc b/libs/ardour/beatbox.cc index 4a1419c478..ff153ab089 100644 --- a/libs/ardour/beatbox.cc +++ b/libs/ardour/beatbox.cc @@ -35,6 +35,7 @@ #include "ardour/session.h" #include "ardour/smf_source.h" #include "ardour/step_sequencer.h" +#include "ardour/tempo.h" using std::cerr; using std::endl; @@ -49,10 +50,9 @@ BeatBox::BeatBox (Session& s) , _start_requested (false) , _running (false) , _measures (2) - , _tempo (120) - , _tempo_request (0) - , _meter_beats (4) - , _meter_beat_type (4) + , _tempo (-1.0) + , _meter_beats (-1) + , _meter_beat_type (-1) , superclock_cnt (0) , last_start (0) , whole_note_superclocks (0) @@ -65,7 +65,7 @@ BeatBox::BeatBox (Session& s) , remove_queue (64) { _display_to_user = true; - _sequencer = new StepSequencer (1, 32); + _sequencer = new StepSequencer (s.tempo_map(), 1, 8, Temporal::Beats (1, 0), Temporal::Beats (4, 0)); } BeatBox::~BeatBox () @@ -73,24 +73,9 @@ BeatBox::~BeatBox () delete _sequencer; } -void -BeatBox::compute_tempo_clocks () -{ - whole_note_superclocks = (superclock_ticks_per_second * 60) / (_tempo / _meter_beat_type); - beat_superclocks = whole_note_superclocks / _meter_beat_type; - tick_superclocks = beat_superclocks / Timecode::BBT_Time::ticks_per_beat; - measure_superclocks = beat_superclocks * _meter_beats; - - _sequencer->set_tempo (_tempo, AudioEngine::instance()->sample_rate()); -} - void BeatBox::start () { - /* compute tempo, beat steps etc. */ - - compute_tempo_clocks (); - /* we can start */ _start_requested = true; @@ -102,12 +87,6 @@ BeatBox::stop () _start_requested = false; } -void -BeatBox::set_tempo (float bpm) -{ - _tempo_request = bpm; -} - void BeatBox::silence (samplecnt_t, samplepos_t) { @@ -115,276 +94,56 @@ BeatBox::silence (samplecnt_t, samplepos_t) } void -BeatBox::run (BufferSet& bufs, samplepos_t /*start_frame*/, samplepos_t /*end_frame*/, double speed, pframes_t nsamples, bool /*result_required*/) +BeatBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nsamples, bool /*result_required*/) { if (bufs.count().n_midi() == 0) { return; } - if (!_running) { - if (_start_requested) { - _running = true; - last_start = superclock_cnt; - } + bool resolve = false; - } else { - if (!_start_requested) { + if (speed == 0) { + if (_running) { + resolve = true; _running = false; - outbound_tracker.resolve_notes (bufs.get_midi (0), 0); } } - superclock_t superclocks = samples_to_superclock (nsamples, _session.sample_rate()); + if (speed != 0) { - if (_tempo_request) { - double ratio = _tempo / _tempo_request; - _tempo = _tempo_request; - _tempo_request = 0; + if (!_running || (last_end != start_sample)) { - compute_tempo_clocks (); - - /* recompute all the event times based on the ratio between the - * new and old tempo. - */ - - for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ++ee) { - (*ee)->time = llrintf ((*ee)->time * ratio); - } - } - - if (!_running) { - superclock_cnt += superclocks; - return; - } - - superclock_t process_start = superclock_cnt - last_start; - superclock_t process_end = process_start + superclocks; - const superclock_t loop_length = _measures * measure_superclocks; - const superclock_t orig_superclocks = superclocks; - - process_start %= loop_length; - process_end %= loop_length; - - Timecode::BBT_Time start = last_time; - - last_time.bars = (process_end / measure_superclocks); - last_time.beats = ((process_end - (last_time.bars * measure_superclocks)) / beat_superclocks); - last_time.ticks = (process_end - (last_time.bars * measure_superclocks) - (last_time.beats * beat_superclocks)) / tick_superclocks; - - /* change to 1-base */ - last_time.bars++; - last_time.beats++; - - std::cerr << "run " << process_start << " .. " << process_end << " => " << start << " .. " << last_time << endl; - - bool two_pass_required; - superclock_t offset = 0; - - if (process_end < process_start) { - two_pass_required = true; - process_end = loop_length; - superclocks = process_end - process_start; - } else { - two_pass_required = false; - } - - Evoral::Event in_event; - - /* do this on the first pass only */ - MidiBuffer& buf = bufs.get_midi (0); - - if (clear_pending) { - - for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ++ee) { - delete *ee; - } - _current_events.clear (); - _incomplete_notes.clear (); - clear_pending = false; - } - - second_pass: - - /* input */ - - for (MidiBuffer::iterator e = buf.begin(); e != buf.end(); ++e) { - const Evoral::Event& in_event = *e; - - superclock_t event_time = superclock_cnt + samples_to_superclock (in_event.time(), _session.sample_rate()); - superclock_t elapsed_time = event_time - last_start; - superclock_t in_loop_time = elapsed_time % loop_length; - superclock_t quantized_time; - - if (_quantize_divisor != 0) { - const superclock_t time_per_grid_unit = whole_note_superclocks / _quantize_divisor; - - if ((in_event.buffer()[0] & 0xf0) == MIDI_CMD_NOTE_OFF) { - - /* note off is special - it must be quantized - * to at least 1 quantization "spacing" after - * the corresponding note on. - */ - - /* look for the note on */ - - IncompleteNotes::iterator ee; - bool found = false; - - for (ee = _incomplete_notes.begin(); ee != _incomplete_notes.end(); ++ee) { - /* check for same note and channel */ - if (((*ee)->buf[1] == in_event.buffer()[1]) && ((*ee)->buf[0] & 0xf) == (in_event.buffer()[0] & 0xf)) { - quantized_time = (*ee)->time + time_per_grid_unit; - _incomplete_notes.erase (ee); - found = true; - break; - } - } - - if (!found) { - cerr << "Note off for " << (int) in_event.buffer()[1] << " seen without corresponding note on among " << _incomplete_notes.size() << endl; - continue; - } - - } else { - quantized_time = (in_loop_time / time_per_grid_unit) * time_per_grid_unit; + if (last_end != start_sample) { + resolve = true; } - } else { - quantized_time = elapsed_time; - } + /* compute the beat position of this first "while-moving + * run() call as an offset into the sequencer's current loop + * length. + */ - /* if computed quantized time is past the end of the loop, wrap - it back around. - */ + TempoMap& tmap (_session.tempo_map()); - quantized_time %= loop_length; + const Temporal::Beats start_beat (tmap.beat_at_sample (start_sample)); + const int32_t tick_duration = _sequencer->duration().to_ticks(); - if (in_event.size() > 24) { - cerr << "Ignored large MIDI event\n"; - continue; - } - Event* new_event = new Event; // pool alloc, thread safe - if (!new_event) { - cerr << "No more events, grow pool\n"; - continue; - } + Temporal::Beats closest_previous_loop_start = Temporal::Beats::ticks ((start_beat.to_ticks() / tick_duration) * tick_duration); + Temporal::Beats offset = Temporal::Beats::ticks ((start_beat.to_ticks() % tick_duration)); + _sequencer->startup (closest_previous_loop_start, offset); + last_start = start_sample; + _running = true; - new_event->time = quantized_time; - new_event->whole_note_superclocks = whole_note_superclocks; - new_event->size = in_event.size(); - memcpy (new_event->buf, in_event.buffer(), new_event->size); - - inbound_tracker.track (new_event->buf); - - _current_events.insert (new_event); - - if ((new_event->buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) { - _incomplete_notes.push_back (new_event); } } - /* Notes added from other threads */ - - Event* added_event; - - if (offset == 0) { - /* during first pass only */ - - while (add_queue.read (&added_event, 1)) { - _current_events.insert (added_event); - - if (((added_event->buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) && - (added_event->time >= process_end || added_event->time < process_start)) { - - /* won't hear it this time, so do immediate play. Off will follow in time */ - - /* e->buf is guaranteed to live through this process cycle, so do not alloc for a copy*/ - const Evoral::Event eev (Evoral::MIDI_EVENT, 0, added_event->size, added_event->buf, false); - - if (buf.insert_event (eev)) { - outbound_tracker.track (added_event->buf); - } - - /* insert a 1-time only note off to turn off this immediate note on */ - - Event* matching_note_off = new Event; - matching_note_off->once = 1; - matching_note_off->size = 3; - - if (_quantize_divisor) { - matching_note_off->time = process_start + (beat_superclocks / _quantize_divisor); - } else { - matching_note_off->time = process_start + beat_superclocks; - } - matching_note_off->time %= loop_length; - - matching_note_off->buf[0] = MIDI_CMD_NOTE_OFF | (added_event->buf[0] & 0xf); - matching_note_off->buf[1] = added_event->buf[1]; - matching_note_off->buf[2] = 0; - - _current_events.insert (matching_note_off); - } - } + if (resolve) { + outbound_tracker.resolve_notes (bufs.get_midi(0), 0); } - /* Output */ - -#if 0 - - for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ) { - Event* e = (*ee); - if ((e->once <= 1) && e->size && (e->time >= process_start && e->time < process_end)) { - const samplepos_t sample_offset_in_buffer = superclock_to_samples (offset + e->time - process_start, _session.sample_rate()); - /* e->buf is guaranteed to live through this process cycle, so do not alloc for a copy*/ - const Evoral::Event eev (Evoral::MIDI_EVENT, sample_offset_in_buffer, e->size, e->buf, false); - if (buf.insert_event (eev)) { - outbound_tracker.track (e->buf); - } - } - - if (e->time >= process_end) { - break; - } - - switch (e->once) { - case 0: - /* normal event, do nothing */ - ++ee; - break; - case 1: - /* delete it next process cycle */ - e->once++; - ++ee; - break; - default: - delete e; - /* old versions of libstc++ don't return an iterator - from set::erase (iterator) - */ - Events::iterator n = ee; - ++n; - _current_events.erase (ee); - ee = n; - } - } -#endif - - _sequencer->run (buf, start, last_time); - - superclock_cnt += superclocks; - - if (two_pass_required) { - offset = superclocks; - superclocks = orig_superclocks - superclocks; - process_start = 0; - process_end = superclocks; - two_pass_required = false; - goto second_pass; - } - - return; + _sequencer->run (bufs.get_midi (0), _running, start_sample, end_sample, outbound_tracker); + last_end = end_sample; } void