alter note off handling in step sequencer to be global to the whole sequencer
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <boost/atomic.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <glibmm/threads.h>
|
||||
|
||||
@@ -134,12 +135,10 @@ class Step : public PBD::Stateful {
|
||||
};
|
||||
double velocity;
|
||||
Temporal::Beats offset;
|
||||
bool on;
|
||||
Temporal::Beats off_at;
|
||||
MIDI::byte off_msg[3];
|
||||
|
||||
Note () : number (-1), velocity (0.0), on (false) {}
|
||||
Note (double n, double v,Temporal::Beats const & o) : number (n), velocity (v), offset (o), on (false) {}
|
||||
Note () : number (-1), velocity (0.0) {}
|
||||
Note (double n, double v,Temporal::Beats const & o) : number (n), velocity (v), offset (o) {}
|
||||
};
|
||||
|
||||
static const int _notes_per_step = 5;
|
||||
@@ -247,6 +246,8 @@ class StepSequencer : public PBD::Stateful
|
||||
XMLNode& get_state();
|
||||
int set_state (XMLNode const &, int);
|
||||
|
||||
void queue_note_off (Temporal::Beats const &, uint8_t note, uint8_t velocity, uint8_t channel);
|
||||
|
||||
private:
|
||||
mutable Glib::Threads::Mutex _sequence_lock;
|
||||
TempoMap& _tempo_map;
|
||||
@@ -299,6 +300,35 @@ class StepSequencer : public PBD::Stateful
|
||||
PBD::RingBuffer<Request*> requests;
|
||||
bool check_requests ();
|
||||
Temporal::Beats reschedule (samplepos_t);
|
||||
|
||||
struct NoteOffBlob : public boost::intrusive::list_base_hook<> {
|
||||
|
||||
NoteOffBlob (Temporal::Beats const & w, uint8_t n, uint8_t v, uint8_t c)
|
||||
: when (w) { buf[0] = 0x80|c; buf[1] = n; buf[2] = v; }
|
||||
|
||||
Temporal::Beats when;
|
||||
uint8_t buf[3];
|
||||
|
||||
static Pool pool;
|
||||
|
||||
void *operator new (size_t) {
|
||||
return pool.alloc ();
|
||||
}
|
||||
|
||||
void operator delete (void* ptr, size_t /* size */) {
|
||||
pool.release (ptr);
|
||||
}
|
||||
|
||||
bool operator< (NoteOffBlob const & other) const {
|
||||
return when < other.when;
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::intrusive::list<NoteOffBlob> NoteOffList;
|
||||
|
||||
NoteOffList note_offs;
|
||||
void check_note_offs (ARDOUR::MidiBuffer&, samplepos_t start_sample, samplepos_t last_sample);
|
||||
void clear_note_offs ();
|
||||
};
|
||||
|
||||
} /* namespace */
|
||||
|
||||
@@ -255,30 +255,6 @@ Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sam
|
||||
{
|
||||
Note& note (_notes[n]);
|
||||
|
||||
/* could be a note off message to be delivered before any note on
|
||||
* message (and the note number may differ from the current value.
|
||||
* Deliver it now, if appropriate.
|
||||
*/
|
||||
|
||||
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) {
|
||||
|
||||
buf.write (off_samples - start_sample, Evoral::MIDI_EVENT, 3, note.off_msg);
|
||||
tracker.remove (note.off_msg[1], _sequence.channel());
|
||||
|
||||
/* record keeping */
|
||||
|
||||
note.on = false;
|
||||
note.off_at = Temporal::Beats();
|
||||
}
|
||||
|
||||
/* XXX we should possibly queue these note offs */
|
||||
|
||||
}
|
||||
|
||||
if (_duration == DurationRatio ()) {
|
||||
/* no duration, so no new notes on */
|
||||
return;
|
||||
@@ -295,95 +271,68 @@ Step::check_note (size_t n, MidiBuffer& buf, bool running, samplepos_t start_sam
|
||||
|
||||
note_on_time += note.offset;
|
||||
|
||||
if (running && !note.on) {
|
||||
/* don't play silent notes */
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (_octave_shift) {
|
||||
|
||||
const int t = mbuf[1] + (12 * _octave_shift);
|
||||
|
||||
if (t > 127 || t < 0) {
|
||||
/* Out of range */
|
||||
return;
|
||||
}
|
||||
|
||||
mbuf[1] = t;
|
||||
}
|
||||
|
||||
mbuf[2] = (uint8_t) floor (note.velocity * 127.0);
|
||||
|
||||
note.off_msg[0] = 0x80 | _sequence.channel();
|
||||
note.off_msg[1] = mbuf[1];
|
||||
note.off_msg[2] = mbuf[2];
|
||||
|
||||
/* 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 (_duration == DurationRatio (1)) {
|
||||
/* use 1 tick less than the sequence step size
|
||||
* just to get non-simultaneous on/off events at
|
||||
* step boundaries.
|
||||
*/
|
||||
note.off_at += Temporal::Beats (0, sequencer().step_size().to_ticks() - 1);
|
||||
} else {
|
||||
note.off_at += Temporal::Beats (0, (sequencer().step_size().to_ticks() * _duration.numerator()) / _duration.denominator());
|
||||
}
|
||||
}
|
||||
if (note.velocity == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* if the buffer size is large and the step size or note length is very
|
||||
* small, the note off could be within the same ::run() cycle as the
|
||||
* note on. So check again to see if we should deliver it in this same
|
||||
* ::run() cycle.
|
||||
*/
|
||||
samplepos_t on_samples = sequencer().tempo_map().sample_at_beat (note_on_time.to_double());
|
||||
|
||||
if (note.on) {
|
||||
if (on_samples >= start_sample && on_samples < end_sample) {
|
||||
|
||||
samplepos_t off_samples = sequencer().tempo_map().sample_at_beat (note.off_at.to_double());
|
||||
uint8_t mbuf[3];
|
||||
|
||||
if (off_samples >= start_sample && off_samples < end_sample) {
|
||||
/* prepare 3 MIDI bytes for note on */
|
||||
|
||||
buf.write (off_samples - start_sample, Evoral::MIDI_EVENT, 3, note.off_msg);
|
||||
tracker.remove (note.off_msg[1], _sequence.channel());
|
||||
mbuf[0] = 0x90 | _sequence.channel();
|
||||
|
||||
/* record keeping */
|
||||
|
||||
note.on = false;
|
||||
note.off_at = Temporal::Beats();
|
||||
switch (_mode) {
|
||||
case AbsolutePitch:
|
||||
mbuf[1] = note.number;
|
||||
break;
|
||||
case RelativePitch:
|
||||
mbuf[1] = _sequence.root() + note.interval;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_octave_shift) {
|
||||
|
||||
const int t = mbuf[1] + (12 * _octave_shift);
|
||||
|
||||
if (t > 127 || t < 0) {
|
||||
/* Out of range */
|
||||
return;
|
||||
}
|
||||
|
||||
mbuf[1] = t;
|
||||
}
|
||||
|
||||
mbuf[2] = (uint8_t) floor (note.velocity * 127.0);
|
||||
|
||||
note.off_msg[0] = 0x80 | _sequence.channel();
|
||||
note.off_msg[1] = mbuf[1];
|
||||
note.off_msg[2] = mbuf[2];
|
||||
|
||||
/* Put it into the MIDI buffer */
|
||||
buf.write (on_samples - start_sample, Evoral::MIDI_EVENT, 3, mbuf);
|
||||
tracker.add (mbuf[1], _sequence.channel());
|
||||
|
||||
/* compute note off time based on our duration */
|
||||
|
||||
Temporal::Beats off_at = note_on_time;
|
||||
|
||||
if (_duration == DurationRatio (1)) {
|
||||
/* use 1 tick less than the sequence step size
|
||||
* just to get non-simultaneous on/off events at
|
||||
* step boundaries.
|
||||
*/
|
||||
off_at += Temporal::Beats (0, sequencer().step_size().to_ticks() - 1);
|
||||
} else {
|
||||
off_at += Temporal::Beats (0, (sequencer().step_size().to_ticks() * _duration.numerator()) / _duration.denominator());
|
||||
}
|
||||
|
||||
sequencer().queue_note_off (off_at, mbuf[1], mbuf[2], _sequence.channel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,15 +344,6 @@ Step::reschedule (Temporal::Beats const & start, Temporal::Beats const & offset)
|
||||
} else {
|
||||
_scheduled_beat = start + _nominal_beat; /* schedule into the current loop iteration */
|
||||
}
|
||||
|
||||
/* MIDI state tracker will deal with any stuck notes, so here we just
|
||||
* update our records to note that all notes are not currently
|
||||
* sounding.
|
||||
*/
|
||||
for (size_t n = 0; n < _notes_per_step; ++n) {
|
||||
_notes[n].on = false;
|
||||
_notes[n].off_at = Temporal::Beats();
|
||||
}
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
@@ -529,6 +469,7 @@ StepSequence::set_state (XMLNode const &, int)
|
||||
/**/
|
||||
|
||||
MultiAllocSingleReleasePool StepSequencer::Request::pool (X_("step sequencer requests"), sizeof (StepSequencer::Request), 64);
|
||||
Pool StepSequencer::NoteOffBlob::pool (X_("step sequencer noteoffs"), sizeof (StepSequencer::NoteOffBlob), 1024);
|
||||
|
||||
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)
|
||||
@@ -615,6 +556,8 @@ StepSequencer::run (MidiBuffer& buf, samplepos_t start_sample, samplepos_t end_s
|
||||
need_reschedule = false;
|
||||
}
|
||||
|
||||
check_note_offs (buf, start_sample, end_sample);
|
||||
|
||||
for (StepSequences::iterator s = _sequences.begin(); s != _sequences.end(); ++s) {
|
||||
(*s)->run (buf, _running, start_sample, end_sample, outbound_tracker);
|
||||
}
|
||||
@@ -627,6 +570,7 @@ StepSequencer::run (MidiBuffer& buf, samplepos_t start_sample, samplepos_t end_s
|
||||
|
||||
if (resolve) {
|
||||
outbound_tracker.resolve_notes (buf, 0);
|
||||
clear_note_offs ();
|
||||
}
|
||||
|
||||
last_start = start_sample;
|
||||
@@ -755,3 +699,42 @@ StepSequencer::check_requests ()
|
||||
|
||||
return reschedule;
|
||||
}
|
||||
|
||||
void
|
||||
StepSequencer::queue_note_off (Temporal::Beats const & when, uint8_t n, uint8_t v, uint8_t c)
|
||||
{
|
||||
NoteOffBlob* nob = new NoteOffBlob (when, n, v, c);
|
||||
NoteOffList::iterator i = std::upper_bound (note_offs.begin(), note_offs.end(), *nob);
|
||||
note_offs.insert (i, *nob);
|
||||
}
|
||||
|
||||
void
|
||||
StepSequencer::check_note_offs (MidiBuffer& mbuf, samplepos_t start_sample, samplepos_t end_sample)
|
||||
{
|
||||
for (NoteOffList::iterator i = note_offs.begin(); i != note_offs.end(); ) {
|
||||
samplepos_t when = _tempo_map.sample_at_beat (i->when.to_double()); /* XXX nutempo */
|
||||
|
||||
cerr << "note off at " << i->when << " sample " << when << " within " << start_sample << " .. " << end_sample << endl;
|
||||
|
||||
if (when >= start_sample && when < end_sample) {
|
||||
mbuf.write (when - start_sample, Evoral::MIDI_EVENT, 3, i->buf);
|
||||
NoteOffBlob& nob (*i);
|
||||
i = note_offs.erase (i);
|
||||
delete &nob;
|
||||
} else if (when < start_sample) {
|
||||
i = note_offs.erase (i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StepSequencer::clear_note_offs ()
|
||||
{
|
||||
for (NoteOffList::iterator i = note_offs.begin(); i != note_offs.end(); ) {
|
||||
NoteOffBlob& nob (*i);
|
||||
delete &nob;
|
||||
i = note_offs.erase (i);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user