a grab bag of changes correcting and improving the way MIDI note on/off tracking is done. may/should fix a number of problem with spurious note-offs under a variety of circumstances

git-svn-id: svn://localhost/ardour2/branches/3.0@11074 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2011-12-26 17:01:31 +00:00
parent bda0f938fb
commit 5558b3cf06
14 changed files with 212 additions and 84 deletions

View File

@ -65,6 +65,8 @@ class MidiDiskstream : public Diskstream
void get_playback (MidiBuffer& dst, framecnt_t);
void set_record_enabled (bool yn);
void reset_tracker ();
boost::shared_ptr<MidiPlaylist> midi_playlist () { return boost::dynamic_pointer_cast<MidiPlaylist>(_playlist); }

View File

@ -75,6 +75,8 @@ public:
return g_atomic_int_get(&_channel_mask) & 0x0000FFFF;
}
void reset_tracker ();
protected:
inline bool is_channel_event(uint8_t event_type_byte) {
// mask out channel information

View File

@ -107,6 +107,8 @@ public:
PBD::Signal1<void, boost::weak_ptr<MidiSource> > DataRecorded;
boost::shared_ptr<MidiBuffer> get_gui_feed_buffer () const;
void set_monitoring (MonitorChoice);
void set_input_active (bool);
bool input_active () const;
PBD::Signal0<void> InputActiveChanged;

View File

@ -141,6 +141,8 @@ class Plugin : public PBD::StatefulDestructible, public Latent
}
void realtime_handle_transport_stopped ();
void realtime_locate ();
void monitoring_changed ();
struct PresetRecord {
PresetRecord () : user (true) {}
@ -257,6 +259,8 @@ private:
bool _have_pending_stop_events;
PresetRecord _last_preset;
bool _parameter_changed_since_last_preset;
void resolve_midi ();
};
PluginPtr find_plugin(ARDOUR::Session&, std::string unique_id, ARDOUR::PluginType);

View File

@ -77,6 +77,8 @@ class PluginInsert : public Processor
bool is_midi_instrument() const;
void realtime_handle_transport_stopped ();
void realtime_locate ();
void monitoring_changed ();
struct PluginControl : public AutomationControl
{

View File

@ -87,6 +87,13 @@ class Processor : public SessionObject, public Automatable, public Latent
virtual void realtime_handle_transport_stopped () {}
virtual void realtime_locate () {}
/* most processors won't care about this, but plugins that
receive MIDI or similar data from an input source that
may suddenly go "quiet" because of monitoring changes
need to know about it.
*/
virtual void monitoring_changed() {}
/* note: derived classes should implement state(), NOT get_state(), to allow
us to merge C++ inheritance and XML lack-of-inheritance reasonably
smoothly.

View File

@ -737,16 +737,16 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
start = loop_start + ((start - loop_start) % loop_length);
//cerr << "to " << start << endl;
}
//cerr << "start is " << start << " loopstart: " << loop_start << " loopend: " << loop_end << endl;
// cerr << "start is " << start << " loopstart: " << loop_start << " loopend: " << loop_end << endl;
}
while (dur) {
/* take any loop into account. we can't read past the end of the loop. */
if (loc && (loop_end - start < dur)) {
if (loc && (loop_end - start <= dur)) {
this_read = loop_end - start;
//cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl;
// cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl;
reloop = true;
} else {
reloop = false;
@ -1102,9 +1102,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
no_capture_stuff_to_do:
if (_playlist) {
midi_playlist()->clear_note_trackers ();
}
reset_tracker ();
}
void
@ -1134,6 +1132,10 @@ MidiDiskstream::transport_looped (framepos_t transport_frame)
last_recordable_frame = max_framepos;
was_recording = true;
}
if (!Config->get_seamless_loop()) {
reset_tracker ();
}
}
void
@ -1477,3 +1479,15 @@ MidiDiskstream::get_gui_feed_buffer () const
b->copy (_gui_feed_buffer);
return b;
}
void
MidiDiskstream::reset_tracker ()
{
_playback_buf->reset_tracker ();
boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
if (mp) {
mp->clear_note_trackers ();
}
}

View File

