summaryrefslogtreecommitdiff
path: root/session_utils
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2015-12-16 00:25:07 +0100
committerRobin Gareus <robin@gareus.org>2015-12-16 00:25:07 +0100
commitb228c1131187f3822ece3bd6ed26869a68fb0dff (patch)
treecea750878ac04fc188f68c1fb761ff9422c8dab8 /session_utils
parentf05afd973b538a2e1543423f42d6859c0254f7ca (diff)
commandline session utilities
Diffstat (limited to 'session_utils')
-rw-r--r--session_utils/README29
-rw-r--r--session_utils/ardour-util.sh.in17
-rw-r--r--session_utils/common.cc161
-rw-r--r--session_utils/common.h38
-rwxr-xr-xsession_utils/debug14
-rw-r--r--session_utils/example.cc30
-rw-r--r--session_utils/export.cc235
-rwxr-xr-xsession_utils/run6
-rw-r--r--session_utils/wscript94
9 files changed, 624 insertions, 0 deletions
diff --git a/session_utils/README b/session_utils/README
new file mode 100644
index 0000000000..510e3a4e1e
--- /dev/null
+++ b/session_utils/README
@@ -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
index 0000000000..3ffc53d966
--- /dev/null
+++ b/session_utils/ardour-util.sh.in
@@ -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
index 0000000000..d7f92871b9
--- /dev/null
+++ b/session_utils/common.cc
@@ -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
index 0000000000..5263e84b5a
--- /dev/null
+++ b/session_utils/common.h
@@ -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
index 0000000000..6182ef28fd
--- /dev/null
+++ b/session_utils/debug
@@ -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
index 0000000000..044d40e17c
--- /dev/null
+++ b/session_utils/example.cc
@@ -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
index 0000000000..e7d169a0d3
--- /dev/null
+++ b/session_utils/export.cc
@@ -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
index 0000000000..1abec31a51
--- /dev/null
+++ b/session_utils/run
@@ -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
index 0000000000..de9111e3e5
--- /dev/null
+++ b/session_utils/wscript
@@ -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'])