From 6bbfc5a4602a4d4f99f8f7e3d591baa1a005662c Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 20 Aug 2017 12:05:17 -0400 Subject: [PATCH] various changes, improvements and fixes for Beatbox API and implementation --- libs/ardour/ardour/beatbox.h | 23 +++--- libs/ardour/beatbox.cc | 138 +++++++++++++++++++++++++++-------- 2 files changed, 120 insertions(+), 41 deletions(-) diff --git a/libs/ardour/ardour/beatbox.h b/libs/ardour/ardour/beatbox.h index a3e3a6d019..8d2e551961 100644 --- a/libs/ardour/ardour/beatbox.h +++ b/libs/ardour/ardour/beatbox.h @@ -59,8 +59,8 @@ class BeatBox : public ARDOUR::Processor { Timecode::BBT_Time get_last_time () const; - void inject_note (int number, int velocity); - void inject_note (int number, int velocity, Timecode::BBT_Time at); + void add_note (int number, int velocity, Timecode::BBT_Time at); + void remove_note (int number, Timecode::BBT_Time at); void set_measure_count (int measures); void set_meter (int beats, int beat_type); @@ -97,14 +97,15 @@ class BeatBox : public ARDOUR::Processor { ARDOUR::MidiStateTracker outbound_tracker; struct Event { - superclock_t time; - superclock_t whole_note_superclocks; - size_t size; - unsigned char buf[24]; + superclock_t time; + superclock_t whole_note_superclocks; + size_t size; + unsigned char buf[24]; + int once; - Event () : time (0), size (0) {} - Event (superclock_t t, size_t sz, unsigned char* b) : time (t), size (sz) { memcpy (buf, b, std::min (sizeof (buf), sz)); } - Event (Event const & other) : time (other.time), size (other.size) { memcpy (buf, other.buf, other.size); } + Event () : time (0), size (0), once (0) {} + Event (superclock_t t, size_t sz, unsigned char* b) : time (t), size (sz), once (0) { memcpy (buf, b, std::min (sizeof (buf), sz)); } + Event (Event const & other) : time (other.time), size (other.size), once (0) { memcpy (buf, other.buf, other.size); } static MultiAllocSingleReleasePool pool; @@ -129,8 +130,8 @@ class BeatBox : public ARDOUR::Processor { void compute_tempo_clocks (); - RingBuffer injection_queue; - void queue_event (Event*); + RingBuffer add_queue; + RingBuffer remove_queue; }; } /* namespace */ diff --git a/libs/ardour/beatbox.cc b/libs/ardour/beatbox.cc index 4faafb2dc2..d51734577c 100644 --- a/libs/ardour/beatbox.cc +++ b/libs/ardour/beatbox.cc @@ -56,7 +56,8 @@ BeatBox::BeatBox (Session& s) , measure_superclocks (0) , _quantize_divisor (4) , clear_pending (false) - , injection_queue (256) + , add_queue (64) + , remove_queue (64) { _display_to_user = true; } @@ -119,6 +120,7 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram } else { if (!_start_requested) { _running = false; + outbound_tracker.resolve_notes (bufs.get_midi (0), 0); } } @@ -145,12 +147,6 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram return; } - Event* injected_event; - - while (injection_queue.read (&injected_event, 1)) { - _current_events.insert (injected_event); - } - superclock_t process_start = superclock_cnt - last_start; superclock_t process_end = process_start + superclocks; const superclock_t loop_length = _measures * measure_superclocks; @@ -208,7 +204,7 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram if (_quantize_divisor != 0) { const superclock_t time_per_grid_unit = whole_note_superclocks / _quantize_divisor; - if ((in_event.buffer()[0] & 0xf) == MIDI_CMD_NOTE_OFF) { + 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 @@ -218,18 +214,20 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram /* 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 (ee == _incomplete_notes.end()) { - cerr << "Note off for " << (int) (*ee)->buf[1] << " seen without corresponding note on among " << _incomplete_notes.size() << endl; + if (!found) { + cerr << "Note off for " << (int) in_event.buffer()[1] << " seen without corresponding note on among " << _incomplete_notes.size() << endl; continue; } @@ -241,6 +239,12 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram quantized_time = elapsed_time; } + /* if computed quantized time is past the end of the loop, wrap + it back around. + */ + + quantized_time %= loop_length; + if (in_event.size() > 24) { cerr << "Ignored large MIDI event\n"; continue; @@ -262,25 +266,86 @@ BeatBox::run (BufferSet& bufs, framepos_t /*start_frame*/, framepos_t /*end_fram _current_events.insert (new_event); - if ((new_event->buf[0] & 0xf) == MIDI_CMD_NOTE_ON) { + 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); + } + } + } + /* Output */ - for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ++ee) { + for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ) { Event* e = (*ee); - if (e->size && (e->time >= process_start && e->time < process_end)) { + if ((e->once <= 1) && e->size && (e->time >= process_start && e->time < process_end)) { const framepos_t sample_offset_in_buffer = superclock_to_samples (offset + e->time - process_start, _session.frame_rate()); - /* e->buf is guaranteed to live through this process * * cycle, so do not alloc for a copy*/ + /* 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); - buf.insert_event (eev); + 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; + ee = _current_events.erase (ee); + } } superclock_cnt += superclocks; @@ -350,17 +415,17 @@ BeatBox::get_last_time() const } void -BeatBox::inject_note (int note, int velocity) +BeatBox::remove_note (int note, Timecode::BBT_Time at) { } void -BeatBox::inject_note (int note, int velocity, Timecode::BBT_Time at) +BeatBox::add_note (int note, int velocity, Timecode::BBT_Time at) { - Event* e = new Event; // pool allocated, thread safe + Event* on = new Event; // pool allocated, thread safe - if (!e) { + if (!on) { cerr << "No more events for injection, grow pool\n"; return; } @@ -372,17 +437,30 @@ BeatBox::inject_note (int note, int velocity, Timecode::BBT_Time at) at.bars %= _measures; at.beats %= _meter_beats; - e->time = (measure_superclocks * at.bars) + (beat_superclocks * at.beats); - e->size = 3; - e->buf[0] = MIDI_CMD_NOTE_ON | (0 & 0xf); - e->buf[1] = note; - e->buf[2] = velocity; + on->time = (measure_superclocks * at.bars) + (beat_superclocks * at.beats); + on->size = 3; + on->buf[0] = MIDI_CMD_NOTE_ON | (0 & 0xf); + on->buf[1] = note; + on->buf[2] = velocity; - queue_event (e); -} + Event* off = new Event; // pool allocated, thread safe -void -BeatBox::queue_event (Event* e) -{ - injection_queue.write (&e, 1); + if (!off) { + cerr << "No more events for injection, grow pool\n"; + return; + } + + if (_quantize_divisor != 0) { + off->time = on->time + (beat_superclocks / _quantize_divisor); + } else { + /* 1/4 second note .. totally arbitrary */ + off->time = on->time + (_session.frame_rate() / 4); + } + off->size = 3; + off->buf[0] = MIDI_CMD_NOTE_OFF | (0 & 0xf); + off->buf[1] = note; + off->buf[2] = 0; + + add_queue.write (&on, 1); + add_queue.write (&off, 1); }