new Temporal API to allow keeping MIDI notes in position after a map-tempo operation

This commit is contained in:
Paul Davis 2023-08-02 14:10:51 -06:00
parent 30d2d7824e
commit fa225846af
11 changed files with 153 additions and 2 deletions

View File

@ -25,6 +25,7 @@
#define __ardour_midi_model_h__
#include <deque>
#include <map>
#include <queue>
#include <utility>
@ -252,6 +253,9 @@ public:
PatchChangePtr unmarshal_patch_change (XMLNode *);
};
void create_mapping_stash (Temporal::Beats const & offset);
void rebuild_from_mapping_stash (Temporal::Beats const & offset);
/** Start a new NoteDiff command.
*
* This has no side-effects on the model or Session, the returned command
@ -359,6 +363,10 @@ private:
MidiSource& _midi_source;
InsertMergePolicy _insert_merge_policy;
typedef std::map<void*,superclock_t> TempoMappingStash;
TempoMappingStash tempo_mapping_stash;
};
} /* namespace ARDOUR */

View File

@ -27,6 +27,7 @@
#include "temporal/beats.h"
#include "temporal/range.h"
#include "temporal/types.h"
#include "pbd/string_convert.h"
@ -54,7 +55,7 @@ class ThawList;
template<typename T> class MidiRingBuffer;
class LIBARDOUR_API MidiRegion : public Region
class LIBARDOUR_API MidiRegion : public Region, public Temporal::TimeThing
{
public:
~MidiRegion();
@ -116,6 +117,9 @@ class LIBARDOUR_API MidiRegion : public Region
timecnt_t const & read_length,
MidiChannelFilter* filter) const;
void globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to);
void swap_domain (Temporal::TimeDomain, Temporal::TimeDomain);
protected:
virtual bool can_trim_start_before_source_start () const {

View File

@ -114,7 +114,7 @@ public:
const DataType& data_type () const { return _type; }
Temporal::TimeDomain time_domain() const;
void globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to);
virtual void globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to);
/** How the region parameters play together:
*

View File

@ -770,9 +770,13 @@ Location::globally_change_time_domain (Temporal::TimeDomain from, Temporal::Time
if (_start.time_domain() == from) {
std::cerr << "switching location [" << name() << "] from " << _start;
_start.set_time_domain (to);
_end.set_time_domain (to);
std::cerr << " to " << _start << std::endl;
domain_swap->add (_start);
domain_swap->add (_end);
} else {

View File

@ -1772,3 +1772,101 @@ MidiModel::control_list_marked_dirty ()
ContentsChanged (); /* EMIT SIGNAL */
}
void
MidiModel::create_mapping_stash (Temporal::Beats const & src_pos_offset)
{
using namespace Evoral;
using namespace Temporal;
TempoMap::SharedPtr tmap (TempoMap::use());
if (!tempo_mapping_stash.empty()) {
return;
}
for (auto const & n : notes()) {
Event<Beats>& on (n->on_event());
superclock_t audio_time = tmap->superclock_at (src_pos_offset + on.time());
tempo_mapping_stash.insert (std::make_pair (&on, audio_time));
Event<Beats>& off (n->off_event());
audio_time = tmap->superclock_at (src_pos_offset + off.time());
tempo_mapping_stash.insert (std::make_pair (&off, audio_time));
}
for (auto const & s : sysexes()) {
superclock_t audio_time = tmap->superclock_at (src_pos_offset + s->time());
tempo_mapping_stash.insert (std::make_pair ((void*)s.get(), audio_time));
}
for (uint8_t chan = 0; chan < 16; ++chan) {
for (auto const & p : pitches(chan)) {
superclock_t audio_time = tmap->superclock_at (src_pos_offset + p->time());
tempo_mapping_stash.insert (std::make_pair ((void*) &p, audio_time));
}
}
for (auto & c : controls()) {
std::shared_ptr<Evoral::ControlList> l = c.second->list();
if (l) {
l->set_time_domain (AudioTime);
}
}
}
void
MidiModel::rebuild_from_mapping_stash (Temporal::Beats const & src_pos_offset)
{
using namespace Evoral;
using namespace Temporal;
if (tempo_mapping_stash.empty()) {
return;
}
TempoMap::SharedPtr tmap (TempoMap::use());
for (auto & n : notes()) {
Event<Beats>& on (n->on_event());
Event<Beats>& off (n->off_event());
TempoMappingStash::iterator tms (tempo_mapping_stash.find (&on));
assert (tms != tempo_mapping_stash.end());
Beats beat_time (tmap->quarters_at_superclock (tms->second) - src_pos_offset);
on.set_time (beat_time);
tms = tempo_mapping_stash.find (&off);
assert (tms != tempo_mapping_stash.end());
beat_time = tmap->quarters_at_superclock (tms->second) - src_pos_offset;
off.set_time (beat_time);
}
for (auto & s : sysexes()) {
TempoMappingStash::iterator tms (tempo_mapping_stash.find ((void*) &s));
assert (tms != tempo_mapping_stash.end());
Beats beat_time (tmap->quarters_at_superclock (tms->second) - src_pos_offset);
s->set_time (beat_time);
}
for (uint8_t chan = 0; chan < 16; ++chan) {
for (auto & p : pitches(chan)) {
TempoMappingStash::iterator tms (tempo_mapping_stash.find ((void*) &p));
assert (tms != tempo_mapping_stash.end());
Beats beat_time (tmap->quarters_at_superclock (tms->second) - src_pos_offset);
p->set_time (beat_time);
}
}
for (auto & c : controls()) {
std::shared_ptr<Evoral::ControlList> l = c.second->list();
if (l) {
l->set_time_domain (BeatTime);
}
}
tempo_mapping_stash.clear ();
}

