Add rubberband Lua bindings to process ardour regions
authorRobin Gareus <robin@gareus.org>
Mon, 13 Jan 2020 07:34:37 +0000 (08:34 +0100)
committerRobin Gareus <robin@gareus.org>
Mon, 13 Jan 2020 16:57:35 +0000 (17:57 +0100)
libs/ardour/ardour/lua_api.h
libs/ardour/lua_api.cc
libs/ardour/luabindings.cc

index bed9db5aa2885cf850b1137259141c33b0be4677..4d3bd380805e8fc6618defa7e173a129bab5a14f 100644 (file)
 #include <string>
 #include <lo/lo.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <rubberband/RubberBandStretcher.h>
 #include <vamp-hostsdk/Plugin.h>
 
 #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<Rubberband>
+       {
+               public:
+                       Rubberband (boost::shared_ptr<AudioRegion>, bool percussive);
+                       ~Rubberband ();
+                       bool set_strech_and_pitch (double stretch_ratio, double pitch_ratio);
+                       bool set_mapping (luabridge::LuaRef tbl);
+                       boost::shared_ptr<AudioRegion> process (luabridge::LuaRef cb);
+                       boost::shared_ptr<Readable> 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<AudioRegion> finalize ();
+
+                       boost::shared_ptr<AudioRegion> _region;
+
+                       uint32_t    _n_channels;
+                       samplecnt_t _read_len;
+                       samplecnt_t _read_start;
+                       samplecnt_t _read_offset;
+
+                       std::vector<boost::shared_ptr<AudioSource> > _asrc;
+
+                       RubberBand::RubberBandStretcher _rbs;
+                       std::map<size_t, size_t>        _mapping;
+
+                       double _stretch_ratio;
+                       double _pitch_ratio;
+
+                       luabridge::LuaRef*            _cb;
+                       boost::shared_ptr<Rubberband> _self;
+                       static const samplecnt_t      _bufsize;
+       };
+
        boost::shared_ptr<Evoral::Note<Temporal::Beats> >
                new_noteptr (uint8_t, Temporal::Beats, Temporal::Beats, uint8_t, uint8_t);
 
index 7d6d54e5d6e084bb0dd148e76431778c3e050704..f294cb3925ca06c006e9a2ce38faa105d2c9bf10 100644 (file)
 #include <cstring>
 #include <vamp-hostsdk/PluginLoader.h>
 
+#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<MidiModel> mm)
        }
        return note_ptr_list;
 }
+
+/* ****************************************************************************/
+
+const samplecnt_t LuaAPI::Rubberband::_bufsize = 256;
+
+LuaAPI::Rubberband::Rubberband (boost::shared_ptr<AudioRegion> 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<double> ();
+               size_t ds = i.value ().cast<double> ();
+               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<Readable>
+LuaAPI::Rubberband::readable ()
+{
+       if (!_self) {
+               _self = boost::shared_ptr<Rubberband> (this, &null_deleter);
+       }
+       return boost::dynamic_pointer_cast<Readable> (_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<AudioRegion>
+LuaAPI::Rubberband::process (luabridge::LuaRef cb)
+{
+       boost::shared_ptr<AudioRegion> 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<string> 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<AudioSource> (
+                                               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<AudioRegion>
+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<boost::shared_ptr<AudioSource>>::iterator i = _asrc.begin (); i != _asrc.end (); ++i) {
+               boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (*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<Region>      r  = RegionFactory::create (sl, plist);
+       boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (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<boost::shared_ptr<AudioSource>>::iterator i = _asrc.begin (); i != _asrc.end (); ++i) {
+                       (*i)->mark_for_remove ();
+               }
+       }
+       _asrc.clear ();
+       delete (_cb);
+       _cb = 0;
+}
index 6c571dd6fd1a119d4c7e3eff0028d40735ce8272..f781530b4a8c27ff17ec87f8c4054177ed935029 100644 (file)
@@ -2446,6 +2446,16 @@ LuaBindings::common (lua_State* L)
                .addFunction ("process", &ARDOUR::LuaAPI::Vamp::process)
                .endClass ()
 
+               .beginClass <ARDOUR::LuaAPI::Rubberband> ("Rubberband")
+               .addConstructor <void (*) (boost::shared_ptr<AudioRegion>, 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