commandline session utilities
authorRobin Gareus <robin@gareus.org>
Tue, 15 Dec 2015 23:25:07 +0000 (00:25 +0100)
committerRobin Gareus <robin@gareus.org>
Tue, 15 Dec 2015 23:25:07 +0000 (00:25 +0100)
session_utils/README [new file with mode: 0644]
session_utils/ardour-util.sh.in [new file with mode: 0644]
session_utils/common.cc [new file with mode: 0644]
session_utils/common.h [new file with mode: 0644]
session_utils/debug [new file with mode: 0755]
session_utils/example.cc [new file with mode: 0644]
session_utils/export.cc [new file with mode: 0644]
session_utils/run [new file with mode: 0755]
session_utils/wscript [new file with mode: 0644]
wscript

diff --git a/session_utils/README b/session_utils/README
new file mode 100644 (file)
index 0000000..510e3a4
--- /dev/null
@@ -0,0 +1,29 @@
+Ardour Session Utilities
+========================
+
+This folder contains some tools which directly use libardour to access ardour
+sessions.
+
+The overall goal it to provide some non-interactive unix-style commandline
+tools, which are installed along with DAW.
+
+
+Adding new tools
+----------------
+
+One c++ source per tool, see "example.cc" and "export.cc"
+
+  cp session_utils/example.cc session_utils/your_new_tool_name.cc
+  edit session_utils/new_tool_name.cc
+       ./waf
+
+The tool is automatically compiled and deployed when installing, using the
+program-name as prefix.  e.g.  "export.cc" becomes "ardour4-export".
+(or "mixbus3-export", depending on the project configuration)
+
+
+Test run from the source
+------------------------
+
+  cd session_utils
+  ./run ardour4-your_new_tool_name
diff --git a/session_utils/ardour-util.sh.in b/session_utils/ardour-util.sh.in
new file mode 100644 (file)
index 0000000..3ffc53d
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# This is a wrapper script for using the ardour-session utilities
+# it is intended to be symlinked into $PATH for every session-tool
+
+export GTK_PATH=@CONFDIR@:@LIBDIR@${GTK_PATH:+:$GTK_PATH}
+export LD_LIBRARY_PATH=@LIBDIR@${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
+
+# Running Ardour requires these 3 variables to be set
+
+export ARDOUR_DATA_PATH=@DATADIR@
+export ARDOUR_CONFIG_PATH=@CONFDIR@
+export ARDOUR_DLL_PATH=@LIBDIR@
+export VAMP_PATH=@LIBDIR@/vamp
+
+SELF=`basename $0`
+exec "@LIBDIR@/utils/$SELF" "$@"
diff --git a/session_utils/common.cc b/session_utils/common.cc
new file mode 100644 (file)
index 0000000..d7f9287
--- /dev/null
@@ -0,0 +1,161 @@
+#include <iostream>
+#include <cstdlib>
+
+
+#include "pbd/debug.h"
+#include "pbd/event_loop.h"
+#include "pbd/error.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/pthread_utils.h"
+
+#include "ardour/audioengine.h"
+
+#include "common.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+static const char* localedir = LOCALEDIR;
+TestReceiver test_receiver;
+
+void
+TestReceiver::receive (Transmitter::Channel chn, const char * str)
+{
+       const char *prefix = "";
+
+       switch (chn) {
+       case Transmitter::Error:
+               prefix = ": [ERROR]: ";
+               break;
+       case Transmitter::Info:
+               /* ignore */
+               return;
+       case Transmitter::Warning:
+               prefix = ": [WARNING]: ";
+               break;
+       case Transmitter::Fatal:
+               prefix = ": [FATAL]: ";
+               break;
+       case Transmitter::Throw:
+               /* this isn't supposed to happen */
+               abort ();
+       }
+
+       /* note: iostreams are already thread-safe: no external
+          lock required.
+       */
+
+       std::cout << prefix << str << std::endl;
+
+       if (chn == Transmitter::Fatal) {
+               ::exit (9);
+       }
+}
+
+/* temporarily required due to some code design confusion (Feb 2014) */
+
+#include "ardour/vst_types.h"
+
+int vstfx_init (void*) { return 0; }
+void vstfx_exit () {}
+void vstfx_destroy_editor (VSTState*) {}
+
+class MyEventLoop : public sigc::trackable, public EventLoop
+{
+       public:
+               MyEventLoop (std::string const& name) : EventLoop (name) {
+                       run_loop_thread = Glib::Threads::Thread::self();
+               }
+
+               void call_slot (InvalidationRecord*, const boost::function<void()>& f) {
+                       if (Glib::Threads::Thread::self() == run_loop_thread) {
+                               f ();
+                       }
+               }
+
+               Glib::Threads::Mutex& slot_invalidation_mutex() { return request_buffer_map_lock; }
+
+       private:
+               Glib::Threads::Thread* run_loop_thread;
+               Glib::Threads::Mutex   request_buffer_map_lock;
+};
+
+static MyEventLoop *event_loop;
+
+void
+SessionUtils::init ()
+{
+       if (!ARDOUR::init (false, true, localedir)) {
+               cerr << "Ardour failed to initialize\n" << endl;
+               ::exit (1);
+       }
+
+       event_loop = new MyEventLoop ("util");
+       EventLoop::set_event_loop_for_thread (event_loop);
+       SessionEvent::create_per_thread_pool ("util", 512);
+
+       test_receiver.listen_to (error);
+       test_receiver.listen_to (info);
+       test_receiver.listen_to (fatal);
+       test_receiver.listen_to (warning);
+}
+
+static Session * _load_session (string dir, string state)
+{
+       AudioEngine* engine = AudioEngine::create ();
+
+       if (!engine->set_backend ("None (Dummy)", "Unit-Test", "")) {
+               std::cerr << "Cannot create Audio/MIDI engine\n";
+               ::exit (EXIT_FAILURE);
+       }
+
+       init_post_engine ();
+
+       if (engine->start () != 0) {
+               std::cerr << "Cannot start Audio/MIDI engine\n";
+               ::exit (EXIT_FAILURE);
+       }
+
+       Session* session = new Session (*engine, dir, state);
+       engine->set_session (session);
+       return session;
+}
+
+Session *
+SessionUtils::load_session (string dir, string state)
+{
+       Session* s = 0;
+       try {
+               s = _load_session (dir, state);
+       } catch (failed_constructor& e) {
+               cerr << "failed_constructor: " << e.what() << "\n";
+               exit (EXIT_FAILURE);
+       } catch (AudioEngine::PortRegistrationFailure& e) {
+               cerr << "PortRegistrationFailure: " << e.what() << "\n";
+               exit (EXIT_FAILURE);
+       } catch (exception& e) {
+               cerr << "exception: " << e.what() << "\n";
+               exit (EXIT_FAILURE);
+       } catch (...) {
+               cerr << "unknown exception.\n";
+               exit (EXIT_FAILURE);
+       }
+       return s;
+}
+
+void
+SessionUtils::unload_session (Session *s)
+{
+       delete s;
+       AudioEngine::instance()->stop ();
+       AudioEngine::destroy ();
+}
+
+void
+SessionUtils::cleanup ()
+{
+       ARDOUR::cleanup ();
+       delete event_loop;
+       pthread_cancel_all ();
+}
diff --git a/session_utils/common.h b/session_utils/common.h
new file mode 100644 (file)
index 0000000..5263e84
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef _session_utils_common_h_
+#define _session_utils_common_h_
+
+#include "pbd/transmitter.h"
+#include "pbd/receiver.h"
+
+#include "ardour/ardour.h"
+#include "ardour/session.h"
+
+class TestReceiver : public Receiver
+{
+  protected:
+    void receive (Transmitter::Channel chn, const char * str);
+};
+
+namespace SessionUtils {
+
+       /** initialize libardour */
+       void init ();
+
+       /** clean up, stop Processing Engine
+        * @param s Session to close (may me NULL)
+        */
+       void cleanup ();
+
+       /** @param dir Session directory.
+        *  @param state Session state file, without .ardour suffix.
+        */
+       ARDOUR::Session * load_session (std::string dir, std::string state);
+
+       /** close session and stop engine
+        * @param s Session to close (may me NULL)
+        */
+       void unload_session (ARDOUR::Session *s);
+
+};
+
+#endif /* _session_utils_misc_h_ */
diff --git a/session_utils/debug b/session_utils/debug
new file mode 100755 (executable)
index 0000000..6182ef2
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+TOP=`dirname "$0"`/..
+. "$TOP/build/gtk2_ardour/ardev_common_waf.sh"
+SELF=$1
+shift
+
+export ARDOUR_INSIDE_GDB=1
+
+if test -n "`which gdb`"; then
+       exec gdb --args "$TOP/build/session_utils/$SELF" "$@"
+fi
+if test -n "`which lldb`"; then
+       exec lldb -- "$TOP/build/session_utils/$SELF" "$@"
+fi
diff --git a/session_utils/example.cc b/session_utils/example.cc
new file mode 100644 (file)
index 0000000..044d40e
--- /dev/null
@@ -0,0 +1,30 @@
+#include <iostream>
+#include <cstdlib>
+
+#include "common.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace SessionUtils;
+
+int main (int argc, char* argv[])
+{
+       SessionUtils::init();
+       Session* s = 0;
+
+       s = SessionUtils::load_session (
+                       "/home/rgareus/Documents/ArdourSessions/TestA/",
+                       "TestA"
+                       );
+
+       printf ("SESSION INFO: routes: %lu\n", s->get_routes()->size ());
+
+       sleep(2);
+
+       //s->save_state ("");
+
+       SessionUtils::unload_session(s);
+       SessionUtils::cleanup();
+
+       return 0;
+}
diff --git a/session_utils/export.cc b/session_utils/export.cc
new file mode 100644 (file)
index 0000000..e7d169a
--- /dev/null
@@ -0,0 +1,235 @@
+#include <iostream>
+#include <cstdlib>
+#include <getopt.h>
+#include <glibmm.h>
+
+#include "common.h"
+
+#include "pbd/basename.h"
+
+#include "ardour/export_handler.h"
+#include "ardour/export_status.h"
+#include "ardour/export_timespan.h"
+#include "ardour/export_channel_configuration.h"
+#include "ardour/export_format_specification.h"
+#include "ardour/export_filename.h"
+#include "ardour/route.h"
+#include "ardour/session_metadata.h"
+#include "ardour/broadcast_info.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace SessionUtils;
+
+static int export_session (Session *session,
+               std::string outfile,
+               std::string samplerate,
+               bool normalize)
+{
+       ExportTimespanPtr tsp = session->get_export_handler()->add_timespan();
+       boost::shared_ptr<ExportChannelConfiguration> ccp = session->get_export_handler()->add_channel_config();
+       boost::shared_ptr<ARDOUR::ExportFilename> fnp = session->get_export_handler()->add_filename();
+       boost::shared_ptr<AudioGrapher::BroadcastInfo> b;
+
+       XMLTree tree;
+
+       tree.read_buffer(std::string(
+"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+"<ExportFormatSpecification name=\"UTIL-WAV-16\" id=\"14792644-44ab-4209-a4f9-7ce6c2910cac\">"
+"  <Encoding id=\"F_WAV\" type=\"T_Sndfile\" extension=\"wav\" name=\"WAV\" has-sample-format=\"true\" channel-limit=\"256\"/>"
+"  <SampleRate rate=\""+ samplerate +"\"/>"
+"  <SRCQuality quality=\"SRC_SincBest\"/>"
+"  <EncodingOptions>"
+"    <Option name=\"sample-format\" value=\"SF_16\"/>"
+"    <Option name=\"dithering\" value=\"D_None\"/>"
+"    <Option name=\"tag-metadata\" value=\"true\"/>"
+"    <Option name=\"tag-support\" value=\"false\"/>"
+"    <Option name=\"broadcast-info\" value=\"false\"/>"
+"  </EncodingOptions>"
+"  <Processing>"
+"    <Normalize enabled=\""+ (normalize ? "true" : "false") +"\" target=\"0\"/>"
+"    <Silence>"
+"      <Start>"
+"        <Trim enabled=\"false\"/>"
+"        <Add enabled=\"false\">"
+"          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
+"        </Add>"
+"      </Start>"
+"      <End>"
+"        <Trim enabled=\"false\"/>"
+"        <Add enabled=\"false\">"
+"          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
+"        </Add>"
+"      </End>"
+"    </Silence>"
+"  </Processing>"
+"</ExportFormatSpecification>"
+));
+
+       boost::shared_ptr<ExportFormatSpecification> fmp = session->get_export_handler()->add_format(*tree.root());
+
+       /* set up range */
+       framepos_t start, end;
+       start = session->current_start_frame();
+       end   = session->current_end_frame();
+       tsp->set_range (start, end);
+       tsp->set_range_id ("session");
+
+       /* add master outs as default */
+       IO* master_out = session->master_out()->output().get();
+       if (!master_out) {
+               PBD::warning << _("Export Util: No Master Out Ports to Connect for Audio Export") << endmsg;
+               return -1;
+       }
+
+       for (uint32_t n = 0; n < master_out->n_ports().n_audio(); ++n) {
+               PortExportChannel * channel = new PortExportChannel ();
+               channel->add_port (master_out->audio (n));
+               ExportChannelPtr chan_ptr (channel);
+               ccp->register_channel (chan_ptr);
+       }
+
+       /* output filename */
+       if (outfile.empty ()) {
+               tsp->set_name ("session");
+       } else {
+               std::string dirname = Glib::path_get_dirname (outfile);
+               std::string basename = Glib::path_get_basename (outfile);
+
+               if (basename.size() > 4 && !basename.compare (basename.size() - 4, 4, ".wav")) {
+                       basename = PBD::basename_nosuffix (basename);
+               }
+
+               fnp->set_folder(dirname);
+               tsp->set_name (basename);
+       }
+
+       cout << "* Writing " << Glib::build_filename (fnp->get_folder(), tsp->name() + ".wav") << endl;
+
+
+       /* output */
+       fnp->set_timespan(tsp);
+       fnp->include_label = false;
+
+       /* do audio export */
+       fmp->set_soundcloud_upload(false);
+       session->get_export_handler()->add_export_config (tsp, ccp, fmp, fnp, b);
+       session->get_export_handler()->do_export();
+
+       boost::shared_ptr<ARDOUR::ExportStatus> status = session->get_export_status ();
+
+       // TODO trap SIGINT -> status->abort();
+
+       while (status->running) {
+               if (status->normalizing) {
+                       double progress = ((float) status->current_normalize_cycle) / status->total_normalize_cycles;
+                       printf ("* Normalizing %.1f%%      \r", 100. * progress); fflush (stdout);
+               } else {
+                       double progress = ((float) status->processed_frames_current_timespan) / status->total_frames_current_timespan;
+                       printf ("* Exporting Audio %.1f%%  \r", 100. * progress); fflush (stdout);
+               }
+               Glib::usleep (1000000);
+       }
+       printf("\n");
+
+       status->finish ();
+
+       printf ("* Done.\n");
+       return 0;
+}
+
+static void usage (int status) {
+       // help2man compatible format (standard GNU help-text)
+       printf ("export - export an ardour session from the commandline.\n\n");
+       printf ("Usage: export [ OPTIONS ] <session-dir> <session-name>\n\n");
+       printf ("Options:\n\
+  -h, --help                 display this help and exit\n\
+  -n, --normalize            normalize signal level (to 0dBFS)\n\
+  -o, --output  <file>       set expected [initial] framerate\n\
+  -s, --samplerate <rate>    samplerate to use (default: 48000)\n\
+  -V, --version              print version information and exit\n\
+\n");
+       printf ("\n\
+The session is exported as 16bit wav.\n\
+If the no output file is given, the session's export dir is used.\n\
+\n");
+
+       printf ("Report bugs to <http://tracker.ardour.org/>\n"
+               "Website: <http://ardour.org/>\n");
+       ::exit (status);
+}
+
+int main (int argc, char* argv[])
+{
+       std::string rate = "48000";
+       std::string outfile;
+       bool normalize = false;
+
+       const char *optstring = "hno:r:V";
+
+       const struct option longopts[] = {
+               { "help",       0, 0, 'h' },
+               { "normalize",  0, 0, 'n' },
+               { "output",     1, 0, 'o' },
+               { "samplerate", 1, 0, 'r' },
+               { "version",    0, 0, 'V' },
+       };
+
+       int c = 0;
+       while (EOF != (c = getopt_long (argc, argv,
+                                       optstring, longopts, (int *) 0))) {
+               switch (c) {
+
+                       case 'n':
+                               normalize = true;
+                               break;
+
+                       case 'o':
+                               outfile = optarg;
+                               break;
+
+                       case 's':
+                               {
+                                       const int sr = atoi (optarg);
+                                       if (sr >= 8000 && sr <= 192000) {
+                                               stringstream ss;
+                                               ss << sr;
+                                               rate = ss.str();
+                                       } else {
+                                               fprintf(stderr, "Invalid Samplerate\n");
+                                       }
+                               }
+                               break;
+
+                       case 'V':
+                               printf ("ardour-utils version %s\n\n", VERSIONSTRING);
+                               printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
+                               exit (0);
+                               break;
+
+                       case 'h':
+                               usage (0);
+                               break;
+
+                       default:
+                                       usage (EXIT_FAILURE);
+                                       break;
+               }
+       }
+
+       if (optind + 2 > argc) {
+               usage (EXIT_FAILURE);
+       }
+
+       SessionUtils::init();
+       Session* s = 0;
+
+       s = SessionUtils::load_session (argv[optind], argv[optind+1]);
+
+       export_session (s, outfile, rate, normalize);
+
+       SessionUtils::unload_session(s);
+       SessionUtils::cleanup();
+
+       return 0;
+}
diff --git a/session_utils/run b/session_utils/run
new file mode 100755 (executable)
index 0000000..1abec31
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+TOP=`dirname "$0"`/..
+. "$TOP/build/gtk2_ardour/ardev_common_waf.sh"
+SELF=$1
+shift
+exec "$TOP/build/session_utils/$SELF" "$@"
diff --git a/session_utils/wscript b/session_utils/wscript
new file mode 100644 (file)
index 0000000..de9111e
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+from waflib import Options, TaskGen
+import waflib.Logs as Logs, waflib.Utils as Utils
+import os
+import shutil
+import sys
+import re
+import time
+from waflib.Task import Task
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+    autowaf.set_options(opt)
+
+def configure(conf):
+    conf.load('misc')
+    conf.load('compiler_cxx')
+    autowaf.configure(conf)
+
+def build_ardour_util(bld, util):
+    pgmprefix = bld.env['PROGRAM_NAME'].lower() + str(bld.env['MAJOR'])
+
+    # just the normal executable version of the GTK GUI
+    obj = bld (features = 'cxx c cxxprogram')
+    # this program does not do the whole hidden symbols thing
+    obj.cxxflags = [ '-fvisibility=default' ]
+    obj.source   = ['common.cc', util + '.cc' ]
+    obj.target   = pgmprefix + '-' + util
+    obj.includes = ['.']
+    obj.use      = [ 'libpbd',
+                     'libardour',
+                     'libardour_cp',
+                     'libtimecode',
+                     'libmidipp',
+                     ]
+    obj.defines = [
+        'VERSIONSTRING="' + str(bld.env['VERSION']) + '"',
+        'DATA_DIR="'   + os.path.normpath(bld.env['DATADIR']) + '"',
+        'CONFIG_DIR="' + os.path.normpath(bld.env['SYSCONFDIR']) + '"',
+        'LOCALEDIR="'  + os.path.join(os.path.normpath(bld.env['DATADIR']), 'locale') + '"',
+        'PACKAGE="'    + "ARDOURUTILS" + '"',
+        ]
+    obj.install_path = bld.env['LIBDIR'] + '/utils'
+    obj.uselib       = 'UUID FLAC FONTCONFIG GLIBMM GTHREAD OGG CURL DL'
+    obj.uselib       += ' FFTW3F'
+    obj.uselib       += ' AUDIOUNITS OSX LO '
+    obj.uselib       += ' TAGLIB '
+
+    if sys.platform == 'darwin':
+        obj.uselib += ' AUDIOUNITS OSX'
+        obj.use    += ' libappleutility'
+    obj.includes += ['../libs']
+
+    if bld.env['build_target'] == 'mingw':
+        if bld.env['DEBUG'] == False:
+            obj.linkflags = ['-mwindows']
+
+    if bld.is_defined('NEED_INTL'):
+        obj.linkflags = ' -lintl'
+
+
+def build(bld):
+    VERSION = "%s.%s" % (bld.env['MAJOR'], bld.env['MINOR'])
+    # no wine
+    if bld.is_defined('WINDOWS_VST_SUPPORT') and bld.env['build_target'] != 'mingw':
+        return
+
+    # don't build/install windows version just yet.
+    # tools/x-win/package.sh uses 'waf install'. The symlinks
+    # and shell wrapper script won't work on windows.
+    if bld.env['build_target'] == 'mingw':
+        return
+
+    pgmprefix = bld.env['PROGRAM_NAME'].lower() + str(bld.env['MAJOR'])
+
+    utils = bld.path.ant_glob('*.cc', excl=['example.cc', 'common.cc'])
+
+    for util in utils:
+        fn = str(util)[:-3]
+        build_ardour_util(bld, fn)
+        bld.symlink_as(bld.env['BINDIR'] + '/' + pgmprefix + "-" + fn, bld.env['LIBDIR'] + '/utils/ardour-util.sh')
+
+    obj              = bld(features = 'subst')
+    obj.source       = 'ardour-util.sh.in'
+    obj.target       = 'ardour-util.sh'
+    obj.chmod        = Utils.O755
+    obj.install_path = bld.env['LIBDIR']  + '/utils'
+    obj.LIBDIR       = os.path.normpath(bld.env['DLLDIR'])
+    obj.DATADIR      = os.path.normpath(bld.env['DATADIR'])
+    obj.CONFDIR      = os.path.normpath(bld.env['CONFDIR'])
diff --git a/wscript b/wscript
index 4f4723bc02714aae285b3b2ba205d25c7641b145..a98adf7e959c3958d5ffe8b9c510fd525f75e833 100644 (file)
--- a/wscript
+++ b/wscript
@@ -223,6 +223,7 @@ children = [
         'mcp',
         'patchfiles',
         'headless',
+        'session_utils',
         # shared helper binaries (plugin-scanner, exec-wrapper)
         'libs/fst',
         'libs/vfork',