start removal of NoteFixer code

Plan is to always read MIDI directly from rendered version. MidiPlaylistSource needs
attention before MidiPlaylist::read() can be removed
This commit is contained in:
Paul Davis 2019-10-17 23:16:55 -06:00
parent addebc3240
commit c0a1aec516
5 changed files with 6 additions and 323 deletions

View file

@ -28,11 +28,12 @@
#include <boost/utility.hpp> #include <boost/utility.hpp>
#include "evoral/Parameter.hpp"
#include "ardour/ardour.h" #include "ardour/ardour.h"
#include "ardour/midi_cursor.h" #include "ardour/midi_cursor.h"
#include "ardour/midi_model.h" #include "ardour/midi_model.h"
#include "ardour/midi_state_tracker.h" #include "ardour/midi_state_tracker.h"
#include "ardour/note_fixer.h"
#include "ardour/playlist.h" #include "ardour/playlist.h"
#include "evoral/Note.hpp" #include "evoral/Note.hpp"
#include "evoral/Parameter.hpp" #include "evoral/Parameter.hpp"
@ -116,20 +117,8 @@ protected:
void region_going_away (boost::weak_ptr<Region> region); void region_going_away (boost::weak_ptr<Region> region);
private: private:
typedef Evoral::Note<Temporal::Beats> Note;
typedef Evoral::Event<samplepos_t> Event;
struct RegionTracker : public boost::noncopyable {
MidiCursor cursor; ///< Cursor (iterator and read state)
MidiStateTracker tracker; ///< Active note tracker
NoteFixer fixer; ///< Edit compensation
};
typedef std::map< Region*, boost::shared_ptr<RegionTracker> > NoteTrackers;
void dump () const; void dump () const;
NoteTrackers _note_trackers;
NoteMode _note_mode; NoteMode _note_mode;
samplepos_t _read_end; samplepos_t _read_end;

View file

@ -1,104 +0,0 @@
/*
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __ardour_note_fixer_h__
#define __ardour_note_fixer_h__
#include <list>
#include <boost/utility.hpp>
#include "temporal/beats.h"
#include "evoral/Note.hpp"
#include "ardour/midi_model.h"
#include "ardour/types.h"
namespace Evoral { template<typename Time> class EventSink; }
namespace ARDOUR {
class BeatsSamplesConverter;
class MidiStateTracker;
class TempoMap;
/** A tracker and compensator for note edit operations.
*
* This monitors edit operations sent to a model that affect active notes
* during a read, and maintains a queue of synthetic events that should be sent
* at the start of the next read to maintain coherent MIDI state.
*/
class NoteFixer : public boost::noncopyable
{
public:
typedef Evoral::Note<Temporal::Beats> Note;
~NoteFixer();
/** Clear all internal state. */
void clear();
/** Handle a region edit during read.
*
* This must be called before the command is applied to the model. Events
* are enqueued to compensate for edits which should be later sent with
* emit() at the start of the next read.
*
* @param tempo_map The tempo-map
* @param cmd Command to compensate for.
* @param origin Timeline position of edited source.
* @param pos Current read position (last read end).
* @param active_notes currently active notes (read/write)
*/
void prepare(TempoMap& tempo_map,
const MidiModel::NoteDiffCommand* cmd,
samplepos_t origin,
samplepos_t pos,
std::set< boost::weak_ptr<Note> >& active_notes);
/** Emit any pending edit compensation events.
*
* @param dst Destination for events.
* @param pos Timestamp to be used for every event, should be the start of
* the read block immediately following any calls to prepare().
* @param tracker Tracker to update with emitted events.
*/
void emit(Evoral::EventSink<samplepos_t>& dst,
samplepos_t pos,
MidiStateTracker& tracker);
private:
typedef Evoral::Event<samplepos_t> Event;
typedef std::list<Event*> Events;
/** Copy a beats event to a samples event with the given time stamp. */
Event* copy_event(samplepos_t time, const Evoral::Event<Temporal::Beats>& ev);
/** Return true iff `note` is active at `pos`. */
bool note_is_active(const BeatsSamplesConverter& converter,
boost::shared_ptr<Note> note,
samplepos_t pos);
Events _events;
};
} /* namespace ARDOUR */
#endif /* __ardour_note_fixer_h__ */

View file

