various changes, improvements and fixes for Beatbox API and implementation

This commit is contained in:
Paul Davis 2017-08-20 12:05:17 -04:00
parent 592175ab80
commit 6bbfc5a460
2 changed files with 120 additions and 41 deletions

View file

@ -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<Event*> injection_queue;
void queue_event (Event*);
RingBuffer<Event*> add_queue;
RingBuffer<Event*> remove_queue;
};
} /* namespace */

View file

@ -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<MidiBuffer::TimeType> 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<MidiBuffer::TimeType> 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);
}