more redesign of beatbox fundamentals, adding back RT-safe modifications to sequencer

This commit is contained in:
Paul Davis
2018-11-09 21:27:42 -05:00
parent e5ec4ab959
commit c0edd61d75
4 changed files with 257 additions and 310 deletions

View File

@@ -58,25 +58,6 @@ class BeatBox : public ARDOUR::Processor {
void silence (samplecnt_t nframes, samplepos_t start_frame);
bool can_support_io_configuration (const ChanCount& in, ChanCount& out);
bool running() const { return _running || _start_requested; }
void start ();
void stop ();
void clear ();
void add_note (int number, int velocity, Timecode::BBT_Time at);
void remove_note (int number, Timecode::BBT_Time at);
void edit_note_number (int old_number, int new_number);
void set_measure_count (int measures);
void set_meter (int beats, int beat_type);
void set_tempo (float bpm);
void set_quantize (int divisor);
float tempo() const { return _tempo; }
int meter_beats() const { return _meter_beats; }
int meter_beat_type() const { return _meter_beat_type; }
XMLNode& state();
XMLNode& get_state(void);
@@ -84,62 +65,8 @@ class BeatBox : public ARDOUR::Processor {
private:
StepSequencer* _sequencer;
bool _start_requested;
bool _running;
int _measures;
float _tempo;
float _tempo_request;
int _meter_beats;
int _meter_beat_type;
samplepos_t last_start;
samplepos_t last_end;
int _sample_rate;
superclock_t whole_note_superclocks;
superclock_t tick_superclocks;
superclock_t beat_superclocks;
superclock_t measure_superclocks;
int _quantize_divisor;
bool clear_pending;
ARDOUR::MidiStateTracker inbound_tracker;
ARDOUR::MidiStateTracker outbound_tracker;
struct Event {
superclock_t time;
superclock_t whole_note_superclocks;
size_t size;
unsigned char buf[24];
int once;
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;
void *operator new (size_t) {
return pool.alloc ();
}
void operator delete (void* ptr, size_t /* size */) {
pool.release (ptr);
}
};
struct EventComparator {
bool operator () (Event const * a, Event const * b) const;
};
typedef std::vector<Event*> IncompleteNotes;
IncompleteNotes _incomplete_notes;
typedef std::set<Event*,EventComparator> Events;
Events _current_events;
void compute_tempo_clocks ();
PBD::RingBuffer<Event*> add_queue;
PBD::RingBuffer<Event*> remove_queue;
bool fill_midi_source (boost::shared_ptr<SMFSource>);

View File

@@ -22,16 +22,20 @@
#include <vector>
#include <unistd.h>
#include <boost/atomic.hpp>
#include <boost/rational.hpp>
#include <glibmm/threads.h>
#include "pbd/pool.h"
#include "pbd/ringbuffer.h"
#include "pbd/stateful.h"
#include "temporal/types.h"
#include "temporal/beats.h"
#include "ardour/mode.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/types.h"
namespace ARDOUR {
@@ -54,7 +58,7 @@ class Step : public PBD::Stateful {
typedef boost::rational<int> DurationRatio;
Step (StepSequence&, Temporal::Beats const & beat, int notenum);
Step (StepSequence&, size_t n, Temporal::Beats const & beat, int notenum);
~Step ();
void set_note (double note, double velocity = 0.5, int n = 0);
@@ -108,6 +112,7 @@ class Step : public PBD::Stateful {
friend class StepSequence; /* HACK */
StepSequence& _sequence;
size_t index;
bool _enabled;
Temporal::Beats _nominal_beat;
Temporal::Beats _scheduled_beat;
@@ -168,8 +173,6 @@ class StepSequence : public PBD::Stateful
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);
@@ -184,17 +187,9 @@ class StepSequence : public PBD::Stateful
void shift_left (size_t n = 1);
void shift_right (size_t n = 1);
size_t start_step() const { return _start; }
size_t end_step() const { return _end; }
void set_start_step (size_t);
void set_end_step (size_t);
void set_start_and_end_step (size_t, size_t);
void set_step_size (Temporal::Beats const &);
Temporal::Beats step_size () const { return _step_size; }
void reset ();
void reschedule (Temporal::Beats const &, Temporal::Beats const &);
void schedule (Temporal::Beats const &);
bool run (MidiBuffer& buf, bool running, samplepos_t, samplepos_t, MidiStateTracker&);
@@ -209,14 +204,7 @@ class StepSequence : public PBD::Stateful
typedef std::vector<Step*> Steps;
Steps _steps;
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;
};
@@ -227,7 +215,8 @@ class StepSequencer : public PBD::Stateful
StepSequencer (TempoMap&, size_t nseqs, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size, int notenum);
~StepSequencer ();
size_t nsteps() const { return _sequences.front()->nsteps(); }
size_t step_capacity() const { return _step_capacity; }
size_t nsteps() const { return _end_step - _start_step; }
size_t nsequences() const { return _sequences.size(); }
int last_step() const;
@@ -236,19 +225,19 @@ class StepSequencer : public PBD::Stateful
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);
size_t start_step() const { return _start_step; }
size_t end_step() const { return _end_step; }
void sync (); /* return all rows to start step */
void reset (); /* return entire state to default */
bool run (MidiBuffer& buf, bool running, samplepos_t, samplepos_t, MidiStateTracker&);
bool run (MidiBuffer& buf, samplepos_t, samplepos_t, double, pframes_t, bool);
TempoMap& tempo_map() const { return _tempo_map; }
@@ -257,17 +246,56 @@ class StepSequencer : public PBD::Stateful
private:
mutable Glib::Threads::Mutex _sequence_lock;
TempoMap& _tempo_map;
typedef std::vector<StepSequence*> StepSequences;
StepSequences _sequences;
TempoMap& _tempo_map;
Temporal::Beats _last_startup; /* last time we started running */
size_t _last_step; /* last step that we executed */
Temporal::Beats _step_size;
int32_t _start;
int32_t _end;
Temporal::Beats _last_start;
int _last_step;
size_t _start_step;
size_t _end_step;
samplepos_t last_start;
samplepos_t last_end; /* end sample time of last run() call */
bool _running;
size_t _step_capacity;
ARDOUR::MidiStateTracker outbound_tracker;
struct Request {
/* bitwise types, so we can combine multiple in one
*/
enum Type {
SetStartStep = 0x1,
SetEndStep = 0x2,
SetNSequences = 0x4,
SetStepSize = 0x8,
};
Type type;
Temporal::Beats step_size;
size_t nsequences;
size_t start_step;
size_t end_step;
static MultiAllocSingleReleasePool pool;
void *operator new (size_t) {
return pool.alloc ();
}
void operator delete (void* ptr, size_t /* size */) {
pool.release (ptr);
}
};
PBD::RingBuffer<Request*> requests;
bool check_requests ();
Temporal::Beats reschedule (samplepos_t);
};
} /* namespace */

