mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-17 04:06:26 +01:00
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:
parent
addebc3240
commit
c0a1aec516
5 changed files with 6 additions and 323 deletions
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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__ */
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue