From c9e4d3f04567266801c195bc8c362d4d0f13420b Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Fri, 16 Nov 2018 12:38:20 -0500 Subject: [PATCH] alter note off handling in step sequencer to be global to the whole sequencer --- libs/ardour/ardour/step_sequencer.h | 38 ++++- libs/ardour/step_sequencer.cc | 209 +++++++++++++--------------- 2 files changed, 130 insertions(+), 117 deletions(-) diff --git a/libs/ardour/ardour/step_sequencer.h b/libs/ardour/ardour/step_sequencer.h index cbd360ef4b..500cbca46b 100644 --- a/libs/ardour/ardour/step_sequencer.h +++ b/libs/ardour/ardour/step_sequencer.h @@ -24,6 +24,7 @@ #include #include +#include #include @@ -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 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 NoteOffList; + + NoteOffList note_offs; + void check_note_offs (ARDOUR::MidiBuffer&, samplepos_t start_sample, samplepos_t last_sample); + void clear_note_offs (); }; } /* namespace */ diff --git a/libs/ardour/step_sequencer.cc b/libs/ardour/step_sequencer.cc index bbaa7bd4a4..94398d2e40 100644 --- a/libs/ardour/step_sequencer.cc +++ b/libs/ardour/step_sequencer.cc @@ -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); + } +}