From fd4c35d46a63b22795e526d1e0375a9b2d6d441c Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 13 Jan 2020 08:34:37 +0100 Subject: Add rubberband Lua bindings to process ardour regions --- libs/ardour/ardour/lua_api.h | 45 +++++++ libs/ardour/lua_api.cc | 273 +++++++++++++++++++++++++++++++++++++++++++ libs/ardour/luabindings.cc | 10 ++ 3 files changed, 328 insertions(+) diff --git a/libs/ardour/ardour/lua_api.h b/libs/ardour/ardour/lua_api.h index bed9db5aa2..4d3bd38080 100644 --- a/libs/ardour/ardour/lua_api.h +++ b/libs/ardour/ardour/lua_api.h @@ -22,12 +22,15 @@ #include #include #include +#include +#include #include #include "evoral/Note.h" #include "ardour/libardour_visibility.h" +#include "ardour/audioregion.h" #include "ardour/midi_model.h" #include "ardour/processor.h" #include "ardour/session.h" @@ -306,6 +309,48 @@ namespace ARDOUR { namespace LuaAPI { }; + class Rubberband : public Readable , public boost::enable_shared_from_this + { + public: + Rubberband (boost::shared_ptr, bool percussive); + ~Rubberband (); + bool set_strech_and_pitch (double stretch_ratio, double pitch_ratio); + bool set_mapping (luabridge::LuaRef tbl); + boost::shared_ptr process (luabridge::LuaRef cb); + boost::shared_ptr readable (); + + /* readable API */ + samplecnt_t readable_length () const { return _read_len; } + uint32_t n_channels () const { return _n_channels; } + samplecnt_t read (Sample*, samplepos_t pos, samplecnt_t cnt, int channel) const; + + private: + Rubberband (Rubberband const&); // no copy construction + bool read_region (bool study); + bool retrieve (float**); + void cleanup (bool abort); + boost::shared_ptr finalize (); + + boost::shared_ptr _region; + + uint32_t _n_channels; + samplecnt_t _read_len; + samplecnt_t _read_start; + samplecnt_t _read_offset; + + std::vector > _asrc; + + RubberBand::RubberBandStretcher _rbs; + std::map _mapping; + + double _stretch_ratio; + double _pitch_ratio; + + luabridge::LuaRef* _cb; + boost::shared_ptr _self; + static const samplecnt_t _bufsize; + }; + boost::shared_ptr > new_noteptr (uint8_t, Temporal::Beats, Temporal::Beats, uint8_t, uint8_t); diff --git a/libs/ardour/lua_api.cc b/libs/ardour/lua_api.cc index 7d6d54e5d6..f294cb3925 100644 --- a/libs/ardour/lua_api.cc +++ b/libs/ardour/lua_api.cc @@ -19,10 +19,14 @@ #include #include +#include "pbd/basename.h" #include "pbd/compose.h" #include "pbd/error.h" #include "pbd/failed_constructor.h" +#include "ardour/analyser.h" +#include "ardour/audiofilesource.h" +#include "ardour/audiosource.h" #include "ardour/lua_api.h" #include "ardour/luaproc.h" #include "ardour/luascripting.h" @@ -30,6 +34,8 @@ #include "ardour/plugin_insert.h" #include "ardour/plugin_manager.h" #include "ardour/readable.h" +#include "ardour/region_factory.h" +#include "ardour/source_factory.h" #include "LuaBridge/LuaBridge.h" @@ -875,3 +881,270 @@ LuaAPI::note_list (boost::shared_ptr mm) } return note_ptr_list; } + +/* ****************************************************************************/ + +const samplecnt_t LuaAPI::Rubberband::_bufsize = 256; + +LuaAPI::Rubberband::Rubberband (boost::shared_ptr r, bool percussive) + : _region (r) + , _rbs (r->session().sample_rate(), r->n_channels(), + percussive ? RubberBand::RubberBandStretcher::DefaultOptions : RubberBand::RubberBandStretcher::PercussiveOptions, + r->stretch (), r->shift ()) + , _stretch_ratio (r->stretch ()) + , _pitch_ratio (r->shift ()) + , _cb (0) +{ + _n_channels = r->n_channels (); + _read_len = r->length () / (double)r->stretch (); + _read_start = r->ancestral_start () + samplecnt_t (r->start () / (double)r->stretch ()); + _read_offset = _read_start - r->start () + r->position (); +} + +LuaAPI::Rubberband::~Rubberband () +{ +} + +bool +LuaAPI::Rubberband::set_strech_and_pitch (double stretch_ratio, double pitch_ratio) +{ + if (stretch_ratio <= 0 || pitch_ratio <= 0) { + return false; + } + _stretch_ratio = stretch_ratio * _region->stretch (); + _pitch_ratio = pitch_ratio * _region->shift (); + return true; +} + +bool +LuaAPI::Rubberband::set_mapping (luabridge::LuaRef tbl) +{ + if (!tbl.isTable ()) { + return false; + } + + _mapping.clear (); + + for (luabridge::Iterator i (tbl); !i.isNil (); ++i) { + if (!i.key ().isNumber () || !i.value ().isNumber ()) { + continue; + } + size_t ss = i.key ().cast (); + size_t ds = i.value ().cast (); + printf ("ADD %ld %ld\n", ss, ds); + _mapping[ss] = ds; + } + return !_mapping.empty (); +} + +samplecnt_t +LuaAPI::Rubberband::read (Sample* buf, samplepos_t pos, samplecnt_t cnt, int channel) const +{ + return _region->master_read_at (buf, NULL, NULL, _read_offset + pos, cnt, channel); +} + +static void null_deleter (LuaAPI::Rubberband*) {} + +boost::shared_ptr +LuaAPI::Rubberband::readable () +{ + if (!_self) { + _self = boost::shared_ptr (this, &null_deleter); + } + return boost::dynamic_pointer_cast (_self); +} + +bool +LuaAPI::Rubberband::read_region (bool study) +{ + samplepos_t pos = 0; + + float** buffers = new float*[_n_channels]; + for (uint32_t c = 0; c < _n_channels; ++c) { + buffers[c] = new float[_bufsize]; + } + + while (pos < _read_len) { + samplecnt_t n_read = 0; + for (uint32_t c = 0; c < _n_channels; ++c) { + samplepos_t to_read = std::min (_bufsize, _read_len - pos); + n_read = read (buffers[c], pos, to_read, c); + if (n_read != to_read) { + pos = 0; + goto errout; + } + } + + pos += n_read; + + assert (!_cb || _cb->type () == LUA_TFUNCTION); + if ((*_cb) (NULL, pos * .5 + (study ? 0 : _read_len / 2))) { + pos = 0; + goto errout; + } + + if (study) { + _rbs.study (buffers, n_read, pos == _read_len); + continue; + } + + assert (_asrc.size () == _n_channels); + _rbs.process (buffers, n_read, pos == _read_len); + + if (!retrieve (buffers)) { + pos = 0; + goto errout; + } + } + + if (!retrieve (buffers)) { + pos = 0; + } + +errout: + if (buffers) { + for (uint32_t c = 0; c < _n_channels; ++c) { + delete[] buffers[c]; + } + delete[] buffers; + } + return pos == _read_len; +} + +bool +LuaAPI::Rubberband::retrieve (float** buffers) +{ + samplecnt_t avail = 0; + while ((avail = _rbs.available ()) > 0) { + samplepos_t to_read = std::min (_bufsize, avail); + _rbs.retrieve (buffers, to_read); + + for (uint32_t c = 0; c < _asrc.size (); ++c) { + if (_asrc[c]->write (buffers[c], to_read) != to_read) { + return false; + } + } + } + return true; +} + +boost::shared_ptr +LuaAPI::Rubberband::process (luabridge::LuaRef cb) +{ + boost::shared_ptr rv; + if (cb.type () == LUA_TFUNCTION) { + _cb = new luabridge::LuaRef (cb); + } + + _rbs.reset (); + _rbs.setDebugLevel (1); + _rbs.setTimeRatio (_stretch_ratio); + _rbs.setPitchScale (_pitch_ratio); + _rbs.setExpectedInputDuration (_read_len); + + /* compare to Filter::make_new_sources */ + vector names = _region->master_source_names (); + Session& session = _region->session (); + samplecnt_t sample_rate = session.sample_rate (); + + for (uint32_t c = 0; c < _n_channels; ++c) { + string name = PBD::basename_nosuffix (names[c]) + "(rb)"; + const string path = session.new_audio_source_path (name, _n_channels, c, false, false); + if (path.empty ()) { + cleanup (true); + return rv; + } + try { + _asrc.push_back (boost::dynamic_pointer_cast ( + SourceFactory::createWritable ( + DataType::AUDIO, session, + path, false, sample_rate))); + } catch (failed_constructor& err) { + cleanup (true); + return rv; + } + } + + /* study */ + if (!read_region (true)) { + cleanup (true); + return rv; + } + + if (!_mapping.empty ()) { + _rbs.setKeyFrameMap (_mapping); + } + + /* process */ + if (!read_region (false)) { + cleanup (true); + return rv; + } + + rv = finalize (); + + cleanup (false); + return rv; +} + +boost::shared_ptr +LuaAPI::Rubberband::finalize () +{ + time_t xnow = time (NULL); + struct tm* now = localtime (&xnow); + + /* this is the same as RBEffect::finish, Filter::finish */ + SourceList sl; + for (std::vector>::iterator i = _asrc.begin (); i != _asrc.end (); ++i) { + boost::shared_ptr afs = boost::dynamic_pointer_cast (*i); + assert (afs); + afs->done_with_peakfile_writes (); + afs->update_header (_region->position (), *now, xnow); + afs->mark_immutable (); + Analyser::queue_source_for_analysis (*i, false); + sl.push_back (*i); + } + + /* create a new region */ + std::string region_name = RegionFactory::new_region_name (_region->name ()); + + PropertyList plist; + plist.add (Properties::start, 0); + plist.add (Properties::length, _region->length ()); + plist.add (Properties::name, region_name); + plist.add (Properties::whole_file, true); + plist.add (Properties::position, _region->position ()); + + boost::shared_ptr r = RegionFactory::create (sl, plist); + boost::shared_ptr ar = boost::dynamic_pointer_cast (r); + + ar->set_scale_amplitude (_region->scale_amplitude ()); + ar->set_fade_in_active (_region->fade_in_active ()); + ar->set_fade_in (_region->fade_in ()); + ar->set_fade_out_active (_region->fade_out_active ()); + ar->set_fade_out (_region->fade_out ()); + *(ar->envelope ()) = *(_region->envelope ()); + + ar->set_ancestral_data (_read_start, _read_len, _stretch_ratio, _pitch_ratio); + ar->set_master_sources (_region->master_sources ()); + ar->set_length (ar->length () * _stretch_ratio, 0); // XXX + if (_stretch_ratio != 1.0) { + // TODO: apply mapping + ar->envelope ()->x_scale (_stretch_ratio); + } + + return ar; +} + +void +LuaAPI::Rubberband::cleanup (bool abort) +{ + if (abort) { + for (std::vector>::iterator i = _asrc.begin (); i != _asrc.end (); ++i) { + (*i)->mark_for_remove (); + } + } + _asrc.clear (); + delete (_cb); + _cb = 0; +} diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index 6c571dd6fd..f781530b4a 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -2446,6 +2446,16 @@ LuaBindings::common (lua_State* L) .addFunction ("process", &ARDOUR::LuaAPI::Vamp::process) .endClass () + .beginClass ("Rubberband") + .addConstructor , bool)> () + .addFunction ("set_strech_and_pitch", &ARDOUR::LuaAPI::Rubberband::set_strech_and_pitch) + .addFunction ("set_mapping", &ARDOUR::LuaAPI::Rubberband::set_mapping) + .addFunction ("process", &ARDOUR::LuaAPI::Rubberband::process) + .addFunction ("readable_length", &ARDOUR::LuaAPI::Rubberband::readable_length) + .addFunction ("n_channels", &ARDOUR::LuaAPI::Rubberband::n_channels) + .addFunction ("readable", &ARDOUR::LuaAPI::Rubberband::readable) + .endClass () + .endNamespace () // end LuaAPI .endNamespace ();// end ARDOUR -- cgit v1.2.3