diff --git a/SConstruct b/SConstruct index 91e0bc0099..e2b34af5cd 100644 --- a/SConstruct +++ b/SConstruct @@ -64,6 +64,7 @@ class LibraryInfo(Environment): self.Append (LIBPATH = other.get ('LIBPATH', [])) self.Append (CPPPATH = other.get('CPPPATH', [])) self.Append (LINKFLAGS = other.get('LINKFLAGS', [])) + self.Append (CCFLAGS = other.get('CCFLAGS', [])) self.Replace(LIBPATH = list(Set(self.get('LIBPATH', [])))) self.Replace(CPPPATH = list(Set(self.get('CPPPATH',[])))) #doing LINKFLAGS breaks -framework @@ -434,6 +435,12 @@ def CheckPKGVersion(context, name, version): context.Result( ret ) return ret +def CheckPKGExists(context, name): + context.Message ('Checking for %s...' % name) + ret = context.TryAction('pkg-config --exists %s' % name)[0] + context.Result (ret) + return ret + conf = Configure(env, custom_tests = { 'CheckPKGConfig' : CheckPKGConfig, 'CheckPKGVersion' : CheckPKGVersion }) @@ -472,27 +479,21 @@ libraries['raptor'].ParseConfig('pkg-config --cflags --libs raptor') libraries['samplerate'] = LibraryInfo() libraries['samplerate'].ParseConfig('pkg-config --cflags --libs samplerate') -libraries['rubberband'] = LibraryInfo() -# -# chris cannam's rubberband has not yet been released -# -if os.path.exists ('libs/rubberband'): - libraries['rubberband'] = LibraryInfo (LIBS='rubberband', - LIBPATH='#libs/rubberband/lib', - CPPPATH='#libs/rubberband/src', - CXXFLAGS='-DUSE_RUBBERBAND') +libraries['fftw3f'] = LibraryInfo() +libraries['fftw3f'].ParseConfig('pkg-config --cflags --libs fftw3f') + +libraries['fftw3'] = LibraryInfo() +libraries['fftw3'].ParseConfig('pkg-config --cflags --libs fftw3') if env['FFT_ANALYSIS']: - libraries['fftw3f'] = LibraryInfo() - libraries['fftw3f'].ParseConfig('pkg-config --cflags --libs fftw3f') # # Check for fftw3 header as well as the library - conf = Configure (libraries['fftw3f']) + # + conf = env.Configure() if conf.CheckHeader ('fftw3.h') == False: - print "FFT Analysis cannot be compiled without the FFTW3 headers, which don't seem to be installed" - sys.exit (1) - libraries['fftw3f'] = conf.Finish(); - + print ('FFT Analysis cannot be compiled without the FFTW3 headers, which do not seem to be installed') + sys.exit (1) + libraries['jack'] = LibraryInfo() libraries['jack'].ParseConfig('pkg-config --cflags --libs jack') @@ -732,6 +733,35 @@ def prep_libcheck(topenv, libinfo): prep_libcheck(env, env) +# +# check for VAMP and rubberband (currently optional) +# + +libraries['vamp'] = LibraryInfo() + +env['RUBBERBAND'] = False + +conf = Configure (libraries['vamp'], custom_tests = { 'CheckPKGExists' : CheckPKGExists } ) + +if conf.CheckPKGExists('vamp-sdk'): + have_vamp = True + libraries['vamp'].ParseConfig('pkg-config --cflags --libs vamp-sdk') +else: + have_vamp = False + +libraries['vamp'] = conf.Finish () + +if have_vamp: + if os.path.exists ('libs/rubberband/src'): + conf = Configure (libraries['vamp']) + if conf.CheckHeader ('fftw3.h'): + env['RUBBERBAND'] = True + libraries['rubberband'] = LibraryInfo (LIBS='rubberband', + LIBPATH='#libs/rubberband', + CPPPATH='#libs/rubberband/rubberband', + CCFLAGS='-DUSE_RUBBERBAND') + libraries['vamp'] = conf.Finish () + # # Check for libusb @@ -972,7 +1002,6 @@ else: CPPPATH='#libs/appleutility') coredirs = [ - 'libs/soundtouch', 'templates' ] @@ -1038,6 +1067,14 @@ if env['SURFACES']: else: env['POWERMATE'] = 0 env['TRANZPORT'] = 0 + +# +# timestretch libraries +# + +timefx_subdirs = ['libs/soundtouch'] +if env['RUBBERBAND']: + timefx_subdirs += ['libs/rubberband'] opts.Save('scache.conf', env) Help(opts.GenerateHelpText(env)) @@ -1212,7 +1249,7 @@ env.AddPostAction (srcdist, Action ('rm -rf ' + str (File (env['DISTTREE'])))) for subdir in coredirs: SConscript (subdir + '/SConscript') -for sublistdir in [ subdirs, gtk_subdirs, surface_subdirs ]: +for sublistdir in [ subdirs, timefx_subdirs, gtk_subdirs, surface_subdirs ]: for subdir in sublistdir: SConscript (subdir + '/SConscript') diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index d7da86949c..0983fc81f8 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -49,8 +49,6 @@ gtkardour.Merge ([ libraries['gtk2'], libraries['xml'], libraries['xslt'], - libraries['soundtouch'], - libraries['rubberband'], libraries['samplerate'], libraries['jack'] ]) @@ -76,6 +74,11 @@ if gtkardour['FFT_ANALYSIS']: gtkardour.Merge ([libraries['fftw3f']]) gtkardour.Append(CCFLAGS='-DFFT_ANALYSIS') +if gtkardour['RUBBERBAND']: + gtkardour.Merge ([ libraries['rubberband'], libraries['vamp'], libraries['fftw3f'], libraries['fftw3'] ]) +else: + gtkardour.Merge ([ libraries['soundtouch'] ]) + skipped_files=Split(""" connection_editor.cc """) diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index 6017dcdba4..7f40b1ec35 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -6,7 +6,7 @@ export ARDOUR_PATH=gtk2_ardour/icons:gtk2_ardour/pixmaps:gtk2_ardour export GTK_PATH=libs/clearlooks -export LD_LIBRARY_PATH=libs/surfaces/control_protocol:libs/ardour:libs/midi++2:libs/pbd:libs/soundtouch:libs/gtkmm2ext:libs/sigc++2:libs/glibmm2:libs/gtkmm2/atk:libs/gtkmm2/pango:libs/gtkmm2/gdk:libs/gtkmm2/gtk:libs/libgnomecanvasmm:libs/libsndfile:libs/appleutility:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH=libs/surfaces/control_protocol:libs/ardour:libs/midi++2:libs/pbd:libs/rubberband:libs/soundtouch:libs/gtkmm2ext:libs/sigc++2:libs/glibmm2:libs/gtkmm2/atk:libs/gtkmm2/pango:libs/gtkmm2/gdk:libs/gtkmm2/gtk:libs/libgnomecanvasmm:libs/libsndfile:libs/appleutility:$LD_LIBRARY_PATH # DYLD_LIBRARY_PATH is for darwin. export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH diff --git a/gtk2_ardour/ardour.menus b/gtk2_ardour/ardour.menus index 4aa97b8996..288bf054d6 100644 --- a/gtk2_ardour/ardour.menus +++ b/gtk2_ardour/ardour.menus @@ -134,6 +134,8 @@ + + diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index deb8ea6106..7117ae9da2 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -45,8 +45,8 @@ #include #include -#include #include +#include #include #include diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 0a3f6a9134..ce1dc20ff0 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -5048,8 +5048,12 @@ Editor::end_time_fx (ArdourCanvas::Item* item, GdkEvent* event) } nframes_t newlen = drag_info.last_pointer_frame - clicked_regionview->region()->position(); +#ifdef USE_RUBBERBAND + float percentage = (float) ((double) newlen / (double) clicked_regionview->region()->length()); +#else float percentage = (float) ((double) newlen - (double) clicked_regionview->region()->length()) / ((double) newlen) * 100.0f; - +#endif + begin_reversible_command (_("timestretch")); if (run_timestretch (selection->regions, percentage) == 0) { diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index 224fcc8656..d29f4a0b10 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -92,7 +92,6 @@ sndfile_helpers.cc sndfilesource.cc source.cc source_factory.cc -stretch.cc tempo.cc utils.cc version.cc @@ -106,6 +105,7 @@ vst_files = [ 'vst_plugin.cc', 'session_vst.cc' ] audiounit_files = [ 'audio_unit.cc' ] coreaudio_files = [ 'coreaudiosource.cc' ] extra_sources = [ ] +timefx_sources = [ ] if ardour['VST']: extra_sources += vst_files @@ -260,22 +260,28 @@ ardour.Merge ([ libraries['samplerate'], libraries['sigc2'], libraries['pbd'], - libraries['soundtouch'], libraries['midi++2'], libraries['glib2'], libraries['glibmm2'] ]) +if ardour['RUBBERBAND']: + ardour.Merge ([ libraries['rubberband'], libraries['vamp'], libraries['fftw3f'] ]) + timefx_sources += [ 'rb_stretch.cc' ] +else: + ardour.Merge ([ libraries['soundtouch'] ]) + timefx_sources += [ 'st_stretch.cc' ] + if ardour['LIBLO']: - ardour.Merge ([ libraries['lo'] ]) + ardour.Merge ([ libraries['lo'] ]) if ardour['COREAUDIO'] or ardour['AUDIOUNITS']: - ardour.Merge ([ libraries['appleutility'] ]) + ardour.Merge ([ libraries['appleutility'] ]) def SharedAsmObjectEmitter(target, source, env): - for tgt in target: - tgt.attributes.shared = 1 - return (target, source) + for tgt in target: + tgt.attributes.shared = 1 + return (target, source) env['BUILDERS']['SharedAsmObject'] = Builder (action = '$CXX -c -fPIC $SOURCE -o $TARGET', @@ -305,12 +311,12 @@ if env['FPU_OPTIMIZATION']: arch_specific_objects = env.SharedAsmObject('sse_functions_64bit.os', 'sse_functions_64bit.s') always_sse_objects += [ sse_env.SharedObject (source = 'sse_functions_xmm.cc') ] -libardour = ardour.SharedLibrary('ardour', ardour_files + always_sse_objects + extra_sources + arch_specific_objects) +libardour = ardour.SharedLibrary('ardour', ardour_files + always_sse_objects + timefx_sources + extra_sources + arch_specific_objects) Default(libardour) if env['NLS']: - i18n (ardour, ardour_files + vst_files + coreaudio_files + audiounit_files, env) + i18n (ardour, ardour_files + vst_files + coreaudio_files + timefx_sources + audiounit_files, env) env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2'), libardour)) @@ -318,6 +324,8 @@ env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ar env.Alias('version', ardour.VersionBuild(['version.cc', 'ardour/version.h'], [])) env.Alias('tarball', env.Distribute (env['DISTTREE'], - [ 'SConscript', 'i18n.h', 'gettext.h', 'sse_functions_xmm.cc', 'sse_functions.s', 'sse_functions_64bit.s' ] + + [ 'SConscript', 'i18n.h', 'gettext.h' ] + + [ 'sse_functions_xmm.cc', 'sse_functions.s', 'sse_functions_64bit.s' ] + + [ 'rb_stretch.cc', 'st_stretch.cc' ] + ardour_files + osc_files + vst_files + coreaudio_files + audiounit_files + glob.glob('po/*.po') + glob.glob('ardour/*.h'))) diff --git a/libs/ardour/ardour/stretch.h b/libs/ardour/ardour/stretch.h index 02cc930ee5..6b5c2957e1 100644 --- a/libs/ardour/ardour/stretch.h +++ b/libs/ardour/ardour/stretch.h @@ -21,14 +21,17 @@ #define __ardour_stretch_h__ #include + +#ifndef USE_RUBBERBAND #include +#endif namespace ARDOUR { class AudioRegion; struct TimeStretchRequest : public InterThreadInfo { - float fraction; + float fraction; bool quick_seek; bool antialias; }; @@ -42,7 +45,10 @@ class Stretch : public AudioFilter { private: TimeStretchRequest& tsr; + +#ifndef USE_RUBBERBAND soundtouch::SoundTouch st; +#endif }; diff --git a/libs/ardour/rb_stretch.cc b/libs/ardour/rb_stretch.cc new file mode 100644 index 0000000000..39d5816a5a --- /dev/null +++ b/libs/ardour/rb_stretch.cc @@ -0,0 +1,276 @@ +/* + Copyright (C) 2004-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include + +#include +#include "/usr/local/music/src/rubberband/rubberband/RubberBandStretcher.h" //!!! + +#include +#include +#include +#include +#include + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace PBD; +using namespace RubberBand; + +Stretch::Stretch (Session& s, TimeStretchRequest& req) + : AudioFilter (s) + , tsr (req) + +{ + tsr.progress = 0.0f; +} + +Stretch::~Stretch () +{ +} + +int +Stretch::run (boost::shared_ptr region) +{ + SourceList nsrcs; + nframes_t done; + int ret = -1; + const nframes_t bufsize = 256; + gain_t* gain_buffer = 0; + Sample** buffers = 0; + char suffix[32]; + string new_name; + string::size_type at; + nframes_t pos = 0; + int avail = 0; + + RubberBandStretcher stretcher (session.frame_rate(), region->n_channels(), + RubberBandStretcher::DefaultOptions, + tsr.fraction, 1.0); + + stretcher.setExpectedInputDuration(region->length()); + stretcher.setDebugLevel(1); + + tsr.progress = 0.0f; + tsr.done = false; + + uint32_t channels = region->n_channels(); + nframes_t duration = region->length(); + + /* the name doesn't need to be super-precise, but allow for 2 fractional + digits just to disambiguate close but not identical stretches. + */ + + snprintf (suffix, sizeof (suffix), "@%d", (int) floor (tsr.fraction * 100.0f)); + + /* create new sources */ + + if (make_new_sources (region, nsrcs, suffix)) { + goto out; + } + + gain_buffer = new gain_t[bufsize]; + buffers = new float *[channels]; + + for (uint32_t i = 0; i < channels; ++i) { + buffers[i] = new float[bufsize]; + } + + /* we read from the master (original) sources for the region, + not the ones currently in use, in case it's already been + subject to timefx. */ + + /* study first, process afterwards. */ + + pos = 0; + avail = 0; + done = 0; + + try { + while (pos < duration && !tsr.cancel) { + + nframes_t this_read = 0; + + for (uint32_t i = 0; i < channels; ++i) { + + this_read = 0; + nframes_t this_time; + + this_time = min(bufsize, duration - pos); + + this_read = region->master_read_at + (buffers[i], + buffers[i], + gain_buffer, + pos + region->position(), + this_time, + i); + + if (this_read != this_time) { + error << string_compose + (_("tempoize: error reading data from %1"), + nsrcs[i]->name()) << endmsg; + goto out; + } + } + + pos += this_read; + done += this_read; + + tsr.progress = ((float) done / duration) * 0.25; + + stretcher.study(buffers, this_read, pos == duration); + } + + pos = 0; + tsr.done = 0; + + while (pos < duration && !tsr.cancel) { + + nframes_t this_read = 0; + + for (uint32_t i = 0; i < channels; ++i) { + + this_read = 0; + nframes_t this_time; + + this_time = min(bufsize, duration - pos); + + this_read = region->master_read_at + (buffers[i], + buffers[i], + gain_buffer, + pos + region->position(), + this_time, + i); + + if (this_read != this_time) { + error << string_compose + (_("tempoize: error reading data from %1"), + nsrcs[i]->name()) << endmsg; + goto out; + } + } + + pos += this_read; + done += this_read; + + tsr.progress = 0.25 + ((float) done / duration) * 0.75; + + stretcher.process(buffers, this_read, pos == duration); + + int avail = 0; + + while ((avail = stretcher.available()) > 0) { + + this_read = min(bufsize, uint32_t(avail)); + + stretcher.retrieve(buffers, this_read); + + for (uint32_t i = 0; i < nsrcs.size(); ++i) { + + if (nsrcs[i]->write(buffers[i], this_read) != + this_read) { + error << string_compose (_("error writing tempo-adjusted data to %1"), nsrcs[i]->name()) << endmsg; + goto out; + } + } + } + } + + while ((avail = stretcher.available()) >= 0) { + + uint32_t this_read = min(bufsize, uint32_t(avail)); + + stretcher.retrieve(buffers, this_read); + + for (uint32_t i = 0; i < nsrcs.size(); ++i) { + + if (nsrcs[i]->write(buffers[i], this_read) != + this_read) { + error << string_compose (_("error writing tempo-adjusted data to %1"), nsrcs[i]->name()) << endmsg; + goto out; + } + } + } + + } catch (runtime_error& err) { + error << _("timefx code failure. please notify ardour-developers.") << endmsg; + error << err.what() << endmsg; + goto out; + } + + new_name = region->name(); + at = new_name.find ('@'); + + // remove any existing stretch indicator + + if (at != string::npos && at > 2) { + new_name = new_name.substr (0, at - 1); + } + + new_name += suffix; + + ret = finish (region, nsrcs, new_name); + + /* now reset ancestral data for each new region */ + + for (vector >::iterator x = results.begin(); x != results.end(); ++x) { + nframes64_t astart = (*x)->ancestral_start(); + nframes64_t alength = (*x)->ancestral_length(); + nframes_t start; + nframes_t length; + + // note: tsr.fraction is a percentage of original length. 100 = no change, + // 50 is half as long, 200 is twice as long, etc. + + float stretch = (*x)->stretch() * (tsr.fraction/100.0); + + start = (nframes_t) floor (astart + ((astart - (*x)->start()) / stretch)); + length = (nframes_t) floor (alength / stretch); + + (*x)->set_ancestral_data (start, length, stretch); + } + + out: + + if (gain_buffer) { + delete [] gain_buffer; + } + + if (buffers) { + for (uint32_t i = 0; i < channels; ++i) { + delete buffers[i]; + } + delete [] buffers; + } + + if (ret || tsr.cancel) { + for (SourceList::iterator si = nsrcs.begin(); si != nsrcs.end(); ++si) { + (*si)->mark_for_remove (); + } + } + + tsr.done = true; + + return ret; +} diff --git a/libs/ardour/stretch.cc b/libs/ardour/st_stretch.cc similarity index 100% rename from libs/ardour/stretch.cc rename to libs/ardour/st_stretch.cc diff --git a/libs/rubberband/SConscript b/libs/rubberband/SConscript new file mode 100644 index 0000000000..db5c4cd3f6 --- /dev/null +++ b/libs/rubberband/SConscript @@ -0,0 +1,28 @@ +# -*- python -*- + +import os +import os.path +import glob + +rubberband_files = glob.glob ('src/*.cpp') + +Import('env install_prefix libraries') +rb = env.Copy() + +rb.Merge ([libraries['fftw3f'], + libraries['vamp'], + libraries['sndfile-ardour'] + ]) + +rb.Append (CPPATH='#libs/rubberband/rubberband', CXXFLAGS="-Ilibs/rubberband/rubberband") + +librb = rb.SharedLibrary('rubberband', rubberband_files) + +Default(librb) + +env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2'), librb)) + +env.Alias('tarball', env.Distribute (env['DISTTREE'], + [ 'SConscript'] + rubberband_files + glob.glob('src/*.h'))) + +