View File

@ -612,3 +612,25 @@ MidiRegion::merge (std::shared_ptr<MidiRegion const> other_region)
set_length (max (length(), position().distance (other_region->end())));
}
void
MidiRegion::swap_domain (Temporal::TimeDomain from, Temporal::TimeDomain to)
{
if (from == Temporal::BeatTime) {
model()->create_mapping_stash (source_position().beats());
} else {
model()->rebuild_from_mapping_stash (source_position().beats());
_model_changed_connection.disconnect ();
model()->ContentsChanged ();
model()->ContentsChanged.connect_same_thread (_model_changed_connection, boost::bind (&MidiRegion::model_contents_changed, this));
}
}
void
MidiRegion::globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to)
{
Region::globally_change_time_domain (from, to);
swap_domain (from, to);
Temporal::domain_swap->add (*this);
}

View File

@ -2199,7 +2199,9 @@ Region::globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDo
if (_length.val().time_domain() == from) {
timecnt_t& l (_length.non_const_val());
std::cerr << "old domain after GCTD " << _length.val() << std::endl;
l.set_time_domain (to);
Temporal::domain_swap->add (l);
std::cerr << "new domain after GCTD " << _length.val() << std::endl;
}
}

View File

@ -302,6 +302,8 @@ Session::any_duration_to_samples (samplepos_t position, AnyTime const & duration
void
Session::globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to)
{
std::cerr << "GCTD from " << from << " to " << to << std::endl;
{
std::shared_ptr<RouteList const> rl (routes.reader());

View File

@ -4882,6 +4882,10 @@ DomainSwapInformation::undo ()
p->set_time_domain (previous);
}
for (auto & tt : time_things) {
tt->swap_domain (previous == AudioTime ? BeatTime : AudioTime, previous);
}
clear ();
}

View File

@ -1273,6 +1273,7 @@ class LIBTEMPORAL_API DomainSwapInformation {
void add (timecnt_t& t) { counts.push_back (&t); }
void add (timepos_t& p) { positions.push_back (&p); }
void add (TimeThing& tt) { time_things.push_back (&tt); }
void clear ();
private:
@ -1280,6 +1281,7 @@ class LIBTEMPORAL_API DomainSwapInformation {
std::vector<timecnt_t*> counts;
std::vector<timepos_t*> positions;
std::vector<TimeThing*> time_things;
TimeDomain previous;
void undo ();

View File

@ -124,6 +124,11 @@ enum RoundMode {
extern void setup_enum_writer ();
struct LIBTEMPORAL_API TimeThing {
virtual ~TimeThing() {}
virtual void swap_domain (Temporal::TimeDomain from, Temporal::TimeDomain to) = 0;
};
}
std::ostream& operator<< (std::ostream& o, Temporal::ratio_t const & r);