commandline session utilities

This commit is contained in:
Robin Gareus 2015-12-16 00:25:07 +01:00
parent f05afd973b
commit b228c11311
10 changed files with 625 additions and 0 deletions

29
session_utils/README Normal file
View File

@ -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

View File

@ -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" "$@"

161
session_utils/common.cc Normal file
View File

@ -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 ();
}

38
session_utils/common.h Normal file
View File

@ -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_ */

14
session_utils/debug Executable file
View File

@ -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

30
session_utils/example.cc Normal file
View File

@ -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;
}

235
session_utils/export.cc Normal file
View File

@ -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;
}

6
session_utils/run Executable file
View File

@ -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" "$@"

94
session_utils/wscript Normal file
View File

@ -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'])

View File

@ -223,6 +223,7 @@ children = [
'mcp',
'patchfiles',
'headless',
'session_utils',
# shared helper binaries (plugin-scanner, exec-wrapper)
'libs/fst',
'libs/vfork',