View File

@@ -42,26 +42,9 @@ using std::endl;
using namespace ARDOUR;
MultiAllocSingleReleasePool BeatBox::Event::pool (X_("beatbox events"), sizeof (Event), 2048);
BeatBox::BeatBox (Session& s)
: Processor (s, _("BeatBox"))
, _sequencer (0)
, _start_requested (false)
, _running (false)
, _measures (2)
, _tempo (-1.0)
, _meter_beats (-1)
, _meter_beat_type (-1)
, last_start (0)
, whole_note_superclocks (0)
, tick_superclocks (0)
, beat_superclocks (0)
, measure_superclocks (0)
, _quantize_divisor (4)
, clear_pending (false)
, add_queue (64)
, remove_queue (64)
{
_display_to_user = true;
_sequencer = new StepSequencer (s.tempo_map(), 12, 32, Temporal::Beats (0, Temporal::Beats::PPQN/8), Temporal::Beats (4, 0), 40);
@@ -72,20 +55,6 @@ BeatBox::~BeatBox ()
delete _sequencer;
}
void
BeatBox::start ()
{
/* we can start */
_start_requested = true;
}
void
BeatBox::stop ()
{
_start_requested = false;
}
void
BeatBox::silence (samplecnt_t, samplepos_t)
{
@@ -93,80 +62,13 @@ BeatBox::silence (samplecnt_t, samplepos_t)
}
void
BeatBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, 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;
}
bool resolve = false;
if (speed == 0) {
if (_running) {
resolve = true;
_running = false;
}
}
if (speed != 0) {
if (!_running || (last_end != start_sample)) {
if (last_end != start_sample) {
resolve = true;
}
/* compute the beat position of this first "while-moving
* run() call as an offset into the sequencer's current loop
* length.
*/
TempoMap& tmap (_session.tempo_map());
const Temporal::Beats start_beat (tmap.beat_at_sample (start_sample));
const int32_t tick_duration = _sequencer->duration().to_ticks();
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;
}
}
if (resolve) {
outbound_tracker.resolve_notes (bufs.get_midi(0), 0);
}
_sequencer->run (bufs.get_midi (0), _running, start_sample, end_sample, outbound_tracker);
last_end = end_sample;
}
void
BeatBox::set_quantize (int divisor)
{
_quantize_divisor = divisor;
}
void
BeatBox::clear ()
{
clear_pending = true;
}
bool
BeatBox::EventComparator::operator() (Event const * a, Event const *b) const
{
if (a->time == b->time) {
if (a->buf[0] == b->buf[0]) {
return a < b;
}
return !ARDOUR::MidiBuffer::second_simultaneous_midi_byte_is_first (a->buf[0], b->buf[0]);
}
return a->time < b->time;
_sequencer->run (bufs.get_midi (0), start_sample, end_sample, speed, nsamples, result_required);
}
bool
@@ -190,68 +92,6 @@ BeatBox::state()
return node;
}
void
BeatBox::edit_note_number (int old_number, int new_number)
{
for (Events::iterator e = _current_events.begin(); e != _current_events.end(); ++e) {
if (((*e)->buf[0] & 0xf0) == MIDI_CMD_NOTE_OFF || ((*e)->buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) {
if ((*e)->buf[1] == old_number) {
(*e)->buf[1] = new_number;
}
}
}
}
void
BeatBox::remove_note (int note, Timecode::BBT_Time at)
{
}
void
BeatBox::add_note (int note, int velocity, Timecode::BBT_Time at)
{
Event* on = new Event; // pool allocated, thread safe
if (!on) {
cerr << "No more events for injection, grow pool\n";
return;
}
/* convert to zero-base */
at.bars--;
at.beats--;
/* clamp to current loop configuration */
at.bars %= _measures;
at.beats %= _meter_beats;
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;
Event* off = new Event; // pool allocated, thread safe
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.sample_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);
}
bool
BeatBox::fill_source (boost::shared_ptr<Source> src)
{
@@ -267,6 +107,7 @@ BeatBox::fill_source (boost::shared_ptr<Source> src)
bool
BeatBox::fill_midi_source (boost::shared_ptr<SMFSource> src)
{
#if 0
Temporal::Beats smf_beats;
if (_current_events.empty()) {
@@ -295,6 +136,6 @@ BeatBox::fill_midi_source (boost::shared_ptr<SMFSource> src)
} catch (...) {
cerr << "Exception during beatbox write to SMF... " << endl;
}
#endif
return false;
}

View File

@@ -30,8 +30,9 @@ using namespace PBD;
using namespace ARDOUR;
using namespace std;
Step::Step (StepSequence &s, Temporal::Beats const & b, int base_note)
Step::Step (StepSequence &s, size_t n, Temporal::Beats const & b, int base_note)
: _sequence (s)
, index (n)
, _enabled (true)
, _nominal_beat (b)
, _skipped (false)
@@ -336,9 +337,9 @@ Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sam
* just to get non-simultaneous on/off events at
* step boundaries.
*/
note.off_at += Temporal::Beats (0, _sequence.step_size().to_ticks() - 1);
note.off_at += Temporal::Beats (0, sequencer().step_size().to_ticks() - 1);
} else {
note.off_at += Temporal::Beats (0, (_sequence.step_size().to_ticks() * _duration.numerator()) / _duration.denominator());
note.off_at += Temporal::Beats (0, (sequencer().step_size().to_ticks() * _duration.numerator()) / _duration.denominator());
}
}
}
@@ -401,22 +402,19 @@ Step::set_state (XMLNode const &, int)
StepSequence::StepSequence (StepSequencer& s, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size, int r)
: _sequencer (s)
, _start (0)
, _end (nsteps - 1)
, _channel (0)
, _step_size (step_size)
, _bar_size (bar_size)
, _root (r)
, _mode (MusicalMode::IonianMajor)
{
Temporal::Beats beats;
for (size_t s = 0; s < nsteps; ++s) {
_steps.push_back (new Step (*this, beats, _root));
beats += step_size;
/* beats is wrong but we will correct in ::schedule */
_steps.push_back (new Step (*this, s, beats, _root));
}
end_beat = beats;
/* schedule them all from zero for now */
schedule (beats);
}
StepSequence::~StepSequence ()
@@ -427,16 +425,29 @@ StepSequence::~StepSequence ()
}
void
StepSequence::startup (Temporal::Beats const & start, Temporal::Beats const & offset)
StepSequence::schedule (Temporal::Beats const & start)
{
for (Steps::iterator i = _steps.begin(); i != _steps.end(); ++i) {
(*i)->reschedule (start, offset);
Temporal::Beats beats (start);
const size_t s = _sequencer.start_step();
const size_t e = _sequencer.end_step();
for (size_t n = s; n < e; ++n) {
_steps[n]->set_beat (beats);
cerr << "beat " << n << " @ " << beats << ' ';
beats += _sequencer.step_size();
}
cerr << endl;
}
void
StepSequence::reset ()
StepSequence::reschedule (Temporal::Beats const & start, Temporal::Beats const & offset)
{
const size_t s = _sequencer.start_step();
const size_t e = _sequencer.end_step();
for (size_t n = s; n < e; ++n) {
_steps[n]->reschedule (start, offset);
}
}
void
@@ -445,23 +456,32 @@ 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);
const size_t s = _sequencer.start_step();
const size_t e = _sequencer.end_step();
const size_t t = _steps.size();
size_t n = 0;
/* steps before the start step .. may still have pending off's or ... */
while (n < s) {
_steps[n++]->run (buf, false, start_sample, end_sample, tracker);
}
/* currently in use steps */
while (n < e) {
_steps[n++]->run (buf, running, start_sample, end_sample, tracker);
}
/* steps after the end step .. may still have pending off's or ... */
while (n < t) {
_steps[n++]->run (buf, false, start_sample, end_sample, tracker);
}
return true;
}
@@ -487,12 +507,19 @@ StepSequence::set_state (XMLNode const &, int)
/**/
MultiAllocSingleReleasePool StepSequencer::Request::pool (X_("step sequencer requests"), sizeof (StepSequencer::Request), 64);
StepSequencer::StepSequencer (TempoMap& tmap, size_t nseqs, size_t nsteps, Temporal::Beats const & step_size, Temporal::Beats const & bar_size, int notenum)
: _tempo_map (tmap)
, _step_size (step_size)
, _start (0)
, _end (nsteps)
, _last_step (0)
, _step_size (step_size)
, _start_step (0)
, _end_step (nsteps)
, last_start (0)
, last_end (0)
, _running (false)
, _step_capacity (nsteps)
, requests (64)
{
for (size_t n = 0; n < nseqs; ++n) {
_sequences.push_back (new StepSequence (*this, nsteps, step_size, bar_size, notenum));
@@ -507,19 +534,82 @@ StepSequencer::~StepSequencer ()
}
}
Temporal::Beats
StepSequencer::reschedule (samplepos_t start_sample)
{
cerr << "SEQ reschedule\n";
/* compute the beat position of this first "while-moving
* run() call as an offset into the sequencer's current loop
* length.
*/
const Temporal::Beats start_beat (_tempo_map.beat_at_sample (start_sample));
const int32_t tick_duration = duration().to_ticks();
const Temporal::Beats closest_previous_loop_start = Temporal::Beats::ticks ((start_beat.to_ticks() / tick_duration) * tick_duration);
const Temporal::Beats offset = Temporal::Beats::ticks ((start_beat.to_ticks() % tick_duration));
for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) {
(*s)->reschedule (closest_previous_loop_start, offset);
}
return closest_previous_loop_start;
}
bool
StepSequencer::run (MidiBuffer& buf, bool running, samplepos_t start_sample, samplepos_t end_sample, MidiStateTracker& tracker)
StepSequencer::run (MidiBuffer& buf, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t, bool)
{
Glib::Threads::Mutex::Lock lm (_sequence_lock);
bool resolve = false;
bool need_reschedule = check_requests ();
if (speed == 0) {
if (_running) {
resolve = true;
_running = false;
}
}
if (speed != 0) {
if (!_running || (last_end != start_sample)) {
if (last_end != start_sample) {
/* non-linear motion, need to resolve notes */
resolve = true;
}
_last_startup = reschedule (start_sample);
last_start = start_sample;
need_reschedule = false;
_running = true;
}
}
if (need_reschedule) {
reschedule (start_sample);
need_reschedule = false;
}
for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) {
(*s)->run (buf, running, start_sample, end_sample, tracker);
(*s)->run (buf, _running, start_sample, end_sample, outbound_tracker);
}
const Temporal::Beats terminal_beat = Temporal::Beats (_tempo_map.beat_at_sample (end_sample - 1));
const size_t dur_ticks = duration().to_ticks();
const size_t step_ticks = _step_size.to_ticks();
_last_step = ((terminal_beat - _last_start).to_ticks() % dur_ticks) / step_ticks;
_last_step = _start_step + (((terminal_beat - _last_startup).to_ticks() % dur_ticks) / step_ticks);
if (resolve) {
outbound_tracker.resolve_notes (buf, 0);
}
last_start = start_sample;
last_end = end_sample;
return true;
}
@@ -549,20 +639,12 @@ StepSequencer::reset ()
Temporal::Beats
StepSequencer::duration() const
{
return _step_size * (_end - _start) ;
return _step_size * (_end_step - _start_step) ;
}
void
StepSequencer::startup (Temporal::Beats const & start, Temporal::Beats const & offset)
StepSequence::reset ()
{
_last_start = start;
{
Glib::Threads::Mutex::Lock lm1 (_sequence_lock);
for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) {
(*s)->startup (start, offset);
}
}
}
StepSequence&
@@ -572,6 +654,24 @@ StepSequencer::sequence (size_t n) const
return *_sequences[n];
}
void
StepSequencer::set_start_step (size_t n)
{
Request* r = new Request;
r->type = Request::SetStartStep;
r->start_step = n;
requests.write (&r, 1);
}
void
StepSequencer::set_end_step (size_t n)
{
Request* r = new Request;
r->type = Request::SetEndStep;
r->end_step = n;
requests.write (&r, 1);
}
XMLNode&
StepSequencer::get_state()
{
@@ -583,3 +683,54 @@ StepSequencer::set_state (XMLNode const &, int)
{
return 0;
}
bool
StepSequencer::check_requests ()
{
Request* req;
bool changed = false;
bool reschedule = false;
while (requests.read (&req, 1)) {
if (req->type & Request::SetStartStep) {
if (req->start_step != _start_step) {
if (req->start_step < _end_step) {
_start_step = req->start_step;
reschedule = true;
changed = true;
}
}
}
if (req->type & Request::SetEndStep) {
if (req->end_step != _end_step) {
if (req->end_step > _start_step) {
_end_step = req->end_step;
reschedule = true;
changed = true;
}
}
}
if (req->type & Request::SetNSequences) {
// XXXX
}
if (req->type & Request::SetStepSize) {
if (_step_size != req->step_size) {
_step_size = req->step_size;
reschedule = true;
changed = true;
}
}
}
delete req;
if (changed) {
PropertyChange pc;
PropertyChanged (pc);
}
return reschedule;
}