new Temporal API to allow keeping MIDI notes in position after a map-tempo operation
This commit is contained in:
parent
30d2d7824e
commit
fa225846af
@ -25,6 +25,7 @@
|
|||||||
#define __ardour_midi_model_h__
|
#define __ardour_midi_model_h__
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -252,6 +253,9 @@ public:
|
|||||||
PatchChangePtr unmarshal_patch_change (XMLNode *);
|
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.
|
/** Start a new NoteDiff command.
|
||||||
*
|
*
|
||||||
* This has no side-effects on the model or Session, the returned command
|
* This has no side-effects on the model or Session, the returned command
|
||||||
@ -359,6 +363,10 @@ private:
|
|||||||
|
|
||||||
MidiSource& _midi_source;
|
MidiSource& _midi_source;
|
||||||
InsertMergePolicy _insert_merge_policy;
|
InsertMergePolicy _insert_merge_policy;
|
||||||
|
|
||||||
|
typedef std::map<void*,superclock_t> TempoMappingStash;
|
||||||
|
TempoMappingStash tempo_mapping_stash;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ARDOUR */
|
} /* namespace ARDOUR */
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "temporal/beats.h"
|
#include "temporal/beats.h"
|
||||||
#include "temporal/range.h"
|
#include "temporal/range.h"
|
||||||
|
#include "temporal/types.h"
|
||||||
|
|
||||||
#include "pbd/string_convert.h"
|
#include "pbd/string_convert.h"
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ class ThawList;
|
|||||||
|
|
||||||
template<typename T> class MidiRingBuffer;
|
template<typename T> class MidiRingBuffer;
|
||||||
|
|
||||||
class LIBARDOUR_API MidiRegion : public Region
|
class LIBARDOUR_API MidiRegion : public Region, public Temporal::TimeThing
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~MidiRegion();
|
~MidiRegion();
|
||||||
@ -116,6 +117,9 @@ class LIBARDOUR_API MidiRegion : public Region
|
|||||||
timecnt_t const & read_length,
|
timecnt_t const & read_length,
|
||||||
MidiChannelFilter* filter) const;
|
MidiChannelFilter* filter) const;
|
||||||
|
|
||||||
|
void globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to);
|
||||||
|
void swap_domain (Temporal::TimeDomain, Temporal::TimeDomain);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual bool can_trim_start_before_source_start () const {
|
virtual bool can_trim_start_before_source_start () const {
|
||||||
|
@ -114,7 +114,7 @@ public:
|
|||||||
|
|
||||||
const DataType& data_type () const { return _type; }
|
const DataType& data_type () const { return _type; }
|
||||||
Temporal::TimeDomain time_domain() const;
|
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:
|
/** How the region parameters play together:
|
||||||
*
|
*
|
||||||
|
@ -770,9 +770,13 @@ Location::globally_change_time_domain (Temporal::TimeDomain from, Temporal::Time
|
|||||||
|
|
||||||
if (_start.time_domain() == from) {
|
if (_start.time_domain() == from) {
|
||||||
|
|
||||||
|
std::cerr << "switching location [" << name() << "] from " << _start;
|
||||||
|
|
||||||
_start.set_time_domain (to);
|
_start.set_time_domain (to);
|
||||||
_end.set_time_domain (to);
|
_end.set_time_domain (to);
|
||||||
|
|
||||||
|
std::cerr << " to " << _start << std::endl;
|
||||||
|
|
||||||
domain_swap->add (_start);
|
domain_swap->add (_start);
|
||||||
domain_swap->add (_end);
|
domain_swap->add (_end);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1772,3 +1772,101 @@ MidiModel::control_list_marked_dirty ()
|
|||||||
|
|
||||||
ContentsChanged (); /* EMIT SIGNAL */
|
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 ();
|
||||||
|
}
|
||||||
|
@ -612,3 +612,25 @@ MidiRegion::merge (std::shared_ptr<MidiRegion const> other_region)
|
|||||||
|
|
||||||
set_length (max (length(), position().distance (other_region->end())));
|
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);
|
||||||
|
}
|
||||||
|
@ -2199,7 +2199,9 @@ Region::globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDo
|
|||||||
|
|
||||||
if (_length.val().time_domain() == from) {
|
if (_length.val().time_domain() == from) {
|
||||||
timecnt_t& l (_length.non_const_val());
|
timecnt_t& l (_length.non_const_val());
|
||||||
|
std::cerr << "old domain after GCTD " << _length.val() << std::endl;
|
||||||
l.set_time_domain (to);
|
l.set_time_domain (to);
|
||||||
Temporal::domain_swap->add (l);
|
Temporal::domain_swap->add (l);
|
||||||
|
std::cerr << "new domain after GCTD " << _length.val() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,6 +302,8 @@ Session::any_duration_to_samples (samplepos_t position, AnyTime const & duration
|
|||||||
void
|
void
|
||||||
Session::globally_change_time_domain (Temporal::TimeDomain from, Temporal::TimeDomain to)
|
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());
|
std::shared_ptr<RouteList const> rl (routes.reader());
|
||||||
|
|
||||||
|
@ -4882,6 +4882,10 @@ DomainSwapInformation::undo ()
|
|||||||
p->set_time_domain (previous);
|
p->set_time_domain (previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto & tt : time_things) {
|
||||||
|
tt->swap_domain (previous == AudioTime ? BeatTime : AudioTime, previous);
|
||||||
|
}
|
||||||
|
|
||||||
clear ();
|
clear ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1273,6 +1273,7 @@ class LIBTEMPORAL_API DomainSwapInformation {
|
|||||||
|
|
||||||
void add (timecnt_t& t) { counts.push_back (&t); }
|
void add (timecnt_t& t) { counts.push_back (&t); }
|
||||||
void add (timepos_t& p) { positions.push_back (&p); }
|
void add (timepos_t& p) { positions.push_back (&p); }
|
||||||
|
void add (TimeThing& tt) { time_things.push_back (&tt); }
|
||||||
void clear ();
|
void clear ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -1280,6 +1281,7 @@ class LIBTEMPORAL_API DomainSwapInformation {
|
|||||||
|
|
||||||
std::vector<timecnt_t*> counts;
|
std::vector<timecnt_t*> counts;
|
||||||
std::vector<timepos_t*> positions;
|
std::vector<timepos_t*> positions;
|
||||||
|
std::vector<TimeThing*> time_things;
|
||||||
TimeDomain previous;
|
TimeDomain previous;
|
||||||
|
|
||||||
void undo ();
|
void undo ();
|
||||||
|
@ -124,6 +124,11 @@ enum RoundMode {
|
|||||||
|
|
||||||
extern void setup_enum_writer ();
|
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);
|
std::ostream& operator<< (std::ostream& o, Temporal::ratio_t const & r);
|
||||||
|
Loading…
Reference in New Issue
Block a user