@ -83,8 +83,16 @@ MidiPlaylist::~MidiPlaylist ()
}
template<typename Time>
struct EventsSortByTime {
struct EventsSortByTimeAndType {
bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
if (a->time() == b->time()) {
if (EventTypeMap::instance().type_is_midi (a->event_type()) && EventTypeMap::instance().type_is_midi (b->event_type())) {
/* negate return value since we must return whether
* or not a should sort before b, not b before a
*/
return !MidiBuffer::second_simultaneous_midi_byte_is_first (a->buffer()[0], b->buffer()[0]);
}
}
return a->time() < b->time();
}
};
@ -98,59 +106,50 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framec
*/
Glib::RecMutex::Lock rm (region_lock);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2 +++++++++++++++++++++++++++++++++++++++++++++++\n", start, start + dur));
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2 +++++++ %3 trackers +++++++++++++++++\n",
start, start + dur, _note_trackers.size()));
framepos_t end = start + dur - 1;
// relevent regions overlapping start <--> end
vector< boost::shared_ptr<Region> > regs;
vector< boost::shared_ptr<Region> > ended;
typedef pair<MidiStateTracker*,framepos_t> TrackerInfo;
vector<TrackerInfo> tracker_info;
uint32_t note_cnt = 0;
NoteTrackers::iterator t;
for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
if ((*i)->coverage (start, end) != OverlapNone) {
regs.push_back(*i);
} else {
NoteTrackers::iterator t = _note_trackers.find ((*i).get());
if (t != _note_trackers.end()) {
/* add it the set of trackers we will do note resolution
on, and remove it from the list we are keeping
around, because we don't need it anymore.
/* in this call to coverage, the return value indicates the
* overlap status of the read range (start...end) WRT to
* the region.
*/
if the end of the region (where we want to theoretically resolve notes)
is outside the current read range, then just do it at the start
of this read range.
*/
switch ((*i)->coverage (start, end)) {
case OverlapStart:
case OverlapInternal:
case OverlapExternal:
regs.push_back (*i);
break;
framepos_t resolve_at = (*i)->last_frame();
if (resolve_at < start || resolve_at >= end) {
resolve_at = start;
}
tracker_info.push_back (TrackerInfo (t->second, resolve_at));
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("time to resolve & remove tracker for %1 @ %2\n", (*i)->name(), resolve_at));
note_cnt += (t->second->on());
_note_trackers.erase (t);
}
case OverlapEnd:
/* this region ends within the read range */
regs.push_back (*i);
ended.push_back (*i);
break;
default:
/* we don't care */
break;
}
}
if (note_cnt == 0 && !tracker_info.empty()) {
/* trackers to dispose of, but they have no notes in them */
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Clearing %1 empty trackers\n", tracker_info.size()));
for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
delete (*t).first;
}
tracker_info.clear ();
}
if (regs.size() == 1 && tracker_info.empty()) {
if (regs.size() == 1 &&
(ended.empty() || (ended.size() == 1 && ended.front() == regs.front()))) {
/* just a single region - read directly into dst */
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, no out-of-bound region tracking info\n", regs.front()->name()));
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, ended during this read %2\n", regs.front()->name(),
ended.size()));
boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(regs.front());
@ -172,12 +171,22 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framec
mr->read_at (dst, start, dur, chan_n, _note_mode, tracker);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
if (new_tracker) {
pair<Region*,MidiStateTracker*> newpair;
newpair.first = mr.get();
newpair.second = tracker;
_note_trackers.insert (newpair);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
if (!ended.empty()) {
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 ended in this read, resolve notes and delete (%2) tracker\n",
mr->name(), ((new_tracker) ? "new" : "old")));
tracker->resolve_notes (dst, mr->last_frame());
delete tracker;
if (!new_tracker) {
_note_trackers.erase (t);
}
} else {
if (new_tracker) {
pair<Region*,MidiStateTracker*> newpair;
newpair.first = mr.get();
newpair.second = tracker;
_note_trackers.insert (newpair);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
}
}
}
@ -191,23 +200,12 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framec
Evoral::EventList<framepos_t> evlist;
for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Resolve %1 notes\n", (*t).first->on()));
(*t).first->resolve_notes (evlist, (*t).second);
delete (*t).first;
}
#ifndef NDEBUG
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After resolution we now have %1 events\n", evlist.size()));
for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
}
#endif
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
if (!mr) {
continue;
}
@ -216,7 +214,6 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framec
MidiStateTracker* tracker;
bool new_tracker = false;
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Before %1 (%2 .. %3) we now have %4 events\n", mr->name(), mr->position(), mr->last_frame(), evlist.size()));
if (t == _note_trackers.end()) {
@ -238,21 +235,39 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framec
}
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
#endif
if (find (ended.begin(), ended.end(), *i) != ended.end()) {
if (new_tracker) {
pair<Region*,MidiStateTracker*> newpair;
newpair.first = mr.get();
newpair.second = tracker;
_note_trackers.insert (newpair);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
/* the region ended within the read range, so
* resolve any dangling notes (i.e. notes whose
* end is beyond the end of the region).
*/
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 ended in this read, resolve notes and delete (%2) tracker\n",
mr->name(), ((new_tracker) ? "new" : "old")));
tracker->resolve_notes (evlist, (*i)->last_frame());
delete tracker;
if (!new_tracker) {
_note_trackers.erase (t);
}
} else {
if (new_tracker) {
pair<Region*,MidiStateTracker*> newpair;
newpair.first = mr.get();
newpair.second = tracker;
_note_trackers.insert (newpair).first;
DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
}
}
}
if (!evlist.empty()) {
/* sort the event list */
EventsSortByTime<framepos_t> time_cmp;
evlist.sort (time_cmp);
EventsSortByTimeAndType<framepos_t> cmp;
evlist.sort (cmp);
#ifndef NDEBUG
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Final we now have %1 events\n", evlist.size()));

View File

@ -270,5 +270,12 @@ MidiRingBuffer<T>::dump(ostream& str)
delete [] buf;
}
template<typename T>
void
MidiRingBuffer<T>::reset_tracker ()
{
_tracker.reset ();
}
template class MidiRingBuffer<framepos_t>;

