Compare commits

...

3 Commits

5 changed files with 134 additions and 7 deletions

View File

@ -94,6 +94,9 @@ STATIC(SetSession, &LuaInstance::SetSession, 0)
// Editor Selection Changed
STATIC(SelectionChanged, &LuaInstance::SelectionChanged, 0)
// Tempo Map Changed (old_map, new_map, from_undo)
STATIC(TempoMapChanged, &Temporal::TempoMap::MapChanged, 3)
// TODO per track/route signals,
// TODO per plugin actions / controllables
// TODO per region actions

View File

@ -234,7 +234,7 @@ CLASSINFO(UIConfiguration);
/* this needs to match gtk2_ardour/luasignal.h */
CLASSKEYS(std::bitset<50ul>); // LuaSignal::LAST_SIGNAL
CLASSKEYS(std::bitset<51ul>); // LuaSignal::LAST_SIGNAL
CLASSKEYS(void);
CLASSKEYS(float);

View File

@ -49,7 +49,8 @@ std::string Meter::xml_node_name = X_("Meter");
SerializedRCUManager<TempoMap> TempoMap::_map_mgr (0);
thread_local TempoMap::SharedPtr TempoMap::_tempo_map_p;
PBD::Signal0<void> TempoMap::MapChanged;
PBD::Signal3<void, TempoMap::SharedPtr, TempoMap::SharedPtr, bool> TempoMap::MapChanged;
#ifndef NDEBUG
#define TEMPO_MAP_ASSERT(expr) TempoMap::map_assert(expr, #expr, __FILE__, __LINE__)
@ -4357,8 +4358,10 @@ TempoMap::write_copy()
}
int
TempoMap::update (TempoMap::WritableSharedPtr m)
TempoMap::update (TempoMap::WritableSharedPtr m, bool from_undo)
{
SharedPtr old_map = read ();
if (!_map_mgr.update (m)) {
return -1;
}
@ -4372,7 +4375,7 @@ TempoMap::update (TempoMap::WritableSharedPtr m)
}
#endif
MapChanged (); /* EMIT SIGNAL */
MapChanged (old_map, read (), from_undo); /* EMIT SIGNAL */
return 0;
}
@ -5005,7 +5008,7 @@ TempoCommand::undo ()
TempoMap::WritableSharedPtr map (TempoMap::write_copy());
map->set_state (*_before, Stateful::current_state_version);
TempoMap::update (map);
TempoMap::update (map, true);
}
void

View File

@ -791,7 +791,7 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
/* API for typical tempo map changes */
LIBTEMPORAL_API static WritableSharedPtr write_copy();
LIBTEMPORAL_API static int update (WritableSharedPtr m);
LIBTEMPORAL_API static int update (WritableSharedPtr m, bool from_undo = false);
LIBTEMPORAL_API static void abort_update ();
/* not part of public API */
@ -1010,7 +1010,7 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
LIBTEMPORAL_API void dump (std::ostream&) const;
LIBTEMPORAL_API static PBD::Signal0<void> MapChanged;
LIBTEMPORAL_API static PBD::Signal3<void, SharedPtr, SharedPtr, bool> MapChanged;
LIBTEMPORAL_API XMLNode& get_state() const;

View File

@ -0,0 +1,121 @@
ardour {
["type"] = "EditorHook",
name = "Tempo Map Audio",
author = "Ardour Team",
description = "Time Stretch Audio when Tempo Map changed",
}
-- subscribe to signals
-- http://manual.ardour.org/lua-scripting/class_reference/#LuaSignal.LuaSignal
function signals ()
s = LuaSignal.Set()
s:add ({[LuaSignal.TempoMapChanged] = true})
return s
end
-- create callback functions
function factory ()
-- callback function which invoked when signal is emitted
return function (signal, ref, ...)
-- 'TempoMapChanged' passes 3 aruguments: old and current tempo-map and a boolean from_undo
local tmo, tmn, from_undo = ...
if from_undo or not Session:collected_undo_commands () then
return
end
function find_track_for_region (region_id)
for route in Session:get_tracks ():iter () do
local track = route:to_track ()
local pl = track:playlist ()
if not pl:region_by_id (region_id):isnil () then
return track
end
end
return nil
end
local all_regions = ARDOUR.RegionFactory.regions()
-- copy region-map
local regions = {}
for _, r in all_regions:iter() do
local ar = r:to_audioregion ()
if ar:isnil () then goto next end
local track = find_track_for_region (r:to_stateful ():id ())
if not track then goto next end
regions[ar] = track
::next::
end
for ar, track in pairs (regions) do
print ("Processing Region: ", ar:name (), track:name ())
-- create Rubberband stretcher
local rb = ARDOUR.LuaAPI.Rubberband (ar, false)
-- the rubberband-filter also implements the readable API.
-- https://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:AudioReadable
-- This allows to read from the master-source of the given audio-region.
-- XXX but, it will not work for incremental tempo-map changes :(
local max_pos = rb:readable ():readable_length ()
-- the beat-map is a table holding audio-sample positions:
-- [from] = to
local beat_map = {}
beat_map[0] = 0
-- get position of region
local region_pos = ar:position()
local beats = tmo:quarters_at (region_pos)
local sample = 0
local beat = 0
if beats:get_ticks () > 0 then
beat = beats:get_beats () + 1
else
beat = beats:get_beats ()
end
local old_pos = tmo:sample_at_beats (beats)
local new_pos = tmn:sample_at_beats (beats)
print ("start", old_pos, new_pos)
while sample < max_pos do
local b = Temporal.Beats (beat, 0)
sample = tmo:sample_at_beats (b) - old_pos
local to = tmn:sample_at_beats (b) - new_pos
beat_map[sample] = to
print ("map ", sample, "to", to)
beat = beat + 1
end
local old_end_beats = tmo:quarters_at (Temporal.timepos_t (region_pos:samples () + max_pos))
local new_end = tmn:sample_at_beats (old_end_beats)
local stretch = (new_end - new_pos) / max_pos
print ("stretch factor", stretch, new_pos, new_end, max_pos)
-- configure rubberband stretch tool
rb:set_strech_and_pitch (stretch, 1) -- no overall stretching, no pitch-shift
rb:set_mapping (beat_map) -- apply beat-map from/to
-- now stretch the region
function rb_progress (_, pos)
end
local nar = rb:process (rb_progress)
-- replace region
if not nar:isnil () then
print ("new audio region: ", nar:name (), nar:length ())
local playlist = track:playlist ()
playlist:to_stateful ():clear_changes () -- prepare undo
playlist:remove_region (ar)
playlist:add_region (nar, Temporal.timepos_t(new_pos), 1, false)
Session:add_stateful_diff_command (playlist:to_statefuldestructible ())
end
end
end
end