@ -123,14 +123,7 @@ MidiPlaylist::read (Evoral::EventSink<samplepos_t>& dst,
Playlist::RegionReadLock rl (this); Playlist::RegionReadLock rl (this);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("---- MidiPlaylist::read %1 .. %2 ----\n", start, start + dur));
string_compose ("---- MidiPlaylist::read %1 .. %2 (%3 trackers) ----\n",
start, start + dur, _note_trackers.size()));
/* First, emit any queued edit fixup events at start. */
for (NoteTrackers::iterator t = _note_trackers.begin(); t != _note_trackers.end(); ++t) {
t->second->fixer.emit(dst, _read_end, t->second->tracker);
}
/* Find relevant regions that overlap [start..end] */ /* Find relevant regions that overlap [start..end] */
const samplepos_t end = start + dur - 1; const samplepos_t end = start + dur - 1;
@ -184,52 +177,20 @@ MidiPlaylist::read (Evoral::EventSink<samplepos_t>& dst,
continue; continue;
} }
/* Get the existing note tracker for this region, or create a new one. */ MidiCursor cursor; // XXX remove me
NoteTrackers::iterator t = _note_trackers.find (mr.get());
bool new_tracker = false;
boost::shared_ptr<RegionTracker> tracker;
if (t == _note_trackers.end()) {
tracker = boost::shared_ptr<RegionTracker>(new RegionTracker);
new_tracker = true;
DEBUG_TRACE (DEBUG::MidiPlaylistIO,
string_compose ("\tPre-read %1 (%2 .. %3): new tracker\n",
mr->name(), mr->position(), mr->last_sample()));
} else {
tracker = t->second;
DEBUG_TRACE (DEBUG::MidiPlaylistIO,
string_compose ("\tPre-read %1 (%2 .. %3): %4 active notes\n",
mr->name(), mr->position(), mr->last_sample(), tracker->tracker.on()));
}
/* Read from region into target. */ /* Read from region into target. */
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("read from %1 at %2 for %3 LR %4 .. %5\n", DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("read from %1 at %2 for %3 LR %4 .. %5\n",
mr->name(), start, dur, mr->name(), start, dur,
(loop_range ? loop_range->from : -1), (loop_range ? loop_range->from : -1),
(loop_range ? loop_range->to : -1))); (loop_range ? loop_range->to : -1)));
mr->read_at (tgt, start, dur, loop_range, tracker->cursor, chan_n, _note_mode, &tracker->tracker, filter); mr->read_at (tgt, start, dur, loop_range, cursor, chan_n, _note_mode, 0, filter);
DEBUG_TRACE (DEBUG::MidiPlaylistIO,
string_compose ("\tPost-read: %1 active notes\n", tracker->tracker.on()));
if (find (ended.begin(), ended.end(), *i) != ended.end()) { if (find (ended.begin(), ended.end(), *i) != ended.end()) {
/* Region ended within the read range, so resolve any active notes /* Region ended within the read range, so resolve any active notes
(either stuck notes in the data, or notes that end after the end (either stuck notes in the data, or notes that end after the end
of the region). */ of the region). */
DEBUG_TRACE (DEBUG::MidiPlaylistIO, DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 ended, resolve notes and delete\n", mr->name()));
string_compose ("\t%1 ended, resolve notes and delete (%2) tracker\n",
mr->name(), ((new_tracker) ? "new" : "old")));
tracker->tracker.resolve_notes (tgt, loop_range ? loop_range->squish ((*i)->last_sample()) : (*i)->last_sample());
tracker->cursor.invalidate (false);
if (!new_tracker) {
_note_trackers.erase (t);
}
} else {
if (new_tracker) {
_note_trackers.insert (make_pair (mr.get(), tracker));
DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
}
} }
} }
@ -254,29 +215,16 @@ MidiPlaylist::read (Evoral::EventSink<samplepos_t>& dst,
void void
MidiPlaylist::reset_note_trackers () MidiPlaylist::reset_note_trackers ()
{ {
Playlist::RegionWriteLock rl (this, false);
DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 reset all note trackers\n", name()));
_note_trackers.clear ();
} }
void void
MidiPlaylist::resolve_note_trackers (Evoral::EventSink<samplepos_t>& dst, samplepos_t time) MidiPlaylist::resolve_note_trackers (Evoral::EventSink<samplepos_t>& dst, samplepos_t time)
{ {
Playlist::RegionWriteLock rl (this, false);
for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
n->second->tracker.resolve_notes(dst, time);
}
DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 resolve all note trackers\n", name()));
_note_trackers.clear ();
} }
void void
MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region) MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
{ {
/* MIDI regions have no dependents (crossfades) but we might be tracking notes */
_note_trackers.erase(region.get());
} }
void void
@ -353,11 +301,6 @@ MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
i = tmp; i = tmp;
} }
NoteTrackers::iterator t = _note_trackers.find(region.get());
if (t != _note_trackers.end()) {
_note_trackers.erase(t);
}
} }
if (changed) { if (changed) {

View file

@ -1,144 +0,0 @@
/*
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "evoral/EventList.hpp"
#include "ardour/beats_samples_converter.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/note_fixer.h"
#include "ardour/tempo.h"
namespace ARDOUR {
NoteFixer::~NoteFixer()
{
clear();
}
void
NoteFixer::clear()
{
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
delete *i;
}
}
void
NoteFixer::prepare(TempoMap& tempo_map,
const MidiModel::NoteDiffCommand* cmd,
const samplepos_t origin,
const samplepos_t pos,
std::set< boost::weak_ptr<Note> >& active_notes)
{
typedef MidiModel::NoteDiffCommand Command;
BeatsSamplesConverter converter(tempo_map, origin);
for (Command::NoteList::const_iterator i = cmd->removed_notes().begin();
i != cmd->removed_notes().end(); ++i) {
if (note_is_active(converter, *i, pos)) {
/* Deleted note spans the end of the latest read, so we will never
read its off event. Emit a note off to prevent a stuck note. */
_events.push_back(copy_event(pos, (*i)->off_event()));
active_notes.erase(*i);
}
}
for (Command::NoteList::const_iterator i = cmd->added_notes().begin();
i != cmd->added_notes().end(); ++i) {
if (note_is_active(converter, *i, pos)) {
/* Added note spans the end of the latest read, so we missed its on
event. Emit note on immediately to make the state consistent. */
_events.push_back(copy_event(pos, (*i)->on_event()));
active_notes.insert(*i);
}
}
for (Command::ChangeList::const_iterator i = cmd->changes().begin();
i != cmd->changes().end(); ++i) {
if (!note_is_active(converter, i->note, pos)) {
/* Note is not currently active, no compensation needed. */
continue;
}
/* Changed note spans the end of the latest read. */
if (i->property == Command::NoteNumber) {
/* Note number has changed, end the old note. */
_events.push_back(copy_event(pos, i->note->off_event()));
/* Start a new note on the new note number. The same note object
is active, so we leave active_notes alone. */
Event* on = copy_event(pos, i->note->on_event());
on->buffer()[1] = (uint8_t)i->new_value.get_int();
_events.push_back(on);
} else if (i->property == Command::StartTime &&
converter.to(i->new_value.get_beats()) >= pos) {
/* Start time has moved from before to after the end of the
latest read, end the old note. */
_events.push_back(copy_event(pos, i->note->off_event()));
active_notes.erase(i->note);
} else if (i->property == Command::Length &&
converter.to(i->note->time() + i->new_value.get_beats()) < pos) {
/* Length has shortened to before the end of the latest read,
end the note. */
_events.push_back(copy_event(pos, i->note->off_event()));
active_notes.erase(i->note);
} else if (i->property == Command::Channel) {
/* Channel has changed, end the old note. */
_events.push_back(copy_event(pos, i->note->off_event()));
/* Start a new note on the new channel. See number change above. */
Event* on = copy_event(pos, i->note->on_event());
on->buffer()[0] &= 0xF0;
on->buffer()[0] |= (uint8_t)i->new_value.get_int();
_events.push_back(on);
}
}
}
void
NoteFixer::emit(Evoral::EventSink<samplepos_t>& dst,
samplepos_t pos,
MidiStateTracker& tracker)
{
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
dst.write(pos, (*i)->event_type(), (*i)->size(), (*i)->buffer());
tracker.track(**i);
delete *i;
}
_events.clear();
}
NoteFixer::Event*
NoteFixer::copy_event(samplepos_t time, const Evoral::Event<Temporal::Beats>& ev)
{
return new Event(ev.event_type(), time, ev.size(), ev.buffer());
}
bool
NoteFixer::note_is_active(const BeatsSamplesConverter& converter,
boost::shared_ptr<Note> note,
samplepos_t pos)
{
const samplepos_t start_time = converter.to(note->time());
const samplepos_t end_time = converter.to(note->end_time());
return (start_time < pos && end_time >= pos);
}
} // namespace ARDOUR

View file

@ -153,7 +153,6 @@ libardour_sources = [
'muteable.cc', 'muteable.cc',
'mute_control.cc', 'mute_control.cc',
'mute_master.cc', 'mute_master.cc',
'note_fixer.cc',
'onset_detector.cc', 'onset_detector.cc',
'operations.cc', 'operations.cc',
'pan_controllable.cc', 'pan_controllable.cc',