View File

@ -20,6 +20,7 @@
#include <iostream>
#include "pbd/compose.h"
#include "pbd/stacktrace.h"
#include "ardour/debug.h"
#include "ardour/event_type_map.h"
@ -57,15 +58,23 @@ MidiStateTracker::track_note_onoffs (const Evoral::MIDIEvent<MidiBuffer::TimeTyp
void
MidiStateTracker::add (uint8_t note, uint8_t chn)
{
if (_active_notes[note+128 * chn] == 0) {
++_on;
}
++_active_notes[note + 128 * chn];
++_on;
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("MST @ %1 ON %2/%3 total on %4\n",
this, (int) note, (int) chn, _on));
if (_active_notes[note+128 * chn] > 1) {
cerr << this << " note " << (int) note << '/' << (int) chn << " was already on, now at " << (int) _active_notes[note+128*chn] << endl;
}
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("%1 ON %2/%3 voices %5 total on %4\n",
this, (int) note, (int) chn, _on,
(int) _active_notes[note+128 * chn]));
}
void
MidiStateTracker::remove (uint8_t note, uint8_t chn)
{
{
switch (_active_notes[note + 128 * chn]) {
case 0:
break;
@ -75,11 +84,12 @@ MidiStateTracker::remove (uint8_t note, uint8_t chn)
break;
default:
--_active_notes [note + 128 * chn];
break;
break;
}
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("MST @ %1 OFF %2/%3 total on %4\n",
this, (int) note, (int) chn, _on));
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("%1 OFF %2/%3 current voices = %5 total on %4\n",
this, (int) note, (int) chn, _on,
(int) _active_notes[note+128 * chn]));
}
void
@ -102,9 +112,10 @@ MidiStateTracker::track (const MidiBuffer::iterator &from, const MidiBuffer::ite
if (ev.type() == MIDI_CTL_ALL_NOTES_OFF) {
cerr << "State tracker sees ALL_NOTES_OFF, silenceing " << sizeof (_active_notes) << endl;
memset (_active_notes, 0, sizeof (_active_notes));
_on = 0;
} else {
track_note_onoffs (ev);
}
track_note_onoffs (ev);
}
}
@ -123,6 +134,9 @@ MidiStateTracker::resolve_notes (MidiBuffer &dst, framepos_t time)
uint8_t buffer[3] = { MIDI_CMD_NOTE_OFF | channel, note, 0 };
Evoral::MIDIEvent<MidiBuffer::TimeType> noteoff
(MIDI_CMD_NOTE_OFF, time, 3, buffer, false);
/* note that we do not care about failure from
push_back() ... should we warn someone ?
*/
dst.push_back (noteoff);
_active_notes[note + 128 * channel]--;
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("%1: MB-resolved note %2/%3 at %4\n",
@ -150,6 +164,9 @@ MidiStateTracker::resolve_notes (Evoral::EventSink<framepos_t> &dst, framepos_t
buf[0] = MIDI_CMD_NOTE_OFF|channel;
buf[1] = note;
buf[2] = 0;
/* note that we do not care about failure from
write() ... should we warn someone ?
*/
dst.write (time, EventTypeMap::instance().midi_event_type (buf[0]), 3, buf);
_active_notes[note + 128 * channel]--;
DEBUG_TRACE (PBD::DEBUG::MidiTrackers, string_compose ("%1: EVS-resolved note %2/%3 at %4\n",

View File

@ -109,11 +109,11 @@ MidiTrack::set_diskstream (boost::shared_ptr<Diskstream> ds)
{
Track::set_diskstream (ds);
midi_diskstream()->reset_tracker ();
_diskstream->set_track (this);
_diskstream->set_destructive (_mode == Destructive);
_diskstream->set_record_enabled (false);
//_diskstream->monitor_input (false);
_diskstream_data_recorded_connection.disconnect ();
boost::shared_ptr<MidiDiskstream> mds = boost::dynamic_pointer_cast<MidiDiskstream> (ds);
@ -391,6 +391,7 @@ void
MidiTrack::realtime_locate ()
{
Glib::RWLock::ReaderLock lm (_processor_lock, Glib::TRY_LOCK);
if (!lm.locked ()) {
return;
}
@ -398,6 +399,8 @@ MidiTrack::realtime_locate ()
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->realtime_locate ();
}
midi_diskstream()->reset_tracker ();
}
void
@ -734,3 +737,14 @@ MidiTrack::act_on_mute ()
}
}
void
MidiTrack::set_monitoring (MonitorChoice mc)
{
Track::set_monitoring (mc);
boost::shared_ptr<MidiDiskstream> md (midi_diskstream());
if (md) {
md->reset_tracker ();
}
}

View File

@ -70,6 +70,7 @@ Plugin::Plugin (AudioEngine& e, Session& s)
, _have_pending_stop_events (false)
, _parameter_changed_since_last_preset (false)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
}
Plugin::Plugin (const Plugin& other)
@ -83,12 +84,11 @@ Plugin::Plugin (const Plugin& other)
, _have_pending_stop_events (false)
, _parameter_changed_since_last_preset (false)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
}
Plugin::~Plugin ()
{
}
void
@ -242,8 +242,10 @@ Plugin::connect_and_run (BufferSet& bufs,
if (bufs.count().n_midi() > 0) {
/* Track notes that we are sending to the plugin */
MidiBuffer& b = bufs.get_midi (0);
bool looped;
_tracker.track (b.begin(), b.end(), looped);
if (_have_pending_stop_events) {
@ -258,12 +260,29 @@ Plugin::connect_and_run (BufferSet& bufs,
void
Plugin::realtime_handle_transport_stopped ()
{
resolve_midi ();
}
void
Plugin::realtime_locate ()
{
resolve_midi ();
}
void
Plugin::monitoring_changed ()
{
resolve_midi ();
}
void
Plugin::resolve_midi ()
{
/* Create note-offs for any active notes and put them in _pending_stop_events, to be picked
up on the next call to connect_and_run ().
*/
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
_pending_stop_events.get_midi(0).clear ();
_tracker.resolve_notes (_pending_stop_events.get_midi (0), 0);
_have_pending_stop_events = true;
@ -345,3 +364,5 @@ Plugin::set_info (PluginInfoPtr info)
{
_info = info;
}

View File

@ -1282,3 +1282,19 @@ PluginInsert::realtime_handle_transport_stopped ()
(*i)->realtime_handle_transport_stopped ();
}
}
void
PluginInsert::realtime_locate ()
{
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
(*i)->realtime_locate ();
}
}
void
PluginInsert::monitoring_changed ()
{
for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
(*i)->monitoring_changed ();
}
}

View File

@ -890,6 +890,11 @@ Track::set_monitoring (MonitorChoice mc)
{
if (mc != _monitoring) {
_monitoring = mc;
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->monitoring_changed ();
}
MonitoringChanged (); /* EMIT SIGNAL */
}
}