summaryrefslogtreecommitdiff
path: root/libs/ardour
diff options
context:
space:
mode:
authorTaybin Rutkin <taybin@taybin.com>2005-09-24 19:13:41 +0000
committerTaybin Rutkin <taybin@taybin.com>2005-09-24 19:13:41 +0000
commit8af0757b61990767f2a85e68f535a5af9976fd79 (patch)
treef9e06fe12cac866d658a2e7074a61aa74d12f68f /libs/ardour
parentf9546e5c76afa101e9dbe8a057e72463b03430e5 (diff)
libardour added.
git-svn-id: svn://localhost/trunk/ardour2@17 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/ardour')
-rw-r--r--libs/ardour/.cvsignore7
-rw-r--r--libs/ardour/ChangeLog118
-rw-r--r--libs/ardour/SConscript206
-rw-r--r--libs/ardour/ardour/.cvsignore1
-rw-r--r--libs/ardour/ardour/ardour.h80
-rw-r--r--libs/ardour/ardour/audio_library.h90
-rw-r--r--libs/ardour/ardour/audio_track.h162
-rw-r--r--libs/ardour/ardour/audioengine.h242
-rw-r--r--libs/ardour/ardour/audiofilter.h53
-rw-r--r--libs/ardour/ardour/audioplaylist.h121
-rw-r--r--libs/ardour/ardour/audioregion.h229
-rw-r--r--libs/ardour/ardour/auditioner.h73
-rw-r--r--libs/ardour/ardour/automation_event.h247
-rw-r--r--libs/ardour/ardour/click.h46
-rw-r--r--libs/ardour/ardour/config.h57
-rw-r--r--libs/ardour/ardour/configuration.h253
-rw-r--r--libs/ardour/ardour/connection.h94
-rw-r--r--libs/ardour/ardour/constsource.h60
-rw-r--r--libs/ardour/ardour/crossfade.h182
-rw-r--r--libs/ardour/ardour/crossfade_compare.h43
-rw-r--r--libs/ardour/ardour/curve.h86
-rw-r--r--libs/ardour/ardour/cycle_timer.h53
-rw-r--r--libs/ardour/ardour/cycles.h221
-rw-r--r--libs/ardour/ardour/dB.h34
-rw-r--r--libs/ardour/ardour/diskstream.h433
-rw-r--r--libs/ardour/ardour/export.h88
-rw-r--r--libs/ardour/ardour/filesource.h163
-rw-r--r--libs/ardour/ardour/gain.h44
-rw-r--r--libs/ardour/ardour/gdither.h93
-rw-r--r--libs/ardour/ardour/gdither_types.h49
-rw-r--r--libs/ardour/ardour/gdither_types_internal.h75
-rw-r--r--libs/ardour/ardour/history.h153
-rw-r--r--libs/ardour/ardour/insert.h183
-rw-r--r--libs/ardour/ardour/io.h408
-rw-r--r--libs/ardour/ardour/ladspa.h606
-rw-r--r--libs/ardour/ardour/ladspa_plugin.h142
-rw-r--r--libs/ardour/ardour/location.h193
-rw-r--r--libs/ardour/ardour/logcurve.h133
-rw-r--r--libs/ardour/ardour/mix.h74
-rw-r--r--libs/ardour/ardour/named_selection.h55
-rw-r--r--libs/ardour/ardour/noise.h19
-rw-r--r--libs/ardour/ardour/panner.h331
-rw-r--r--libs/ardour/ardour/peak.h17
-rw-r--r--libs/ardour/ardour/playlist.h278
-rw-r--r--libs/ardour/ardour/playlist_templates.h49
-rw-r--r--libs/ardour/ardour/plugin.h194
-rw-r--r--libs/ardour/ardour/plugin_manager.h63
-rw-r--r--libs/ardour/ardour/plugin_state.h14
-rw-r--r--libs/ardour/ardour/port.h213
-rw-r--r--libs/ardour/ardour/recent_sessions.h39
-rw-r--r--libs/ardour/ardour/redirect.h153
-rw-r--r--libs/ardour/ardour/region.h259
-rw-r--r--libs/ardour/ardour/region_factory.h22
-rw-r--r--libs/ardour/ardour/reverse.h38
-rw-r--r--libs/ardour/ardour/route.h359
-rw-r--r--libs/ardour/ardour/route_group.h111
-rw-r--r--libs/ardour/ardour/route_group_specialized.h22
-rw-r--r--libs/ardour/ardour/send.h63
-rw-r--r--libs/ardour/ardour/seqsource.h55
-rw-r--r--libs/ardour/ardour/session.h1754
-rw-r--r--libs/ardour/ardour/session_connection.h40
-rw-r--r--libs/ardour/ardour/session_diskstream.h42
-rw-r--r--libs/ardour/ardour/session_playlist.h47
-rw-r--r--libs/ardour/ardour/session_region.h19
-rw-r--r--libs/ardour/ardour/session_route.h89
-rw-r--r--libs/ardour/ardour/session_selection.h40
-rw-r--r--libs/ardour/ardour/silentsource.h56
-rw-r--r--libs/ardour/ardour/slave.h151
-rw-r--r--libs/ardour/ardour/sndfile_helpers.h37
-rw-r--r--libs/ardour/ardour/sndfilesource.h63
-rw-r--r--libs/ardour/ardour/soundseq.h54
-rw-r--r--libs/ardour/ardour/source.h182
-rw-r--r--libs/ardour/ardour/spline.h90
-rw-r--r--libs/ardour/ardour/state_manager.h48
-rw-r--r--libs/ardour/ardour/stateful.h51
-rw-r--r--libs/ardour/ardour/tempo.h323
-rw-r--r--libs/ardour/ardour/timestamps.h13
-rw-r--r--libs/ardour/ardour/types.h243
-rw-r--r--libs/ardour/ardour/utils.h59
-rw-r--r--libs/ardour/ardour/vst_plugin.h112
-rw-r--r--libs/ardour/audio_library.cc513
-rw-r--r--libs/ardour/audio_playlist.cc873
-rw-r--r--libs/ardour/audio_track.cc1063
-rw-r--r--libs/ardour/audioengine.cc1115
-rw-r--r--libs/ardour/audiofilter.cc87
-rw-r--r--libs/ardour/audioregion.cc1405
-rw-r--r--libs/ardour/auditioner.cc181
-rw-r--r--libs/ardour/automation.cc13
-rw-r--r--libs/ardour/automation_event.cc1211
-rw-r--r--libs/ardour/configuration.cc1105
-rw-r--r--libs/ardour/connection.cc275
-rw-r--r--libs/ardour/crossfade.cc877
-rw-r--r--libs/ardour/curve.cc449
-rw-r--r--libs/ardour/cycle_timer.cc73
-rw-r--r--libs/ardour/default_click.cc1175
-rw-r--r--libs/ardour/diskstream.cc2338
-rw-r--r--libs/ardour/filesource.cc1101
-rw-r--r--libs/ardour/gain.cc62
-rw-r--r--libs/ardour/gdither.cc475
-rw-r--r--libs/ardour/gettext.h82
-rw-r--r--libs/ardour/globals.cc440
-rw-r--r--libs/ardour/i18n.h11
-rw-r--r--libs/ardour/import.cc380
-rw-r--r--libs/ardour/insert.cc1026
-rw-r--r--libs/ardour/io.cc2821
-rw-r--r--libs/ardour/jack_slave.cc91
-rw-r--r--libs/ardour/ladspa_plugin.cc724
-rw-r--r--libs/ardour/location.cc718
-rw-r--r--libs/ardour/mix.cc149
-rw-r--r--libs/ardour/mtc_slave.cc321
-rw-r--r--libs/ardour/named_selection.cc117
-rw-r--r--libs/ardour/panner.cc1670
-rw-r--r--libs/ardour/playlist.cc1754
-rw-r--r--libs/ardour/playlist_factory.cc68
-rw-r--r--libs/ardour/plugin.cc351
-rw-r--r--libs/ardour/plugin_manager.cc486
-rw-r--r--libs/ardour/port.cc63
-rw-r--r--libs/ardour/recent_sessions.cc127
-rw-r--r--libs/ardour/redirect.cc465
-rw-r--r--libs/ardour/region.cc995
-rw-r--r--libs/ardour/reverse.cc125
-rw-r--r--libs/ardour/route.cc2421
-rw-r--r--libs/ardour/route_group.cc184
-rw-r--r--libs/ardour/send.cc162
-rw-r--r--libs/ardour/session.cc3491
-rw-r--r--libs/ardour/session_butler.cc460
-rw-r--r--libs/ardour/session_click.cc253
-rw-r--r--libs/ardour/session_events.cc440
-rw-r--r--libs/ardour/session_export.cc640
-rw-r--r--libs/ardour/session_feedback.cc245
-rw-r--r--libs/ardour/session_midi.cc1498
-rw-r--r--libs/ardour/session_process.cc816
-rw-r--r--libs/ardour/session_state.cc3121
-rw-r--r--libs/ardour/session_time.cc853
-rw-r--r--libs/ardour/session_timefx.cc187
-rw-r--r--libs/ardour/session_transport.cc1151
-rw-r--r--libs/ardour/session_vst.cc316
-rw-r--r--libs/ardour/sndfile_helpers.cc162
-rw-r--r--libs/ardour/sndfilesource.cc207
-rw-r--r--libs/ardour/source.cc842
-rw-r--r--libs/ardour/state_manager.cc66
-rw-r--r--libs/ardour/stateful.cc133
-rw-r--r--libs/ardour/tempo.cc1323
-rw-r--r--libs/ardour/utils.cc224
-rw-r--r--libs/ardour/vst_plugin.cc489
145 files changed, 58521 insertions, 0 deletions
diff --git a/libs/ardour/.cvsignore b/libs/ardour/.cvsignore
new file mode 100644
index 0000000000..74244ff78c
--- /dev/null
+++ b/libs/ardour/.cvsignore
@@ -0,0 +1,7 @@
+libardour.la
+libardour.pc
+version.cc
+*.lo
+*.os
+*.mo
+*.pot
diff --git a/libs/ardour/ChangeLog b/libs/ardour/ChangeLog
new file mode 100644
index 0000000000..a0d3b193b6
--- /dev/null
+++ b/libs/ardour/ChangeLog
@@ -0,0 +1,118 @@
+2002-11-24 gettextize <bug-gnu-gettext@gnu.org>
+
+ * configure.ac (AC_OUTPUT): Add intl/Makefile,
+
+2002-11-24 gettextize <bug-gnu-gettext@gnu.org>
+
+ * Makefile.am (ACLOCAL_AMFLAGS): New variable.
+
+2001-10-26 Paul Davis <pbd>
+
+ * playlist.cc (recover_backup): restored the backup recovery code
+ for playlists.
+
+ * diskstream.cc (do_refill): added state_lock to diskstream, just
+ to be safe.
+
+ * session.cc (butler_thread_work): changed Session ISA thread to
+ HASA thread.
+
+2001-10-23 Paul Davis <pbd>
+
+ merged in marcus' patch for edit/mix group save/restore, and
+ rationalized both it and the existing code for Route::set_state()
+
+2001-10-20 Paul Davis <pbd>
+
+ * session.cc (get_state): in get_state, use the public order for routes.
+
+2001-10-18 Paul Davis <pbd>
+
+ * playlist.cc (read): stop a muted region from causing a playlist
+ read error.
+
+2001-10-17 Paul Davis <pbd>
+
+ * region.cc (read_at): remove staccato noise caused by not
+ shifting target buffer when !opaque.
+
+2001-10-15 Paul Davis <pbd>
+
+ * region.cc (set_fade_out_active): made region fade in/out optional.
+
+ * configure.in: patches from Ben related to libxml++
+
+2001-10-12 Paul Davis <pbd>
+
+ * session.cc (XMLRegionFactory): move most XML-based Region
+ constructor into region.
+
+
+2001-10-10 Paul Davis <pbd>
+
+ * session.cc (load_sources): add whole-file regions when loading
+ sources.
+
+2001-10-09 Paul Davis <pbd>
+
+ * ardour/session.h: fix an ugly bug with a non-reference return type.
+
+2001-10-04 Paul Davis <pbd>
+
+ * playlist.cc (split_region): ensure that left region after split
+ is in the right place.
+
+ * auditioner.cc (play_audition): stop existing audition before
+ starting a new one.
+
+2001-10-03 Paul Davis <pbd>
+
+ * session.cc (process): stop regular process() call from operating
+ on hidden diskstreams and routes. the butler thread still works on
+ all diskstreams, every time, which might be a mistake.
+
+2001-10-02 Paul Davis <pbd>
+
+ * session.cc (set_auto_play_range): added provisional support
+ for play ranges using session events. added code to use
+ auditioner.
+
+ * auditioner.cc: new file/object to support auditioning.
+
+ * route.cc: remove seek() function (didn't exist).
+
+ * session.cc (process): use list<DiskStream *> instead of GList
+ for diskstreams. add auditioner object.
+
+2001-09-30 Paul Davis <pbd>
+
+ * playlist.cc (split_region): fix problem with region splitting
+ not defining two *smaller* regions of the original.
+
+ * region.cc (set_position): remove RegionTemplate object.
+
+ * playlist.cc (struct RegionSorter ): fix sorting to use position,
+ not start - whatever was i thinking ?
+
+2001-09-28 Paul Davis <pbd>
+
+ * source.cc: emit source creation signal.
+
+ * session.cc (first_stage_init): catch all source creation events.
+
+ * sndfilesource.cc (init): fix up an off-by-one substr-length
+ error when creating a sndfilesource.
+
+2001-09-27 Paul Davis <pbd>
+
+ * route.cc (operator): correct loop increment bug that caused a
+ hang when an Insert is added to a Route as a Redirect.
+
+2001-09-25 Paul Davis <pbd>
+
+ * session.cc: make new file sources be partially named for their
+ initial host diskstream.
+
+ peak file construction now carried out en-masse at the
+ end of capture run.
+
diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript
new file mode 100644
index 0000000000..e9b38e75c2
--- /dev/null
+++ b/libs/ardour/SConscript
@@ -0,0 +1,206 @@
+# -*- python -*-
+
+import os
+import glob
+
+Import('env final_prefix install_prefix final_config_prefix libraries i18n')
+
+ardour = env.Copy()
+
+#
+# this defines the version number of libardour
+#
+
+domain = 'libardour'
+
+ardour.Append(DOMAIN = domain, MAJOR = 1, MINOR = 0, MICRO = 0)
+ardour.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"")
+ardour.Append(CCFLAGS="-DLIBSIGC_DISABLE_DEPRECATED")
+ardour.Append(PACKAGE = domain)
+ardour.Append(POTFILE = domain + '.pot')
+
+ardour_files=Split("""
+audio_library.cc
+audio_playlist.cc
+audio_track.cc
+audioengine.cc
+audiofilter.cc
+audioregion.cc
+auditioner.cc
+automation.cc
+automation_event.cc
+configuration.cc
+connection.cc
+crossfade.cc
+curve.cc
+cycle_timer.cc
+default_click.cc
+diskstream.cc
+filesource.cc
+gain.cc
+gdither.cc
+globals.cc
+import.cc
+insert.cc
+io.cc
+jack_slave.cc
+ladspa_plugin.cc
+location.cc
+mtc_slave.cc
+named_selection.cc
+panner.cc
+playlist.cc
+playlist_factory.cc
+plugin.cc
+plugin_manager.cc
+port.cc
+recent_sessions.cc
+redirect.cc
+region.cc
+reverse.cc
+route.cc
+route_group.cc
+send.cc
+session.cc
+session_butler.cc
+session_click.cc
+session_events.cc
+session_export.cc
+session_feedback.cc
+session_midi.cc
+session_process.cc
+session_state.cc
+session_time.cc
+session_timefx.cc
+session_transport.cc
+sndfile_helpers.cc
+sndfilesource.cc
+source.cc
+state_manager.cc
+stateful.cc
+tempo.cc
+utils.cc
+version.cc
+mix.cc
+""")
+
+arch_specific_objects = [ ]
+
+vst_files = [ 'vst_plugin.cc', 'session_vst.cc' ]
+extra_sources = [ ]
+
+if ardour['VST']:
+ extra_sources += vst_files
+
+ardour.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE")
+ardour.Append(CXXFLAGS="-DDATA_DIR=\\\""+final_prefix+"/share\\\"")
+ardour.Append(CXXFLAGS="-DCONFIG_DIR=\\\""+final_config_prefix+"\\\"")
+ardour.Append(CXXFLAGS="-DLOCALEDIR=\\\""+final_prefix+"/share/locale\\\"")
+
+ardour.Merge ([ libraries['jack'] ])
+
+#
+# See if JACK supports jack_client_open()
+#
+
+jack_test_source_file = """
+#include <jack/jack.h>
+int main(int argc, char **argv)
+{
+ jack_client_open ("foo", 0, 0);
+ return 0;
+}
+"""
+def CheckJackClientOpen(context):
+ context.Message('Checking for jack_client_open()...')
+ result = context.TryLink(jack_test_source_file, '.c')
+ context.Result(result)
+ return result
+
+#
+# See if JACK supports jack_recompute_total_latencies()
+#
+
+jack_test_source_file = """
+#include <jack/jack.h>
+int main(int argc, char **argv)
+{
+ jack_recompute_total_latencies ((jack_client_t*) 0);
+ return 0;
+}
+"""
+def CheckJackRecomputeLatencies(context):
+ context.Message('Checking for jack_recompute_total_latencies()...')
+ result = context.TryLink(jack_test_source_file, '.c')
+ context.Result(result)
+ return result
+
+conf = Configure(ardour, custom_tests = {
+ 'CheckJackClientOpen' : CheckJackClientOpen,
+ 'CheckJackRecomputeLatencies' : CheckJackRecomputeLatencies
+})
+
+if conf.CheckJackClientOpen():
+ ardour.Append(CXXFLAGS="-DHAVE_JACK_CLIENT_OPEN")
+
+if conf.CheckJackRecomputeLatencies():
+ ardour.Append(CXXFLAGS="-DHAVE_JACK_RECOMPUTE_LATENCIES")
+
+#
+# Optional header files
+#
+
+if conf.CheckCHeader('wordexp.h'):
+ ardour.Append(CXXFLAGS="-DHAVE_WORDEXP")
+
+if conf.CheckCHeader('sys/vfs.h'):
+ ardour.Append(CXXFLAGS="-DHAVE_SYS_VFS_H")
+
+ardour = conf.Finish ()
+
+ardour.Merge ([
+ libraries['core'],
+ libraries['xml'],
+ libraries['sndfile'],
+ libraries['lrdf'],
+ libraries['samplerate'],
+ libraries['sigc2'],
+ libraries['pbd3'],
+ libraries['soundtouch'],
+ libraries['midi++2']
+ ])
+
+
+ardour.VersionBuild(['version.cc', 'ardour/version.h'], 'SConscript')
+
+def SharedAsmObjectEmitter(target, source, env):
+ for tgt in target:
+ tgt.attributes.shared = 1
+ return (target, source)
+
+
+env['BUILDERS']['SharedAsmObject'] = Builder (action = '$CXX -c -fPIC $SOURCE -o $TARGET',
+ emitter = SharedAsmObjectEmitter,
+ suffix = '$SHOBJSUFFIX',
+ src_suffix = '.s',
+ single_source = 1)
+
+if env['DEVBUILD'] == 1:
+ if env['BUILD_SSE_OPTIMIZATIONS'] == 1:
+ arch_specific_objects = env.SharedAsmObject('sse_functions.os', 'sse_functions.s')
+ libardour = ardour.SharedLibrary('ardour', ardour_files + extra_sources + arch_specific_objects)
+else:
+ if env['BUILD_SSE_OPTIMIZATIONS'] == 1:
+ arch_specific_objects = env.StaticObject(target='sse_functions',source='sse_functions.s')
+
+ libardour = ardour.StaticLibrary('ardour', ardour_files + extra_sources + arch_specific_objects)
+
+Default(libardour)
+
+if env['NLS']:
+ i18n (ardour, ardour_files + vst_files, env)
+
+env.Alias('tarball', env.Distribute (env['DISTTREE'],
+ [ 'SConscript', 'i18n.h', 'gettext.h', 'sse_functions.s' ] +
+ ardour_files + vst_files +
+ glob.glob('po/*.po') + glob.glob('ardour/*.h')))
diff --git a/libs/ardour/ardour/.cvsignore b/libs/ardour/ardour/.cvsignore
new file mode 100644
index 0000000000..67020331ba
--- /dev/null
+++ b/libs/ardour/ardour/.cvsignore
@@ -0,0 +1 @@
+version.h
diff --git a/libs/ardour/ardour/ardour.h b/libs/ardour/ardour/ardour.h
new file mode 100644
index 0000000000..31bd22590e
--- /dev/null
+++ b/libs/ardour/ardour/ardour.h
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 1999 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_ardour_h__
+#define __ardour_ardour_h__
+
+#include <limits.h>
+#include <string>
+#include <signal.h>
+
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/failed_constructor.h>
+
+#include <ardour/configuration.h>
+#include <ardour/types.h>
+
+using namespace PBD;
+
+namespace MIDI {
+ class MachineControl;
+ class Port;
+}
+
+namespace ARDOUR {
+
+ class AudioEngine;
+
+ static const jack_nframes_t max_frames = JACK_MAX_FRAMES;
+
+ int init (AudioEngine&, bool with_vst, bool try_optimization, void (*sighandler)(int,siginfo_t*,void*) = 0);
+ int cleanup ();
+ std::string find_config_file (std::string name);
+ std::string find_data_file (std::string name);
+
+ const layer_t max_layer = UCHAR_MAX;
+
+ id_t new_id();
+
+ Change new_change ();
+
+ extern Change StartChanged;
+ extern Change LengthChanged;
+ extern Change PositionChanged;
+ extern Change NameChanged;
+ extern Change BoundsChanged;
+
+ struct LocaleGuard {
+ LocaleGuard (const char*);
+ ~LocaleGuard ();
+ const char* old;
+ };
+
+};
+
+/* how do we make these be within the Ardour namespace? */
+
+extern MIDI::Port* default_mmc_port;
+extern MIDI::Port* default_mtc_port;
+extern MIDI::Port* default_midi_port;
+
+#endif /* __ardour_ardour_h__ */
+
diff --git a/libs/ardour/ardour/audio_library.h b/libs/ardour/ardour/audio_library.h
new file mode 100644
index 0000000000..3aab3993b9
--- /dev/null
+++ b/libs/ardour/ardour/audio_library.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2003 Paul Davis
+ Author: Taybin Rutkin
+
+ 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audio_library_h__
+#define __ardour_audio_library_h__
+
+#include <list>
+#include <string>
+#include <map>
+
+#include <sigc++/signal.h>
+
+using std::list;
+using std::string;
+using std::map;
+
+namespace ARDOUR {
+
+class AudioLibrary
+{
+ public:
+ AudioLibrary ();
+ ~AudioLibrary ();
+
+ // add_group returns the URI of the created group
+ string add_group (string group, string parent_uri = "");
+ void remove_group (string uri);
+ void get_groups (list<string>& groups, string parent_uri = "");
+
+ // add_member returns the URI of the created group
+ string add_member (string member, string parent_uri = "");
+ void remove_member (string uri);
+ void get_members (list<string>& members, string parent_uri = "");
+ string get_member_filename (string uri);
+
+ void search_members_and (list<string>& results,
+ const map<string,string>& fields);
+ void search_members_or (list<string>& results,
+ const map<string,string>& fields);
+
+ void add_field (string field);
+ void get_fields (list<string>& fields);
+ void remove_field (string field);
+ string get_field (string uri, string field);
+ void set_field (string uri, string field, string literal);
+
+ string get_label (string uri);
+ void set_label (string uri, string label);
+
+ sigc::signal<void, string, string> added_group; // group, parent
+ sigc::signal<void, string, string> added_member;// member, parent
+ sigc::signal<void, string> removed_group;
+ sigc::signal<void, string> removed_member;
+ sigc::signal<void> fields_changed;
+
+ private:
+ void save_changes ();
+ string field_uri (string name);
+
+ bool is_rdf_type (string uri, string type);
+ void remove_uri (string uri);
+
+ string src;
+
+ void initialize_db();
+};
+
+extern AudioLibrary* Library;
+
+} // ARDOUR namespace
+
+#endif // __ardour_audio_library_h__
diff --git a/libs/ardour/ardour/audio_track.h b/libs/ardour/ardour/audio_track.h
new file mode 100644
index 0000000000..e7ba315e45
--- /dev/null
+++ b/libs/ardour/ardour/audio_track.h
@@ -0,0 +1,162 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audio_track_h__
+#define __ardour_audio_track_h__
+
+#include <ardour/route.h>
+
+namespace ARDOUR {
+
+class Session;
+class DiskStream;
+class AudioPlaylist;
+
+class AudioTrack : public Route
+{
+ public:
+ AudioTrack (Session&, string name, Route::Flag f = Route::Flag (0));
+ AudioTrack (Session&, const XMLNode&);
+ ~AudioTrack ();
+
+ int set_name (string str, void *src);
+
+ int roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+
+ jack_nframes_t offset, int declick, bool can_record, bool rec_monitors_input);
+ int no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t offset, bool state_changing, bool can_record, bool rec_monitors_input);
+ int silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t offset, bool can_record, bool rec_monitors_input);
+
+ void toggle_monitor_input ();
+
+ bool can_record() const { return true; }
+ void set_record_enable (bool yn, void *src);
+
+ DiskStream& disk_stream() const { return *diskstream; }
+ int set_diskstream (DiskStream&, void *);
+ int use_diskstream (string name);
+ int use_diskstream (id_t id);
+
+ jack_nframes_t update_total_latency();
+ void set_latency_delay (jack_nframes_t);
+
+ int export_stuff (vector<Sample*>& buffers, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t end_frame);
+
+ sigc::signal<void,void*> diskstream_changed;
+
+ enum FreezeState {
+ NoFreeze,
+ Frozen,
+ UnFrozen
+ };
+
+ FreezeState freeze_state() const;
+
+ sigc::signal<void> FreezeChange;
+
+ void freeze (InterThreadInfo&);
+ void unfreeze ();
+
+ void bounce (InterThreadInfo&);
+ void bounce_range (jack_nframes_t start, jack_nframes_t end, InterThreadInfo&);
+
+ XMLNode& get_state();
+ int set_state(const XMLNode& node);
+
+ MIDI::Controllable& midi_rec_enable_control() {
+ return _midi_rec_enable_control;
+ }
+
+ void reset_midi_control (MIDI::Port*, bool);
+ void send_all_midi_feedback ();
+
+ bool record_enabled() const;
+ void set_meter_point (MeterPoint, void* src);
+
+ protected:
+ DiskStream *diskstream;
+ MeterPoint _saved_meter_point;
+
+ void passthru_silence (jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset, int declick,
+ bool meter);
+
+ uint32_t n_process_buffers ();
+
+ private:
+ struct FreezeRecordInsertInfo {
+ FreezeRecordInsertInfo(XMLNode& st)
+ : state (st), insert (0) {}
+
+ XMLNode state;
+ Insert* insert;
+ id_t id;
+ UndoAction memento;
+ };
+
+ struct FreezeRecord {
+ FreezeRecord() {
+ playlist = 0;
+ have_mementos = false;
+ }
+
+ ~FreezeRecord();
+
+ AudioPlaylist* playlist;
+ vector<FreezeRecordInsertInfo*> insert_info;
+ bool have_mementos;
+ FreezeState state;
+ };
+
+ FreezeRecord _freeze_record;
+ XMLNode* pending_state;
+
+ void diskstream_record_enable_changed (void *src);
+ void diskstream_input_channel_changed (void *src);
+
+ void input_change_handler (void *src);
+
+ sigc::connection recenable_connection;
+ sigc::connection ic_connection;
+
+ XMLNode& state(bool);
+
+ int deprecated_use_diskstream_connections ();
+ void set_state_part_two ();
+ void set_state_part_three ();
+
+ struct MIDIRecEnableControl : public MIDI::Controllable {
+ MIDIRecEnableControl (AudioTrack&, MIDI::Port *);
+ void set_value (float);
+ void send_feedback (bool);
+ MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force = false);
+ AudioTrack& track;
+ bool setting;
+ bool last_written;
+ };
+
+ MIDIRecEnableControl _midi_rec_enable_control;
+};
+
+}; /* namespace ARDOUR*/
+
+#endif /* __ardour_audio_track_h__ */
diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h
new file mode 100644
index 0000000000..d349f5bae5
--- /dev/null
+++ b/libs/ardour/ardour/audioengine.h
@@ -0,0 +1,242 @@
+/*
+ Copyright (C) 2002-2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audioengine_h__
+#define __ardour_audioengine_h__
+
+#include <list>
+#include <set>
+#include <cmath>
+#include <exception>
+#include <string>
+
+#include <sigc++/signal.h>
+#include <pthread.h>
+#include <ardour/ardour.h>
+#include <jack/jack.h>
+#include <jack/transport.h>
+
+namespace ARDOUR {
+
+class Session;
+class Port;
+
+class AudioEngine : public sigc::trackable
+{
+ public:
+ AudioEngine (std::string client_name);
+ virtual ~AudioEngine ();
+
+ jack_client_t* jack() const { return _jack; }
+ bool connected() const { return _jack != 0; }
+
+ std::string client_name() const { return jack_client_name; }
+
+ int reconnect_to_jack ();
+ int disconnect_from_jack();
+
+ bool will_reconnect_at_halt ();
+ void set_reconnect_at_halt (bool);
+
+ int stop ();
+ int start ();
+ bool running() const { return _running; }
+
+ PBD::NonBlockingLock& process_lock() { return _process_lock; }
+
+ jack_nframes_t frame_rate();
+ jack_nframes_t frames_per_cycle();
+
+ int usecs_per_cycle () const { return _usecs_per_cycle; }
+
+ jack_nframes_t frames_since_cycle_start () {
+ if (!_running || !_jack) return 0;
+ return jack_frames_since_cycle_start (_jack);
+ }
+ jack_nframes_t frame_time () {
+ if (!_running || !_jack) return 0;
+ return jack_frame_time (_jack);
+ }
+
+ jack_nframes_t transport_frame () const {
+ if (!_running || !_jack) return 0;
+ return jack_get_current_transport_frame (_jack);
+ }
+
+ int request_buffer_size (jack_nframes_t);
+
+ jack_nframes_t set_monitor_check_interval (jack_nframes_t);
+
+ float get_cpu_load() {
+ if (!_running || !_jack) return 0;
+ return jack_cpu_load (_jack);
+ }
+
+ void set_session (Session *);
+ void remove_session ();
+
+ class PortRegistrationFailure : public std::exception {
+ public:
+ virtual const char *what() const throw() { return "failed port registration"; }
+ };
+
+ class NoBackendAvailable : public std::exception {
+ public:
+ virtual const char *what() const throw() { return "could not connect to engine backend"; }
+ };
+
+ Port *register_audio_input_port (const string& portname);
+ Port *register_audio_output_port (const string& portname);
+ int unregister_port (Port *);
+
+ int connect (const string& source, const string& destination);
+ int disconnect (const string& source, const string& destination);
+ int disconnect (Port *);
+
+ const char ** get_ports (const string& port_name_pattern, const string& type_name_pattern, uint32_t flags);
+
+ uint32_t n_physical_outputs () const;
+ uint32_t n_physical_inputs () const;
+
+ string get_nth_physical_output (uint32_t n) {
+ return get_nth_physical (n, JackPortIsInput);
+ }
+
+ string get_nth_physical_input (uint32_t n) {
+ return get_nth_physical (n, JackPortIsOutput);
+ }
+
+ jack_nframes_t get_port_total_latency (const Port&);
+ void update_total_latencies ();
+
+ /* the caller may not delete the object pointed to by
+ the return value
+ */
+
+ Port *get_port_by_name (const string& name, bool keep = true);
+
+ enum TransportState {
+ TransportStopped = JackTransportStopped,
+ TransportRolling = JackTransportRolling,
+ TransportLooping = JackTransportLooping,
+ TransportStarting = JackTransportStarting
+ };
+
+ void transport_start ();
+ void transport_stop ();
+ void transport_locate (jack_nframes_t);
+ TransportState transport_state ();
+
+ int reset_timebase ();
+
+ /* start/stop freewheeling */
+
+ int freewheel (bool onoff);
+ bool freewheeling() const { return _freewheeling; }
+
+ /* this signal is sent for every process() cycle while freewheeling.
+ the regular process() call to session->process() is not made.
+ */
+
+ sigc::signal<int,jack_nframes_t> Freewheel;
+
+ sigc::signal<void> Xrun;
+
+ /* this signal is if JACK notifies us of a graph order event */
+
+ sigc::signal<void> GraphReordered;
+
+ /* this signal is emitted if the sample rate changes */
+
+ sigc::signal<void,jack_nframes_t> SampleRateChanged;
+
+ /* this signal is sent if JACK ever disconnects us */
+
+ sigc::signal<void> Halted;
+
+ /* these two are emitted when the engine itself is
+ started and stopped
+ */
+
+ sigc::signal<void> Running;
+ sigc::signal<void> Stopped;
+
+ std::string make_port_name_relative (std::string);
+ std::string make_port_name_non_relative (std::string);
+
+ private:
+ ARDOUR::Session *session;
+ jack_client_t *_jack;
+ std::string jack_client_name;
+ PBD::NonBlockingLock port_lock;
+ PBD::NonBlockingLock _process_lock;
+ PBD::Lock session_remove_lock;
+ pthread_cond_t session_removed;
+ bool session_remove_pending;
+ bool _running;
+ bool _has_run;
+ jack_nframes_t _buffer_size;
+ jack_nframes_t _frame_rate;
+ jack_nframes_t monitor_check_interval;
+ jack_nframes_t last_monitor_check;
+ jack_nframes_t _processed_frames;
+ bool _freewheeling;
+ bool _freewheel_thread_registered;
+ sigc::slot<int,jack_nframes_t> freewheel_action;
+ bool reconnect_on_halt;
+ int _usecs_per_cycle;
+
+ typedef std::set<Port*> Ports;
+ Ports ports;
+
+ int process_callback (jack_nframes_t nframes);
+ void remove_all_ports ();
+
+ typedef std::pair<std::string,std::string> PortConnection;
+ typedef std::list<PortConnection> PortConnections;
+
+ PortConnections port_connections;
+ void remove_connections_for (Port*);
+
+ string get_nth_physical (uint32_t which, int flags);
+
+ static int _xrun_callback (void *arg);
+ static int _graph_order_callback (void *arg);
+ static int _process_callback (jack_nframes_t nframes, void *arg);
+ static int _sample_rate_callback (jack_nframes_t nframes, void *arg);
+ static int _bufsize_callback (jack_nframes_t nframes, void *arg);
+ static void _jack_timebase_callback (jack_transport_state_t, jack_nframes_t, jack_position_t*, int, void*);
+ static int _jack_sync_callback (jack_transport_state_t, jack_position_t*, void *arg);
+ static void _freewheel_callback (int , void *arg);
+
+ void jack_timebase_callback (jack_transport_state_t, jack_nframes_t, jack_position_t*, int);
+ int jack_sync_callback (jack_transport_state_t, jack_position_t*);
+ int jack_bufsize_callback (jack_nframes_t);
+ int jack_sample_rate_callback (jack_nframes_t);
+
+ static void halted (void *);
+ static void meter (Port *port, jack_nframes_t nframes);
+
+ int connect_to_jack (std::string client_name);
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_audioengine_h__ */
diff --git a/libs/ardour/ardour/audiofilter.h b/libs/ardour/ardour/audiofilter.h
new file mode 100644
index 0000000000..d0fc275cf6
--- /dev/null
+++ b/libs/ardour/ardour/audiofilter.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audiofilter_h__
+#define __ardour_audiofilter_h__
+
+#include <vector>
+#include <ardour/audioregion.h>
+
+namespace ARDOUR {
+
+class AudioRegion;
+class Session;
+class FileSource;
+
+class AudioFilter {
+
+ public:
+ AudioFilter (ARDOUR::Session& s)
+ : session (s){}
+ virtual ~AudioFilter() {}
+
+
+ virtual int run (ARDOUR::AudioRegion&) = 0;
+ std::vector<ARDOUR::AudioRegion*> results;
+
+ protected:
+ ARDOUR::Session& session;
+
+ int make_new_sources (ARDOUR::AudioRegion&, ARDOUR::AudioRegion::SourceList&);
+ int finish (ARDOUR::AudioRegion&, ARDOUR::AudioRegion::SourceList&);
+};
+
+} /* namespace */
+
+#endif /* __ardour_audiofilter_h__ */
diff --git a/libs/ardour/ardour/audioplaylist.h b/libs/ardour/ardour/audioplaylist.h
new file mode 100644
index 0000000000..26cce91b12
--- /dev/null
+++ b/libs/ardour/ardour/audioplaylist.h
@@ -0,0 +1,121 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audio_playlist_h__
+#define __ardour_audio_playlist_h__
+
+#include <vector>
+#include <list>
+
+#include <ardour/ardour.h>
+#include <ardour/playlist.h>
+
+namespace ARDOUR {
+
+class Session;
+class Region;
+class AudioRegion;
+class Source;
+
+class AudioPlaylist : public ARDOUR::Playlist
+{
+ public:
+ typedef std::list<Crossfade*> Crossfades;
+
+ private:
+
+ struct State : public ARDOUR::StateManager::State {
+ RegionList regions;
+ std::list<UndoAction> region_states;
+
+ Crossfades crossfades;
+ std::list<UndoAction> crossfade_states;
+
+ State (std::string why) : ARDOUR::StateManager::State (why) {}
+ ~State ();
+ };
+
+ public:
+ AudioPlaylist (Session&, const XMLNode&, bool hidden = false);
+ AudioPlaylist (Session&, string name, bool hidden = false);
+ AudioPlaylist (const AudioPlaylist&, string name, bool hidden = false);
+ AudioPlaylist (const AudioPlaylist&, jack_nframes_t start, jack_nframes_t cnt, string name, bool hidden = false);
+
+ void clear (bool with_delete = false, bool with_save = true);
+
+ jack_nframes_t read (Sample *dst, Sample *mixdown, float *gain_buffer, jack_nframes_t start, jack_nframes_t cnt, uint32_t chan_n=0);
+
+ int set_state (const XMLNode&);
+ UndoAction get_memento() const;
+
+ sigc::signal<void,Crossfade *> NewCrossfade;
+
+ template<class T> void foreach_crossfade (T *t, void (T::*func)(Crossfade *));
+ void crossfades_at (jack_nframes_t frame, Crossfades&);
+
+ template<class T> void apply_to_history (T& obj, void (T::*method)(const ARDOUR::StateManager::StateMap&, state_id_t)) {
+ RegionLock rlock (this);
+ (obj.*method) (states, _current_state_id);
+ }
+
+ bool destroy_region (Region*);
+
+ void get_equivalent_regions (const AudioRegion&, std::vector<AudioRegion*>&);
+ void get_region_list_equivalent_regions (const AudioRegion&, std::vector<AudioRegion*>&);
+
+ void drop_all_states ();
+
+ protected:
+
+ /* state management */
+
+ StateManager::State* state_factory (std::string) const;
+ Change restore_state (StateManager::State&);
+ void send_state_change (Change);
+
+ /* playlist "callbacks" */
+ void notify_crossfade_added (Crossfade *);
+ void flush_notifications ();
+
+ void refresh_dependents (Region& region);
+ void check_dependents (Region& region, bool norefresh);
+ void remove_dependents (Region& region);
+
+ protected:
+ ~AudioPlaylist (); /* public should use unref() */
+
+ private:
+ Crossfades _crossfades;
+ Crossfades _pending_xfade_adds;
+
+ void crossfade_invalidated (Crossfade*);
+ XMLNode& state (bool full_state);
+ void dump () const;
+
+ bool region_changed (Change, Region*);
+ void crossfade_changed (Change);
+ void add_crossfade (Crossfade&);
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_audio_playlist_h__ */
+
+
diff --git a/libs/ardour/ardour/audioregion.h b/libs/ardour/ardour/audioregion.h
new file mode 100644
index 0000000000..44159f73f5
--- /dev/null
+++ b/libs/ardour/ardour/audioregion.h
@@ -0,0 +1,229 @@
+/*
+ Copyright (C) 2000-2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_audio_region_h__
+#define __ardour_audio_region_h__
+
+#include <vector>
+
+#include <pbd/fastlog.h>
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/source.h>
+#include <ardour/gain.h>
+#include <ardour/region.h>
+#include <ardour/export.h>
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Route;
+class Playlist;
+class Session;
+class AudioFilter;
+
+struct AudioRegionState : public RegionState
+{
+ AudioRegionState (std::string why);
+
+ Curve _fade_in;
+ Curve _fade_out;
+ Curve _envelope;
+ gain_t _scale_amplitude;
+ uint32_t _fade_in_disabled;
+ uint32_t _fade_out_disabled;
+};
+
+class AudioRegion : public Region
+{
+ public:
+ typedef vector<Source *> SourceList;
+
+ static Change FadeInChanged;
+ static Change FadeOutChanged;
+ static Change FadeInActiveChanged;
+ static Change FadeOutActiveChanged;
+ static Change EnvelopeActiveChanged;
+ static Change ScaleAmplitudeChanged;
+ static Change EnvelopeChanged;
+
+ AudioRegion (Source&, jack_nframes_t start, jack_nframes_t length, bool announce = true);
+ AudioRegion (Source&, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+ AudioRegion (SourceList &, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+ AudioRegion (const AudioRegion&, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+ AudioRegion (const AudioRegion&);
+ AudioRegion (Source&, const XMLNode&);
+ AudioRegion (SourceList &, const XMLNode&);
+ ~AudioRegion();
+
+ bool region_list_equivalent (const AudioRegion&);
+ bool source_equivalent (const AudioRegion&);
+ bool equivalent (const AudioRegion&);
+ bool size_equivalent (const AudioRegion&);
+
+ void lock_sources ();
+ void unlock_sources ();
+ Source& source (uint32_t n=0) const { if (n < sources.size()) return *sources[n]; else return *sources[0]; }
+
+ void set_scale_amplitude (gain_t);
+ gain_t scale_amplitude() const { return _scale_amplitude; }
+
+ void normalize_to (float target_in_dB = 0.0f);
+
+ uint32_t n_channels() { return sources.size(); }
+ vector<string> master_source_names();
+
+ bool envelope_active () const { return _flags & Region::EnvelopeActive; }
+ bool fade_in_active () const { return _flags & Region::FadeIn; }
+ bool fade_out_active () const { return _flags & Region::FadeOut; }
+ bool captured() const { return !(_flags & (Region::Flag (Region::Import|Region::External))); }
+
+ Curve& fade_in() { return _fade_in; }
+ Curve& fade_out() { return _fade_out; }
+ Curve& envelope() { return _envelope; }
+
+ jack_nframes_t read_peaks (PeakData *buf, jack_nframes_t npeaks, jack_nframes_t offset, jack_nframes_t cnt, uint32_t chan_n=0, double samples_per_unit= 1.0) const;
+
+ virtual jack_nframes_t read_at (Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t position, jack_nframes_t cnt,
+ uint32_t chan_n = 0,
+ jack_nframes_t read_frames = 0,
+ jack_nframes_t skip_frames = 0) const;
+
+ jack_nframes_t master_read_at (Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t position, jack_nframes_t cnt, uint32_t chan_n=0) const;
+
+
+ XMLNode& state (bool);
+ XMLNode& get_state ();
+ int set_state (const XMLNode&);
+
+ static void set_default_fade (float steepness, jack_nframes_t len);
+
+ enum FadeShape {
+ Linear,
+ Fast,
+ Slow,
+ LogA,
+ LogB,
+
+ };
+
+ void set_fade_in_active (bool yn);
+ void set_fade_in_shape (FadeShape);
+ void set_fade_in_length (jack_nframes_t);
+ void set_fade_in (FadeShape, jack_nframes_t);
+
+ void set_fade_out_active (bool yn);
+ void set_fade_out_shape (FadeShape);
+ void set_fade_out_length (jack_nframes_t);
+ void set_fade_out (FadeShape, jack_nframes_t);
+
+ void set_envelope_active (bool yn);
+
+ int separate_by_channel (ARDOUR::Session&, vector<AudioRegion*>&) const;
+
+ uint32_t read_data_count() const { return _read_data_count; }
+
+ ARDOUR::Playlist* playlist() const { return _playlist; }
+
+ UndoAction get_memento() const;
+
+ /* filter */
+
+ int apply (AudioFilter&);
+
+ /* export */
+
+ int exportme (ARDOUR::Session&, ARDOUR::AudioExportSpecification&);
+
+ Region* get_parent();
+
+ /* xfade/fade interactions */
+
+ void suspend_fade_in ();
+ void suspend_fade_out ();
+ void resume_fade_in ();
+ void resume_fade_out ();
+
+ private:
+ friend class Playlist;
+
+ private:
+ SourceList sources;
+ SourceList master_sources; /* used when timefx are applied, so
+ we can always use the original
+ source.
+ */
+ mutable Curve _fade_in;
+ FadeShape _fade_in_shape;
+ mutable Curve _fade_out;
+ FadeShape _fade_out_shape;
+ mutable Curve _envelope;
+ gain_t _scale_amplitude;
+ uint32_t _fade_in_disabled;
+ uint32_t _fade_out_disabled;
+
+ void set_default_fades ();
+ void set_default_fade_in ();
+ void set_default_fade_out ();
+ void set_default_envelope ();
+
+ StateManager::State* state_factory (std::string why) const;
+ Change restore_state (StateManager::State&);
+
+ void recompute_gain_at_end ();
+ void recompute_gain_at_start ();
+
+ bool copied() const { return _flags & Copied; }
+ void maybe_uncopy ();
+ void rename_after_first_edit ();
+
+ jack_nframes_t _read_at (const SourceList&, Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t position, jack_nframes_t cnt,
+ uint32_t chan_n = 0,
+ jack_nframes_t read_frames = 0,
+ jack_nframes_t skip_frames = 0) const;
+
+ bool verify_start (jack_nframes_t position);
+ bool verify_length (jack_nframes_t position);
+ bool verify_start_mutable (jack_nframes_t& start);
+ bool verify_start_and_length (jack_nframes_t start, jack_nframes_t length);
+ void recompute_at_start ();
+ void recompute_at_end ();
+
+ void envelope_changed (Change);
+
+ void source_deleted (Source*);
+};
+
+} /* namespace ARDOUR */
+
+/* access from C objects */
+
+extern "C" {
+ int region_read_peaks_from_c (void *arg, uint32_t npeaks, uint32_t start, uint32_t length, intptr_t data, uint32_t n_chan, double samples_per_unit);
+ uint32_t region_length_from_c (void *arg);
+ uint32_t sourcefile_length_from_c (void *arg);
+}
+
+#endif /* __ardour_audio_region_h__ */
diff --git a/libs/ardour/ardour/auditioner.h b/libs/ardour/ardour/auditioner.h
new file mode 100644
index 0000000000..b79620eaa2
--- /dev/null
+++ b/libs/ardour/ardour/auditioner.h
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_auditioner_h__
+#define __ardour_auditioner_h__
+
+#include <string>
+#include <pthread.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/atomic.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audio_track.h>
+
+namespace ARDOUR {
+
+class Session;
+class AudioRegion;
+class AudioPlaylist;
+
+class Auditioner : public AudioTrack
+{
+ public:
+ Auditioner (Session&);
+ ~Auditioner ();
+
+ void audition_region (AudioRegion&);
+
+ ARDOUR::AudioPlaylist& prepare_playlist ();
+ void audition_current_playlist ();
+
+ int play_audition (jack_nframes_t nframes);
+
+ void cancel_audition () {
+ atomic_set (&_active, 0);
+ }
+
+ bool active() const { return atomic_read (&_active); }
+
+ private:
+ AudioRegion *the_region;
+ jack_nframes_t current_frame;
+ atomic_t _active;
+ PBD::Lock lock;
+ jack_nframes_t length;
+
+ void drop_ports ();
+ static void *_drop_ports (void *);
+ void actually_drop_ports ();
+ void output_changed (IOChange, void*);
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_auditioner_h__ */
diff --git a/libs/ardour/ardour/automation_event.h b/libs/ardour/ardour/automation_event.h
new file mode 100644
index 0000000000..562a424cc7
--- /dev/null
+++ b/libs/ardour/ardour/automation_event.h
@@ -0,0 +1,247 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_automation_event_h__
+#define __ardour_automation_event_h__
+
+#include <stdint.h>
+#include <list>
+#include <cmath>
+
+#include <sigc++/signal.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+#include <pbd/xml++.h>
+#include <ardour/ardour.h>
+#include <ardour/state_manager.h>
+
+namespace ARDOUR {
+
+struct ControlEvent {
+ double when;
+ double value;
+
+ ControlEvent (double w, double v)
+ : when (w), value (v) { }
+ ControlEvent (const ControlEvent& other)
+ : when (other.when), value (other.value) {}
+
+ virtual ~ControlEvent() {}
+
+// bool operator==(const ControlEvent& other) {
+// return value == other.value && when == other.when;
+// }
+
+};
+
+class AutomationList : public StateManager
+{
+ public:
+ typedef std::list<ControlEvent*> AutomationEventList;
+ typedef AutomationEventList::iterator iterator;
+ typedef AutomationEventList::const_iterator const_iterator;
+
+ AutomationList(double default_value, bool no_state = false);
+ ~AutomationList();
+
+ AutomationList (const AutomationList&);
+ AutomationList (const AutomationList&, double start, double end);
+ AutomationList& operator= (const AutomationList&);
+ bool operator== (const AutomationList&);
+
+ void freeze();
+ void thaw ();
+
+ AutomationEventList::size_type size() const { return events.size(); }
+ bool empty() const { return events.empty(); }
+
+ void reset_default (double val) {
+ default_value = val;
+ }
+
+ void clear ();
+ void x_scale (double factor);
+ bool extend_to (double);
+
+ void reposition_for_rt_add (double when);
+ void rt_add (double when, double value);
+ iterator add (double when, double value, iterator, bool ignore_mode = false);
+ void add (double when, double value, bool for_loading = false);
+
+ void erase_range (double start, double end);
+ void erase (iterator);
+ void erase (iterator, iterator);
+ void move_range (iterator start, iterator end, double, double);
+ void modify (iterator, double, double);
+
+ AutomationList* cut (double, double);
+ AutomationList* copy (double, double);
+ void clear (double, double);
+
+ AutomationList* cut (iterator, iterator);
+ AutomationList* copy (iterator, iterator);
+ void clear (iterator, iterator);
+
+ bool paste (AutomationList&, double position, float times);
+
+ void set_automation_state (AutoState);
+ AutoState automation_state() const { return _state; }
+ sigc::signal<void> automation_style_changed;
+
+ void set_automation_style (AutoStyle m);
+ AutoStyle automation_style() const { return _style; }
+ sigc::signal<void> automation_state_changed;
+
+ bool automation_playback() {
+ return (_state & Play) || ((_state & Touch) && !_touching);
+ }
+ bool automation_write () {
+ return (_state & Write) || ((_state & Touch) && _touching);
+ }
+
+ void start_touch ();
+ void stop_touch ();
+ bool touching() const { return _touching; }
+
+ void set_yrange (double min, double max) {
+ min_yval = min;
+ max_yval = max;
+ }
+
+ double get_max_y() const { return max_yval; }
+ double get_min_y() const { return min_yval; }
+
+ void truncate_end (double length);
+ void truncate_start (double length);
+
+ iterator begin() { return events.begin(); }
+ iterator end() { return events.end(); }
+
+ ControlEvent* back() { return events.back(); }
+ ControlEvent* front() { return events.front(); }
+
+ const_iterator const_begin() const { return events.begin(); }
+ const_iterator const_end() const { return events.end(); }
+
+ std::pair<AutomationList::iterator,AutomationList::iterator> control_points_adjacent (double when);
+
+ template<class T> void apply_to_points (T& obj, void (T::*method)(const AutomationList&)) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ (obj.*method)(*this);
+ }
+
+ UndoAction get_memento () const;
+
+ virtual void store_state (XMLNode& node) const;
+ virtual void load_state (const XMLNode&);
+
+ void set_max_xval (double);
+ double get_max_xval() const { return max_xval; }
+
+ double eval (double where) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ return unlocked_eval (where);
+ }
+
+ double rt_safe_eval (double where, bool& ok) {
+
+ TentativeLockMonitor lm (lock, __LINE__, __FILE__);
+
+ if ((ok = lm.locked())) {
+ return unlocked_eval (where);
+ } else {
+ return 0.0;
+ }
+ }
+
+ struct TimeComparator {
+ bool operator() (const ControlEvent* a, const ControlEvent* b) {
+ return a->when < b->when;
+ }
+ };
+
+ protected:
+ struct State : public ARDOUR::StateManager::State {
+ AutomationEventList events;
+
+ State (std::string why) : ARDOUR::StateManager::State (why) {}
+ };
+
+ AutomationEventList events;
+ mutable PBD::NonBlockingLock lock;
+ bool _frozen;
+ bool changed_when_thawed;
+ bool _dirty;
+
+ struct LookupCache {
+ double left; /* leftmost x coordinate used when finding "range" */
+ std::pair<AutomationList::iterator,AutomationList::iterator> range;
+ };
+
+ LookupCache lookup_cache;
+
+ AutoState _state;
+ AutoStyle _style;
+ bool _touching;
+ bool _new_touch;
+ double max_xval;
+ double min_yval;
+ double max_yval;
+ double default_value;
+ bool no_state;
+
+ iterator rt_insertion_point;
+ double rt_pos;
+
+ void maybe_signal_changed ();
+ void mark_dirty ();
+ void _x_scale (double factor);
+
+ /* called by type-specific unlocked_eval() to handle
+ common case of 0, 1 or 2 control points.
+ */
+
+ double shared_eval (double x);
+
+ /* called by shared_eval() to handle any case of
+ 3 or more control points.
+ */
+
+ virtual double multipoint_eval (double x);
+
+ /* called by locked entry point and various private
+ locations where we already hold the lock.
+ */
+
+ virtual double unlocked_eval (double where);
+
+ Change restore_state (StateManager::State&);
+ StateManager::State* state_factory (std::string why) const;
+
+ virtual ControlEvent* point_factory (double,double) const;
+ virtual ControlEvent* point_factory (const ControlEvent&) const;
+
+
+ AutomationList* cut_copy_clear (double, double, int op);
+};
+
+} // namespace
+
+#endif /* __ardour_automation_event_h__ */
diff --git a/libs/ardour/ardour/click.h b/libs/ardour/ardour/click.h
new file mode 100644
index 0000000000..71214978a5
--- /dev/null
+++ b/libs/ardour/ardour/click.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_click_h__
+#define __ardour_click_h__
+
+#include <ardour/io.h>
+
+namespace ARDOUR {
+
+class ClickIO : public IO
+{
+ public:
+ ClickIO (Session& s, const string& name,
+
+ int input_min = -1, int input_max = -1,
+
+ int output_min = -1, int output_max = -1)
+ : IO (s, name, input_min, input_max, output_min, output_max) {}
+
+ ~ClickIO() {}
+
+ protected:
+ uint32_t pans_required () const { return 1; }
+};
+
+}; /* namespace ARDOUR */
+
+#endif /*__ardour_click_h__ */
diff --git a/libs/ardour/ardour/config.h b/libs/ardour/ardour/config.h
new file mode 100644
index 0000000000..45bf2bac6d
--- /dev/null
+++ b/libs/ardour/ardour/config.h
@@ -0,0 +1,57 @@
+/* config.h. Generated automatically by configure. */
+/* config.h.in. Generated automatically from configure.in by autoheader. */
+
+/* Define if you don't have vprintf but do have _doprnt. */
+/* #undef HAVE_DOPRNT */
+
+/* Define if you have the vprintf function. */
+/* #undef HAVE_VPRINTF */
+
+/* Define if you have the ANSI C header files. */
+/* #undef STDC_HEADERS */
+
+/* Define if your <sys/time.h> declares struct tm. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Define if you have the regcomp function. */
+/* #undef HAVE_REGCOMP */
+
+/* Define if you have the strerror function. */
+/* #undef HAVE_STRERROR */
+
+/* Define if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define if you have the <ladspa.h> header file. */
+#define HAVE_LADSPA_H 1
+
+/* Define if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define if you have the <sys/dir.h> header file. */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define if you have the <sys/ndir.h> header file. */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if you have the gdbm library (-lgdbm). */
+#define HAVE_LIBGDBM 1
+
+/* Define if you have the sndfile library (-lsndfile). */
+#define HAVE_LIBSNDFILE 1
+
+/* Name of package */
+#define PACKAGE "ardour"
+
+/* Version number of package */
+#define VERSION "0.107.3"
+
diff --git a/libs/ardour/ardour/configuration.h b/libs/ardour/ardour/configuration.h
new file mode 100644
index 0000000000..0c42027646
--- /dev/null
+++ b/libs/ardour/ardour/configuration.h
@@ -0,0 +1,253 @@
+/*
+ Copyright (C) 1999 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_configuration_h__
+#define __ardour_configuration_h__
+
+#include <map>
+
+#include <sys/types.h>
+#include <string>
+
+#include <ardour/types.h>
+#include <ardour/stateful.h>
+
+using std::string;
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Configuration : public Stateful
+{
+ public:
+ Configuration();
+ virtual ~Configuration();
+
+ struct MidiPortDescriptor {
+ string tag;
+ string device;
+ string type;
+ string mode;
+
+ MidiPortDescriptor (const XMLNode&);
+ XMLNode& get_state();
+ };
+
+ std::map<string,MidiPortDescriptor *> midi_ports;
+
+ int load_state ();
+ int save_state ();
+
+ XMLNode& option_node (const string &, const string &);
+
+ int set_state (const XMLNode&);
+ XMLNode& get_state (void);
+
+ XMLNode * get_keys() const;
+ void set_keys(XMLNode *);
+
+ void set_use_vst (bool yn);
+ bool get_use_vst();
+
+ bool get_trace_midi_input ();
+ void set_trace_midi_input (bool);
+
+ bool get_trace_midi_output ();
+ void set_trace_midi_output (bool);
+
+ string get_raid_path();
+ void set_raid_path(string);
+
+ uint32_t get_minimum_disk_io();
+ void set_minimum_disk_io(uint32_t);
+
+ float get_track_buffer();
+ void set_track_buffer(float);
+
+ bool does_hiding_groups_deactivates_groups();
+ void set_hiding_groups_deactivates_groups(bool);
+
+ string get_auditioner_output_left();
+ void set_auditioner_output_left(string);
+
+ string get_auditioner_output_right();
+ void set_auditioner_output_right(string);
+
+ bool get_mute_affects_pre_fader();
+ void set_mute_affects_pre_fader (bool);
+
+ bool get_mute_affects_post_fader();
+ void set_mute_affects_post_fader (bool);
+
+ bool get_mute_affects_control_outs ();
+ void set_mute_affects_control_outs (bool);
+
+ bool get_mute_affects_main_outs ();
+ void set_mute_affects_main_outs (bool);
+
+ bool get_solo_latch ();
+ void set_solo_latch (bool);
+
+ uint32_t get_disk_choice_space_threshold();
+ void set_disk_choice_space_threshold (uint32_t);
+
+ string get_mmc_port_name();
+ void set_mmc_port_name(string);
+
+ string get_mtc_port_name();
+ void set_mtc_port_name(string);
+
+ string get_midi_port_name();
+ void set_midi_port_name(string);
+
+ bool get_use_hardware_monitoring();
+ void set_use_hardware_monitoring(bool);
+
+ bool get_jack_time_master();
+ void set_jack_time_master(bool);
+
+ bool get_native_format_is_bwf();
+ void set_native_format_is_bwf(bool);
+
+ bool get_plugins_stop_with_transport();
+ void set_plugins_stop_with_transport(bool);
+
+ bool get_no_sw_monitoring();
+ void set_no_sw_monitoring(bool);
+
+ bool get_stop_recording_on_xrun();
+ void set_stop_recording_on_xrun(bool);
+
+ bool get_verify_remove_last_capture();
+ void set_verify_remove_last_capture(bool);
+
+ bool get_stop_at_session_end();
+ void set_stop_at_session_end(bool);
+
+ bool get_seamless_looping();
+ void set_seamless_looping(bool);
+
+ bool get_auto_xfade();
+ void set_auto_xfade (bool);
+
+ bool get_no_new_session_dialog();
+ void set_no_new_session_dialog(bool);
+
+ uint32_t get_timecode_skip_limit ();
+ void set_timecode_skip_limit (uint32_t);
+
+ bool get_timecode_source_is_synced ();
+ void set_timecode_source_is_synced (bool);
+
+ string get_user_ardour_path ();
+ string get_system_ardour_path ();
+
+ gain_t get_quieten_at_speed ();
+ void set_quieten_at_speed (gain_t);
+
+ private:
+ void set_defaults ();
+ string get_system_path();
+ string get_user_path();
+
+ /* this is subject to wordexp, so we need
+ to keep the original (user-entered) form
+ around. e.g. ~/blah-> /home/foo/blah
+ */
+
+ string raid_path;
+ bool raid_path_is_user;
+ string orig_raid_path;
+
+ uint32_t minimum_disk_io_bytes;
+ bool minimum_disk_io_bytes_is_user;
+ float track_buffer_seconds;
+ bool track_buffer_seconds_is_user;
+ bool hiding_groups_deactivates_groups;
+ bool hiding_groups_deactivates_groups_is_user;
+ string auditioner_output_left;
+ bool auditioner_output_left_is_user;
+ string auditioner_output_right;
+ bool auditioner_output_right_is_user;
+ bool mute_affects_pre_fader;
+ bool mute_affects_pre_fader_is_user;
+ bool mute_affects_post_fader;
+ bool mute_affects_post_fader_is_user;
+ bool mute_affects_control_outs;
+ bool mute_affects_control_outs_is_user;
+ bool mute_affects_main_outs;
+ bool mute_affects_main_outs_is_user;
+ bool solo_latch;
+ bool solo_latch_is_user;
+ uint32_t disk_choice_space_threshold;
+ bool disk_choice_space_threshold_is_user;
+ string mtc_port_name;
+ bool mtc_port_name_is_user;
+ string mmc_port_name;
+ bool mmc_port_name_is_user;
+ string midi_port_name;
+ bool midi_port_name_is_user;
+ bool use_hardware_monitoring;
+ bool use_hardware_monitoring_is_user;
+ bool be_jack_time_master;
+ bool be_jack_time_master_is_user;
+ bool native_format_is_bwf;
+ bool native_format_is_bwf_is_user;
+ bool trace_midi_input;
+ bool trace_midi_input_is_user;
+ bool trace_midi_output;
+ bool trace_midi_output_is_user;
+ bool plugins_stop_with_transport;
+ bool plugins_stop_with_transport_is_user;
+ bool no_sw_monitoring;
+ bool no_sw_monitoring_is_user;
+ bool stop_recording_on_xrun;
+ bool stop_recording_on_xrun_is_user;
+ bool verify_remove_last_capture;
+ bool verify_remove_last_capture_is_user;
+ bool stop_at_session_end;
+ bool stop_at_session_end_is_user;
+ bool seamless_looping;
+ bool seamless_looping_is_user;
+ bool auto_xfade;
+ bool auto_xfade_is_user;
+ bool no_new_session_dialog;
+ bool no_new_session_dialog_is_user;
+ uint32_t timecode_skip_limit;
+ bool timecode_skip_limit_is_user;
+ bool timecode_source_is_synced;
+ bool timecode_source_is_synced_is_user;
+ bool use_vst; /* always per-user */
+ bool quieten_at_speed;
+ bool quieten_at_speed_is_user;
+
+ XMLNode *key_node;
+ bool user_configuration;
+
+ XMLNode& state (bool user_only);
+};
+
+extern Configuration *Config;
+extern gain_t speed_quietning; /* see comment in configuration.cc */
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_configuration_h__ */
diff --git a/libs/ardour/ardour/connection.h b/libs/ardour/ardour/connection.h
new file mode 100644
index 0000000000..b33af9cb21
--- /dev/null
+++ b/libs/ardour/ardour/connection.h
@@ -0,0 +1,94 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_connection_h__
+#define __ardour_connection_h__
+
+#include <vector>
+#include <string>
+#include <sigc++/signal.h>
+#include <pbd/lockmonitor.h>
+#include <ardour/stateful.h>
+
+using std::vector;
+using std::string;
+
+namespace ARDOUR {
+
+class Connection : public Stateful, public sigc::trackable {
+ public:
+ Connection (string name, bool sdep = false) : _name (name), _sysdep(sdep) {}
+ ~Connection() {}
+
+ typedef vector<string> PortList;
+
+ void set_name (string name, void *src);
+ string name() const { return _name; }
+
+ bool system_dependent() const { return _sysdep; }
+
+ uint32_t nports () const { return _ports.size(); }
+ const PortList& port_connections (int port) const;
+
+ void add_connection (int port, string portname);
+ void remove_connection (int port, string portname);
+
+ void add_port ();
+ void remove_port (int port);
+ void clear ();
+
+ sigc::signal<void,void*> NameChanged;
+ sigc::signal<void> ConfigurationChanged;
+ sigc::signal<void,int> ConnectionsChanged;
+
+ bool operator==(const Connection& other) const;
+
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ protected:
+ Connection (const XMLNode&);
+
+ private:
+ mutable PBD::Lock port_lock;
+ vector<PortList> _ports;
+ string _name;
+ bool _sysdep;
+
+ int set_connections (const string& str);
+ int parse_io_string (const string& str, vector<string>& ports);
+};
+
+class InputConnection : public Connection {
+ public:
+ InputConnection (string name, bool sdep = false) : Connection (name, sdep) {}
+ InputConnection (const XMLNode&);
+};
+
+class OutputConnection : public Connection {
+ public:
+ OutputConnection (string name, bool sdep = false) : Connection (name, sdep) {}
+ OutputConnection (const XMLNode&);
+};
+
+}
+
+#endif /* __ardour_connection_h__ */
+
diff --git a/libs/ardour/ardour/constsource.h b/libs/ardour/ardour/constsource.h
new file mode 100644
index 0000000000..d000afb347
--- /dev/null
+++ b/libs/ardour/ardour/constsource.h
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __playlist_const_buffer_h__
+#define __playlist_const_buffer_h__
+
+#include <string>
+#include <cstdlib>
+
+#include "edl.h"
+
+namespace EDL {
+
+class ConstSource : public Source {
+ public:
+ ConstSource (const gchar *id) {
+ _type = Source::Const;
+ value = strtod (id, 0);
+ strncpy (idstr, id, 15);
+ idstr[15] = '\0';
+ }
+
+ const gchar * const id() { return idstr; }
+
+ uint32_t length() { return ~0U; }
+
+ uint32_t read (Source::Data *dst, uint32_t start, uint32_t cnt) {
+ uint32_t n = cnt;
+ while (n--) *dst++ = value;
+ return cnt;
+ }
+ void peak (guint8 *max, guint8 *min, uint32_t start, uint32_t cnt) {
+ *max = *min = (guint8) value;
+ }
+
+ private:
+ Source::Data value;
+ gchar idstr[16];
+};
+
+}; /* namespace EDL */
+
+#endif /* __playlist_const_buffer_h__ */
diff --git a/libs/ardour/ardour/crossfade.h b/libs/ardour/ardour/crossfade.h
new file mode 100644
index 0000000000..419b980a83
--- /dev/null
+++ b/libs/ardour/ardour/crossfade.h
@@ -0,0 +1,182 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_overlap_h__
+#define __ardour_overlap_h__
+
+#include <vector>
+#include <algorithm>
+
+#include <sigc++/signal.h>
+
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/curve.h>
+#include <ardour/audioregion.h>
+#include <ardour/state_manager.h>
+#include <ardour/crossfade_compare.h>
+
+namespace ARDOUR {
+
+class AudioRegion;
+class Playlist;
+
+struct CrossfadeState : public StateManager::State {
+ CrossfadeState (std::string reason) : StateManager::State (reason) {}
+
+ UndoAction fade_in_memento;
+ UndoAction fade_out_memento;
+ jack_nframes_t position;
+ jack_nframes_t length;
+ AnchorPoint anchor_point;
+ bool follow_overlap;
+ bool active;
+};
+
+class Crossfade : public Stateful, public StateManager
+{
+ public:
+
+ class NoCrossfadeHere: std::exception {
+ public:
+ virtual const char *what() const throw() { return "no crossfade should be constructed here"; }
+ };
+
+ /* constructor for "fixed" xfades at each end of an internal overlap */
+
+ Crossfade (ARDOUR::AudioRegion& in, ARDOUR::AudioRegion& out,
+ jack_nframes_t position,
+ jack_nframes_t initial_length,
+ AnchorPoint);
+
+ /* constructor for xfade between two regions that are overlapped in any way
+ except the "internal" case.
+ */
+
+ Crossfade (ARDOUR::AudioRegion& in, ARDOUR::AudioRegion& out, CrossfadeModel, bool active);
+
+ /* the usual XML constructor */
+
+ Crossfade (const ARDOUR::Playlist&, XMLNode&);
+ virtual ~Crossfade();
+
+ bool operator== (const ARDOUR::Crossfade&);
+
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ ARDOUR::AudioRegion& in() const { return *_in; }
+ ARDOUR::AudioRegion& out() const { return *_out; }
+
+ jack_nframes_t read_at (Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t position, jack_nframes_t cnt,
+ uint32_t chan_n,
+ jack_nframes_t read_frames = 0,
+ jack_nframes_t skip_frames = 0);
+
+ bool refresh ();
+
+ uint32_t upper_layer () const {
+ return std::max (_in->layer(), _out->layer());
+ }
+
+ uint32_t lower_layer () const {
+ return std::min (_in->layer(), _out->layer());
+ }
+
+ bool involves (ARDOUR::AudioRegion& region) const {
+ return _in == &region || _out == &region;
+ }
+
+ bool involves (ARDOUR::AudioRegion& a, ARDOUR::AudioRegion& b) const {
+ return (_in == &a && _out == &b) || (_in == &b && _out == &a);
+ }
+
+ jack_nframes_t length() const { return _length; }
+ jack_nframes_t overlap_length() const;
+ jack_nframes_t position() const { return _position; }
+
+ sigc::signal<void,Crossfade*> Invalidated;
+ sigc::signal<void> GoingAway;
+
+ bool covers (jack_nframes_t frame) const {
+ return _position <= frame && frame < _position + _length;
+ }
+
+ OverlapType coverage (jack_nframes_t start, jack_nframes_t end) const;
+
+ UndoAction get_memento() const;
+
+ static void set_buffer_size (jack_nframes_t);
+
+ bool active () const { return _active; }
+ void set_active (bool yn);
+
+ bool following_overlap() const { return _follow_overlap; }
+ bool can_follow_overlap() const;
+ void set_follow_overlap (bool yn);
+
+ Curve& fade_in() { return _fade_in; }
+ Curve& fade_out() { return _fade_out; }
+
+ jack_nframes_t set_length (jack_nframes_t);
+
+ static jack_nframes_t short_xfade_length() { return _short_xfade_length; }
+ static void set_short_xfade_length (jack_nframes_t n);
+
+ static Change ActiveChanged;
+
+ private:
+ friend struct CrossfadeComparePtr;
+
+ static jack_nframes_t _short_xfade_length;
+
+ ARDOUR::AudioRegion* _in;
+ ARDOUR::AudioRegion* _out;
+ bool _active;
+ bool _in_update;
+ OverlapType overlap_type;
+ jack_nframes_t _length;
+ jack_nframes_t _position;
+ AnchorPoint _anchor_point;
+ bool _follow_overlap;
+ bool _fixed;
+ Curve _fade_in;
+ Curve _fade_out;
+
+ static Sample* crossfade_buffer_out;
+ static Sample* crossfade_buffer_in;
+
+ void initialize ();
+ int compute (ARDOUR::AudioRegion&, ARDOUR::AudioRegion&, CrossfadeModel);
+ bool update (bool force);
+
+ StateManager::State* state_factory (std::string why) const;
+ Change restore_state (StateManager::State&);
+
+ void member_changed (ARDOUR::Change);
+
+};
+
+
+} // namespace ARDOUR
+
+#endif /* __ardour_overlap_h__ */
diff --git a/libs/ardour/ardour/crossfade_compare.h b/libs/ardour/ardour/crossfade_compare.h
new file mode 100644
index 0000000000..2ecf79c04c
--- /dev/null
+++ b/libs/ardour/ardour/crossfade_compare.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_crossfade_compare_h__
+#define __ardour_crossfade_compare_h__
+
+/* this exists so that playlist.h doesn't have to include crossfade.h
+ */
+
+namespace ARDOUR {
+
+class Crossfade;
+
+struct CrossfadeComparePtr {
+ bool operator() (const Crossfade *a, const Crossfade *b) const;
+};
+
+enum AnchorPoint {
+ StartOfIn,
+ EndOfIn,
+ EndOfOut
+};
+
+}
+
+#endif /* __ardour_crossfade_compare_h__ */
diff --git a/libs/ardour/ardour/curve.h b/libs/ardour/ardour/curve.h
new file mode 100644
index 0000000000..1c6a4c5bc4
--- /dev/null
+++ b/libs/ardour/ardour/curve.h
@@ -0,0 +1,86 @@
+/*
+ Copyright (C) 2001-2003 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_curve_h__
+#define __ardour_curve_h__
+
+#include <sys/types.h>
+#include <sigc++/signal.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+#include <list>
+#include <algorithm>
+#include <pthread.h>
+#include <ardour/automation_event.h>
+
+namespace ARDOUR {
+
+struct CurvePoint : public ControlEvent
+{
+ double coeff[4];
+
+ CurvePoint (double w, double v)
+ : ControlEvent (w, v) {
+
+ coeff[0] = coeff[1] = coeff[2] = coeff[3] = 0.0;
+ }
+
+ ~CurvePoint() {}
+};
+
+class Curve : public AutomationList
+{
+ public:
+ Curve (double min_yval, double max_yval, double defaultvalue, bool nostate = false);
+ ~Curve ();
+ Curve (const Curve& other);
+ Curve (const Curve& other, double start, double end);
+
+ bool rt_safe_get_vector (double x0, double x1, float *arg, int32_t veclen);
+ void get_vector (double x0, double x1, float *arg, int32_t veclen);
+
+ AutomationEventList::iterator closest_control_point_before (double xval);
+ AutomationEventList::iterator closest_control_point_after (double xval);
+
+ void solve ();
+
+ protected:
+ ControlEvent* point_factory (double,double) const;
+ ControlEvent* point_factory (const ControlEvent&) const;
+
+ Change restore_state (StateManager::State&);
+
+ private:
+ AutomationList::iterator last_bound;
+
+ double unlocked_eval (double where);
+ double multipoint_eval (double x);
+
+ void _get_vector (double x0, double x1, float *arg, int32_t veclen);
+
+};
+
+}; /* namespace ARDOUR */
+
+extern "C" {
+ void curve_get_vector_from_c (void *arg, double, double, float*, int32_t);
+}
+
+#endif /* __ardour_curve_h__ */
diff --git a/libs/ardour/ardour/cycle_timer.h b/libs/ardour/ardour/cycle_timer.h
new file mode 100644
index 0000000000..1ec7c74903
--- /dev/null
+++ b/libs/ardour/ardour/cycle_timer.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_cycle_timer_h__
+#define __ardour_cycle_timer_h__
+
+#include <string>
+#include <cstdio>
+
+#include <ardour/cycles.h>
+
+using std::string;
+
+class CycleTimer {
+ private:
+ static float cycles_per_usec;
+ uint32_t long entry;
+ uint32_t long exit;
+ string _name;
+
+ public:
+ CycleTimer(string name) : _name (name){
+ if (cycles_per_usec == 0) {
+ cycles_per_usec = get_mhz ();
+ }
+ entry = get_cycles();
+ }
+ ~CycleTimer() {
+ exit = get_cycles();
+ printf ("%s: %.9f usecs (%lu-%lu)\n", _name.c_str(), (float) (exit - entry) / cycles_per_usec, entry, exit);
+ }
+
+ static float get_mhz ();
+};
+
+#endif /* __ardour_cycle_timer_h__ */
diff --git a/libs/ardour/ardour/cycles.h b/libs/ardour/ardour/cycles.h
new file mode 100644
index 0000000000..f194988da9
--- /dev/null
+++ b/libs/ardour/ardour/cycles.h
@@ -0,0 +1,221 @@
+/*
+ Copyright (C) 2001 Paul Davis
+ Code derived from various headers from the Linux kernel
+
+ 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_cycles_h__
+#define __ardour_cycles_h__
+
+#include <stdint.h>
+
+#if defined(__i386__) || defined(__x86_64__)
+
+/*
+ * Standard way to access the cycle counter on i586+ CPUs.
+ * Currently only used on SMP.
+ *
+ * If you really have a SMP machine with i486 chips or older,
+ * compile for that, and this will just always return zero.
+ * That's ok, it just means that the nicer scheduling heuristics
+ * won't work for you.
+ *
+ * We only use the low 32 bits, and we'd simply better make sure
+ * that we reschedule before that wraps. Scheduling at least every
+ * four billion cycles just basically sounds like a good idea,
+ * regardless of how fast the machine is.
+ */
+typedef uint64_t cycles_t;
+
+extern cycles_t cacheflush_time;
+
+#define rdtscll(val) \
+ __asm__ __volatile__("rdtsc" : "=A" (val))
+
+static inline cycles_t get_cycles (void)
+{
+ uint32_t long ret;
+
+ rdtscll(ret);
+ return ret;
+}
+
+#elif defined(__powerpc__)
+
+#define CPU_FTR_601 0x00000100
+
+typedef uint32_t cycles_t;
+
+/*
+ * For the "cycle" counter we use the timebase lower half.
+ * Currently only used on SMP.
+ */
+
+extern cycles_t cacheflush_time;
+
+static inline cycles_t get_cycles(void)
+{
+ cycles_t ret = 0;
+
+ __asm__ __volatile__(
+ "98: mftb %0\n"
+ "99:\n"
+ ".section __ftr_fixup,\"a\"\n"
+ " .long %1\n"
+ " .long 0\n"
+ " .long 98b\n"
+ " .long 99b\n"
+ ".previous"
+ : "=r" (ret) : "i" (CPU_FTR_601));
+ return ret;
+}
+
+#elif defined(__ia64__)
+/* ia64 */
+
+typedef uint32_t cycles_t;
+static inline cycles_t
+get_cycles (void)
+{
+ cycles_t ret;
+ __asm__ __volatile__ ("mov %0=ar.itc" : "=r"(ret));
+ return ret;
+}
+
+#elif defined(__alpha__)
+/* alpha */
+
+/*
+ * Standard way to access the cycle counter.
+ * Currently only used on SMP for scheduling.
+ *
+ * Only the low 32 bits are available as a continuously counting entity.
+ * But this only means we'll force a reschedule every 8 seconds or so,
+ * which isn't an evil thing.
+ */
+
+typedef uint32_t cycles_t;
+static inline cycles_t get_cycles (void)
+{
+ cycles_t ret;
+ __asm__ __volatile__ ("rpcc %0" : "=r"(ret));
+ return ret;
+}
+
+#elif defined(__s390__)
+/* s390 */
+
+typedef uint32_t long cycles_t;
+static inline cycles_t get_cycles(void)
+{
+ cycles_t cycles;
+ __asm__("stck 0(%0)" : : "a" (&(cycles)) : "memory", "cc");
+ return cycles >> 2;
+}
+
+#elif defined(__hppa__)
+/* hppa/parisc */
+
+#define mfctl(reg) ({ \
+ uint32_t cr; \
+ __asm__ __volatile__( \
+ "mfctl " #reg ",%0" : \
+ "=r" (cr) \
+ ); \
+ cr; \
+})
+
+typedef uint32_t cycles_t;
+static inline cycles_t get_cycles (void)
+{
+ return mfctl(16);
+}
+
+#elif defined(__mips__)
+/* mips/mipsel */
+
+/*
+ * Standard way to access the cycle counter.
+ * Currently only used on SMP for scheduling.
+ *
+ * Only the low 32 bits are available as a continuously counting entity.
+ * But this only means we'll force a reschedule every 8 seconds or so,
+ * which isn't an evil thing.
+ *
+ * We know that all SMP capable CPUs have cycle counters.
+ */
+
+#define __read_32bit_c0_register(source, sel) \
+({ int __res; \
+ if (sel == 0) \
+ __asm__ __volatile__( \
+ "mfc0\t%0, " #source "\n\t" \
+ : "=r" (__res)); \
+ else \
+ __asm__ __volatile__( \
+ ".set\tmips32\n\t" \
+ "mfc0\t%0, " #source ", " #sel "\n\t" \
+ ".set\tmips0\n\t" \
+ : "=r" (__res)); \
+ __res; \
+})
+
+/* #define CP0_COUNT $9 */
+#define read_c0_count() __read_32bit_c0_register($9, 0)
+
+typedef uint32_t cycles_t;
+static inline cycles_t get_cycles (void)
+{
+ return read_c0_count();
+}
+
+/* begin mach */
+#elif defined(__APPLE__)
+#include <CoreAudio/CoreAudioTypes.h>
+#include <CoreAudio/HostTime.h>
+typedef UInt64 cycles_t;
+static inline cycles_t get_cycles (void)
+{
+ UInt64 time = AudioGetCurrentHostTime();
+ return AudioConvertHostTimeToNanos(time);
+}
+/* end mach */
+
+#else
+
+/* debian: sparc, arm, m68k */
+
+#warning You are compiling libardour on a platform for which ardour/cycles.h needs work
+
+#include <sys/time.h>
+
+typedef long cycles_t;
+
+extern cycles_t cacheflush_time;
+
+static inline cycles_t get_cycles(void)
+{
+ struct timeval tv;
+ gettimeofday (&tv, NULL);
+
+ return tv.tv_usec;
+}
+
+#endif
+
+#endif /* __ardour_cycles_h__ */
diff --git a/libs/ardour/ardour/dB.h b/libs/ardour/ardour/dB.h
new file mode 100644
index 0000000000..703de6fb1a
--- /dev/null
+++ b/libs/ardour/ardour/dB.h
@@ -0,0 +1,34 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_dB_h__
+#define __ardour_dB_h__
+
+#include <pbd/fastlog.h>
+
+static inline float dB_to_coefficient (float dB) {
+ return dB > -318.8f ? pow (10.0f, dB * 0.05f) : 0.0f;
+}
+
+static inline float coefficient_to_dB (float coeff) {
+ return 20.0f * fast_log10 (coeff);
+}
+
+#endif /* __ardour_dB_h__ */
diff --git a/libs/ardour/ardour/diskstream.h b/libs/ardour/ardour/diskstream.h
new file mode 100644
index 0000000000..9028f0d9e0
--- /dev/null
+++ b/libs/ardour/ardour/diskstream.h
@@ -0,0 +1,433 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_diskstream_h__
+#define __ardour_diskstream_h__
+
+#include <sigc++/signal.h>
+
+#include <cmath>
+#include <string>
+#include <queue>
+#include <map>
+#include <vector>
+
+#include <time.h>
+
+#include <pbd/fastlog.h>
+#include <pbd/ringbufferNPT.h>
+#include <pbd/atomic.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/session.h>
+#include <ardour/route_group.h>
+#include <ardour/route.h>
+#include <ardour/port.h>
+#include <ardour/utils.h>
+#include <ardour/stateful.h>
+
+struct tm;
+
+namespace ARDOUR {
+
+class AudioEngine;
+class Send;
+class Session;
+class AudioPlaylist;
+class FileSource;
+class IO;
+
+class DiskStream : public Stateful, public sigc::trackable
+{
+ public:
+ enum Flag {
+ Recordable = 0x1,
+ Hidden = 0x2
+ };
+
+ DiskStream (Session &, const string& name, Flag f = Recordable);
+ DiskStream (Session &, const XMLNode&);
+
+ string name() const { return _name; }
+
+ ARDOUR::IO* io() const { return _io; }
+ void set_io (ARDOUR::IO& io);
+
+ DiskStream& ref() { _refcnt++; return *this; }
+ void unref() { if (_refcnt) _refcnt--; if (_refcnt == 0) delete this; }
+ uint32_t refcnt() const { return _refcnt; }
+
+ float playback_buffer_load() const;
+ float capture_buffer_load() const;
+
+ void set_flag (Flag f) {
+ _flags |= f;
+ }
+
+ void unset_flag (Flag f) {
+ _flags &= ~f;
+ }
+
+ AlignStyle alignment_style() const { return _alignment_style; }
+ void set_align_style (AlignStyle);
+ void set_persistent_align_style (AlignStyle);
+
+ bool hidden() const { return _flags & Hidden; }
+ bool recordable() const { return _flags & Recordable; }
+
+ jack_nframes_t roll_delay() const { return _roll_delay; }
+ void set_roll_delay (jack_nframes_t);
+
+ void set_name (string str, void* src);
+
+ string input_source (uint32_t n=0) const {
+ if (n < channels.size()) {
+ return channels[n].source ? channels[n].source->name() : "";
+ } else {
+ return "";
+ }
+ }
+
+ Port *input_source_port (uint32_t n=0) const {
+ if (n < channels.size()) return channels[n].source; return 0;
+ }
+
+ void set_record_enabled (bool yn, void *src);
+ bool record_enabled() const { return atomic_read (&_record_enabled); }
+ void punch_in ();
+ void punch_out ();
+
+ bool reversed() const { return _actual_speed < 0.0f; }
+ float speed() const { return _visible_speed; }
+ void set_speed (float);
+
+ float peak_power(uint32_t n=0) {
+ float x = channels[n].peak_power;
+ channels[n].peak_power = 0.0f;
+ if (x > 0.0f) {
+ return 20.0f * fast_log10(x);
+ } else {
+ return minus_infinity();
+ }
+ }
+
+ int use_playlist (AudioPlaylist *);
+ int use_new_playlist ();
+ int use_copy_playlist ();
+
+ void start_scrub (jack_nframes_t where);
+ void end_scrub ();
+
+ Sample *playback_buffer (uint32_t n=0) {
+ if (n < channels.size())
+ return channels[n].current_playback_buffer;
+ return 0;
+ }
+
+ Sample *capture_buffer (uint32_t n=0) {
+ if (n < channels.size())
+ return channels[n].current_capture_buffer;
+ return 0;
+ }
+
+ AudioPlaylist *playlist () { return _playlist; }
+
+ FileSource *fades_source (uint32_t n=0) {
+ if (n < channels.size())
+ return channels[n].fades_source;
+ return 0;
+ }
+ FileSource *write_source (uint32_t n=0) {
+ if (n < channels.size())
+ return channels[n].write_source;
+ return 0;
+ }
+
+ jack_nframes_t current_capture_start() const { return capture_start_frame; }
+ jack_nframes_t current_capture_end() const { return capture_start_frame + capture_captured; }
+ jack_nframes_t get_capture_start_frame (uint32_t n=0);
+ jack_nframes_t get_captured_frames (uint32_t n=0);
+
+ uint32_t n_channels() { return _n_channels; }
+
+ int add_channel ();
+ int remove_channel ();
+
+ static void set_disk_io_chunk_frames (uint32_t n) {
+ disk_io_chunk_frames = n;
+ }
+
+ static jack_nframes_t disk_io_frames() { return disk_io_chunk_frames; }
+
+ sigc::signal<void,void*> record_enable_changed;
+ sigc::signal<void> speed_changed;
+ sigc::signal<void,void*> reverse_changed;
+ sigc::signal<void> PlaylistChanged;
+ sigc::signal<void> AlignmentStyleChanged;
+
+ static sigc::signal<void> DiskOverrun;
+ static sigc::signal<void> DiskUnderrun;
+ static sigc::signal<void,DiskStream*> DiskStreamCreated; // XXX use a ref with sigc2
+ static sigc::signal<void,DiskStream*> CannotRecordNoInput; // XXX use a ref with sigc2
+ static sigc::signal<void,list<Source*>*> DeleteSources;
+
+ /* stateful */
+
+ XMLNode& get_state(void);
+ int set_state(const XMLNode& node);
+
+ void monitor_input (bool);
+
+ jack_nframes_t capture_offset() const { return _capture_offset; }
+ void set_capture_offset ();
+
+ static void swap_by_ptr (Sample *first, Sample *last) {
+ while (first < last) {
+ Sample tmp = *first;
+ *first++ = *last;
+ *last-- = tmp;
+ }
+ }
+
+ static void swap_by_ptr (Sample *first, Sample *last, jack_nframes_t n) {
+ while (n--) {
+ Sample tmp = *first;
+ *first++ = *last;
+ *last-- = tmp;
+ }
+ }
+
+ bool slaved() const { return _slaved; }
+ void set_slaved(bool yn) { _slaved = yn; }
+
+ int set_loop (Location *loc);
+ sigc::signal<void,Location *> LoopSet;
+
+ std::list<Region*>& last_capture_regions () {
+ return _last_capture_regions;
+ }
+
+ void handle_input_change (IOChange, void *src);
+
+ id_t id() const { return _id; }
+
+ XMLNode* deprecated_io_node;
+
+ protected:
+ friend class Session;
+
+ /* the Session is the only point of access for these
+ because they require that the Session is "inactive"
+ while they are called.
+ */
+
+ void set_pending_overwrite (bool);
+ int overwrite_existing_buffers ();
+ void reverse_scrub_buffer (bool to_forward);
+ void set_block_size (jack_nframes_t);
+ int internal_playback_seek (jack_nframes_t distance);
+ int can_internal_playback_seek (jack_nframes_t distance);
+ void reset_write_sources (bool);
+ void non_realtime_input_change ();
+
+ uint32_t read_data_count() const { return _read_data_count; }
+ uint32_t write_data_count() const { return _write_data_count; }
+
+ protected:
+ friend class Auditioner;
+ int seek (jack_nframes_t which_sample, bool complete_refill = false);
+
+ protected:
+ friend class AudioTrack;
+
+ void prepare ();
+ int process (jack_nframes_t transport_frame, jack_nframes_t nframes, jack_nframes_t offset, bool can_record, bool rec_monitors_input);
+ bool commit (jack_nframes_t nframes);
+ void recover (); /* called if commit will not be called, but process was */
+
+ private:
+
+ /* use unref() to destroy a diskstream */
+
+ ~DiskStream();
+
+ struct ChannelInfo {
+
+ Sample *playback_wrap_buffer;
+ Sample *capture_wrap_buffer;
+ Sample *speed_buffer;
+
+ float peak_power;
+
+ FileSource *fades_source;
+ FileSource *write_source;
+
+ Port *source;
+ Sample *current_capture_buffer;
+ Sample *current_playback_buffer;
+
+ RingBufferNPT<Sample> *playback_buf;
+ RingBufferNPT<Sample> *capture_buf;
+
+ Sample* scrub_buffer;
+ Sample* scrub_forward_buffer;
+ Sample* scrub_reverse_buffer;
+
+ RingBufferNPT<Sample>::rw_vector playback_vector;
+ RingBufferNPT<Sample>::rw_vector capture_vector;
+ };
+
+ typedef vector<ChannelInfo> ChannelList;
+
+ string _name;
+ ARDOUR::Session& _session;
+ ARDOUR::IO* _io;
+ ChannelList channels;
+ uint32_t _n_channels;
+ id_t _id;
+
+ atomic_t _record_enabled;
+ bool rec_monitoring_off_for_roll;
+ AudioPlaylist* _playlist;
+ float _visible_speed;
+ float _actual_speed;
+ /* items needed for speed change logic */
+ bool _buffer_reallocation_required;
+ bool _seek_required;
+
+ bool force_refill;
+ jack_nframes_t capture_start_frame;
+ jack_nframes_t capture_captured;
+ bool was_recording;
+ jack_nframes_t adjust_capture_position;
+ jack_nframes_t _capture_offset;
+ jack_nframes_t _roll_delay;
+ jack_nframes_t first_recordable_frame;
+ jack_nframes_t last_recordable_frame;
+ int last_possibly_recording;
+ AlignStyle _alignment_style;
+ bool _scrubbing;
+ bool _slaved;
+ bool _processed;
+ Location* loop_location;
+ jack_nframes_t overwrite_frame;
+ off_t overwrite_offset;
+ bool pending_overwrite;
+ bool overwrite_queued;
+ IOChange input_change_pending;
+ jack_nframes_t wrap_buffer_size;
+ jack_nframes_t speed_buffer_size;
+
+ uint64_t last_phase;
+ uint64_t phi;
+
+ jack_nframes_t file_frame;
+ jack_nframes_t playback_sample;
+ jack_nframes_t playback_distance;
+
+ uint32_t _read_data_count;
+ uint32_t _write_data_count;
+
+ bool in_set_state;
+ AlignStyle _persistent_alignment_style;
+ bool first_input_change;
+
+ PBD::NonBlockingLock state_lock;
+
+ jack_nframes_t scrub_start;
+ jack_nframes_t scrub_buffer_size;
+ jack_nframes_t scrub_offset;
+ uint32_t _refcnt;
+
+ sigc::connection ports_created_c;
+ sigc::connection plmod_connection;
+ sigc::connection plstate_connection;
+ sigc::connection plgone_connection;
+
+ /* the two central butler operations */
+
+ int do_flush (bool force = false);
+ int do_refill (Sample *mixdown_buffer, float *gain_buffer);
+
+ int read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, jack_nframes_t& start, jack_nframes_t cnt,
+ ChannelInfo& channel_info, int channel, bool reversed);
+
+ uint32_t i_am_the_modifier;
+
+ /* XXX fix this redundancy ... */
+
+ void playlist_changed (Change);
+ void playlist_modified ();
+ void playlist_deleted (Playlist*);
+ void session_controls_changed (Session::ControlType);
+
+ void finish_capture (bool rec_monitors_input);
+ void clean_up_capture (struct tm&, time_t, bool abort);
+ void transport_stopped (struct tm&, time_t, bool abort);
+
+ struct CaptureInfo {
+ uint32_t start;
+ uint32_t frames;
+ };
+
+ vector<CaptureInfo*> capture_info;
+ PBD::Lock capture_info_lock;
+
+ void init (Flag);
+
+ void init_channel (ChannelInfo &chan);
+ void destroy_channel (ChannelInfo &chan);
+
+ static jack_nframes_t disk_io_chunk_frames;
+
+ int use_new_write_source (uint32_t n=0);
+ int use_new_fade_source (uint32_t n=0);
+
+ int find_and_use_playlist (const string&);
+
+ void allocate_temporary_buffers ();
+
+ unsigned char _flags;
+
+ int create_input_port ();
+ int connect_input_port ();
+ int seek_unlocked (jack_nframes_t which_sample);
+
+ int ports_created ();
+
+ bool realtime_set_speed (float, bool global_change);
+ void non_realtime_set_speed ();
+
+ std::list<Region*> _last_capture_regions;
+ std::vector<FileSource*> capturing_sources;
+ int use_pending_capture_data (XMLNode& node);
+
+ void get_input_sources ();
+
+ void check_record_status (jack_nframes_t transport_frame, jack_nframes_t nframes, bool can_record);
+
+ void set_align_style_from_io();
+
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_diskstream_h__ */
diff --git a/libs/ardour/ardour/export.h b/libs/ardour/ardour/export.h
new file mode 100644
index 0000000000..9a6da1592b
--- /dev/null
+++ b/libs/ardour/ardour/export.h
@@ -0,0 +1,88 @@
+#ifndef __ardour_export_h__
+#define __ardour_export_h__
+
+#include <map>
+#include <vector>
+#include <string>
+
+#include <sigc++/signal.h>
+
+#include <sndfile.h>
+#include <samplerate.h>
+
+#include <ardour/ardour.h>
+#include <ardour/gdither.h>
+
+using std::map;
+using std::vector;
+using std::string;
+using std::pair;
+
+namespace ARDOUR
+{
+ class Port;
+
+ typedef pair<Port *, uint32_t> PortChannelPair;
+ typedef map<uint32_t, vector<PortChannelPair> > AudioExportPortMap;
+
+ struct AudioExportSpecification : public SF_INFO, public sigc::trackable {
+
+ AudioExportSpecification();
+ ~AudioExportSpecification ();
+
+ void init ();
+ void clear ();
+
+
+ int prepare (jack_nframes_t blocksize, jack_nframes_t frame_rate);
+
+ int process (jack_nframes_t nframes);
+
+ /* set by the user */
+
+ string path;
+ jack_nframes_t sample_rate;
+
+ int src_quality;
+ SNDFILE* out;
+ uint32_t channels;
+ AudioExportPortMap port_map;
+ jack_nframes_t start_frame;
+ jack_nframes_t end_frame;
+ GDitherType dither_type;
+ bool do_freewheel;
+
+ /* used exclusively during export */
+
+ jack_nframes_t frame_rate;
+ GDither dither;
+ float* dataF;
+ float* dataF2;
+ float* leftoverF;
+ jack_nframes_t leftover_frames;
+ jack_nframes_t max_leftover_frames;
+ void* output_data;
+ jack_nframes_t out_samples_max;
+ uint32_t sample_bytes;
+ uint32_t data_width;
+
+ jack_nframes_t total_frames;
+ SF_INFO sfinfo;
+ SRC_DATA src_data;
+ SRC_STATE* src_state;
+ jack_nframes_t pos;
+
+ sigc::connection freewheel_connection;
+
+ /* shared between UI thread and audio thread */
+
+ float progress; /* audio thread sets this */
+ bool stop; /* UI sets this */
+ bool running; /* audio thread sets to false when export is done */
+
+ int status;
+
+ };
+};
+
+#endif /* __ardour_export_h__ */
diff --git a/libs/ardour/ardour/filesource.h b/libs/ardour/ardour/filesource.h
new file mode 100644
index 0000000000..2c24d87793
--- /dev/null
+++ b/libs/ardour/ardour/filesource.h
@@ -0,0 +1,163 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __playlist_file_buffer_h__
+#define __playlist_file_buffer_h__
+
+// darwin supports 64 by default and doesn't provide wrapper functions.
+#if defined (__APPLE__)
+typedef off_t off64_t;
+#define open64 open
+#define close64 close
+#define lseek64 lseek
+#define pread64 pread
+#define pwrite64 pwrite
+#endif
+
+#include <vector>
+#include <string>
+
+#include <ardour/source.h>
+
+struct tm;
+
+using std::string;
+
+namespace ARDOUR {
+
+class FileSource : public Source {
+ public:
+ FileSource (string path, jack_nframes_t rate, bool repair_first = false);
+ FileSource (const XMLNode&, jack_nframes_t rate);
+ ~FileSource ();
+
+ jack_nframes_t length() const { return _length; }
+ jack_nframes_t read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const;
+ jack_nframes_t write (Sample *src, jack_nframes_t cnt);
+ void mark_for_remove();
+ string peak_path(string audio_path);
+ string old_peak_path(string audio_path);
+ string path() const { return _path; }
+
+ int update_header (jack_nframes_t when, struct tm&, time_t);
+
+ int move_to_trash (const string trash_dir_name);
+
+ static bool is_empty (string path);
+ void mark_streaming_write_completed ();
+
+ void mark_take (string);
+ string take_id() const { return _take_id; }
+
+ static void set_bwf_country_code (string x);
+ static void set_bwf_organization_code (string x);
+ static void set_bwf_serial_number (int);
+
+ static void set_search_path (string);
+
+ private:
+ int fd;
+ string _path;
+ bool remove_at_unref;
+ bool is_bwf;
+ off64_t data_offset;
+ string _take_id;
+
+ static char bwf_country_code[3];
+ static char bwf_organization_code[4];
+ static char bwf_serial_number[13];
+
+ struct GenericChunk {
+ char id[4];
+ int32_t size;
+ };
+
+ struct WAVEChunk : public GenericChunk {
+ char text[4]; /* wave pseudo-chunk id "WAVE" */
+ };
+
+ struct FMTChunk : public GenericChunk {
+ int16_t formatTag; /* format tag; currently pcm */
+ int16_t nChannels; /* number of channels */
+ uint32_t nSamplesPerSec; /* sample rate in hz */
+ int32_t nAvgBytesPerSec; /* average bytes per second */
+ int16_t nBlockAlign; /* number of bytes per sample */
+ int16_t nBitsPerSample; /* number of bits in a sample */
+ };
+
+ struct BroadcastChunk : public GenericChunk {
+ char description[256];
+ char originator[32];
+ char originator_reference[32];
+ char origination_date[10];
+ char origination_time[8];
+ int32_t time_reference_low;
+ int32_t time_reference_high;
+ int16_t version; /* 1.0 because we have umid and 190 bytes of "reserved" */
+ char umid[64];
+ char reserved[190];
+ /* we don't treat coding history as part of the struct */
+ };
+
+ struct ChunkInfo {
+ string name;
+ uint32_t size;
+ off64_t offset;
+
+ ChunkInfo (string s, uint32_t sz, off64_t o)
+ : name (s), size (sz), offset (o) {}
+ };
+
+ vector<ChunkInfo> chunk_info;
+
+ struct {
+ WAVEChunk wave;
+ FMTChunk format;
+ GenericChunk data;
+ BroadcastChunk bext;
+ vector<string> coding_history;
+ } header;
+
+ int init (string, bool must_exist, jack_nframes_t);
+ jack_nframes_t read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const;
+
+ int discover_chunks (bool silent);
+ ChunkInfo* lookup_chunk (string name);
+
+ int write_header ();
+ int read_header (bool silent);
+
+ int check_header (jack_nframes_t rate, bool silent);
+ int fill_header (jack_nframes_t rate);
+
+ int read_broadcast_data (ChunkInfo&);
+ void compute_header_size ();
+
+ static const int32_t wave_header_size = sizeof (WAVEChunk) + sizeof (FMTChunk) + sizeof (GenericChunk);
+ static const int32_t bwf_header_size = wave_header_size + sizeof (BroadcastChunk);
+
+ static string search_path;
+
+ int repair (string, jack_nframes_t);
+};
+
+}
+
+#endif /* __playlist_file_buffer_h__ */
diff --git a/libs/ardour/ardour/gain.h b/libs/ardour/ardour/gain.h
new file mode 100644
index 0000000000..3bd2d3be61
--- /dev/null
+++ b/libs/ardour/ardour/gain.h
@@ -0,0 +1,44 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_gain_h__
+#define __ardour_gain_h__
+
+#include "ardour.h"
+#include "curve.h"
+
+namespace ARDOUR {
+
+struct Gain : public Curve {
+
+ Gain();
+ Gain (const Gain&);
+ Gain& operator= (const Gain&);
+
+ static void fill_linear_fade_in (Gain& curve, jack_nframes_t frames);
+ static void fill_linear_volume_fade_in (Gain& curve, jack_nframes_t frames);
+ static void fill_linear_fade_out (Gain& curve, jack_nframes_t frames);
+ static void fill_linear_volume_fade_out (Gain& curve, jack_nframes_t frames);
+
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_gain_h__ */
diff --git a/libs/ardour/ardour/gdither.h b/libs/ardour/ardour/gdither.h
new file mode 100644
index 0000000000..51343b13c4
--- /dev/null
+++ b/libs/ardour/ardour/gdither.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef GDITHER_H
+#define GDITHER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gdither_types.h"
+
+/* Create and initialise a state structure, takes a dither type, a number of
+ * channels and a bit depth as input
+ *
+ * The Dither type is one of
+ *
+ * GDitherNone - straight nearest neighbour rounding. Theres no pressing
+ * reason to do this at 8 or 16 bit, but you might want to at 24, for some
+ * reason. At the lest it will save you writing int->float conversion code,
+ * which is arder than it sounds.
+ *
+ * GDitherRect - mathematically most accurate, lowest noise floor, but not
+ * that good for audio. It is the fastest though.
+ *
+ * GDitherTri - a happy medium between Rectangular and Shaped, reasonable
+ * noise floor, not too obvious, quite fast.
+ *
+ * GDitherShaped - should have the least audible impact, but has the highest
+ * noise floor, fairly CPU intensive. Not advisible if your going to apply
+ * any frequency manipulation afterwards.
+ *
+ * channels, sets the number of channels in the output data, output data will
+ * be written interleaved into the area given to gdither_run(). Set to 1
+ * if you are not working with interleaved buffers.
+ *
+ * bit depth, sets the bit width of the output sample data, it can be one of:
+ *
+ * GDither8bit - 8 bit unsiged
+ * GDither16bit - 16 bit signed
+ * GDither32bit - 24+bits in upper bits of a 32 bit word
+ * GDitherFloat - IEEE floating point (32bits)
+ * GDitherDouble - Double precision IEEE floating point (64bits)
+ *
+ * dither_depth, set the number of bits before the signal will be truncated to,
+ * eg. 16 will produce an output stream with 16bits-worth of signal. Setting to
+ * zero or greater than the width of the output format will dither to the
+ * maximum precision allowed by the output format.
+ */
+GDither gdither_new(GDitherType type, uint32_t channels,
+
+ GDitherSize bit_depth, int dither_depth);
+
+/* Frees memory used by gdither_new.
+ */
+void gdither_free(GDither s);
+
+/* Applies dithering to the supplied signal.
+ *
+ * channel is the channel number you are processing (0 - channles-1), length is
+ * the length of the input, in samples, x is the input samples (float), y is
+ * where the output samples will be written, it should have the approaprate
+ * type for the chosen bit depth
+ */
+void gdither_runf(GDither s, uint32_t channel, uint32_t length,
+ float *x, void *y);
+
+/* see gdither_runf, vut input argument is double format */
+void gdither_run(GDither s, uint32_t channel, uint32_t length,
+ double *x, void *y);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/ardour/ardour/gdither_types.h b/libs/ardour/ardour/gdither_types.h
new file mode 100644
index 0000000000..46feb55fbc
--- /dev/null
+++ b/libs/ardour/ardour/gdither_types.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef GDITHER_TYPES_H
+#define GDITHER_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ GDitherNone = 0,
+ GDitherRect,
+ GDitherTri,
+ GDitherShaped
+} GDitherType;
+
+typedef enum {
+ GDither8bit = 8,
+ GDither16bit = 16,
+ GDither32bit = 32,
+ GDitherFloat = 25,
+ GDitherDouble = 54
+} GDitherSize;
+
+typedef void *GDither;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/ardour/ardour/gdither_types_internal.h b/libs/ardour/ardour/gdither_types_internal.h
new file mode 100644
index 0000000000..55d5792833
--- /dev/null
+++ b/libs/ardour/ardour/gdither_types_internal.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef GDITHER_TYPES_H
+#define GDITHER_TYPES_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GDITHER_SH_BUF_SIZE 8
+#define GDITHER_SH_BUF_MASK 7
+
+/* this must agree with whats in gdither_types.h */
+typedef enum {
+ GDitherNone = 0,
+ GDitherRect,
+ GDitherTri,
+ GDitherShaped
+} GDitherType;
+
+typedef enum {
+ GDither8bit = 8,
+ GDither16bit = 16,
+ GDither32bit = 32,
+ GDitherFloat = 25,
+ GDitherDouble = 54
+} GDitherSize;
+
+typedef struct {
+ uint32_t phase;
+ float buffer[GDITHER_SH_BUF_SIZE];
+} GDitherShapedState;
+
+typedef struct GDither_s {
+ GDitherType type;
+ uint32_t channels;
+ uint32_t bit_depth;
+ uint32_t dither_depth;
+ float scale;
+ uint32_t post_scale;
+ float post_scale_fp;
+ float bias;
+
+ int clamp_u;
+
+ int clamp_l;
+ float *tri_state;
+ GDitherShapedState *shaped_state;
+} *GDither;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/ardour/ardour/history.h b/libs/ardour/ardour/history.h
new file mode 100644
index 0000000000..91a89d5ac2
--- /dev/null
+++ b/libs/ardour/ardour/history.h
@@ -0,0 +1,153 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_history_h__
+#define __ardour_history_h__
+
+#include <list>
+#include <sigc++/signal.h>
+
+template<class T>
+//struct History : public SigC::Object
+struct History : public sigc::trackable
+{
+ typedef list<T *> StateList;
+
+ StateList states;
+ StateList::iterator current;
+
+ History() { current = states.end(); }
+ ~History() { states.erase (states.begin(), states.end()); }
+
+ sigc::signal<void> CurrentChanged;
+
+ void clear () {
+ for (StateList::iterator i = states.begin(); i != states.end(); i++) {
+ delete *i;
+ }
+ states.clear ();
+ current = states.end();
+ CurrentChanged();
+ }
+
+ void push (T *state) {
+ /* remove any "undone" history above the current location
+ in the history, before pushing new state.
+ */
+ if (current != states.begin() && current != states.end()) {
+ states.erase (states.begin(), current);
+ }
+ current = states.insert (states.begin(), state);
+ CurrentChanged ();
+ }
+
+ T *top() {
+ if (current != states.end()) {
+ return *current;
+ } else {
+ return 0;
+ }
+ }
+
+ T *pop (bool remove) {
+ if (current == states.end()) {
+ return 0;
+ }
+
+ if (current == states.begin()) {
+ return *current;
+ }
+
+ current--;
+ T *state = *current;
+
+ if (remove) {
+ states.erase (current);
+ }
+
+ CurrentChanged ();
+ return state;
+ }
+
+ T *earlier (uint32_t n) {
+ StateList::iterator i;
+
+ if (current == states.end()) {
+ return 0;
+ }
+
+ if (n == 0) {
+ return *current;
+ }
+
+ /* the list is in LIFO order, so move toward the end to go "earlier" */
+
+ for (i = current; n && i != states.end(); i++, n--);
+
+ if (i == states.end()) {
+ return 0;
+ } else {
+ current = i;
+ CurrentChanged ();
+ return *i;
+ }
+ }
+
+ T *later (uint32_t n) {
+ StateList::iterator i;
+
+ if (current == states.end()) {
+ return 0;
+ }
+
+ if (n == 0) {
+ return *current;
+ }
+
+ /* the list is in LIFO order, so move toward the beginning to go "later" */
+
+ for (i = current; n && i != states.begin(); i--, n--);
+ if (i != current) {
+ current = i;
+ CurrentChanged();
+ }
+ return *i;
+ }
+
+ T *nth (uint32_t n) {
+ StateList::iterator i;
+
+ for (i = states.begin(); n && i != states.end(); n--, i++);
+
+ if (i != states.end()) {
+ if (i != current) {
+ current = i;
+ CurrentChanged ();
+ }
+ return *i;
+ } else {
+ return 0;
+ }
+ }
+
+};
+
+#endif /* __ardour_history_h__ */
+
diff --git a/libs/ardour/ardour/insert.h b/libs/ardour/ardour/insert.h
new file mode 100644
index 0000000000..803e16497d
--- /dev/null
+++ b/libs/ardour/ardour/insert.h
@@ -0,0 +1,183 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_insert_h__
+#define __ardour_insert_h__
+
+#include <vector>
+#include <string>
+#include <exception>
+
+#include <sigc++/signal.h>
+#include <ardour/ardour.h>
+#include <ardour/redirect.h>
+#include <ardour/plugin_state.h>
+
+class XMLNode;
+
+namespace MIDI {
+ class Port;
+}
+
+namespace ARDOUR {
+
+class Session;
+class Plugin;
+class Route;
+
+class Insert : public Redirect
+{
+ public:
+ Insert(Session& s, Placement p);
+ Insert(Session& s, string name, Placement p);
+
+ Insert(Session& s, Placement p, int imin, int imax, int omin, int omax);
+
+ virtual ~Insert() { }
+
+ virtual void run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset) = 0;
+ virtual void activate () {}
+ virtual void deactivate () {}
+
+ virtual int32_t can_support_input_configuration (int32_t in) const = 0;
+ virtual int32_t configure_io (int32_t magic, int32_t in, int32_t out) = 0;
+ virtual int32_t compute_output_streams (int32_t cnt) const = 0;
+};
+
+class PortInsert : public Insert
+{
+ public:
+ PortInsert (Session&, Placement);
+ PortInsert (Session&, const XMLNode&);
+ PortInsert (const PortInsert&);
+ ~PortInsert ();
+
+ XMLNode& state(bool full);
+ XMLNode& get_state(void);
+ int set_state(const XMLNode&);
+
+ void init ();
+ void run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+
+ jack_nframes_t latency();
+
+ uint32_t output_streams() const;
+ uint32_t input_streams() const;
+
+ int32_t can_support_input_configuration (int32_t) const;
+ int32_t configure_io (int32_t magic, int32_t in, int32_t out);
+ int32_t compute_output_streams (int32_t cnt) const;
+};
+
+struct PluginInsertState : public RedirectState
+{
+ PluginInsertState (std::string why)
+ : RedirectState (why) {}
+ ~PluginInsertState() {}
+
+ PluginState plugin_state;
+};
+
+class PluginInsert : public Insert
+{
+ public:
+ PluginInsert (Session&, Plugin&, Placement);
+ PluginInsert (Session&, const XMLNode&);
+ PluginInsert (const PluginInsert&);
+ ~PluginInsert ();
+
+ static const string port_automation_node_name;
+
+ XMLNode& state(bool);
+ XMLNode& get_state(void);
+ int set_state(const XMLNode&);
+
+ StateManager::State* state_factory (std::string why) const;
+ Change restore_state (StateManager::State&);
+
+ void run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void silence (jack_nframes_t nframes, jack_nframes_t offset);
+ void activate ();
+ void deactivate ();
+
+ void set_block_size (jack_nframes_t nframes);
+
+ uint32_t output_streams() const;
+ uint32_t input_streams() const;
+ uint32_t natural_output_streams() const;
+ uint32_t natural_input_streams() const;
+
+ int set_count (uint32_t num);
+ uint32_t get_count () const { return _plugins.size(); }
+
+ int32_t can_support_input_configuration (int32_t) const;
+ int32_t configure_io (int32_t magic, int32_t in, int32_t out);
+ int32_t compute_output_streams (int32_t cnt) const;
+
+ bool is_generator() const;
+
+ void reset_midi_control (MIDI::Port*, bool);
+ void send_all_midi_feedback ();
+
+ void set_parameter (uint32_t port, float val);
+
+ AutoState get_port_automation_state (uint32_t port);
+ void set_port_automation_state (uint32_t port, AutoState);
+ void protect_automation ();
+
+ float default_parameter_value (uint32_t which);
+
+ Plugin& plugin(uint32_t num=0) const {
+ if (num < _plugins.size()) {
+ return *_plugins[num];
+ } else {
+ return *_plugins[0]; // we always have one
+ }
+ }
+
+ string describe_parameter (uint32_t);
+
+ jack_nframes_t latency();
+
+ void transport_stopped (jack_nframes_t now);
+ void automation_snapshot (jack_nframes_t now);
+
+ protected:
+ void store_state (PluginInsertState&) const;
+
+ private:
+
+ void parameter_changed (uint32_t, float);
+
+ vector<Plugin*> _plugins;
+ void automation_run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void connect_and_run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset, bool with_auto, jack_nframes_t now = 0);
+
+ void init ();
+ void set_automatable ();
+ void auto_state_changed (uint32_t which);
+ void automation_list_creation_callback (uint32_t, AutomationList&);
+
+ Plugin* plugin_factory (Plugin&);
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_insert_h__ */
diff --git a/libs/ardour/ardour/io.h b/libs/ardour/ardour/io.h
new file mode 100644
index 0000000000..c67473dcc0
--- /dev/null
+++ b/libs/ardour/ardour/io.h
@@ -0,0 +1,408 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_io_h__
+#define __ardour_io_h__
+
+#include <string>
+#include <vector>
+#include <cmath>
+#include <sigc++/signal.h>
+#include <jack/jack.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/fastlog.h>
+#include <pbd/undo.h>
+#include <pbd/atomic.h>
+#include <midi++/controllable.h>
+
+#include <ardour/ardour.h>
+#include <ardour/stateful.h>
+#include <ardour/utils.h>
+#include <ardour/state_manager.h>
+#include <ardour/curve.h>
+
+using std::string;
+using std::vector;
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Session;
+class AudioEngine;
+class Port;
+class Connection;
+class Panner;
+
+class IO : public Stateful, public ARDOUR::StateManager
+{
+
+ public:
+ static const string state_node_name;
+
+ IO (Session&, string name,
+ int input_min = -1, int input_max = -1,
+ int output_min = -1, int output_max = -1);
+
+ virtual ~IO();
+
+ int input_minimum() const { return _input_minimum; }
+ int input_maximum() const { return _input_maximum; }
+ int output_minimum() const { return _output_minimum; }
+ int output_maximum() const { return _output_maximum; }
+
+ void set_input_minimum (int n);
+ void set_input_maximum (int n);
+ void set_output_minimum (int n);
+ void set_output_maximum (int n);
+
+ const string& name() const { return _name; }
+ virtual int set_name (string str, void *src);
+
+ virtual void silence (jack_nframes_t, jack_nframes_t offset);
+
+ void pan (vector<Sample*>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset, gain_t gain_coeff);
+ void pan_automated (vector<Sample*>& bufs, uint32_t nbufs, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset);
+ void collect_input (vector<Sample*>&, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void deliver_output (vector<Sample *>&, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void deliver_output_no_pan (vector<Sample *>&, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void just_meter_input (jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset);
+
+ virtual uint32_t n_process_buffers () { return 0; }
+
+ virtual void set_gain (gain_t g, void *src);
+ void inc_gain (gain_t delta, void *src);
+ gain_t gain () const { return _desired_gain; }
+ virtual gain_t effective_gain () const;
+
+ Panner& panner() { return *_panner; }
+
+ int ensure_io (uint32_t, uint32_t, bool clear, void *src);
+
+ int use_input_connection (Connection&, void *src);
+ int use_output_connection (Connection&, void *src);
+
+ Connection *input_connection() const { return _input_connection; }
+ Connection *output_connection() const { return _output_connection; }
+
+ int add_input_port (string source, void *src);
+ int add_output_port (string destination, void *src);
+
+ int remove_input_port (Port *, void *src);
+ int remove_output_port (Port *, void *src);
+
+ int set_input (Port *, void *src);
+
+ int connect_input (Port *our_port, string other_port, void *src);
+ int connect_output (Port *our_port, string other_port, void *src);
+
+ int disconnect_input (Port *our_port, string other_port, void *src);
+ int disconnect_output (Port *our_port, string other_port, void *src);
+
+ int disconnect_inputs (void *src);
+ int disconnect_outputs (void *src);
+
+ jack_nframes_t output_latency() const;
+ jack_nframes_t input_latency() const;
+ void set_port_latency (jack_nframes_t);
+
+ Port *output (uint32_t n) const {
+ if (n < _noutputs) {
+ return _outputs[n];
+ } else {
+ return 0;
+ }
+ }
+
+ Port *input (uint32_t n) const {
+ if (n < _ninputs) {
+ return _inputs[n];
+ } else {
+ return 0;
+ }
+ }
+
+ uint32_t n_inputs () const { return _ninputs; }
+ uint32_t n_outputs () const { return _noutputs; }
+
+ sigc::signal<void,IOChange,void*> input_changed;
+ sigc::signal<void,IOChange,void*> output_changed;
+
+ sigc::signal<void,void*> gain_changed;
+ sigc::signal<void,void*> name_changed;
+
+ virtual XMLNode& state (bool full);
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ virtual UndoAction get_memento() const;
+
+
+ static int disable_connecting (void);
+
+ static int enable_connecting (void);
+
+ static int disable_ports (void);
+
+ static int enable_ports (void);
+
+ static int disable_panners (void);
+
+ static int reset_panners (void);
+
+ static sigc::signal<int> PortsLegal;
+ static sigc::signal<int> PannersLegal;
+ static sigc::signal<int> ConnectingLegal;
+ static sigc::signal<void,uint32_t> MoreOutputs;
+ static sigc::signal<int> PortsCreated;
+
+ /* MIDI control */
+
+ void set_midi_to_gain_function (gain_t (*function)(double val)) {
+ _midi_gain_control.midi_to_gain = function;
+ }
+
+ void set_gain_to_midi_function (double (*function)(gain_t gain)) {
+ _midi_gain_control.gain_to_midi = function;
+ }
+
+ MIDI::Controllable& midi_gain_control() {
+ return _midi_gain_control;
+ }
+
+ virtual void reset_midi_control (MIDI::Port*, bool on);
+
+ virtual void send_all_midi_feedback ();
+ virtual MIDI::byte* write_midi_feedback (MIDI::byte*, int32_t& bufsize);
+
+ /* Peak metering */
+
+ float peak_input_power (uint32_t n) {
+ if (n < std::max(_ninputs, _noutputs)) {
+ float x = _stored_peak_power[n];
+ if(x > 0.0) {
+ return 20 * fast_log10(x);
+ } else {
+ return minus_infinity();
+ }
+ } else {
+ return minus_infinity();
+ }
+ }
+
+ static sigc::signal<void> GrabPeakPower;
+
+ /* automation */
+
+ void clear_automation ();
+
+ bool gain_automation_recording() const {
+ return (_gain_automation_curve.automation_state() & (Write|Touch));
+ }
+
+ bool gain_automation_playback() const {
+ return (_gain_automation_curve.automation_state() & Play) ||
+ ((_gain_automation_curve.automation_state() & Touch) &&
+ !_gain_automation_curve.touching());
+ }
+
+ virtual void set_gain_automation_state (AutoState);
+ AutoState gain_automation_state() const { return _gain_automation_curve.automation_state(); }
+ sigc::signal<void> gain_automation_state_changed;
+
+ virtual void set_gain_automation_style (AutoStyle);
+ AutoStyle gain_automation_style () const { return _gain_automation_curve.automation_style(); }
+ sigc::signal<void> gain_automation_style_changed;
+
+ static void set_automation_interval (jack_nframes_t frames) {
+ _automation_interval = frames;
+ }
+
+ static jack_nframes_t automation_interval() {
+ return _automation_interval;
+ }
+
+ virtual void transport_stopped (jack_nframes_t now);
+ virtual void automation_snapshot (jack_nframes_t now);
+
+ ARDOUR::Curve& gain_automation_curve () { return _gain_automation_curve; }
+
+ void start_gain_touch ();
+ void end_gain_touch ();
+
+ void start_pan_touch (uint32_t which);
+ void end_pan_touch (uint32_t which);
+
+ id_t id() const { return _id; }
+
+ void defer_pan_reset ();
+ void allow_pan_reset ();
+
+ /* the session calls this for master outs before
+ anyone else. controls outs too, at some point.
+ */
+
+ XMLNode *pending_state_node;
+ int ports_became_legal ();
+
+ private:
+ mutable PBD::Lock io_lock;
+
+ protected:
+ Session& _session;
+ Panner* _panner;
+ gain_t _gain;
+ gain_t _effective_gain;
+ gain_t _desired_gain;
+ PBD::NonBlockingLock declick_lock;
+ vector<Port*> _outputs;
+ vector<Port*> _inputs;
+ vector<float> _peak_power;
+ vector<float> _stored_peak_power;
+ string _name;
+ Connection* _input_connection;
+ Connection* _output_connection;
+ id_t _id;
+ bool no_panner_reset;
+ XMLNode* deferred_state;
+
+ virtual void set_deferred_state() {}
+
+ void reset_peak_meters();
+ void reset_panner ();
+
+ virtual uint32_t pans_required() const { return _ninputs; }
+
+ static void apply_declick (vector<Sample*>&, uint32_t nbufs, jack_nframes_t nframes,
+ gain_t initial, gain_t target, bool invert_polarity);
+
+ struct MIDIGainControl : public MIDI::Controllable {
+ MIDIGainControl (IO&, MIDI::Port *);
+ void set_value (float);
+
+ void send_feedback (gain_t);
+ MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, gain_t val, bool force = false);
+
+ IO& io;
+ bool setting;
+ MIDI::byte last_written;
+
+ gain_t (*midi_to_gain) (double val);
+ double (*gain_to_midi) (gain_t gain);
+ };
+
+ MIDIGainControl _midi_gain_control;
+
+ /* state management */
+
+ Change restore_state (State&);
+ StateManager::State* state_factory (std::string why) const;
+ void send_state_changed();
+
+ bool get_midi_node_info (XMLNode * node, MIDI::eventType & ev, MIDI::channel_t & chan, MIDI::byte & additional);
+ bool set_midi_node_info (XMLNode * node, MIDI::eventType ev, MIDI::channel_t chan, MIDI::byte additional);
+
+ /* automation */
+
+ jack_nframes_t last_automation_snapshot;
+ static jack_nframes_t _automation_interval;
+
+ AutoState _gain_automation_state;
+ AutoStyle _gain_automation_style;
+
+ bool apply_gain_automation;
+ Curve _gain_automation_curve;
+
+ int save_automation (const string&);
+ int load_automation (const string&);
+
+ PBD::NonBlockingLock automation_lock;
+
+ /* AudioTrack::deprecated_use_diskstream_connections() needs these */
+
+ int set_inputs (const string& str);
+ int set_outputs (const string& str);
+
+ static bool connecting_legal;
+ static bool ports_legal;
+
+ private:
+
+ uint32_t _ninputs;
+ uint32_t _noutputs;
+
+ /* are these the best variable names ever, or what? */
+
+ sigc::connection input_connection_configuration_connection;
+ sigc::connection output_connection_configuration_connection;
+ sigc::connection input_connection_connection_connection;
+ sigc::connection output_connection_connection_connection;
+
+ static bool panners_legal;
+
+ int connecting_became_legal ();
+ int panners_became_legal ();
+ sigc::connection connection_legal_c;
+ sigc::connection port_legal_c;
+ sigc::connection panner_legal_c;
+
+ int _input_minimum;
+ int _input_maximum;
+ int _output_minimum;
+ int _output_maximum;
+
+
+ static int parse_io_string (const string&, vector<string>& chns);
+
+ static int parse_gain_string (const string&, vector<string>& chns);
+
+ int set_sources (vector<string>&, void *src, bool add);
+ int set_destinations (vector<string>&, void *src, bool add);
+
+ int ensure_inputs (uint32_t, bool clear, bool lockit, void *src);
+ int ensure_outputs (uint32_t, bool clear, bool lockit, void *src);
+
+ void drop_input_connection ();
+ void drop_output_connection ();
+
+ void input_connection_configuration_changed ();
+ void input_connection_connection_changed (int);
+ void output_connection_configuration_changed ();
+ void output_connection_connection_changed (int);
+
+ int create_ports (const XMLNode&);
+ int make_connections (const XMLNode&);
+
+ void setup_peak_meters ();
+ void grab_peak_power ();
+
+ bool ensure_inputs_locked (uint32_t, bool clear, void *src);
+ bool ensure_outputs_locked (uint32_t, bool clear, void *src);
+
+ int32_t find_input_port_hole ();
+ int32_t find_output_port_hole ();
+};
+
+}; /* namespace ARDOUR */
+
+#endif /*__ardour_io_h__ */
diff --git a/libs/ardour/ardour/ladspa.h b/libs/ardour/ardour/ladspa.h
new file mode 100644
index 0000000000..e552f35bb5
--- /dev/null
+++ b/libs/ardour/ardour/ladspa.h
@@ -0,0 +1,606 @@
+/* ladspa.h
+
+ Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
+ Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
+ Stefan Westerfeld.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public License
+ as published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA. */
+
+#ifndef LADSPA_INCLUDED
+#define LADSPA_INCLUDED
+
+#define LADSPA_VERSION "1.1"
+#define LADSPA_VERSION_MAJOR 1
+#define LADSPA_VERSION_MINOR 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*****************************************************************************/
+
+/* Overview:
+
+ There is a large number of synthesis packages in use or development
+ on the Linux platform at this time. This API (`The Linux Audio
+ Developer's Simple Plugin API') attempts to give programmers the
+ ability to write simple `plugin' audio processors in C/C++ and link
+ them dynamically (`plug') into a range of these packages (`hosts').
+ It should be possible for any host and any plugin to communicate
+ completely through this interface.
+
+ This API is deliberately short and simple. To achieve compatibility
+ with a range of promising Linux sound synthesis packages it
+ attempts to find the `greatest common divisor' in their logical
+ behaviour. Having said this, certain limiting decisions are
+ implicit, notably the use of a fixed type (LADSPA_Data) for all
+ data transfer and absence of a parameterised `initialisation'
+ phase. See below for the LADSPA_Data typedef.
+
+ Plugins are expected to distinguish between control and audio
+ data. Plugins have `ports' that are inputs or outputs for audio or
+ control data and each plugin is `run' for a `block' corresponding
+ to a short time interval measured in samples. Audio data is
+ communicated using arrays of LADSPA_Data, allowing a block of audio
+ to be processed by the plugin in a single pass. Control data is
+ communicated using single LADSPA_Data values. Control data has a
+ single value at the start of a call to the `run()' or `run_adding()'
+ function, and may be considered to remain this value for its
+ duration. The plugin may assume that all its input and output ports
+ have been connected to the relevant data location (see the
+ `connect_port()' function below) before it is asked to run.
+
+ Plugins will reside in shared object files suitable for dynamic
+ linking by dlopen() and family. The file will provide a number of
+ `plugin types' that can be used to instantiate actual plugins
+ (sometimes known as `plugin instances') that can be connected
+ together to perform tasks.
+
+ This API contains very limited error-handling. */
+
+/*****************************************************************************/
+
+/* Fundamental data type passed in and out of plugin. This data type
+ is used to communicate audio samples and control values. It is
+ assumed that the plugin will work sensibly given any numeric input
+ value although it may have a preferred range (see hints below).
+
+ For audio it is generally assumed that 1.0f is the `0dB' reference
+ amplitude and is a `normal' signal level. */
+
+typedef float LADSPA_Data;
+
+/*****************************************************************************/
+
+/* Special Plugin Properties:
+
+ Optional features of the plugin type are encapsulated in the
+ LADSPA_Properties type. This is assembled by ORing individual
+ properties together. */
+
+
+typedef int LADSPA_Properties;
+
+/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
+ real-time dependency (e.g. listens to a MIDI device) and so its
+ output must not be cached or subject to significant latency. */
+#define LADSPA_PROPERTY_REALTIME 0x1
+
+/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
+ may cease to work correctly if the host elects to use the same data
+ location for both input and output (see connect_port()). This
+ should be avoided as enabling this flag makes it impossible for
+ hosts to use the plugin to process audio `in-place.' */
+#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
+
+/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
+ is capable of running not only in a conventional host but also in a
+ `hard real-time' environment. To qualify for this the plugin must
+ satisfy all of the following:
+
+ (1) The plugin must not use malloc(), free() or other heap memory
+ management within its run() or run_adding() functions. All new
+ memory used in run() must be managed via the stack. These
+ restrictions only apply to the run() function.
+
+ (2) The plugin will not attempt to make use of any library
+ functions with the exceptions of functions in the ANSI standard C
+ and C maths libraries, which the host is expected to provide.
+
+ (3) The plugin will not access files, devices, pipes, sockets, IPC
+ or any other mechanism that might result in process or thread
+ blocking.
+
+ (4) The plugin will take an amount of time to execute a run() or
+ run_adding() call approximately of form (A+B*SampleCount) where A
+ and B depend on the machine and host in use. This amount of time
+ may not depend on input signals or plugin state. The host is left
+ the responsibility to perform timings to estimate upper bounds for
+ A and B. */
+#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
+
+#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
+#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
+#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
+
+/*****************************************************************************/
+
+/* Plugin Ports:
+
+ Plugins have `ports' that are inputs or outputs for audio or
+ data. Ports can communicate arrays of LADSPA_Data (for audio
+ inputs/outputs) or single LADSPA_Data values (for control
+ input/outputs). This information is encapsulated in the
+ LADSPA_PortDescriptor type which is assembled by ORing individual
+ properties together.
+
+ Note that a port must be an input or an output port but not both
+ and that a port must be a control or audio port but not both. */
+
+
+typedef int LADSPA_PortDescriptor;
+
+/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
+#define LADSPA_PORT_INPUT 0x1
+
+/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
+#define LADSPA_PORT_OUTPUT 0x2
+
+/* Property LADSPA_PORT_CONTROL indicates that the port is a control
+ port. */
+#define LADSPA_PORT_CONTROL 0x4
+
+/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
+ port. */
+#define LADSPA_PORT_AUDIO 0x8
+
+#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
+#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
+#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
+#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
+
+/*****************************************************************************/
+
+/* Plugin Port Range Hints:
+
+ The host may wish to provide a representation of data entering or
+ leaving a plugin (e.g. to generate a GUI automatically). To make
+ this more meaningful, the plugin should provide `hints' to the host
+ describing the usual values taken by the data.
+
+ Note that these are only hints. The host may ignore them and the
+ plugin must not assume that data supplied to it is meaningful. If
+ the plugin receives invalid input data it is expected to continue
+ to run without failure and, where possible, produce a sensible
+ output (e.g. a high-pass filter given a negative cutoff frequency
+ might switch to an all-pass mode).
+
+ Hints are meaningful for all input and output ports but hints for
+ input control ports are expected to be particularly useful.
+
+ More hint information is encapsulated in the
+ LADSPA_PortRangeHintDescriptor type which is assembled by ORing
+ individual hint types together. Hints may require further
+ LowerBound and UpperBound information.
+
+ All the hint information for a particular port is aggregated in the
+ LADSPA_PortRangeHint structure. */
+
+
+typedef int LADSPA_PortRangeHintDescriptor;
+
+/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) lower
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of LowerBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_BELOW 0x1
+
+/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) upper
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of UpperBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_ABOVE 0x2
+
+/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
+ considered a Boolean toggle. Data less than or equal to zero should
+ be considered `off' or `false,' and data above zero should be
+ considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
+ conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
+ LADSPA_HINT_DEFAULT_1. */
+#define LADSPA_HINT_TOGGLED 0x4
+
+/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
+ should be interpreted as multiples of the sample rate. For
+ instance, a frequency range from 0Hz to the Nyquist frequency (half
+ the sample rate) could be requested by this hint in conjunction
+ with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
+ at all must support this hint to retain meaning. */
+#define LADSPA_HINT_SAMPLE_RATE 0x8
+
+/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
+ user will find it more intuitive to view values using a logarithmic
+ scale. This is particularly useful for frequencies and gains. */
+#define LADSPA_HINT_LOGARITHMIC 0x10
+
+/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
+ probably wish to provide a stepped control taking only integer
+ values. Any bounds set should be slightly wider than the actual
+ integer range required to avoid floating point rounding errors. For
+ instance, the integer set {0,1,2,3} might be described as [-0.1,
+ 3.1]. */
+#define LADSPA_HINT_INTEGER 0x20
+
+/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
+ value for the port that is sensible as a default. For instance,
+ this value is suitable for use as an initial value in a user
+ interface or as a value the host might assign to a control port
+ when the user has not provided one. Defaults are encoded using a
+ mask so only one default may be specified for a port. Some of the
+ hints make use of lower and upper bounds, in which case the
+ relevant bound or bounds must be available and
+ LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
+ default must be rounded if LADSPA_HINT_INTEGER is present. Default
+ values were introduced in LADSPA v1.1. */
+#define LADSPA_HINT_DEFAULT_MASK 0x3C0
+
+/* This default values indicates that no default is provided. */
+#define LADSPA_HINT_DEFAULT_NONE 0x0
+
+/* This default hint indicates that the suggested lower bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
+
+/* This default hint indicates that a low value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
+ log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
+ * 0.25). */
+#define LADSPA_HINT_DEFAULT_LOW 0x80
+
+/* This default hint indicates that a middle value between the
+ suggested lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
+ log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
+ 0.5). */
+#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
+
+/* This default hint indicates that a high value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
+ log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
+ * 0.75). */
+#define LADSPA_HINT_DEFAULT_HIGH 0x100
+
+/* This default hint indicates that the suggested upper bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
+
+/* This default hint indicates that the number 0 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_0 0x200
+
+/* This default hint indicates that the number 1 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_1 0x240
+
+/* This default hint indicates that the number 100 should be used. */
+#define LADSPA_HINT_DEFAULT_100 0x280
+
+/* This default hint indicates that the Hz frequency of `concert A'
+ should be used. This will be 440 unless the host uses an unusual
+ tuning convention, in which case it may be within a few Hz. */
+#define LADSPA_HINT_DEFAULT_440 0x2C0
+
+#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
+#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
+#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
+#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
+#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
+#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
+
+#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
+#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MINIMUM)
+#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_LOW)
+#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MIDDLE)
+#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_HIGH)
+#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MAXIMUM)
+#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_0)
+#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_1)
+#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_100)
+#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_440)
+
+typedef struct _LADSPA_PortRangeHint {
+
+ /* Hints about the port. */
+ LADSPA_PortRangeHintDescriptor HintDescriptor;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data LowerBound;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data UpperBound;
+
+} LADSPA_PortRangeHint;
+
+/*****************************************************************************/
+
+/* Plugin Handles:
+
+ This plugin handle indicates a particular instance of the plugin
+ concerned. It is valid to compare this to NULL (0 for C++) but
+ otherwise the host should not attempt to interpret it. The plugin
+ may use it to reference internal instance data. */
+
+typedef void * LADSPA_Handle;
+
+/*****************************************************************************/
+
+/* Descriptor for a Type of Plugin:
+
+ This structure is used to describe a plugin type. It provides a
+ number of functions to examine the type, instantiate it, link it to
+ buffers and workspaces and to run it. */
+
+typedef struct _LADSPA_Descriptor {
+
+ /* This numeric identifier indicates the plugin type
+ uniquely. Plugin programmers may reserve ranges of IDs from a
+ central body to avoid clashes. Hosts may assume that IDs are
+ below 0x1000000. */
+ unsigned long UniqueID;
+
+ /* This identifier can be used as a unique, case-sensitive
+ identifier for the plugin type within the plugin file. Plugin
+ types should be identified by file and label rather than by index
+ or plugin name, which may be changed in new plugin
+ versions. Labels must not contain white-space characters. */
+ const char * Label;
+
+ /* This indicates a number of properties of the plugin. */
+ LADSPA_Properties Properties;
+
+ /* This member points to the null-terminated name of the plugin
+ (e.g. "Sine Oscillator"). */
+ const char * Name;
+
+ /* This member points to the null-terminated string indicating the
+ maker of the plugin. This can be an empty string but not NULL. */
+ const char * Maker;
+
+ /* This member points to the null-terminated string indicating any
+ copyright applying to the plugin. If no Copyright applies the
+ string "None" should be used. */
+ const char * Copyright;
+
+ /* This indicates the number of ports (input AND output) present on
+ the plugin. */
+ unsigned long PortCount;
+
+ /* This member indicates an array of port descriptors. Valid indices
+ vary from 0 to PortCount-1. */
+ const LADSPA_PortDescriptor * PortDescriptors;
+
+ /* This member indicates an array of null-terminated strings
+ describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
+ 0 to PortCount-1. */
+ const char * const * PortNames;
+
+ /* This member indicates an array of range hints for each port (see
+ above). Valid indices vary from 0 to PortCount-1. */
+ const LADSPA_PortRangeHint * PortRangeHints;
+
+ /* This may be used by the plugin developer to pass any custom
+ implementation data into an instantiate call. It must not be used
+ or interpreted by the host. It is expected that most plugin
+ writers will not use this facility as LADSPA_Handle should be
+ used to hold instance data. */
+ void * ImplementationData;
+
+ /* This member is a function pointer that instantiates a plugin. A
+ handle is returned indicating the new plugin instance. The
+ instantiation function accepts a sample rate as a parameter. The
+ plugin descriptor from which this instantiate function was found
+ must also be passed. This function must return NULL if
+ instantiation fails.
+
+ Note that instance initialisation should generally occur in
+ activate() rather than here. */
+ LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
+ unsigned long SampleRate);
+
+ /* This member is a function pointer that connects a port on an
+ instantiated plugin to a memory location at which a block of data
+ for the port will be read/written. The data location is expected
+ to be an array of LADSPA_Data for audio ports or a single
+ LADSPA_Data value for control ports. Memory issues will be
+ managed by the host. The plugin must read/write the data at these
+ locations every time run() or run_adding() is called and the data
+ present at the time of this connection call should not be
+ considered meaningful.
+
+ connect_port() may be called more than once for a plugin instance
+ to allow the host to change the buffers that the plugin is
+ reading or writing. These calls may be made before or after
+ activate() or deactivate() calls.
+
+ connect_port() must be called at least once for each port before
+ run() or run_adding() is called. When working with blocks of
+ LADSPA_Data the plugin should pay careful attention to the block
+ size passed to the run function as the block allocated may only
+ just be large enough to contain the block of samples.
+
+ Plugin writers should be aware that the host may elect to use the
+ same buffer for more than one port and even use the same buffer
+ for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
+ However, overlapped buffers or use of a single buffer for both
+ audio and control data may result in unexpected behaviour. */
+ void (*connect_port)(LADSPA_Handle Instance,
+ unsigned long Port,
+ LADSPA_Data * DataLocation);
+
+ /* This member is a function pointer that initialises a plugin
+ instance and activates it for use. This is separated from
+ instantiate() to aid real-time support and so that hosts can
+ reinitialise a plugin instance by calling deactivate() and then
+ activate(). In this case the plugin instance must reset all state
+ information dependent on the history of the plugin instance
+ except for any data locations provided by connect_port() and any
+ gain set by set_run_adding_gain(). If there is nothing for
+ activate() to do then the plugin writer may provide a NULL rather
+ than an empty function.
+
+ When present, hosts must call this function once before run() (or
+ run_adding()) is called for the first time. This call should be
+ made as close to the run() call as possible and indicates to
+ real-time plugins that they are now live. Plugins should not rely
+ on a prompt call to run() after activate(). activate() may not be
+ called again unless deactivate() is called first. Note that
+ connect_port() may be called before or after a call to
+ activate(). */
+ void (*activate)(LADSPA_Handle Instance);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. Two parameters are required: the first is a
+ handle to the particular instance to be run and the second
+ indicates the block size (in samples) for which the plugin
+ instance may run.
+
+ Note that if an activate() function exists then it must be called
+ before run() or run_adding(). If deactivate() is called for a
+ plugin instance then the plugin instance may not be reused until
+ activate() has been called again.
+
+ If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
+ then there are various things that the plugin should not do
+ within the run() or run_adding() functions (see above). */
+ void (*run)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. This has identical behaviour to run() except
+ in the way data is output from the plugin. When run() is used,
+ values are written directly to the memory areas associated with
+ the output ports. However when run_adding() is called, values
+ must be added to the values already present in the memory
+ areas. Furthermore, output values written must be scaled by the
+ current gain set by set_run_adding_gain() (see below) before
+ addition.
+
+ run_adding() is optional. When it is not provided by a plugin,
+ this function pointer must be set to NULL. When it is provided,
+ the function set_run_adding_gain() must be provided also. */
+ void (*run_adding)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that sets the output gain for
+ use when run_adding() is called (see above). If this function is
+ never called the gain is assumed to default to 1. Gain
+ information should be retained when activate() or deactivate()
+ are called.
+
+ This function should be provided by the plugin if and only if the
+ run_adding() function is provided. When it is absent this
+ function pointer must be set to NULL. */
+ void (*set_run_adding_gain)(LADSPA_Handle Instance,
+ LADSPA_Data Gain);
+
+ /* This is the counterpart to activate() (see above). If there is
+ nothing for deactivate() to do then the plugin writer may provide
+ a NULL rather than an empty function.
+
+ Hosts must deactivate all activated units after they have been
+ run() (or run_adding()) for the last time. This call should be
+ made as close to the last run() call as possible and indicates to
+ real-time plugins that they are no longer live. Plugins should
+ not rely on prompt deactivation. Note that connect_port() may be
+ called before or after a call to deactivate().
+
+ Deactivation is not similar to pausing as the plugin instance
+ will be reinitialised when activate() is called to reuse it. */
+ void (*deactivate)(LADSPA_Handle Instance);
+
+ /* Once an instance of a plugin has been finished with it can be
+ deleted using the following function. The instance handle passed
+ ceases to be valid after this call.
+
+ If activate() was called for a plugin instance then a
+ corresponding call to deactivate() must be made before cleanup()
+ is called. */
+ void (*cleanup)(LADSPA_Handle Instance);
+
+} LADSPA_Descriptor;
+
+/**********************************************************************/
+
+/* Accessing a Plugin: */
+
+/* The exact mechanism by which plugins are loaded is host-dependent,
+ however all most hosts will need to know is the name of shared
+ object file containing the plugin types. To allow multiple hosts to
+ share plugin types, hosts may wish to check for environment
+ variable LADSPA_PATH. If present, this should contain a
+ colon-separated path indicating directories that should be searched
+ (in order) when loading plugin types.
+
+ A plugin programmer must include a function called
+ "ladspa_descriptor" with the following function prototype within
+ the shared object file. This function will have C-style linkage (if
+ you are using C++ this is taken care of by the `extern "C"' clause
+ at the top of the file).
+
+ A host will find the plugin shared object file by one means or
+ another, find the ladspa_descriptor() function, call it, and
+ proceed from there.
+
+ Plugin types are accessed by index (not ID) using values from 0
+ upwards. Out of range indexes must result in this function
+ returning NULL, so the plugin count can be determined by checking
+ for the least index that results in NULL being returned. */
+
+const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
+
+/* Datatype corresponding to the ladspa_descriptor() function. */
+typedef const LADSPA_Descriptor *
+(*LADSPA_Descriptor_Function)(unsigned long Index);
+
+/**********************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LADSPA_INCLUDED */
+
+/* EOF */
diff --git a/libs/ardour/ardour/ladspa_plugin.h b/libs/ardour/ardour/ladspa_plugin.h
new file mode 100644
index 0000000000..2451953ce5
--- /dev/null
+++ b/libs/ardour/ardour/ladspa_plugin.h
@@ -0,0 +1,142 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_ladspa_plugin_h__
+#define __ardour_ladspa_plugin_h__
+
+#include <list>
+#include <set>
+#include <vector>
+#include <string>
+#include <dlfcn.h>
+
+#include <midi++/controllable.h>
+#include <sigc++/signal.h>
+
+#include <jack/types.h>
+#include <ardour/ladspa.h>
+#include <ardour/stateful.h>
+#include <ardour/plugin_state.h>
+#include <ardour/plugin.h>
+#include <ardour/ladspa_plugin.h>
+
+using std::string;
+using std::vector;
+using std::list;
+
+namespace ARDOUR {
+class AudioEngine;
+class Session;
+
+class LadspaPlugin : public ARDOUR::Plugin
+{
+ public:
+ LadspaPlugin (void *module, ARDOUR::AudioEngine&, ARDOUR::Session&, uint32_t index, jack_nframes_t sample_rate);
+ LadspaPlugin (const LadspaPlugin &);
+ ~LadspaPlugin ();
+
+ /* Plugin interface */
+
+ uint32_t unique_id() const { return descriptor->UniqueID; }
+ const char * label() const { return descriptor->Label; }
+ const char * name() const { return descriptor->Name; }
+ const char * maker() const { return descriptor->Maker; }
+ uint32_t parameter_count() const { return descriptor->PortCount; }
+ float default_value (uint32_t port);
+ jack_nframes_t latency() const;
+ void set_parameter (uint32_t port, float val);
+ float get_parameter (uint32_t port) const;
+ int get_parameter_descriptor (uint32_t which, ParameterDescriptor&) const;
+ std::set<uint32_t> automatable() const;
+ uint32_t nth_parameter (uint32_t port, bool& ok) const;
+ void activate () {
+ if (descriptor->activate) {
+ descriptor->activate (handle);
+ }
+ was_activated = true;
+ }
+ void deactivate () {
+ if (descriptor->deactivate)
+ descriptor->deactivate (handle);
+ }
+ void cleanup () {
+ if (was_activated && descriptor->cleanup) {
+ descriptor->cleanup (handle);
+ }
+ }
+ void set_block_size (jack_nframes_t nframes) {}
+
+ int connect_and_run (vector<Sample*>& bufs, uint32_t maxbuf, int32_t& in, int32_t& out, jack_nframes_t nframes, jack_nframes_t offset);
+ void store_state (ARDOUR::PluginState&);
+ void restore_state (ARDOUR::PluginState&);
+ string describe_parameter (uint32_t);
+ string state_node_name() const { return "ladspa"; }
+ void print_parameter (uint32_t, char*, uint32_t len) const;
+
+ bool parameter_is_audio(uint32_t) const;
+ bool parameter_is_control(uint32_t) const;
+ bool parameter_is_input(uint32_t) const;
+ bool parameter_is_output(uint32_t) const;
+ bool parameter_is_toggled(uint32_t) const;
+
+ XMLNode& get_state();
+ int set_state(const XMLNode& node);
+ bool save_preset(string name);
+
+ bool has_editor() const { return false; }
+
+ int require_output_streams (uint32_t);
+
+ /* LADSPA extras */
+
+ LADSPA_Properties properties() const { return descriptor->Properties; }
+ uint32_t index() const { return _index; }
+ const char * copyright() const { return descriptor->Copyright; }
+ LADSPA_PortDescriptor port_descriptor(uint32_t i) const { return descriptor->PortDescriptors[i]; }
+ const LADSPA_PortRangeHint * port_range_hints() const { return descriptor->PortRangeHints; }
+ const char * const * port_names() const { return descriptor->PortNames; }
+ void set_gain (float gain) {
+ descriptor->set_run_adding_gain (handle, gain);
+ }
+ void run_adding (uint32_t nsamples) {
+ descriptor->run_adding (handle, nsamples);
+ }
+ void connect_port (uint32_t port, float *ptr) {
+ descriptor->connect_port (handle, port, ptr);
+ }
+
+ private:
+ void *module;
+ const LADSPA_Descriptor *descriptor;
+ LADSPA_Handle handle;
+ jack_nframes_t sample_rate;
+ LADSPA_Data *control_data;
+ LADSPA_Data *shadow_data;
+ LADSPA_Data *latency_control_port;
+ uint32_t _index;
+ bool was_activated;
+
+ void init (void *mod, uint32_t index, jack_nframes_t rate);
+ void run (jack_nframes_t nsamples);
+ void latency_compute_run ();
+};
+}
+
+#endif /* __ardour_ladspa_plugin_h__ */
diff --git a/libs/ardour/ardour/location.h b/libs/ardour/ardour/location.h
new file mode 100644
index 0000000000..1da67c78da
--- /dev/null
+++ b/libs/ardour/ardour/location.h
@@ -0,0 +1,193 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_location_h__
+#define __ardour_location_h__
+
+#include <string>
+#include <list>
+#include <iostream>
+#include <map>
+
+#include <sys/types.h>
+#include <pthread.h>
+#include <sigc++/signal.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+
+#include "ardour.h"
+#include "stateful.h"
+#include "state_manager.h"
+
+using std::string;
+
+namespace ARDOUR {
+
+class Location : public Stateful, public sigc::trackable
+{
+ public:
+ enum Flags {
+ IsMark = 0x1,
+ IsAutoPunch = 0x2,
+ IsAutoLoop = 0x4,
+ IsHidden = 0x8,
+ IsCDMarker = 0x10,
+ IsEnd = 0x20
+ };
+
+ Location (jack_nframes_t sample_start,
+ jack_nframes_t sample_end,
+ const string &name,
+ Flags bits = Flags(0))
+
+ : _name (name),
+ _start (sample_start),
+ _end (sample_end),
+ _flags (bits) { }
+
+ Location () {
+ _start = 0;
+ _end = 0;
+ _flags = 0;
+ }
+
+ Location (const Location& other);
+ Location* operator= (const Location& other);
+
+ jack_nframes_t start() { return _start; }
+ jack_nframes_t end() { return _end; }
+ jack_nframes_t length() { return _end - _start; }
+
+ int set_start (jack_nframes_t s);
+ int set_end (jack_nframes_t e);
+ int set (jack_nframes_t start, jack_nframes_t end);
+
+ const string& name() { return _name; }
+ void set_name (const string &str) { _name = str; name_changed(this); }
+
+ void set_auto_punch (bool yn, void *src);
+ void set_auto_loop (bool yn, void *src);
+ void set_hidden (bool yn, void *src);
+ void set_cd (bool yn, void *src);
+ void set_is_end (bool yn, void* src);
+
+ bool is_auto_punch () { return _flags & IsAutoPunch; }
+ bool is_auto_loop () { return _flags & IsAutoLoop; }
+ bool is_mark () { return _flags & IsMark; }
+ bool is_hidden () { return _flags & IsHidden; }
+ bool is_cd_marker () { return _flags & IsCDMarker; }
+ bool is_end() { return _flags & IsEnd; }
+
+ sigc::signal<void,Location*> name_changed;
+ sigc::signal<void,Location*> end_changed;
+ sigc::signal<void,Location*> start_changed;
+
+ sigc::signal<void,Location*,void*> FlagsChanged;
+
+ /* this is sent only when both start&end change at the same time */
+
+ sigc::signal<void,Location*> changed;
+
+ /* CD Track / CD-Text info */
+
+ std::map<string, string> cd_info;
+ XMLNode& cd_info_node (const string &, const string &);
+
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ private:
+ string _name;
+ jack_nframes_t _start;
+ jack_nframes_t _end;
+ uint32_t _flags;
+
+ void set_mark (bool yn);
+ bool set_flag_internal (bool yn, Flags flag);
+};
+
+class Locations : public Stateful, public StateManager
+{
+ public:
+ typedef std::list<Location *> LocationList;
+
+ Locations ();
+ ~Locations ();
+
+ void add (Location *, bool make_current = false);
+ void remove (Location *);
+ void clear ();
+ void clear_markers ();
+ void clear_ranges ();
+
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ Location* auto_loop_location () const;
+ Location* auto_punch_location () const;
+ Location* end_location() const;
+
+ int set_current (Location *, bool want_lock = true);
+ Location *current () const { return current_location; }
+
+ Location *first_location_before (jack_nframes_t);
+ Location *first_location_after (jack_nframes_t);
+
+ sigc::signal<void,Location*> current_changed;
+ sigc::signal<void> changed;
+ sigc::signal<void,Location*> added;
+ sigc::signal<void,Location*> removed;
+
+ template<class T> void apply (T& obj, void (T::*method)(LocationList&)) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ (obj.*method)(locations);
+ }
+
+ template<class T1, class T2> void apply (T1& obj, void (T1::*method)(LocationList&, T2& arg), T2& arg) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ (obj.*method)(locations, arg);
+ }
+
+ UndoAction get_memento () const;
+
+ private:
+
+ struct State : public ARDOUR::StateManager::State {
+ LocationList locations;
+ LocationList states;
+
+ State (std::string why) : ARDOUR::StateManager::State (why) {}
+ };
+
+ LocationList locations;
+ Location *current_location;
+ PBD::Lock lock;
+
+ int set_current_unlocked (Location *);
+ void location_changed (Location*);
+
+ Change restore_state (StateManager::State&);
+ StateManager::State* state_factory (std::string why) const;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_location_h__ */
diff --git a/libs/ardour/ardour/logcurve.h b/libs/ardour/ardour/logcurve.h
new file mode 100644
index 0000000000..84911b0369
--- /dev/null
+++ b/libs/ardour/ardour/logcurve.h
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2001 Steve Harris & 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_logcurve_h__
+#define __ardour_logcurve_h__
+
+#include <pbd/fastlog.h>
+#include <pbd/lockmonitor.h>
+
+namespace ARDOUR {
+
+class LogCurve {
+ public:
+ LogCurve (float steepness = 0.2, uint32_t len = 0) {
+ l = len;
+ S = steepness;
+ a = log(S);
+ b = 1.0f / log(1.0f + (1.0f / S));
+ }
+
+ bool operator== (const LogCurve& other) const {
+ return S == other.S && l == other.l;
+ }
+
+ bool operator!= (const LogCurve& other) const {
+ return S != other.S || l != other.l;
+ }
+
+ float value (float frac) const {
+ return (fast_log(frac + S) - a) * b;
+ }
+
+ float value (uint32_t pos) const {
+ return (fast_log(((float) pos/l) + S) - a) * b;
+ }
+
+ float invert_value (float frac) const {
+ return (a - fast_log(frac + S)) * b;
+ }
+
+ float invert_value (uint32_t pos) const {
+ return (a - fast_log(((float) pos/l) + S)) * b;
+ }
+
+ void fill (float *vec, uint32_t veclen, bool invert) const {
+ float dx = 1.0f/veclen;
+ float x;
+ uint32_t i;
+
+ if (!invert) {
+
+ vec[0] = 0.0;
+ vec[veclen-1] = 1.0;
+
+ for (i = 1, x = 0; i < veclen - 1; x += dx, i++) {
+ vec[i] = value (x);
+ }
+
+ } else {
+
+ vec[0] = 1.0;
+ vec[veclen-1] = 0.0;
+
+ for (i = veclen-2, x = 0.0f; i > 0; x += dx, i--) {
+ vec[i] = value (x);
+ }
+ }
+ }
+
+ float steepness() const { return S; }
+ uint32_t length() const { return l; }
+
+ void set_steepness (float steepness) {
+ S = steepness;
+ a = log(S);
+ b = 1.0f / log(1.0f + (1.0f / S));
+ }
+ void set_length (uint32_t len) { l = len; }
+
+ mutable PBD::NonBlockingLock lock;
+
+ protected:
+ float a;
+ float b;
+ float S;
+ uint32_t l;
+};
+
+class LogCurveIn : public LogCurve
+{
+ public:
+ LogCurveIn (float steepness = 0.2, uint32_t len = 0)
+ : LogCurve (steepness, len) {}
+
+ float value (float frac) const {
+ return (fast_log(frac + S) - a) * b;
+ }
+
+ float value (uint32_t pos) const {
+ return (fast_log(((float) pos/l) + S) - a) * b;
+ }
+};
+
+class LogCurveOut : public LogCurve
+{
+ public:
+ LogCurveOut (float steepness = 0.2, uint32_t len = 0)
+ : LogCurve (steepness, len) {}
+
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_logcurve_h__ */
+
+
diff --git a/libs/ardour/ardour/mix.h b/libs/ardour/ardour/mix.h
new file mode 100644
index 0000000000..b351e3ef17
--- /dev/null
+++ b/libs/ardour/ardour/mix.h
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 2005 Sampo Savolainen
+
+ 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.
+
+ $Id$
+*/
+#ifndef __ardour_mix_h__
+#define __ardour_mix_h__
+
+#include <ardour/types.h>
+#include <ardour/utils.h>
+#include <ardour/io.h>
+
+#if defined (ARCH_X86) && defined (BUILD_SSE_OPTIMIZATIONS)
+
+extern "C" {
+/* SSE functions */
+ float x86_sse_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current);
+
+ void x86_sse_apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain);
+
+ void x86_sse_mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain);
+
+ void x86_sse_mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes);
+}
+
+/* debug wrappers for SSE functions */
+
+float debug_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current);
+
+void debug_apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain);
+
+void debug_mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain);
+
+void debug_mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes);
+
+#endif
+
+#if defined (__APPLE__)
+
+float veclib_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current);
+
+void veclib_apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain);
+
+void veclib_mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain);
+
+void veclib_mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes);
+
+#endif
+
+/* non-optimized functions */
+
+float compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current);
+
+void apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain);
+
+void mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain);
+
+void mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes);
+
+#endif /* __ardour_mix_h__ */
diff --git a/libs/ardour/ardour/named_selection.h b/libs/ardour/ardour/named_selection.h
new file mode 100644
index 0000000000..91bb816181
--- /dev/null
+++ b/libs/ardour/ardour/named_selection.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_named_selection_h__
+#define __ardour_named_selection_h__
+
+#include <string>
+#include <list>
+
+#include <ardour/stateful.h>
+
+class XMLNode;
+
+namespace ARDOUR
+{
+class Session;
+class Playlist;
+
+struct NamedSelection : public Stateful
+{
+ NamedSelection (std::string, std::list<Playlist*>&);
+ NamedSelection (Session&, const XMLNode&);
+ virtual ~NamedSelection ();
+
+ std::string name;
+ std::list<Playlist*> playlists;
+
+ XMLNode& get_state (void);
+
+ int set_state (const XMLNode&);
+
+ static sigc::signal<void,NamedSelection*> NamedSelectionCreated;
+};
+
+}/* namespace ARDOUR */
+
+#endif /* __ardour_named_selection_h__ */
+
diff --git a/libs/ardour/ardour/noise.h b/libs/ardour/ardour/noise.h
new file mode 100644
index 0000000000..b06b02399b
--- /dev/null
+++ b/libs/ardour/ardour/noise.h
@@ -0,0 +1,19 @@
+#ifndef NOISE_H
+#define NOISE_H
+
+/* Can be overrriden with any code that produces whitenoise between 0.0f and
+ * 1.0f, eg (random() / (float)RAND_MAX) should be a good source of noise, but
+ * its expensive */
+#ifndef GDITHER_NOISE
+#define GDITHER_NOISE gdither_noise()
+#endif
+
+inline static float gdither_noise()
+{
+ static uint32_t rnd = 23232323;
+ rnd = (rnd * 196314165) + 907633515;
+
+ return rnd * 2.3283064365387e-10f;
+}
+
+#endif
diff --git a/libs/ardour/ardour/panner.h b/libs/ardour/ardour/panner.h
new file mode 100644
index 0000000000..806f350e03
--- /dev/null
+++ b/libs/ardour/ardour/panner.h
@@ -0,0 +1,331 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_panner_h__
+#define __ardour_panner_h__
+
+#include <cmath>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <sigc++/signal.h>
+
+#include <midi++/controllable.h>
+
+#include <ardour/types.h>
+#include <ardour/stateful.h>
+#include <ardour/curve.h>
+
+using std::istream;
+using std::ostream;
+
+namespace ARDOUR {
+
+class Session;
+class Panner;
+
+class StreamPanner : public sigc::trackable, public Stateful
+{
+ public:
+ StreamPanner (Panner& p);
+ ~StreamPanner ();
+
+ void set_muted (bool yn);
+ bool muted() const { return _muted; }
+
+ void set_position (float x, bool link_call = false);
+ void set_position (float x, float y, bool link_call = false);
+ void set_position (float x, float y, float z, bool link_call = false);
+
+ void get_position (float& xpos) const { xpos = x; }
+ void get_position (float& xpos, float& ypos) const { xpos = x; ypos = y; }
+ void get_position (float& xpos, float& ypos, float& zpos) const { xpos = x; ypos = y; zpos = z; }
+
+ void get_effective_position (float& xpos) const { xpos = effective_x; }
+ void get_effective_position (float& xpos, float& ypos) const { xpos = effective_x; ypos = effective_y; }
+ void get_effective_position (float& xpos, float& ypos, float& zpos) const { xpos = effective_x; ypos = effective_y; zpos = effective_z; }
+
+ /* the basic panner API */
+
+ virtual void distribute (Sample* src, Sample** obufs, gain_t gain_coeff, jack_nframes_t nframes) = 0;
+ virtual void distribute_automated (Sample* src, Sample** obufs,
+ jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, pan_t** buffers) = 0;
+
+ /* automation */
+
+ virtual void snapshot (jack_nframes_t now) = 0;
+ virtual void transport_stopped (jack_nframes_t frame) = 0;
+ virtual void set_automation_state (AutoState) = 0;
+ virtual void set_automation_style (AutoStyle) = 0;
+
+ /* MIDI control */
+
+ struct MIDIControl : public MIDI::Controllable {
+ MIDIControl (StreamPanner&, MIDI::Port *);
+ void set_value (float);
+ void send_feedback (gain_t);
+ MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, gain_t val, bool force = false);
+
+ pan_t (*midi_to_pan)(double val);
+ double (*pan_to_midi)(pan_t p);
+
+ StreamPanner& sp;
+ bool setting;
+ gain_t last_written;
+ };
+
+ MIDIControl& midi_control() { return _midi_control; }
+ void reset_midi_control (MIDI::Port *, bool);
+
+ /* XXX this is wrong. for multi-dimensional panners, there
+ must surely be more than 1 automation curve.
+ */
+
+ virtual Curve& automation() = 0;
+
+
+ virtual int load (istream&, string path, uint32_t&) = 0;
+
+ virtual int save (ostream&) const = 0;
+
+ sigc::signal<void> Changed; /* for position */
+ sigc::signal<void> StateChanged; /* for mute */
+
+ int set_state (const XMLNode&);
+ virtual XMLNode& state (bool full_state) = 0;
+
+ Panner & get_parent() { return parent; }
+
+ protected:
+ friend class Panner;
+ Panner& parent;
+
+ float x;
+ float y;
+ float z;
+
+ /* these are for automation. they store the last value
+ used by the most recent process() cycle.
+ */
+
+ float effective_x;
+ float effective_y;
+ float effective_z;
+
+ bool _muted;
+ MIDIControl _midi_control;
+
+ void add_state (XMLNode&);
+ bool get_midi_node_info (XMLNode * node, MIDI::eventType & ev, MIDI::channel_t & chan, MIDI::byte & additional);
+ bool set_midi_node_info (XMLNode * node, MIDI::eventType ev, MIDI::channel_t chan, MIDI::byte additional);
+
+ virtual void update () = 0;
+};
+
+class BaseStereoPanner : public StreamPanner
+{
+ public:
+ BaseStereoPanner (Panner&);
+ ~BaseStereoPanner ();
+
+ /* this class just leaves the pan law itself to be defined
+ by the update(), distribute_automated()
+ methods. derived classes also need a factory method
+ and a type name. See EqualPowerStereoPanner as an example.
+ */
+
+ void distribute (Sample* src, Sample** obufs, gain_t gain_coeff, jack_nframes_t nframes);
+
+ int load (istream&, string path, uint32_t&);
+ int save (ostream&) const;
+ void snapshot (jack_nframes_t now);
+ void transport_stopped (jack_nframes_t frame);
+ void set_automation_state (AutoState);
+ void set_automation_style (AutoStyle);
+
+ Curve& automation() { return _automation; }
+
+ protected:
+ float left;
+ float right;
+ float desired_left;
+ float desired_right;
+ float left_interp;
+ float right_interp;
+
+ Curve _automation;
+};
+
+class EqualPowerStereoPanner : public BaseStereoPanner
+{
+ public:
+ EqualPowerStereoPanner (Panner&);
+ ~EqualPowerStereoPanner ();
+
+ void distribute_automated (Sample* src, Sample** obufs,
+ jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, pan_t** buffers);
+
+ void get_current_coefficients (pan_t*) const;
+ void get_desired_coefficients (pan_t*) const;
+
+ static StreamPanner* factory (Panner&);
+ static string name;
+
+ XMLNode& state (bool full_state);
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ private:
+ void update ();
+};
+
+class Multi2dPanner : public StreamPanner
+{
+ public:
+ Multi2dPanner (Panner& parent);
+ ~Multi2dPanner ();
+
+ void snapshot (jack_nframes_t now);
+ void transport_stopped (jack_nframes_t frame);
+ void set_automation_state (AutoState);
+ void set_automation_style (AutoStyle);
+
+ /* XXX this is wrong. for multi-dimensional panners, there
+ must surely be more than 1 automation curve.
+ */
+
+ Curve& automation() { return _automation; }
+
+ void distribute (Sample* src, Sample** obufs, gain_t gain_coeff, jack_nframes_t nframes);
+ void distribute_automated (Sample* src, Sample** obufs,
+ jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, pan_t** buffers);
+
+ int load (istream&, string path, uint32_t&);
+ int save (ostream&) const;
+
+ static StreamPanner* factory (Panner&);
+ static string name;
+
+ XMLNode& state (bool full_state);
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ private:
+ Curve _automation;
+ void update ();
+};
+
+class Panner : public std::vector<StreamPanner*>, public Stateful, public sigc::trackable
+{
+ public:
+ struct Output {
+ float x;
+ float y;
+ pan_t current_pan;
+ pan_t desired_pan;
+
+ Output (float xp, float yp)
+ : x (xp), y (yp), current_pan (0.0f), desired_pan (0.f) {}
+
+ };
+
+ Panner (string name, Session&);
+ virtual ~Panner ();
+
+ void set_name (string);
+
+ bool bypassed() const { return _bypassed; }
+ void set_bypassed (bool yn);
+
+ StreamPanner* add ();
+ void remove (uint32_t which);
+ void clear ();
+ void reset (uint32_t noutputs, uint32_t npans);
+
+ void snapshot (jack_nframes_t now);
+ void transport_stopped (jack_nframes_t frame);
+
+ void clear_automation ();
+
+ void set_automation_state (AutoState);
+ AutoState automation_state() const;
+ void set_automation_style (AutoStyle);
+ AutoStyle automation_style() const;
+ bool touching() const;
+
+ int load ();
+ int save () const;
+
+ XMLNode& get_state (void);
+ XMLNode& state (bool full);
+ int set_state (const XMLNode&);
+
+ sigc::signal<void> Changed;
+
+ static bool equivalent (pan_t a, pan_t b) {
+ return fabsf (a - b) < 0.002; // about 1 degree of arc for a stereo panner
+ }
+
+ void move_output (uint32_t, float x, float y);
+ uint32_t nouts() const { return outputs.size(); }
+ Output& output (uint32_t n) { return outputs[n]; }
+
+ std::vector<Output> outputs;
+ Session& session() const { return _session; }
+
+ void reset_midi_control (MIDI::Port *, bool);
+ void send_all_midi_feedback ();
+ MIDI::byte* write_midi_feedback (MIDI::byte*, int32_t& bufsize);
+
+ enum LinkDirection {
+ SameDirection,
+ OppositeDirection
+ };
+
+ LinkDirection link_direction() const { return _link_direction; }
+ void set_link_direction (LinkDirection);
+
+ bool linked() const { return _linked; }
+ void set_linked (bool yn);
+
+ sigc::signal<void> LinkStateChanged;
+ sigc::signal<void> StateChanged; /* for bypass */
+
+ /* only StreamPanner should call these */
+
+ void set_position (float x, StreamPanner& orig);
+ void set_position (float x, float y, StreamPanner& orig);
+ void set_position (float x, float y, float z, StreamPanner& orig);
+
+ private:
+
+ string automation_path;
+ Session& _session;
+ uint32_t current_outs;
+ bool _linked;
+ bool _bypassed;
+ LinkDirection _link_direction;
+
+ static float current_automation_version_number;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /*__ardour_panner_h__ */
diff --git a/libs/ardour/ardour/peak.h b/libs/ardour/ardour/peak.h
new file mode 100644
index 0000000000..d08357024b
--- /dev/null
+++ b/libs/ardour/ardour/peak.h
@@ -0,0 +1,17 @@
+#ifndef __ardour_peak_h__
+#define __ardour_peak_h__
+
+#include <cmath>
+#include <ardour/types.h>
+#include <ardour/utils.h>
+
+static inline float
+compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current)
+{
+ for (jack_nframes_t i = 0; i < nsamples; ++i) {
+ current = f_max (current, fabsf (buf[i]));
+ }
+ return current;
+}
+
+#endif /* __ardour_peak_h__ */
diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h
new file mode 100644
index 0000000000..a5451c30d5
--- /dev/null
+++ b/libs/ardour/ardour/playlist.h
@@ -0,0 +1,278 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_playlist_h__
+#define __ardour_playlist_h__
+
+#include <string>
+#include <set>
+#include <map>
+#include <list>
+
+#include <sys/stat.h>
+
+#include <sigc++/signal.h>
+#include <pbd/atomic.h>
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/crossfade_compare.h>
+#include <ardour/location.h>
+#include <ardour/stateful.h>
+#include <ardour/source.h>
+#include <ardour/state_manager.h>
+
+namespace ARDOUR {
+
+class Session;
+class Region;
+
+class Playlist : public Stateful, public StateManager {
+ public:
+ typedef list<Region*> RegionList;
+
+ Playlist (Session&, const XMLNode&, bool hidden = false);
+ Playlist (Session&, string name, bool hidden = false);
+ Playlist (const Playlist&, string name, bool hidden = false);
+ Playlist (const Playlist&, jack_nframes_t start, jack_nframes_t cnt, string name, bool hidden = false);
+
+ virtual jack_nframes_t read (Sample *dst, Sample *mixdown, float *gain_buffer, jack_nframes_t start, jack_nframes_t cnt, uint32_t chan_n=0) = 0;
+ virtual void clear (bool with_delete = false, bool with_save = true);
+ virtual void dump () const;
+ virtual UndoAction get_memento() const = 0;
+
+ void ref();
+ void unref();
+ uint32_t refcnt() const { return _refcnt; }
+
+ const string& name() const { return _name; }
+ void set_name (const string& str);
+
+ bool frozen() const { return _frozen; }
+ void set_frozen (bool yn);
+
+ bool hidden() const { return _hidden; }
+ bool empty() const;
+ jack_nframes_t get_maximum_extent () const;
+ layer_t top_layer() const;
+
+ EditMode get_edit_mode() const { return _edit_mode; }
+ void set_edit_mode (EditMode);
+
+ /* Editing operations */
+
+ void add_region (const Region&, jack_nframes_t position, float times = 1, bool with_save = true);
+ void remove_region (Region *);
+ void replace_region (Region& old, Region& newr, jack_nframes_t pos);
+ void split_region (Region&, jack_nframes_t position);
+ void partition (jack_nframes_t start, jack_nframes_t end, bool just_top_level);
+ void duplicate (Region&, jack_nframes_t position, float times);
+ void nudge_after (jack_nframes_t start, jack_nframes_t distance, bool forwards);
+
+ Region* find_region (id_t) const;
+
+ Playlist* cut (list<AudioRange>&, bool result_is_hidden = true);
+ Playlist* copy (list<AudioRange>&, bool result_is_hidden = true);
+ int paste (Playlist&, jack_nframes_t position, float times);
+
+ uint32_t read_data_count() { return _read_data_count; }
+
+ RegionList* regions_at (jack_nframes_t frame);
+ RegionList* regions_touched (jack_nframes_t start, jack_nframes_t end);
+ Region* top_region_at (jack_nframes_t frame);
+
+ Region* find_next_region (jack_nframes_t frame, RegionPoint point, int dir);
+
+ template<class T> void foreach_region (T *t, void (T::*func)(Region *, void *), void *arg);
+ template<class T> void foreach_region (T *t, void (T::*func)(Region *));
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&);
+ XMLNode& get_template ();
+
+ sigc::signal<void,Region *> RegionAdded;
+ sigc::signal<void,Region *> RegionRemoved;
+
+ sigc::signal<void,Playlist*,bool> InUse;
+ sigc::signal<void> Modified;
+ sigc::signal<void> NameChanged;
+ sigc::signal<void> LengthChanged;
+ sigc::signal<void> LayeringChanged;
+ sigc::signal<void,Playlist *> GoingAway;
+ sigc::signal<void> StatePushed;
+
+ static sigc::signal<void,Playlist*> PlaylistCreated;
+
+ static string bump_name (string old_name, Session&);
+ static string bump_name_once (string old_name);
+
+ void freeze ();
+ void thaw ();
+
+ void raise_region (Region&);
+ void lower_region (Region&);
+ void raise_region_to_top (Region&);
+ void lower_region_to_bottom (Region&);
+
+ uint32_t read_data_count() const { return _read_data_count; }
+
+ Session& session() { return _session; }
+
+ id_t get_orig_diskstream_id () const { return _orig_diskstream_id; }
+ void set_orig_diskstream_id (id_t did) { _orig_diskstream_id = did; }
+
+ /* destructive editing */
+
+ virtual bool destroy_region (Region *) = 0;
+
+ protected:
+ friend class Session;
+ virtual ~Playlist (); /* members of the public use unref() */
+
+ protected:
+ struct RegionLock {
+ RegionLock (Playlist *pl, bool do_block_notify = true) : playlist (pl), block_notify (do_block_notify) {
+ playlist->region_lock.lock();
+ if (block_notify) {
+ playlist->delay_notifications();
+ }
+ }
+ ~RegionLock() {
+ playlist->region_lock.unlock();
+ if (block_notify) {
+ playlist->release_notifications ();
+ }
+ }
+ Playlist *playlist;
+ bool block_notify;
+ };
+
+ friend class RegionLock;
+
+ RegionList regions;
+ string _name;
+ Session& _session;
+ atomic_t block_notifications;
+ atomic_t ignore_state_changes;
+ mutable PBD::NonBlockingLock region_lock;
+ RegionList pending_removals;
+ RegionList pending_adds;
+ RegionList pending_bounds;
+ bool pending_modified;
+ bool pending_length;
+ bool save_on_thaw;
+ string last_save_reason;
+ bool in_set_state;
+ bool _hidden;
+ bool _splicing;
+ bool _nudging;
+ uint32_t _refcnt;
+ EditMode _edit_mode;
+ bool in_flush;
+ bool in_partition;
+ bool _frozen;
+ uint32_t subcnt;
+ uint32_t _read_data_count;
+ id_t _orig_diskstream_id;
+ uint64_t layer_op_counter;
+ jack_nframes_t freeze_length;
+
+ void init (bool hide);
+
+ bool holding_state () const {
+ return atomic_read (&block_notifications) != 0 ||
+ atomic_read (&ignore_state_changes) != 0;
+ }
+
+ /* prevent the compiler from ever generating these */
+
+ Playlist (const Playlist&);
+ Playlist (Playlist&);
+
+ void delay_notifications ();
+ void release_notifications ();
+ virtual void flush_notifications ();
+
+ void notify_region_removed (Region *);
+ void notify_region_added (Region *);
+ void notify_length_changed ();
+ void notify_layering_changed ();
+ void notify_modified ();
+ void notify_state_changed (Change);
+
+ void mark_session_dirty();
+
+ void region_changed_proxy (Change, Region*);
+ virtual bool region_changed (Change, Region*);
+
+ void region_bounds_changed (Change, Region *);
+ void region_deleted (Region *);
+
+ void sort_regions ();
+
+ void possibly_splice ();
+ void possibly_splice_unlocked();
+ void core_splice ();
+ void splice_locked ();
+ void splice_unlocked ();
+
+ virtual void check_dependents (Region& region, bool norefresh) {}
+ virtual void refresh_dependents (Region& region) {}
+ virtual void remove_dependents (Region& region) {}
+
+ virtual XMLNode& state (bool);
+
+ /* override state_manager::save_state so we can check in_set_state() */
+
+ void save_state (std::string why);
+ void maybe_save_state (std::string why);
+
+ void add_region_internal (Region *, jack_nframes_t position, bool delay_sort = false);
+
+ int remove_region_internal (Region *, bool delay_sort = false);
+ RegionList *find_regions_at (jack_nframes_t frame);
+ void copy_regions (RegionList&) const;
+ void partition_internal (jack_nframes_t start, jack_nframes_t end, bool cutting, RegionList& thawlist);
+
+ jack_nframes_t _get_maximum_extent() const;
+
+ Playlist* cut_copy (Playlist* (Playlist::*pmf)(jack_nframes_t, jack_nframes_t, bool),
+ list<AudioRange>& ranges, bool result_is_hidden);
+ Playlist *cut (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden);
+ Playlist *copy (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden);
+
+
+ int move_region_to_layer (layer_t, Region& r, int dir);
+ void relayer ();
+
+ static Playlist* copyPlaylist (const Playlist&, jack_nframes_t start, jack_nframes_t length,
+ string name, bool result_is_hidden);
+
+ void unset_freeze_parent (Playlist*);
+ void unset_freeze_child (Playlist*);
+
+ void timestamp_layer_op (Region&);
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_playlist_h__ */
+
+
diff --git a/libs/ardour/ardour/playlist_templates.h b/libs/ardour/ardour/playlist_templates.h
new file mode 100644
index 0000000000..d3d682b8c5
--- /dev/null
+++ b/libs/ardour/ardour/playlist_templates.h
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_playlist_templates_h__
+#define __ardour_playlist_templates_h__
+
+namespace ARDOUR {
+
+template<class T> void AudioPlaylist::foreach_crossfade (T *t, void (T::*func)(Crossfade *)) {
+ RegionLock rlock (this, false);
+ for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); i++) {
+ (t->*func) (*i);
+ }
+}
+
+template<class T> void Playlist::foreach_region (T *t, void (T::*func)(Region *, void *), void *arg) {
+ RegionLock rlock (this, false);
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); i++) {
+ (t->*func) ((*i), arg);
+ }
+ }
+
+template<class T> void Playlist::foreach_region (T *t, void (T::*func)(Region *)) {
+ RegionLock rlock (this, false);
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); i++) {
+ (t->*func) (*i);
+ }
+}
+
+}
+
+#endif /* __ardour_playlist_templates_h__ */
diff --git a/libs/ardour/ardour/plugin.h b/libs/ardour/ardour/plugin.h
new file mode 100644
index 0000000000..8839393b72
--- /dev/null
+++ b/libs/ardour/ardour/plugin.h
@@ -0,0 +1,194 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_ladspa_h__
+#define __ardour_ladspa_h__
+
+#include <midi++/controllable.h>
+#include <sigc++/signal.h>
+
+#include <jack/types.h>
+#include <ardour/types.h>
+#include <ardour/stateful.h>
+#include <ardour/plugin_state.h>
+#include <ardour/cycles.h>
+
+#include <list>
+#include <vector>
+#include <set>
+#include <map>
+
+using std::string;
+using std::vector;
+using std::list;
+using std::set;
+using std::map;
+
+namespace ARDOUR {
+
+class AudioEngine;
+class Session;
+
+class PluginInfo {
+ public:
+ enum Type {
+ LADSPA,
+ VST
+ };
+
+ PluginInfo () { };
+ PluginInfo (const PluginInfo &o)
+ : name(o.name), n_inputs(o.n_inputs), n_outputs(o.n_outputs),
+ path (o.path), index(o.index) {}
+ ~PluginInfo () { };
+ string name;
+ string category;
+ uint32_t n_inputs;
+ uint32_t n_outputs;
+ Type type;
+
+ private:
+ friend class PluginManager;
+ string path;
+ uint32_t index;
+};
+
+class Plugin : public Stateful, public sigc::trackable
+
+{
+ public:
+ Plugin (ARDOUR::AudioEngine&, ARDOUR::Session&);
+ Plugin (const Plugin&);
+ ~Plugin ();
+
+ struct ParameterDescriptor {
+
+ /* essentially a union of LADSPA and VST info */
+
+ bool integer_step;
+ bool toggled;
+ bool logarithmic;
+ bool sr_dependent;
+ string label;
+ float lower;
+ float upper;
+ float step;
+ float smallstep;
+ float largestep;
+
+ bool min_unbound;
+ bool max_unbound;
+ };
+
+ virtual uint32_t unique_id() const = 0;
+ virtual const char * label() const = 0;
+ virtual const char * name() const = 0;
+ virtual const char * maker() const = 0;
+ virtual uint32_t parameter_count () const = 0;
+ virtual float default_value (uint32_t port) = 0;
+ virtual jack_nframes_t latency() const = 0;
+ virtual void set_parameter (uint32_t which, float val) = 0;
+ virtual float get_parameter(uint32_t which) const = 0;
+
+ virtual int get_parameter_descriptor (uint32_t which, ParameterDescriptor&) const = 0;
+ virtual uint32_t nth_parameter (uint32_t which, bool& ok) const = 0;
+ virtual void activate () = 0;
+ virtual void deactivate () = 0;
+ virtual void set_block_size (jack_nframes_t nframes) = 0;
+
+ virtual int connect_and_run (vector<Sample*>& bufs, uint32_t maxbuf, int32_t& in, int32_t& out, jack_nframes_t nframes, jack_nframes_t offset) = 0;
+ virtual std::set<uint32_t> automatable() const = 0;
+ virtual void store_state (ARDOUR::PluginState&) = 0;
+ virtual void restore_state (ARDOUR::PluginState&) = 0;
+ virtual string describe_parameter (uint32_t) = 0;
+ virtual string state_node_name() const = 0;
+ virtual void print_parameter (uint32_t, char*, uint32_t len) const = 0;
+
+ virtual bool parameter_is_audio(uint32_t) const = 0;
+ virtual bool parameter_is_control(uint32_t) const = 0;
+ virtual bool parameter_is_input(uint32_t) const = 0;
+ virtual bool parameter_is_output(uint32_t) const = 0;
+
+ virtual bool save_preset(string name) = 0;
+ virtual bool load_preset (const string preset_label);
+ virtual list<string> get_presets();
+
+ virtual bool has_editor() const = 0;
+
+ sigc::signal<void,uint32_t,float> ParameterChanged;
+ sigc::signal<void,Plugin *> GoingAway;
+
+ void reset_midi_control (MIDI::Port*, bool);
+ void send_all_midi_feedback ();
+ MIDI::byte* write_midi_feedback (MIDI::byte*, int32_t& bufsize);
+ MIDI::Controllable *get_nth_midi_control (uint32_t);
+
+ PluginInfo & get_info() { return _info; }
+ void set_info (const PluginInfo &inf) { _info = inf; }
+
+ ARDOUR::AudioEngine& engine() const { return _engine; }
+ ARDOUR::Session& session() const { return _session; }
+
+ void set_cycles (uint32_t c) { _cycles = c; }
+ cycles_t cycles() const { return _cycles; }
+
+ protected:
+ ARDOUR::AudioEngine& _engine;
+ ARDOUR::Session& _session;
+ PluginInfo _info;
+ uint32_t _cycles;
+ map<string,string> presets;
+ bool save_preset(string name, string domain /* vst, ladspa etc. */);
+
+ void setup_midi_controls ();
+
+
+ struct MIDIPortControl : public MIDI::Controllable {
+ MIDIPortControl (Plugin&, uint32_t abs_port_id, MIDI::Port *,
+ float lower, float upper, bool toggled, bool logarithmic);
+
+ void set_value (float);
+ void send_feedback (float);
+ MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, float val, bool force = false);
+
+ Plugin& plugin;
+ uint32_t absolute_port;
+ float upper;
+ float lower;
+ float range;
+ bool toggled;
+ bool logarithmic;
+
+ bool setting;
+ float last_written;
+ };
+
+ vector<MIDIPortControl*> midi_controls;
+
+
+};
+
+/* this is actually defined in plugin_manager.cc */
+
+Plugin * find_plugin(ARDOUR::Session&, string name, PluginInfo::Type);
+
+} // namespace ARDOUR
+
+#endif /* __ardour_plugin_h__ */
diff --git a/libs/ardour/ardour/plugin_manager.h b/libs/ardour/ardour/plugin_manager.h
new file mode 100644
index 0000000000..1a07c67c8d
--- /dev/null
+++ b/libs/ardour/ardour/plugin_manager.h
@@ -0,0 +1,63 @@
+#ifndef __ardour_plugin_manager_h__
+#define __ardour_plugin_manager_h__
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <ardour/types.h>
+
+namespace ARDOUR {
+
+class PluginInfo;
+class Plugin;
+class Session;
+class AudioEngine;
+
+class PluginManager {
+ public:
+ PluginManager (ARDOUR::AudioEngine&);
+ ~PluginManager ();
+
+ std::list<PluginInfo*> &vst_plugin_info () { return _vst_plugin_info; }
+ std::list<PluginInfo*> &ladspa_plugin_info () { return _ladspa_plugin_info; }
+ void refresh ();
+
+ int add_ladspa_directory (std::string dirpath);
+ int add_vst_directory (std::string dirpath);
+
+ Plugin *load (ARDOUR::Session& s, PluginInfo* info);
+
+ static PluginManager* the_manager() { return _manager; }
+
+ private:
+ ARDOUR::AudioEngine& _engine;
+ std::list<PluginInfo*> _vst_plugin_info;
+ std::list<PluginInfo*> _ladspa_plugin_info;
+ std::map<uint32_t, std::string> rdf_type;
+
+ std::string ladspa_path;
+ std::string vst_path;
+
+ void ladspa_refresh ();
+ void vst_refresh ();
+
+ void add_lrdf_data (const std::string &path);
+ void add_ladspa_presets ();
+ void add_vst_presets ();
+ void add_presets (std::string domain);
+
+ int vst_discover_from_path (std::string path);
+ int vst_discover (std::string path);
+
+ int ladspa_discover_from_path (std::string path);
+ int ladspa_discover (std::string path);
+
+ std::string get_ladspa_category (uint32_t id);
+
+ static PluginManager* _manager; // singleton
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_plugin_manager_h__ */
diff --git a/libs/ardour/ardour/plugin_state.h b/libs/ardour/ardour/plugin_state.h
new file mode 100644
index 0000000000..bd499e2b90
--- /dev/null
+++ b/libs/ardour/ardour/plugin_state.h
@@ -0,0 +1,14 @@
+#ifndef __ardour_plugin_state_h__
+#define __ardour_plugin_state_h__
+
+#include <map>
+
+namespace ARDOUR {
+
+struct PluginState {
+ std::map<uint32_t,float> parameters;
+};
+
+}
+
+#endif /* __ardour_plugin_state_h__ */
diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h
new file mode 100644
index 0000000000..e5edcf72ac
--- /dev/null
+++ b/libs/ardour/ardour/port.h
@@ -0,0 +1,213 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_port_h__
+#define __ardour_port_h__
+
+#include <sigc++/signal.h>
+#include <pbd/failed_constructor.h>
+#include <ardour/ardour.h>
+#include <jack/jack.h>
+
+namespace ARDOUR {
+
+class AudioEngine;
+
+class Port : public sigc::trackable {
+ public:
+ virtual ~Port() {
+ free (port);
+ }
+
+ Sample *get_buffer (jack_nframes_t nframes) {
+ if (_flags & JackPortIsOutput) {
+ return _buffer;
+ } else {
+ return (Sample *) jack_port_get_buffer (port, nframes);
+ }
+ }
+
+ void reset_buffer () {
+ if (_flags & JackPortIsOutput) {
+ _buffer = (Sample *) jack_port_get_buffer (port, 0);
+ } else {
+ _buffer = 0; /* catch illegal attempts to use it */
+ }
+ silent = false;
+ }
+
+ string name() {
+ return _name;
+ }
+
+ string short_name() {
+ return jack_port_short_name (port);
+ }
+
+ int set_name (string str);
+
+ JackPortFlags flags() const {
+ return _flags;
+ }
+
+ bool is_mine (jack_client_t *client) {
+ return jack_port_is_mine (client, port);
+ }
+
+ const char* type() const {
+ return _type.c_str();
+ }
+
+ int connected () const {
+ return jack_port_connected (port);
+ }
+
+ bool connected_to (const string& portname) const {
+ return jack_port_connected_to (port, portname.c_str());
+ }
+
+ const char ** get_connections () const {
+ return jack_port_get_connections (port);
+ }
+
+ void reset_overs () {
+ _short_overs = 0;
+ _long_overs = 0;
+ overlen = 0;
+ }
+
+ void reset_peak_meter () {
+ _peak = 0;
+ }
+
+ void reset_meters () {
+ reset_peak_meter ();
+ reset_overs ();
+ }
+
+ void enable_metering() {
+ metering++;
+ }
+
+ void disable_metering () {
+ if (metering) { metering--; }
+ }
+
+ float peak_db() const { return _peak_db; }
+ jack_default_audio_sample_t peak() const { return _peak; }
+
+ uint32_t short_overs () const { return _short_overs; }
+ uint32_t long_overs () const { return _long_overs; }
+
+ static void set_short_over_length (jack_nframes_t);
+ static void set_long_over_length (jack_nframes_t);
+
+ bool receives_input() const {
+ return _flags & JackPortIsInput;
+ }
+
+ bool sends_output () const {
+ return _flags & JackPortIsOutput;
+ }
+
+ bool monitoring_input () const {
+ return jack_port_monitoring_input (port);
+ }
+
+ bool can_monitor () const {
+ return _flags & JackPortCanMonitor;
+ }
+
+ void ensure_monitor_input (bool yn) {
+ jack_port_request_monitor (port, yn);
+ }
+
+ void request_monitor_input (bool yn) {
+ jack_port_request_monitor (port, yn);
+ }
+
+ jack_nframes_t latency () const {
+ return jack_port_get_latency (port);
+ }
+
+ void set_latency (jack_nframes_t nframes) {
+ jack_port_set_latency (port, nframes);
+ }
+
+ sigc::signal<void,bool> MonitorInputChanged;
+ sigc::signal<void,bool> ClockSyncChanged;
+
+ bool is_silent() const { return silent; }
+
+ void silence (jack_nframes_t nframes, jack_nframes_t offset) {
+ /* assumes that the port is an output port */
+
+ if (!silent) {
+ memset (_buffer + offset, 0, sizeof (Sample) * nframes);
+ if (offset == 0) {
+ /* XXX this isn't really true, but i am not sure
+ how to set this correctly. we really just
+ want to set it true when the entire port
+ buffer has been overrwritten.
+ */
+ silent = true;
+ }
+ }
+ }
+
+ void mark_silence (bool yn) {
+ silent = yn;
+ }
+
+ private:
+ friend class AudioEngine;
+
+ Port (jack_port_t *port);
+ void reset ();
+
+ /* engine isn't supposed to below here */
+
+ Sample *_buffer;
+
+ /* cache these 3 from JACK so that we can
+ access them for reconnecting.
+ */
+
+ JackPortFlags _flags;
+ string _type;
+ string _name;
+
+ bool last_monitor : 1;
+ bool silent : 1;
+ jack_port_t *port;
+ jack_nframes_t overlen;
+ jack_default_audio_sample_t _peak;
+ float _peak_db;
+ uint32_t _short_overs;
+ uint32_t _long_overs;
+ unsigned short metering;
+
+ static jack_nframes_t long_over_length;
+ static jack_nframes_t short_over_length;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_port_h__ */
diff --git a/libs/ardour/ardour/recent_sessions.h b/libs/ardour/ardour/recent_sessions.h
new file mode 100644
index 0000000000..9b1beea4af
--- /dev/null
+++ b/libs/ardour/ardour/recent_sessions.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2004 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.
+
+*/
+
+#ifndef __ardour_recent_sessions_h__
+#define __ardour_recent_sessions__h__
+
+#include <deque>
+#include <utility>
+#include <string>
+
+using std::deque;
+using std::pair;
+using std::string;
+
+namespace ARDOUR {
+ typedef deque<pair<string,string> > RecentSessions;
+
+ int read_recent_sessions (RecentSessions& rs);
+ int store_recent_sessions (string name, string path);
+ int write_recent_sessions (RecentSessions& rs);
+}; // namespace ARDOUR
+
+#endif // __ardour_recent_sessions_h__
diff --git a/libs/ardour/ardour/redirect.h b/libs/ardour/ardour/redirect.h
new file mode 100644
index 0000000000..fc7de9d814
--- /dev/null
+++ b/libs/ardour/ardour/redirect.h
@@ -0,0 +1,153 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_redirect_h__
+#define __ardour_redirect_h__
+
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+#include <sigc++/signal.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/io.h>
+#include <ardour/automation_event.h>
+
+using std::map;
+using std::set;
+using std::string;
+using std::vector;
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Session;
+
+struct RedirectState : public StateManager::State {
+ RedirectState (string why)
+ : StateManager::State (why) {}
+ ~RedirectState () {}
+
+ bool active;
+};
+
+class Redirect : public IO
+{
+ public:
+ static const string state_node_name;
+
+ Redirect (Session&, const string& name, Placement,
+ int input_min = -1, int input_max = -1, int output_min = -1, int output_max = -1);
+ Redirect (const Redirect&);
+ virtual ~Redirect ();
+
+ static Redirect *clone (const Redirect&);
+
+ bool active () const { return _active; }
+ void set_active (bool yn, void *src);
+
+ virtual uint32_t output_streams() const { return n_outputs(); }
+ virtual uint32_t input_streams () const { return n_inputs(); }
+ virtual uint32_t natural_output_streams() const { return n_outputs(); }
+ virtual uint32_t natural_input_streams () const { return n_inputs(); }
+
+ uint32_t sort_key() const { return _sort_key; }
+ void set_sort_key (uint32_t sk,void *src) { _sort_key = sk; sort_key_changed (this, src); }
+
+ Placement placement() const { return _placement; }
+ void set_placement (Placement, void *src);
+
+ virtual void run (vector<Sample *>& ibufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset) = 0;
+ virtual void activate () = 0;
+ virtual void deactivate () = 0;
+ virtual jack_nframes_t latency() { return 0; }
+
+ virtual void set_block_size (jack_nframes_t nframes) {}
+
+ sigc::signal<void,Redirect*,void*> active_changed;
+ sigc::signal<void,Redirect*,void*> sort_key_changed;
+ sigc::signal<void,Redirect*,void*> placement_changed;
+ sigc::signal<void,Redirect*,bool> AutomationPlaybackChanged;
+ sigc::signal<void,Redirect*,uint32_t> AutomationChanged;
+ sigc::signal<void,Redirect*> GoingAway;
+
+ static sigc::signal<void,Redirect*> RedirectCreated;
+
+ XMLNode& state (bool full);
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ StateManager::State* state_factory (string why) const;
+ Change restore_state (StateManager::State&);
+
+ void *get_gui () const { return _gui; }
+ void set_gui (void *p) { _gui = p; }
+
+ virtual string describe_parameter (uint32_t which);
+ virtual float default_parameter_value (uint32_t which) {
+ return 1.0f;
+ }
+
+ int load_automation (string path);
+ int save_automation (string path);
+
+ void what_has_automation (set<uint32_t>&) const;
+ void what_has_visible_automation (set<uint32_t>&) const;
+ const set<uint32_t>& what_can_be_automated () const { return can_automate_list; }
+
+ void mark_automation_visible (uint32_t, bool);
+
+ AutomationList& automation_list (uint32_t);
+ bool find_next_event (jack_nframes_t, jack_nframes_t, ControlEvent&) const;
+
+ virtual void transport_stopped (jack_nframes_t frame) {};
+
+ protected:
+ void set_placement (const string&, void *src);
+
+ /* children may use this stuff as they see fit */
+
+ map<uint32_t,AutomationList*> parameter_automation;
+ set<uint32_t> visible_parameter_automation;
+
+ mutable PBD::NonBlockingLock _automation_lock;
+
+ void can_automate (uint32_t);
+ set<uint32_t> can_automate_list;
+
+ void store_state (RedirectState&) const;
+
+ virtual void automation_list_creation_callback (uint32_t, AutomationList&) {}
+
+ private:
+ bool _active;
+ Placement _placement;
+ uint32_t _sort_key;
+ void* _gui; /* generic, we don't know or care what this is */
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_redirect_h__ */
diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h
new file mode 100644
index 0000000000..e067f56f44
--- /dev/null
+++ b/libs/ardour/ardour/region.h
@@ -0,0 +1,259 @@
+/*
+ Copyright (C) 2000-2001 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_region_h__
+#define __ardour_region_h__
+
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/logcurve.h>
+#include <ardour/state_manager.h>
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Playlist;
+class Source;
+
+enum RegionEditState {
+ EditChangesNothing = 0,
+ EditChangesName = 1,
+ EditChangesID = 2
+};
+
+struct RegionState : public StateManager::State {
+
+ RegionState (std::string why) : StateManager::State (why) {}
+
+ jack_nframes_t _start;
+ jack_nframes_t _length;
+ jack_nframes_t _position;
+ uint32_t _flags;
+ jack_nframes_t _sync_position;
+ layer_t _layer;
+ string _name;
+ mutable RegionEditState _first_edit;
+};
+
+class Region : public Stateful, public StateManager
+{
+ public:
+ enum Flag {
+ Muted = 0x1,
+ Opaque = 0x2,
+ EnvelopeActive = 0x4,
+ DefaultFadeIn = 0x8,
+ DefaultFadeOut = 0x10,
+ Locked = 0x20,
+ Automatic = 0x40,
+ WholeFile = 0x80,
+ FadeIn = 0x100,
+ FadeOut = 0x200,
+ Copied = 0x400,
+ Import = 0x800,
+ External = 0x1000,
+ SyncMarked = 0x2000,
+ LeftOfSplit = 0x4000,
+ RightOfSplit = 0x8000,
+ Hidden = 0x10000,
+ DoNotSaveState = 0x20000,
+ //
+ range_guarantoor = USHRT_MAX
+ };
+
+ static const Flag DefaultFlags = Flag (Opaque|DefaultFadeIn|DefaultFadeOut|FadeIn|FadeOut);
+
+ static Change FadeChanged;
+ static Change SyncOffsetChanged;
+ static Change MuteChanged;
+ static Change OpacityChanged;
+ static Change LockChanged;
+ static Change LayerChanged;
+ static Change HiddenChanged;
+
+ Region (jack_nframes_t start, jack_nframes_t length,
+ const string& name, layer_t = 0, Flag flags = DefaultFlags);
+ Region (const Region&, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Flag flags = DefaultFlags);
+ Region (const Region&);
+ Region (const XMLNode&);
+ ~Region();
+
+ ARDOUR::id_t id() const { return _id; }
+
+ /* Note: changing the name of a Region does not constitute an edit */
+
+ string name() const { return _name; }
+ void set_name (string str);
+
+ jack_nframes_t position () const { return _position; }
+ jack_nframes_t start () const { return _start; }
+ jack_nframes_t length() const { return _length; }
+ layer_t layer () const { return _layer; }
+ jack_nframes_t sync_offset(int& dir) const;
+ jack_nframes_t sync_position() const;
+
+ jack_nframes_t adjust_to_sync (jack_nframes_t);
+
+ /* first_frame() is an alias; last_frame() just hides some math */
+
+ jack_nframes_t first_frame() const { return _position; }
+ jack_nframes_t last_frame() const { return _position + _length - 1; }
+
+ bool hidden() const { return _flags & Hidden; }
+ bool muted() const { return _flags & Muted; }
+ bool opaque () const { return _flags & Opaque; }
+ bool envelope_active () const { return _flags & EnvelopeActive; }
+ bool locked() const { return _flags & Locked; }
+ bool automatic() const { return _flags & Automatic; }
+ bool whole_file() const { return _flags & WholeFile ; }
+ Flag flags() const { return _flags; }
+
+ virtual bool should_save_state () const { return !(_flags & DoNotSaveState); };
+
+ void freeze ();
+ void thaw (const string& why);
+
+ bool covers (jack_nframes_t frame) const {
+ return _position <= frame && frame < _position + _length;
+ }
+
+ OverlapType coverage (jack_nframes_t start, jack_nframes_t end) const {
+ return ARDOUR::coverage (_position, _position + _length - 1, start, end);
+ }
+
+ virtual jack_nframes_t read_at (Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t position, jack_nframes_t cnt,
+ uint32_t chan_n = 0,
+ jack_nframes_t read_frames = 0,
+ jack_nframes_t skip_frames = 0) const = 0;
+
+ /* EDITING OPERATIONS */
+
+ void set_length (jack_nframes_t, void *src);
+ void set_start (jack_nframes_t, void *src);
+ void set_position (jack_nframes_t, void *src);
+ void set_position_on_top (jack_nframes_t, void *src);
+ void special_set_position (jack_nframes_t);
+ void nudge_position (long, void *src);
+
+ void move_to_natural_position (void *src);
+
+ void trim_start (jack_nframes_t new_position, void *src);
+ void trim_front (jack_nframes_t new_position, void *src);
+ void trim_end (jack_nframes_t new_position, void *src);
+ void trim_to (jack_nframes_t position, jack_nframes_t length, void *src);
+
+ void set_layer (layer_t l); /* ONLY Playlist can call this */
+ void raise ();
+ void lower ();
+ void raise_to_top ();
+ void lower_to_bottom ();
+
+ void set_sync_position (jack_nframes_t n);
+ void clear_sync_position ();
+ void set_hidden (bool yn);
+ void set_muted (bool yn);
+ void set_opaque (bool yn);
+ void set_envelope_active (bool yn);
+ void set_locked (bool yn);
+
+ virtual uint32_t read_data_count() const { return _read_data_count; }
+
+ ARDOUR::Playlist* playlist() const { return _playlist; }
+
+ virtual UndoAction get_memento() const = 0;
+
+ void set_playlist (ARDOUR::Playlist*);
+
+ virtual void lock_sources () {}
+ virtual void unlock_sources () {}
+
+ /* serialization */
+
+ virtual XMLNode& state (bool);
+ XMLNode& get_state ();
+ int set_state (const XMLNode&);
+
+ sigc::signal<void,Region*> GoingAway;
+
+ /* This is emitted only when a new id is assigned. Therefore,
+ in a pure Region copy, it will not be emitted.
+
+ It must be emitted by derived classes, not Region
+ itself, to permit dynamic_cast<> to be used to
+ infer the type of Region.
+ */
+
+ static sigc::signal<void,Region*> CheckNewRegion;
+
+ virtual Region* get_parent() = 0;
+
+ uint64_t last_layer_op() const { return _last_layer_op; }
+ void set_last_layer_op (uint64_t when);
+
+ protected:
+
+ jack_nframes_t _start;
+ jack_nframes_t _length;
+ jack_nframes_t _position;
+ Flag _flags;
+ jack_nframes_t _sync_position;
+ layer_t _layer;
+ string _name;
+ mutable RegionEditState _first_edit;
+ int _frozen;
+ PBD::Lock lock;
+ ARDOUR::id_t _id;
+ ARDOUR::Playlist* _playlist;
+ mutable uint32_t _read_data_count; // modified in read()
+ Change pending_changed;
+ uint64_t _last_layer_op; // timestamp
+
+ XMLNode& get_short_state (); /* used only by Session */
+
+ /* state management */
+
+ void send_change (Change);
+ void send_state_changed ();
+
+ /* derived classes need these during their own state management calls */
+
+ void store_state (RegionState&) const;
+ Change restore_and_return_flags (RegionState&);
+
+ void trim_to_internal (jack_nframes_t position, jack_nframes_t length, void *src);
+
+ bool copied() const { return _flags & Copied; }
+ void maybe_uncopy ();
+ void first_edit ();
+
+ virtual bool verify_start (jack_nframes_t) = 0;
+ virtual bool verify_start_and_length (jack_nframes_t, jack_nframes_t) = 0;
+ virtual bool verify_start_mutable (jack_nframes_t&_start) = 0;
+ virtual bool verify_length (jack_nframes_t) = 0;
+ virtual void recompute_at_start () = 0;
+ virtual void recompute_at_end () = 0;
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_region_h__ */
diff --git a/libs/ardour/ardour/region_factory.h b/libs/ardour/ardour/region_factory.h
new file mode 100644
index 0000000000..f72c0a52d8
--- /dev/null
+++ b/libs/ardour/ardour/region_factory.h
@@ -0,0 +1,22 @@
+#ifndef __ardour_region_factory_h__
+#define __ardour_region_factory_h__
+
+#include <ardour/types.h>
+#include <ardour/region.h>
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Session;
+
+Region* createRegion (const Region&, jack_nframes_t start,
+ jack_nframes_t length, std::string name,
+ layer_t = 0, Region::Flag flags = Region::DefaultFlags);
+// Region* createRegion (const Region&, std::string name);
+Region* createRegion (const Region&);
+Region* createRegion (Session&, XMLNode&, bool);
+
+}
+
+#endif /* __ardour_region_factory_h__ */
diff --git a/libs/ardour/ardour/reverse.h b/libs/ardour/ardour/reverse.h
new file mode 100644
index 0000000000..05ea8a1353
--- /dev/null
+++ b/libs/ardour/ardour/reverse.h
@@ -0,0 +1,38 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_reverse_h__
+#define __ardour_reverse_h__
+
+#include <ardour/audiofilter.h>
+
+namespace ARDOUR {
+
+class Reverse : public AudioFilter {
+ public:
+ Reverse (ARDOUR::Session&);
+ ~Reverse ();
+
+ int run (ARDOUR::AudioRegion&);
+};
+
+} /* namespace */
+
+#endif /* __ardour_reverse_h__ */
diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h
new file mode 100644
index 0000000000..8f4028c99f
--- /dev/null
+++ b/libs/ardour/ardour/route.h
@@ -0,0 +1,359 @@
+/*
+ Copyright (C) 2000-2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_route_h__
+#define __ardour_route_h__
+
+#include <cmath>
+#include <list>
+#include <set>
+#include <map>
+#include <string>
+
+#include <pthread.h>
+
+#include <pbd/atomic.h>
+#include <pbd/fastlog.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+#include <pbd/undo.h>
+#include <midi++/controllable.h>
+
+#include <ardour/ardour.h>
+#include <ardour/stateful.h>
+#include <ardour/io.h>
+#include <ardour/session.h>
+#include <ardour/redirect.h>
+
+namespace ARDOUR {
+
+class Insert;
+class Send;
+class RouteGroup;
+
+enum mute_type {
+ PRE_FADER = 0x1,
+ POST_FADER = 0x2,
+ CONTROL_OUTS = 0x4,
+ MAIN_OUTS = 0x8
+};
+
+class Route : public IO
+{
+ private:
+ typedef list<Redirect *> RedirectList;
+
+ public:
+
+ enum Flag {
+ Hidden = 0x1,
+ MasterOut = 0x2,
+ ControlOut = 0x4,
+ };
+
+
+ Route (Session&, std::string name, int input_min, int input_max, int output_min, int output_max, Flag flags = Flag(0));
+ Route (Session&, const XMLNode&);
+ virtual ~Route();
+
+ std::string comment() { return _comment; }
+ void set_comment (std::string str, void *src);
+
+ long order_key(std::string name) const;
+ void set_order_key (std::string name, long n);
+
+ bool hidden() const { return _flags & Hidden; }
+ bool master() const { return _flags & MasterOut; }
+ bool control() const { return _flags & ControlOut; }
+
+ /* these are the core of the API of a Route. see the protected sections as well */
+
+
+ virtual int roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t offset, int declick, bool can_record, bool rec_monitors_input);
+
+ virtual int no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t offset, bool state_changing, bool can_record, bool rec_monitors_input);
+
+ virtual int silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t offset, bool can_record, bool rec_monitors_input);
+ virtual void toggle_monitor_input ();
+ virtual bool can_record() const { return false; }
+ virtual void set_record_enable (bool yn, void *src) {}
+ virtual bool record_enabled() const { return false; }
+ virtual void transport_stopped (bool abort, bool did_locate, bool flush_redirects);
+ virtual void set_pending_declick (int);
+
+ /* end of vfunc-based API */
+
+ /* override IO::set_gain() to provide group control */
+
+ void set_gain (gain_t val, void *src);
+ void inc_gain (gain_t delta, void *src);
+
+ bool active() const { return _active; }
+ void set_active (bool yn);
+
+ void set_solo (bool yn, void *src);
+ bool soloed() const { return _soloed; }
+
+ void set_solo_safe (bool yn, void *src);
+ bool solo_safe() const { return _solo_safe; }
+
+ void set_mute (bool yn, void *src);
+ bool muted() const { return _muted; }
+
+ void set_mute_config (mute_type, bool, void *src);
+ bool get_mute_config (mute_type);
+
+ void set_phase_invert (bool yn, void *src);
+ bool phase_invert() const { return _phase_invert; }
+
+ void set_edit_group (RouteGroup *, void *);
+ RouteGroup *edit_group () { return _edit_group; }
+
+ void set_mix_group (RouteGroup *, void *);
+ RouteGroup *mix_group () { return _mix_group; }
+
+ virtual void set_meter_point (MeterPoint, void *src);
+ MeterPoint meter_point() const { return _meter_point; }
+
+ /* Redirects */
+
+ void flush_redirects ();
+
+ template<class T> void foreach_redirect (T *obj, void (T::*func)(Redirect *)) {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (obj->*func) (*i);
+ }
+ }
+
+ Redirect *nth_redirect (uint32_t n) {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ RedirectList::iterator i;
+ for (i = _redirects.begin(); i != _redirects.end() && n; ++i, --n);
+ if (i == _redirects.end()) {
+ return 0;
+ } else {
+ return *i;
+ }
+ }
+
+ uint32_t max_redirect_outs () const { return redirect_max_outs; }
+
+ int add_redirect (Redirect *, void *src, uint32_t* err_streams = 0);
+ int add_redirects (const RedirectList&, void *src, uint32_t* err_streams = 0);
+ int remove_redirect (Redirect *, void *src, uint32_t* err_streams = 0);
+ int copy_redirects (const Route&, Placement, uint32_t* err_streams = 0);
+ int sort_redirects (uint32_t* err_streams = 0);
+
+ void clear_redirects (void *src);
+ void all_redirects_flip();
+ void all_redirects_active (bool state);
+
+ virtual jack_nframes_t update_total_latency();
+ jack_nframes_t signal_latency() const { return _own_latency; }
+ virtual void set_latency_delay (jack_nframes_t);
+
+ sigc::signal<void,void*> solo_changed;
+ sigc::signal<void,void*> solo_safe_changed;
+ sigc::signal<void,void*> comment_changed;
+ sigc::signal<void,void*> mute_changed;
+ sigc::signal<void,void*> pre_fader_changed;
+ sigc::signal<void,void*> post_fader_changed;
+ sigc::signal<void,void*> control_outs_changed;
+ sigc::signal<void,void*> main_outs_changed;
+ sigc::signal<void,void*> redirects_changed;
+ sigc::signal<void,void*> record_enable_changed;
+ sigc::signal<void,void*> edit_group_changed;
+ sigc::signal<void,void*> mix_group_changed;
+ sigc::signal<void> active_changed;
+ sigc::signal<void,void*> meter_change;
+
+ sigc::signal<void> GoingAway;
+
+ /* gui's call this for their own purposes. */
+
+ sigc::signal<void,std::string,void*> gui_changed;
+
+ /* stateful */
+
+ XMLNode& get_state();
+ int set_state(const XMLNode& node);
+ XMLNode& get_template();
+
+ sigc::signal<void,void*> SelectedChanged;
+
+ /* undo */
+
+ UndoAction get_memento() const;
+ void set_state (state_id_t);
+
+ int set_control_outs (const vector<std::string>& ports);
+ IO* control_outs() { return _control_outs; }
+
+ bool feeds (Route *);
+ set<Route *> fed_by;
+
+ struct MIDIToggleControl : public MIDI::Controllable {
+ enum ToggleType {
+ MuteControl = 0,
+ SoloControl
+ };
+
+ MIDIToggleControl (Route&, ToggleType, MIDI::Port *);
+ void set_value (float);
+ void send_feedback (bool);
+ MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force = false);
+
+ Route& route;
+ ToggleType type;
+ bool setting;
+ bool last_written;
+ };
+
+ MIDI::Controllable& midi_solo_control() {
+ return _midi_solo_control;
+ }
+ MIDI::Controllable& midi_mute_control() {
+ return _midi_mute_control;
+ }
+
+ virtual void reset_midi_control (MIDI::Port*, bool);
+ virtual void send_all_midi_feedback ();
+ virtual MIDI::byte* write_midi_feedback (MIDI::byte*, int32_t& bufsize);
+
+ void automation_snapshot (jack_nframes_t now);
+
+ void protect_automation ();
+
+ protected:
+ friend class Session;
+
+ void set_solo_mute (bool yn);
+ void set_block_size (jack_nframes_t nframes);
+ bool has_external_redirects() const;
+ void curve_reallocate ();
+
+ protected:
+ unsigned char _flags;
+
+ /* tight cache-line access here is more important than sheer speed of
+ access.
+ */
+
+ bool _muted : 1;
+ bool _soloed : 1;
+ bool _solo_muted : 1;
+ bool _solo_safe : 1;
+ bool _phase_invert : 1;
+ bool _recordable : 1;
+ bool _active : 1;
+ bool _mute_affects_pre_fader : 1;
+ bool _mute_affects_post_fader : 1;
+ bool _mute_affects_control_outs : 1;
+ bool _mute_affects_main_outs : 1;
+ bool _silent : 1;
+ bool _declickable : 1;
+ int _pending_declick;
+
+ MeterPoint _meter_point;
+
+ gain_t solo_gain;
+ gain_t mute_gain;
+ gain_t desired_solo_gain;
+ gain_t desired_mute_gain;
+
+ jack_nframes_t check_initial_delay (jack_nframes_t, jack_nframes_t&, jack_nframes_t&);
+
+ jack_nframes_t _initial_delay;
+ jack_nframes_t _roll_delay;
+ jack_nframes_t _own_latency;
+ RedirectList _redirects;
+ PBD::NonBlockingLock redirect_lock;
+ IO *_control_outs;
+ PBD::NonBlockingLock control_outs_lock;
+ RouteGroup *_edit_group;
+ RouteGroup *_mix_group;
+ std::string _comment;
+ bool _have_internal_generator;
+
+ MIDIToggleControl _midi_solo_control;
+ MIDIToggleControl _midi_mute_control;
+
+ void passthru (jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset, int declick, bool meter_inputs);
+
+ void process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
+ jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset, bool with_redirects, int declick,
+ bool meter);
+
+ protected:
+ /* for derived classes */
+
+ virtual XMLNode& state(bool);
+
+ void silence (jack_nframes_t nframes, jack_nframes_t offset);
+ sigc::connection input_signal_connection;
+
+ state_id_t _current_state_id;
+ uint32_t redirect_max_outs;
+
+ uint32_t pans_required() const;
+ uint32_t n_process_buffers ();
+
+ private:
+ void init ();
+
+ static uint32_t order_key_cnt;
+ typedef std::map<std::string,long> OrderKeys;
+ OrderKeys order_keys;
+
+ void input_change_handler (IOChange, void *src);
+ void output_change_handler (IOChange, void *src);
+
+ bool legal_redirect (Redirect&);
+ int reset_plugin_counts (uint32_t*); /* locked */
+ int _reset_plugin_counts (uint32_t*); /* unlocked */
+
+ /* plugin count handling */
+
+ struct InsertCount {
+ ARDOUR::Insert& insert;
+ int32_t cnt;
+ int32_t in;
+ int32_t out;
+
+ InsertCount (ARDOUR::Insert& ins) : insert (ins), cnt (-1) {}
+ };
+
+ int32_t apply_some_plugin_counts (std::list<InsertCount>& iclist);
+ int32_t check_some_plugin_counts (std::list<InsertCount>& iclist, int32_t required_inputs, uint32_t* err_streams);
+
+ void set_deferred_state ();
+ void add_redirect_from_xml (const XMLNode&);
+ void redirect_active_proxy (Redirect*, void*);
+};
+
+}; /* namespace ARDOUR*/
+
+#endif /* __ardour_route_h__ */
diff --git a/libs/ardour/ardour/route_group.h b/libs/ardour/ardour/route_group.h
new file mode 100644
index 0000000000..910500ccf8
--- /dev/null
+++ b/libs/ardour/ardour/route_group.h
@@ -0,0 +1,111 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_route_group_h__
+#define __ardour_route_group_h__
+
+#include <list>
+#include <string>
+#include <stdint.h>
+#include <sigc++/signal.h>
+#include <ardour/stateful.h>
+#include <ardour/types.h>
+
+using std::string;
+using std::list;
+
+namespace ARDOUR {
+
+class Route;
+class AudioTrack;
+
+class RouteGroup : public Stateful, public sigc::trackable {
+ public:
+ enum Flag {
+ Relative = 0x1,
+ Active = 0x2,
+ Hidden = 0x4,
+ };
+
+ RouteGroup(const string &n, Flag f = Flag(0)) : _name (n), _flags (f) {}
+
+ const string& name() { return _name; }
+
+ bool is_active () const { return _flags & Active; }
+ bool is_relative () const { return _flags & Relative; }
+ bool is_hidden () const { return _flags & Hidden; }
+ bool empty() const {return routes.empty();}
+
+ gain_t get_max_factor(gain_t factor);
+ gain_t get_min_factor(gain_t factor);
+
+ int size() { return routes.size();}
+ ARDOUR::Route * first () const { return *routes.begin();}
+
+ void set_active (bool yn, void *src);
+ void set_relative (bool yn, void *src);
+ void set_hidden (bool yn, void *src);
+
+
+ int add (Route *);
+
+ int remove (Route *);
+
+ template<class T> void apply (void (Route::*func)(T, void *), T val, void *src) {
+ for (list<Route *>::iterator i = routes.begin(); i != routes.end(); i++) {
+ ((*i)->*func)(val, this);
+ }
+ }
+
+ template<class T> void foreach_route (T *obj, void (T::*func)(Route&)) {
+ for (list<Route *>::iterator i = routes.begin(); i != routes.end(); i++) {
+ (obj->*func)(**i);
+ }
+ }
+
+ /* to use these, #include <ardour/route_group_specialized.h> */
+
+ template<class T> void apply (void (AudioTrack::*func)(T, void *), T val, void *src);
+
+ void clear () {
+ routes.clear ();
+ changed();
+ }
+
+ const list<Route*>& route_list() { return routes; }
+
+ sigc::signal<void> changed;
+ sigc::signal<void,void*> FlagsChanged;
+
+ XMLNode& get_state (void);
+
+ int set_state (const XMLNode&);
+
+ private:
+ list<Route *> routes;
+ string _name;
+ uint32_t _flags;
+
+ void remove_when_going_away (Route*);
+};
+
+} /* namespace */
+
+#endif /* __ardour_route_group_h__ */
diff --git a/libs/ardour/ardour/route_group_specialized.h b/libs/ardour/ardour/route_group_specialized.h
new file mode 100644
index 0000000000..32c627eb7a
--- /dev/null
+++ b/libs/ardour/ardour/route_group_specialized.h
@@ -0,0 +1,22 @@
+#ifndef __ardour_route_group_specialized_h__
+#define __ardour_route_group_specialized_h__
+
+#include <ardour/route_group.h>
+#include <ardour/audio_track.h>
+
+namespace ARDOUR {
+
+template<class T> void
+RouteGroup::apply (void (AudioTrack::*func)(T, void *), T val, void *src)
+{
+ for (list<Route *>::iterator i = routes.begin(); i != routes.end(); i++) {
+ AudioTrack *at;
+ if ((at = dynamic_cast<AudioTrack*>(*i)) != 0) {
+ (at->*func)(val, this);
+ }
+ }
+}
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_route_group_specialized_h__ */
diff --git a/libs/ardour/ardour/send.h b/libs/ardour/ardour/send.h
new file mode 100644
index 0000000000..a94318f2a5
--- /dev/null
+++ b/libs/ardour/ardour/send.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_send_h__
+#define __ardour_send_h__
+
+#include <sigc++/signal.h>
+#include <string>
+
+#include <ardour/ardour.h>
+#include <ardour/audioengine.h>
+
+#include "io.h"
+#include "stateful.h"
+#include "redirect.h"
+
+namespace ARDOUR {
+
+class Send : public Redirect {
+ public:
+ Send (Session&, Placement);
+ Send (Session&, const XMLNode&);
+ Send (const Send&);
+ ~Send ();
+
+ void run (vector<Sample *> &bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset);
+ void activate() {}
+ void deactivate () {}
+
+ void set_metering (bool yn);
+
+ XMLNode& state(bool full);
+ XMLNode& get_state(void);
+ int set_state(const XMLNode& node);
+
+ uint32_t pans_required() const { return expected_inputs; }
+ void expect_inputs (uint32_t);
+
+ private:
+ bool _metering;
+ uint32_t expected_inputs;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_send_h__ */
diff --git a/libs/ardour/ardour/seqsource.h b/libs/ardour/ardour/seqsource.h
new file mode 100644
index 0000000000..7e38d27915
--- /dev/null
+++ b/libs/ardour/ardour/seqsource.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __playlist_seqsource_h__
+#define __playlist_seqsource_h__
+
+#include <string>
+
+#include "edl.h"
+
+namespace EDL {
+
+class PlaylistSource : public Source {
+ public:
+ PlaylistSource (Playlist&);
+ ~PlaylistSource ();
+
+ const gchar * const id() { return playlist.name().c_str(); }
+ uint32_t length() { return playlist.length(); }
+ uint32_t read (Source::Data *dst, uint32_t start, uint32_t cnt) {
+ return playlist.read (dst, start, cnt, false);
+ }
+ uint32_t write (Source::Data *src, uint32_t where, uint32_t cnt) {
+ return playlist.write (src, where, cnt);
+ }
+
+// int read_peaks (peak_data_t *, uint32_t npeaks, uint32_t start, uint32_t cnt);
+// int build_peak (uint32_t first_frame, uint32_t cnt);
+
+ protected:
+
+ private:
+ Playlist& playlist;
+};
+
+}; /* namespace EDL */
+
+#endif /* __playlist_seqsource_h__ */
diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h
new file mode 100644
index 0000000000..b58b0a926b
--- /dev/null
+++ b/libs/ardour/ardour/session.h
@@ -0,0 +1,1754 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_h__
+#define __ardour_session_h__
+
+#include <string>
+#if __GNUC__ >= 3
+#include <ext/slist>
+using __gnu_cxx::slist;
+#else
+#include <slist.h>
+#endif
+#include <map>
+#include <vector>
+#include <set>
+#include <stack>
+#include <stdint.h>
+
+#include <sndfile.h>
+
+#include <pbd/error.h>
+#include <pbd/atomic.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+#include <pbd/pool.h>
+
+#include <midi++/types.h>
+#include <midi++/mmc.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/location.h>
+#include <ardour/stateful.h>
+#include <ardour/gain.h>
+#include <ardour/io.h>
+
+class XMLTree;
+class XMLNode;
+class AEffect;
+
+namespace MIDI {
+ class Port;
+}
+
+namespace ARDOUR {
+
+class Port;
+class AudioEngine;
+class Slave;
+class DiskStream;
+class Route;
+class AuxInput;
+class Source;
+class FileSource;
+class Auditioner;
+class Insert;
+class Send;
+class Redirect;
+class PortInsert;
+class PluginInsert;
+class Connection;
+class TempoMap;
+class AudioTrack;
+class NamedSelection;
+class AudioRegion;
+class Region;
+class Playlist;
+class VSTPlugin;
+
+struct AudioExportSpecification;
+struct RouteGroup;
+
+using std::vector;
+using std::string;
+using std::list;
+using std::map;
+using std::set;
+
+class Session : public sigc::trackable, public Stateful
+
+{
+ private:
+ typedef std::pair<Route*,bool> RouteBooleanState;
+ typedef vector<RouteBooleanState> GlobalRouteBooleanState;
+ typedef std::pair<Route*,MeterPoint> RouteMeterState;
+ typedef vector<RouteMeterState> GlobalRouteMeterState;
+
+ public:
+ enum RecordState {
+ Disabled = 0,
+ Enabled = 1,
+ Recording = 2
+ };
+
+ enum SlaveSource {
+ None = 0,
+ MTC,
+ JACK,
+ };
+
+ enum AutoConnectOption {
+ AutoConnectPhysical = 0x1,
+ AutoConnectMaster = 0x2
+ };
+
+ struct Event {
+ enum Type {
+ SetTransportSpeed,
+ SetDiskstreamSpeed,
+ Locate,
+ LocateRoll,
+ SetLoop,
+ PunchIn,
+ PunchOut,
+ RangeStop,
+ RangeLocate,
+ Overwrite,
+ SetSlaveSource,
+ Audition,
+ InputConfigurationChange,
+ SetAudioRange,
+ SetPlayRange,
+
+ /* only one of each of these events
+ can be queued at any one time
+ */
+
+ StopOnce,
+ AutoLoop,
+ };
+
+ enum Action {
+ Add,
+ Remove,
+ Replace,
+ Clear
+ };
+
+ Type type;
+ Action action;
+ jack_nframes_t action_frame;
+ jack_nframes_t target_frame;
+ float speed;
+
+ union {
+ void* ptr;
+ bool yes_or_no;
+ Session::SlaveSource slave;
+ };
+
+ list<AudioRange> audio_range;
+ list<MusicRange> music_range;
+
+ Event(Type t, Action a, jack_nframes_t when, jack_nframes_t where, float spd, bool yn = false)
+ : type (t),
+ action (a),
+ action_frame (when),
+ target_frame (where),
+ speed (spd),
+ yes_or_no (yn) {}
+
+ void set_ptr (void* p) {
+ ptr = p;
+ }
+
+ bool before (const Event& other) const {
+ return action_frame < other.action_frame;
+ }
+
+ bool after (const Event& other) const {
+ return action_frame > other.action_frame;
+ }
+
+ static bool compare (const Event *e1, const Event *e2) {
+ return e1->before (*e2);
+ }
+
+ void *operator new (size_t ignored) {
+ return pool.alloc ();
+ }
+
+ void operator delete(void *ptr, size_t size) {
+ pool.release (ptr);
+ }
+
+ static const jack_nframes_t Immediate = 0;
+
+ private:
+ static MultiAllocSingleReleasePool pool;
+ };
+
+ /* creating from an XML file */
+
+ Session (AudioEngine&,
+ string fullpath,
+ string snapshot_name,
+ string* mix_template = 0);
+
+ /* creating a new Session */
+
+ Session (AudioEngine&,
+ string fullpath,
+ string snapshot_name,
+ AutoConnectOption input_auto_connect,
+ AutoConnectOption output_auto_connect,
+ uint32_t control_out_channels,
+ uint32_t master_out_channels,
+ uint32_t n_physical_in,
+ uint32_t n_physical_out,
+ jack_nframes_t initial_length);
+
+ virtual ~Session ();
+
+
+ static int find_session (string str, string& path, string& snapshot, bool& isnew);
+
+ string path() const { return _path; }
+ string name() const { return _name; }
+ string snap_name() const { return _current_snapshot_name; }
+
+ void set_snap_name ();
+
+ void set_dirty ();
+ void set_clean ();
+ bool dirty() const { return _state_of_the_state & Dirty; }
+ sigc::signal<void> DirtyChanged;
+
+ string sound_dir () const;
+ string peak_dir () const;
+ string dead_sound_dir () const;
+ string automation_dir () const;
+
+ static string template_path ();
+ static string template_dir ();
+ static void get_template_list (list<string>&);
+
+ static string peak_path_from_audio_path (string);
+ static string old_peak_path_from_audio_path (string);
+
+ void process (jack_nframes_t nframes);
+
+ vector<Sample*>& get_passthru_buffers() { return _passthru_buffers; }
+ vector<Sample*>& get_silent_buffers (uint32_t howmany);
+
+ DiskStream *diskstream_by_id (id_t id);
+ DiskStream *diskstream_by_name (string name);
+
+ bool have_captured() const { return _have_captured; }
+
+ void refill_all_diskstream_buffers ();
+ uint32_t diskstream_buffer_size() const { return dstream_buffer_size; }
+ uint32_t get_next_diskstream_id() const { return n_diskstreams(); }
+ uint32_t n_diskstreams() const;
+
+ typedef list<DiskStream *> DiskStreamList;
+
+ Session::DiskStreamList disk_streams() const {
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ return diskstreams; /* XXX yes, force a copy */
+ }
+
+ void foreach_diskstream (void (DiskStream::*func)(void));
+ template<class T> void foreach_diskstream (T *obj, void (T::*func)(DiskStream&));
+
+ typedef slist<Route *> RouteList;
+
+ RouteList get_routes() const {
+ LockMonitor rlock (route_lock, __LINE__, __FILE__);
+ return routes; /* XXX yes, force a copy */
+ }
+
+ uint32_t nroutes() const { return routes.size(); }
+ uint32_t ntracks () const;
+ uint32_t nbusses () const;
+
+ struct RoutePublicOrderSorter {
+ bool operator() (Route *, Route *b);
+ };
+
+ template<class T> void foreach_route (T *obj, void (T::*func)(Route&));
+ template<class T> void foreach_route (T *obj, void (T::*func)(Route*));
+ template<class T, class A> void foreach_route (T *obj, void (T::*func)(Route&, A), A arg);
+
+ Route *route_by_name (string);
+
+ bool route_name_unique (string) const;
+
+ bool get_record_enabled() const {
+ return (record_status () >= Enabled);
+ }
+
+ RecordState record_status() const {
+ return (RecordState) atomic_read (&_record_status);
+ }
+
+ bool actively_recording () {
+ return record_status() == Recording;
+ }
+
+ bool record_enabling_legal () const;
+ void maybe_enable_record ();
+ void disable_record ();
+ void step_back_from_record ();
+
+ sigc::signal<void> going_away;
+
+ /* Proxy signal for region hidden changes */
+
+ sigc::signal<void,Region*> RegionHiddenChange;
+
+ /* Emitted when all i/o connections are complete */
+
+ sigc::signal<void> IOConnectionsComplete;
+
+ /* Record status signals */
+
+ sigc::signal<void> RecordEnabled;
+ sigc::signal<void> RecordDisabled;
+
+ /* Transport mechanism signals */
+
+ sigc::signal<void> TransportStateChange; /* generic */
+ sigc::signal<void,jack_nframes_t> PositionChanged; /* sent after any non-sequential motion */
+ sigc::signal<void> DurationChanged;
+ sigc::signal<void> HaltOnXrun;
+
+ sigc::signal<void,Route*> RouteAdded;
+ sigc::signal<void,DiskStream*> DiskStreamAdded;
+
+ void request_roll ();
+ void request_bounded_roll (jack_nframes_t start, jack_nframes_t end);
+ void request_stop (bool abort = false);
+ void request_locate (jack_nframes_t frame, bool with_roll = false);
+ void request_auto_loop (bool yn);
+ jack_nframes_t last_transport_start() const { return _last_roll_location; }
+ void goto_end () { request_locate (end_location->start(), false);}
+ void goto_start () { request_locate (0, false); }
+ void use_rf_shuttle_speed ();
+ void request_transport_speed (float speed);
+ void request_overwrite_buffer (DiskStream*);
+ void request_diskstream_speed (DiskStream&, float speed);
+ void request_input_change_handling ();
+
+ int wipe ();
+ int wipe_diskstream (DiskStream *);
+
+ int remove_region_from_region_list (Region&);
+
+ jack_nframes_t current_end_frame() const { return end_location->start(); }
+ jack_nframes_t frame_rate() const { return _current_frame_rate; }
+ double frames_per_smpte_frame() const { return _frames_per_smpte_frame; }
+ jack_nframes_t frames_per_hour() const { return _frames_per_hour; }
+ jack_nframes_t smpte_frames_per_hour() const { return _smpte_frames_per_hour; }
+
+ /* Locations */
+
+ Locations *locations() { return &_locations; }
+
+ sigc::signal<void,Location*> auto_loop_location_changed;
+ sigc::signal<void,Location*> auto_punch_location_changed;
+ sigc::signal<void> locations_modified;
+
+ void set_auto_punch_location (Location *);
+ void set_auto_loop_location (Location *);
+
+
+ enum ControlType {
+ AutoPlay,
+ AutoLoop,
+ AutoReturn,
+ AutoInput,
+ PunchIn,
+ PunchOut,
+ SendMTC,
+ MMCControl,
+ Live,
+ RecordingPlugins,
+ CrossFadesActive,
+ SendMMC,
+ SlaveType,
+ Clicking,
+ EditingMode,
+ PlayRange,
+ AlignChoice,
+ SeamlessLoop,
+ MidiFeedback,
+ MidiControl
+ };
+
+ sigc::signal<void,ControlType> ControlChanged;
+
+ void set_auto_play (bool yn);
+ void set_auto_return (bool yn);
+ void set_auto_input (bool yn);
+ void set_input_auto_connect (bool yn);
+ void set_output_auto_connect (AutoConnectOption);
+ void set_punch_in (bool yn);
+ void set_punch_out (bool yn);
+ void set_send_mtc (bool yn);
+ void set_send_mmc (bool yn);
+ void set_mmc_control (bool yn);
+ void set_midi_feedback (bool yn);
+ void set_midi_control (bool yn);
+ void set_recording_plugins (bool yn);
+ void set_crossfades_active (bool yn);
+ void set_seamless_loop (bool yn);
+
+ bool get_auto_play () const { return auto_play; }
+ bool get_auto_input () const { return auto_input; }
+ bool get_auto_loop () const { return auto_loop; }
+ bool get_seamless_loop () const { return seamless_loop; }
+ bool get_punch_in () const { return punch_in; }
+ bool get_punch_out () const { return punch_out; }
+ bool get_all_safe () const { return all_safe; }
+ bool get_auto_return () const { return auto_return; }
+ bool get_send_mtc () const;
+ bool get_send_mmc () const;
+ bool get_mmc_control () const;
+ bool get_midi_feedback () const;
+ bool get_midi_control () const;
+ bool get_recording_plugins () const { return recording_plugins; }
+ bool get_crossfades_active () const { return crossfades_active; }
+
+ AutoConnectOption get_input_auto_connect () const { return input_auto_connect; }
+ AutoConnectOption get_output_auto_connect () const { return output_auto_connect; }
+
+ enum LayerModel {
+ LaterHigher,
+ MoveAddHigher,
+ AddHigher
+ };
+
+ void set_layer_model (LayerModel);
+ LayerModel get_layer_model () const { return layer_model; }
+
+ sigc::signal<void> LayerModelChanged;
+
+ void set_xfade_model (CrossfadeModel);
+ CrossfadeModel get_xfade_model () const { return xfade_model; }
+
+ void set_align_style (AlignStyle);
+ AlignStyle get_align_style () const { return align_style; }
+
+ void add_event (jack_nframes_t action_frame, Event::Type type, jack_nframes_t target_frame = 0);
+ void remove_event (jack_nframes_t frame, Event::Type type);
+ void clear_events (Event::Type type);
+
+ jack_nframes_t get_block_size() const { return current_block_size; }
+ jack_nframes_t worst_output_latency () const { return _worst_output_latency; }
+ jack_nframes_t worst_input_latency () const { return _worst_input_latency; }
+ jack_nframes_t worst_track_latency () const { return _worst_track_latency; }
+
+ int save_state (string snapshot_name, bool pending = false);
+ int restore_state (string snapshot_name);
+ int save_template (string template_name);
+
+ static int rename_template (string old_name, string new_name);
+
+ static int delete_template (string name);
+
+ sigc::signal<void,string> StateSaved;
+ sigc::signal<void> StateReady;
+
+ vector<string*>* possible_states() const;
+ static vector<string*>* possible_states(string path);
+
+ XMLNode& get_state();
+ int set_state(const XMLNode& node);
+ XMLNode& get_template();
+
+ void add_instant_xml (XMLNode&, const std::string& dir);
+
+ void swap_configuration(Configuration** new_config);
+ void copy_configuration(Configuration* new_config);
+
+ enum StateOfTheState {
+ Clean = 0x0,
+ Dirty = 0x1,
+ CannotSave = 0x2,
+ Deletion = 0x4,
+ InitialConnecting = 0x8,
+ Loading = 0x10,
+ InCleanup = 0x20
+ };
+
+ StateOfTheState state_of_the_state() const { return _state_of_the_state; }
+
+ RouteGroup* add_edit_group (string);
+ RouteGroup* add_mix_group (string);
+
+ RouteGroup *mix_group_by_name (string);
+ RouteGroup *edit_group_by_name (string);
+
+ sigc::signal<void,RouteGroup*> edit_group_added;
+ sigc::signal<void,RouteGroup*> mix_group_added;
+
+ template<class T> void foreach_edit_group (T *obj, void (T::*func)(RouteGroup *)) {
+ list<RouteGroup *>::iterator i;
+ for (i = edit_groups.begin(); i != edit_groups.end(); i++) {
+ (obj->*func)(*i);
+ }
+ }
+
+ template<class T> void foreach_mix_group (T *obj, void (T::*func)(RouteGroup *)) {
+ list<RouteGroup *>::iterator i;
+ for (i = mix_groups.begin(); i != mix_groups.end(); i++) {
+ (obj->*func)(*i);
+ }
+ }
+
+ /* fundamental operations. duh. */
+
+
+ AudioTrack *new_audio_track (int input_channels, int output_channels);
+
+ Route *new_audio_route (int input_channels, int output_channels);
+
+ void remove_route (Route&);
+ void resort_routes (void *src);
+
+ AudioEngine &engine() { return _engine; };
+
+ /* configuration. there should really be accessors/mutators
+ for these
+ */
+
+ float meter_hold () { return _meter_hold; }
+ void set_meter_hold (float);
+ sigc::signal<void> MeterHoldChanged;
+
+ float meter_falloff () { return _meter_falloff; }
+ void set_meter_falloff (float);
+ sigc::signal<void> MeterFalloffChanged;
+
+ int32_t max_level;
+ int32_t min_level;
+ string click_emphasis_sound;
+ string click_sound;
+ bool click_requested;
+ jack_nframes_t over_length_short;
+ jack_nframes_t over_length_long;
+ bool send_midi_timecode;
+ bool send_midi_machine_control;
+ float shuttle_speed_factor;
+ float shuttle_speed_threshold;
+ float rf_speed;
+ float smpte_frames_per_second;
+ bool smpte_drop_frames;
+ AnyTime preroll;
+ AnyTime postroll;
+
+ /* Time */
+
+ jack_nframes_t transport_frame () const {return _transport_frame; }
+ jack_nframes_t audible_frame () const;
+
+ int set_smpte_type (float fps, bool drop_frames);
+
+ void bbt_time (jack_nframes_t when, BBT_Time&);
+
+ ARDOUR::smpte_wrap_t smpte_increment( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_decrement( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_increment_subframes( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_decrement_subframes( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_increment_seconds( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_increment_minutes( SMPTE_Time& smpte ) const;
+ ARDOUR::smpte_wrap_t smpte_increment_hours( SMPTE_Time& smpte ) const;
+ void smpte_frames_floor( SMPTE_Time& smpte ) const;
+ void smpte_seconds_floor( SMPTE_Time& smpte ) const;
+ void smpte_minutes_floor( SMPTE_Time& smpte ) const;
+ void smpte_hours_floor( SMPTE_Time& smpte ) const;
+ void smpte_to_sample( SMPTE_Time& smpte, jack_nframes_t& sample, bool use_offset, bool use_subframes ) const;
+ void sample_to_smpte( jack_nframes_t sample, SMPTE_Time& smpte, bool use_offset, bool use_subframes ) const;
+ void smpte_time (SMPTE_Time &);
+ void smpte_time (jack_nframes_t when, SMPTE_Time&);
+ void smpte_time_subframes (jack_nframes_t when, SMPTE_Time&);
+
+ void smpte_duration (jack_nframes_t, SMPTE_Time&) const;
+ void smpte_duration_string (char *, jack_nframes_t) const;
+
+ void set_smpte_offset (jack_nframes_t);
+ jack_nframes_t smpte_offset () const { return _smpte_offset; }
+ void set_smpte_offset_negative (bool);
+ bool smpte_offset_negative () const { return _smpte_offset_negative; }
+
+ jack_nframes_t convert_to_frames_at (jack_nframes_t position, AnyTime&);
+
+ sigc::signal<void> SMPTEOffsetChanged;
+ sigc::signal<void> SMPTETypeChanged;
+
+ void request_slave_source (SlaveSource, jack_nframes_t pos = 0);
+ SlaveSource slave_source() const { return _slave_type; }
+ bool synced_to_jack() const { return _slave_type == JACK; }
+ float transport_speed() const { return _transport_speed; }
+ bool transport_stopped() const { return _transport_speed == 0.0f; }
+ bool transport_rolling() const { return _transport_speed != 0.0f; }
+
+ int jack_slave_sync (jack_nframes_t);
+
+ TempoMap& tempo_map() { return *_tempo_map; }
+
+ /* region info */
+
+ sigc::signal<void,AudioRegion *> AudioRegionAdded;
+ sigc::signal<void,AudioRegion *> AudioRegionRemoved;
+
+ int region_name (string& result, string base = string(""), bool newlevel = false) const;
+ string new_region_name (string);
+ string path_from_region_name (string name, string identifier);
+
+ AudioRegion* find_whole_file_parent (AudioRegion&);
+ void find_equivalent_playlist_regions (AudioRegion&, std::vector<AudioRegion*>& result);
+
+ AudioRegion *XMLRegionFactory (const XMLNode&, bool full);
+
+ template<class T> void foreach_audio_region (T *obj, void (T::*func)(AudioRegion *));
+
+ /* source management */
+
+ struct import_status : public InterThreadInfo {
+ string doing_what;
+
+ /* control info */
+ bool multichan;
+ bool sample_convert;
+ volatile bool freeze;
+ string pathname;
+ };
+
+ int import_audiofile (import_status&);
+ bool sample_rate_convert (import_status&, string infile, string& outfile);
+ string build_tmp_convert_name (string file);
+
+ Session::SlaveSource post_export_slave;
+ jack_nframes_t post_export_position;
+
+ int start_audio_export (ARDOUR::AudioExportSpecification&);
+ int stop_audio_export (ARDOUR::AudioExportSpecification&);
+
+ void add_source (Source *);
+ int remove_file_source (FileSource&);
+
+ struct cleanup_report {
+ vector<string> paths;
+ int32_t space;
+ };
+
+ int cleanup_sources (cleanup_report&);
+ int cleanup_trash_sources (cleanup_report&);
+
+ int destroy_region (Region*);
+ int destroy_regions (list<Region*>);
+
+ int remove_last_capture ();
+
+ /* handlers should return -1 for "stop cleanup", 0 for
+ "yes, delete this playlist" and 1 for "no, don't delete
+ this playlist.
+ */
+
+ sigc::signal<int,ARDOUR::Playlist*> AskAboutPlaylistDeletion;
+
+
+ /* handlers should return !0 for use pending state, 0 for
+ ignore it.
+ */
+
+ static sigc::signal<int> AskAboutPendingState;
+
+ sigc::signal<void,Source *> SourceAdded;
+ sigc::signal<void,Source *> SourceRemoved;
+
+ FileSource *create_file_source (ARDOUR::DiskStream&, int32_t chan);
+ Source *get_source (ARDOUR::id_t);
+
+ /* playlist management */
+
+ Playlist* playlist_by_name (string name);
+ void add_playlist (Playlist *);
+ sigc::signal<void,Playlist*> PlaylistAdded;
+ sigc::signal<void,Playlist*> PlaylistRemoved;
+
+ Playlist *get_playlist (string name);
+
+ uint32_t n_playlists() const;
+
+ template<class T> void foreach_playlist (T *obj, void (T::*func)(Playlist *));
+
+ /* named selections */
+
+ NamedSelection* named_selection_by_name (string name);
+ void add_named_selection (NamedSelection *);
+ void remove_named_selection (NamedSelection *);
+
+ template<class T> void foreach_named_selection (T& obj, void (T::*func)(NamedSelection&));
+ sigc::signal<void> NamedSelectionAdded;
+ sigc::signal<void> NamedSelectionRemoved;
+
+ /* fade curves */
+
+ float get_default_fade_length () const { return default_fade_msecs; }
+ float get_default_fade_steepness () const { return default_fade_steepness; }
+ void set_default_fade (float steepness, float msecs);
+
+ /* auditioning */
+
+ Auditioner& the_auditioner() { return *auditioner; }
+ void audition_playlist ();
+ void audition_region (AudioRegion&);
+ void cancel_audition ();
+ bool is_auditioning () const;
+
+ sigc::signal<void,bool> AuditionActive;
+
+ /* flattening stuff */
+
+ int write_one_track (AudioTrack&, jack_nframes_t start, jack_nframes_t cnt, bool overwrite, vector<Source*>&,
+ InterThreadInfo& wot);
+ int freeze (InterThreadInfo&);
+
+ /* session-wide solo/mute/rec-enable */
+
+ enum SoloModel {
+ InverseMute,
+ SoloBus
+ };
+
+ bool soloing() const { return currently_soloing; }
+
+ SoloModel solo_model() const { return _solo_model; }
+ void set_solo_model (SoloModel);
+
+ bool solo_latched() const { return _solo_latched; }
+ void set_solo_latched (bool yn);
+
+ void set_all_solo (bool);
+ void set_all_mute (bool);
+
+ sigc::signal<void,bool> SoloActive;
+
+ void record_disenable_all ();
+ void record_enable_all ();
+
+ /* control/master out */
+
+ IO* control_out() const { return _control_out; }
+ IO* master_out() const { return _master_out; }
+
+ /* insert/send management */
+
+ uint32_t n_port_inserts() const { return _port_inserts.size(); }
+ uint32_t n_plugin_inserts() const { return _plugin_inserts.size(); }
+ uint32_t n_sends() const { return _sends.size(); }
+
+ string next_send_name();
+ string next_insert_name();
+
+ /* s/w "RAID" management */
+
+ jack_nframes_t available_capture_duration();
+
+ /* I/O Connections */
+
+ template<class T> void foreach_connection (T *obj, void (T::*func)(Connection *));
+ void add_connection (Connection *);
+ void remove_connection (Connection *);
+ Connection *connection_by_name (string) const;
+
+ sigc::signal<void,Connection *> ConnectionAdded;
+ sigc::signal<void,Connection *> ConnectionRemoved;
+
+ /* MIDI */
+
+ int set_mtc_port (string port_tag);
+ int set_mmc_port (string port_tag);
+ int set_midi_port (string port_tag);
+ MIDI::Port *mtc_port() const { return _mtc_port; }
+ MIDI::Port *mmc_port() const { return _mmc_port; }
+ MIDI::Port *midi_port() const { return _midi_port; }
+
+ sigc::signal<void> MTC_PortChanged;
+ sigc::signal<void> MMC_PortChanged;
+ sigc::signal<void> MIDI_PortChanged;
+
+ void set_trace_midi_input (bool, MIDI::Port* port = 0);
+ void set_trace_midi_output (bool, MIDI::Port* port = 0);
+
+ bool get_trace_midi_input(MIDI::Port *port = 0);
+ bool get_trace_midi_output(MIDI::Port *port = 0);
+
+ void send_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t, MIDI::EventTwoBytes);
+ void send_all_midi_feedback ();
+
+ void deliver_midi (MIDI::Port*, MIDI::byte*, int32_t size);
+
+ /* Scrubbing */
+
+ void start_scrub (jack_nframes_t where);
+ void stop_scrub ();
+ void set_scrub_speed (float);
+ jack_nframes_t scrub_buffer_size() const;
+ sigc::signal<void> ScrubReady;
+
+ /* History (for editors, mixers, UIs etc.) */
+
+ void undo (uint32_t n) {
+ history.undo (n);
+ }
+ void redo (uint32_t n) {
+ history.redo (n);
+ }
+
+ uint32_t undo_depth() const { return history.undo_depth(); }
+ uint32_t redo_depth() const { return history.redo_depth(); }
+ string next_undo() const { return history.next_undo(); }
+ string next_redo() const { return history.next_redo(); }
+
+ void begin_reversible_command (string cmd_name, UndoAction *private_undo = 0);
+ void commit_reversible_command (UndoAction* private_redo = 0);
+
+ void add_undo (const UndoAction& ua) {
+ current_cmd.add_undo (ua);
+ }
+ void add_redo (const UndoAction& ua) {
+ current_cmd.add_redo (ua);
+ }
+ void add_redo_no_execute (const UndoAction& ua) {
+ current_cmd.add_redo_no_execute (ua);
+ }
+
+ UndoAction global_solo_memento (void *src);
+ UndoAction global_mute_memento (void *src);
+ UndoAction global_record_enable_memento (void *src);
+ UndoAction global_metering_memento (void *src);
+
+ /* edit mode */
+
+ void set_edit_mode (EditMode);
+ EditMode get_edit_mode () const { return _edit_mode; }
+
+ /* clicking */
+
+ IO& click_io() { return *_click_io; }
+ void set_clicking (bool yn);
+ bool get_clicking() const;
+
+ void set_click_sound (string path);
+ void set_click_emphasis_sound (string path);
+
+ /* tempo FX */
+
+ struct TimeStretchRequest {
+ ARDOUR::AudioRegion* region;
+ float fraction; /* session: read ; GUI: write */
+ float progress; /* session: write ; GUI: read */
+ bool running; /* read/write */
+ bool quick_seek; /* GUI: write */
+ bool antialias; /* GUI: write */
+
+ TimeStretchRequest () : region (0) {}
+ };
+
+ AudioRegion* tempoize_region (TimeStretchRequest&);
+
+ string raid_path() const;
+ void set_raid_path(string);
+
+ /* need to call this whenever we change native file formats */
+
+ void reset_native_file_format();
+
+ /* disk, buffer loads */
+
+ uint32_t playback_load ();
+ uint32_t capture_load ();
+ uint32_t playback_load_min ();
+ uint32_t capture_load_min ();
+
+ void reset_playback_load_min ();
+ void reset_capture_load_min ();
+
+ float read_data_rate () const;
+ float write_data_rate () const;
+
+ /* ranges */
+
+ void set_audio_range (list<AudioRange>&);
+ void set_music_range (list<MusicRange>&);
+
+ void request_play_range (bool yn);
+ bool get_play_range () const { return _play_range; }
+
+ /* favorite dirs */
+ typedef vector<string> FavoriteDirs;
+
+ static int read_favorite_dirs (FavoriteDirs&);
+
+ static int write_favorite_dirs (FavoriteDirs&);
+
+ /* file suffixes */
+
+ static const char* template_suffix() { return _template_suffix; }
+ static const char* statefile_suffix() { return _statefile_suffix; }
+ static const char* pending_suffix() { return _pending_suffix; }
+
+ /* buffers for gain and pan */
+
+ gain_t* gain_automation_buffer () const { return _gain_automation_buffer; }
+ pan_t** pan_automation_buffer() const { return _pan_automation_buffer; }
+
+ /* VST support */
+
+ static long vst_callback (AEffect* effect,
+ long opcode,
+ long index,
+ long value,
+ void* ptr,
+ float opt);
+
+ typedef float (*compute_peak_t) (Sample *, jack_nframes_t, float);
+ typedef void (*apply_gain_to_buffer_t) (Sample *, jack_nframes_t, float);
+ typedef void (*mix_buffers_with_gain_t) (Sample *, Sample *, jack_nframes_t, float);
+ typedef void (*mix_buffers_no_gain_t) (Sample *, Sample *, jack_nframes_t);
+
+ static compute_peak_t compute_peak;
+ static apply_gain_to_buffer_t apply_gain_to_buffer;
+ static mix_buffers_with_gain_t mix_buffers_with_gain;
+ static mix_buffers_no_gain_t mix_buffers_no_gain;
+
+ protected:
+ friend class AudioEngine;
+ void set_block_size (jack_nframes_t nframes);
+ void set_frame_rate (jack_nframes_t nframes);
+
+ protected:
+ friend class DiskStream;
+ void stop_butler ();
+ void wait_till_butler_finished();
+
+ protected:
+ friend class Route;
+ void schedule_curve_reallocation ();
+ void update_latency_compensation (bool, bool);
+
+ private:
+ int create (bool& new_session, string* mix_template, jack_nframes_t initial_length);
+
+ static const char* _template_suffix;
+ static const char* _statefile_suffix;
+ static const char* _pending_suffix;
+
+ enum SubState {
+ PendingDeclickIn = 0x1,
+ PendingDeclickOut = 0x2,
+ StopPendingCapture = 0x4,
+ AutoReturning = 0x10,
+ PendingLocate = 0x20,
+ PendingSetLoop = 0x40
+ };
+
+ /* stuff used in process() should be close together to
+ maximise cache hits
+ */
+
+ typedef void (Session::*process_function_type)(jack_nframes_t);
+
+ AudioEngine &_engine;
+ atomic_t processing_prohibited;
+ process_function_type process_function;
+ process_function_type last_process_function;
+ jack_nframes_t _current_frame_rate;
+ int transport_sub_state;
+ atomic_t _record_status;
+ jack_nframes_t _transport_frame;
+ Location* end_location;
+ Slave *_slave;
+ SlaveSource _slave_type;
+ float _transport_speed;
+ volatile float _desired_transport_speed;
+ float _last_transport_speed;
+ jack_nframes_t _last_slave_transport_frame;
+ jack_nframes_t maximum_output_latency;
+ jack_nframes_t last_stop_frame;
+ vector<Sample *> _passthru_buffers;
+ vector<Sample *> _silent_buffers;
+ jack_nframes_t current_block_size;
+ jack_nframes_t _worst_output_latency;
+ jack_nframes_t _worst_input_latency;
+ jack_nframes_t _worst_track_latency;
+ bool _have_captured;
+ float _meter_hold;
+ float _meter_falloff;
+ bool _end_location_is_free;
+
+ void set_worst_io_latencies (bool take_lock);
+ void set_worst_io_latencies_x (IOChange asifwecare, void *ignored) {
+ set_worst_io_latencies (true);
+ }
+
+ void update_latency_compensation_proxy (void* ignored);
+
+ void ensure_passthru_buffers (uint32_t howmany);
+
+ void process_scrub (jack_nframes_t);
+ void process_without_events (jack_nframes_t);
+ void process_with_events (jack_nframes_t);
+ void process_audition (jack_nframes_t);
+ int process_export (jack_nframes_t, ARDOUR::AudioExportSpecification*);
+
+ /* slave tracking */
+
+ static const int delta_accumulator_size = 25;
+ int delta_accumulator_cnt;
+ long delta_accumulator[delta_accumulator_size];
+ long average_slave_delta;
+ int average_dir;
+ bool have_first_delta_accumulator;
+
+ enum SlaveState {
+ Stopped,
+ Waiting,
+ Running
+ };
+
+ SlaveState slave_state;
+ jack_nframes_t slave_wait_end;
+
+ void reset_slave_state ();
+ bool follow_slave (jack_nframes_t, jack_nframes_t);
+
+ bool _exporting;
+ int prepare_to_export (ARDOUR::AudioExportSpecification&);
+
+ void prepare_diskstreams ();
+ void commit_diskstreams (jack_nframes_t, bool& session_requires_butler);
+ int process_routes (jack_nframes_t, jack_nframes_t);
+ int silent_process_routes (jack_nframes_t, jack_nframes_t);
+
+ bool get_rec_monitors_input () {
+ if (actively_recording()) {
+ return true;
+ } else {
+ if (auto_input) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ int get_transport_declick_required () {
+
+ if (transport_sub_state & PendingDeclickIn) {
+ transport_sub_state &= ~PendingDeclickIn;
+ return 1;
+ } else if (transport_sub_state & PendingDeclickOut) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ bool maybe_stop (jack_nframes_t limit) {
+ if ((_transport_speed > 0.0f && _transport_frame >= limit) || (_transport_speed < 0.0f && _transport_frame == 0)) {
+ stop_transport ();
+ return true;
+ }
+ return false;
+ }
+
+ void check_declick_out ();
+
+ MIDI::MachineControl* mmc;
+ MIDI::Port* _mmc_port;
+ MIDI::Port* _mtc_port;
+ MIDI::Port* _midi_port;
+ string _path;
+ string _name;
+ bool recording_plugins;
+
+ /* toggles */
+
+ bool auto_play;
+ bool punch_in;
+ bool punch_out;
+ bool auto_loop;
+ bool seamless_loop;
+ bool loop_changing;
+ jack_nframes_t last_loopend;
+ bool auto_input;
+ bool crossfades_active;
+ bool all_safe;
+ bool auto_return;
+ bool monitor_in;
+ bool send_mtc;
+ bool send_mmc;
+ bool mmc_control;
+ bool midi_feedback;
+ bool midi_control;
+
+ RingBuffer<Event*> pending_events;
+
+ void hookup_io ();
+ void when_engine_running ();
+ sigc::connection first_time_running;
+ void graph_reordered ();
+
+ string _current_snapshot_name;
+
+ XMLTree* state_tree;
+ bool state_was_pending;
+ StateOfTheState _state_of_the_state;
+
+ void auto_save();
+ int load_options (const XMLNode&);
+ XMLNode& get_options () const;
+ int load_state (string snapshot_name);
+
+ jack_nframes_t _last_roll_location;
+ jack_nframes_t _last_record_location;
+ bool pending_locate_roll;
+ jack_nframes_t pending_locate_frame;
+
+ bool pending_locate_flush;
+ bool pending_abort;
+ bool pending_auto_loop;
+
+ Sample* butler_mixdown_buffer;
+ float* butler_gain_buffer;
+ pthread_t butler_thread;
+ PBD::NonBlockingLock butler_request_lock;
+ pthread_cond_t butler_paused;
+ bool butler_should_run;
+ atomic_t butler_should_do_transport_work;
+ int butler_request_pipe[2];
+
+ struct ButlerRequest {
+ enum Type {
+ Wake,
+ Run,
+ Pause,
+ Quit
+ };
+ };
+
+ enum PostTransportWork {
+ PostTransportStop = 0x1,
+ PostTransportDisableRecord = 0x2,
+ PostTransportPosition = 0x8,
+ PostTransportDidRecord = 0x20,
+ PostTransportDuration = 0x40,
+ PostTransportLocate = 0x80,
+ PostTransportRoll = 0x200,
+ PostTransportAbort = 0x800,
+ PostTransportOverWrite = 0x1000,
+ PostTransportSpeed = 0x2000,
+ PostTransportAudition = 0x4000,
+ PostTransportScrub = 0x8000,
+ PostTransportReverse = 0x10000,
+ PostTransportInputChange = 0x20000,
+ PostTransportCurveRealloc = 0x40000
+ };
+
+ static const PostTransportWork ProcessCannotProceedMask =
+ PostTransportWork (PostTransportInputChange|
+ PostTransportSpeed|
+ PostTransportReverse|
+ PostTransportCurveRealloc|
+ PostTransportScrub|
+ PostTransportAudition|
+ PostTransportLocate|
+ PostTransportStop);
+
+ PostTransportWork post_transport_work;
+
+ void summon_butler ();
+ void schedule_butler_transport_work ();
+ int start_butler_thread ();
+ void terminate_butler_thread ();
+ static void *_butler_thread_work (void *arg);
+ void* butler_thread_work ();
+
+ uint32_t cumulative_rf_motion;
+ uint32_t rf_scale;
+
+ void set_rf_speed (float speed);
+ void reset_rf_scale (jack_nframes_t frames_moved);
+
+ Locations _locations;
+ void locations_changed ();
+ void locations_added (Location*);
+ void handle_locations_changed (Locations::LocationList&);
+
+ sigc::connection auto_punch_start_changed_connection;
+ sigc::connection auto_punch_end_changed_connection;
+ sigc::connection auto_punch_changed_connection;
+ void auto_punch_start_changed (Location *);
+ void auto_punch_end_changed (Location *);
+ void auto_punch_changed (Location *);
+
+ sigc::connection auto_loop_start_changed_connection;
+ sigc::connection auto_loop_end_changed_connection;
+ sigc::connection auto_loop_changed_connection;
+ void auto_loop_changed (Location *);
+
+ typedef list<Event *> Events;
+ Events events;
+ Events immediate_events;
+ Events::iterator next_event;
+
+ /* there can only ever be one of each of these */
+
+ Event *auto_loop_event;
+ Event *punch_out_event;
+ Event *punch_in_event;
+
+ /* events */
+
+ void dump_events () const;
+ void queue_event (Event *ev);
+ void merge_event (Event*);
+ void replace_event (Event::Type, jack_nframes_t action_frame, jack_nframes_t target = 0);
+ bool _replace_event (Event*);
+ bool _remove_event (Event *);
+ void _clear_event_type (Event::Type);
+
+ void first_stage_init (string path, string snapshot_name);
+ int second_stage_init (bool new_tracks);
+ void find_current_end ();
+ void remove_empty_sounds ();
+
+ void setup_midi_control ();
+ int midi_read (MIDI::Port *);
+
+ void enable_record ();
+
+ void increment_transport_position (uint32_t val) {
+ if (max_frames - val < _transport_frame) {
+ _transport_frame = max_frames;
+ } else {
+ _transport_frame += val;
+ }
+ }
+
+ void decrement_transport_position (uint32_t val) {
+ if (val < _transport_frame) {
+ _transport_frame -= val;
+ } else {
+ _transport_frame = 0;
+ }
+ }
+
+ void post_transport_motion ();
+ static void *session_loader_thread (void *arg);
+
+ void *do_work();
+
+ void set_next_event ();
+ void process_event (Event *);
+
+ /* MIDI Machine Control */
+
+ void deliver_mmc (MIDI::MachineControl::Command, jack_nframes_t);
+ void deliver_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t, MIDI::EventTwoBytes);
+ void deliver_data (MIDI::Port* port, MIDI::byte*, int32_t size);
+
+ void spp_start (MIDI::Parser&);
+ void spp_continue (MIDI::Parser&);
+ void spp_stop (MIDI::Parser&);
+
+ void mmc_deferred_play (MIDI::MachineControl &);
+ void mmc_stop (MIDI::MachineControl &);
+ void mmc_step (MIDI::MachineControl &, int);
+ void mmc_pause (MIDI::MachineControl &);
+ void mmc_record_pause (MIDI::MachineControl &);
+ void mmc_record_strobe (MIDI::MachineControl &);
+ void mmc_record_exit (MIDI::MachineControl &);
+ void mmc_track_record_status (MIDI::MachineControl &,
+ uint32_t track, bool enabled);
+ void mmc_fast_forward (MIDI::MachineControl &);
+ void mmc_rewind (MIDI::MachineControl &);
+ void mmc_locate (MIDI::MachineControl &, const MIDI::byte *);
+ void mmc_shuttle (MIDI::MachineControl &mmc, float speed, bool forw);
+ void mmc_record_enable (MIDI::MachineControl &mmc, size_t track, bool enabled);
+
+ struct timeval last_mmc_step;
+ double step_speed;
+
+ typedef sigc::slot<bool> MidiTimeoutCallback;
+ typedef list<MidiTimeoutCallback> MidiTimeoutList;
+
+ MidiTimeoutList midi_timeouts;
+ bool mmc_step_timeout ();
+
+ MIDI::byte mmc_buffer[32];
+ MIDI::byte mtc_msg[16];
+ MIDI::byte mtc_smpte_bits; /* encoding of SMTPE type for MTC */
+ MIDI::byte midi_msg[16];
+ jack_nframes_t outbound_mtc_smpte_frame;
+ SMPTE_Time transmitting_smpte_time;
+ int next_quarter_frame_to_send;
+
+ double _frames_per_smpte_frame; /* has to be floating point because of drop frame */
+ jack_nframes_t _frames_per_hour;
+ jack_nframes_t _smpte_frames_per_hour;
+ jack_nframes_t _smpte_offset;
+ bool _smpte_offset_negative;
+
+ /* cache the most-recently requested time conversions.
+ this helps when we have multiple clocks showing the
+ same time (e.g. the transport frame)
+ */
+
+ bool last_smpte_valid;
+ jack_nframes_t last_smpte_when;
+ SMPTE_Time last_smpte;
+
+ int send_full_time_code ();
+ int send_midi_time_code ();
+
+ void send_full_time_code_in_another_thread ();
+ void send_midi_time_code_in_another_thread ();
+ void send_time_code_in_another_thread (bool full);
+ void send_mmc_in_another_thread (MIDI::MachineControl::Command, jack_nframes_t frame = 0);
+
+ /* Feedback */
+
+ typedef sigc::slot<int> FeedbackFunctionPtr;
+ static void* _feedback_thread_work (void *);
+ void* feedback_thread_work ();
+ int feedback_generic_midi_function ();
+ std::list<FeedbackFunctionPtr> feedback_functions;
+ int active_feedback;
+ int feedback_request_pipe[2];
+ pthread_t feedback_thread;
+
+ struct FeedbackRequest {
+ enum Type {
+ Start,
+ Stop,
+ Quit
+ };
+ };
+
+ int init_feedback();
+ int start_feedback ();
+ int stop_feedback ();
+ void terminate_feedback ();
+ int poke_feedback (FeedbackRequest::Type);
+
+ jack_nframes_t adjust_apparent_position (jack_nframes_t frames);
+
+ void reset_record_status ();
+
+ int no_roll (jack_nframes_t nframes, jack_nframes_t offset);
+
+ bool non_realtime_work_pending() const { return static_cast<bool>(post_transport_work); }
+ bool process_can_proceed() const { return !(post_transport_work & ProcessCannotProceedMask); }
+
+ struct MIDIRequest {
+
+ enum Type {
+ SendFullMTC,
+ SendMTC,
+ SendMMC,
+ PortChange,
+ SendMessage,
+ Deliver,
+ Quit
+ };
+
+ Type type;
+ MIDI::MachineControl::Command mmc_cmd;
+ jack_nframes_t locate_frame;
+
+ // for SendMessage type
+
+ MIDI::Port * port;
+ MIDI::channel_t chan;
+ union {
+ MIDI::EventTwoBytes data;
+ MIDI::byte* buf;
+ };
+
+ union {
+ MIDI::eventType ev;
+ int32_t size;
+ };
+
+ MIDIRequest () {}
+
+ void *operator new(size_t ignored) {
+ return pool.alloc ();
+ };
+
+ void operator delete(void *ptr, size_t size) {
+ pool.release (ptr);
+ }
+
+ private:
+ static MultiAllocSingleReleasePool pool;
+ };
+
+ PBD::Lock midi_lock;
+ pthread_t midi_thread;
+ int midi_request_pipe[2];
+ atomic_t butler_active;
+ RingBuffer<MIDIRequest*> midi_requests;
+
+ int start_midi_thread ();
+ void terminate_midi_thread ();
+ void poke_midi_thread ();
+ static void *_midi_thread_work (void *arg);
+ void midi_thread_work ();
+ void change_midi_ports ();
+ int use_config_midi_ports ();
+
+ bool waiting_to_start;
+
+ void set_auto_loop (bool yn);
+ void overwrite_some_buffers (DiskStream*);
+ void flush_all_redirects ();
+ void locate (jack_nframes_t, bool with_roll, bool with_flush, bool with_loop=false);
+ void start_locate (jack_nframes_t, bool with_roll, bool with_flush, bool with_loop=false);
+ void force_locate (jack_nframes_t frame, bool with_roll = false);
+ void set_diskstream_speed (DiskStream*, float speed);
+ void set_transport_speed (float speed, bool abort = false);
+ void stop_transport (bool abort = false);
+ void start_transport ();
+ void actually_start_transport ();
+ void realtime_stop (bool abort);
+ void non_realtime_start_scrub ();
+ void non_realtime_set_speed ();
+ void non_realtime_stop (bool abort);
+ void non_realtime_overwrite ();
+ void non_realtime_buffer_fill ();
+ void butler_transport_work ();
+ void post_transport ();
+ void engine_halted ();
+ void xrun_recovery ();
+
+ TempoMap *_tempo_map;
+ void tempo_map_changed (Change);
+
+ /* edit/mix groups */
+
+ int load_route_groups (const XMLNode&, bool is_edit);
+ int load_edit_groups (const XMLNode&);
+ int load_mix_groups (const XMLNode&);
+
+
+ list<RouteGroup *> edit_groups;
+ list<RouteGroup *> mix_groups;
+
+ /* disk-streams */
+
+ DiskStreamList diskstreams;
+ mutable PBD::Lock diskstream_lock;
+ uint32_t dstream_buffer_size;
+ void add_diskstream (DiskStream*);
+ int load_diskstreams (const XMLNode&);
+
+ /* routes stuff */
+
+ RouteList routes;
+ mutable PBD::NonBlockingLock route_lock;
+ void add_route (Route*);
+
+ int load_routes (const XMLNode&);
+ Route* XMLRouteFactory (const XMLNode&);
+
+ /* mixer stuff */
+
+ bool _solo_latched;
+ SoloModel _solo_model;
+ bool solo_update_disabled;
+ bool currently_soloing;
+
+ void route_mute_changed (void *src);
+ void route_solo_changed (void *src, Route *);
+ void catch_up_on_solo ();
+ void update_route_solo_state ();
+ void modify_solo_mute (bool, bool);
+ void strip_portname_for_solo (string& portname);
+
+ /* REGION MANAGEMENT */
+
+ mutable PBD::Lock region_lock;
+ typedef map<ARDOUR::id_t,AudioRegion *> AudioRegionList;
+ AudioRegionList audio_regions;
+
+ void region_renamed (Region *);
+ void region_changed (Change, Region *);
+ void add_region (Region *);
+ void remove_region (Region *);
+
+ int load_regions (const XMLNode& node);
+
+ /* SOURCES */
+
+ mutable PBD::Lock source_lock;
+ typedef std::map<id_t, Source *> SourceList;
+
+ SourceList sources;
+
+ int load_sources (const XMLNode& node);
+ XMLNode& get_sources_as_xml ();
+
+ void remove_source (Source *);
+
+ Source *XMLSourceFactory (const XMLNode&);
+
+ /* PLAYLISTS */
+
+ mutable PBD::Lock playlist_lock;
+ typedef set<Playlist *> PlaylistList;
+ PlaylistList playlists;
+ PlaylistList unused_playlists;
+
+ int load_playlists (const XMLNode&);
+ int load_unused_playlists (const XMLNode&);
+ void remove_playlist (Playlist *);
+ void track_playlist (Playlist *, bool);
+
+ Playlist *playlist_factory (string name);
+ Playlist *XMLPlaylistFactory (const XMLNode&);
+
+ void playlist_length_changed (Playlist *);
+ void diskstream_playlist_changed (DiskStream *);
+
+ /* NAMED SELECTIONS */
+
+ mutable PBD::Lock named_selection_lock;
+ typedef set<NamedSelection *> NamedSelectionList;
+ NamedSelectionList named_selections;
+
+ int load_named_selections (const XMLNode&);
+
+ NamedSelection *named_selection_factory (string name);
+ NamedSelection *XMLNamedSelectionFactory (const XMLNode&);
+
+ /* DEFAULT FADE CURVES */
+
+ float default_fade_steepness;
+ float default_fade_msecs;
+
+ /* AUDITIONING */
+
+ Auditioner *auditioner;
+ void set_audition (AudioRegion*);
+ void non_realtime_set_audition ();
+ AudioRegion *pending_audition_region;
+
+ /* EXPORT */
+
+ /* FLATTEN */
+
+ int flatten_one_track (AudioTrack&, jack_nframes_t start, jack_nframes_t cnt);
+
+ /* INSERT AND SEND MANAGEMENT */
+
+ slist<PortInsert *> _port_inserts;
+ slist<PluginInsert *> _plugin_inserts;
+ slist<Send *> _sends;
+ uint32_t send_cnt;
+ uint32_t insert_cnt;
+
+ void add_redirect (Redirect *);
+ void remove_redirect (Redirect *);
+
+ /* S/W RAID */
+
+ struct space_and_path {
+ uint32_t blocks; /* 4kB blocks */
+ string path;
+
+ space_and_path() {
+ blocks = 0;
+ }
+ };
+
+ struct space_and_path_ascending_cmp {
+ bool operator() (space_and_path a, space_and_path b) {
+ return a.blocks > b.blocks;
+ }
+ };
+
+ void setup_raid_path (string path);
+
+ vector<space_and_path> session_dirs;
+ vector<space_and_path>::iterator last_rr_session_dir;
+ uint32_t _total_free_4k_blocks;
+ PBD::Lock space_lock;
+
+ static const char* sound_dir_name;
+ static const char* dead_sound_dir_name;
+ static const char* peak_dir_name;
+
+ string discover_best_sound_dir ();
+ int ensure_sound_dir (string, string&);
+ void refresh_disk_space ();
+
+ atomic_t _playback_load;
+ atomic_t _capture_load;
+ atomic_t _playback_load_min;
+ atomic_t _capture_load_min;
+
+ /* I/O Connections */
+
+ typedef list<Connection *> ConnectionList;
+ mutable PBD::Lock connection_lock;
+ ConnectionList _connections;
+ int load_connections (const XMLNode&);
+
+ int set_slave_source (SlaveSource, jack_nframes_t);
+
+ void reverse_diskstream_buffers ();
+
+ UndoHistory history;
+ UndoCommand current_cmd;
+
+ GlobalRouteBooleanState get_global_route_boolean (bool (Route::*method)(void) const);
+ GlobalRouteMeterState get_global_route_metering ();
+
+ void set_global_route_boolean (GlobalRouteBooleanState s, void (Route::*method)(bool, void*), void *arg);
+ void set_global_route_metering (GlobalRouteMeterState s, void *arg);
+
+ void set_global_mute (GlobalRouteBooleanState s, void *src);
+ void set_global_solo (GlobalRouteBooleanState s, void *src);
+ void set_global_record_enable (GlobalRouteBooleanState s, void *src);
+
+ void jack_timebase_callback (jack_transport_state_t, jack_nframes_t, jack_position_t*, int);
+ int jack_sync_callback (jack_transport_state_t, jack_position_t*);
+ void record_enable_change_all (bool yn);
+
+ XMLNode& state(bool);
+
+ /* click track */
+
+ struct Click {
+ jack_nframes_t start;
+ jack_nframes_t duration;
+ jack_nframes_t offset;
+ const Sample *data;
+
+ Click (jack_nframes_t s, jack_nframes_t d, const Sample *b)
+ : start (s), duration (d), data (b) { offset = 0; }
+
+ void *operator new(size_t ignored) {
+ return pool.alloc ();
+ };
+
+ void operator delete(void *ptr, size_t size) {
+ pool.release (ptr);
+ }
+
+ private:
+ static Pool pool;
+ };
+
+ typedef list<Click*> Clicks;
+
+ Clicks clicks;
+ bool _clicking;
+ IO* _click_io;
+ Sample* click_data;
+ Sample* click_emphasis_data;
+ jack_nframes_t click_length;
+ jack_nframes_t click_emphasis_length;
+
+ static const Sample default_click[];
+ static const jack_nframes_t default_click_length;
+ static const Sample default_click_emphasis[];
+ static const jack_nframes_t default_click_emphasis_length;
+
+ Click *get_click();
+ void setup_click_sounds (int which);
+ void clear_clicks ();
+ void click (jack_nframes_t start, jack_nframes_t nframes, jack_nframes_t offset);
+
+ vector<Route*> master_outs;
+
+ EditMode _edit_mode;
+ EditMode pending_edit_mode;
+
+ /* range playback */
+
+ list<AudioRange> current_audio_range;
+ bool _play_range;
+ void set_play_range (bool yn);
+ void setup_auto_play ();
+
+ /* main outs */
+ uint32_t main_outs;
+
+ IO* _master_out;
+ IO* _control_out;
+
+ AutoConnectOption input_auto_connect;
+ AutoConnectOption output_auto_connect;
+
+ AlignStyle align_style;
+
+ gain_t* _gain_automation_buffer;
+ pan_t** _pan_automation_buffer;
+ void allocate_pan_automation_buffers (jack_nframes_t nframes, uint32_t howmany, bool force);
+ uint32_t _npan_buffers;
+
+ /* VST support */
+
+ long _vst_callback (VSTPlugin*,
+ long opcode,
+ long index,
+ long value,
+ void* ptr,
+ float opt);
+
+ /* number of hardware audio ports we're using,
+ based on max (requested,available)
+ */
+
+ uint32_t n_physical_outputs;
+ uint32_t n_physical_inputs;
+
+ void remove_pending_capture_state ();
+
+ int find_all_sources (std::string path, std::set<std::string>& result);
+ int find_all_sources_across_snapshots (std::set<std::string>& result, bool exclude_this_snapshot);
+
+ LayerModel layer_model;
+ CrossfadeModel xfade_model;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_session_h__ */
diff --git a/libs/ardour/ardour/session_connection.h b/libs/ardour/ardour/session_connection.h
new file mode 100644
index 0000000000..caa20ed387
--- /dev/null
+++ b/libs/ardour/ardour/session_connection.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_connection_h__
+#define __ardour_session_connection_h__
+
+#include <ardour/session.h>
+#include <ardour/connection.h>
+
+namespace ARDOUR {
+
+template<class T> void
+Session::foreach_connection (T *obj, void (T::*func)(Connection *))
+{
+ LockMonitor lm (connection_lock, __LINE__, __FILE__);
+ for (ConnectionList::iterator i = _connections.begin(); i != _connections.end(); i++) {
+ (obj->*func) (*i);
+ }
+}
+
+} /* namespace */
+
+#endif /* __ardour_session_connection_h__ */
diff --git a/libs/ardour/ardour/session_diskstream.h b/libs/ardour/ardour/session_diskstream.h
new file mode 100644
index 0000000000..55c79f549f
--- /dev/null
+++ b/libs/ardour/ardour/session_diskstream.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_diskstream_h__
+#define __ardour_session_diskstream_h__
+
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+
+namespace ARDOUR {
+
+template<class T> void
+Session::foreach_diskstream (T *obj, void (T::*func)(DiskStream&))
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); i++) {
+ if (!(*i)->hidden()) {
+ (obj->*func) (**i);
+ }
+ }
+}
+
+} /* namespace */
+
+#endif /* __ardour_session_diskstream_h__ */
diff --git a/libs/ardour/ardour/session_playlist.h b/libs/ardour/ardour/session_playlist.h
new file mode 100644
index 0000000000..925a60182a
--- /dev/null
+++ b/libs/ardour/ardour/session_playlist.h
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_playlist_h__
+#define __ardour_session_playlist_h__
+
+#include <ardour/session.h>
+#include <ardour/playlist.h>
+
+namespace ARDOUR {
+
+template<class T> void
+Session::foreach_playlist (T *obj, void (T::*func)(Playlist *))
+{
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); i++) {
+ if (!(*i)->hidden()) {
+ (obj->*func) (*i);
+ }
+ }
+ for (PlaylistList::iterator i = unused_playlists.begin(); i != unused_playlists.end(); i++) {
+ if (!(*i)->hidden()) {
+ (obj->*func) (*i);
+ }
+ }
+}
+
+} /* namespace */
+
+#endif /* __ardour_session_playlist_h__ */
diff --git a/libs/ardour/ardour/session_region.h b/libs/ardour/ardour/session_region.h
new file mode 100644
index 0000000000..16580d8e73
--- /dev/null
+++ b/libs/ardour/ardour/session_region.h
@@ -0,0 +1,19 @@
+#ifndef __ardour_session_region_h__
+#define __ardour_session_region_h__
+
+#include <ardour/session.h>
+#include <ardour/audioregion.h>
+
+namespace ARDOUR {
+
+template<class T> void Session::foreach_audio_region (T *obj, void (T::*func)(AudioRegion *))
+{
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+ for (AudioRegionList::iterator i = audio_regions.begin(); i != audio_regions.end(); i++) {
+ (obj->*func) ((*i).second);
+ }
+}
+
+}
+
+#endif /* __ardour_session_region_h__ */
diff --git a/libs/ardour/ardour/session_route.h b/libs/ardour/ardour/session_route.h
new file mode 100644
index 0000000000..0b126531dc
--- /dev/null
+++ b/libs/ardour/ardour/session_route.h
@@ -0,0 +1,89 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_route_h__
+#define __ardour_session_route_h__
+
+#include <iostream>
+
+#include <pbd/lockmonitor.h>
+#include <ardour/session.h>
+#include <ardour/route.h>
+
+namespace ARDOUR {
+
+template<class T> void
+Session::foreach_route (T *obj, void (T::*func)(Route&))
+{
+ RouteList public_order;
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ public_order = routes;
+ }
+
+ RoutePublicOrderSorter cmp;
+ public_order.sort (cmp);
+
+ for (RouteList::iterator i = public_order.begin(); i != public_order.end(); i++) {
+ (obj->*func) (**i);
+ }
+}
+
+template<class T> void
+Session::foreach_route (T *obj, void (T::*func)(Route*))
+{
+ RouteList public_order;
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ public_order = routes;
+ }
+
+ RoutePublicOrderSorter cmp;
+ public_order.sort (cmp);
+
+ for (RouteList::iterator i = public_order.begin(); i != public_order.end(); i++) {
+ (obj->*func) (*i);
+ }
+}
+
+
+template<class T, class A> void
+Session::foreach_route (T *obj, void (T::*func)(Route&, A), A arg1)
+{
+ RouteList public_order;
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ public_order = routes;
+ }
+
+ RoutePublicOrderSorter cmp;
+ public_order.sort (cmp);
+
+ for (RouteList::iterator i = public_order.begin(); i != public_order.end(); i++) {
+ (obj->*func) (**i, arg1);
+ }
+}
+
+} /* namespace */
+
+#endif /* __ardour_session_route_h__ */
diff --git a/libs/ardour/ardour/session_selection.h b/libs/ardour/ardour/session_selection.h
new file mode 100644
index 0000000000..a1dd31d7ff
--- /dev/null
+++ b/libs/ardour/ardour/session_selection.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_session_named_selection_h__
+#define __ardour_session_named_selection_h__
+
+#include <ardour/session.h>
+#include <ardour/named_selection.h>
+
+namespace ARDOUR {
+
+template<class T> void
+Session::foreach_named_selection (T& obj, void (T::*func)(NamedSelection&))
+{
+ LockMonitor lm (named_selection_lock, __LINE__, __FILE__);
+ for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); i++) {
+ (obj.*func) (**i);
+ }
+}
+
+} /* namespace */
+
+#endif /* __ardour_session_named_selection_h__ */
diff --git a/libs/ardour/ardour/silentsource.h b/libs/ardour/ardour/silentsource.h
new file mode 100644
index 0000000000..0079e5f103
--- /dev/null
+++ b/libs/ardour/ardour/silentsource.h
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __playlist_const_buffer_h__
+#define __playlist_const_buffer_h__
+
+#include <string>
+#include <cstdlib>
+
+#include "source.h"
+
+namespace ARDOUR {
+
+class SilentSource : public Source {
+ public:
+ SilentSource () {
+ _name = "Silent Source";
+ }
+
+ static bool is_silent_source (const string& name) {
+ return name == "Silent Source";
+ }
+
+ jack_nframes_t length() { return ~0U; }
+
+ jack_nframes_t read (Source::Data *dst, jack_nframes_t start, jack_nframes_t cnt) {
+ jack_nframes_t n = cnt;
+ while (n--) *dst++ = 0;
+ return cnt;
+ }
+
+ void peak (guint8 *max, guint8 *min, jack_nframes_t start, jack_nframes_t cnt) {
+ *max = *min = 0;
+ }
+};
+
+}
+
+#endif /* __playlist_const_buffer_h__ */
diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h
new file mode 100644
index 0000000000..90e63aed83
--- /dev/null
+++ b/libs/ardour/ardour/slave.h
@@ -0,0 +1,151 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_slave_h__
+#define __ardour_slave_h__
+
+#include <vector>
+
+#include <jack/jack.h>
+
+#include <pthread.h>
+#include <sigc++/signal.h>
+#include <ardour/ardour.h>
+#include <midi++/parser.h>
+#include <midi++/types.h>
+
+namespace MIDI {
+ class Port;
+}
+
+namespace ARDOUR {
+class Session;
+
+class Slave {
+ public:
+ Slave() { }
+ virtual ~Slave() {}
+
+ virtual bool speed_and_position (float&, jack_nframes_t&) = 0;
+ virtual bool locked() const = 0;
+ virtual bool ok() const = 0;
+ virtual bool starting() const { return false; }
+ virtual jack_nframes_t resolution() const = 0;
+ virtual bool requires_seekahead () const = 0;
+};
+
+
+class MTC_Slave : public Slave, public sigc::trackable {
+ public:
+ MTC_Slave (Session&, MIDI::Port&);
+ ~MTC_Slave ();
+
+ void rebind (MIDI::Port&);
+ bool speed_and_position (float&, jack_nframes_t&);
+
+ bool locked() const;
+ bool ok() const;
+ void handle_locate (const MIDI::byte*);
+
+ jack_nframes_t resolution() const;
+ bool requires_seekahead () const { return true; }
+
+ private:
+ Session& session;
+ MIDI::Port* port;
+ std::vector<sigc::connection> connections;
+
+ struct SafeTime {
+
+
+ int guard1;
+ //SMPTE_Time mtc;
+ jack_nframes_t position;
+ jack_nframes_t timestamp;
+ int guard2;
+
+ SafeTime() {
+ guard1 = 0;
+ guard2 = 0;
+ timestamp = 0;
+ }
+ };
+
+ SafeTime current;
+ jack_nframes_t mtc_frame; /* current time */
+ jack_nframes_t last_inbound_frame; /* when we got it; audio clocked */
+
+ float mtc_speed;
+ jack_nframes_t first_mtc_frame;
+ jack_nframes_t first_mtc_time;
+
+ static const int32_t accumulator_size = 128;
+ float accumulator[accumulator_size];
+ int32_t accumulator_index;
+ bool have_first_accumulated_speed;
+
+ void reset ();
+ void update_mtc_qtr (MIDI::Parser&);
+ void update_mtc_time (const MIDI::byte *, bool);
+ void update_mtc_status (MIDI::Parser::MTC_Status);
+ void read_current (SafeTime *) const;
+};
+
+class ADAT_Slave : public Slave
+{
+ public:
+ ADAT_Slave () {}
+ ~ADAT_Slave () {}
+
+ bool speed_and_position (float& speed, jack_nframes_t& pos) {
+ speed = 0;
+ pos = 0;
+ return false;
+ }
+
+ bool locked() const { return false; }
+ bool ok() const { return false; }
+ jack_nframes_t resolution() const { return 1; }
+ bool requires_seekahead () const { return true; }
+};
+
+class JACK_Slave : public Slave
+{
+ public:
+ JACK_Slave (jack_client_t*);
+ ~JACK_Slave ();
+
+ bool speed_and_position (float& speed, jack_nframes_t& pos);
+
+ bool starting() const { return _starting; }
+ bool locked() const;
+ bool ok() const;
+ jack_nframes_t resolution() const { return 1; }
+ bool requires_seekahead () const { return false; }
+
+ private:
+ jack_client_t* jack;
+ float speed;
+ bool _starting;
+};
+
+} /* namespace */
+
+#endif /* __ardour_slave_h__ */
diff --git a/libs/ardour/ardour/sndfile_helpers.h b/libs/ardour/ardour/sndfile_helpers.h
new file mode 100644
index 0000000000..5bb4937410
--- /dev/null
+++ b/libs/ardour/ardour/sndfile_helpers.h
@@ -0,0 +1,37 @@
+#ifndef __sndfile_helpers_h__
+#define __sndfile_helpers_h__
+
+#include <string>
+#include <sndfile.h>
+
+using std::string;
+
+// Use this define when initializing arrarys for use in sndfile_*_format()
+#define SNDFILE_STR_LENGTH 32
+
+#define SNDFILE_HEADER_FORMATS 7
+extern const char * const sndfile_header_formats_strings[SNDFILE_HEADER_FORMATS+1];
+
+extern int sndfile_header_formats[SNDFILE_HEADER_FORMATS];
+
+#define SNDFILE_BITDEPTH_FORMATS 5
+extern const char * const sndfile_bitdepth_formats_strings[SNDFILE_BITDEPTH_FORMATS+1];
+
+extern int sndfile_bitdepth_formats[SNDFILE_BITDEPTH_FORMATS];
+
+#define SNDFILE_ENDIAN_FORMATS 2
+extern const char * const sndfile_endian_formats_strings[SNDFILE_ENDIAN_FORMATS+1];
+
+extern int sndfile_endian_formats[SNDFILE_ENDIAN_FORMATS];
+
+int sndfile_bitdepth_format_from_string(string);
+int sndfile_header_format_from_string(string);
+int sndfile_endian_format_from_string(string);
+
+int sndfile_data_width (int format);
+
+// It'd be nice if libsndfile did this for us
+string sndfile_major_format(int);
+string sndfile_minor_format(int);
+
+#endif /* __sndfile_helpers_h__ */
diff --git a/libs/ardour/ardour/sndfilesource.h b/libs/ardour/ardour/sndfilesource.h
new file mode 100644
index 0000000000..7f9712b7ec
--- /dev/null
+++ b/libs/ardour/ardour/sndfilesource.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __playlist_snd_file_buffer_h__
+#define __playlist_snd_file_buffer_h__
+
+#include <sndfile.h>
+
+#include <ardour/source.h>
+
+namespace ARDOUR {
+
+class SndFileSource : public Source {
+ public:
+ SndFileSource (const string& path_plus_channel, bool build_peak = true);
+ SndFileSource (const XMLNode&);
+ ~SndFileSource ();
+
+ jack_nframes_t length() const { return _info.frames; }
+ jack_nframes_t read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const;
+ void mark_for_remove() {} // we never remove external sndfiles
+ string peak_path(string audio_path);
+ string old_peak_path(string audio_path);
+ string path() const { return _path; }
+
+ static void set_peak_dir (string dir) { peak_dir = dir; }
+
+ private:
+ static string peak_dir;
+
+ SNDFILE *sf;
+ SF_INFO _info;
+ uint16_t channel;
+ mutable float *tmpbuf;
+ mutable jack_nframes_t tmpbufsize;
+ mutable PBD::Lock _tmpbuf_lock;
+ string _path;
+
+ void init (const string &str, bool build_peak);
+ jack_nframes_t read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const;
+};
+
+}; /* namespace EDL */
+
+#endif /* __playlist_snd_file_buffer_h__ */
+
diff --git a/libs/ardour/ardour/soundseq.h b/libs/ardour/ardour/soundseq.h
new file mode 100644
index 0000000000..4a318e9750
--- /dev/null
+++ b/libs/ardour/ardour/soundseq.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#ifndef __soundseq_h__
+#define __soundseq_h__
+
+#include "edl.h"
+
+namespace ARDOUR {
+
+typedef gint16 peak_datum;
+
+struct peak_data_t {
+ peak_datum min;
+ peak_datum max;
+};
+
+const uint32_t frames_per_peak = 2048;
+
+class Sound : public EDL::Piece {
+ public:
+ int peak (peak_data_t& pk, uint32_t start, uint32_t cnt);
+ int read_peaks (peak_data_t *, uint32_t npeaks, uint32_t start, uint32_t cnt);
+ int build_peak (uint32_t first_frame, uint32_t cnt);
+};
+
+class SoundPlaylist : public EDL::Playlist {
+ public:
+ int read_peaks (peak_data_t *, uint32_t npeaks, uint32_t start, uint32_t cnt);
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __soundseq_h__ */
+
+
+
diff --git a/libs/ardour/ardour/source.h b/libs/ardour/ardour/source.h
new file mode 100644
index 0000000000..a6138f8239
--- /dev/null
+++ b/libs/ardour/ardour/source.h
@@ -0,0 +1,182 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_source_h__
+#define __ardour_source_h__
+
+#include <list>
+#include <vector>
+#include <string>
+
+#include <time.h>
+
+#include <sigc++/signal.h>
+
+#include <ardour/ardour.h>
+#include <ardour/stateful.h>
+#include <pbd/xml++.h>
+
+using std::list;
+using std::vector;
+using std::string;
+
+namespace ARDOUR {
+
+struct PeakData {
+ typedef Sample PeakDatum;
+
+ PeakDatum min;
+ PeakDatum max;
+};
+
+const jack_nframes_t frames_per_peak = 256;
+
+class Source : public Stateful, public sigc::trackable
+{
+ public:
+ Source (bool announce=true);
+ Source (const XMLNode&);
+ virtual ~Source ();
+
+ const string& name() const { return _name; }
+ ARDOUR::id_t id() const { return _id; }
+
+ /* returns the number of items in this `source' */
+
+ virtual jack_nframes_t length() const {
+ return _length;
+ }
+
+ virtual jack_nframes_t read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const {
+ return 0;
+ }
+
+ virtual jack_nframes_t write (Sample *src, jack_nframes_t cnt) {
+ return 0;
+ }
+
+ uint32_t use_cnt() const { return _use_cnt; }
+ void use ();
+ void release ();
+
+ virtual void mark_for_remove() = 0;
+ virtual void mark_streaming_write_completed () {}
+
+ time_t timestamp() const { return _timestamp; }
+ void stamp (time_t when) { _timestamp = when; }
+
+ void set_captured_for (string str) { _captured_for = str; }
+ string captured_for() const { return _captured_for; }
+
+ uint32_t read_data_count() const { return _read_data_count; }
+ uint32_t write_data_count() const { return _write_data_count; }
+
+ int read_peaks (PeakData *peaks, jack_nframes_t npeaks, jack_nframes_t start, jack_nframes_t cnt, double samples_per_unit) const;
+ int build_peaks ();
+ bool peaks_ready (sigc::slot<void>) const;
+
+ static sigc::signal<void,Source*> SourceCreated;
+
+ sigc::signal<void,Source *> GoingAway;
+ mutable sigc::signal<void> PeaksReady;
+ mutable sigc::signal<void,jack_nframes_t,jack_nframes_t> PeakRangeReady;
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&);
+
+
+ static int start_peak_thread ();
+ static void stop_peak_thread ();
+
+ static void set_build_missing_peakfiles (bool yn) {
+ _build_missing_peakfiles = yn;
+ }
+ static void set_build_peakfiles (bool yn) {
+ _build_peakfiles = yn;
+ }
+
+ protected:
+ static bool _build_missing_peakfiles;
+ static bool _build_peakfiles;
+
+ string _name;
+ uint32_t _use_cnt;
+ bool _peaks_built;
+ mutable PBD::Lock _lock;
+ jack_nframes_t _length;
+ bool next_peak_clear_should_notify;
+ string peakpath;
+ int peakfile; /* fd */
+ time_t _timestamp;
+ string _captured_for;
+
+ mutable uint32_t _read_data_count; // modified in read()
+ mutable uint32_t _write_data_count; // modified in write()
+
+ int initialize_peakfile (bool newfile, string path);
+ void build_peaks_from_scratch ();
+
+ int do_build_peak (jack_nframes_t, jack_nframes_t);
+ virtual jack_nframes_t read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const = 0;
+ virtual string peak_path(string audio_path) = 0;
+ virtual string old_peak_path(string audio_path) = 0;
+
+ static pthread_t peak_thread;
+ static bool have_peak_thread;
+ static void* peak_thread_work(void*);
+
+ static int peak_request_pipe[2];
+
+ struct PeakRequest {
+ enum Type {
+ Build,
+ Quit
+ };
+ };
+
+ static vector<Source*> pending_peak_sources;
+ static PBD::Lock pending_peak_sources_lock;
+
+ static void queue_for_peaks (Source&);
+ static void clear_queue_for_peaks ();
+
+ struct PeakBuildRecord {
+ jack_nframes_t frame;
+ jack_nframes_t cnt;
+
+ PeakBuildRecord (jack_nframes_t f, jack_nframes_t c)
+ : frame (f), cnt (c) {}
+ PeakBuildRecord (const PeakBuildRecord& other) {
+ frame = other.frame;
+ cnt = other.cnt;
+ }
+ };
+
+ list<Source::PeakBuildRecord *> pending_peak_builds;
+
+ private:
+ ARDOUR::id_t _id;
+
+ bool Source::file_changed (string path);
+};
+
+}
+
+#endif /* __ardour_source_h__ */
diff --git a/libs/ardour/ardour/spline.h b/libs/ardour/ardour/spline.h
new file mode 100644
index 0000000000..de1ece6edb
--- /dev/null
+++ b/libs/ardour/ardour/spline.h
@@ -0,0 +1,90 @@
+/* This code is based upon work that bore the legend:
+ *
+ * Copyright (C) 1997 David Mosberger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ardour_spline_h__
+#define __ardour_spline_h__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _spline Spline;
+typedef struct _spline_point SplinePoint;
+
+struct _spline_point
+{
+ float x;
+ float y;
+};
+
+Spline *spline_new (void);
+void spline_free (Spline *);
+
+void spline_set (Spline *, uint32_t n, SplinePoint *);
+void spline_add (Spline *, uint32_t n, SplinePoint *);
+void spline_solve (Spline *);
+float spline_eval (Spline *, float val);
+void spline_fill (Spline *, float x0, float x1, float *vec, uint32_t veclen);
+float spline_get_max_x (Spline *);
+float spline_get_min_x (Spline *);
+
+struct _spline
+{
+ float *deriv2;
+ float *x;
+ float *y;
+ float max_x;
+ float min_x;
+ SplinePoint *points;
+ uint32_t npoints;
+ uint32_t space;
+
+#ifdef __cplusplus
+
+ void set (uint32_t n, SplinePoint *points) {
+ spline_set (this, n, points);
+ }
+
+ void add (uint32_t n, SplinePoint *points) {
+ spline_add (this, n, points);
+ }
+
+ void solve () {
+ spline_solve (this);
+ }
+
+ float eval (float val) {
+ return spline_eval (this, val);
+ }
+
+ void fill (float x0, float x1, float *vec, uint32_t veclen) {
+ spline_fill (this, x0, x1, vec, veclen);
+ }
+
+#endif /* __cplusplus */
+
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ardour_spline_h__ */
diff --git a/libs/ardour/ardour/state_manager.h b/libs/ardour/ardour/state_manager.h
new file mode 100644
index 0000000000..9dc2ea66ad
--- /dev/null
+++ b/libs/ardour/ardour/state_manager.h
@@ -0,0 +1,48 @@
+#ifndef __ardour_state_manager_h__
+#define __ardour_state_manager_h__
+
+#include <list>
+#include <string>
+
+#include <sigc++/signal.h>
+
+#include <ardour/ardour.h>
+
+namespace ARDOUR {
+
+typedef uint32_t state_id_t;
+
+
+ class StateManager : virtual public sigc::trackable
+{
+ public:
+ struct State {
+ std::string operation;
+ State (std::string why) : operation (why) {}
+ virtual ~State() {}
+ };
+
+ typedef std::list<State*> StateMap;
+
+ StateManager ();
+ virtual ~StateManager ();
+
+ virtual void drop_all_states ();
+ virtual void use_state (state_id_t);
+ virtual void save_state (std::string why);
+
+ sigc::signal<void,Change> StateChanged;
+
+ state_id_t _current_state_id;
+
+ protected:
+ StateMap states;
+
+ virtual Change restore_state (State&) = 0;
+ virtual State* state_factory (std::string why) const = 0;
+ virtual void send_state_changed (Change);
+};
+
+} // namespace ARDOUR
+
+#endif /* __ardour_state_manager_h__ */
diff --git a/libs/ardour/ardour/stateful.h b/libs/ardour/ardour/stateful.h
new file mode 100644
index 0000000000..4f4cb20b39
--- /dev/null
+++ b/libs/ardour/ardour/stateful.h
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_stateful_h__
+#define __ardour_stateful_h__
+
+#include <string>
+
+class XMLNode;
+
+class Stateful {
+ public:
+ Stateful();
+ virtual ~Stateful();
+
+ virtual XMLNode& get_state (void) = 0;
+
+ virtual int set_state (const XMLNode&) = 0;
+
+ /* Extra XML nodes */
+
+ void add_extra_xml (XMLNode&);
+ XMLNode *extra_xml (const std::string& str);
+
+ virtual void add_instant_xml (XMLNode&, const std::string& dir);
+ XMLNode *instant_xml (const std::string& str, const std::string& dir);
+
+ protected:
+ XMLNode *_extra_xml;
+ XMLNode *_instant_xml;
+};
+
+#endif /* __ardour_stateful_h__ */
+
diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h
new file mode 100644
index 0000000000..2f04f603e7
--- /dev/null
+++ b/libs/ardour/ardour/tempo.h
@@ -0,0 +1,323 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_tempo_h__
+#define __ardour_tempo_h__
+
+#include <list>
+#include <string>
+#include <vector>
+#include <cmath>
+#include <pthread.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/undo.h>
+#include <sigc++/signal.h>
+
+#include <ardour/ardour.h>
+#include <ardour/stateful.h>
+#include <ardour/state_manager.h>
+
+class XMLNode;
+
+using std::list;
+using std::vector;
+
+namespace ARDOUR {
+
+class Tempo {
+ public:
+ Tempo (double bpm)
+ : _beats_per_minute (bpm) {}
+ Tempo (const Tempo& other) {
+ _beats_per_minute = other._beats_per_minute;
+ }
+ void operator= (const Tempo& other) {
+ if (&other != this) {
+ _beats_per_minute = other._beats_per_minute;
+ }
+ }
+
+ double beats_per_minute () const { return _beats_per_minute; }
+ double frames_per_beat (jack_nframes_t sr) const {
+ return ((60.0 * sr) / _beats_per_minute);
+ }
+
+ protected:
+ double _beats_per_minute;
+};
+
+class Meter {
+ public:
+ static const double ticks_per_beat;
+
+ Meter (double bpb, double bt)
+ : _beats_per_bar (bpb), _note_type (bt) {}
+ Meter (const Meter& other) {
+ _beats_per_bar = other._beats_per_bar;
+ _note_type = other._note_type;
+ }
+ void operator= (const Meter& other) {
+ if (&other != this) {
+ _beats_per_bar = other._beats_per_bar;
+ _note_type = other._note_type;
+ }
+ }
+
+ double beats_per_bar () const { return _beats_per_bar; }
+ double note_divisor() const { return _note_type; }
+
+ double frames_per_bar (const Tempo&, jack_nframes_t sr) const;
+
+ protected:
+
+ /* this is the number of beats in a bar. it is a real value
+ because there are musical traditions on our planet
+ that do not limit themselves to integral numbers of beats
+ per bar.
+ */
+
+ double _beats_per_bar;
+
+ /* this is the type of "note" that a beat represents. for example,
+ 4.0 would be a quarter (crotchet) note, 8.0 would be an eighth
+ (quaver) note, etc.
+ */
+
+ double _note_type;
+};
+
+class MetricSection {
+ public:
+ MetricSection (const BBT_Time& start)
+ : _start (start), _frame (0), _movable (true) {}
+ virtual ~MetricSection() {}
+
+ const BBT_Time& start() const { return _start; }
+ const jack_nframes_t frame() const { return _frame; }
+
+ void set_movable (bool yn) { _movable = yn; }
+ bool movable() const { return _movable; }
+
+ virtual void set_frame (jack_nframes_t f) {
+ _frame = f;
+ };
+
+ virtual void set_start (const BBT_Time& w) {
+ _start = w;
+ }
+
+ /* MeterSections are not stateful in the full sense,
+ but we do want them to control their own
+ XML state information.
+ */
+
+ virtual XMLNode& get_state() const = 0;
+
+ private:
+ BBT_Time _start;
+ jack_nframes_t _frame;
+ bool _movable;
+};
+
+class MeterSection : public MetricSection, public Meter {
+ public:
+ MeterSection (const BBT_Time& start, double bpb, double note_type)
+ : MetricSection (start), Meter (bpb, note_type) {}
+ MeterSection (const XMLNode&);
+
+ static const string xml_state_node_name;
+
+ XMLNode& get_state() const;
+};
+
+class TempoSection : public MetricSection, public Tempo {
+ public:
+ TempoSection (const BBT_Time& start, double qpm)
+ : MetricSection (start), Tempo (qpm) {}
+ TempoSection (const XMLNode&);
+
+ static const string xml_state_node_name;
+
+ XMLNode& get_state() const;
+};
+
+typedef list<MetricSection*> Metrics;
+
+class TempoMapState : public StateManager::State {
+ public:
+ TempoMapState (std::string why)
+ : StateManager::State (why) {
+ metrics = new Metrics;
+ }
+
+ Metrics *metrics;
+};
+
+class TempoMap : public Stateful, public StateManager {
+ public:
+
+ TempoMap (jack_nframes_t frame_rate);
+ ~TempoMap();
+
+ /* measure-based stuff */
+
+ enum BBTPointType {
+ Bar,
+ Beat,
+ };
+
+ struct BBTPoint {
+ BBTPointType type;
+ jack_nframes_t frame;
+ const Meter* meter;
+ const Tempo* tempo;
+ uint32_t bar;
+ uint32_t beat;
+
+ BBTPoint (const Meter& m, const Tempo& t, jack_nframes_t f, BBTPointType ty, uint32_t b, uint32_t e)
+ : type (ty), frame (f), meter (&m), tempo (&t), bar (b), beat (e) {}
+ };
+
+ typedef vector<BBTPoint> BBTPointList;
+
+ template<class T> void apply_with_metrics (T& obj, void (T::*method)(const Metrics&)) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ (obj.*method)(*metrics);
+ }
+
+ BBTPointList *get_points (jack_nframes_t start, jack_nframes_t end) const;
+
+ void bbt_time (jack_nframes_t when, BBT_Time&) const;
+ jack_nframes_t frame_time (const BBT_Time&) const;
+ jack_nframes_t bbt_duration_at (jack_nframes_t, const BBT_Time&, int dir) const;
+
+ static const Tempo& default_tempo() { return _default_tempo; }
+ static const Meter& default_meter() { return _default_meter; }
+
+ const Tempo& tempo_at (jack_nframes_t);
+ const Meter& meter_at (jack_nframes_t);
+
+ void add_tempo(const Tempo&, BBT_Time where);
+ void add_meter(const Meter&, BBT_Time where);
+
+ void move_tempo (TempoSection&, const BBT_Time& to);
+ void move_meter (MeterSection&, const BBT_Time& to);
+
+ void remove_tempo(const TempoSection&);
+ void remove_meter(const MeterSection&);
+
+ void replace_tempo (TempoSection& existing, const Tempo& replacement);
+ void replace_meter (MeterSection& existing, const Meter& replacement);
+
+
+ jack_nframes_t round_to_bar (jack_nframes_t frame, int dir);
+
+ jack_nframes_t round_to_beat (jack_nframes_t frame, int dir);
+
+ jack_nframes_t round_to_beat_subdivision (jack_nframes_t fr, int sub_num);
+
+ jack_nframes_t round_to_tick (jack_nframes_t frame, int dir);
+
+ void set_length (jack_nframes_t frames);
+
+ XMLNode& get_state (void);
+ int set_state (const XMLNode&);
+
+ void dump (std::ostream&) const;
+ void clear ();
+
+ UndoAction get_memento() const;
+
+ /* this is a helper class that we use to be able to keep
+ track of which meter *AND* tempo are in effect at
+ a given point in time.
+ */
+
+ class Metric {
+ public:
+ Metric (const Meter& m, const Tempo& t) : _meter (&m), _tempo (&t), _frame (0) {}
+
+ void set_tempo (const Tempo& t) { _tempo = &t; }
+ void set_meter (const Meter& m) { _meter = &m; }
+ void set_frame (jack_nframes_t f) { _frame = f; }
+ void set_start (const BBT_Time& t) { _start = t; }
+
+ const Meter& meter() const { return *_meter; }
+ const Tempo& tempo() const { return *_tempo; }
+ jack_nframes_t frame() const { return _frame; }
+ const BBT_Time& start() const { return _start; }
+
+ private:
+ const Meter* _meter;
+ const Tempo* _tempo;
+ jack_nframes_t _frame;
+ BBT_Time _start;
+
+ };
+
+ Metric metric_at (BBT_Time bbt) const;
+ Metric metric_at (jack_nframes_t) const;
+ void bbt_time_with_metric (jack_nframes_t, BBT_Time&, const Metric&) const;
+
+ private:
+ static Tempo _default_tempo;
+ static Meter _default_meter;
+
+ Metrics *metrics;
+ jack_nframes_t _frame_rate;
+ jack_nframes_t last_bbt_when;
+ bool last_bbt_valid;
+ BBT_Time last_bbt;
+ mutable PBD::Lock lock;
+
+ void timestamp_metrics ();
+
+
+ jack_nframes_t round_to_type (jack_nframes_t fr, int dir, BBTPointType);
+
+ jack_nframes_t frame_time_unlocked (const BBT_Time&) const;
+
+ void bbt_time_unlocked (jack_nframes_t, BBT_Time&) const;
+
+ jack_nframes_t bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const;
+
+ const MeterSection& first_meter() const;
+ const TempoSection& first_tempo() const;
+
+ jack_nframes_t count_frames_between (const BBT_Time&, const BBT_Time&) const;
+ jack_nframes_t count_frames_between_metrics (const Meter&, const Tempo&, const BBT_Time&, const BBT_Time&) const;
+
+ int move_metric_section (MetricSection&, const BBT_Time& to);
+ void do_insert (MetricSection* section);
+
+ Change restore_state (StateManager::State&);
+ StateManager::State* state_factory (std::string why) const;
+
+ bool in_set_state;
+
+ /* override state_manager::save_state so we can check in_set_state */
+
+ void save_state (std::string why);
+
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_tempo_h__ */
diff --git a/libs/ardour/ardour/timestamps.h b/libs/ardour/ardour/timestamps.h
new file mode 100644
index 0000000000..4ebc28b2f9
--- /dev/null
+++ b/libs/ardour/ardour/timestamps.h
@@ -0,0 +1,13 @@
+#ifndef __ardour_timestamps_h__
+#define __ardour_timestamps_h__
+
+#ifdef WITH_JACK_TIMESTAMPS
+#include <jack/timestamps.h>
+#else
+#define jack_timestamp(s)
+#define jack_init_timestamps(n)
+#define jack_dump_timestamps(o)
+#define jack_reset_timestamps()
+#endif
+
+#endif /* __ardour_timestamps_h__ */
diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h
new file mode 100644
index 0000000000..95f3c471f2
--- /dev/null
+++ b/libs/ardour/ardour/types.h
@@ -0,0 +1,243 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_types_h__
+#define __ardour_types_h__
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS /* PRI<foo>; C++ requires explicit requesting of these */
+#endif
+
+#include <inttypes.h>
+#include <jack/types.h>
+#include <map>
+
+#if __GNUC__ < 3
+
+typedef int intptr_t;
+#endif
+
+namespace ARDOUR {
+
+ class Source;
+
+ typedef jack_default_audio_sample_t Sample;
+ typedef float pan_t;
+ typedef float gain_t;
+ typedef uint32_t layer_t;
+ typedef uint64_t id_t;
+
+ enum IOChange {
+ NoChange = 0,
+ ConfigurationChanged = 0x1,
+ ConnectionsChanged = 0x2
+ };
+
+ enum OverlapType {
+ OverlapNone, // no overlap
+ OverlapInternal, // the overlap is 100% with the object
+ OverlapStart, // overlap covers start, but ends within
+ OverlapEnd, // overlap begins within and covers end
+ OverlapExternal // overlap extends to (at least) begin+end
+ };
+
+ OverlapType coverage (jack_nframes_t start_a, jack_nframes_t end_a,
+ jack_nframes_t start_b, jack_nframes_t end_b);
+
+ enum AutomationType {
+ GainAutomation = 0x1,
+ PanAutomation = 0x2,
+ PluginAutomation = 0x4,
+ SoloAutomation = 0x8,
+ MuteAutomation = 0x10,
+ };
+
+ enum AutoState {
+ Off = 0x0,
+ Write = 0x1,
+ Touch = 0x2,
+ Play = 0x4
+ };
+
+ enum AutoStyle {
+ Absolute = 0x1,
+ Trim = 0x2
+ };
+
+ enum AlignStyle {
+ CaptureTime,
+ ExistingMaterial
+ };
+
+ enum MeterPoint {
+ MeterInput,
+ MeterPreFader,
+ MeterPostFader
+ };
+
+ enum smpte_wrap_t {
+ smpte_wrap_none = 0,
+ smpte_wrap_frames,
+ smpte_wrap_seconds,
+ smpte_wrap_minutes,
+ smpte_wrap_hours
+ };
+
+ struct SMPTE_Time {
+ bool negative;
+ long hours;
+ long minutes;
+ long seconds;
+ long frames;
+ long subframes; // mostly not used
+
+ SMPTE_Time() {
+ negative = false;
+ hours = 0;
+ minutes = 0;
+ seconds = 0;
+ frames = 0;
+ subframes = 0;
+ }
+
+ };
+
+ struct BBT_Time {
+ uint32_t bars;
+ uint32_t beats;
+ uint32_t ticks;
+
+ BBT_Time() {
+ bars = 1;
+ beats = 1;
+ ticks = 0;
+ }
+
+ /* we can't define arithmetic operators for BBT_Time, because
+ the results depend on a TempoMap, but we can define
+ a useful check on the less-than condition.
+ */
+
+ bool operator< (const BBT_Time& other) const {
+ return bars < other.bars ||
+ (bars == other.bars && beats < other.beats) ||
+ (bars == other.bars && beats == other.beats && ticks < other.ticks);
+ }
+
+ bool operator== (const BBT_Time& other) const {
+ return bars == other.bars && beats == other.beats && ticks == other.ticks;
+ }
+
+ };
+
+ struct AnyTime {
+ enum Type {
+ SMPTE,
+ BBT,
+ Frames,
+ Seconds
+ };
+
+ Type type;
+
+ SMPTE_Time smpte;
+ BBT_Time bbt;
+
+ union {
+ jack_nframes_t frames;
+ double seconds;
+ };
+ };
+
+ struct AudioRange {
+ jack_nframes_t start;
+ jack_nframes_t end;
+ uint32_t id;
+
+ AudioRange (jack_nframes_t s, jack_nframes_t e, uint32_t i) : start (s), end (e) , id (i) {}
+
+ jack_nframes_t length() { return end - start + 1; }
+
+ bool operator== (const AudioRange& other) const {
+ return start == other.start && end == other.end && id == other.id;
+ }
+
+ bool equal (const AudioRange& other) const {
+ return start == other.start && end == other.end;
+ }
+
+ OverlapType coverage (jack_nframes_t s, jack_nframes_t e) const {
+ return ARDOUR::coverage (start, end, s, e);
+ }
+ };
+
+ struct MusicRange {
+ BBT_Time start;
+ BBT_Time end;
+ uint32_t id;
+
+ MusicRange (BBT_Time& s, BBT_Time& e, uint32_t i)
+ : start (s), end (e), id (i) {}
+
+ bool operator== (const MusicRange& other) const {
+ return start == other.start && end == other.end && id == other.id;
+ }
+
+ bool equal (const MusicRange& other) const {
+ return start == other.start && end == other.end;
+ }
+ };
+
+ enum EditMode {
+ Slide,
+ Splice,
+ };
+
+ enum RegionPoint {
+ Start,
+ End,
+ SyncPoint
+ };
+
+ enum Change {
+ range_guarantee = ~0
+ };
+
+
+ enum Placement {
+ PreFader,
+ PostFader
+ };
+
+ enum CrossfadeModel {
+ FullCrossfade,
+ ShortCrossfade
+ };
+
+ struct InterThreadInfo {
+ volatile bool done;
+ volatile bool cancel;
+ volatile float progress;
+ pthread_t thread;
+ };
+};
+
+#endif /* __ardour_types_h__ */
+
diff --git a/libs/ardour/ardour/utils.h b/libs/ardour/ardour/utils.h
new file mode 100644
index 0000000000..36ee6f105f
--- /dev/null
+++ b/libs/ardour/ardour/utils.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 1999 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_utils_h__
+#define __ardour_utils_h__
+
+#include <iostream>
+#include <string>
+#include <cmath>
+
+#include "ardour.h"
+
+class XMLNode;
+
+using std::ostream;
+
+void elapsed_time_to_str (char *buf, uint32_t seconds);
+string legalize_for_path (std::string str);
+ostream& operator<< (ostream& o, const ARDOUR::BBT_Time& bbt);
+XMLNode* find_named_node (const XMLNode& node, std::string name);
+string placement_as_string (ARDOUR::Placement);
+
+static inline float f_max(float x, float a) {
+ x -= a;
+ x += fabsf (x);
+ x *= 0.5f;
+ x += a;
+
+ return (x);
+}
+
+int cmp_nocase (const std::string& s, const std::string& s2);
+
+int tokenize_fullpath (string fullpath, string& path, string& name);
+
+int touch_file(string path);
+
+uint32_t long get_uid();
+
+string region_name_from_path (string path);
+
+#endif /* __ardour_utils_h__ */
diff --git a/libs/ardour/ardour/vst_plugin.h b/libs/ardour/ardour/vst_plugin.h
new file mode 100644
index 0000000000..6ae64ab50c
--- /dev/null
+++ b/libs/ardour/ardour/vst_plugin.h
@@ -0,0 +1,112 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#ifndef __ardour_vst_plugin_h__
+#define __ardour_vst_plugin_h__
+
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+#include <string>
+#include <dlfcn.h>
+
+#include <midi++/controllable.h>
+#include <sigc++/signal.h>
+
+#include <jack/types.h>
+#include <ardour/stateful.h>
+#include <ardour/plugin_state.h>
+#include <ardour/plugin.h>
+#include <ardour/vst_plugin.h>
+
+using std::string;
+using std::vector;
+using std::list;
+using std::map;
+
+struct _FSTHandle;
+struct _FST;
+typedef struct _FSTHandle FSTHandle;
+typedef struct _FST FST;
+class AEffect;
+
+namespace ARDOUR {
+class AudioEngine;
+class Session;
+
+class VSTPlugin : public ARDOUR::Plugin
+{
+ public:
+ VSTPlugin (ARDOUR::AudioEngine&, ARDOUR::Session&, FSTHandle* handle);
+ VSTPlugin (const VSTPlugin &);
+ ~VSTPlugin ();
+
+ /* Plugin interface */
+
+ uint32_t unique_id() const;
+ const char * label() const;
+ const char * name() const;
+ const char * maker() const;
+ uint32_t parameter_count() const;
+ float default_value (uint32_t port);
+ jack_nframes_t latency() const;
+ void set_parameter (uint32_t port, float val);
+ float get_parameter (uint32_t port) const;
+ int get_parameter_descriptor (uint32_t which, ParameterDescriptor&) const;
+ std::set<uint32_t> automatable() const;
+ uint32_t nth_parameter (uint32_t port, bool& ok) const;
+ void activate ();
+ void deactivate ();
+ void set_block_size (jack_nframes_t nframes);
+ int connect_and_run (vector<Sample*>& bufs, uint32_t maxbuf, int32_t& in, int32_t& out, jack_nframes_t nframes, jack_nframes_t offset);
+ void store_state (ARDOUR::PluginState&);
+ void restore_state (ARDOUR::PluginState&);
+ string describe_parameter (uint32_t);
+ string state_node_name() const { return "vst"; }
+ void print_parameter (uint32_t, char*, uint32_t len) const;
+
+ bool parameter_is_audio(uint32_t i) const { return false; }
+ bool parameter_is_control(uint32_t i) const { return true; }
+ bool parameter_is_input(uint32_t i) const { return true; }
+ bool parameter_is_output(uint32_t i) const { return false; }
+
+ bool load_preset (const string preset_label );
+ bool save_preset(string name);
+
+ bool has_editor () const;
+
+ XMLNode& get_state();
+ int set_state(const XMLNode& node);
+
+ AEffect* plugin() const { return _plugin; }
+ FST* fst() const { return _fst; }
+
+
+ private:
+ FSTHandle* handle;
+ FST* _fst;
+ AEffect* _plugin;
+ bool been_resumed;
+};
+
+}
+
+#endif /* __ardour_vst_plugin_h__ */
diff --git a/libs/ardour/audio_library.cc b/libs/ardour/audio_library.cc
new file mode 100644
index 0000000000..e9fd0111b9
--- /dev/null
+++ b/libs/ardour/audio_library.cc
@@ -0,0 +1,513 @@
+/*
+ Copyright (C) 2003 Paul Davis
+ Author: Taybin Rutkin
+
+ 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.
+
+ $Id$
+*/
+
+#include <cstdio> // Needed so that libraptor (included in lrdf) won't complain
+#include <iostream>
+#include <sstream>
+#include <cctype>
+
+#include <lrdf.h>
+
+#include <pbd/compose.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/audio_library.h>
+#include <ardour/utils.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+namespace std {
+struct UriSorter {
+ bool operator() (string a, string b) const {
+ return cmp_nocase(Library->get_label(a), Library->get_label(b)) == -1;
+ }
+};
+};
+
+static char* GROUP = "http://ardour.org/ontology/Group";
+static char* SOUNDFILE = "http://ardour.org/ontology/Soundfile";
+static char* hasFile = "http://ardour.org/ontology/hasFile";
+static char* memberOf = "http://ardour.org/ontology/memberOf";
+static char* subGroupOf = "http://ardour.org/ontology/subGroupOf";
+
+AudioLibrary::AudioLibrary ()
+{
+ src = "file:" + Config->get_user_ardour_path() + "sfdb";
+
+ // workaround for possible bug in raptor that crashes when saving to a
+ // non-existant file.
+ touch_file(Config->get_user_ardour_path() + "sfdb");
+
+ lrdf_read_file(src.c_str());
+
+ lrdf_statement pattern;
+
+ pattern.subject = GROUP;
+ pattern.predicate = RDF_TYPE;
+ pattern.object = RDFS_CLASS;
+ pattern.object_type = lrdf_uri;
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+
+ // if empty DB, create basic schema
+ if (matches == 0) {
+ initialize_db ();
+ save_changes();
+ }
+
+ lrdf_free_statements(matches);
+}
+
+AudioLibrary::~AudioLibrary ()
+{
+}
+
+void
+AudioLibrary::initialize_db ()
+{
+ // define ardour:Group
+ lrdf_add_triple(src.c_str(), GROUP, RDF_TYPE, RDFS_CLASS, lrdf_uri);
+ // define ardour:Soundfile
+ lrdf_add_triple(src.c_str(), SOUNDFILE, RDF_TYPE, RDFS_CLASS, lrdf_uri);
+
+ // add intergral fields
+ add_field("channels");
+ add_field("samplerate");
+ add_field("resolution");
+ add_field("format");
+}
+
+void
+AudioLibrary::save_changes ()
+{
+ if (lrdf_export_by_source(src.c_str(), src.substr(5).c_str())) {
+ warning << compose(_("Could not open %1. Audio Library not saved"), src) << endmsg;
+ }
+}
+
+string
+AudioLibrary::add_group (string group, string parent_uri)
+{
+ string local_group(compose("file:sfbd/group/%1", get_uid()));
+
+ lrdf_add_triple(src.c_str(), local_group.c_str(),
+ RDFS_BASE "label", group.c_str(), lrdf_literal);
+
+ if (parent_uri == ""){
+ lrdf_add_triple(src.c_str(), local_group.c_str(),
+ subGroupOf, GROUP, lrdf_uri);
+ } else {
+ lrdf_add_triple(src.c_str(), local_group.c_str(),
+ subGroupOf, parent_uri.c_str(), lrdf_uri);
+ }
+
+ added_group(local_group, parent_uri); /* EMIT SIGNAL */
+
+ return local_group;
+}
+
+void
+AudioLibrary::remove_group (string uri)
+{
+ list<string> items;
+ list<string>::iterator i;
+
+ get_members(items, uri);
+ for (i = items.begin(); i != items.end(); ++i) {
+ remove_member(*i);
+ }
+
+ items.clear();
+
+ get_groups(items, uri);
+ for (i = items.begin(); i != items.end(); ++i) {
+ remove_group(*i);
+ }
+
+ lrdf_remove_uri_matches(uri.c_str());
+ save_changes ();
+
+ removed_group(uri); /* EMIT SIGNAL */
+}
+
+void
+AudioLibrary::get_groups (list<string>& groups, string parent_uri)
+{
+ lrdf_statement pattern;
+
+ pattern.subject = 0;
+ pattern.predicate = subGroupOf;
+ if (parent_uri == ""){
+ pattern.object = strdup(GROUP);
+ } else {
+ pattern.object = strdup(parent_uri.c_str());
+ }
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+
+ lrdf_statement* current = matches;
+ while (current != 0) {
+ groups.push_back(current->subject);
+ current = current->next;
+ }
+
+ lrdf_free_statements(matches);
+ free (pattern.object);
+
+ UriSorter cmp;
+ groups.sort(cmp);
+ groups.unique();
+}
+
+string
+AudioLibrary::add_member (string member, string parent_uri)
+{
+ string local_member(compose("file:sfdb/soundfile/%1", get_uid()));
+ string file_uri(compose("file:%1", member));
+
+ lrdf_add_triple(src.c_str(), local_member.c_str(), RDF_TYPE,
+ SOUNDFILE, lrdf_uri);
+ lrdf_add_triple(src.c_str(), local_member.c_str(), hasFile,
+ file_uri.c_str(), lrdf_uri);
+
+ string::size_type size = member.find_last_of('/');
+ string label = member.substr(++size);
+
+ lrdf_add_triple(src.c_str(), local_member.c_str(), RDFS_BASE "label",
+ label.c_str(), lrdf_literal);
+
+ if (parent_uri == ""){
+ lrdf_add_triple(src.c_str(), local_member.c_str(), memberOf,
+ GROUP, lrdf_uri);
+ } else {
+ lrdf_add_triple(src.c_str(), local_member.c_str(), memberOf,
+ parent_uri.c_str(), lrdf_uri);
+ }
+
+ save_changes ();
+
+ added_member (local_member, parent_uri); /* EMIT SIGNAL */
+
+ return local_member;
+}
+
+void
+AudioLibrary::remove_member (string uri)
+{
+ lrdf_remove_uri_matches (uri.c_str());
+
+ save_changes ();
+
+ removed_member(uri); /* EMIT SIGNAL */
+}
+
+void
+AudioLibrary::get_members (list<string>& members, string parent_uri)
+{
+ lrdf_statement pattern;
+
+ pattern.subject = 0;
+ pattern.predicate = memberOf;
+ if (parent_uri == ""){
+ pattern.object = strdup(GROUP);
+ } else {
+ pattern.object = strdup(parent_uri.c_str());
+ }
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+
+ lrdf_statement* current = matches;
+ while (current != 0) {
+ members.push_back(current->subject);
+ current = current->next;
+ }
+
+ lrdf_free_statements(matches);
+ free (pattern.object);
+
+ UriSorter cmp;
+ members.sort(cmp);
+ members.unique();
+}
+
+void
+AudioLibrary::search_members_and (list<string>& members,
+ const map<string,string>& fields)
+{
+ lrdf_statement **head;
+ lrdf_statement* pattern = 0;
+ lrdf_statement* old = 0;
+ head = &pattern;
+
+ map<string,string>::const_iterator i;
+ for (i = fields.begin(); i != fields.end(); ++i){
+ pattern = new lrdf_statement;
+ pattern->subject = "?";
+ pattern->predicate = strdup(field_uri(i->first).c_str());
+ pattern->object = strdup((i->second).c_str());
+ pattern->next = old;
+
+ old = pattern;
+ }
+
+ if (*head != 0) {
+ lrdf_uris* ulist = lrdf_match_multi(*head);
+ for (uint32_t j = 0; ulist && j < ulist->count; ++j) {
+// printf("AND: %s\n", ulist->items[j]);
+ members.push_back(ulist->items[j]);
+ }
+ lrdf_free_uris(ulist);
+
+ UriSorter cmp;
+ members.sort(cmp);
+ members.unique();
+ }
+
+ // memory clean up
+ pattern = *head;
+ while(pattern){
+ free(pattern->predicate);
+ free(pattern->object);
+ old = pattern;
+ pattern = pattern->next;
+ delete old;
+ }
+}
+
+void
+AudioLibrary::search_members_or (list<string>& members,
+ const map<string,string>& fields)
+{
+ map<string,string>::const_iterator i;
+
+ lrdf_statement pattern;
+ for (i = fields.begin(); i != fields.end(); ++i) {
+ pattern.subject = 0;
+ pattern.predicate = strdup(field_uri(i->first).c_str());
+ pattern.object = strdup((i->second).c_str());
+ pattern.object_type = lrdf_literal;
+
+ lrdf_statement* matched = lrdf_matches(&pattern);
+
+ lrdf_statement* old = matched;
+ while(matched) {
+// printf ("OR: %s\n", matched->subject);
+ members.push_back(matched->subject);
+ matched = matched->next;
+ }
+
+ free(pattern.predicate);
+ free(pattern.object);
+ lrdf_free_statements (old);
+ }
+
+ UriSorter cmp;
+ members.sort(cmp);
+ members.unique();
+}
+
+string
+AudioLibrary::get_member_filename (string uri)
+{
+ lrdf_statement pattern;
+ pattern.subject = strdup(uri.c_str());
+ pattern.predicate = hasFile;
+ pattern.object = 0;
+ pattern.object_type = lrdf_uri;
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+ if (matches) {
+ string file = matches->object;
+ lrdf_free_statements(matches);
+
+ string::size_type pos = file.find(":");
+ return file.substr(++pos);
+ } else {
+ warning << _("Could not find member filename") << endmsg;
+ return "-Unknown-";
+ }
+}
+
+void
+AudioLibrary::add_field (string name)
+{
+ string local_field = field_uri(name);
+ lrdf_statement pattern;
+ pattern.subject = strdup(local_field.c_str());
+ pattern.predicate = RDF_TYPE;
+ pattern.object = RDF_BASE "Property";
+ pattern.object_type = lrdf_uri;
+
+ if(lrdf_exists_match(&pattern)) {
+ return;
+ }
+
+ // of type rdf:Property
+ lrdf_add_triple(src.c_str(), local_field.c_str(), RDF_TYPE,
+ RDF_BASE "Property", lrdf_uri);
+ // of range ardour:Soundfile
+ lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "range",
+ SOUNDFILE, lrdf_uri);
+ // of domain rdf:Literal
+ lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "domain",
+ RDF_BASE "Literal", lrdf_uri);
+
+ set_label (local_field, name);
+
+ save_changes();
+
+ fields_changed(); /* EMIT SIGNAL */
+}
+
+void
+AudioLibrary::get_fields (list<string>& fields)
+{
+ lrdf_statement pattern;
+
+ pattern.subject = 0;
+ pattern.predicate = RDFS_BASE "range";
+ pattern.object = SOUNDFILE;
+ pattern.object_type = lrdf_uri;
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+
+ lrdf_statement* current = matches;
+ while (current != 0) {
+ fields.push_back(get_label(current->subject));
+
+ current = current->next;
+ }
+
+ lrdf_free_statements(matches);
+
+ fields.sort();
+ fields.unique();
+}
+
+void
+AudioLibrary::remove_field (string name)
+{
+ lrdf_remove_uri_matches(field_uri(name).c_str());
+ save_changes();
+ fields_changed (); /* EMIT SIGNAL */
+}
+
+string
+AudioLibrary::get_field (string uri, string field)
+{
+ lrdf_statement pattern;
+
+ pattern.subject = strdup(uri.c_str());
+
+ pattern.predicate = strdup(field_uri(field).c_str());
+
+ pattern.object = 0;
+ pattern.object_type = lrdf_literal;
+
+ lrdf_statement* matches = lrdf_matches(&pattern);
+ free(pattern.subject);
+ free(pattern.predicate);
+
+ stringstream object;
+ if (matches != 0){
+ object << matches->object;
+ }
+
+ lrdf_free_statements(matches);
+ return object.str();
+}
+
+void
+AudioLibrary::set_field (string uri, string field, string literal)
+{
+ lrdf_statement pattern;
+
+ pattern.subject = strdup(uri.c_str());
+
+ string local_field = field_uri(field);
+ pattern.predicate = strdup(local_field.c_str());
+
+ pattern.object = 0;
+ pattern.object_type = lrdf_literal;
+
+ lrdf_remove_matches(&pattern);
+ free(pattern.subject);
+ free(pattern.predicate);
+
+ lrdf_add_triple(src.c_str(), uri.c_str(), local_field.c_str(),
+ literal.c_str(), lrdf_literal);
+
+ save_changes();
+
+ fields_changed(); /* EMIT SIGNAL */
+}
+
+string
+AudioLibrary::field_uri (string name)
+{
+ stringstream local_field;
+ local_field << "file:sfdb/fields/" << name;
+
+ return local_field.str();
+}
+
+string
+AudioLibrary::get_label (string uri)
+{
+ lrdf_statement pattern;
+ pattern.subject = strdup(uri.c_str());
+ pattern.predicate = RDFS_BASE "label";
+ pattern.object = 0;
+ pattern.object_type = lrdf_literal;
+
+ lrdf_statement* matches = lrdf_matches (&pattern);
+ free(pattern.subject);
+
+ stringstream label;
+ if (matches != 0){
+ label << matches->object;
+ }
+
+ lrdf_free_statements(matches);
+
+ return label.str();
+}
+
+void
+AudioLibrary::set_label (string uri, string label)
+{
+ lrdf_statement pattern;
+ pattern.subject = strdup(uri.c_str());
+ pattern.predicate = RDFS_BASE "label";
+ pattern.object = 0;
+ pattern.object_type = lrdf_literal;
+
+ lrdf_remove_matches(&pattern);
+ free(pattern.subject);
+
+ lrdf_add_triple(src.c_str(), uri.c_str(), RDFS_BASE "label",
+ label.c_str(), lrdf_literal);
+}
+
diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc
new file mode 100644
index 0000000000..e4a244aa16
--- /dev/null
+++ b/libs/ardour/audio_playlist.cc
@@ -0,0 +1,873 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+
+#include <stdlib.h>
+
+#include <sigc++/bind.h>
+
+#include <ardour/types.h>
+#include <ardour/configuration.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/audioregion.h>
+#include <ardour/crossfade.h>
+#include <ardour/crossfade_compare.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace sigc;
+using namespace std;
+
+AudioPlaylist::State::~State ()
+{
+}
+
+AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden)
+ : Playlist (session, node, hidden)
+{
+ in_set_state = true;
+ set_state (node);
+ in_set_state = false;
+
+ save_state (_("initial state"));
+
+ if (!hidden) {
+ PlaylistCreated (this); /* EMIT SIGNAL */
+ }
+}
+
+AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
+ : Playlist (session, name, hidden)
+{
+ save_state (_("initial state"));
+
+ if (!hidden) {
+ PlaylistCreated (this); /* EMIT SIGNAL */
+ }
+
+}
+
+AudioPlaylist::AudioPlaylist (const AudioPlaylist& other, string name, bool hidden)
+ : Playlist (other, name, hidden)
+{
+ save_state (_("initial state"));
+
+ if (!hidden) {
+ PlaylistCreated (this); /* EMIT SIGNAL */
+ }
+}
+
+AudioPlaylist::AudioPlaylist (const AudioPlaylist& other, jack_nframes_t start, jack_nframes_t cnt, string name, bool hidden)
+ : Playlist (other, start, cnt, name, hidden)
+{
+ save_state (_("initial state"));
+
+ /* this constructor does NOT notify others (session) */
+}
+
+AudioPlaylist::~AudioPlaylist ()
+{
+ set<Crossfade*> all_xfades;
+ set<Region*> all_regions;
+
+ GoingAway (this);
+
+ /* find every region we've ever used, and add it to the set of
+ all regions. same for xfades;
+ */
+
+ for (RegionList::iterator x = regions.begin(); x != regions.end(); ++x) {
+ all_regions.insert (*x);
+ }
+
+ for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end(); ++x) {
+ all_xfades.insert (*x);
+ }
+
+ for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+
+ AudioPlaylist::State* apstate = dynamic_cast<AudioPlaylist::State*> (*i);
+
+ for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) {
+ all_regions.insert (*r);
+ }
+ for (Crossfades::iterator xf = apstate->crossfades.begin(); xf != apstate->crossfades.end(); ++xf) {
+ all_xfades.insert (*xf);
+ }
+
+ delete apstate;
+ }
+
+ /* delete every region */
+
+ for (set<Region *>::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) {
+ (*ar)->unlock_sources ();
+ delete *ar;
+ }
+
+ /* delete every crossfade */
+
+ for (set<Crossfade *>::iterator axf = all_xfades.begin(); axf != all_xfades.end(); ++axf) {
+ delete *axf;
+ }
+}
+
+struct RegionSortByLayer {
+ bool operator() (Region *a, Region *b) {
+ return a->layer() < b->layer();
+ }
+};
+
+jack_nframes_t
+AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, jack_nframes_t start,
+ jack_nframes_t cnt, unsigned chan_n)
+{
+ jack_nframes_t ret = cnt;
+ jack_nframes_t end;
+ jack_nframes_t read_frames;
+ jack_nframes_t skip_frames;
+
+ /* optimizing this memset() away involves a lot of conditionals
+ that may well cause more of a hit due to cache misses
+ and related stuff than just doing this here.
+
+ it would be great if someone could measure this
+ at some point.
+
+ one way or another, parts of the requested area
+ that are not written to by Region::region_at()
+ for all Regions that cover the area need to be
+ zeroed.
+ */
+
+ memset (buf, 0, sizeof (Sample) * cnt);
+
+ /* this function is never called from a realtime thread, so
+ its OK to block (for short intervals).
+ */
+
+ LockMonitor rm (region_lock, __LINE__, __FILE__);
+
+ end = start + cnt - 1;
+
+ read_frames = 0;
+ skip_frames = 0;
+ _read_data_count = 0;
+
+ map<uint32_t,vector<Region*> > relevant_regions;
+ map<uint32_t,vector<Crossfade*> > relevant_xfades;
+ vector<uint32_t> relevant_layers;
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+ if ((*i)->coverage (start, end) != OverlapNone) {
+
+ relevant_regions[(*i)->layer()].push_back (*i);
+ relevant_layers.push_back ((*i)->layer());
+ }
+ }
+
+ for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ if ((*i)->coverage (start, end) != OverlapNone) {
+ relevant_xfades[(*i)->upper_layer()].push_back (*i);
+ }
+ }
+
+// RegionSortByLayer layer_cmp;
+// relevant_regions.sort (layer_cmp);
+
+ /* XXX this whole per-layer approach is a hack that
+ should be removed once Crossfades become
+ CrossfadeRegions and we just grab a list of relevant
+ regions and call read_at() on all of them.
+ */
+
+ sort (relevant_layers.begin(), relevant_layers.end());
+
+ for (vector<uint32_t>::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) {
+
+ vector<Region*>& r (relevant_regions[*l]);
+ vector<Crossfade*>& x (relevant_xfades[*l]);
+
+ for (vector<Region*>::iterator i = r.begin(); i != r.end(); ++i) {
+ (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n, read_frames, skip_frames);
+ _read_data_count += (*i)->read_data_count();
+ }
+
+ for (vector<Crossfade*>::iterator i = x.begin(); i != x.end(); ++i) {
+
+ (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n);
+
+ /* don't JACK up _read_data_count, since its the same data as we just
+ read from the regions, and the OS should handle that for us.
+ */
+ }
+ }
+
+ return ret;
+}
+
+
+void
+AudioPlaylist::remove_dependents (Region& region)
+{
+ Crossfades::iterator i, tmp;
+ AudioRegion* r = dynamic_cast<AudioRegion*> (&region);
+
+ if (r == 0) {
+ fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
+ << endmsg;
+ return;
+ }
+
+ for (i = _crossfades.begin(); i != _crossfades.end(); ) {
+ tmp = i;
+ tmp++;
+
+ if ((*i)->involves (*r)) {
+ /* do not delete crossfades */
+ _crossfades.erase (i);
+ }
+
+ i = tmp;
+ }
+}
+
+
+void
+AudioPlaylist::flush_notifications ()
+{
+ Playlist::flush_notifications();
+
+ if (in_flush) {
+ return;
+ }
+
+ in_flush = true;
+
+ Crossfades::iterator a;
+ for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) {
+ NewCrossfade (*a); /* EMIT SIGNAL */
+ }
+
+ _pending_xfade_adds.clear ();
+
+ in_flush = false;
+}
+
+void
+AudioPlaylist::refresh_dependents (Region& r)
+{
+ AudioRegion* ar = dynamic_cast<AudioRegion*>(&r);
+ set<Crossfade*> updated;
+
+ if (ar == 0) {
+ return;
+ }
+
+ for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
+
+ Crossfades::iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ /* only update them once */
+
+ if ((*x)->involves (*ar)) {
+
+ if (find (updated.begin(), updated.end(), *x) == updated.end()) {
+ if ((*x)->refresh ()) {
+ /* not invalidated by the refresh */
+ updated.insert (*x);
+ }
+ }
+ }
+
+ x = tmp;
+ }
+}
+
+void
+AudioPlaylist::check_dependents (Region& r, bool norefresh)
+{
+ AudioRegion* other;
+ AudioRegion* region;
+ AudioRegion* top;
+ AudioRegion* bottom;
+ Crossfade* xfade;
+
+ if (in_set_state || in_partition) {
+ return;
+ }
+
+ if ((region = dynamic_cast<AudioRegion*> (&r)) == 0) {
+ fatal << _("programming error: non-audio Region tested for overlap in audio playlist")
+ << endmsg;
+ return;
+ }
+
+ if (!norefresh) {
+ refresh_dependents (r);
+ }
+
+ if (!Config->get_auto_xfade()) {
+ return;
+ }
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ other = dynamic_cast<AudioRegion*> (*i);
+
+ if (other == region) {
+ continue;
+ }
+
+ if (other->muted() || region->muted()) {
+ continue;
+ }
+
+ if (other->layer() < region->layer()) {
+ top = region;
+ bottom = other;
+ } else {
+ top = other;
+ bottom = region;
+ }
+
+ try {
+
+ if (top->coverage (bottom->position(), bottom->last_frame()) != OverlapNone) {
+
+ /* check if the upper region is within the lower region */
+
+ if (top->first_frame() > bottom->first_frame() &&
+ top->last_frame() < bottom->last_frame()) {
+
+
+ /* [ -------- top ------- ]
+ * {=========== bottom =============}
+ */
+
+ /* to avoid discontinuities at the region boundaries of an internal
+ overlap (this region is completely within another), we create
+ two hidden crossfades at each boundary. this is not dependent
+ on the auto-xfade option, because we require it as basic
+ audio engineering.
+ */
+
+ jack_nframes_t xfade_length = min ((jack_nframes_t) 720, top->length());
+
+ /* in, out */
+ xfade = new Crossfade (*top, *bottom, xfade_length, top->first_frame(), StartOfIn);
+ add_crossfade (*xfade);
+ xfade = new Crossfade (*bottom, *top, xfade_length, top->last_frame() - xfade_length, EndOfOut);
+ add_crossfade (*xfade);
+
+ } else {
+
+ xfade = new Crossfade (*other, *region, _session.get_xfade_model(), _session.get_crossfades_active());
+ add_crossfade (*xfade);
+ }
+ }
+ }
+
+ catch (failed_constructor& err) {
+ continue;
+ }
+
+ catch (Crossfade::NoCrossfadeHere& err) {
+ continue;
+ }
+
+ }
+}
+
+void
+AudioPlaylist::add_crossfade (Crossfade& xfade)
+{
+ Crossfades::iterator ci;
+
+ for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) {
+ if (*(*ci) == xfade) { // Crossfade::operator==()
+ break;
+ }
+ }
+
+ if (ci != _crossfades.end()) {
+ delete &xfade;
+ } else {
+ _crossfades.push_back (&xfade);
+
+ xfade.Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated));
+ xfade.StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed));
+
+ notify_crossfade_added (&xfade);
+ }
+}
+
+void AudioPlaylist::notify_crossfade_added (Crossfade *x)
+{
+ if (atomic_read(&block_notifications)) {
+ _pending_xfade_adds.insert (_pending_xfade_adds.end(), x);
+ } else {
+ NewCrossfade (x); /* EMIT SIGNAL */
+ }
+}
+
+void
+AudioPlaylist::crossfade_invalidated (Crossfade* xfade)
+{
+ Crossfades::iterator i;
+
+ xfade->in().resume_fade_in ();
+ xfade->out().resume_fade_out ();
+
+ if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) {
+ _crossfades.erase (i);
+ }
+}
+
+int
+AudioPlaylist::set_state (const XMLNode& node)
+{
+ XMLNode *child;
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+
+ if (!in_set_state) {
+ Playlist::set_state (node);
+ }
+
+ nlist = node.children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ child = *niter;
+
+ if (child->name() == "Crossfade") {
+
+ Crossfade *xfade;
+
+ try {
+ xfade = new Crossfade (*((const Playlist *)this), *child);
+ }
+
+ catch (failed_constructor& err) {
+ // cout << compose (_("could not create crossfade object in playlist %1"),
+ // _name)
+ // << endl;
+ continue;
+ }
+
+ Crossfades::iterator ci;
+
+ for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) {
+ if (*(*ci) == *xfade) {
+ break;
+ }
+ }
+
+ if (ci == _crossfades.end()) {
+ _crossfades.push_back (xfade);
+ xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated));
+ xfade->StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed));
+ /* no need to notify here */
+ } else {
+ delete xfade;
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+void
+AudioPlaylist::drop_all_states ()
+{
+ set<Crossfade*> all_xfades;
+ set<Region*> all_regions;
+
+ /* find every region we've ever used, and add it to the set of
+ all regions. same for xfades;
+ */
+
+ for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+
+ AudioPlaylist::State* apstate = dynamic_cast<AudioPlaylist::State*> (*i);
+
+ for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) {
+ all_regions.insert (*r);
+ }
+ for (Crossfades::iterator xf = apstate->crossfades.begin(); xf != apstate->crossfades.end(); ++xf) {
+ all_xfades.insert (*xf);
+ }
+ }
+
+ /* now remove from the "all" lists every region that is in the current list. */
+
+ for (list<Region*>::iterator i = regions.begin(); i != regions.end(); ++i) {
+ set<Region*>::iterator x = all_regions.find (*i);
+ if (x != all_regions.end()) {
+ all_regions.erase (x);
+ }
+ }
+
+ /* ditto for every crossfade */
+
+ for (list<Crossfade*>::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ set<Crossfade*>::iterator x = all_xfades.find (*i);
+ if (x != all_xfades.end()) {
+ all_xfades.erase (x);
+ }
+ }
+
+ /* delete every region that is left - these are all things that are part of our "history" */
+
+ for (set<Region *>::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) {
+ (*ar)->unlock_sources ();
+ delete *ar;
+ }
+
+ /* delete every crossfade that is left (ditto as per regions) */
+
+ for (set<Crossfade *>::iterator axf = all_xfades.begin(); axf != all_xfades.end(); ++axf) {
+ delete *axf;
+ }
+
+ /* Now do the generic thing ... */
+
+ StateManager::drop_all_states ();
+}
+
+StateManager::State*
+AudioPlaylist::state_factory (std::string why) const
+{
+ State* state = new State (why);
+
+ state->regions = regions;
+ state->region_states.clear ();
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ state->region_states.push_back ((*i)->get_memento());
+ }
+
+ state->crossfades = _crossfades;
+ state->crossfade_states.clear ();
+ for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ state->crossfade_states.push_back ((*i)->get_memento());
+ }
+
+ return state;
+}
+
+Change
+AudioPlaylist::restore_state (StateManager::State& state)
+{
+ {
+ RegionLock rlock (this);
+ State* apstate = dynamic_cast<State*> (&state);
+
+ in_set_state = true;
+
+ regions = apstate->regions;
+
+ for (list<UndoAction>::iterator s = apstate->region_states.begin(); s != apstate->region_states.end(); ++s) {
+ *s;
+ }
+
+ _crossfades = apstate->crossfades;
+
+ for (list<UndoAction>::iterator s = apstate->crossfade_states.begin(); s != apstate->crossfade_states.end(); ++s) {
+ *s;
+ }
+
+ in_set_state = false;
+ }
+
+ notify_length_changed ();
+ return Change (~0);
+}
+
+UndoAction
+AudioPlaylist::get_memento () const
+{
+ return sigc::bind (mem_fun (*(const_cast<AudioPlaylist*> (this)), &StateManager::use_state), _current_state_id);
+}
+
+void
+AudioPlaylist::clear (bool with_delete, bool with_save)
+{
+ if (with_delete) {
+ for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ delete *i;
+ }
+ }
+
+ _crossfades.clear ();
+
+ Playlist::clear (with_delete, with_save);
+}
+
+XMLNode&
+AudioPlaylist::state (bool full_state)
+{
+ XMLNode& node = Playlist::state (full_state);
+
+ if (full_state) {
+ for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ node.add_child_nocopy ((*i)->get_state());
+ }
+ }
+
+ return node;
+}
+
+void
+AudioPlaylist::dump () const
+{
+ Region *r;
+ Crossfade *x;
+
+ cerr << "Playlist \"" << _name << "\" " << endl
+ << regions.size() << " regions "
+ << _crossfades.size() << " crossfades"
+ << endl;
+
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ r = *i;
+ cerr << " " << r->name() << " @ " << r << " ["
+ << r->start() << "+" << r->length()
+ << "] at "
+ << r->position()
+ << " on layer "
+ << r->layer ()
+ << endl;
+ }
+
+ for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ x = *i;
+ cerr << " xfade ["
+ << x->out().name()
+ << ','
+ << x->in().name()
+ << " @ "
+ << x->position()
+ << " length = "
+ << x->length ()
+ << " active ? "
+ << (x->active() ? "yes" : "no")
+ << endl;
+ }
+}
+
+bool
+AudioPlaylist::destroy_region (Region* region)
+{
+ AudioRegion* r = dynamic_cast<AudioRegion*> (region);
+ bool changed = false;
+ Crossfades::iterator c, ctmp;
+ set<Crossfade*> unique_xfades;
+
+ if (r == 0) {
+ fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
+ << endmsg;
+ /*NOTREACHED*/
+ return false;
+ }
+
+ {
+ RegionLock rlock (this);
+ RegionList::iterator i;
+ RegionList::iterator tmp;
+
+ for (i = regions.begin(); i != regions.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i) == region) {
+ (*i)->unlock_sources ();
+ regions.erase (i);
+ changed = true;
+ }
+
+ i = tmp;
+ }
+ }
+
+ for (c = _crossfades.begin(); c != _crossfades.end(); ) {
+ ctmp = c;
+ ++ctmp;
+
+ if ((*c)->involves (*r)) {
+ unique_xfades.insert (*c);
+ _crossfades.erase (c);
+ }
+
+ c = ctmp;
+ }
+
+ for (StateMap::iterator s = states.begin(); s != states.end(); ) {
+ StateMap::iterator tmp;
+
+ tmp = s;
+ ++tmp;
+
+ State* astate = dynamic_cast<State*> (*s);
+
+ for (c = astate->crossfades.begin(); c != astate->crossfades.end(); ) {
+
+ ctmp = c;
+ ++ctmp;
+
+ if ((*c)->involves (*r)) {
+ unique_xfades.insert (*c);
+ _crossfades.erase (c);
+ }
+
+ c = ctmp;
+ }
+
+ list<UndoAction>::iterator rsi, rsitmp;
+ RegionList::iterator ri, ritmp;
+
+ for (ri = astate->regions.begin(), rsi = astate->region_states.begin();
+ ri != astate->regions.end() && rsi != astate->region_states.end();) {
+
+
+ ritmp = ri;
+ ++ritmp;
+
+ rsitmp = rsi;
+ ++rsitmp;
+
+ if (region == (*ri)) {
+ astate->regions.erase (ri);
+ astate->region_states.erase (rsi);
+ }
+
+ ri = ritmp;
+ rsi = rsitmp;
+ }
+
+ s = tmp;
+ }
+
+ for (set<Crossfade*>::iterator c = unique_xfades.begin(); c != unique_xfades.end(); ++c) {
+ delete *c;
+ }
+
+ if (changed) {
+ /* overload this, it normally means "removed", not destroyed */
+ notify_region_removed (region);
+ }
+
+ return changed;
+}
+
+void
+AudioPlaylist::crossfade_changed (Change ignored)
+{
+ if (in_flush) {
+ return;
+ }
+
+ /* XXX is there a loop here? can an xfade change not happen
+ due to a playlist change? well, sure activation would
+ be an example. maybe we should check the type of change
+ that occured.
+ */
+
+ maybe_save_state (_("xfade change"));
+ notify_modified ();
+}
+
+void
+AudioPlaylist::get_equivalent_regions (const AudioRegion& other, vector<AudioRegion*>& results)
+{
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ AudioRegion* ar = dynamic_cast<AudioRegion*> (*i);
+
+ if (ar && ar->equivalent (other)) {
+ results.push_back (ar);
+ }
+ }
+}
+
+void
+AudioPlaylist::get_region_list_equivalent_regions (const AudioRegion& other, vector<AudioRegion*>& results)
+{
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ AudioRegion* ar = dynamic_cast<AudioRegion*> (*i);
+
+ if (ar && ar->region_list_equivalent (other)) {
+ results.push_back (ar);
+ }
+ }
+}
+
+bool
+AudioPlaylist::region_changed (Change what_changed, Region* region)
+{
+ if (in_flush || in_set_state) {
+ return false;
+ }
+
+ Change our_interests = Change (AudioRegion::FadeInChanged|
+ AudioRegion::FadeOutChanged|
+ AudioRegion::FadeInActiveChanged|
+ AudioRegion::FadeOutActiveChanged|
+ AudioRegion::EnvelopeActiveChanged|
+ AudioRegion::ScaleAmplitudeChanged|
+ AudioRegion::EnvelopeChanged);
+ bool parent_wants_notify;
+
+ parent_wants_notify = Playlist::region_changed (what_changed, region);
+
+ maybe_save_state (_("region modified"));
+
+ if (parent_wants_notify || (what_changed & our_interests)) {
+ notify_modified ();
+ }
+
+ return true;
+}
+
+void
+AudioPlaylist::crossfades_at (jack_nframes_t frame, Crossfades& clist)
+{
+ RegionLock rlock (this);
+
+ for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+ jack_nframes_t start, end;
+
+ start = (*i)->position();
+ end = start + (*i)->overlap_length(); // not length(), important difference
+
+ if (frame >= start && frame <= end) {
+ clist.push_back (*i);
+ }
+ }
+}
diff --git a/libs/ardour/audio_track.cc b/libs/ardour/audio_track.cc
new file mode 100644
index 0000000000..b0c1d96e7b
--- /dev/null
+++ b/libs/ardour/audio_track.cc
@@ -0,0 +1,1063 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+#include <pbd/error.h>
+#include <sigc++/retype.h>
+#include <sigc++/retype_return.h>
+#include <sigc++/bind.h>
+
+#include <ardour/audio_track.h>
+#include <ardour/diskstream.h>
+#include <ardour/session.h>
+#include <ardour/redirect.h>
+#include <ardour/audioregion.h>
+#include <ardour/route_group_specialized.h>
+#include <ardour/insert.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/panner.h>
+#include <ardour/utils.h>
+
+
+
+#include "i18n.h"
+
+using namespace std;
+//using namespace sigc;
+using namespace ARDOUR;
+
+AudioTrack::AudioTrack (Session& sess, string name, Route::Flag flag)
+ : Route (sess, name, 1, -1, -1, -1, flag),
+ diskstream (0),
+ _midi_rec_enable_control (*this, _session.midi_port())
+{
+ DiskStream::Flag dflags = DiskStream::Flag (0);
+
+ if (_flags & Hidden) {
+ dflags = DiskStream::Flag (dflags | DiskStream::Hidden);
+ } else {
+ dflags = DiskStream::Flag (dflags | DiskStream::Recordable);
+ }
+
+ DiskStream* ds = new DiskStream (_session, name, dflags);
+
+ set_diskstream (*ds, this);
+
+ _declickable = true;
+ _freeze_record.state = NoFreeze;
+ _saved_meter_point = _meter_point;
+
+ // we do this even though Route already did it in it's init
+ reset_midi_control (_session.midi_port(), _session.get_midi_control());
+
+}
+
+AudioTrack::AudioTrack (Session& sess, const XMLNode& node)
+ : Route (sess, "to be renamed", 0, 0, -1, -1),
+ diskstream (0),
+ _midi_rec_enable_control (*this, _session.midi_port())
+{
+ _freeze_record.state = NoFreeze;
+ set_state (node);
+ _declickable = true;
+ _saved_meter_point = _meter_point;
+
+ // we do this even though Route already did it in it's init
+ reset_midi_control (_session.midi_port(), _session.get_midi_control());
+}
+
+AudioTrack::~AudioTrack ()
+{
+ if (diskstream) {
+ diskstream->unref();
+ }
+}
+
+int
+AudioTrack::deprecated_use_diskstream_connections ()
+{
+ if (diskstream->deprecated_io_node == 0) {
+ return 0;
+ }
+
+ const XMLProperty* prop;
+ XMLNode& node (*diskstream->deprecated_io_node);
+
+ /* don't do this more than once. */
+
+ diskstream->deprecated_io_node = 0;
+
+ set_input_minimum (-1);
+ set_input_maximum (-1);
+ set_output_minimum (-1);
+ set_output_maximum (-1);
+
+ if ((prop = node.property ("gain")) != 0) {
+ set_gain (atof (prop->value().c_str()), this);
+ _gain = _desired_gain;
+ }
+
+ if ((prop = node.property ("input-connection")) != 0) {
+ Connection* c = _session.connection_by_name (prop->value());
+
+ if (c == 0) {
+ error << compose(_("Unknown connection \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
+
+ if ((c = _session.connection_by_name (_("in 1"))) == 0) {
+ error << _("No input connections available as a replacement")
+ << endmsg;
+ return -1;
+ } else {
+ info << compose (_("Connection %1 was not available - \"in 1\" used instead"), prop->value())
+ << endmsg;
+ }
+ }
+
+ use_input_connection (*c, this);
+
+ } else if ((prop = node.property ("inputs")) != 0) {
+ if (set_inputs (prop->value())) {
+ error << compose(_("improper input channel list in XML node (%1)"), prop->value()) << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+AudioTrack::set_diskstream (DiskStream& ds, void *src)
+{
+ if (diskstream) {
+ diskstream->unref();
+ }
+
+ diskstream = &ds.ref();
+ diskstream->set_io (*this);
+
+ if (diskstream->deprecated_io_node) {
+
+ if (!connecting_legal) {
+ ConnectingLegal.connect (mem_fun (*this, &AudioTrack::deprecated_use_diskstream_connections));
+ } else {
+ deprecated_use_diskstream_connections ();
+ }
+ }
+
+ diskstream->set_record_enabled (false, this);
+ diskstream->monitor_input (false);
+
+ ic_connection.disconnect();
+ ic_connection = input_changed.connect (mem_fun (*diskstream, &DiskStream::handle_input_change));
+
+ diskstream_changed (src); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+int
+AudioTrack::use_diskstream (string name)
+{
+ DiskStream *dstream;
+
+ if ((dstream = _session.diskstream_by_name (name)) == 0) {
+ error << compose(_("AudioTrack: diskstream \"%1\" not known by session"), name) << endmsg;
+ return -1;
+ }
+
+ return set_diskstream (*dstream, this);
+}
+
+int
+AudioTrack::use_diskstream (id_t id)
+{
+ DiskStream *dstream;
+
+ if ((dstream = _session.diskstream_by_id (id)) == 0) {
+ error << compose(_("AudioTrack: diskstream \"%1\" not known by session"), id) << endmsg;
+ return -1;
+ }
+
+ return set_diskstream (*dstream, this);
+}
+
+bool
+AudioTrack::record_enabled () const
+{
+ return diskstream->record_enabled ();
+}
+
+void
+AudioTrack::set_record_enable (bool yn, void *src)
+{
+ if (_freeze_record.state == Frozen) {
+ return;
+ }
+
+ if (_mix_group && src != _mix_group && _mix_group->is_active()) {
+ _mix_group->apply (&AudioTrack::set_record_enable, yn, _mix_group);
+ return;
+ }
+
+ /* keep track of the meter point as it was before we rec-enabled */
+
+ if (!diskstream->record_enabled()) {
+ _saved_meter_point = _meter_point;
+ }
+
+ diskstream->set_record_enabled (yn, src);
+
+ if (diskstream->record_enabled()) {
+ set_meter_point (MeterInput, this);
+ } else {
+ set_meter_point (_saved_meter_point, this);
+ }
+}
+
+void
+AudioTrack::set_meter_point (MeterPoint p, void *src)
+{
+ Route::set_meter_point (p, src);
+}
+
+XMLNode&
+AudioTrack::state(bool full_state)
+{
+ XMLNode& track_state (Route::state (full_state));
+ char buf[64];
+
+ /* we don't return diskstream state because we don't
+ own the diskstream exclusively. control of the diskstream
+ state is ceded to the Session, even if we create the
+ diskstream.
+ */
+
+ snprintf (buf, sizeof (buf), "%" PRIu64, diskstream->id());
+ track_state.add_property ("diskstream-id", buf);
+
+ return track_state;
+}
+
+int
+AudioTrack::set_state (const XMLNode& node)
+{
+ const XMLProperty *prop;
+ XMLNodeConstIterator iter;
+ XMLNodeList midi_kids;
+
+ if (Route::set_state (node)) {
+ return -1;
+ }
+
+ midi_kids = node.children ("MIDI");
+
+ for (iter = midi_kids.begin(); iter != midi_kids.end(); ++iter) {
+
+ XMLNodeList kids;
+ XMLNodeConstIterator miter;
+ XMLNode* child;
+
+ kids = (*iter)->children ();
+
+ for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+ child =* miter;
+
+ if (child->name() == "rec_enable") {
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* ditto */
+ MIDI::channel_t chn = 0; /* ditto */
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_rec_enable_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI rec_enable control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+ }
+ }
+
+
+ if ((prop = node.property ("diskstream-id")) == 0) {
+
+ /* some old sessions use the diskstream name rather than the ID */
+
+ if ((prop = node.property ("diskstream")) == 0) {
+ fatal << _("programming error: AudioTrack given state without diskstream!") << endmsg;
+ /*NOTREACHED*/
+ return -1;
+ }
+
+ if (use_diskstream (prop->value())) {
+ return -1;
+ }
+
+ } else {
+
+ id_t id = strtoull (prop->value().c_str(), 0, 10);
+
+ if (use_diskstream (id)) {
+ return -1;
+ }
+ }
+
+ pending_state = const_cast<XMLNode*> (&node);
+
+ _session.StateReady.connect (mem_fun (*this, &AudioTrack::set_state_part_two));
+
+ return 0;
+}
+
+XMLNode&
+AudioTrack::get_state()
+{
+ XMLNode& root (Route::get_state());
+ XMLNode* freeze_node;
+ char buf[32];
+
+ if (_freeze_record.playlist) {
+ XMLNode* inode;
+
+ freeze_node = new XMLNode (X_("freeze-info"));
+ freeze_node->add_property ("playlist", _freeze_record.playlist->name());
+ snprintf (buf, sizeof (buf), "%d", (int) _freeze_record.state);
+ freeze_node->add_property ("state", buf);
+
+ for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+ inode = new XMLNode (X_("insert"));
+ snprintf (buf, sizeof (buf), "%" PRIu64, (*i)->id);
+ inode->add_property (X_("id"), buf);
+ inode->add_child_copy ((*i)->state);
+
+ freeze_node->add_child_nocopy (*inode);
+ }
+
+ root.add_child_nocopy (*freeze_node);
+ }
+
+ /* Alignment: act as a proxy for the diskstream */
+
+ XMLNode* align_node = new XMLNode (X_("alignment"));
+ switch (diskstream->alignment_style()) {
+ case ExistingMaterial:
+ snprintf (buf, sizeof (buf), X_("existing"));
+ break;
+ case CaptureTime:
+ snprintf (buf, sizeof (buf), X_("capture"));
+ break;
+ }
+ align_node->add_property (X_("style"), buf);
+ root.add_child_nocopy (*align_node);
+
+ /* MIDI control */
+
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ XMLNode* midi_node = 0;
+ XMLNode* child;
+ XMLNodeList midikids;
+
+ midikids = root.children ("MIDI");
+ if (!midikids.empty()) {
+ midi_node = midikids.front();
+ }
+ else {
+ midi_node = root.add_child ("MIDI");
+ }
+
+ if (_midi_rec_enable_control.get_control_info (chn, ev, additional) && midi_node) {
+
+ child = midi_node->add_child ("rec_enable");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+
+
+ return root;
+}
+
+void
+AudioTrack::set_state_part_two ()
+{
+ XMLNode* fnode;
+ XMLProperty* prop;
+ LocaleGuard lg (X_("POSIX"));
+
+ /* This is called after all session state has been restored but before
+ have been made ports and connections are established.
+ */
+
+ if (pending_state == 0) {
+ return;
+ }
+
+ if ((fnode = find_named_node (*pending_state, X_("freeze-info"))) != 0) {
+
+
+ _freeze_record.have_mementos = false;
+ _freeze_record.state = Frozen;
+
+ for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+ delete *i;
+ }
+ _freeze_record.insert_info.clear ();
+
+ if ((prop = fnode->property (X_("playlist"))) != 0) {
+ Playlist* pl = _session.playlist_by_name (prop->value());
+ if (pl) {
+ _freeze_record.playlist = dynamic_cast<AudioPlaylist*> (pl);
+ } else {
+ _freeze_record.playlist = 0;
+ _freeze_record.state = NoFreeze;
+ return;
+ }
+ }
+
+ if ((prop = fnode->property (X_("state"))) != 0) {
+ _freeze_record.state = (FreezeState) atoi (prop->value().c_str());
+ }
+
+ XMLNodeConstIterator citer;
+ XMLNodeList clist = fnode->children();
+
+ for (citer = clist.begin(); citer != clist.end(); ++citer) {
+ if ((*citer)->name() != X_("insert")) {
+ continue;
+ }
+
+ if ((prop = (*citer)->property (X_("id"))) == 0) {
+ continue;
+ }
+
+ FreezeRecordInsertInfo* frii = new FreezeRecordInsertInfo (*((*citer)->children().front()));
+ frii->insert = 0;
+ sscanf (prop->value().c_str(), "%llu", &frii->id);
+ _freeze_record.insert_info.push_back (frii);
+ }
+ }
+
+ /* Alignment: act as a proxy for the diskstream */
+
+ if ((fnode = find_named_node (*pending_state, X_("alignment"))) != 0) {
+
+ if ((prop = fnode->property (X_("style"))) != 0) {
+ if (prop->value() == "existing") {
+ diskstream->set_persistent_align_style (ExistingMaterial);
+ } else if (prop->value() == "capture") {
+ diskstream->set_persistent_align_style (CaptureTime);
+ }
+ }
+ }
+ return;
+}
+
+uint32_t
+AudioTrack::n_process_buffers ()
+{
+ return max ((uint32_t) diskstream->n_channels(), redirect_max_outs);
+}
+
+void
+AudioTrack::passthru_silence (jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t nframes, jack_nframes_t offset, int declick, bool meter)
+{
+ uint32_t nbufs = n_process_buffers ();
+ process_output_buffers (_session.get_silent_buffers (nbufs), nbufs, start_frame, end_frame, nframes, offset, true, declick, meter);
+}
+
+int
+AudioTrack::no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool session_state_changing, bool can_record, bool rec_monitors_input)
+{
+ if (n_outputs() == 0) {
+ return 0;
+ }
+
+ if (!_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ if (session_state_changing) {
+
+ /* XXX is this safe to do against transport state changes? */
+
+ passthru_silence (start_frame, end_frame, nframes, offset, 0, false);
+ return 0;
+ }
+
+ diskstream->check_record_status (start_frame, nframes, can_record);
+
+ bool send_silence;
+
+ if (_have_internal_generator) {
+ /* since the instrument has no input streams,
+ there is no reason to send any signal
+ into the route.
+ */
+ send_silence = true;
+ } else {
+
+ if (_session.get_auto_input()) {
+ if (Config->get_no_sw_monitoring()) {
+ send_silence = true;
+ } else {
+ send_silence = false;
+ }
+ } else {
+ if (diskstream->record_enabled()) {
+ if (Config->get_no_sw_monitoring()) {
+ send_silence = true;
+ } else {
+ send_silence = false;
+ }
+ } else {
+ send_silence = true;
+ }
+ }
+ }
+
+ apply_gain_automation = false;
+
+ if (send_silence) {
+
+ /* if we're sending silence, but we want the meters to show levels for the signal,
+ meter right here.
+ */
+
+ if (_have_internal_generator) {
+ passthru_silence (start_frame, end_frame, nframes, offset, 0, true);
+ } else {
+ if (_meter_point == MeterInput) {
+ just_meter_input (start_frame, end_frame, nframes, offset);
+ }
+ passthru_silence (start_frame, end_frame, nframes, offset, 0, false);
+ }
+
+ } else {
+
+ /* we're sending signal, but we may still want to meter the input.
+ */
+
+ passthru (start_frame, end_frame, nframes, offset, 0, (_meter_point == MeterInput));
+ }
+
+ return 0;
+}
+
+int
+AudioTrack::roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, int declick,
+ bool can_record, bool rec_monitors_input)
+{
+ int dret;
+ Sample* b;
+ Sample* tmpb;
+ jack_nframes_t transport_frame;
+
+ automation_snapshot (start_frame);
+
+ if (n_outputs() == 0 && _redirects.empty()) {
+ return 0;
+ }
+
+ if (!_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ transport_frame = _session.transport_frame();
+
+ if ((nframes = check_initial_delay (nframes, offset, transport_frame)) == 0) {
+ /* need to do this so that the diskstream sets its
+ playback distance to zero, thus causing diskstream::commit
+ to do nothing.
+ */
+ return diskstream->process (transport_frame, 0, 0, can_record, rec_monitors_input);
+ }
+
+ _silent = false;
+ apply_gain_automation = false;
+
+ if ((dret = diskstream->process (transport_frame, nframes, offset, can_record, rec_monitors_input)) != 0) {
+
+ silence (nframes, offset);
+
+ return dret;
+ }
+
+ /* special condition applies */
+
+ if (_meter_point == MeterInput) {
+ just_meter_input (start_frame, end_frame, nframes, offset);
+ }
+
+ if (diskstream->record_enabled() && !can_record && !_session.get_auto_input()) {
+
+ /* not actually recording, but we want to hear the input material anyway,
+ at least potentially (depending on monitoring options)
+ */
+
+ passthru (start_frame, end_frame, nframes, offset, 0, true);
+
+ } else if ((b = diskstream->playback_buffer(0)) != 0) {
+
+ /*
+ XXX is it true that the earlier test on n_outputs()
+ means that we can avoid checking it again here? i think
+ so, because changing the i/o configuration of an IO
+ requires holding the AudioEngine lock, which we hold
+ while in the process() tree.
+ */
+
+
+ /* copy the diskstream data to all output buffers */
+
+ vector<Sample*>& bufs = _session.get_passthru_buffers ();
+ uint32_t limit = n_process_buffers ();
+
+ uint32_t n;
+ uint32_t i;
+
+
+ for (i = 0, n = 1; i < limit; ++i, ++n) {
+ memcpy (bufs[i], b, sizeof (Sample) * nframes);
+ if (n < diskstream->n_channels()) {
+ tmpb = diskstream->playback_buffer(n);
+ if (tmpb!=0) {
+ b = tmpb;
+ }
+ }
+ }
+
+ /* don't waste time with automation if we're recording */
+
+ if (!diskstream->record_enabled()) {
+
+ if (gain_automation_playback()) {
+ apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes);
+ }
+ }
+
+ process_output_buffers (bufs, limit, start_frame, end_frame, nframes, offset, (!_session.get_record_enabled() || _session.get_recording_plugins()), declick, (_meter_point != MeterInput));
+
+ } else {
+ /* problem with the diskstream; just be quiet for a bit */
+ silence (nframes, offset);
+ }
+
+ return 0;
+}
+
+int
+AudioTrack::silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool can_record, bool rec_monitors_input)
+{
+ if (n_outputs() == 0 && _redirects.empty()) {
+ return 0;
+ }
+
+ if (!_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ _silent = true;
+ apply_gain_automation = false;
+
+ silence (nframes, offset);
+
+ return diskstream->process (_session.transport_frame() + offset, nframes, offset, can_record, rec_monitors_input);
+}
+
+void
+AudioTrack::toggle_monitor_input ()
+{
+ for (vector<Port*>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ (*i)->request_monitor_input(!(*i)->monitoring_input());
+ }
+}
+
+int
+AudioTrack::set_name (string str, void *src)
+{
+ if (record_enabled() && _session.actively_recording()) {
+ /* this messes things up if done while recording */
+ return -1;
+ }
+
+ diskstream->set_name (str, src);
+ return IO::set_name (str, src);
+}
+
+int
+AudioTrack::export_stuff (vector<Sample*>& buffers, uint32_t nbufs, jack_nframes_t start, jack_nframes_t nframes)
+{
+ gain_t gain_automation[nframes];
+ gain_t gain_buffer[nframes];
+ float mix_buffer[nframes];
+ RedirectList::iterator i;
+ bool post_fader_work = false;
+ gain_t this_gain = _gain;
+ vector<Sample*>::iterator bi;
+ Sample * b;
+
+ LockMonitor rlock (redirect_lock, __LINE__, __FILE__);
+
+ if (diskstream->playlist()->read (buffers[0], mix_buffer, gain_buffer, start, nframes) != nframes) {
+ return -1;
+ }
+
+ uint32_t n=1;
+ bi = buffers.begin();
+ b = buffers[0];
+ ++bi;
+ for (; bi != buffers.end(); ++bi, ++n) {
+ if (n < diskstream->n_channels()) {
+ if (diskstream->playlist()->read ((*bi), mix_buffer, gain_buffer, start, nframes, n) != nframes) {
+ return -1;
+ }
+ b = (*bi);
+ }
+ else {
+ /* duplicate last across remaining buffers */
+ memcpy ((*bi), b, sizeof (Sample) * nframes);
+ }
+ }
+
+
+ /* note: only run inserts during export. other layers in the machinery
+ will already have checked that there are no external port inserts.
+ */
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ Insert *insert;
+
+ if ((insert = dynamic_cast<Insert*>(*i)) != 0) {
+ switch (insert->placement()) {
+ case PreFader:
+ insert->run (buffers, nbufs, nframes, 0);
+ break;
+ case PostFader:
+ post_fader_work = true;
+ break;
+ }
+ }
+ }
+
+ if (_gain_automation_curve.automation_state() == Play) {
+
+ _gain_automation_curve.get_vector (start, start + nframes, gain_automation, nframes);
+
+ for (bi = buffers.begin(); bi != buffers.end(); ++bi) {
+ Sample *b = *bi;
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ b[n] *= gain_automation[n];
+ }
+ }
+
+ } else {
+
+ for (bi = buffers.begin(); bi != buffers.end(); ++bi) {
+ Sample *b = *bi;
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ b[n] *= this_gain;
+ }
+ }
+ }
+
+ if (post_fader_work) {
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ PluginInsert *insert;
+
+ if ((insert = dynamic_cast<PluginInsert*>(*i)) != 0) {
+ switch ((*i)->placement()) {
+ case PreFader:
+ break;
+ case PostFader:
+ insert->run (buffers, nbufs, nframes, 0);
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+AudioTrack::set_latency_delay (jack_nframes_t longest_session_latency)
+{
+ Route::set_latency_delay (longest_session_latency);
+ diskstream->set_roll_delay (_roll_delay);
+}
+
+jack_nframes_t
+AudioTrack::update_total_latency ()
+{
+ _own_latency = 0;
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if ((*i)->active ()) {
+ _own_latency += (*i)->latency ();
+ }
+ }
+
+ set_port_latency (_own_latency);
+
+ return _own_latency;
+}
+
+void
+AudioTrack::bounce (InterThreadInfo& itt)
+{
+ vector<Source*> srcs;
+ _session.write_one_track (*this, 0, _session.current_end_frame(), false, srcs, itt);
+}
+
+
+void
+AudioTrack::bounce_range (jack_nframes_t start, jack_nframes_t end, InterThreadInfo& itt)
+{
+ vector<Source*> srcs;
+ _session.write_one_track (*this, start, end, false, srcs, itt);
+}
+
+void
+AudioTrack::freeze (InterThreadInfo& itt)
+{
+ Insert* insert;
+ vector<Source*> srcs;
+ string new_playlist_name;
+ Playlist* new_playlist;
+ string dir;
+ AudioRegion* region;
+ string region_name;
+
+ if ((_freeze_record.playlist = diskstream->playlist()) == 0) {
+ return;
+ }
+
+ uint32_t n = 1;
+
+ while (n < ULONG_MAX) {
+
+ string candidate;
+
+ candidate = compose ("<F%2>%1", _freeze_record.playlist->name(), n);
+
+ if (_session.playlist_by_name (candidate) == 0) {
+ new_playlist_name = candidate;
+ break;
+ }
+
+ ++n;
+
+ }
+
+ if (n == ULONG_MAX) {
+ error << compose (X_("There Are too many frozen versions of playlist \"%1\""
+ " to create another one"), _freeze_record.playlist->name())
+ << endmsg;
+ return;
+ }
+
+ if (_session.write_one_track (*this, 0, _session.current_end_frame(), true, srcs, itt)) {
+ return;
+ }
+
+ _freeze_record.insert_info.clear ();
+ _freeze_record.have_mementos = true;
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); ++r) {
+
+ if ((insert = dynamic_cast<Insert*>(*r)) != 0) {
+
+ FreezeRecordInsertInfo* frii = new FreezeRecordInsertInfo ((*r)->get_state());
+
+ frii->insert = insert;
+ frii->id = insert->id();
+ frii->memento = (*r)->get_memento();
+
+ _freeze_record.insert_info.push_back (frii);
+
+ /* now deactivate the insert */
+
+ insert->set_active (false, this);
+ }
+ }
+ }
+
+ new_playlist = new AudioPlaylist (_session, new_playlist_name, false);
+ region_name = new_playlist_name;
+
+ /* create a new region from all filesources, keep it private */
+
+ region = new AudioRegion (srcs, 0, srcs[0]->length(),
+ region_name, 0,
+ (AudioRegion::Flag) (AudioRegion::WholeFile|AudioRegion::DefaultFlags),
+ false);
+
+ new_playlist->set_orig_diskstream_id (diskstream->id());
+ new_playlist->add_region (*region, 0);
+ new_playlist->set_frozen (true);
+ region->set_locked (true);
+
+ diskstream->use_playlist (dynamic_cast<AudioPlaylist*>(new_playlist));
+ diskstream->set_record_enabled (false, this);
+
+ _freeze_record.state = Frozen;
+ FreezeChange(); /* EMIT SIGNAL */
+}
+
+void
+AudioTrack::unfreeze ()
+{
+ if (_freeze_record.playlist) {
+ diskstream->use_playlist (_freeze_record.playlist);
+
+ if (_freeze_record.have_mementos) {
+
+ for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+ (*i)->memento ();
+ }
+
+ } else {
+
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ for (vector<FreezeRecordInsertInfo*>::iterator ii = _freeze_record.insert_info.begin(); ii != _freeze_record.insert_info.end(); ++ii) {
+ if ((*ii)->id == (*i)->id()) {
+ (*i)->set_state (((*ii)->state));
+ break;
+ }
+ }
+ }
+ }
+
+ _freeze_record.playlist = 0;
+ }
+
+ _freeze_record.state = UnFrozen;
+ FreezeChange (); /* EMIT SIGNAL */
+}
+
+AudioTrack::FreezeRecord::~FreezeRecord ()
+{
+ for (vector<FreezeRecordInsertInfo*>::iterator i = insert_info.begin(); i != insert_info.end(); ++i) {
+ delete *i;
+ }
+}
+
+AudioTrack::FreezeState
+AudioTrack::freeze_state() const
+{
+ return _freeze_record.state;
+}
+
+
+void
+AudioTrack::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ Route::reset_midi_control (port, on);
+
+ _midi_rec_enable_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_rec_enable_control.midi_rebind (port, chn);
+}
+
+void
+AudioTrack::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+
+ Route::send_all_midi_feedback();
+
+ _midi_rec_enable_control.send_feedback (record_enabled());
+ }
+}
+
+
+AudioTrack::MIDIRecEnableControl::MIDIRecEnableControl (AudioTrack& s, MIDI::Port* port)
+ : MIDI::Controllable (port, 0), track (s), setting(false)
+{
+ last_written = false; /* XXX need a good out of bound value */
+}
+
+void
+AudioTrack::MIDIRecEnableControl::set_value (float val)
+{
+ bool bval = ((val >= 0.5f) ? true: false);
+
+ setting = true;
+ track.set_record_enable (bval, this);
+ setting = false;
+}
+
+void
+AudioTrack::MIDIRecEnableControl::send_feedback (bool value)
+{
+
+ if (!setting && get_midi_feedback()) {
+ MIDI::byte val = (MIDI::byte) (value ? 127: 0);
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ track._session.send_midi_message (get_port(), ev, ch, data);
+ }
+ }
+
+}
+
+MIDI::byte*
+AudioTrack::MIDIRecEnableControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force)
+{
+ if (get_midi_feedback()) {
+
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+
+ if (get_control_info (ch, ev, additional)) {
+ if (val != last_written || force) {
+ *buf++ = ev & ch;
+ *buf++ = additional; /* controller number */
+ *buf++ = (MIDI::byte) (val ? 127: 0);
+ last_written = val;
+ bufsize -= 3;
+ }
+ }
+ }
+
+ return buf;
+}
diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc
new file mode 100644
index 0000000000..2792bd5e65
--- /dev/null
+++ b/libs/ardour/audioengine.cc
@@ -0,0 +1,1115 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#include <unistd.h>
+#include <cerrno>
+#include <vector>
+#include <pbd/pthread_utils.h>
+
+#include <ardour/audioengine.h>
+#include <ardour/port.h>
+#include <ardour/session.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/utils.h>
+#ifdef VST_SUPPORT
+#include <fst.h>
+#endif
+
+#include <ardour/timestamps.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+jack_nframes_t Port::short_over_length = 2;
+jack_nframes_t Port::long_over_length = 10;
+
+AudioEngine::AudioEngine (string client_name)
+{
+ pthread_cond_init (&session_removed, 0);
+ session = 0;
+ session_remove_pending = false;
+ _running = false;
+ _has_run = false;
+ last_monitor_check = 0;
+ monitor_check_interval = max_frames;
+ _processed_frames = 0;
+ _freewheeling = false;
+ _usecs_per_cycle = 0;
+ _jack = 0;
+ _frame_rate = 0;
+ _buffer_size = 0;
+ _freewheeling = false;
+ _freewheel_thread_registered = false;
+
+ if (connect_to_jack (client_name)) {
+ throw NoBackendAvailable ();
+ }
+
+}
+
+AudioEngine::~AudioEngine ()
+{
+ if (_running) {
+ jack_client_close (_jack);
+ }
+}
+
+void
+_thread_init_callback (void *arg)
+{
+ /* make sure that anybody who needs to know about this thread
+ knows about it.
+ */
+
+ PBD::ThreadCreated (pthread_self(), X_("Audioengine"));
+
+#ifdef VST_SUPPORT
+ if (Config->get_use_vst()) {
+ fst_adopt_thread ();
+ }
+#endif
+}
+
+int
+AudioEngine::start ()
+{
+ if (!_running) {
+
+ if (session) {
+ jack_nframes_t blocksize = jack_get_buffer_size (_jack);
+
+ session->set_block_size (blocksize);
+ session->set_frame_rate (jack_get_sample_rate (_jack));
+
+ /* page in as much of the session process code as we
+ can before we really start running.
+ */
+
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ session->process (blocksize);
+ }
+
+ _processed_frames = 0;
+ last_monitor_check = 0;
+
+ jack_on_shutdown (_jack, halted, this);
+ jack_set_graph_order_callback (_jack, _graph_order_callback, this);
+ jack_set_thread_init_callback (_jack, _thread_init_callback, this);
+ jack_set_process_callback (_jack, _process_callback, this);
+ jack_set_sample_rate_callback (_jack, _sample_rate_callback, this);
+ jack_set_buffer_size_callback (_jack, _bufsize_callback, this);
+ jack_set_xrun_callback (_jack, _xrun_callback, this);
+ jack_set_sync_callback (_jack, _jack_sync_callback, this);
+ jack_set_freewheel_callback (_jack, _freewheel_callback, this);
+
+ if (Config->get_jack_time_master()) {
+ jack_set_timebase_callback (_jack, 0, _jack_timebase_callback, this);
+ }
+
+ if (jack_activate (_jack) == 0) {
+ _running = true;
+ _has_run = true;
+ Running(); /* EMIT SIGNAL */
+ } else {
+ error << _("cannot activate JACK client") << endmsg;
+ }
+ }
+
+ return _running ? 0 : -1;
+}
+
+int
+AudioEngine::stop ()
+{
+ if (_running) {
+ _running = false;
+ jack_deactivate (_jack);
+ Stopped(); /* EMIT SIGNAL */
+ }
+
+ return _running ? -1 : 0;
+}
+
+
+void
+AudioEngine::_jack_timebase_callback (jack_transport_state_t state, jack_nframes_t nframes,
+
+ jack_position_t* pos, int new_position, void *arg)
+{
+ static_cast<AudioEngine*> (arg)->jack_timebase_callback (state, nframes, pos, new_position);
+}
+
+void
+AudioEngine::jack_timebase_callback (jack_transport_state_t state, jack_nframes_t nframes,
+
+ jack_position_t* pos, int new_position)
+{
+ if (session && session->synced_to_jack()) {
+ session->jack_timebase_callback (state, nframes, pos, new_position);
+ }
+}
+
+int
+AudioEngine::_jack_sync_callback (jack_transport_state_t state, jack_position_t* pos, void* arg)
+{
+ return static_cast<AudioEngine*> (arg)->jack_sync_callback (state, pos);
+}
+
+int
+AudioEngine::jack_sync_callback (jack_transport_state_t state, jack_position_t* pos)
+{
+ if (session) {
+ return session->jack_sync_callback (state, pos);
+ } else {
+ return true;
+ }
+}
+
+int
+AudioEngine::_xrun_callback (void *arg)
+{
+ static_cast<AudioEngine *>(arg)->Xrun (); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+AudioEngine::_graph_order_callback (void *arg)
+{
+ static_cast<AudioEngine *>(arg)->GraphReordered (); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+AudioEngine::_process_callback (jack_nframes_t nframes, void *arg)
+{
+ return static_cast<AudioEngine *> (arg)->process_callback (nframes);
+}
+
+void
+AudioEngine::_freewheel_callback (int onoff, void *arg)
+{
+ static_cast<AudioEngine*>(arg)->_freewheeling = onoff;
+}
+
+void
+AudioEngine::meter (Port *port, jack_nframes_t nframes)
+{
+ double peak;
+ uint32_t overlen;
+ jack_default_audio_sample_t *buf;
+
+ buf = port->get_buffer (nframes);
+ peak = port->_peak;
+ overlen = port->overlen;
+
+ {
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+
+ /* 1) peak metering */
+
+ peak = f_max (peak, buf[n]);
+
+ /* 2) clip/over metering */
+
+ if (buf[n] >= 1.0) {
+ overlen++;
+ } else if (overlen) {
+ if (overlen > Port::short_over_length) {
+ port->_short_overs++;
+ }
+ if (overlen > Port::long_over_length) {
+ port->_long_overs++;
+ }
+ overlen = 0;
+ }
+ }
+ }
+
+ /* post-loop check on the final status of overlen */
+
+ if (overlen > Port::short_over_length) {
+ port->_short_overs++;
+ }
+ if (overlen > Port::long_over_length) {
+ port->_short_overs++;
+ }
+
+ if (peak > 0.0) {
+ port->_peak_db= 20 * fast_log10 (peak);
+ } else {
+ port->_peak_db = minus_infinity();
+ }
+
+ port->_peak = peak;
+ port->overlen = overlen;
+}
+
+int
+AudioEngine::process_callback (jack_nframes_t nframes)
+{
+ TentativeLockMonitor tm (_process_lock, __LINE__, __FILE__);
+ jack_nframes_t next_processed_frames;
+
+ /* handle wrap around of total frames counter */
+
+ if (max_frames - _processed_frames < nframes) {
+ next_processed_frames = nframes - (max_frames - _processed_frames);
+ } else {
+ next_processed_frames = _processed_frames + nframes;
+ }
+
+ if (!tm.locked() || session == 0) {
+ _processed_frames = next_processed_frames;
+ return 0;
+ }
+
+ if (session_remove_pending) {
+ session = 0;
+ session_remove_pending = false;
+ pthread_cond_signal (&session_removed);
+ _processed_frames = next_processed_frames;
+ return 0;
+ }
+
+ if (_freewheeling) {
+ if (Freewheel (nframes)) {
+ _freewheeling = false;
+ jack_set_freewheel (_jack, false);
+ }
+ return 0;
+ }
+
+ /* do input peak metering */
+
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ if ((*i)->metering) {
+ meter ((*i), nframes);
+ }
+ }
+
+ session->process (nframes);
+
+ if (!_running) {
+ /* we were zombified, maybe because a ladspa plugin took
+ too long, or jackd exited, or something like that.
+ */
+
+ _processed_frames = next_processed_frames;
+ return 0;
+ }
+
+ if (last_monitor_check + monitor_check_interval < next_processed_frames) {
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+
+ Port *port = (*i);
+ bool x;
+
+ if (port->last_monitor != (x = port->monitoring_input ())) {
+ port->last_monitor = x;
+ /* XXX I think this is dangerous, due to
+ a likely mutex in the signal handlers ...
+ */
+ port->MonitorInputChanged (x); /* EMIT SIGNAL */
+ }
+ }
+ last_monitor_check = next_processed_frames;
+ }
+
+ _processed_frames = next_processed_frames;
+ return 0;
+}
+
+int
+AudioEngine::_sample_rate_callback (jack_nframes_t nframes, void *arg)
+{
+ return static_cast<AudioEngine *> (arg)->jack_sample_rate_callback (nframes);
+}
+
+int
+AudioEngine::jack_sample_rate_callback (jack_nframes_t nframes)
+{
+ _frame_rate = nframes;
+ _usecs_per_cycle = (int) floor ((((double) frames_per_cycle() / nframes)) * 1000000.0);
+
+ /* check for monitor input change every 1/10th of second */
+
+ monitor_check_interval = nframes / 10;
+ last_monitor_check = 0;
+
+ if (session) {
+ session->set_frame_rate (nframes);
+ }
+
+ SampleRateChanged (nframes); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+int
+AudioEngine::_bufsize_callback (jack_nframes_t nframes, void *arg)
+{
+ return static_cast<AudioEngine *> (arg)->jack_bufsize_callback (nframes);
+}
+
+int
+AudioEngine::jack_bufsize_callback (jack_nframes_t nframes)
+{
+ _buffer_size = nframes;
+ _usecs_per_cycle = (int) floor ((((double) nframes / frame_rate())) * 1000000.0);
+ last_monitor_check = 0;
+
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ (*i)->reset();
+ }
+
+ if (session) {
+ session->set_block_size (_buffer_size);
+ }
+
+ return 0;
+}
+
+void
+AudioEngine::set_session (Session *s)
+{
+ if (!session) {
+ s->set_block_size (jack_get_buffer_size (_jack));
+ s->set_frame_rate (jack_get_sample_rate (_jack));
+ session = s;
+ }
+}
+
+void
+AudioEngine::remove_session ()
+{
+ LockMonitor lm (_process_lock, __LINE__, __FILE__);
+
+ if (_running) {
+
+ if (session) {
+ session_remove_pending = true;
+ pthread_cond_wait (&session_removed, _process_lock.mutex());
+ }
+
+ } else {
+
+ session = 0;
+
+ }
+
+ remove_all_ports ();
+
+}
+
+Port *
+AudioEngine::register_audio_input_port (const string& portname)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("register audio input port called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return 0;
+ }
+ }
+
+ jack_port_t *p = jack_port_register (_jack, portname.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+
+ if (p) {
+
+ Port *newport;
+ if ((newport = new Port (p)) != 0) {
+ ports.insert (ports.begin(), newport);
+ }
+ return newport;
+
+ } else {
+
+ pthread_mutex_unlock (_process_lock.mutex());
+ throw PortRegistrationFailure();
+ }
+
+ return 0;
+}
+
+Port *
+AudioEngine::register_audio_output_port (const string& portname)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("register audio output port called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return 0;
+ }
+ }
+
+ jack_port_t *p;
+
+ if ((p = jack_port_register (_jack, portname.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) != 0) {
+ Port *newport = new Port (p);
+ ports.insert (ports.begin(), newport);
+ return newport;
+
+ } else {
+
+ pthread_mutex_unlock (_process_lock.mutex());
+ throw PortRegistrationFailure ();
+ }
+
+ return 0;
+}
+
+int
+AudioEngine::unregister_port (Port *port)
+{
+ if (!_running) {
+ /* probably happening when the engine has been halted by JACK,
+ in which case, there is nothing we can do here.
+ */
+ return 0;
+ }
+
+ if (port) {
+
+ int ret = jack_port_unregister (_jack, port->port);
+
+ if (ret == 0) {
+
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ if ((*i) == port) {
+ ports.erase (i);
+ break;
+ }
+ }
+
+ remove_connections_for (port);
+ }
+
+ return ret;
+
+ } else {
+ return -1;
+ }
+}
+
+int
+AudioEngine::connect (const string& source, const string& destination)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("connect called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return -1;
+ }
+ }
+
+ string s = make_port_name_non_relative (source);
+ string d = make_port_name_non_relative (destination);
+
+ int ret = jack_connect (_jack, s.c_str(), d.c_str());
+
+ if (ret == 0) {
+ pair<string,string> c (s, d);
+ port_connections.push_back (c);
+ } else {
+ error << compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"),
+ source, s, destination, d)
+ << endmsg;
+ }
+
+ return ret;
+}
+
+int
+AudioEngine::disconnect (const string& source, const string& destination)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("disconnect called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return -1;
+ }
+ }
+
+ string s = make_port_name_non_relative (source);
+ string d = make_port_name_non_relative (destination);
+
+ int ret = jack_disconnect (_jack, s.c_str(), d.c_str());
+
+ if (ret == 0) {
+ pair<string,string> c (s, d);
+ PortConnections::iterator i;
+
+ if ((i = find (port_connections.begin(), port_connections.end(), c)) != port_connections.end()) {
+ port_connections.erase (i);
+ }
+ }
+
+ return ret;
+}
+
+int
+AudioEngine::disconnect (Port *port)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("disconnect called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return -1;
+ }
+ }
+
+ int ret = jack_port_disconnect (_jack, port->port);
+
+ if (ret == 0) {
+ remove_connections_for (port);
+ }
+
+ return ret;
+
+}
+
+jack_nframes_t
+AudioEngine::frame_rate ()
+{
+ if (_jack) {
+ if (_frame_rate == 0) {
+ return (_frame_rate = jack_get_sample_rate (_jack));
+ } else {
+ return _frame_rate;
+ }
+ } else {
+ fatal << X_("programming error: AudioEngine::frame_rate() called while disconnected from JACK")
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+ }
+}
+
+jack_nframes_t
+AudioEngine::frames_per_cycle ()
+{
+ if (_jack) {
+ if (_buffer_size == 0) {
+ return (_buffer_size = jack_get_buffer_size (_jack));
+ } else {
+ return _buffer_size;
+ }
+ } else {
+ fatal << X_("programming error: AudioEngine::frame_rate() called while disconnected from JACK")
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+ }
+}
+
+Port *
+AudioEngine::get_port_by_name (const string& portname, bool keep)
+{
+ LockMonitor lm (_process_lock, __LINE__, __FILE__);
+
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("get_port_by_name() called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return 0;
+ }
+ }
+
+ /* check to see if we have a Port for this name already */
+
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ if (portname == (*i)->name()) {
+ return (*i);
+ }
+ }
+
+ jack_port_t *p;
+
+ if ((p = jack_port_by_name (_jack, portname.c_str())) != 0) {
+ Port *newport = new Port (p);
+ if (keep && newport->is_mine (_jack)) {
+ ports.insert (newport);
+ }
+ return newport;
+ } else {
+ return 0;
+ }
+}
+
+const char **
+AudioEngine::get_ports (const string& port_name_pattern, const string& type_name_pattern, uint32_t flags)
+{
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("get_ports called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return 0;
+ }
+ }
+ return jack_get_ports (_jack, port_name_pattern.c_str(), type_name_pattern.c_str(), flags);
+}
+
+void
+AudioEngine::halted (void *arg)
+{
+ AudioEngine *ae = reinterpret_cast<AudioEngine *> (arg);
+
+ ae->_running = false;
+ ae->_jack = 0;
+
+ ae->_buffer_size = 0;
+ ae->_frame_rate = 0;
+
+ ae->Halted(); /* EMIT SIGNAL */
+}
+
+uint32_t
+AudioEngine::n_physical_outputs () const
+{
+ const char ** ports;
+ uint32_t i = 0;
+
+ if (!_jack) {
+ return 0;
+ }
+
+ if ((ports = jack_get_ports (_jack, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == 0) {
+ return 0;
+ }
+
+ if (ports) {
+ for (i = 0; ports[i]; ++i);
+ free (ports);
+ }
+ return i;
+}
+
+uint32_t
+AudioEngine::n_physical_inputs () const
+{
+ const char ** ports;
+ uint32_t i = 0;
+
+ if (!_jack) {
+ return 0;
+ }
+
+ if ((ports = jack_get_ports (_jack, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == 0) {
+ return 0;
+ }
+
+ if (ports) {
+ for (i = 0; ports[i]; ++i);
+ free (ports);
+ }
+ return i;
+}
+
+string
+
+AudioEngine::get_nth_physical (uint32_t n, int flag)
+{
+ const char ** ports;
+ uint32_t i;
+ string ret;
+
+ if (!_running || !_jack) {
+ if (!_has_run) {
+ fatal << _("get_nth_physical called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ } else {
+ return "";
+ }
+ }
+
+ ports = jack_get_ports (_jack, NULL, NULL, JackPortIsPhysical|flag);
+
+ if (ports == 0) {
+ return "";
+ }
+
+ for (i = 0; i < n && ports[i]; ++i);
+
+ if (ports[i]) {
+ ret = ports[i];
+ }
+
+ free ((char *) ports);
+
+ return ret;
+}
+
+jack_nframes_t
+AudioEngine::get_port_total_latency (const Port& port)
+{
+ if (!_jack) {
+ fatal << _("get_port_total_latency() called with no JACK client connection") << endmsg;
+ /*NOTREACHED*/
+ }
+
+ if (!_running) {
+ if (!_has_run) {
+ fatal << _("get_port_total_latency() called before engine was started") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+
+ return jack_port_get_total_latency (_jack, port.port);
+}
+
+void
+AudioEngine::transport_stop ()
+{
+ // cerr << "tell JACK to stop\n";
+ if (_jack) {
+ jack_transport_stop (_jack);
+ }
+}
+
+void
+AudioEngine::transport_start ()
+{
+ // cerr << "tell JACK to start\n";
+ if (_jack) {
+ jack_transport_start (_jack);
+ }
+}
+
+void
+AudioEngine::transport_locate (jack_nframes_t where)
+{
+ // cerr << "tell JACK to locate to " << where << endl;
+ if (_jack) {
+ jack_transport_locate (_jack, where);
+ }
+}
+
+AudioEngine::TransportState
+AudioEngine::transport_state ()
+{
+ if (_jack) {
+ jack_position_t pos;
+ return (TransportState) jack_transport_query (_jack, &pos);
+ } else {
+ return (TransportState) JackTransportStopped;
+ }
+}
+
+int
+AudioEngine::reset_timebase ()
+{
+ if (_jack) {
+ if (Config->get_jack_time_master()) {
+ return jack_set_timebase_callback (_jack, 0, _jack_timebase_callback, this);
+ } else {
+ return jack_release_timebase (_jack);
+ }
+ } else {
+ return -1;
+ }
+}
+
+int
+AudioEngine::freewheel (bool onoff)
+{
+ if (_jack) {
+
+ if (onoff) {
+ _freewheel_thread_registered = false;
+ }
+
+ return jack_set_freewheel (_jack, onoff);
+
+ } else {
+ return -1;
+ }
+}
+
+void
+AudioEngine::remove_all_ports ()
+{
+ /* process lock MUST be held */
+
+ if (_jack) {
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ jack_port_unregister (_jack, (*i)->port);
+ }
+ }
+
+ ports.clear ();
+ port_connections.clear ();
+}
+
+void
+AudioEngine::remove_connections_for (Port* port)
+{
+ for (PortConnections::iterator i = port_connections.begin(); i != port_connections.end(); ) {
+ PortConnections::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i).first == port->name()) {
+ port_connections.erase (i);
+ }
+
+ i = tmp;
+ }
+}
+
+#ifdef HAVE_JACK_CLIENT_OPEN
+
+int
+AudioEngine::connect_to_jack (string client_name)
+{
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ const char *server_name = NULL;
+
+ jack_client_name = client_name; /* might be reset below */
+
+ _jack = jack_client_open (jack_client_name.c_str(), options, &status, server_name);
+
+ if (_jack == NULL) {
+
+ if (status & JackServerFailed) {
+ error << _("Unable to connect to JACK server") << endmsg;
+ }
+
+ error << compose (_("Could not connect to JACK server as \"%1\""), jack_client_name) << endmsg;
+ return -1;
+ }
+
+ if (status & JackServerStarted) {
+ info << _("JACK server started") << endmsg;
+ }
+
+ if (status & JackNameNotUnique) {
+ jack_client_name = jack_get_client_name (_jack);
+ }
+
+ return 0;
+}
+
+#else
+
+int
+AudioEngine::connect_to_jack (string client_name)
+{
+ jack_client_name = client_name;
+
+ if ((_jack = jack_client_new (client_name.c_str())) == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif /* HAVE_JACK_CLIENT_OPEN */
+
+int
+AudioEngine::disconnect_from_jack ()
+{
+ if (_jack == 0) {
+ return 0;
+ }
+
+ if (jack_client_close (_jack)) {
+ error << _("cannot shutdown connection to JACK") << endmsg;
+ }
+
+ _buffer_size = 0;
+ _frame_rate = 0;
+
+ if (_running) {
+ _running = false;
+ Stopped(); /* EMIT SIGNAL */
+ }
+
+ _jack = 0;
+ return 0;
+}
+
+int
+AudioEngine::reconnect_to_jack ()
+{
+ if (_jack) {
+ disconnect_from_jack ();
+ /* XXX give jackd a chance */
+ usleep (250000);
+ }
+
+ if (connect_to_jack (jack_client_name)) {
+ error << _("failed to connect to JACK") << endmsg;
+ return -1;
+ }
+
+ Ports::iterator i;
+
+ for (i = ports.begin(); i != ports.end(); ++i) {
+
+ /* XXX hack hack hack */
+
+ string long_name = (*i)->name();
+ string short_name;
+
+ short_name = long_name.substr (long_name.find_last_of (':') + 1);
+
+ if (((*i)->port = jack_port_register (_jack, short_name.c_str(), (*i)->type(), (*i)->flags(), 0)) == 0) {
+ error << compose (_("could not reregister %1"), (*i)->name()) << endmsg;
+ break;
+ } else {
+ }
+
+ (*i)->reset ();
+
+ if ((*i)->flags() & JackPortIsOutput) {
+ (*i)->silence (jack_get_buffer_size (_jack), 0);
+ }
+ }
+
+ if (i != ports.end()) {
+ for (Ports::iterator i = ports.begin(); i != ports.end(); ++i) {
+ jack_port_unregister (_jack, (*i)->port);
+ }
+ return -1;
+ }
+
+
+ if (session) {
+ jack_nframes_t blocksize = jack_get_buffer_size (_jack);
+ session->set_block_size (blocksize);
+ session->set_frame_rate (jack_get_sample_rate (_jack));
+ }
+
+ last_monitor_check = 0;
+
+ jack_on_shutdown (_jack, halted, this);
+ jack_set_graph_order_callback (_jack, _graph_order_callback, this);
+ jack_set_thread_init_callback (_jack, _thread_init_callback, this);
+ jack_set_process_callback (_jack, _process_callback, this);
+ jack_set_sample_rate_callback (_jack, _sample_rate_callback, this);
+ jack_set_buffer_size_callback (_jack, _bufsize_callback, this);
+ jack_set_xrun_callback (_jack, _xrun_callback, this);
+ jack_set_sync_callback (_jack, _jack_sync_callback, this);
+ jack_set_freewheel_callback (_jack, _freewheel_callback, this);
+
+ if (Config->get_jack_time_master()) {
+ jack_set_timebase_callback (_jack, 0, _jack_timebase_callback, this);
+ }
+
+ if (jack_activate (_jack) == 0) {
+ _running = true;
+ _has_run = true;
+ } else {
+ return -1;
+ }
+
+ /* re-establish connections */
+
+ for (PortConnections::iterator i = port_connections.begin(); i != port_connections.end(); ++i) {
+
+ int err;
+
+ if ((err = jack_connect (_jack, (*i).first.c_str(), (*i).second.c_str())) != 0) {
+ if (err != EEXIST) {
+ error << compose (_("could not reconnect %1 and %2 (err = %3)"),
+ (*i).first, (*i).second, err)
+ << endmsg;
+ }
+ }
+ }
+
+ Running (); /* EMIT SIGNAL*/
+
+ return 0;
+}
+
+int
+AudioEngine::request_buffer_size (jack_nframes_t nframes)
+{
+ if (_jack) {
+ int ret = jack_set_buffer_size (_jack, nframes);
+ return ret;
+ } else {
+ return -1;
+ }
+}
+
+void
+AudioEngine::update_total_latencies ()
+{
+#ifdef HAVE_JACK_RECOMPUTE_LATENCIES
+ jack_recompute_total_latencies (_jack);
+#endif
+}
+
+string
+AudioEngine::make_port_name_relative (string portname)
+{
+ string::size_type len;
+ string::size_type n;
+
+ len = portname.length();
+
+ for (n = 0; n < len; ++n) {
+ if (portname[n] == ':') {
+ break;
+ }
+ }
+
+ if ((n != len) && (portname.substr (0, n) == jack_client_name)) {
+ return portname.substr (n+1);
+ }
+
+ return portname;
+}
+
+string
+AudioEngine::make_port_name_non_relative (string portname)
+{
+ string str;
+
+ if (portname.find_first_of (':') != string::npos) {
+ return portname;
+ }
+
+ str = jack_client_name;
+ str += ':';
+ str += portname;
+
+ return str;
+}
+
diff --git a/libs/ardour/audiofilter.cc b/libs/ardour/audiofilter.cc
new file mode 100644
index 0000000000..ab4728495f
--- /dev/null
+++ b/libs/ardour/audiofilter.cc
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <time.h>
+#include <cerrno>
+
+#include <pbd/basename.h>
+#include <ardour/filesource.h>
+#include <ardour/session.h>
+#include <ardour/audioregion.h>
+#include <ardour/audiofilter.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+int
+AudioFilter::make_new_sources (AudioRegion& region, AudioRegion::SourceList& nsrcs)
+{
+ vector<string> names = region.master_source_names();
+
+ for (uint32_t i = 0; i < region.n_channels(); ++i) {
+
+ string path = session.path_from_region_name (PBD::basename_nosuffix (names[i]), string (""));
+
+ if (path.length() == 0) {
+ error << compose (_("audiofilter: error creating name for new audio file based on %1"), region.name())
+ << endmsg;
+ return -1;
+ }
+
+ try {
+ nsrcs.push_back (new FileSource (path, session.frame_rate()));
+ }
+
+ catch (failed_constructor& err) {
+ error << compose (_("audiofilter: error creating new audio file %1 (%2)"), path, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+AudioFilter::finish (AudioRegion& region, AudioRegion::SourceList& nsrcs)
+{
+ string region_name;
+
+ /* update headers on new sources */
+
+ time_t xnow;
+ struct tm* now;
+
+ time (&xnow);
+ now = localtime (&xnow);
+
+ for (AudioRegion::SourceList::iterator si = nsrcs.begin(); si != nsrcs.end(); ++si) {
+ dynamic_cast<FileSource*>((*si))->update_header (session.transport_frame(), *now, xnow);
+ }
+
+ /* create a new region */
+
+ region_name = session.new_region_name (region.name());
+ results.clear ();
+ results.push_back (new AudioRegion (nsrcs, 0, region.length(), region_name, 0,
+ Region::Flag (Region::WholeFile|Region::DefaultFlags)));
+
+ return 0;
+}
diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc
new file mode 100644
index 0000000000..8d4a5f0177
--- /dev/null
+++ b/libs/ardour/audioregion.cc
@@ -0,0 +1,1405 @@
+/*
+ Copyright (C) 2000-2001 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <climits>
+#include <cfloat>
+
+#include <set>
+
+#include <sigc++/bind.h>
+#include <sigc++/class_slot.h>
+
+#include <pbd/basename.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/audioregion.h>
+#include <ardour/session.h>
+#include <ardour/gain.h>
+#include <ardour/dB.h>
+#include <ardour/playlist.h>
+#include <ardour/audiofilter.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+/* a Session will reset these to its chosen defaults by calling AudioRegion::set_default_fade() */
+
+Change AudioRegion::FadeInChanged = ARDOUR::new_change();
+Change AudioRegion::FadeOutChanged = ARDOUR::new_change();
+Change AudioRegion::FadeInActiveChanged = ARDOUR::new_change();
+Change AudioRegion::FadeOutActiveChanged = ARDOUR::new_change();
+Change AudioRegion::EnvelopeActiveChanged = ARDOUR::new_change();
+Change AudioRegion::ScaleAmplitudeChanged = ARDOUR::new_change();
+Change AudioRegion::EnvelopeChanged = ARDOUR::new_change();
+
+AudioRegionState::AudioRegionState (string why)
+ : RegionState (why),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+}
+
+AudioRegion::AudioRegion (Source& src, jack_nframes_t start, jack_nframes_t length, bool announce)
+ : Region (start, length, PBD::basename_nosuffix(src.name()), 0, Region::Flag(Region::DefaultFlags|Region::External)),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+ /* basic AudioRegion constructor */
+
+ sources.push_back (&src);
+ master_sources.push_back (&src);
+ src.GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+
+ _scale_amplitude = 1.0;
+
+ set_default_fades ();
+ set_default_envelope ();
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ if (announce) {
+ CheckNewRegion (this); /* EMIT SIGNAL */
+ }
+}
+
+AudioRegion::AudioRegion (Source& src, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t layer, Flag flags, bool announce)
+ : Region (start, length, name, layer, flags),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+ /* basic AudioRegion constructor */
+
+ sources.push_back (&src);
+ master_sources.push_back (&src);
+ src.GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+
+ _scale_amplitude = 1.0;
+
+ set_default_fades ();
+ set_default_envelope ();
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ if (announce) {
+ CheckNewRegion (this); /* EMIT SIGNAL */
+ }
+}
+
+AudioRegion::AudioRegion (SourceList& srcs, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t layer, Flag flags, bool announce)
+ : Region (start, length, name, layer, flags),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+ /* basic AudioRegion constructor */
+
+ for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) {
+ sources.push_back (*i);
+ master_sources.push_back (*i);
+
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ }
+
+ _scale_amplitude = 1.0;
+
+ set_default_fades ();
+ set_default_envelope ();
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ if (announce) {
+ CheckNewRegion (this); /* EMIT SIGNAL */
+ }
+}
+
+
+AudioRegion::AudioRegion (const AudioRegion& other, jack_nframes_t offset, jack_nframes_t length, const string& name, layer_t layer, Flag flags, bool announce)
+ : Region (other, offset, length, name, layer, flags),
+ _fade_in (other._fade_in),
+ _fade_out (other._fade_out),
+ _envelope (other._envelope, (double) offset, (double) offset + length)
+{
+ /* create a new AudioRegion, that is part of an existing one */
+
+ set<Source*> unique_srcs;
+
+ for (SourceList::const_iterator i= other.sources.begin(); i != other.sources.end(); ++i) {
+ sources.push_back (*i);
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ unique_srcs.insert (*i);
+ }
+
+ for (SourceList::const_iterator i = other.master_sources.begin(); i != other.master_sources.end(); ++i) {
+ if (unique_srcs.find (*i) == unique_srcs.end()) {
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ }
+ master_sources.push_back (*i);
+ }
+
+ /* return to default fades if the existing ones are too long */
+
+ if (_flags & LeftOfSplit) {
+ if (_fade_in.back()->when >= _length) {
+ set_default_fade_in ();
+ }
+ set_default_fade_out ();
+ _flags = Flag (_flags & ~Region::RightOfSplit);
+ }
+
+ if (_flags & RightOfSplit) {
+ if (_fade_out.back()->when >= _length) {
+ set_default_fade_out ();
+ }
+ set_default_fade_in ();
+ _flags = Flag (_flags & ~Region::LeftOfSplit);
+ }
+
+ _scale_amplitude = other._scale_amplitude;
+
+ _fade_in_disabled = 0;
+ _fade_out_disabled = 0;
+
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ if (announce) {
+ CheckNewRegion (this); /* EMIT SIGNAL */
+ }
+}
+
+AudioRegion::AudioRegion (const AudioRegion &other)
+ : Region (other),
+ _fade_in (other._fade_in),
+ _fade_out (other._fade_out),
+ _envelope (other._envelope)
+{
+ /* Pure copy constructor */
+
+ set<Source*> unique_srcs;
+
+ for (SourceList::const_iterator i = other.sources.begin(); i != other.sources.end(); ++i) {
+ sources.push_back (*i);
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ unique_srcs.insert (*i);
+ }
+
+ for (SourceList::const_iterator i = other.master_sources.begin(); i != other.master_sources.end(); ++i) {
+ master_sources.push_back (*i);
+ if (unique_srcs.find (*i) == unique_srcs.end()) {
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ }
+ }
+
+ _scale_amplitude = other._scale_amplitude;
+ _envelope = other._envelope;
+
+ _fade_in_disabled = 0;
+ _fade_out_disabled = 0;
+
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ /* NOTE: no CheckNewRegion signal emitted here. This is the copy constructor */
+}
+
+AudioRegion::AudioRegion (Source& src, const XMLNode& node)
+ : Region (node),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+ sources.push_back (&src);
+ master_sources.push_back (&src);
+ src.GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+
+ set_default_fades ();
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ CheckNewRegion (this); /* EMIT SIGNAL */
+}
+
+AudioRegion::AudioRegion (SourceList& srcs, const XMLNode& node)
+ : Region (node),
+ _fade_in (0.0, 2.0, 1.0, false),
+ _fade_out (0.0, 2.0, 1.0, false),
+ _envelope (0.0, 2.0, 1.0, false)
+{
+ /* basic AudioRegion constructor */
+
+ set<Source*> unique_srcs;
+
+ for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) {
+ sources.push_back (*i);
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ unique_srcs.insert (*i);
+ }
+
+ for (SourceList::iterator i = srcs.begin(); i != srcs.end(); ++i) {
+ master_sources.push_back (*i);
+ if (unique_srcs.find (*i) == unique_srcs.end()) {
+ (*i)->GoingAway.connect (mem_fun (*this, &AudioRegion::source_deleted));
+ }
+ }
+
+ set_default_fades ();
+ _scale_amplitude = 1.0;
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ save_state ("initial state");
+
+ _envelope.StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed));
+
+ CheckNewRegion (this); /* EMIT SIGNAL */
+}
+
+AudioRegion::~AudioRegion ()
+{
+ GoingAway (this);
+}
+
+StateManager::State*
+AudioRegion::state_factory (std::string why) const
+{
+ AudioRegionState* state = new AudioRegionState (why);
+
+ Region::store_state (*state);
+
+ state->_fade_in = _fade_in;
+ state->_fade_out = _fade_out;
+ state->_envelope = _envelope;
+ state->_scale_amplitude = _scale_amplitude;
+ state->_fade_in_disabled = _fade_in_disabled;
+ state->_fade_out_disabled = _fade_out_disabled;
+
+ return state;
+}
+
+Change
+AudioRegion::restore_state (StateManager::State& sstate)
+{
+ AudioRegionState* state = dynamic_cast<AudioRegionState*> (&sstate);
+
+ Change what_changed = Region::restore_and_return_flags (*state);
+
+ if (_flags != Flag (state->_flags)) {
+
+ uint32_t old_flags = _flags;
+
+ _flags = Flag (state->_flags);
+
+ if ((old_flags ^ state->_flags) & EnvelopeActive) {
+ what_changed = Change (what_changed|EnvelopeActiveChanged);
+ }
+ }
+
+ if (!(_fade_in == state->_fade_in)) {
+ _fade_in = state->_fade_in;
+ what_changed = Change (what_changed|FadeInChanged);
+ }
+
+ if (!(_fade_out == state->_fade_out)) {
+ _fade_out = state->_fade_out;
+ what_changed = Change (what_changed|FadeOutChanged);
+ }
+
+ if (_scale_amplitude != state->_scale_amplitude) {
+ _scale_amplitude = state->_scale_amplitude;
+ what_changed = Change (what_changed|ScaleAmplitudeChanged);
+ }
+
+ if (_fade_in_disabled != state->_fade_in_disabled) {
+ if (_fade_in_disabled == 0 && state->_fade_in_disabled) {
+ set_fade_in_active (false);
+ } if (_fade_in_disabled && state->_fade_in_disabled == 0) {
+ set_fade_in_active (true);
+ }
+ _fade_in_disabled = state->_fade_in_disabled;
+ }
+
+ if (_fade_out_disabled != state->_fade_out_disabled) {
+ if (_fade_out_disabled == 0 && state->_fade_out_disabled) {
+ set_fade_out_active (false);
+ } if (_fade_out_disabled && state->_fade_out_disabled == 0) {
+ set_fade_out_active (true);
+ }
+ _fade_out_disabled = state->_fade_out_disabled;
+ }
+
+ /* XXX need a way to test stored state versus current for envelopes */
+
+ _envelope = state->_envelope;
+ what_changed = Change (what_changed);
+
+ return what_changed;
+}
+
+UndoAction
+AudioRegion::get_memento() const
+{
+ return sigc::bind (mem_fun (*(const_cast<AudioRegion *> (this)), &StateManager::use_state), _current_state_id);
+}
+
+bool
+AudioRegion::verify_length (jack_nframes_t len)
+{
+ for (uint32_t n=0; n < sources.size(); ++n) {
+ if (_start > sources[n]->length() - len) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+AudioRegion::verify_start_and_length (jack_nframes_t new_start, jack_nframes_t new_length)
+{
+ for (uint32_t n=0; n < sources.size(); ++n) {
+ if (new_length > sources[n]->length() - new_start) {
+ return false;
+ }
+ }
+ return true;
+}
+bool
+AudioRegion::verify_start (jack_nframes_t pos)
+{
+ for (uint32_t n=0; n < sources.size(); ++n) {
+ if (pos > sources[n]->length() - _length) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+AudioRegion::verify_start_mutable (jack_nframes_t& new_start)
+{
+ for (uint32_t n=0; n < sources.size(); ++n) {
+ if (new_start > sources[n]->length() - _length) {
+ new_start = sources[n]->length() - _length;
+ }
+ }
+ return true;
+}
+void
+AudioRegion::set_envelope_active (bool yn)
+{
+ if (envelope_active() != yn) {
+ char buf[64];
+ if (yn) {
+ snprintf (buf, sizeof (buf), "envelope active");
+ _flags = Flag (_flags|EnvelopeActive);
+ } else {
+ snprintf (buf, sizeof (buf), "envelope off");
+ _flags = Flag (_flags & ~EnvelopeActive);
+ }
+ if (!_frozen) {
+ save_state (buf);
+ }
+ send_change (EnvelopeActiveChanged);
+
+ }
+}
+
+jack_nframes_t
+AudioRegion::read_peaks (PeakData *buf, jack_nframes_t npeaks, jack_nframes_t offset, jack_nframes_t cnt, uint32_t chan_n, double samples_per_unit) const
+{
+ if (chan_n >= sources.size()) {
+ return 0;
+ }
+
+ if (sources[chan_n]->read_peaks (buf, npeaks, offset, cnt, samples_per_unit)) {
+ return 0;
+ } else {
+ if (_scale_amplitude != 1.0) {
+ for (jack_nframes_t n = 0; n < npeaks; ++n) {
+ buf[n].max *= _scale_amplitude;
+ buf[n].min *= _scale_amplitude;
+ }
+ }
+ return cnt;
+ }
+}
+
+jack_nframes_t
+AudioRegion::read_at (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, jack_nframes_t position,
+ jack_nframes_t cnt,
+ uint32_t chan_n, jack_nframes_t read_frames, jack_nframes_t skip_frames) const
+{
+ return _read_at (sources, buf, mixdown_buffer, gain_buffer, position, cnt, chan_n, read_frames, skip_frames);
+}
+
+jack_nframes_t
+AudioRegion::master_read_at (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, jack_nframes_t position,
+ jack_nframes_t cnt, uint32_t chan_n) const
+{
+ return _read_at (master_sources, buf, mixdown_buffer, gain_buffer, position, cnt, chan_n, 0, 0);
+}
+
+jack_nframes_t
+AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buffer, float *gain_buffer,
+ jack_nframes_t position, jack_nframes_t cnt,
+ uint32_t chan_n, jack_nframes_t read_frames, jack_nframes_t skip_frames) const
+{
+ jack_nframes_t internal_offset;
+ jack_nframes_t buf_offset;
+ jack_nframes_t to_read;
+
+ /* precondition: caller has verified that we cover the desired section */
+
+ if (chan_n >= sources.size()) {
+ return 0; /* read nothing */
+ }
+
+ if (position < _position) {
+ internal_offset = 0;
+ buf_offset = _position - position;
+ cnt -= buf_offset;
+ } else {
+ internal_offset = position - _position;
+ buf_offset = 0;
+ }
+
+ if (internal_offset >= _length) {
+ return 0; /* read nothing */
+ }
+
+
+ if ((to_read = min (cnt, _length - internal_offset)) == 0) {
+ return 0; /* read nothing */
+ }
+
+ if (opaque()) {
+ /* overwrite whatever is there */
+ mixdown_buffer = buf + buf_offset;
+ } else {
+ mixdown_buffer += buf_offset;
+ }
+
+ if (muted()) {
+ return 0; /* read nothing */
+ }
+
+ _read_data_count = 0;
+
+ if (srcs[chan_n]->read (mixdown_buffer, _start + internal_offset, to_read) != to_read) {
+ return 0; /* "read nothing" */
+ }
+
+ _read_data_count += srcs[chan_n]->read_data_count();
+
+ /* fade in */
+
+ if (_flags & FadeIn) {
+
+ jack_nframes_t fade_in_length = (jack_nframes_t) _fade_in.back()->when;
+
+ /* see if this read is within the fade in */
+
+ if (internal_offset < fade_in_length) {
+
+ jack_nframes_t limit;
+
+ limit = min (to_read, fade_in_length - internal_offset);
+
+ _fade_in.get_vector (internal_offset, internal_offset+limit, gain_buffer, limit);
+
+ for (jack_nframes_t n = 0; n < limit; ++n) {
+ mixdown_buffer[n] *= gain_buffer[n];
+ }
+ }
+ }
+
+ /* fade out */
+
+ if (_flags & FadeOut) {
+
+
+
+
+ /* see if some part of this read is within the fade out */
+
+ /* ................. >| REGION
+ _length
+
+ { } FADE
+ fade_out_length
+ ^
+ _length - fade_out_length
+ |--------------|
+ ^internal_offset
+ ^internal_offset + to_read
+
+ we need the intersection of [internal_offset,internal_offset+to_read] with
+ [_length - fade_out_length, _length]
+
+ */
+
+
+ jack_nframes_t fade_out_length = (jack_nframes_t) _fade_out.back()->when;
+ jack_nframes_t fade_interval_start = max(internal_offset, _length-fade_out_length);
+ jack_nframes_t fade_interval_end = min(internal_offset + to_read, _length);
+
+ if (fade_interval_end > fade_interval_start) {
+ /* (part of the) the fade out is in this buffer */
+
+ jack_nframes_t limit = fade_interval_end - fade_interval_start;
+ jack_nframes_t curve_offset = fade_interval_start - (_length-fade_out_length);
+ jack_nframes_t fade_offset = fade_interval_start - internal_offset;
+
+ _fade_out.get_vector (curve_offset,curve_offset+limit, gain_buffer, limit);
+
+ for (jack_nframes_t n = 0, m = fade_offset; n < limit; ++n, ++m) {
+ mixdown_buffer[m] *= gain_buffer[n];
+ }
+ }
+
+ }
+
+ /* Regular gain curves */
+
+ if (envelope_active()) {
+ _envelope.get_vector (internal_offset, internal_offset + to_read, gain_buffer, to_read);
+
+ if (_scale_amplitude != 1.0f) {
+ for (jack_nframes_t n = 0; n < to_read; ++n) {
+ mixdown_buffer[n] *= gain_buffer[n] * _scale_amplitude;
+ }
+ } else {
+ for (jack_nframes_t n = 0; n < to_read; ++n) {
+ mixdown_buffer[n] *= gain_buffer[n];
+ }
+ }
+ } else if (_scale_amplitude != 1.0f) {
+ Session::apply_gain_to_buffer (mixdown_buffer, to_read, _scale_amplitude);
+ }
+
+ if (!opaque()) {
+
+ /* gack. the things we do for users.
+ */
+
+ buf += buf_offset;
+
+ for (jack_nframes_t n = 0; n < to_read; ++n) {
+ buf[n] += mixdown_buffer[n];
+ }
+ }
+
+ return to_read;
+}
+
+XMLNode&
+AudioRegion::get_state ()
+{
+ return state (true);
+}
+
+XMLNode&
+AudioRegion::state (bool full)
+{
+ XMLNode& node (Region::state (full));
+ XMLNode *child;
+ char buf[64];
+ char buf2[64];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof (buf), "0x%x", (int) _flags);
+ node.add_property ("flags", buf);
+ snprintf (buf, sizeof(buf), "%f", _scale_amplitude);
+ node.add_property ("scale-gain", buf);
+
+ for (uint32_t n=0; n < sources.size(); ++n) {
+ snprintf (buf2, sizeof(buf2), "source-%d", n);
+ snprintf (buf, sizeof(buf), "%" PRIu64, sources[n]->id());
+ node.add_property (buf2, buf);
+ }
+
+ snprintf (buf, sizeof (buf), "%u", (uint32_t) sources.size());
+ node.add_property ("channels", buf);
+
+ if (full) {
+
+ child = node.add_child (X_("FadeIn"));
+
+ if ((_flags & DefaultFadeIn)) {
+ child->add_property (X_("default"), X_("yes"));
+ } else {
+ _fade_in.store_state (*child);
+ }
+
+ child = node.add_child (X_("FadeOut"));
+
+ if ((_flags & DefaultFadeOut)) {
+ child->add_property (X_("default"), X_("yes"));
+ } else {
+ _fade_out.store_state (*child);
+ }
+ }
+
+ child = node.add_child ("Envelope");
+
+ if (full) {
+ bool default_env = false;
+
+ // If there are only two points, the points are in the start of the region and the end of the region
+ // so, if they are both at 1.0f, that means the default region.
+ if (_envelope.size() == 2 &&
+ _envelope.front()->value == 1.0f &&
+ _envelope.back()->value==1.0f) {
+ if (_envelope.front()->when == 0 && _envelope.back()->when == _length) {
+ default_env = true;
+ }
+ }
+
+ if (default_env) {
+ child->add_property ("default", "yes");
+ } else {
+ _envelope.store_state (*child);
+ }
+ } else {
+ child->add_property ("default", "yes");
+ }
+
+ if (full && _extra_xml) {
+ node.add_child_copy (*_extra_xml);
+ }
+
+ return node;
+}
+
+int
+AudioRegion::set_state (const XMLNode& node)
+{
+ const XMLNodeList& nlist = node.children();
+ const XMLProperty *prop;
+ LocaleGuard lg (X_("POSIX"));
+
+ Region::set_state (node);
+
+ if ((prop = node.property ("flags")) != 0) {
+ _flags = Flag (strtol (prop->value().c_str(), (char **) 0, 16));
+ }
+
+ if ((prop = node.property ("scale-gain")) != 0) {
+ _scale_amplitude = atof (prop->value().c_str());
+ } else {
+ _scale_amplitude = 1.0;
+ }
+
+ /* Now find envelope description and other misc child items */
+
+ for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ XMLNode *child;
+ XMLProperty *prop;
+
+ child = (*niter);
+
+ if (child->name() == "Envelope") {
+
+ _envelope.clear ();
+
+ if ((prop = child->property ("default")) != 0) {
+ set_default_envelope ();
+ } else {
+ _envelope.load_state (*child);
+ }
+
+ _envelope.set_max_xval (_length);
+ _envelope.truncate_end (_length);
+
+ } else if (child->name() == "FadeIn") {
+
+ _fade_in.clear ();
+
+ if ((prop = child->property ("default")) != 0 || (prop = child->property ("steepness")) != 0) {
+ set_default_fade_in ();
+ } else {
+
+ _fade_in.load_state (*child);
+ }
+
+ } else if (child->name() == "FadeOut") {
+
+ _fade_out.clear ();
+
+ if ((prop = child->property ("default")) != 0 || (prop = child->property ("steepness")) != 0) {
+ set_default_fade_out ();
+ } else {
+ _fade_out.load_state (*child);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+AudioRegion::set_fade_in_shape (FadeShape shape)
+{
+ set_fade_in (shape, (jack_nframes_t) _fade_in.back()->when);
+}
+
+void
+AudioRegion::set_fade_out_shape (FadeShape shape)
+{
+ set_fade_out (shape, (jack_nframes_t) _fade_out.back()->when);
+}
+
+void
+AudioRegion::set_fade_in (FadeShape shape, jack_nframes_t len)
+{
+ _fade_in.freeze ();
+ _fade_in.clear ();
+
+ switch (shape) {
+ case Linear:
+ _fade_in.add (0.0, 0.0);
+ _fade_in.add (len, 1.0);
+ break;
+
+ case Fast:
+ _fade_in.add (0, 0);
+ _fade_in.add (len * 0.389401, 0.0333333);
+ _fade_in.add (len * 0.629032, 0.0861111);
+ _fade_in.add (len * 0.829493, 0.233333);
+ _fade_in.add (len * 0.9447, 0.483333);
+ _fade_in.add (len * 0.976959, 0.697222);
+ _fade_in.add (len, 1);
+ break;
+
+ case Slow:
+ _fade_in.add (0, 0);
+ _fade_in.add (len * 0.0207373, 0.197222);
+ _fade_in.add (len * 0.0645161, 0.525);
+ _fade_in.add (len * 0.152074, 0.802778);
+ _fade_in.add (len * 0.276498, 0.919444);
+ _fade_in.add (len * 0.481567, 0.980556);
+ _fade_in.add (len * 0.767281, 1);
+ _fade_in.add (len, 1);
+ break;
+
+ case LogA:
+ _fade_in.add (0, 0);
+ _fade_in.add (len * 0.0737327, 0.308333);
+ _fade_in.add (len * 0.246544, 0.658333);
+ _fade_in.add (len * 0.470046, 0.886111);
+ _fade_in.add (len * 0.652074, 0.972222);
+ _fade_in.add (len * 0.771889, 0.988889);
+ _fade_in.add (len, 1);
+ break;
+
+ case LogB:
+ _fade_in.add (0, 0);
+ _fade_in.add (len * 0.304147, 0.0694444);
+ _fade_in.add (len * 0.529954, 0.152778);
+ _fade_in.add (len * 0.725806, 0.333333);
+ _fade_in.add (len * 0.847926, 0.558333);
+ _fade_in.add (len * 0.919355, 0.730556);
+ _fade_in.add (len, 1);
+ break;
+ }
+
+ _fade_in.thaw ();
+ _fade_in_shape = shape;
+
+ if (!_frozen) {
+ save_state (_("fade in change"));
+ }
+
+ send_change (FadeInChanged);
+}
+
+void
+AudioRegion::set_fade_out (FadeShape shape, jack_nframes_t len)
+{
+ _fade_out.freeze ();
+ _fade_out.clear ();
+
+ switch (shape) {
+ case Fast:
+ _fade_out.add (len * 0, 1);
+ _fade_out.add (len * 0.023041, 0.697222);
+ _fade_out.add (len * 0.0553, 0.483333);
+ _fade_out.add (len * 0.170507, 0.233333);
+ _fade_out.add (len * 0.370968, 0.0861111);
+ _fade_out.add (len * 0.610599, 0.0333333);
+ _fade_out.add (len * 1, 0);
+ break;
+
+ case LogA:
+ _fade_out.add (len * 0, 1);
+ _fade_out.add (len * 0.228111, 0.988889);
+ _fade_out.add (len * 0.347926, 0.972222);
+ _fade_out.add (len * 0.529954, 0.886111);
+ _fade_out.add (len * 0.753456, 0.658333);
+ _fade_out.add (len * 0.9262673, 0.308333);
+ _fade_out.add (len * 1, 0);
+ break;
+
+ case Slow:
+ _fade_out.add (len * 0, 1);
+ _fade_out.add (len * 0.305556, 1);
+ _fade_out.add (len * 0.548611, 0.991736);
+ _fade_out.add (len * 0.759259, 0.931129);
+ _fade_out.add (len * 0.918981, 0.68595);
+ _fade_out.add (len * 0.976852, 0.22865);
+ _fade_out.add (len * 1, 0);
+ break;
+
+ case LogB:
+ _fade_out.add (len * 0, 1);
+ _fade_out.add (len * 0.080645, 0.730556);
+ _fade_out.add (len * 0.277778, 0.289256);
+ _fade_out.add (len * 0.470046, 0.152778);
+ _fade_out.add (len * 0.695853, 0.0694444);
+ _fade_out.add (len * 1, 0);
+ break;
+
+ case Linear:
+ _fade_out.add (len * 0, 1);
+ _fade_out.add (len * 1, 0);
+ break;
+ }
+
+ _fade_out.thaw ();
+ _fade_out_shape = shape;
+
+ if (!_frozen) {
+ save_state (_("fade in change"));
+ }
+
+ send_change (FadeOutChanged);
+}
+
+void
+AudioRegion::set_fade_in_length (jack_nframes_t len)
+{
+ bool changed = _fade_in.extend_to (len);
+
+ if (changed) {
+ _flags = Flag (_flags & ~DefaultFadeIn);
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "fade in length changed to %u", len);
+ save_state (buf);
+ }
+
+ send_change (FadeInChanged);
+ }
+}
+
+void
+AudioRegion::set_fade_out_length (jack_nframes_t len)
+{
+ bool changed = _fade_out.extend_to (len);
+
+ if (changed) {
+ _flags = Flag (_flags & ~DefaultFadeOut);
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "fade out length changed to %u", len);
+ save_state (buf);
+ }
+ }
+
+ send_change (FadeOutChanged);
+}
+
+void
+AudioRegion::set_fade_in_active (bool yn)
+{
+ if (yn == (_flags & FadeIn)) {
+ return;
+ }
+ if (yn) {
+ _flags = Flag (_flags|FadeIn);
+ } else {
+ _flags = Flag (_flags & ~FadeIn);
+ }
+
+ send_change (FadeInActiveChanged);
+}
+
+void
+AudioRegion::set_fade_out_active (bool yn)
+{
+ if (yn == (_flags & FadeOut)) {
+ return;
+ }
+ if (yn) {
+ _flags = Flag (_flags | FadeOut);
+ } else {
+ _flags = Flag (_flags & ~FadeOut);
+ }
+
+ send_change (FadeOutActiveChanged);
+}
+
+void
+AudioRegion::set_default_fade_in ()
+{
+ set_fade_in (Linear, 64);
+}
+
+void
+AudioRegion::set_default_fade_out ()
+{
+ set_fade_out (Linear, 64);
+}
+
+void
+AudioRegion::set_default_fades ()
+{
+ _fade_in_disabled = 0;
+ _fade_out_disabled = 0;
+ set_default_fade_in ();
+ set_default_fade_out ();
+}
+
+void
+AudioRegion::set_default_envelope ()
+{
+ _envelope.freeze ();
+ _envelope.clear ();
+ _envelope.add (0, 1.0f);
+ _envelope.add (_length, 1.0f);
+ _envelope.thaw ();
+}
+
+void
+AudioRegion::recompute_at_end ()
+{
+ /* our length has changed. recompute a new final point by interpolating
+ based on the the existing curve.
+ */
+
+ _envelope.freeze ();
+ _envelope.truncate_end (_length);
+ _envelope.set_max_xval (_length);
+ _envelope.thaw ();
+
+ if (_fade_in.back()->when > _length) {
+ _fade_in.extend_to (_length);
+ send_change (FadeInChanged);
+ }
+
+ if (_fade_out.back()->when > _length) {
+ _fade_out.extend_to (_length);
+ send_change (FadeOutChanged);
+ }
+}
+
+void
+AudioRegion::recompute_at_start ()
+{
+ /* as above, but the shift was from the front */
+
+ _envelope.truncate_start (_length);
+
+ if (_fade_in.back()->when > _length) {
+ _fade_in.extend_to (_length);
+ send_change (FadeInChanged);
+ }
+
+ if (_fade_out.back()->when > _length) {
+ _fade_out.extend_to (_length);
+ send_change (FadeOutChanged);
+ }
+}
+
+int
+AudioRegion::separate_by_channel (Session& session, vector<AudioRegion*>& v) const
+{
+ SourceList srcs;
+ string new_name;
+
+ for (SourceList::const_iterator i = master_sources.begin(); i != master_sources.end(); ++i) {
+
+ srcs.clear ();
+ srcs.push_back (*i);
+
+ /* generate a new name */
+
+ if (session.region_name (new_name, _name)) {
+ return -1;
+ }
+
+ /* create a copy with just one source */
+
+ v.push_back (new AudioRegion (srcs, _start, _length, new_name, _layer, _flags));
+ }
+
+ return 0;
+}
+
+void
+AudioRegion::source_deleted (Source* ignored)
+{
+ delete this;
+}
+
+void
+AudioRegion::lock_sources ()
+{
+ SourceList::iterator i;
+ set<Source*> unique_srcs;
+
+ for (i = sources.begin(); i != sources.end(); ++i) {
+ unique_srcs.insert (*i);
+ (*i)->use ();
+ }
+
+ for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+ if (unique_srcs.find (*i) == unique_srcs.end()) {
+ (*i)->use ();
+ }
+ }
+}
+
+void
+AudioRegion::unlock_sources ()
+{
+ SourceList::iterator i;
+ set<Source*> unique_srcs;
+
+ for (i = sources.begin(); i != sources.end(); ++i) {
+ unique_srcs.insert (*i);
+ (*i)->release ();
+ }
+
+ for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+ if (unique_srcs.find (*i) == unique_srcs.end()) {
+ (*i)->release ();
+ }
+ }
+}
+
+vector<string>
+AudioRegion::master_source_names ()
+{
+ SourceList::iterator i;
+
+ vector<string> names;
+ for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+ names.push_back((*i)->name());
+ }
+
+ return names;
+}
+
+bool
+AudioRegion::region_list_equivalent (const AudioRegion& other)
+{
+ return size_equivalent (other) && source_equivalent (other) && _name == other._name;
+}
+
+bool
+AudioRegion::source_equivalent (const AudioRegion& other)
+{
+ SourceList::iterator i;
+ SourceList::const_iterator io;
+
+ for (i = sources.begin(), io = other.sources.begin(); i != sources.end() && io != other.sources.end(); ++i, ++io) {
+ if ((*i)->id() != (*io)->id()) {
+ return false;
+ }
+ }
+
+ for (i = master_sources.begin(), io = other.master_sources.begin(); i != master_sources.end() && io != other.master_sources.end(); ++i, ++io) {
+ if ((*i)->id() != (*io)->id()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+AudioRegion::equivalent (const AudioRegion& other)
+{
+ return _start == other._start &&
+ _position == other._position &&
+ _length == other._length;
+}
+
+bool
+AudioRegion::size_equivalent (const AudioRegion& other)
+{
+ return _start == other._start &&
+ _length == other._length;
+}
+
+int
+AudioRegion::apply (AudioFilter& filter)
+{
+ return filter.run (*this);
+}
+
+int
+AudioRegion::exportme (Session& session, AudioExportSpecification& spec)
+{
+ const jack_nframes_t blocksize = 4096;
+ jack_nframes_t to_read;
+ int status = -1;
+
+ spec.channels = sources.size();
+
+ if (spec.prepare (blocksize, session.frame_rate())) {
+ goto out;
+ }
+
+ spec.pos = 0;
+ spec.total_frames = _length;
+
+ while (spec.pos < _length && !spec.stop) {
+
+
+ /* step 1: interleave */
+
+ to_read = min (_length - spec.pos, blocksize);
+
+ if (spec.channels == 1) {
+
+ if (sources.front()->read (spec.dataF, _start + spec.pos, to_read) != to_read) {
+ goto out;
+ }
+
+ } else {
+
+ Sample buf[blocksize];
+
+ for (uint32_t chan = 0; chan < spec.channels; ++chan) {
+
+ if (sources[chan]->read (buf, _start + spec.pos, to_read) != to_read) {
+ goto out;
+ }
+
+ for (jack_nframes_t x = 0; x < to_read; ++x) {
+ spec.dataF[chan+(x*spec.channels)] = buf[x];
+ }
+ }
+ }
+
+ if (spec.process (to_read)) {
+ goto out;
+ }
+
+ spec.pos += to_read;
+ spec.progress = (double) spec.pos /_length;
+
+ }
+
+ status = 0;
+
+ out:
+ spec.running = false;
+ spec.status = status;
+ spec.clear();
+
+ return status;
+}
+
+Region*
+AudioRegion::get_parent()
+{
+ Region* r = 0;
+
+ if (_playlist) {
+ r = _playlist->session().find_whole_file_parent (*this);
+ }
+
+ return r;
+}
+
+void
+AudioRegion::set_scale_amplitude (gain_t g)
+{
+ _scale_amplitude = g;
+
+ /* tell the diskstream we're in */
+
+ if (_playlist) {
+ _playlist->Modified();
+ }
+
+ /* tell everybody else */
+
+ send_change (ScaleAmplitudeChanged);
+}
+
+void
+AudioRegion::normalize_to (float target_dB)
+{
+ const jack_nframes_t blocksize = 256 * 1048;
+ Sample buf[blocksize];
+ jack_nframes_t fpos;
+ jack_nframes_t fend;
+ jack_nframes_t to_read;
+ double maxamp = 0;
+ gain_t target = dB_to_coefficient (target_dB);
+
+ if (target == 1.0f) {
+ /* do not normalize to precisely 1.0 (0 dBFS), to avoid making it appear
+ that we may have clipped.
+ */
+ target -= FLT_EPSILON;
+ }
+
+ fpos = _start;
+ fend = _start + _length;
+
+ /* first pass: find max amplitude */
+
+ while (fpos < fend) {
+
+ uint32_t n;
+
+ to_read = min (fend - fpos, blocksize);
+
+ for (n = 0; n < n_channels(); ++n) {
+
+ /* read it in */
+
+ if (source (n).read (buf, fpos, to_read) != to_read) {
+ return;
+ }
+
+ maxamp = Session::compute_peak (buf, to_read, maxamp);
+ }
+
+ fpos += to_read;
+ };
+
+ if (maxamp == 0.0f) {
+ /* don't even try */
+ return;
+ }
+
+ if (maxamp == target) {
+ /* we can't do anything useful */
+ return;
+ }
+
+ /* compute scale factor */
+
+ _scale_amplitude = target/maxamp;
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), _("normalized to %.2fdB"), target_dB);
+ save_state (buf);
+ }
+
+ /* tell the diskstream we're in */
+
+ if (_playlist) {
+ _playlist->Modified();
+ }
+
+ /* tell everybody else */
+
+ send_change (ScaleAmplitudeChanged);
+}
+
+void
+AudioRegion::envelope_changed (Change ignored)
+{
+ save_state (_("envelope change"));
+ send_change (EnvelopeChanged);
+}
+
+void
+AudioRegion::suspend_fade_in ()
+{
+ if (++_fade_in_disabled == 1) {
+ set_fade_in_active (false);
+ }
+}
+
+void
+AudioRegion::resume_fade_in ()
+{
+ if (_fade_in_disabled && --_fade_in_disabled == 0) {
+ set_fade_in_active (true);
+ }
+}
+
+void
+AudioRegion::suspend_fade_out ()
+{
+ if (++_fade_out_disabled == 1) {
+ set_fade_out_active (false);
+ }
+}
+
+void
+AudioRegion::resume_fade_out ()
+{
+ if (_fade_out_disabled && --_fade_out_disabled == 0) {
+ set_fade_out_active (true);
+ }
+}
+
+extern "C" {
+
+ int region_read_peaks_from_c (void *arg, uint32_t npeaks, uint32_t start, uint32_t cnt, intptr_t data, uint32_t n_chan, double samples_per_unit)
+{
+ return ((AudioRegion *) arg)->read_peaks ((PeakData *) data, (jack_nframes_t) npeaks, (jack_nframes_t) start, (jack_nframes_t) cnt, n_chan,samples_per_unit);
+}
+
+uint32_t region_length_from_c (void *arg)
+{
+
+ return ((AudioRegion *) arg)->length();
+}
+
+uint32_t sourcefile_length_from_c (void *arg)
+{
+ return ( (AudioRegion *) arg)->source().length() ;
+}
+
+} /* extern "C" */
diff --git a/libs/ardour/auditioner.cc b/libs/ardour/auditioner.cc
new file mode 100644
index 0000000000..ed97cf7b39
--- /dev/null
+++ b/libs/ardour/auditioner.cc
@@ -0,0 +1,181 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#include <pbd/lockmonitor.h>
+
+#include <ardour/diskstream.h>
+#include <ardour/audioregion.h>
+#include <ardour/route.h>
+#include <ardour/session.h>
+#include <ardour/auditioner.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/panner.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+Auditioner::Auditioner (Session& s)
+ : AudioTrack (s, "auditioner", Route::Hidden)
+{
+ string left = Config->get_auditioner_output_left();
+ string right = Config->get_auditioner_output_right();
+
+ if ((left.length() == 0) && (right.length() == 0)) {
+ return;
+ }
+
+ defer_pan_reset ();
+
+ if (left.length()) {
+ add_output_port (left, this);
+ }
+
+ if (right.length()) {
+ disk_stream().add_channel();
+ add_output_port (right, this);
+ }
+
+ allow_pan_reset ();
+
+ IO::output_changed.connect (mem_fun (*this, &Auditioner::output_changed));
+
+ the_region = 0;
+ atomic_set (&_active, 0);
+}
+
+Auditioner::~Auditioner ()
+{
+}
+
+AudioPlaylist&
+Auditioner::prepare_playlist ()
+{
+ diskstream->playlist()->clear (false, false);
+ return *diskstream->playlist();
+}
+
+void
+Auditioner::audition_current_playlist ()
+{
+ if (atomic_read (&_active)) {
+ /* don't go via session for this, because we are going
+ to remain active.
+ */
+ cancel_audition ();
+ }
+
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ diskstream->seek (0);
+ length = diskstream->playlist()->get_maximum_extent();
+ current_frame = 0;
+
+ /* force a panner reset now that we have all channels */
+
+ _panner->reset (n_outputs(), diskstream->n_channels());
+
+ atomic_set (&_active, 1);
+}
+
+void
+Auditioner::audition_region (AudioRegion& region)
+{
+ if (atomic_read (&_active)) {
+ /* don't go via session for this, because we are going
+ to remain active.
+ */
+ cancel_audition ();
+ }
+
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ the_region = new AudioRegion (region);
+ the_region->set_position (0, this);
+
+ diskstream->playlist()->clear (true, false);
+ diskstream->playlist()->add_region (*the_region, 0, 1, false);
+
+ while (diskstream->n_channels() < the_region->n_channels()) {
+ diskstream->add_channel ();
+ }
+
+ while (diskstream->n_channels() > the_region->n_channels()) {
+ diskstream->remove_channel ();
+ }
+
+ /* force a panner reset now that we have all channels */
+
+ _panner->reset (n_outputs(), diskstream->n_channels());
+
+ length = the_region->length();
+ diskstream->seek (0);
+ current_frame = 0;
+ atomic_set (&_active, 1);
+}
+
+int
+Auditioner::play_audition (jack_nframes_t nframes)
+{
+ bool need_butler;
+ jack_nframes_t this_nframes;
+ int ret;
+
+ if (atomic_read (&_active) == 0) {
+ silence (nframes, 0);
+ return 0;
+ }
+
+ this_nframes = min (nframes, length - current_frame);
+
+ diskstream->prepare ();
+
+ if ((ret = roll (this_nframes, current_frame, current_frame + nframes, 0, false, false, false)) != 0) {
+ silence (nframes, 0);
+ return ret;
+ }
+
+ need_butler = diskstream->commit (this_nframes);
+ current_frame += this_nframes;
+
+ if (current_frame >= length) {
+ _session.cancel_audition ();
+ return 0;
+ } else {
+ return need_butler ? 1 : 0;
+ }
+}
+
+void
+Auditioner::output_changed (IOChange change, void* src)
+{
+ if (change & ConnectionsChanged) {
+ const char ** connections;
+ connections = output (0)->get_connections ();
+ if (connections) {
+ Config->set_auditioner_output_left (connections[0]);
+ free (connections);
+ }
+
+ connections = output (1)->get_connections ();
+ if (connections) {
+ Config->set_auditioner_output_right (connections[0]);
+ free (connections);
+ }
+ }
+}
diff --git a/libs/ardour/automation.cc b/libs/ardour/automation.cc
new file mode 100644
index 0000000000..2e3116ba17
--- /dev/null
+++ b/libs/ardour/automation.cc
@@ -0,0 +1,13 @@
+#include <stdint.h>
+
+template<class AutomatedObject>
+struct AutomationEvent {
+ uint32_t frame;
+ AutomatedObject *object;
+ void (AutomatedObject::*function) (void *);
+ void *arg;
+
+ void operator() (){
+ object->function (arg);
+ }
+};
diff --git a/libs/ardour/automation_event.cc b/libs/ardour/automation_event.cc
new file mode 100644
index 0000000000..ea7e0e1b71
--- /dev/null
+++ b/libs/ardour/automation_event.cc
@@ -0,0 +1,1211 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#include <set>
+#include <climits>
+#include <float.h>
+#include <cmath>
+#include <algorithm>
+#include <sigc++/bind.h>
+#include <ardour/automation_event.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace sigc;
+
+#if 0
+static void dumpit (const AutomationList& al, string prefix = "")
+{
+ cerr << prefix << &al << endl;
+ for (AutomationList::const_iterator i = al.const_begin(); i != al.const_end(); ++i) {
+ cerr << prefix << '\t' << (*i)->when << ',' << (*i)->value << endl;
+ }
+ cerr << "\n";
+}
+#endif
+
+AutomationList::AutomationList (double defval, bool with_state)
+{
+ _frozen = false;
+ changed_when_thawed = false;
+ _state = Off;
+ _style = Absolute;
+ _touching = false;
+ no_state = with_state;
+ min_yval = FLT_MIN;
+ max_yval = FLT_MAX;
+ max_xval = 0; // means "no limit"
+ default_value = defval;
+ _dirty = false;
+ rt_insertion_point = events.end();
+ lookup_cache.left = -1;
+ lookup_cache.range.first = events.end();
+
+ if (!no_state) {
+ save_state (_("initial"));
+ }
+}
+
+AutomationList::AutomationList (const AutomationList& other)
+{
+ _frozen = false;
+ changed_when_thawed = false;
+ _style = other._style;
+ min_yval = other.min_yval;
+ max_yval = other.max_yval;
+ max_xval = other.max_xval;
+ default_value = other.default_value;
+ _state = other._state;
+ _touching = other._touching;
+ _dirty = false;
+ rt_insertion_point = events.end();
+ no_state = other.no_state;
+ lookup_cache.left = -1;
+ lookup_cache.range.first = events.end();
+
+ for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
+ /* we have to use other point_factory() because
+ its virtual and we're in a constructor.
+ */
+ events.push_back (other.point_factory (**i));
+ }
+
+ mark_dirty ();
+}
+
+AutomationList::AutomationList (const AutomationList& other, double start, double end)
+{
+ _frozen = false;
+ changed_when_thawed = false;
+ _style = other._style;
+ min_yval = other.min_yval;
+ max_yval = other.max_yval;
+ max_xval = other.max_xval;
+ default_value = other.default_value;
+ _state = other._state;
+ _touching = other._touching;
+ _dirty = false;
+ rt_insertion_point = events.end();
+ no_state = other.no_state;
+ lookup_cache.left = -1;
+ lookup_cache.range.first = events.end();
+
+ /* now grab the relevant points, and shift them back if necessary */
+
+ AutomationList* section = const_cast<AutomationList*>(&other)->copy (start, end);
+
+ if (!section->empty()) {
+ for (AutomationList::iterator i = section->begin(); i != section->end(); ++i) {
+ events.push_back (other.point_factory ((*i)->when, (*i)->value));
+ }
+ }
+
+ delete section;
+
+ mark_dirty ();
+}
+
+AutomationList::~AutomationList()
+{
+ std::set<ControlEvent*> all_events;
+ AutomationList::State* asp;
+
+ for (AutomationEventList::iterator x = events.begin(); x != events.end(); ++x) {
+ all_events.insert (*x);
+ }
+
+ for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+
+ if ((asp = dynamic_cast<AutomationList::State*> (*i)) != 0) {
+
+ for (AutomationEventList::iterator x = asp->events.begin(); x != asp->events.end(); ++x) {
+ all_events.insert (*x);
+ }
+ }
+ }
+
+ for (std::set<ControlEvent*>::iterator i = all_events.begin(); i != all_events.end(); ++i) {
+ delete (*i);
+ }
+}
+
+bool
+AutomationList::operator== (const AutomationList& other)
+{
+ return events == other.events;
+}
+
+AutomationList&
+AutomationList::operator= (const AutomationList& other)
+{
+ if (this != &other) {
+
+ events.clear ();
+
+ for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
+ events.push_back (point_factory (**i));
+ }
+
+ min_yval = other.min_yval;
+ max_yval = other.max_yval;
+ max_xval = other.max_xval;
+ default_value = other.default_value;
+
+ mark_dirty ();
+ maybe_signal_changed ();
+ }
+
+ return *this;
+}
+
+void
+AutomationList::maybe_signal_changed ()
+{
+ mark_dirty ();
+
+ if (_frozen) {
+ changed_when_thawed = true;
+ } else {
+ StateChanged (Change (0));
+ }
+}
+
+void
+AutomationList::set_automation_state (AutoState s)
+{
+ if (s != _state) {
+ _state = s;
+ automation_state_changed (); /* EMIT SIGNAL */
+ }
+}
+
+void
+AutomationList::set_automation_style (AutoStyle s)
+{
+ if (s != _style) {
+ _style = s;
+ automation_style_changed (); /* EMIT SIGNAL */
+ }
+}
+
+void
+AutomationList::start_touch ()
+{
+ _touching = true;
+ _new_touch = true;
+}
+
+void
+AutomationList::stop_touch ()
+{
+ _touching = false;
+ _new_touch = false;
+}
+
+void
+AutomationList::clear ()
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ events.clear ();
+ if (!no_state) {
+ save_state (_("cleared"));
+ }
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+AutomationList::x_scale (double factor)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ _x_scale (factor);
+}
+
+bool
+AutomationList::extend_to (double when)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ if (events.empty() || events.back()->when == when) {
+ return false;
+ }
+ double factor = when / events.back()->when;
+ _x_scale (factor);
+ return true;
+}
+
+void AutomationList::_x_scale (double factor)
+{
+ for (AutomationList::iterator i = events.begin(); i != events.end(); ++i) {
+ (*i)->when = floor ((*i)->when * factor);
+ }
+
+ save_state ("x-scaled");
+ mark_dirty ();
+}
+
+void
+AutomationList::reposition_for_rt_add (double when)
+{
+ rt_insertion_point = events.end();
+}
+
+#define last_rt_insertion_point rt_insertion_point
+
+void
+AutomationList::rt_add (double when, double value)
+{
+ /* this is for automation recording */
+
+ if ((_state & Touch) && !_touching) {
+ return;
+ }
+
+ // cerr << "RT: alist @ " << this << " add " << value << " @ " << when << endl;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ iterator where;
+ TimeComparator cmp;
+ ControlEvent cp (when, 0.0);
+ bool done = false;
+
+ if ((last_rt_insertion_point != events.end()) && ((*last_rt_insertion_point)->when < when) ) {
+
+ /* we have a previous insertion point, so we should delete
+ everything between it and the position where we are going
+ to insert this point.
+ */
+
+ iterator after = last_rt_insertion_point;
+
+ if (++after != events.end()) {
+ iterator far = after;
+
+ while (far != events.end()) {
+ if ((*far)->when > when) {
+ break;
+ }
+ ++far;
+ }
+
+ if(_new_touch) {
+ where = far;
+ last_rt_insertion_point = where;
+
+ if((*where)->when == when) {
+ (*where)->value = value;
+ done = true;
+ }
+ } else {
+ where = events.erase (after, far);
+ }
+
+ } else {
+
+ where = after;
+
+ }
+
+ iterator previous = last_rt_insertion_point;
+ --previous;
+
+ if (last_rt_insertion_point != events.begin() && (*last_rt_insertion_point)->value == value && (*previous)->value == value) {
+ (*last_rt_insertion_point)->when = when;
+ done = true;
+
+ }
+
+ } else {
+
+ where = lower_bound (events.begin(), events.end(), &cp, cmp);
+
+ if (where != events.end()) {
+ if ((*where)->when == when) {
+ (*where)->value = value;
+ done = true;
+ }
+ }
+ }
+
+ if (!done) {
+ last_rt_insertion_point = events.insert (where, point_factory (when, value));
+ }
+
+ _new_touch = false;
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+#undef last_rt_insertion_point
+
+void
+AutomationList::add (double when, double value, bool for_loading)
+{
+ /* this is for graphical editing and loading data from storage */
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ TimeComparator cmp;
+ ControlEvent cp (when, 0.0f);
+ bool insert = true;
+ iterator insertion_point;
+
+ for (insertion_point = lower_bound (events.begin(), events.end(), &cp, cmp); insertion_point != events.end(); ++insertion_point) {
+
+ /* only one point allowed per time point */
+
+ if ((*insertion_point)->when == when) {
+ (*insertion_point)->value = value;
+ insert = false;
+ break;
+ }
+
+ if ((*insertion_point)->when >= when) {
+ break;
+ }
+ }
+
+ if (insert) {
+
+ events.insert (insertion_point, point_factory (when, value));
+ reposition_for_rt_add (0);
+
+ }
+
+ mark_dirty ();
+
+ if (!no_state && !for_loading) {
+ save_state (_("added event"));
+ }
+ }
+
+ if (!for_loading) {
+ maybe_signal_changed ();
+ }
+}
+
+void
+AutomationList::erase (AutomationList::iterator i)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ events.erase (i);
+ reposition_for_rt_add (0);
+ if (!no_state) {
+ save_state (_("removed event"));
+ }
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+AutomationList::erase (AutomationList::iterator start, AutomationList::iterator end)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ events.erase (start, end);
+ reposition_for_rt_add (0);
+ if (!no_state) {
+ save_state (_("removed multiple events"));
+ }
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+AutomationList::erase_range (double start, double endt)
+{
+ bool erased = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ TimeComparator cmp;
+ ControlEvent cp (start, 0.0f);
+ iterator s;
+ iterator e;
+
+ if ((s = lower_bound (events.begin(), events.end(), &cp, cmp)) != events.end()) {
+ cp.when = endt;
+ e = upper_bound (events.begin(), events.end(), &cp, cmp);
+ events.erase (s, e);
+ reposition_for_rt_add (0);
+ erased = true;
+ if (!no_state) {
+ save_state (_("removed range"));
+ }
+ mark_dirty ();
+ }
+
+ }
+
+ if (erased) {
+ maybe_signal_changed ();
+ }
+}
+
+void
+AutomationList::move_range (iterator start, iterator end, double xdelta, double ydelta)
+{
+ /* note: we assume higher level logic is in place to avoid this
+ reordering the time-order of control events in the list. ie. all
+ points after end are later than (end)->when.
+ */
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ while (start != end) {
+ (*start)->when += xdelta;
+ (*start)->value += ydelta;
+ ++start;
+ }
+
+ if (!no_state) {
+ save_state (_("event range adjusted"));
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+AutomationList::modify (iterator iter, double when, double val)
+{
+ /* note: we assume higher level logic is in place to avoid this
+ reordering the time-order of control events in the list. ie. all
+ points after *iter are later than when.
+ */
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ (*iter)->when = when;
+ (*iter)->value = val;
+ if (!no_state) {
+ save_state (_("event adjusted"));
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+std::pair<AutomationList::iterator,AutomationList::iterator>
+AutomationList::control_points_adjacent (double xval)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ iterator i;
+ TimeComparator cmp;
+ ControlEvent cp (xval, 0.0f);
+ std::pair<iterator,iterator> ret;
+
+ ret.first = events.end();
+ ret.second = events.end();
+
+ for (i = lower_bound (events.begin(), events.end(), &cp, cmp); i != events.end(); ++i) {
+
+ if (ret.first == events.end()) {
+ if ((*i)->when >= xval) {
+ if (i != events.begin()) {
+ ret.first = i;
+ --ret.first;
+ } else {
+ return ret;
+ }
+ }
+ }
+
+ if ((*i)->when > xval) {
+ ret.second = i;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void
+AutomationList::freeze ()
+{
+ _frozen = true;
+}
+
+void
+AutomationList::thaw ()
+{
+ _frozen = false;
+ if (changed_when_thawed) {
+ StateChanged(Change(0)); /* EMIT SIGNAL */
+ }
+}
+
+StateManager::State*
+AutomationList::state_factory (std::string why) const
+{
+ State* state = new State (why);
+
+ for (AutomationEventList::const_iterator x = events.begin(); x != events.end(); ++x) {
+ state->events.push_back (point_factory (**x));
+ }
+
+ return state;
+}
+
+Change
+AutomationList::restore_state (StateManager::State& state)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ State* lstate = dynamic_cast<State*> (&state);
+
+ events.clear ();
+ for (AutomationEventList::const_iterator x = lstate->events.begin(); x != lstate->events.end(); ++x) {
+ events.push_back (point_factory (**x));
+ }
+ }
+
+ return Change (0);
+}
+
+UndoAction
+AutomationList::get_memento () const
+{
+ return sigc::bind (mem_fun (*(const_cast<AutomationList*> (this)), &StateManager::use_state), _current_state_id);
+}
+
+void
+AutomationList::set_max_xval (double x)
+{
+ max_xval = x;
+}
+
+void
+AutomationList::mark_dirty ()
+{
+ lookup_cache.left = -1;
+ _dirty = true;
+}
+
+void
+AutomationList::truncate_end (double last_coordinate)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ ControlEvent cp (last_coordinate, 0);
+ list<ControlEvent*>::reverse_iterator i;
+ double last_val;
+
+ if (events.empty()) {
+ fatal << _("programming error:")
+ << "AutomationList::truncate_end() called on an empty list"
+ << endmsg;
+ /*NOTREACHED*/
+ return;
+ }
+
+ if (last_coordinate == events.back()->when) {
+ return;
+ }
+
+ if (last_coordinate > events.back()->when) {
+
+ /* extending end:
+ */
+
+ iterator foo = events.begin();
+ bool lessthantwo;
+
+ if (foo == events.end()) {
+ lessthantwo = true;
+ } else if (++foo == events.end()) {
+ lessthantwo = true;
+ } else {
+ lessthantwo = false;
+ }
+
+ if (lessthantwo) {
+ /* less than 2 points: add a new point */
+ events.push_back (point_factory (last_coordinate, events.back()->value));
+ } else {
+
+ /* more than 2 points: check to see if the last 2 values
+ are equal. if so, just move the position of the
+ last point. otherwise, add a new point.
+ */
+
+ iterator penultimate = events.end();
+ --penultimate; /* points at last point */
+ --penultimate; /* points at the penultimate point */
+
+ if (events.back()->value == (*penultimate)->value) {
+ events.back()->when = last_coordinate;
+ } else {
+ events.push_back (point_factory (last_coordinate, events.back()->value));
+ }
+ }
+
+ } else {
+
+ /* shortening end */
+
+ last_val = unlocked_eval (last_coordinate);
+ last_val = max ((double) min_yval, last_val);
+ last_val = min ((double) max_yval, last_val);
+
+ i = events.rbegin();
+
+ /* make i point to the last control point */
+
+ ++i;
+
+ /* now go backwards, removing control points that are
+ beyond the new last coordinate.
+ */
+
+ uint32_t sz = events.size();
+
+ while (i != events.rend() && sz > 2) {
+ list<ControlEvent*>::reverse_iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->when < last_coordinate) {
+ break;
+ }
+
+ events.erase (i.base());
+ --sz;
+
+ i = tmp;
+ }
+
+ events.back()->when = last_coordinate;
+ events.back()->value = last_val;
+ }
+
+ reposition_for_rt_add (0);
+ mark_dirty();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+AutomationList::truncate_start (double overall_length)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ AutomationList::iterator i;
+ double first_legal_value;
+ double first_legal_coordinate;
+
+ if (events.empty()) {
+ fatal << _("programming error:")
+ << "AutomationList::truncate_start() called on an empty list"
+ << endmsg;
+ /*NOTREACHED*/
+ return;
+ }
+
+ if (overall_length == events.back()->when) {
+ /* no change in overall length */
+ return;
+ }
+
+ if (overall_length > events.back()->when) {
+
+ /* growing at front: duplicate first point. shift all others */
+
+ double shift = overall_length - events.back()->when;
+ uint32_t np;
+
+ for (np = 0, i = events.begin(); i != events.end(); ++i, ++np) {
+ (*i)->when += shift;
+ }
+
+ if (np < 2) {
+
+ /* less than 2 points: add a new point */
+ events.push_front (point_factory (0, events.front()->value));
+
+ } else {
+
+ /* more than 2 points: check to see if the first 2 values
+ are equal. if so, just move the position of the
+ first point. otherwise, add a new point.
+ */
+
+ iterator second = events.begin();
+ ++second; /* points at the second point */
+
+ if (events.front()->value == (*second)->value) {
+ /* first segment is flat, just move start point back to zero */
+ events.front()->when = 0;
+ } else {
+ /* leave non-flat segment in place, add a new leading point. */
+ events.push_front (point_factory (0, events.front()->value));
+ }
+ }
+
+ } else {
+
+ /* shrinking at front */
+
+ first_legal_coordinate = events.back()->when - overall_length;
+ first_legal_value = unlocked_eval (first_legal_coordinate);
+ first_legal_value = max (min_yval, first_legal_value);
+ first_legal_value = min (max_yval, first_legal_value);
+
+ /* remove all events earlier than the new "front" */
+
+ i = events.begin();
+
+ while (i != events.end() && !events.empty()) {
+ list<ControlEvent*>::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->when > first_legal_coordinate) {
+ break;
+ }
+
+ events.erase (i);
+
+ i = tmp;
+ }
+
+
+ /* shift all remaining points left to keep their same
+ relative position
+ */
+
+ for (i = events.begin(); i != events.end(); ++i) {
+ (*i)->when -= first_legal_coordinate;
+ }
+
+ /* add a new point for the interpolated new value */
+
+ events.push_front (point_factory (0, first_legal_value));
+ }
+
+ reposition_for_rt_add (0);
+
+ mark_dirty();
+ }
+
+ maybe_signal_changed ();
+}
+
+double
+AutomationList::unlocked_eval (double x)
+{
+ return shared_eval (x);
+}
+
+double
+AutomationList::shared_eval (double x)
+{
+ pair<AutomationEventList::iterator,AutomationEventList::iterator> range;
+ int32_t npoints;
+ double lpos, upos;
+ double lval, uval;
+ double fraction;
+
+ npoints = events.size();
+
+ switch (npoints) {
+ case 0:
+ return default_value;
+
+ case 1:
+ if (x >= events.front()->when) {
+ return events.front()->value;
+ } else {
+ // return default_value;
+ return events.front()->value;
+ }
+
+ case 2:
+ if (x >= events.back()->when) {
+ return events.back()->value;
+ } else if (x == events.front()->when) {
+ return events.front()->value;
+ } else if (x < events.front()->when) {
+ // return default_value;
+ return events.front()->value;
+ }
+
+ lpos = events.front()->when;
+ lval = events.front()->value;
+ upos = events.back()->when;
+ uval = events.back()->value;
+
+ /* linear interpolation betweeen the two points
+ */
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+ return lval + (fraction * (uval - lval));
+
+ default:
+
+ if (x >= events.back()->when) {
+ return events.back()->value;
+ } else if (x == events.front()->when) {
+ return events.front()->value;
+ } else if (x < events.front()->when) {
+ // return default_value;
+ return events.front()->value;
+ }
+
+ return multipoint_eval (x);
+ break;
+ }
+}
+
+double
+AutomationList::multipoint_eval (double x)
+{
+ pair<AutomationList::iterator,AutomationList::iterator> range;
+ double upos, lpos;
+ double uval, lval;
+ double fraction;
+
+ /* only do the range lookup if x is in a different range than last time
+ this was called (or if the lookup cache has been marked "dirty" (left<0)
+ */
+
+ if ((lookup_cache.left < 0) ||
+ ((lookup_cache.left > x) ||
+ (lookup_cache.range.first == events.end()) ||
+ ((*lookup_cache.range.second)->when < x))) {
+
+ ControlEvent cp (x, 0);
+ TimeComparator cmp;
+
+ lookup_cache.range = equal_range (events.begin(), events.end(), &cp, cmp);
+ }
+
+ range = lookup_cache.range;
+
+ if (range.first == range.second) {
+
+ /* x does not exist within the list as a control point */
+
+ lookup_cache.left = x;
+
+ if (range.first != events.begin()) {
+ --range.first;
+ lpos = (*range.first)->when;
+ lval = (*range.first)->value;
+ } else {
+ /* we're before the first point */
+ // return default_value;
+ return events.front()->value;
+ }
+
+ if (range.second == events.end()) {
+ /* we're after the last point */
+ return events.back()->value;
+ }
+
+ upos = (*range.second)->when;
+ uval = (*range.second)->value;
+
+ /* linear interpolation betweeen the two points
+ on either side of x
+ */
+
+ fraction = (double) (x - lpos) / (double) (upos - lpos);
+ return lval + (fraction * (uval - lval));
+
+ }
+
+ /* x is a control point in the data */
+ lookup_cache.left = -1;
+ return (*range.first)->value;
+}
+
+AutomationList*
+AutomationList::cut (iterator start, iterator end)
+{
+ AutomationList* nal = new AutomationList (default_value);
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ for (iterator x = start; x != end; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ nal->events.push_back (point_factory (**x));
+ events.erase (x);
+
+ reposition_for_rt_add (0);
+
+ x = tmp;
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+
+ return nal;
+}
+
+AutomationList*
+AutomationList::cut_copy_clear (double start, double end, int op)
+{
+ AutomationList* nal = new AutomationList (default_value);
+ iterator s, e;
+ ControlEvent cp (start, 0.0);
+ TimeComparator cmp;
+ bool changed = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ if ((s = lower_bound (events.begin(), events.end(), &cp, cmp)) == events.end()) {
+ return nal;
+ }
+
+ cp.when = end;
+ e = upper_bound (events.begin(), events.end(), &cp, cmp);
+
+ if (op != 2 && (*s)->when != start) {
+ nal->events.push_back (point_factory (0, unlocked_eval (start)));
+ }
+
+ for (iterator x = s; x != e; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ changed = true;
+
+ /* adjust new points to be relative to start, which
+ has been set to zero.
+ */
+
+ if (op != 2) {
+ nal->events.push_back (point_factory ((*x)->when - start, (*x)->value));
+ }
+
+ if (op != 1) {
+ events.erase (x);
+ }
+
+ x = tmp;
+ }
+
+ if (op != 2 && nal->events.back()->when != end - start) {
+ nal->events.push_back (point_factory (end - start, unlocked_eval (end)));
+ }
+
+ if (changed) {
+ reposition_for_rt_add (0);
+ if (!no_state) {
+ save_state (_("cut/copy/clear"));
+ }
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+
+ return nal;
+
+}
+
+AutomationList*
+AutomationList::copy (iterator start, iterator end)
+{
+ AutomationList* nal = new AutomationList (default_value);
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ for (iterator x = start; x != end; ) {
+ iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ nal->events.push_back (point_factory (**x));
+
+ x = tmp;
+ }
+
+ if (!no_state) {
+ save_state (_("copy"));
+ }
+ }
+
+ return nal;
+}
+
+AutomationList*
+AutomationList::cut (double start, double end)
+{
+ return cut_copy_clear (start, end, 0);
+}
+
+AutomationList*
+AutomationList::copy (double start, double end)
+{
+ return cut_copy_clear (start, end, 1);
+}
+
+void
+AutomationList::clear (double start, double end)
+{
+ (void) cut_copy_clear (start, end, 2);
+}
+
+bool
+AutomationList::paste (AutomationList& alist, double pos, float times)
+{
+ if (alist.events.empty()) {
+ return false;
+ }
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ iterator where;
+ iterator prev;
+ double end = 0;
+ ControlEvent cp (pos, 0.0);
+ TimeComparator cmp;
+
+ where = upper_bound (events.begin(), events.end(), &cp, cmp);
+
+ for (iterator i = alist.begin();i != alist.end(); ++i) {
+ events.insert (where, point_factory( (*i)->when+pos,( *i)->value));
+ end = (*i)->when + pos;
+ }
+
+
+ /* move all points after the insertion along the timeline by
+ the correct amount.
+ */
+
+ while (where != events.end()) {
+ iterator tmp;
+ if ((*where)->when <= end) {
+ tmp = where;
+ ++tmp;
+ events.erase(where);
+ where = tmp;
+
+ } else {
+ break;
+ }
+ }
+
+ reposition_for_rt_add (0);
+
+ if (!no_state) {
+ save_state (_("paste"));
+ }
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+ return true;
+}
+
+ControlEvent*
+AutomationList::point_factory (double when, double val) const
+{
+ return new ControlEvent (when, val);
+}
+
+ControlEvent*
+AutomationList::point_factory (const ControlEvent& other) const
+{
+ return new ControlEvent (other);
+}
+
+void
+AutomationList::store_state (XMLNode& node) const
+{
+ LocaleGuard lg (X_("POSIX"));
+
+ for (const_iterator i = const_begin(); i != const_end(); ++i) {
+ char buf[64];
+
+ XMLNode *pointnode = new XMLNode ("point");
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*i)->when));
+ pointnode->add_property ("x", buf);
+ snprintf (buf, sizeof (buf), "%f", (*i)->value);
+ pointnode->add_property ("y", buf);
+
+ node.add_child_nocopy (*pointnode);
+ }
+}
+
+void
+AutomationList::load_state (const XMLNode& node)
+{
+ const XMLNodeList& elist = node.children();
+ XMLNodeConstIterator i;
+ XMLProperty* prop;
+ jack_nframes_t x;
+ double y;
+
+ clear ();
+
+ for (i = elist.begin(); i != elist.end(); ++i) {
+
+ if ((prop = (*i)->property ("x")) == 0) {
+ error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
+ continue;
+ }
+ x = atoi (prop->value().c_str());
+
+ if ((prop = (*i)->property ("y")) == 0) {
+ error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
+ continue;
+ }
+ y = atof (prop->value().c_str());
+
+ add (x, y);
+ }
+}
diff --git a/libs/ardour/configuration.cc b/libs/ardour/configuration.cc
new file mode 100644
index 0000000000..f7452f4fd0
--- /dev/null
+++ b/libs/ardour/configuration.cc
@@ -0,0 +1,1105 @@
+/*
+ Copyright (C) 1999 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.
+
+ $Id$
+*/
+
+#include <unistd.h>
+#include <cstdio> /* for snprintf, grrr */
+
+#ifdef HAVE_WORDEXP
+#include <wordexp.h>
+#endif
+
+#include <pbd/failed_constructor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/diskstream.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+
+/* this is global so that we do not have to indirect through an object pointer
+ to reference it.
+*/
+
+namespace ARDOUR {
+ float speed_quietning = 0.251189; // -12dB reduction for ffwd or rewind
+}
+
+Configuration::Configuration ()
+{
+ key_node = 0;
+ user_configuration = false;
+ set_defaults ();
+}
+
+Configuration::~Configuration ()
+{
+}
+
+string
+Configuration::get_user_path()
+{
+ char *envvar;
+
+ if ((envvar = getenv ("ARDOUR_RC")) != 0) {
+ return envvar;
+ }
+
+ return find_config_file ("ardour.rc");
+}
+
+string
+Configuration::get_system_path()
+{
+ char* envvar;
+
+ if ((envvar = getenv ("ARDOUR_SYSTEM_RC")) != 0) {
+ return envvar;
+ }
+
+ return find_config_file ("ardour_system.rc");
+}
+
+int
+Configuration::load_state ()
+{
+ string rcfile;
+
+ /* load system configuration first */
+
+ rcfile = get_system_path ();
+
+ if (rcfile.length()) {
+
+ XMLTree tree;
+
+ cerr << "Loading system configuration file " << rcfile << endl;
+
+ if (!tree.read (rcfile.c_str())) {
+ error << compose(_("Ardour: cannot read system configuration file \"%1\""), rcfile) << endmsg;
+ return -1;
+ }
+
+ if (set_state (*tree.root())) {
+ error << compose(_("Ardour: system configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
+ return -1;
+ }
+ }
+
+ /* from this point on, all configuration changes are user driven */
+
+ user_configuration = true;
+
+ /* now load configuration file for user */
+
+ rcfile = get_user_path ();
+
+ if (rcfile.length()) {
+
+ XMLTree tree;
+
+ cerr << "Loading user configuration file " << rcfile << endl;
+
+ if (!tree.read (rcfile)) {
+ error << compose(_("Ardour: cannot read configuration file \"%1\""), rcfile) << endmsg;
+ return -1;
+ }
+
+
+ if (set_state (*tree.root())) {
+ error << compose(_("Ardour: configuration file \"%1\" not loaded successfully."), rcfile) << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Configuration::save_state()
+{
+ XMLTree tree;
+ string rcfile;
+ char *envvar;
+
+ /* Note: this only writes the per-user file, and therefore
+ only saves variables marked as user-set or modified
+ */
+
+ if ((envvar = getenv ("ARDOUR_RC")) != 0) {
+ if (strlen (envvar) == 0) {
+ return -1;
+ }
+ rcfile = envvar;
+ } else {
+
+ if ((envvar = getenv ("HOME")) == 0) {
+ return -1;
+ }
+ if (strlen (envvar) == 0) {
+ return -1;
+ }
+ rcfile = envvar;
+ rcfile += "/.ardour/ardour.rc";
+ }
+
+ if (rcfile.length()) {
+ tree.set_root (&state (true));
+ if (!tree.write (rcfile.c_str())){
+ error << _("Config file not saved") << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+XMLNode&
+Configuration::get_state ()
+{
+ return state (false);
+}
+
+XMLNode&
+Configuration::state (bool user_only)
+{
+ XMLNode* root = new XMLNode("Ardour");
+ LocaleGuard lg (X_("POSIX"));
+
+ typedef map<string, MidiPortDescriptor*>::const_iterator CI;
+ for(CI m = midi_ports.begin(); m != midi_ports.end(); ++m){
+ root->add_child_nocopy(m->second->get_state());
+ }
+
+ XMLNode* node = new XMLNode("Config");
+ char buf[32];
+
+ if (!user_only || minimum_disk_io_bytes_is_user) {
+ snprintf(buf, sizeof(buf), "%" PRIu32 , minimum_disk_io_bytes);
+ node->add_child_nocopy(option_node("minimum-disk-io-bytes", string(buf)));
+ }
+ if (!user_only || track_buffer_seconds_is_user) {
+ snprintf(buf, sizeof(buf), "%f", track_buffer_seconds);
+ node->add_child_nocopy(option_node("track-buffer-seconds", string(buf)));
+ }
+ if (!user_only || disk_choice_space_threshold_is_user) {
+ snprintf(buf, sizeof(buf), "%" PRIu32, disk_choice_space_threshold);
+ node->add_child_nocopy(option_node("disk-choice-space-threshold", string(buf)));
+ }
+
+ if (!user_only || mute_affects_pre_fader_is_user) {
+ node->add_child_nocopy(option_node("mute-affects-pre-fader", mute_affects_pre_fader?"yes":"no"));
+ }
+ if (!user_only || mute_affects_post_fader_is_user) {
+ node->add_child_nocopy(option_node("mute-affects-post-fader", mute_affects_post_fader?"yes":"no"));
+ }
+ if (!user_only || mute_affects_control_outs_is_user) {
+ node->add_child_nocopy(option_node("mute-affects-control-outs", mute_affects_control_outs?"yes":"no"));
+ }
+ if (!user_only || mute_affects_main_outs_is_user) {
+ node->add_child_nocopy(option_node("mute-affects-main-outs", mute_affects_main_outs?"yes":"no"));
+ }
+ if (!user_only || solo_latch_is_user) {
+ node->add_child_nocopy(option_node("solo-latch", solo_latch?"yes":"no"));
+ }
+ if (!user_only || raid_path_is_user) {
+ node->add_child_nocopy(option_node("raid-path", orig_raid_path));
+ }
+ if (!user_only || mtc_port_name_is_user) {
+ node->add_child_nocopy(option_node("mtc-port", mtc_port_name));
+ }
+ if (!user_only || mmc_port_name_is_user) {
+ node->add_child_nocopy(option_node("mmc-port", mmc_port_name));
+ }
+ if (!user_only || midi_port_name_is_user) {
+ node->add_child_nocopy(option_node("midi-port", midi_port_name));
+ }
+ if (!user_only || use_hardware_monitoring_is_user) {
+ node->add_child_nocopy(option_node("hardware-monitoring", use_hardware_monitoring?"yes":"no"));
+ }
+ if (!user_only || be_jack_time_master_is_user) {
+ node->add_child_nocopy(option_node("jack-time-master", be_jack_time_master?"yes":"no"));
+ }
+ if (!user_only || native_format_is_bwf_is_user) {
+ node->add_child_nocopy(option_node("native-format-bwf", native_format_is_bwf?"yes":"no"));
+ }
+ if (!user_only || trace_midi_input_is_user) {
+ node->add_child_nocopy(option_node("trace-midi-input", trace_midi_input?"yes":"no"));
+ }
+ if (!user_only || trace_midi_output_is_user) {
+ node->add_child_nocopy(option_node("trace-midi-output", trace_midi_output?"yes":"no"));
+ }
+ if (!user_only || plugins_stop_with_transport_is_user) {
+ node->add_child_nocopy(option_node("plugins-stop-with-transport", plugins_stop_with_transport?"yes":"no"));
+ }
+ if (!user_only || no_sw_monitoring_is_user) {
+ node->add_child_nocopy(option_node("no-sw-monitoring", no_sw_monitoring?"yes":"no"));
+ }
+ if (!user_only || stop_recording_on_xrun_is_user) {
+ node->add_child_nocopy(option_node("stop-recording-on-xrun", stop_recording_on_xrun?"yes":"no"));
+ }
+ if (!user_only || verify_remove_last_capture_is_user) {
+ node->add_child_nocopy(option_node("verify-remove-last-capture", verify_remove_last_capture?"yes":"no"));
+ }
+ if (!user_only || stop_at_session_end_is_user) {
+ node->add_child_nocopy(option_node("stop-at-session-end", stop_at_session_end?"yes":"no"));
+ }
+ if (!user_only || seamless_looping_is_user) {
+ node->add_child_nocopy(option_node("seamless-loop", seamless_looping?"yes":"no"));
+ }
+ if (!user_only || auto_xfade_is_user) {
+ node->add_child_nocopy(option_node("auto-xfade", auto_xfade?"yes":"no"));
+ }
+ if (!user_only || no_new_session_dialog_is_user) {
+ node->add_child_nocopy(option_node("no-new-session-dialog", no_new_session_dialog?"yes":"no"));
+ }
+ if (!user_only || timecode_source_is_synced_is_user) {
+ node->add_child_nocopy(option_node("timecode-source-is-synced", timecode_source_is_synced?"yes":"no"));
+ }
+ if (!user_only || auditioner_output_left_is_user) {
+ node->add_child_nocopy(option_node("auditioner-left-out", auditioner_output_left));
+ }
+ if (!user_only || auditioner_output_right_is_user) {
+ node->add_child_nocopy(option_node("auditioner-right-out", auditioner_output_right));
+ }
+ if (!user_only || quieten_at_speed_is_user) {
+ snprintf (buf, sizeof (buf), "%f", speed_quietning);
+ node->add_child_nocopy(option_node("quieten-at-speed", buf));
+ }
+
+ /* use-vst is always per-user */
+ node->add_child_nocopy (option_node ("use-vst", use_vst?"yes":"no"));
+
+ root->add_child_nocopy (*node);
+
+ if (key_node) {
+ root->add_child_copy (*key_node);
+ }
+
+ if (_extra_xml) {
+ root->add_child_copy (*_extra_xml);
+ }
+
+ return *root;
+}
+
+int
+Configuration::set_state (const XMLNode& root)
+{
+ if (root.name() != "Ardour") {
+ return -1;
+ }
+
+ XMLNodeList nlist = root.children();
+ XMLNodeConstIterator niter;
+ XMLNode *node;
+ XMLProperty *prop;
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ node = *niter;
+
+ if (node->name() == "MIDI-port") {
+
+ try {
+ pair<string,MidiPortDescriptor*> newpair;
+ newpair.second = new MidiPortDescriptor (*node);
+ newpair.first = newpair.second->tag;
+ midi_ports.insert (newpair);
+ }
+
+ catch (failed_constructor& err) {
+ warning << _("ill-formed MIDI port specification in ardour rcfile (ignored)") << endmsg;
+ }
+
+ } else if (node->name() == "Config") {
+
+ XMLNodeList option_list = node->children();
+ XMLNodeConstIterator option_iter;
+ XMLNode *option_node;
+
+ string option_name;
+ string option_value;
+
+ for (option_iter = option_list.begin(); option_iter != option_list.end(); ++option_iter) {
+
+ option_node = *option_iter;
+
+ if (option_node->name() != "Option") {
+ continue;
+ }
+
+ if ((prop = option_node->property ("name")) != 0) {
+ option_name = prop->value();
+ } else {
+ throw failed_constructor ();
+ }
+
+ if ((prop = option_node->property ("value")) != 0) {
+ option_value = prop->value();
+ } else {
+ throw failed_constructor ();
+ }
+
+ if (option_name == "minimum-disk-io-bytes") {
+ set_minimum_disk_io (atoi (option_value.c_str()));
+ } else if (option_name == "track-buffer-seconds") {
+ set_track_buffer (atof (option_value.c_str()));
+ } else if (option_name == "raid-path") {
+ set_raid_path (option_value);
+ } else if (option_name == "hiding-groups-deactivates-groups") {
+ set_hiding_groups_deactivates_groups (option_value == "yes");
+ } else if (option_name == "mute-affects-pre-fader") {
+ set_mute_affects_pre_fader (option_value == "yes");
+ } else if (option_name == "mute-affects-post-fader") {
+ set_mute_affects_post_fader (option_value == "yes");
+ } else if (option_name == "mute-affects-control-outs") {
+ set_mute_affects_control_outs (option_value == "yes");
+ } else if (option_name == "mute-affects-main-outs") {
+ set_mute_affects_main_outs (option_value == "yes");
+ } else if (option_name == "solo-latch") {
+ set_solo_latch (option_value == "yes");
+ } else if (option_name == "mtc-port") {
+ set_mtc_port_name (option_value);
+ } else if (option_name == "mmc-port") {
+ set_mmc_port_name (option_value);
+ } else if (option_name == "midi-port") {
+ set_midi_port_name (option_value);
+ } else if (option_name == "hardware-monitoring") {
+ set_use_hardware_monitoring (option_value == "yes");
+ } else if (option_name == "jack-time-master") {
+ set_jack_time_master (option_value == "yes");
+ } else if (option_name == "trace-midi-input") {
+ set_trace_midi_input (option_value == "yes");
+ } else if (option_name == "trace-midi-output") {
+ set_trace_midi_output (option_value == "yes");
+ } else if (option_name == "plugins-stop-with-transport") {
+ set_plugins_stop_with_transport (option_value == "yes");
+ } else if (option_name == "no-sw-monitoring") {
+ set_no_sw_monitoring (option_value == "yes");
+ } else if (option_name == "stop-recording-on-xrun") {
+ set_stop_recording_on_xrun (option_value == "yes");
+ } else if (option_name == "verify-remove-last-capture") {
+ set_verify_remove_last_capture (option_value == "yes");
+ } else if (option_name == "stop-at-session-end") {
+ set_stop_at_session_end (option_value == "yes");
+ } else if (option_name == "seamless-loop") {
+ set_seamless_looping (option_value == "yes");
+ } else if (option_name == "auto-xfade") {
+ set_auto_xfade (option_value == "yes");
+ } else if (option_name == "no-new-session-dialog") {
+ set_no_new_session_dialog (option_value == "yes");
+ } else if (option_name == "timecode-source-is-synced") {
+ set_timecode_source_is_synced (option_value == "yes");
+ } else if (option_name == "auditioner-left-out") {
+ set_auditioner_output_left (option_value);
+ } else if (option_name == "auditioner-right-out") {
+ set_auditioner_output_right (option_value);
+ } else if (option_name == "use-vst") {
+ set_use_vst (option_value == "yes");
+ } else if (option_name == "quieten-at-speed") {
+ float v;
+ if (sscanf (option_value.c_str(), "%f", &v) == 1) {
+ set_quieten_at_speed (v);
+ }
+ }
+ }
+
+ } else if (node->name() == "Keys") {
+ /* defer handling of this for UI objects */
+ key_node = new XMLNode (*node);
+ } else if (node->name() == "extra") {
+ _extra_xml = new XMLNode (*node);
+ }
+ }
+
+ DiskStream::set_disk_io_chunk_frames (minimum_disk_io_bytes / sizeof (Sample));
+
+ return 0;
+}
+
+void
+Configuration::set_defaults ()
+{
+ raid_path = "";
+ orig_raid_path = raid_path;
+
+ mtc_port_name = N_("default");
+ mmc_port_name = N_("default");
+ midi_port_name = N_("default");
+#ifdef __APPLE__
+ auditioner_output_left = N_("coreaudio:Built-in Audio:in1");
+ auditioner_output_right = N_("coreaudio:Built-in Audio:in2");
+#else
+ auditioner_output_left = N_("alsa_pcm:playback_1");
+ auditioner_output_right = N_("alsa_pcm:playback_2");
+#endif
+ minimum_disk_io_bytes = 1024 * 256;
+ track_buffer_seconds = 5.0;
+ hiding_groups_deactivates_groups = true;
+ mute_affects_pre_fader = 1;
+ mute_affects_post_fader = 1;
+ mute_affects_control_outs = 1;
+ mute_affects_main_outs = 1;
+ solo_latch = 1;
+ use_hardware_monitoring = true;
+ be_jack_time_master = true;
+ native_format_is_bwf = true;
+ trace_midi_input = false;
+ trace_midi_output = false;
+ plugins_stop_with_transport = false;
+ no_sw_monitoring = false;
+ stop_recording_on_xrun = false;
+ verify_remove_last_capture = true;
+ stop_at_session_end = true;
+ seamless_looping = false;
+ auto_xfade = true;
+ no_new_session_dialog = false;
+ timecode_source_is_synced = true;
+ use_vst = true; /* if we build with VST_SUPPORT, otherwise no effect */
+ quieten_at_speed = true;
+
+ // this is about 5 minutes at 48kHz, 4 bytes/sample
+ disk_choice_space_threshold = 57600000;
+
+ /* at this point, no variables from from the user */
+
+ raid_path_is_user = false;
+ minimum_disk_io_bytes_is_user = false;
+ track_buffer_seconds_is_user = false;
+ hiding_groups_deactivates_groups_is_user = false;
+ auditioner_output_left_is_user = false;
+ auditioner_output_right_is_user = false;
+ mute_affects_pre_fader_is_user = false;
+ mute_affects_post_fader_is_user = false;
+ mute_affects_control_outs_is_user = false;
+ mute_affects_main_outs_is_user = false;
+ solo_latch_is_user = false;
+ disk_choice_space_threshold_is_user = false;
+ mtc_port_name_is_user = false;
+ mmc_port_name_is_user = false;
+ midi_port_name_is_user = false;
+ use_hardware_monitoring_is_user = false;
+ be_jack_time_master_is_user = false;
+ native_format_is_bwf_is_user = false;
+ trace_midi_input_is_user = false;
+ trace_midi_output_is_user = false;
+ plugins_stop_with_transport_is_user = false;
+ no_sw_monitoring_is_user = false;
+ stop_recording_on_xrun_is_user = false;
+ verify_remove_last_capture_is_user = false;
+ stop_at_session_end_is_user = false;
+ seamless_looping_is_user = false;
+ auto_xfade_is_user = false;
+ no_new_session_dialog_is_user = false;
+ timecode_source_is_synced_is_user = false;
+ quieten_at_speed_is_user = false;
+}
+
+Configuration::MidiPortDescriptor::MidiPortDescriptor (const XMLNode& node)
+{
+ const XMLProperty *prop;
+ bool have_tag = false;
+ bool have_device = false;
+ bool have_type = false;
+ bool have_mode = false;
+
+ if ((prop = node.property ("tag")) != 0) {
+ tag = prop->value();
+ have_tag = true;
+ }
+
+ if ((prop = node.property ("device")) != 0) {
+ device = prop->value();
+ have_device = true;
+ }
+
+ if ((prop = node.property ("type")) != 0) {
+ type = prop->value();
+ have_type = true;
+ }
+
+ if ((prop = node.property ("mode")) != 0) {
+ mode = prop->value();
+ have_mode = true;
+ }
+
+ if (!have_tag || !have_device || !have_type || !have_mode) {
+ throw failed_constructor();
+ }
+}
+
+XMLNode&
+Configuration::MidiPortDescriptor::get_state()
+{
+ XMLNode* root = new XMLNode("MIDI-port");
+
+ root->add_property("tag", tag);
+ root->add_property("device", device);
+ root->add_property("type", type);
+ root->add_property("mode", mode);
+
+ return *root;
+}
+
+XMLNode&
+Configuration::option_node(const string & name, const string & value)
+{
+ XMLNode* root = new XMLNode("Option");
+
+ root->add_property("name", name);
+ root->add_property("value", value);
+
+ return *root;
+}
+
+string
+Configuration::get_raid_path()
+{
+ return raid_path;
+}
+
+void
+Configuration::set_raid_path(string path)
+{
+#ifdef HAVE_WORDEXP
+ /* Handle tilde and environment variable expansion in session path */
+ wordexp_t expansion;
+ switch (wordexp (path.c_str(), &expansion, WRDE_NOCMD|WRDE_UNDEF)) {
+ case 0:
+ break;
+ default:
+ error << _("illegal or badly-formed string used for RAID path") << endmsg;
+ return;
+ }
+
+ if (expansion.we_wordc > 1) {
+ error << _("RAID search path is ambiguous") << endmsg;
+ return;
+ }
+
+ raid_path = expansion.we_wordv[0];
+ orig_raid_path = path;
+ wordfree (&expansion);
+#else
+ raid_path = orig_raid_path = path;
+#endif
+
+ if (user_configuration) {
+ raid_path_is_user = true;
+ }
+}
+
+uint32_t
+Configuration::get_minimum_disk_io()
+{
+ return minimum_disk_io_bytes;
+}
+
+void
+Configuration::set_minimum_disk_io(uint32_t min)
+{
+ minimum_disk_io_bytes = min;
+ if (user_configuration) {
+ minimum_disk_io_bytes_is_user = true;
+ }
+}
+
+float
+Configuration::get_track_buffer()
+{
+ return track_buffer_seconds;
+}
+
+void
+Configuration::set_track_buffer(float buffer)
+{
+ track_buffer_seconds = buffer;
+ if (user_configuration) {
+ track_buffer_seconds_is_user = true;
+ }
+}
+
+bool
+Configuration::does_hiding_groups_deactivates_groups()
+{
+ return hiding_groups_deactivates_groups;
+}
+
+void
+Configuration::set_hiding_groups_deactivates_groups(bool hiding)
+{
+ hiding_groups_deactivates_groups = hiding;
+ if (user_configuration) {
+ hiding_groups_deactivates_groups_is_user = true;
+ }
+}
+
+string
+Configuration::get_auditioner_output_left ()
+{
+ return auditioner_output_left;
+}
+
+void
+Configuration::set_auditioner_output_left (string str)
+{
+ auditioner_output_left = str;
+ if (user_configuration) {
+ auditioner_output_left_is_user = true;
+ }
+}
+
+string
+Configuration::get_auditioner_output_right ()
+{
+ return auditioner_output_right;
+}
+
+void
+Configuration::set_auditioner_output_right (string str)
+{
+ auditioner_output_right = str;
+ if (user_configuration) {
+ auditioner_output_right_is_user = true;
+ }
+}
+
+bool
+Configuration::get_mute_affects_pre_fader()
+{
+ return mute_affects_pre_fader;
+}
+
+void
+Configuration::set_mute_affects_pre_fader (bool affects)
+{
+ mute_affects_pre_fader = affects;
+ if (user_configuration) {
+ mute_affects_pre_fader_is_user = true;
+ }
+}
+
+bool
+Configuration::get_mute_affects_post_fader()
+{
+ return mute_affects_post_fader;
+}
+
+void
+Configuration::set_mute_affects_post_fader (bool affects)
+{
+ mute_affects_post_fader = affects;
+ if (user_configuration) {
+ mute_affects_post_fader_is_user = true;
+ }
+}
+
+bool
+Configuration::get_mute_affects_control_outs()
+{
+ return mute_affects_control_outs;
+}
+
+void
+Configuration::set_mute_affects_control_outs (bool affects)
+{
+ mute_affects_control_outs = affects;
+ if (user_configuration) {
+ mute_affects_control_outs_is_user = true;
+ }
+}
+
+bool
+Configuration::get_mute_affects_main_outs()
+{
+ return mute_affects_main_outs;
+}
+
+void
+Configuration::set_mute_affects_main_outs (bool affects)
+{
+ mute_affects_main_outs = affects;
+ if (user_configuration) {
+ mute_affects_main_outs_is_user = true;
+ }
+}
+
+bool
+Configuration::get_solo_latch()
+{
+ return solo_latch;
+}
+
+void
+Configuration::set_solo_latch (bool latch)
+{
+ solo_latch = latch;
+ if (user_configuration) {
+ solo_latch_is_user = true;
+ }
+}
+
+XMLNode *
+Configuration::get_keys () const
+{
+ return key_node;
+}
+
+void
+Configuration::set_keys (XMLNode* keys)
+{
+ key_node = keys;
+}
+
+uint32_t
+Configuration::get_disk_choice_space_threshold ()
+{
+ return disk_choice_space_threshold;
+}
+
+void
+Configuration::set_disk_choice_space_threshold (uint32_t val)
+{
+ disk_choice_space_threshold = val;
+ if (user_configuration) {
+ disk_choice_space_threshold_is_user = true;
+ }
+}
+
+string
+Configuration::get_mmc_port_name ()
+{
+ return mmc_port_name;
+}
+
+void
+Configuration::set_mmc_port_name (string name)
+{
+ mmc_port_name = name;
+ if (user_configuration) {
+ mmc_port_name_is_user = true;
+ }
+}
+
+string
+Configuration::get_mtc_port_name ()
+{
+ return mtc_port_name;
+}
+
+void
+Configuration::set_mtc_port_name (string name)
+{
+ mtc_port_name = name;
+ if (user_configuration) {
+ mtc_port_name_is_user = true;
+ }
+}
+
+string
+Configuration::get_midi_port_name ()
+{
+ return midi_port_name;
+}
+
+void
+Configuration::set_midi_port_name (string name)
+{
+ midi_port_name = name;
+ if (user_configuration) {
+ midi_port_name_is_user = true;
+ }
+}
+
+bool
+Configuration::get_use_hardware_monitoring()
+{
+ return use_hardware_monitoring;
+}
+
+void
+Configuration::set_use_hardware_monitoring(bool yn)
+{
+ use_hardware_monitoring = yn;
+ if (user_configuration) {
+ use_hardware_monitoring_is_user = true;
+ }
+}
+
+bool
+Configuration::get_jack_time_master()
+{
+ return be_jack_time_master;
+}
+
+void
+Configuration::set_jack_time_master(bool yn)
+{
+ be_jack_time_master = yn;
+ if (user_configuration) {
+ be_jack_time_master_is_user = true;
+ }
+}
+
+bool
+Configuration::get_native_format_is_bwf()
+{
+ return native_format_is_bwf;
+}
+
+void
+Configuration::set_native_format_is_bwf(bool yn)
+{
+ native_format_is_bwf = yn;
+ if (user_configuration) {
+ native_format_is_bwf_is_user = true;
+ }
+}
+
+bool
+Configuration::get_trace_midi_input ()
+{
+ return trace_midi_input;
+}
+
+void
+Configuration::set_trace_midi_input (bool yn)
+{
+ trace_midi_input = yn;
+ if (user_configuration) {
+ trace_midi_input_is_user = true;
+ }
+}
+
+bool
+Configuration::get_trace_midi_output ()
+{
+ return trace_midi_output;
+}
+
+void
+Configuration::set_trace_midi_output (bool yn)
+{
+ trace_midi_output = yn;
+ if (user_configuration) {
+ trace_midi_output_is_user = true;
+ }
+}
+
+bool
+Configuration::get_plugins_stop_with_transport ()
+{
+ return plugins_stop_with_transport;
+}
+
+void
+Configuration::set_plugins_stop_with_transport (bool yn)
+{
+ plugins_stop_with_transport = yn;
+ if (user_configuration) {
+ plugins_stop_with_transport_is_user = true;
+ }
+}
+
+bool
+Configuration::get_no_sw_monitoring ()
+{
+ return no_sw_monitoring;
+}
+
+void
+Configuration::set_no_sw_monitoring (bool yn)
+{
+ no_sw_monitoring = yn;
+ if (user_configuration) {
+ no_sw_monitoring_is_user = true;
+ }
+}
+
+bool
+Configuration::get_stop_recording_on_xrun ()
+{
+ return stop_recording_on_xrun;
+}
+
+void
+Configuration::set_stop_recording_on_xrun (bool yn)
+{
+ stop_recording_on_xrun = yn;
+ if (user_configuration) {
+ stop_recording_on_xrun_is_user = true;
+ }
+}
+
+bool
+Configuration::get_verify_remove_last_capture ()
+{
+ return verify_remove_last_capture;
+}
+
+void
+Configuration::set_verify_remove_last_capture (bool yn)
+{
+ verify_remove_last_capture = yn;
+ if (user_configuration) {
+ verify_remove_last_capture_is_user = true;
+ }
+}
+
+bool
+Configuration::get_stop_at_session_end ()
+{
+ return stop_at_session_end;
+}
+
+void
+Configuration::set_stop_at_session_end (bool yn)
+{
+ stop_at_session_end = yn;
+ if (user_configuration) {
+ stop_at_session_end_is_user = true;
+ }
+}
+
+bool
+Configuration::get_seamless_looping ()
+{
+ return seamless_looping;
+}
+
+void
+Configuration::set_seamless_looping (bool yn)
+{
+ seamless_looping = yn;
+ if (user_configuration) {
+ seamless_looping_is_user = true;
+ }
+}
+
+bool
+Configuration::get_auto_xfade ()
+{
+ return auto_xfade;
+}
+
+void
+Configuration::set_auto_xfade (bool yn)
+{
+ auto_xfade = yn;
+ if (user_configuration) {
+ auto_xfade_is_user = true;
+ }
+}
+
+string
+Configuration::get_user_ardour_path ()
+{
+ string path;
+ char* envvar;
+
+ if ((envvar = getenv ("HOME")) == 0 || strlen (envvar) == 0) {
+ return "/";
+ }
+
+ path = envvar;
+ path += "/.ardour/";
+
+ return path;
+}
+
+string
+Configuration::get_system_ardour_path ()
+{
+ string path;
+ char* envvar;
+
+ if ((envvar = getenv ("ARDOUR_DATA_PATH")) != 0) {
+ path += envvar;
+ if (path[path.length()-1] != ':') {
+ path += ':';
+ }
+ }
+
+ path += DATA_DIR;
+ path += "/ardour/";
+
+ return path;
+}
+
+bool
+Configuration::get_no_new_session_dialog()
+{
+ return no_new_session_dialog;
+}
+
+void
+Configuration::set_no_new_session_dialog(bool yn)
+{
+ no_new_session_dialog = yn;
+ if (user_configuration) {
+ no_new_session_dialog_is_user = true;
+ }
+}
+
+bool
+Configuration::get_timecode_source_is_synced()
+{
+ return timecode_source_is_synced;
+}
+
+void
+Configuration::set_timecode_source_is_synced (bool yn)
+{
+ timecode_source_is_synced = yn;
+ if (user_configuration) {
+ timecode_source_is_synced_is_user = true;
+ }
+}
+
+bool
+Configuration::get_use_vst ()
+{
+ return use_vst;
+}
+
+void
+Configuration::set_use_vst (bool yn)
+{
+ use_vst = yn;
+}
+
+gain_t
+Configuration::get_quieten_at_speed()
+{
+ return speed_quietning;
+}
+
+void
+Configuration::set_quieten_at_speed (float gain_coefficient)
+{
+ speed_quietning = gain_coefficient;
+ if (user_configuration) {
+ quieten_at_speed_is_user = true;
+ }
+}
diff --git a/libs/ardour/connection.cc b/libs/ardour/connection.cc
new file mode 100644
index 0000000000..ea4e6d58e5
--- /dev/null
+++ b/libs/ardour/connection.cc
@@ -0,0 +1,275 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+
+#include <pbd/failed_constructor.h>
+#include <ardour/ardour.h>
+#include <ardour/connection.h>
+#include <pbd/xml++.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+
+Connection::Connection (const XMLNode& node)
+{
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+}
+
+InputConnection::InputConnection (const XMLNode& node)
+ : Connection (node)
+{
+}
+
+OutputConnection::OutputConnection (const XMLNode& node)
+ : Connection (node)
+{
+}
+
+void
+Connection::set_name (string name, void *src)
+{
+ _name = name;
+ NameChanged (src);
+}
+
+void
+Connection::add_connection (int port, string portname)
+{
+ {
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ _ports[port].push_back (portname);
+ }
+ ConnectionsChanged (port); /* EMIT SIGNAL */
+}
+
+void
+Connection::remove_connection (int port, string portname)
+{
+ bool changed = false;
+
+ {
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ PortList& pl = _ports[port];
+ PortList::iterator i = find (pl.begin(), pl.end(), portname);
+
+ if (i != pl.end()) {
+ pl.erase (i);
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ ConnectionsChanged (port); /* EMIT SIGNAL */
+ }
+}
+
+const Connection::PortList&
+Connection::port_connections (int port) const
+{
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ return _ports[port];
+}
+
+bool
+Connection::operator== (const Connection& other) const
+{
+ return other._ports == _ports;
+}
+
+void
+Connection::add_port ()
+{
+ {
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ _ports.push_back (PortList());
+ }
+ ConfigurationChanged(); /* EMIT SIGNAL */
+}
+
+void
+Connection::remove_port (int which_port)
+{
+ bool changed = false;
+
+ {
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ vector<PortList>::iterator i;
+ int n;
+
+ for (n = 0, i = _ports.begin(); i != _ports.end() && n < which_port; ++i, ++n);
+
+ if (i != _ports.end()) {
+ _ports.erase (i);
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ ConfigurationChanged(); /* EMIT SIGNAL */
+ }
+}
+
+void
+Connection::clear ()
+{
+ {
+ LockMonitor lm (port_lock, __LINE__, __FILE__);
+ _ports.clear ();
+ }
+
+ ConfigurationChanged(); /* EMIT SIGNAL */
+}
+
+XMLNode&
+Connection::get_state ()
+{
+ XMLNode *node;
+ string str;
+
+ if (dynamic_cast<InputConnection *> (this)) {
+ node = new XMLNode ("InputConnection");
+ } else {
+ node = new XMLNode ("OutputConnection");
+ }
+
+ node->add_property ("name", _name);
+
+ for (vector<PortList>::iterator i = _ports.begin(); i != _ports.end(); ++i) {
+
+ str += '{';
+
+ for (vector<string>::iterator ii = (*i).begin(); ii != (*i).end(); ++ii) {
+ if (ii != (*i).begin()) {
+ str += ',';
+ }
+ str += *ii;
+ }
+ str += '}';
+ }
+
+ node->add_property ("connections", str);
+
+ return *node;
+}
+
+int
+Connection::set_state (const XMLNode& node)
+{
+ const XMLProperty *prop;
+
+ if ((prop = node.property ("name")) == 0) {
+ error << _("Node for Connection has no \"name\" property") << endmsg;
+ return -1;
+ }
+
+ _name = prop->value();
+ _sysdep = false;
+
+ if ((prop = node.property ("connections")) == 0) {
+ error << _("Node for Connection has no \"connections\" property") << endmsg;
+ return -1;
+ }
+
+ set_connections (prop->value());
+
+ return 0;
+}
+
+int
+Connection::set_connections (const string& str)
+{
+ vector<string> ports;
+ int i;
+ int n;
+ int nports;
+
+ if ((nports = count (str.begin(), str.end(), '{')) == 0) {
+ return 0;
+ }
+
+ for (n = 0; n < nports; ++n) {
+ add_port ();
+ }
+
+ string::size_type start, end, ostart;
+
+ ostart = 0;
+ start = 0;
+ end = 0;
+ i = 0;
+
+ while ((start = str.find_first_of ('{', ostart)) != string::npos) {
+ start += 1;
+
+ if ((end = str.find_first_of ('}', start)) == string::npos) {
+ error << compose(_("IO: badly formed string in XML node for inputs \"%1\""), str) << endmsg;
+ return -1;
+ }
+
+ if ((n = parse_io_string (str.substr (start, end - start), ports)) < 0) {
+ error << compose(_("bad input string in XML node \"%1\""), str) << endmsg;
+
+ return -1;
+
+ } else if (n > 0) {
+
+ for (int x = 0; x < n; ++x) {
+ add_connection (i, ports[x]);
+ }
+ }
+
+ ostart = end+1;
+ i++;
+ }
+
+ return 0;
+}
+
+int
+Connection::parse_io_string (const string& str, vector<string>& ports)
+{
+ string::size_type pos, opos;
+
+ if (str.length() == 0) {
+ return 0;
+ }
+
+ pos = 0;
+ opos = 0;
+
+ ports.clear ();
+
+ while ((pos = str.find_first_of (',', opos)) != string::npos) {
+ ports.push_back (str.substr (opos, pos - opos));
+ opos = pos + 1;
+ }
+
+ if (opos < str.length()) {
+ ports.push_back (str.substr(opos));
+ }
+
+ return ports.size();
+}
+
diff --git a/libs/ardour/crossfade.cc b/libs/ardour/crossfade.cc
new file mode 100644
index 0000000000..1e9543519b
--- /dev/null
+++ b/libs/ardour/crossfade.cc
@@ -0,0 +1,877 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#include <sigc++/bind.h>
+
+#include <ardour/types.h>
+#include <ardour/crossfade.h>
+#include <ardour/crossfade_compare.h>
+#include <ardour/audioregion.h>
+#include <ardour/playlist.h>
+#include <ardour/utils.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+jack_nframes_t Crossfade::_short_xfade_length = 0;
+Change Crossfade::ActiveChanged = ARDOUR::new_change();
+
+/* XXX if and when we ever implement parallel processing of the process()
+ callback, these will need to be handled on a per-thread basis.
+*/
+
+Sample* Crossfade::crossfade_buffer_out = 0;
+Sample* Crossfade::crossfade_buffer_in = 0;
+
+void
+Crossfade::set_buffer_size (jack_nframes_t sz)
+{
+ if (crossfade_buffer_out) {
+ delete [] crossfade_buffer_out;
+ crossfade_buffer_out = 0;
+ }
+
+ if (crossfade_buffer_in) {
+ delete [] crossfade_buffer_in;
+ crossfade_buffer_in = 0;
+ }
+
+ if (sz) {
+ crossfade_buffer_out = new Sample[sz];
+ crossfade_buffer_in = new Sample[sz];
+ }
+}
+
+bool
+Crossfade::operator== (const Crossfade& other)
+{
+ return (_in == other._in) && (_out == other._out);
+}
+
+Crossfade::Crossfade (ARDOUR::AudioRegion& in, ARDOUR::AudioRegion& out,
+ jack_nframes_t length,
+ jack_nframes_t position,
+ AnchorPoint ap)
+ : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB
+ _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB
+{
+ _in = &in;
+ _out = &out;
+ _length = length;
+ _position = position;
+ _anchor_point = ap;
+ _follow_overlap = false;
+ _active = true;
+ _fixed = true;
+
+ initialize ();
+}
+
+Crossfade::Crossfade (ARDOUR::AudioRegion& a, ARDOUR::AudioRegion& b, CrossfadeModel model, bool act)
+ : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB
+ _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB
+{
+ _in_update = false;
+ _fixed = false;
+
+ if (compute (a, b, model)) {
+ throw failed_constructor();
+ }
+
+ _active = act;
+
+ initialize ();
+
+}
+
+Crossfade::Crossfade (const Playlist& playlist, XMLNode& node)
+ : _fade_in (0.0, 2.0, 1.0), // linear (gain coefficient) => -inf..+6dB
+ _fade_out (0.0, 2.0, 1.0) // linear (gain coefficient) => -inf..+6dB
+{
+ Region* r;
+ XMLProperty* prop;
+ id_t id;
+ LocaleGuard lg (X_("POSIX"));
+
+ /* we have to find the in/out regions before we can do anything else */
+
+ if ((prop = node.property ("in")) == 0) {
+ error << _("Crossfade: no \"in\" region in state") << endmsg;
+ throw failed_constructor();
+ }
+
+ sscanf (prop->value().c_str(), "%llu", &id);
+
+ if ((r = playlist.find_region (id)) == 0) {
+ error << compose (_("Crossfade: no \"in\" region %1 found in playlist %2"), id, playlist.name())
+ << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((_in = dynamic_cast<AudioRegion*> (r)) == 0) {
+ throw failed_constructor();
+ }
+
+ if ((prop = node.property ("out")) == 0) {
+ error << _("Crossfade: no \"out\" region in state") << endmsg;
+ throw failed_constructor();
+ }
+
+ sscanf (prop->value().c_str(), "%llu", &id);
+
+ if ((r = playlist.find_region (id)) == 0) {
+ error << compose (_("Crossfade: no \"out\" region %1 found in playlist %2"), id, playlist.name())
+ << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((_out = dynamic_cast<AudioRegion*> (r)) == 0) {
+ throw failed_constructor();
+ }
+
+ _length = 0;
+ initialize();
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+}
+
+Crossfade::~Crossfade ()
+{
+ for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+ delete *i;
+ }
+}
+
+void
+Crossfade::initialize ()
+{
+ _in_update = false;
+
+ _out->suspend_fade_out ();
+ _in->suspend_fade_in ();
+
+ _fade_out.freeze ();
+ _fade_out.clear ();
+ _fade_out.add (0.0, 1.0);
+ _fade_out.add ((_length * 0.1), 0.99);
+ _fade_out.add ((_length * 0.2), 0.97);
+ _fade_out.add ((_length * 0.8), 0.03);
+ _fade_out.add ((_length * 0.9), 0.01);
+ _fade_out.add (_length, 0.0);
+ _fade_out.thaw ();
+
+ _fade_in.freeze ();
+ _fade_in.clear ();
+ _fade_in.add (0.0, 0.0);
+ _fade_in.add ((_length * 0.1), 0.01);
+ _fade_in.add ((_length * 0.2), 0.03);
+ _fade_in.add ((_length * 0.8), 0.97);
+ _fade_in.add ((_length * 0.9), 0.99);
+ _fade_in.add (_length, 1.0);
+ _fade_in.thaw ();
+
+// _in->StateChanged.connect (slot (*this, &Crossfade::member_changed));
+// _out->StateChanged.connect (slot (*this, &Crossfade::member_changed));
+
+ overlap_type = _in->coverage (_out->position(), _out->last_frame());
+
+ save_state ("initial");
+}
+
+int
+Crossfade::compute (AudioRegion& a, AudioRegion& b, CrossfadeModel model)
+{
+ AudioRegion* top;
+ AudioRegion* bottom;
+ jack_nframes_t short_xfade_length;
+
+ short_xfade_length = _short_xfade_length;
+
+ if (a.layer() < b.layer()) {
+ top = &b;
+ bottom = &a;
+ } else {
+ top = &a;
+ bottom = &b;
+ }
+
+ /* first check for matching ends */
+
+ if (top->first_frame() == bottom->first_frame()) {
+
+ /* Both regions start at the same point */
+
+ if (top->last_frame() < bottom->last_frame()) {
+
+ /* top ends before bottom, so put an xfade
+ in at the end of top.
+ */
+
+ /* [-------- top ---------- ]
+ * {====== bottom =====================}
+ */
+
+ _in = bottom;
+ _out = top;
+
+ if (top->last_frame() < short_xfade_length) {
+ _position = 0;
+ } else {
+ _position = top->last_frame() - short_xfade_length;
+ }
+
+ _length = min (short_xfade_length, top->length());
+ _follow_overlap = false;
+ _anchor_point = EndOfIn;
+ _active = true;
+ _fixed = true;
+
+ } else {
+ /* top ends after (or same time) as bottom - no xfade
+ */
+
+ /* [-------- top ------------------------ ]
+ * {====== bottom =====================}
+ */
+
+ throw NoCrossfadeHere();
+ }
+
+ } else if (top->last_frame() == bottom->last_frame()) {
+
+ /* Both regions end at the same point */
+
+ if (top->first_frame() > bottom->first_frame()) {
+
+ /* top starts after bottom, put an xfade in at the
+ start of top
+ */
+
+ /* [-------- top ---------- ]
+ * {====== bottom =====================}
+ */
+
+ _in = top;
+ _out = bottom;
+ _position = top->first_frame();
+ _length = min (short_xfade_length, top->length());
+ _follow_overlap = false;
+ _anchor_point = StartOfIn;
+ _active = true;
+ _fixed = true;
+
+ } else {
+ /* top starts before bottom - no xfade
+ */
+
+ /* [-------- top ------------------------ ]
+ * {====== bottom =====================}
+ */
+
+ throw NoCrossfadeHere();
+ }
+
+ } else {
+
+ /* OK, time to do more regular overlapping */
+
+ OverlapType ot = top->coverage (bottom->first_frame(), bottom->last_frame());
+
+ switch (ot) {
+ case OverlapNone:
+ /* should be NOTREACHED as a precondition of creating
+ a new crossfade, but we need to handle it here.
+ */
+ throw NoCrossfadeHere();
+ break;
+
+ case OverlapInternal:
+ case OverlapExternal:
+ /* should be NOTREACHED because of tests above */
+ throw NoCrossfadeHere();
+ break;
+
+ case OverlapEnd: /* top covers start of bottom but ends within it */
+
+ /* [---- top ------------------------]
+ * { ==== bottom ============ }
+ */
+
+ _in = bottom;
+ _out = top;
+ _position = bottom->first_frame();
+ _anchor_point = StartOfIn;
+
+ if (model == FullCrossfade) {
+ _length = _out->first_frame() + _out->length() - _in->first_frame();
+ /* leave active alone */
+ _follow_overlap = true;
+ } else {
+ _length = min (short_xfade_length, top->length());
+ _active = true;
+ _follow_overlap = false;
+
+ }
+ break;
+
+ case OverlapStart: /* top starts within bottom but covers bottom's end */
+
+ /* { ==== top ============ }
+ * [---- bottom -------------------]
+ */
+
+ _in = top;
+ _out = bottom;
+ _position = top->first_frame();
+ _anchor_point = StartOfIn;
+
+ if (model == FullCrossfade) {
+ _length = _out->first_frame() + _out->length() - _in->first_frame();
+ /* leave active alone */
+ _follow_overlap = true;
+ } else {
+ _length = min (short_xfade_length, top->length());
+ _active = true;
+ _follow_overlap = false;
+
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+jack_nframes_t
+Crossfade::read_at (Sample *buf, Sample *mixdown_buffer,
+ float *gain_buffer, jack_nframes_t start, jack_nframes_t cnt, uint32_t chan_n,
+ jack_nframes_t read_frames, jack_nframes_t skip_frames)
+{
+ jack_nframes_t offset;
+ jack_nframes_t to_write;
+
+ if (!_active) {
+ return 0;
+ }
+
+ if (start < _position) {
+
+ /* handle an initial section of the read area that we do not
+ cover.
+ */
+
+ offset = _position - start;
+
+ if (offset < cnt) {
+ cnt -= offset;
+ } else {
+ return 0;
+ }
+
+ start = _position;
+ buf += offset;
+ to_write = min (_length, cnt);
+
+ } else {
+
+ to_write = min (_length - (start - _position), cnt);
+
+ }
+
+ offset = start - _position;
+
+ _out->read_at (crossfade_buffer_out, mixdown_buffer, gain_buffer, start, to_write, chan_n, read_frames, skip_frames);
+ _in->read_at (crossfade_buffer_in, mixdown_buffer, gain_buffer, start, to_write, chan_n, read_frames, skip_frames);
+
+ float* fiv = new float[to_write];
+ float* fov = new float[to_write];
+
+ _fade_in.get_vector (offset, offset+to_write, fiv, to_write);
+ _fade_out.get_vector (offset, offset+to_write, fov, to_write);
+
+ /* note: although we have not explicitly taken into account the return values
+ from _out->read_at() or _in->read_at(), the length() function does this
+ implicitly. why? because it computes a value based on the in+out regions'
+ position and length, and so we know precisely how much data they could return.
+ */
+
+ for (jack_nframes_t n = 0; n < to_write; ++n) {
+ buf[n] = (crossfade_buffer_out[n] * fov[n]) + (crossfade_buffer_in[n] * fiv[n]);
+ }
+
+ delete [] fov;
+ delete [] fiv;
+
+ return to_write;
+}
+
+OverlapType
+Crossfade::coverage (jack_nframes_t start, jack_nframes_t end) const
+{
+ jack_nframes_t my_end = _position + _length;
+
+ if ((start >= _position) && (end <= my_end)) {
+ return OverlapInternal;
+ }
+ if ((end >= _position) && (end <= my_end)) {
+ return OverlapStart;
+ }
+ if ((start >= _position) && (start <= my_end)) {
+ return OverlapEnd;
+ }
+ if ((_position >= start) && (_position <= end) && (my_end <= end)) {
+ return OverlapExternal;
+ }
+ return OverlapNone;
+}
+
+void
+Crossfade::set_active (bool yn)
+{
+ if (_active != yn) {
+ _active = yn;
+ save_state (_("active changed"));
+ send_state_changed (ActiveChanged);
+ }
+}
+
+bool
+Crossfade::refresh ()
+{
+ /* crossfades must be between non-muted regions */
+
+ if (_out->muted() || _in->muted()) {
+ Invalidated (this);
+ return false;
+ }
+
+ /* overlap type must be Start, End or External */
+
+ OverlapType ot;
+
+ ot = _in->coverage (_out->first_frame(), _out->last_frame());
+
+ switch (ot) {
+ case OverlapNone:
+ case OverlapInternal:
+ Invalidated (this);
+ return false;
+
+ default:
+ break;
+ }
+
+ /* overlap type must not have altered */
+
+ if (ot != overlap_type) {
+ Invalidated (this);
+ return false;
+ }
+
+ /* time to update */
+
+ return update (false);
+}
+
+bool
+Crossfade::update (bool force)
+{
+ jack_nframes_t newlen;
+ bool save = false;
+
+ if (_follow_overlap) {
+ newlen = _out->first_frame() + _out->length() - _in->first_frame();
+ } else {
+ newlen = _length;
+ }
+
+ if (newlen == 0) {
+ Invalidated (this);
+ return false;
+ }
+
+ _in_update = true;
+
+ if (force || (_follow_overlap && newlen != _length) || (_length > newlen)) {
+
+ double factor = newlen / (double) _length;
+
+ _fade_out.x_scale (factor);
+ _fade_in.x_scale (factor);
+
+ _length = newlen;
+
+ save = true;
+
+ }
+
+ switch (_anchor_point) {
+ case StartOfIn:
+ if (_position != _in->first_frame()) {
+ _position = _in->first_frame();
+ save = true;
+ }
+ break;
+
+ case EndOfIn:
+ if (_position != _in->last_frame() - _length) {
+ _position = _in->last_frame() - _length;
+ save = true;
+ }
+ break;
+
+ case EndOfOut:
+ if (_position != _out->last_frame() - _length) {
+ _position = _out->last_frame() - _length;
+ save = true;
+ }
+ }
+
+ if (save) {
+ save_state ("updated");
+ }
+
+ /* UI's may need to know that the overlap changed even
+ though the xfade length did not.
+ */
+
+ send_state_changed (BoundsChanged); /* EMIT SIGNAL */
+
+ _in_update = false;
+
+ return true;
+}
+
+void
+Crossfade::member_changed (Change what_changed)
+{
+ Change what_we_care_about = Change (Region::MuteChanged|
+ Region::LayerChanged|
+ ARDOUR::BoundsChanged);
+
+ if (what_changed & what_we_care_about) {
+ refresh ();
+ }
+}
+
+Change
+Crossfade::restore_state (StateManager::State& state)
+{
+ CrossfadeState* xfstate = dynamic_cast<CrossfadeState*> (&state);
+ Change what_changed = Change (0);
+
+ _in_update = true;
+
+ xfstate->fade_in_memento ();
+ xfstate->fade_out_memento ();
+
+ if (_length != xfstate->length) {
+ what_changed = Change (what_changed|LengthChanged);
+ _length = xfstate->length;
+ }
+ if (_active != xfstate->active) {
+ what_changed = Change (what_changed|ActiveChanged);
+ _active = xfstate->active;
+ }
+ if (_position != xfstate->position) {
+ what_changed = Change (what_changed|PositionChanged);
+ _position = xfstate->position;
+ }
+
+ /* XXX what to do about notifications for these? I don't
+ think (G)UI cares about them because they are
+ implicit in the bounds.
+ */
+
+ _follow_overlap = xfstate->follow_overlap;
+ _anchor_point = xfstate->anchor_point;
+
+ _in_update = false;
+
+ return Change (what_changed);
+}
+
+StateManager::State*
+Crossfade::state_factory (std::string why) const
+{
+ CrossfadeState* state = new CrossfadeState (why);
+
+ state->fade_in_memento = _fade_in.get_memento ();
+ state->fade_out_memento = _fade_out.get_memento ();
+ state->active = _active;
+ state->length = _length;
+ state->position = _position;
+ state->follow_overlap = _follow_overlap;
+ state->anchor_point = _anchor_point;
+
+ return state;
+}
+
+UndoAction
+Crossfade::get_memento() const
+{
+ return sigc::bind (mem_fun (*(const_cast<Crossfade *> (this)), &StateManager::use_state), _current_state_id);
+}
+
+XMLNode&
+Crossfade::get_state ()
+{
+ XMLNode* node = new XMLNode (X_("Crossfade"));
+ XMLNode* child;
+ char buf[64];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof(buf), "%" PRIu64, _out->id());
+ node->add_property ("out", buf);
+ snprintf (buf, sizeof(buf), "%" PRIu64, _in->id());
+ node->add_property ("in", buf);
+ node->add_property ("active", (_active ? "yes" : "no"));
+ node->add_property ("follow-overlap", (_follow_overlap ? "yes" : "no"));
+ node->add_property ("fixed", (_fixed ? "yes" : "no"));
+ snprintf (buf, sizeof(buf), "%" PRIu32, _length);
+ node->add_property ("length", buf);
+ snprintf (buf, sizeof(buf), "%" PRIu32, (uint32_t) _anchor_point);
+ node->add_property ("anchor-point", buf);
+ snprintf (buf, sizeof(buf), "%" PRIu32, (uint32_t) _position);
+ node->add_property ("position", buf);
+
+ child = node->add_child ("FadeIn");
+
+ for (AutomationList::iterator ii = _fade_in.begin(); ii != _fade_in.end(); ++ii) {
+ XMLNode* pnode;
+
+ pnode = new XMLNode ("point");
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*ii)->when));
+ pnode->add_property ("x", buf);
+ snprintf (buf, sizeof (buf), "%f", (*ii)->value);
+ pnode->add_property ("y", buf);
+ child->add_child_nocopy (*pnode);
+ }
+
+ child = node->add_child ("FadeOut");
+
+ for (AutomationList::iterator ii = _fade_out.begin(); ii != _fade_out.end(); ++ii) {
+ XMLNode* pnode;
+
+ pnode = new XMLNode ("point");
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*ii)->when));
+ pnode->add_property ("x", buf);
+ snprintf (buf, sizeof (buf), "%f", (*ii)->value);
+ pnode->add_property ("y", buf);
+ child->add_child_nocopy (*pnode);
+ }
+
+ return *node;
+}
+
+int
+Crossfade::set_state (const XMLNode& node)
+{
+ XMLNodeConstIterator i;
+ XMLNodeList children;
+ XMLNode* fi;
+ XMLNode* fo;
+ const XMLProperty* prop;
+ LocaleGuard lg (X_("POSIX"));
+
+ if ((prop = node.property ("position")) != 0) {
+ _position = atoi (prop->value().c_str());
+ } else {
+ warning << _("old-style crossfade information - no position information") << endmsg;
+ _position = _in->first_frame();
+ }
+
+ if ((prop = node.property ("active")) != 0) {
+ _active = (prop->value() == "yes");
+ } else {
+ _active = true;
+ }
+
+ if ((prop = node.property ("follow-overlap")) != 0) {
+ _follow_overlap = (prop->value() == "yes");
+ } else {
+ _follow_overlap = false;
+ }
+
+ if ((prop = node.property ("fixed")) != 0) {
+ _fixed = (prop->value() == "yes");
+ } else {
+ _fixed = false;
+ }
+
+ if ((prop = node.property ("anchor-point")) != 0) {
+ _anchor_point = AnchorPoint (atoi ((prop->value().c_str())));
+ } else {
+ _anchor_point = StartOfIn;
+ }
+
+ if ((prop = node.property ("length")) != 0) {
+
+ _length = atol (prop->value().c_str());
+
+ } else {
+
+ /* XXX this branch is legacy code from before
+ the point where we stored xfade lengths.
+ */
+
+ if ((_length = overlap_length()) == 0) {
+ throw failed_constructor();
+ }
+ }
+
+ if ((fi = find_named_node (node, "FadeIn")) == 0) {
+ return -1;
+ }
+
+ if ((fo = find_named_node (node, "FadeOut")) == 0) {
+ return -1;
+ }
+
+ /* fade in */
+
+ _fade_in.clear ();
+
+ children = fi->children();
+
+ for (i = children.begin(); i != children.end(); ++i) {
+ if ((*i)->name() == "point") {
+ jack_nframes_t x;
+ float y;
+
+ prop = (*i)->property ("x");
+ sscanf (prop->value().c_str(), "%" PRIu32, &x);
+
+ prop = (*i)->property ("y");
+ sscanf (prop->value().c_str(), "%f", &y);
+
+ _fade_in.add (x, y);
+ }
+ }
+
+ /* fade out */
+
+ _fade_out.clear ();
+
+ children = fo->children();
+
+ for (i = children.begin(); i != children.end(); ++i) {
+ if ((*i)->name() == "point") {
+ jack_nframes_t x;
+ float y;
+ XMLProperty* prop;
+
+ prop = (*i)->property ("x");
+ sscanf (prop->value().c_str(), "%" PRIu32, &x);
+
+ prop = (*i)->property ("y");
+ sscanf (prop->value().c_str(), "%f", &y);
+
+ _fade_out.add (x, y);
+ }
+ }
+
+ return 0;
+}
+
+bool
+Crossfade::can_follow_overlap () const
+{
+ return !_fixed;
+}
+
+void
+Crossfade::set_follow_overlap (bool yn)
+{
+ if (yn == _follow_overlap || _fixed) {
+ return;
+ }
+
+ _follow_overlap = yn;
+
+ if (!yn) {
+ set_length (_short_xfade_length);
+ } else {
+ set_length (_out->first_frame() + _out->length() - _in->first_frame());
+ }
+}
+
+jack_nframes_t
+Crossfade::set_length (jack_nframes_t len)
+{
+ jack_nframes_t limit;
+
+ switch (_anchor_point) {
+ case StartOfIn:
+ limit = _in->length();
+ break;
+
+ case EndOfIn:
+ limit = _in->length();
+ break;
+
+ case EndOfOut:
+ limit = _out->length();
+ break;
+
+ }
+
+ len = min (limit, len);
+
+ double factor = len / (double) _length;
+
+ _in_update = true;
+ _fade_out.x_scale (factor);
+ _fade_in.x_scale (factor);
+ _in_update = false;
+
+ _length = len;
+
+ save_state ("length changed");
+
+ send_state_changed (LengthChanged);
+
+ return len;
+}
+
+jack_nframes_t
+Crossfade::overlap_length () const
+{
+ if (_fixed) {
+ return _length;
+ }
+ return _out->first_frame() + _out->length() - _in->first_frame();
+}
+
+void
+Crossfade::set_short_xfade_length (jack_nframes_t n)
+{
+ _short_xfade_length = n;
+}
diff --git a/libs/ardour/curve.cc b/libs/ardour/curve.cc
new file mode 100644
index 0000000000..613222020c
--- /dev/null
+++ b/libs/ardour/curve.cc
@@ -0,0 +1,449 @@
+/*
+ Copyright (C) 2001-2003 Paul Davis
+
+ Contains ideas derived from "Constrained Cubic Spline Interpolation"
+ by CJC Kruger (www.korf.co.uk/spline.pdf).
+
+ 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.
+
+ $Id$
+*/
+
+#include <iostream>
+#include <float.h>
+#include <cmath>
+#include <climits>
+#include <cfloat>
+#include <cmath>
+
+#include <pbd/lockmonitor.h>
+#include <sigc++/bind.h>
+
+#include "ardour/curve.h"
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+using namespace sigc;
+
+Curve::Curve (double minv, double maxv, double canv, bool nostate)
+ : AutomationList (canv, nostate)
+{
+ min_yval = minv;
+ max_yval = maxv;
+}
+
+Curve::Curve (const Curve& other)
+ : AutomationList (other)
+{
+ min_yval = other.min_yval;
+ max_yval = other.max_yval;
+}
+
+Curve::Curve (const Curve& other, double start, double end)
+ : AutomationList (other, start, end)
+{
+ min_yval = other.min_yval;
+ max_yval = other.max_yval;
+}
+
+Curve::~Curve ()
+{
+}
+
+void
+Curve::solve ()
+{
+ uint32_t npoints;
+
+ if (!_dirty) {
+ return;
+ }
+
+ if ((npoints = events.size()) > 2) {
+
+ /* Compute coefficients needed to efficiently compute a constrained spline
+ curve. See "Constrained Cubic Spline Interpolation" by CJC Kruger
+ (www.korf.co.uk/spline.pdf) for more details.
+ */
+
+ double x[npoints];
+ double y[npoints];
+ uint32_t i;
+ AutomationEventList::iterator xx;
+
+ for (i = 0, xx = events.begin(); xx != events.end(); ++xx, ++i) {
+ x[i] = (double) (*xx)->when;
+ y[i] = (double) (*xx)->value;
+ }
+
+ double lp0, lp1, fpone;
+
+ lp0 =(x[1] - x[0])/(y[1] - y[0]);
+ lp1 = (x[2] - x[1])/(y[2] - y[1]);
+
+ if (lp0*lp1 < 0) {
+ fpone = 0;
+ } else {
+ fpone = 2 / (lp1 + lp0);
+ }
+
+ double fplast = 0;
+
+ for (i = 0, xx = events.begin(); xx != events.end(); ++xx, ++i) {
+
+ CurvePoint* cp = dynamic_cast<CurvePoint*>(*xx);
+
+ if (cp == 0) {
+ fatal << _("programming error: ")
+ << X_("non-CurvePoint event found in event list for a Curve")
+ << endmsg;
+ /*NOTREACHED*/
+ }
+
+ double xdelta; /* gcc is wrong about possible uninitialized use */
+ double xdelta2; /* ditto */
+ double ydelta; /* ditto */
+ double fppL, fppR;
+ double fpi;
+
+ if (i > 0) {
+ xdelta = x[i] - x[i-1];
+ xdelta2 = xdelta * xdelta;
+ ydelta = y[i] - y[i-1];
+ }
+
+ /* compute (constrained) first derivatives */
+
+ if (i == 0) {
+
+ /* first segment */
+
+ fplast = ((3 * (y[1] - y[0]) / (2 * (x[1] - x[0]))) - (fpone * 0.5));
+
+ /* we don't store coefficients for i = 0 */
+
+ continue;
+
+ } else if (i == npoints - 1) {
+
+ /* last segment */
+
+ fpi = ((3 * ydelta) / (2 * xdelta)) - (fplast * 0.5);
+
+ } else {
+
+ /* all other segments */
+
+ double slope_before = ((x[i+1] - x[i]) / (y[i+1] - y[i]));
+ double slope_after = (xdelta / ydelta);
+
+ if (slope_after * slope_before < 0.0) {
+ /* slope changed sign */
+ fpi = 0.0;
+ } else {
+ fpi = 2 / (slope_before + slope_after);
+ }
+
+ }
+
+ /* compute second derivative for either side of control point `i' */
+
+ fppL = (((-2 * (fpi + (2 * fplast))) / (xdelta))) +
+ ((6 * ydelta) / xdelta2);
+
+ fppR = (2 * ((2 * fpi) + fplast) / xdelta) -
+ ((6 * ydelta) / xdelta2);
+
+ /* compute polynomial coefficients */
+
+ double b, c, d;
+
+ d = (fppR - fppL) / (6 * xdelta);
+ c = ((x[i] * fppL) - (x[i-1] * fppR))/(2 * xdelta);
+
+ double xim12, xim13;
+ double xi2, xi3;
+
+ xim12 = x[i-1] * x[i-1]; /* "x[i-1] squared" */
+ xim13 = xim12 * x[i-1]; /* "x[i-1] cubed" */
+ xi2 = x[i] * x[i]; /* "x[i] squared" */
+ xi3 = xi2 * x[i]; /* "x[i] cubed" */
+
+ b = (ydelta - (c * (xi2 - xim12)) - (d * (xi3 - xim13))) / xdelta;
+
+ /* store */
+
+ cp->coeff[0] = y[i-1] - (b * x[i-1]) - (c * xim12) - (d * xim13);
+ cp->coeff[1] = b;
+ cp->coeff[2] = c;
+ cp->coeff[3] = d;
+
+ fplast = fpi;
+ }
+
+ }
+
+ _dirty = false;
+}
+
+bool
+Curve::rt_safe_get_vector (double x0, double x1, float *vec, int32_t veclen)
+{
+ TentativeLockMonitor lm (lock, __LINE__, __FILE__);
+
+ if (!lm.locked()) {
+ return false;
+ } else {
+ _get_vector (x0, x1, vec, veclen);
+ return true;
+ }
+}
+
+void
+Curve::get_vector (double x0, double x1, float *vec, int32_t veclen)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ _get_vector (x0, x1, vec, veclen);
+}
+
+void
+Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen)
+{
+ double rx, dx, lx, hx, max_x, min_x;
+ int32_t i;
+ int32_t original_veclen;
+ int32_t npoints;
+
+ if ((npoints = events.size()) == 0) {
+ for (i = 0; i < veclen; ++i) {
+ vec[i] = default_value;
+ }
+ return;
+ }
+
+ /* events is now known not to be empty */
+
+ max_x = events.back()->when;
+ min_x = events.front()->when;
+
+ lx = max (min_x, x0);
+
+ if (x1 < 0) {
+ x1 = events.back()->when;
+ }
+
+ hx = min (max_x, x1);
+
+ original_veclen = veclen;
+
+ if (x0 < min_x) {
+
+ /* fill some beginning section of the array with the
+ initial (used to be default) value
+ */
+
+ double frac = (min_x - x0) / (x1 - x0);
+ int32_t subveclen = (int32_t) floor (veclen * frac);
+
+ subveclen = min (subveclen, veclen);
+
+ for (i = 0; i < subveclen; ++i) {
+ vec[i] = events.front()->value;
+ }
+
+ veclen -= subveclen;
+ vec += subveclen;
+ }
+
+ if (veclen && x1 > max_x) {
+
+ /* fill some end section of the array with the default or final value */
+
+ double frac = (x1 - max_x) / (x1 - x0);
+
+ int32_t subveclen = (int32_t) floor (original_veclen * frac);
+
+ float val;
+
+ subveclen = min (subveclen, veclen);
+
+ val = events.back()->value;
+
+ i = veclen - subveclen;
+
+ for (i = veclen - subveclen; i < veclen; ++i) {
+ vec[i] = val;
+ }
+
+ veclen -= subveclen;
+ }
+
+ if (veclen == 0) {
+ return;
+ }
+
+ if (npoints == 1 ) {
+
+ for (i = 0; i < veclen; ++i) {
+ vec[i] = events.front()->value;
+ }
+ return;
+ }
+
+
+ if (npoints == 2) {
+
+ /* linear interpolation between 2 points */
+
+ /* XXX I'm not sure that this is the right thing to
+ do here. but its not a common case for the envisaged
+ uses.
+ */
+
+ if (veclen > 1) {
+ dx = (hx - lx) / (veclen - 1) ;
+ } else {
+ dx = 0; // not used
+ }
+
+ double slope = (events.back()->value - events.front()->value)/
+ (events.back()->when - events.front()->when);
+ double yfrac = dx*slope;
+
+ vec[0] = events.front()->value + slope * (lx - events.front()->when);
+
+ for (i = 1; i < veclen; ++i) {
+ vec[i] = vec[i-1] + yfrac;
+ }
+
+ return;
+ }
+
+ if (_dirty) {
+ solve ();
+ }
+
+ rx = lx;
+
+ if (veclen > 1) {
+
+ dx = (hx - lx) / veclen;
+
+ for (i = 0; i < veclen; ++i, rx += dx) {
+ vec[i] = multipoint_eval (rx);
+ }
+ }
+}
+
+double
+Curve::unlocked_eval (double x)
+{
+ if (_dirty) {
+ solve ();
+ }
+
+ return shared_eval (x);
+}
+
+double
+Curve::multipoint_eval (double x)
+{
+ pair<AutomationEventList::iterator,AutomationEventList::iterator> range;
+
+ if ((lookup_cache.left < 0) ||
+ ((lookup_cache.left > x) ||
+ (lookup_cache.range.first == events.end()) ||
+ ((*lookup_cache.range.second)->when < x))) {
+
+ TimeComparator cmp;
+ ControlEvent cp (x, 0.0);
+
+ lookup_cache.range = equal_range (events.begin(), events.end(), &cp, cmp);
+ }
+
+ range = lookup_cache.range;
+
+ /* EITHER
+
+ a) x is an existing control point, so first == existing point, second == next point
+
+ OR
+
+ b) x is between control points, so range is empty (first == second, points to where
+ to insert x)
+
+ */
+
+ if (range.first == range.second) {
+
+ /* x does not exist within the list as a control point */
+
+ lookup_cache.left = x;
+
+ if (range.first == events.begin()) {
+ /* we're before the first point */
+ // return default_value;
+ events.front()->value;
+ }
+
+ if (range.second == events.end()) {
+ /* we're after the last point */
+ return events.back()->value;
+ }
+
+ double x2 = x * x;
+ CurvePoint* cp = dynamic_cast<CurvePoint*> (*range.second);
+
+ return cp->coeff[0] + (cp->coeff[1] * x) + (cp->coeff[2] * x2) + (cp->coeff[3] * x2 * x);
+ }
+
+ /* x is a control point in the data */
+ /* invalidate the cached range because its not usable */
+ lookup_cache.left = -1;
+ return (*range.first)->value;
+}
+
+ControlEvent*
+Curve::point_factory (double when, double val) const
+{
+ return new CurvePoint (when, val);
+}
+
+ControlEvent*
+Curve::point_factory (const ControlEvent& other) const
+{
+ return new CurvePoint (other.when, other.value);
+}
+
+Change
+Curve::restore_state (StateManager::State& state)
+{
+ mark_dirty ();
+ return AutomationList::restore_state (state);
+}
+
+
+extern "C" {
+
+void
+curve_get_vector_from_c (void *arg, double x0, double x1, float* vec, int32_t vecsize)
+{
+ static_cast<Curve*>(arg)->get_vector (x0, x1, vec, vecsize);
+}
+
+}
diff --git a/libs/ardour/cycle_timer.cc b/libs/ardour/cycle_timer.cc
new file mode 100644
index 0000000000..c48b7fb861
--- /dev/null
+++ b/libs/ardour/cycle_timer.cc
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2002 Andrew Morton
+
+ 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.
+
+ $Id$
+*/
+
+#include <cstdio>
+#include <pbd/error.h>
+#include <ardour/cycle_timer.h>
+
+#include "i18n.h"
+
+float CycleTimer::cycles_per_usec = 0;
+
+float
+CycleTimer::get_mhz()
+{
+ FILE *f;
+
+ if ((f = fopen("/proc/cpuinfo", "r")) == 0) {
+ fatal << _("CycleTimer::get_mhz(): can't open /proc/cpuinfo") << endmsg;
+ /*NOTREACHED*/
+ return 0.0f;
+ }
+
+ while (true) {
+
+ float mhz;
+ int ret;
+ char buf[1000];
+
+ if (fgets (buf, sizeof(buf), f) == 0) {
+ fatal << _("CycleTimer::get_mhz(): cannot locate cpu MHz in /proc/cpuinfo") << endmsg;
+ /*NOTREACHED*/
+ return 0.0f;
+ }
+
+#ifdef __powerpc__
+
+ int imhz;
+
+ /* why can't the PPC crew standardize their /proc/cpuinfo format ? */
+ ret = sscanf (buf, "clock\t: %dMHz", &imhz);
+ mhz = (float) imhz;
+
+#else /* XXX don't assume its x86 just because its not power pc */
+ ret = sscanf (buf, "cpu MHz : %f", &mhz);
+
+#endif
+ if (ret == 1) {
+ fclose(f);
+ return mhz;
+ }
+ }
+
+ fatal << _("cannot locate cpu MHz in /proc/cpuinfo") << endmsg;
+ /*NOTREACHED*/
+ return 0.0f;
+}
diff --git a/libs/ardour/default_click.cc b/libs/ardour/default_click.cc
new file mode 100644
index 0000000000..d692af83ec
--- /dev/null
+++ b/libs/ardour/default_click.cc
@@ -0,0 +1,1175 @@
+/*
+ Copyright (C) 20002 Paul Davis
+ Sounds by Nick Mainsbridge.
+
+ 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.
+
+ $Id$
+*/
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+
+using namespace ARDOUR;
+
+const Sample Session::default_click_emphasis[] = {
+
+
+ 0.011260986, 0.055389404, 0.10488892, 0.14285278, 0.17984009,
+ 0.20492554, 0.2244873, 0.23187256, 0.23144531, 0.21932983,
+ 0.19973755, 0.17034912, 0.13473511, 0.09274292, 0.04699707,
+ -0.0009765625, -0.048919678, -0.095123291, -0.13772583,
+ -0.17495728, -0.20523071, -0.22750854, -0.24038696, -0.24423218,
+ -0.23831177, -0.22305298, -0.19918823, -0.16748047, -0.12948608,
+ -0.086730957, -0.041015625, 0.0060424805, 0.052276611,
+ 0.09588623, 0.13513184, 0.16827393, 0.19415283, 0.21182251,
+ 0.22024536, 0.2194519, 0.20932007, 0.19030762, 0.1633606,
+ 0.1295166, 0.090118408, 0.046844482, 0.0015563965, -0.0440979,
+ -0.088043213, -0.12854004, -0.16415405, -0.19317627,
+ -0.21456909, -0.22750854, -0.23141479, -0.22619629, -0.21224976,
+ -0.18994141, -0.1602478, -0.12457275, -0.084228516,
+ -0.040893555, 0.0035400391, 0.047485352, 0.08895874, 0.12628174,
+ 0.15808105, 0.1829834, 0.19998169, 0.20849609, 0.2081604,
+ 0.19891357, 0.18139648, 0.15621948, 0.12438965, 0.087371826,
+ 0.046569824, 0.0036621094, -0.039489746, -0.081207275,
+ -0.11987305, -0.15371704, -0.18154907, -0.20220947, -0.21481323,
+ -0.21896362, -0.21453857, -0.20159912, -0.18084717, -0.15319824,
+ -0.11959839, -0.081604004, -0.040771484, 0.001373291,
+ 0.04296875, 0.082275391, 0.11791992, 0.14828491, 0.17214966,
+ 0.18869019, 0.19711304, 0.19714355, 0.18896484, 0.17272949,
+ 0.14916992, 0.11941528, 0.084564209, 0.046081543, 0.005645752,
+ -0.03527832, -0.074890137, -0.11154175, -0.14389038,
+ -0.17053223, -0.19036865, -0.20272827, -0.20709229, -0.20321655,
+ -0.19143677, -0.17221069, -0.14624023, -0.11477661,
+ -0.079040527, -0.040435791, -0.00064086914, 0.038726807,
+ 0.076171875, 0.11004639, 0.13900757, 0.16201782, 0.177948,
+ 0.18634033, 0.18685913, 0.17938232, 0.16436768, 0.14254761,
+ 0.11459351, 0.081817627, 0.045623779, 0.0073242188,
+ -0.031402588, -0.068939209, -0.10388184, -0.13473511,
+ -0.16018677, -0.17938232, -0.19143677, -0.19589233, -0.19271851,
+ -0.1819458, -0.16400146, -0.13980103, -0.11026001, -0.076507568,
+ -0.040130615, -0.0024414062, 0.034973145, 0.070465088,
+ 0.10275269, 0.13052368, 0.15255737, 0.1680603, 0.17642212,
+ 0.17718506, 0.17056274, 0.15673828, 0.13626099, 0.1100769,
+ 0.079284668, 0.045013428, 0.0088195801, -0.027862549,
+ -0.06362915, -0.096862793, -0.12631226, -0.15081787,
+ -0.16931152, -0.18109131, -0.18579102, -0.18310547, -0.17321777,
+ -0.15661621, -0.13391113, -0.10604858, -0.074310303,
+ -0.039794922, -0.0040588379, 0.031433105, 0.065338135,
+ 0.096191406, 0.1227417, 0.14398193, 0.15905762, 0.16726685,
+ 0.16845703, 0.16247559, 0.14962769, 0.1305542, 0.10595703,
+ 0.076812744, 0.044494629, 0.010162354, -0.024719238,
+ -0.058685303, -0.090423584, -0.11865234, -0.14208984,
+ -0.15994263, -0.17150879, -0.1762085, -0.17401123, -0.16500854,
+ -0.1494751, -0.12820435, -0.10205078, -0.071990967,
+ -0.039398193, -0.0055847168, 0.028198242, 0.060424805,
+ 0.089782715, 0.11523438, 0.13565063, 0.15014648, 0.1583252,
+ 0.159729, 0.15438843, 0.14260864, 0.12475586, 0.10162354,
+ 0.074310303, 0.043762207, 0.011291504, -0.02166748,
+ -0.053924561, -0.084136963, -0.1109314, -0.13342285,
+ -0.15060425, -0.16177368, -0.16659546, -0.16485596, -0.15655518,
+ -0.14221191, -0.12234497, -0.097747803, -0.069519043,
+ -0.038818359, -0.0067749023, 0.025115967, 0.055633545,
+ 0.083618164, 0.1078186, 0.12731934, 0.14138794, 0.14935303,
+ 0.15097046, 0.14630127, 0.13537598, 0.11877441, 0.097259521,
+ 0.071563721, 0.04284668, 0.012329102, -0.018859863,
+ -0.049346924, -0.077880859, -0.10342407, -0.12484741,
+ -0.14126587, -0.15213013, -0.1569519, -0.1555481, -0.14807129,
+ -0.13479614, -0.11624146, -0.093322754, -0.066864014,
+ -0.03793335, -0.0078735352, 0.022247314, 0.051116943,
+ 0.077575684, 0.10055542, 0.11920166, 0.13265991, 0.14047241,
+ 0.14233398, 0.13812256, 0.12814331, 0.11282349, 0.092681885,
+ 0.068695068, 0.0418396, 0.013122559, -0.016174316, -0.044891357,
+ -0.071929932, -0.096038818, -0.11636353, -0.1321106,
+ -0.14254761, -0.14733887, -0.14633179, -0.13955688, -0.12728882,
+ -0.11016846, -0.088745117, -0.063995361, -0.036987305,
+ -0.0087280273, 0.019592285, 0.04675293, 0.071746826,
+ 0.093536377, 0.11120605, 0.12411499, 0.13171387, 0.13366699,
+ 0.1300354, 0.12094116, 0.10671997, 0.088104248, 0.065765381,
+ 0.040588379, 0.013763428, -0.013702393, -0.040740967,
+ -0.066101074, -0.088897705, -0.10818481, -0.12310791,
+ -0.13314819, -0.13793945, -0.13720703, -0.13113403, -0.11993408,
+ -0.10400391, -0.084136963, -0.061126709, -0.035858154,
+ -0.0094299316, 0.017059326, 0.042633057, 0.066131592,
+ 0.086669922, 0.10345459, 0.11572266, 0.12304688, 0.12521362,
+ 0.12203979, 0.11373901, 0.10070801, 0.083435059, 0.062652588,
+ 0.039276123, 0.014221191, -0.011505127, -0.036773682,
+ -0.060638428, -0.082092285, -0.10025024, -0.11444092,
+ -0.12408447, -0.12875366, -0.12835693, -0.1229248, -0.11260986,
+ -0.097991943, -0.079620361, -0.058166504, -0.034698486,
+ -0.010040283, 0.014770508, 0.038665771, 0.06072998, 0.080108643,
+ 0.095916748, 0.10760498, 0.11471558, 0.11691284, 0.11419678,
+ 0.10671997, 0.094726562, 0.078765869, 0.059570312, 0.037811279,
+ 0.014465332, -0.0094604492, -0.03314209, -0.055480957,
+ -0.075592041, -0.09274292, -0.10617065, -0.11532593, -0.1199646,
+ -0.11981201, -0.11489868, -0.1055603, -0.092102051,
+ -0.075073242, -0.055267334, -0.033447266, -0.010467529,
+ 0.012634277, 0.035003662, 0.05569458, 0.073822021, 0.088745117,
+ 0.099853516, 0.10665894, 0.10894775, 0.10665894, 0.099853516,
+ 0.088897705, 0.07421875, 0.056427002, 0.0362854, 0.01461792,
+ -0.0077209473, -0.029754639, -0.050628662, -0.069519043,
+ -0.085571289, -0.098236084, -0.10702515, -0.11151123,
+ -0.11157227, -0.1072998, -0.098754883, -0.086364746,
+ -0.070739746, -0.052368164, -0.032104492, -0.01083374,
+ 0.01071167, 0.031585693, 0.050872803, 0.067932129, 0.081970215,
+ 0.092437744, 0.098999023, 0.10134888, 0.099395752, 0.093292236,
+ 0.083282471, 0.069763184, 0.053375244, 0.034729004, 0.01461792,
+ -0.0061035156, -0.026641846, -0.046142578, -0.063751221,
+ -0.078857422, -0.090820312, -0.099121094, -0.10351562,
+ -0.1038208, -0.099975586, -0.092254639, -0.080932617,
+ -0.066497803, -0.049560547, -0.030822754, -0.011016846,
+ 0.0090026855, 0.028411865, 0.046478271, 0.062408447,
+ 0.075592041, 0.085571289, 0.091827393, 0.094177246, 0.092590332,
+ 0.087097168, 0.077941895, 0.065582275, 0.050445557, 0.033203125,
+ 0.014587402, -0.0046691895, -0.023803711, -0.04196167,
+ -0.05847168, -0.072662354, -0.083892822, -0.091827393,
+ -0.096099854, -0.0965271, -0.093170166, -0.086181641,
+ -0.075775146, -0.062530518, -0.046875, -0.029510498,
+ -0.011169434, 0.0074462891, 0.02557373, 0.042419434,
+ 0.057342529, 0.069793701, 0.079162598, 0.085174561, 0.087585449,
+ 0.086273193, 0.081329346, 0.073028564, 0.061645508, 0.047668457,
+ 0.031768799, 0.014465332, -0.0034484863, -0.021209717,
+ -0.038208008, -0.053649902, -0.066925049, -0.077575684,
+ -0.085113525, -0.089233398, -0.08984375, -0.086914062,
+ -0.080535889, -0.071075439, -0.058837891, -0.044342041,
+ -0.028289795, -0.011230469, 0.0061340332, 0.023010254,
+ 0.038787842, 0.05279541, 0.064483643, 0.073394775, 0.079162598,
+ 0.081542969, 0.080535889, 0.07611084, 0.068481445, 0.058044434,
+ 0.045166016, 0.03036499, 0.014343262, -0.0023193359,
+ -0.018951416, -0.034820557, -0.049285889, -0.061828613,
+ -0.071868896, -0.079040527, -0.083099365, -0.083831787,
+ -0.081237793, -0.075500488, -0.066772461, -0.055480957,
+ -0.042114258, -0.027130127, -0.011230469, 0.0049133301,
+ 0.020751953, 0.035552979, 0.048706055, 0.059783936, 0.068237305,
+ 0.073760986, 0.076202393, 0.075408936, 0.07144165, 0.064483643,
+ 0.054840088, 0.042877197, 0.029174805, 0.014190674,
+ -0.0014038086, -0.016937256, -0.031829834, -0.045501709,
+ -0.057312012, -0.066864014, -0.073760986, -0.077667236,
+ -0.078552246, -0.076293945, -0.071044922, -0.063018799,
+ -0.052581787, -0.040100098, -0.026153564, -0.011291504,
+ 0.00390625, 0.018768311, 0.032714844, 0.045196533, 0.05569458,
+ 0.063751221, 0.069152832, 0.071563721, 0.070983887, 0.067443848,
+ 0.061035156, 0.052093506, 0.040985107, 0.028137207, 0.014099121,
+ -0.00051879883, -0.015197754, -0.029296875, -0.042205811,
+ -0.053466797, -0.062591553, -0.069213867, -0.073120117,
+ -0.07409668, -0.072113037, -0.067321777, -0.059906006,
+ -0.050140381, -0.038482666, -0.025360107, -0.011322021,
+ 0.0029907227, 0.017089844, 0.03036499, 0.042236328, 0.052276611,
+ 0.060089111, 0.0652771, 0.067749023, 0.067382812, 0.064117432,
+ 0.058227539, 0.04989624, 0.039428711, 0.02734375, 0.014099121,
+ 0.00021362305, -0.013702393, -0.027099609, -0.039459229,
+ -0.050231934, -0.058990479, -0.065429688, -0.069244385,
+ -0.0703125, -0.068634033, -0.064208984, -0.057250977,
+ -0.048126221, -0.037109375, -0.024688721, -0.011413574,
+ 0.0021972656, 0.015625, 0.028259277, 0.039642334, 0.049285889,
+ 0.056793213, 0.061889648, 0.064361572, 0.064117432, 0.061187744,
+ 0.05569458, 0.047851562, 0.03805542, 0.026611328, 0.014038086,
+ 0.00088500977, -0.012329102, -0.025146484, -0.03692627,
+ -0.047241211, -0.055725098, -0.061920166, -0.065673828,
+ -0.066864014, -0.065338135, -0.061279297, -0.05480957,
+ -0.046203613, -0.035797119, -0.024078369, -0.011474609,
+ 0.0014648438, 0.014251709, 0.02633667, 0.037200928, 0.046447754,
+ 0.053741455, 0.05871582, 0.061157227, 0.061096191, 0.058441162,
+ 0.053314209, 0.04598999, 0.036712646, 0.025909424, 0.014007568,
+ 0.0014953613, -0.011138916, -0.02331543, -0.034576416,
+ -0.044494629, -0.052612305, -0.058654785, -0.06237793,
+ -0.063598633, -0.062316895, -0.058563232, -0.052490234,
+ -0.044403076, -0.034606934, -0.023468018, -0.011505127,
+ 0.00079345703, 0.013000488, 0.024505615, 0.034942627,
+ 0.04385376, 0.050872803, 0.05569458, 0.058197021, 0.058227539,
+ 0.055786133, 0.051055908, 0.044158936, 0.035430908, 0.02520752,
+ 0.013916016, 0.0020141602, -0.009979248, -0.021636963,
+ -0.032440186, -0.041931152, -0.04977417, -0.055664062,
+ -0.059265137, -0.060577393, -0.05947876, -0.055999756,
+ -0.050354004, -0.042724609, -0.033447266, -0.022918701,
+ -0.011566162, 0.00018310547, 0.011810303, 0.022827148,
+ 0.032836914, 0.041381836, 0.048187256, 0.05291748, 0.055389404,
+ 0.055541992, 0.053344727, 0.048919678, 0.042449951, 0.034210205,
+ 0.024536133, 0.013793945, 0.0025024414, -0.008972168,
+ -0.020080566, -0.030426025, -0.039581299, -0.047149658,
+ -0.05279541, -0.056396484, -0.057739258, -0.056793213,
+ -0.053619385, -0.048309326, -0.041137695, -0.03237915,
+ -0.022369385, -0.011566162, -0.00039672852, 0.01071167,
+ 0.02130127, 0.030883789, 0.039123535, 0.045684814, 0.050292969,
+ 0.052764893, 0.053039551, 0.051055908, 0.046936035, 0.040863037,
+ 0.033081055, 0.023895264, 0.01373291, 0.0029296875,
+ -0.0079956055, -0.01864624, -0.028564453, -0.037353516,
+ -0.044647217, -0.050170898, -0.05368042, -0.055084229,
+ -0.054290771, -0.051361084, -0.046386719, -0.039642334,
+ -0.031311035, -0.021820068, -0.011566162, -0.00088500977,
+ 0.0097351074, 0.019836426, 0.029083252, 0.037017822,
+ 0.043365479, 0.04788208, 0.050323486, 0.050689697, 0.048919678,
+ 0.045074463, 0.039367676, 0.032012939, 0.02331543, 0.01361084,
+ 0.0033569336, -0.0071105957, -0.017303467, -0.026794434,
+ -0.035247803, -0.042327881, -0.047668457, -0.051147461,
+ -0.052581787, -0.0519104, -0.049224854, -0.044586182,
+ -0.03817749, -0.030334473, -0.021331787, -0.011505127,
+ -0.0013427734, 0.0088195801, 0.01852417, 0.027374268,
+ 0.03503418, 0.04119873, 0.045593262, 0.048034668, 0.048492432,
+ 0.046905518, 0.043334961, 0.037963867, 0.031005859, 0.022735596,
+ 0.013519287, 0.003692627, -0.0062866211, -0.016021729,
+ -0.025177002, -0.033325195, -0.040130615, -0.045349121,
+ -0.04876709, -0.050231934, -0.049713135, -0.047210693,
+ -0.04284668, -0.036834717, -0.029388428, -0.020812988,
+ -0.011474609, -0.001739502, 0.0079650879, 0.017272949,
+ 0.025787354, 0.033203125, 0.039154053, 0.043457031, 0.045898438,
+ 0.046417236, 0.04498291, 0.041687012, 0.036621094, 0.030029297,
+ 0.022186279, 0.013397217, 0.0040588379, -0.0054931641,
+ -0.014862061, -0.023651123, -0.031494141, -0.038085938,
+ -0.043151855, -0.046508789, -0.048034668, -0.047607422,
+ -0.045318604, -0.041259766, -0.035552979, -0.0284729,
+ -0.020324707, -0.011413574, -0.0021057129, 0.0071716309,
+ 0.016113281, 0.02432251, 0.031463623, 0.037231445, 0.041442871,
+ 0.043884277, 0.044464111, 0.043212891, 0.040100098, 0.035339355,
+ 0.02911377, 0.021636963, 0.013275146, 0.0043640137,
+ -0.0048217773, -0.013763428, -0.022216797, -0.029785156,
+ -0.03616333, -0.041107178, -0.044403076, -0.045928955,
+ -0.045623779, -0.043548584, -0.039703369, -0.034332275,
+ -0.027648926, -0.019866943, -0.011352539, -0.0024719238,
+ 0.0064697266, 0.015045166, 0.022918701, 0.029815674,
+ 0.035430908, 0.039520264, 0.04196167, 0.042602539, 0.041473389,
+ 0.038604736, 0.034118652, 0.02822876, 0.021148682, 0.013153076,
+ 0.0046081543, -0.0041503906, -0.012756348, -0.020874023,
+ -0.028167725, -0.034332275, -0.039154053, -0.042388916,
+ -0.043945312, -0.043762207, -0.041809082, -0.038238525,
+ -0.033172607, -0.026794434, -0.01940918, -0.011291504,
+ -0.0027770996, 0.0057678223, 0.014007568, 0.021636963,
+ 0.028259277, 0.033691406, 0.037719727, 0.040100098, 0.04083252,
+ 0.039825439, 0.03717041, 0.032958984, 0.027374268, 0.020599365,
+ 0.013000488, 0.0048522949, -0.0035400391, -0.011810303,
+ -0.019592285, -0.026641846, -0.032623291, -0.03729248,
+ -0.040466309, -0.042053223, -0.041931152, -0.04019165,
+ -0.036834717, -0.032012939, -0.026000977, -0.018951416,
+ -0.011199951, -0.0030517578, 0.0051269531, 0.013061523,
+ 0.020385742, 0.026794434, 0.032073975, 0.035980225, 0.038360596,
+ 0.039123535, 0.038238525, 0.035766602, 0.031799316, 0.026489258,
+ 0.020080566, 0.0128479, 0.0050354004, -0.0029907227,
+ -0.010894775, -0.018432617, -0.02520752, -0.030975342,
+ -0.035522461, -0.038635254, -0.040222168, -0.04019165,
+ -0.038574219, -0.035430908, -0.030914307, -0.025177002,
+ -0.018493652, -0.011108398, -0.0032958984, 0.0045471191,
+ 0.012145996, 0.019195557, 0.025390625, 0.030487061, 0.034301758,
+ 0.036651611, 0.037475586, 0.036712646, 0.034423828, 0.030670166,
+ 0.025665283, 0.019592285, 0.012664795, 0.0052185059,
+ -0.0024719238, -0.010070801, -0.017272949, -0.023803711,
+ -0.029388428, -0.033813477, -0.036865234, -0.038452148,
+ -0.038513184, -0.03704834, -0.034118652, -0.029815674,
+ -0.024383545, -0.018035889, -0.010955811, -0.0035400391,
+ 0.0039978027, 0.011291504, 0.018066406, 0.024047852,
+ 0.028991699, 0.032684326, 0.035003662, 0.035858154, 0.035217285,
+ 0.033081055, 0.029571533, 0.024810791, 0.019042969, 0.012481689,
+ 0.0053405762, -0.0020141602, -0.0092773438, -0.016204834,
+ -0.022491455, -0.027862549, -0.032165527, -0.03515625,
+ -0.036743164, -0.036865234, -0.035552979, -0.032775879,
+ -0.028778076, -0.023620605, -0.017547607, -0.01083374,
+ -0.0037231445, 0.0034790039, 0.010467529, 0.016967773,
+ 0.022735596, 0.027526855, 0.03112793, 0.033416748, 0.034301758,
+ 0.033752441, 0.031768799, 0.0284729, 0.023986816, 0.01852417,
+ 0.012237549, 0.0054626465, -0.0015563965, -0.0085449219,
+ -0.015167236, -0.021209717, -0.026428223, -0.030578613,
+ -0.033508301, -0.035095215, -0.03527832, -0.034057617,
+ -0.031524658, -0.027709961, -0.022827148, -0.017089844,
+ -0.010681152, -0.00390625, 0.0029907227, 0.0096740723,
+ 0.015960693, 0.021484375, 0.026123047, 0.029632568, 0.031890869,
+ 0.032806396, 0.032348633, 0.030517578, 0.027435303, 0.023193359,
+ 0.017974854, 0.012023926, 0.0055847168, -0.001159668,
+ -0.0078125, -0.014190674, -0.020019531, -0.025024414,
+ -0.029052734, -0.031921387, -0.033477783, -0.033752441,
+ -0.032653809, -0.030273438, -0.026702881, -0.022094727,
+ -0.016601562, -0.010498047, -0.0040588379, 0.002532959,
+ 0.008972168, 0.014953613, 0.020324707, 0.024780273, 0.028198242,
+ 0.030426025, 0.031341553, 0.030975342, 0.029296875, 0.026397705,
+ 0.022399902, 0.017486572, 0.011810303, 0.005645752,
+ -0.00076293945, -0.0071411133, -0.013275146, -0.018859863,
+ -0.023681641, -0.027587891, -0.03036499, -0.031951904,
+ -0.03225708, -0.03125, -0.029052734, -0.025695801, -0.02130127,
+ -0.016113281, -0.010345459, -0.0041503906, 0.0021362305,
+ 0.0082702637, 0.014068604, 0.019195557, 0.023498535,
+ 0.026824951, 0.028991699, 0.029937744, 0.029663086, 0.028106689,
+ 0.025390625, 0.021636963, 0.016967773, 0.011566162,
+ 0.0057067871, -0.00039672852, -0.0065307617, -0.012359619,
+ -0.01776123, -0.022399902, -0.026184082, -0.028900146,
+ -0.030456543, -0.030792236, -0.029937744, -0.027862549,
+ -0.024688721, -0.020568848, -0.015655518, -0.010131836,
+ -0.0042419434, 0.001739502, 0.0076599121, 0.013183594,
+ 0.018127441, 0.022277832, 0.025512695, 0.027618408, 0.028625488,
+ 0.028381348, 0.026977539, 0.02444458, 0.020874023, 0.016448975,
+ 0.011352539, 0.0057373047, -9.1552734e-05, -0.0059204102,
+ -0.011535645, -0.016693115, -0.021179199, -0.024841309,
+ -0.02746582, -0.029022217, -0.029418945, -0.028625488,
+ -0.026702881, -0.023742676, -0.019866943, -0.015197754,
+ -0.0099487305, -0.0043334961, 0.0014038086, 0.007019043,
+ 0.012329102, 0.017089844, 0.021087646, 0.024230957, 0.026306152,
+ 0.027282715, 0.027130127, 0.025848389, 0.023468018, 0.020111084,
+ 0.015930176, 0.011077881, 0.0057678223, 0.00021362305,
+ -0.0053710938, -0.010742188, -0.015686035, -0.020019531,
+ -0.023529053, -0.026123047, -0.027648926, -0.028076172,
+ -0.027374268, -0.025604248, -0.022827148, -0.019134521,
+ -0.01473999, -0.0097351074, -0.0043945312, 0.0010681152,
+ 0.0064697266, 0.011535645, 0.016113281, 0.019989014,
+ 0.023010254, 0.025024414, 0.026031494, 0.025939941, 0.024749756,
+ 0.02255249, 0.019378662, 0.015411377, 0.01083374, 0.0057678223,
+ 0.00045776367, -0.0048522949, -0.010009766, -0.01473999,
+ -0.018890381, -0.02230835, -0.024810791, -0.02633667,
+ -0.026794434, -0.026184082, -0.024536133, -0.021911621,
+ -0.018463135, -0.014251709, -0.009552002, -0.0044555664,
+ 0.00076293945, 0.0059204102, 0.010772705, 0.015167236,
+ 0.018890381, 0.021820068, 0.023803711, 0.024810791, 0.024749756,
+ 0.023681641, 0.021606445, 0.01864624, 0.014923096, 0.010559082,
+ 0.0057373047, 0.0007019043, -0.0043945312, -0.0093078613,
+ -0.01385498, -0.017852783, -0.021118164, -0.02355957,
+ -0.025054932, -0.025543213, -0.024993896, -0.023498535,
+ -0.021057129, -0.01776123, -0.013824463, -0.0093383789,
+ -0.004486084, 0.00048828125, 0.0054016113, 0.010040283,
+ 0.014251709, 0.017852783, 0.020690918, 0.022613525, 0.023620605,
+ 0.023620605, 0.022644043, 0.020721436, 0.017944336, 0.014404297,
+ 0.010284424, 0.0057373047, 0.00091552734, -0.0039367676,
+ -0.0086364746, -0.013000488, -0.016815186, -0.019989014,
+ -0.022338867, -0.023803711, -0.02432251, -0.023864746,
+ -0.022460938, -0.020172119, -0.017089844, -0.013366699,
+ -0.0090942383, -0.0045166016, 0.00021362305, 0.0049133301,
+ 0.0093688965, 0.013397217, 0.016845703, 0.019592285,
+ 0.021484375, 0.022491455, 0.022521973, 0.021636963, 0.019866943,
+ 0.017242432, 0.013916016, 0.010009766, 0.0056762695,
+ 0.0010986328, -0.0035095215, -0.0079956055, -0.012176514,
+ -0.015838623, -0.018890381, -0.021179199, -0.022613525,
+ -0.023162842, -0.022766113, -0.021484375, -0.019348145,
+ -0.016418457, -0.012908936, -0.0088806152, -0.0045166016, 0,
+ 0.0044555664, 0.0087280273, 0.012573242, 0.015899658,
+ 0.01852417, 0.020385742, 0.021362305, 0.021484375, 0.0206604,
+ 0.019012451, 0.016571045, 0.013427734, 0.0097045898,
+ 0.0056152344, 0.0012817383, -0.003112793, -0.0073852539,
+ -0.011383057, -0.014923096, -0.017822266, -0.020050049,
+ -0.021453857, -0.022003174, -0.021697998, -0.020507812,
+ -0.018493652, -0.015777588, -0.012451172, -0.0086364746,
+ -0.004486084, -0.00021362305, 0.0040588379, 0.0080871582,
+ 0.011779785, 0.014984131, 0.01751709, 0.019317627, 0.020294189,
+ 0.020446777, 0.019714355, 0.018188477, 0.015869141, 0.012908936,
+ 0.0094299316, 0.0055541992, 0.0014343262, -0.002746582,
+ -0.0068359375, -0.010620117, -0.014007568, -0.016815186,
+ -0.018981934, -0.020355225, -0.020904541, -0.020629883,
+ -0.019561768, -0.017700195, -0.015136719, -0.011993408,
+ -0.008392334, -0.0044555664, -0.00039672852, 0.0036621094,
+ 0.0075073242, 0.011047363, 0.014099121, 0.016540527,
+ 0.018280029, 0.019256592, 0.01940918, 0.018768311, 0.017364502,
+ 0.015197754, 0.012420654, 0.0091247559, 0.0054626465,
+ 0.0015563965, -0.0024108887, -0.0062866211, -0.0099182129,
+ -0.013153076, -0.015838623, -0.017913818, -0.019256592,
+ -0.019836426, -0.019622803, -0.018615723, -0.016876221,
+ -0.01449585, -0.011535645, -0.0081176758, -0.0044250488,
+ -0.00057983398, 0.0032958984, 0.0069580078, 0.010314941,
+ 0.013244629, 0.015594482, 0.017272949, 0.018249512, 0.018432617,
+ 0.017852783, 0.016540527, 0.014556885, 0.011932373,
+ 0.0088195801, 0.0053710938, 0.0016784668, -0.0021057129,
+ -0.0057678223, -0.0092468262, -0.012329102, -0.014892578,
+ -0.016906738, -0.018188477, -0.018768311, -0.018615723,
+ -0.017700195, -0.016082764, -0.01385498, -0.011047363,
+ -0.0078430176, -0.0043640137, -0.0007019043, 0.0029296875,
+ 0.006439209, 0.0096435547, 0.012451172, 0.014678955,
+ 0.016326904, 0.017272949, 0.017486572, 0.016967773, 0.01574707,
+ 0.013885498, 0.011444092, 0.0085144043, 0.0052490234,
+ 0.0017700195, -0.0018005371, -0.0053100586, -0.0085754395,
+ -0.011535645, -0.014007568, -0.015899658, -0.017181396,
+ -0.01776123, -0.01763916, -0.016815186, -0.015319824,
+ -0.013214111, -0.0105896, -0.007598877, -0.0042724609,
+ -0.00082397461, 0.0026245117, 0.0059509277, 0.0090026855,
+ 0.011657715, 0.013824463, 0.015380859, 0.016296387, 0.016540527,
+ 0.016082764, 0.014984131, 0.013244629, 0.010955811,
+ 0.0082092285, 0.0051269531, 0.0018310547, -0.0015258789,
+ -0.0048522949, -0.0079650879, -0.010772705, -0.013122559,
+ -0.014953613, -0.016204834, -0.01675415, -0.016693115,
+ -0.015930176, -0.014556885, -0.01260376, -0.010131836,
+ -0.0072937012, -0.0042114258, -0.00094604492, 0.0023193359,
+ 0.0054626465, 0.0083618164, 0.010894775, 0.012939453,
+ 0.014465332, 0.015380859, 0.015625, 0.015228271, 0.014221191,
+ 0.012573242, 0.010467529, 0.0078735352, 0.0049743652,
+ 0.0018920898, -0.0012817383, -0.0044250488, -0.0073852539,
+ -0.010040283, -0.012298584, -0.014038086, -0.015228271,
+ -0.015808105, -0.01574707, -0.015075684, -0.013793945,
+ -0.011962891, -0.0096740723, -0.007019043, -0.004119873,
+ -0.0010375977, 0.0020446777, 0.0050048828, 0.0077514648,
+ 0.010162354, 0.012115479, 0.013580322, 0.014465332, 0.01473999,
+ 0.014373779, 0.013458252, 0.011962891, 0.0099487305,
+ 0.0075378418, 0.0048522949, 0.0019226074, -0.0010681152,
+ -0.0040283203, -0.0068054199, -0.0093383789, -0.011474609,
+ -0.013153076, -0.014312744, -0.014862061, -0.014862061,
+ -0.014251709, -0.013061523, -0.011352539, -0.0092468262,
+ -0.0067443848, -0.0039978027, -0.0011291504, 0.0017700195,
+ 0.0045776367, 0.0071716309, 0.0094604492, 0.011322021,
+ 0.012695312, 0.013580322, 0.01385498, 0.013549805, 0.012695312,
+ 0.011322021, 0.0094604492, 0.007232666, 0.0046691895,
+ 0.001953125, -0.00085449219, -0.0036621094, -0.0062866211,
+ -0.0086669922, -0.010681152, -0.012298584, -0.013397217,
+ -0.013946533, -0.013946533, -0.013397217, -0.012329102,
+ -0.010772705, -0.0087585449, -0.006439209, -0.00390625,
+ -0.0011901855, 0.0015258789, 0.0041809082, 0.0066223145,
+ 0.0087585449, 0.010559082, 0.011871338, 0.012695312,
+ 0.013000488, 0.012756348, 0.011962891, 0.010681152, 0.008972168,
+ 0.0068664551, 0.0045166016, 0.001953125, -0.00067138672,
+ -0.0032958984, -0.0057678223, -0.0079956055, -0.0099182129,
+ -0.011444092, -0.012512207, -0.013061523, -0.013092041,
+ -0.01260376, -0.01159668, -0.010162354, -0.0083007812,
+ -0.0061645508, -0.0037536621, -0.0012512207, 0.0013122559,
+ 0.0037841797, 0.0061035156, 0.0081176758, 0.0097961426,
+ 0.011077881, 0.011871338, 0.012145996, 0.011932373, 0.011230469,
+ 0.010070801, 0.0084838867, 0.0065307617, 0.0043334961,
+ 0.001953125, -0.00051879883, -0.0029602051, -0.005279541,
+ -0.0073852539, -0.009185791, -0.010620117, -0.011627197,
+ -0.012176514, -0.012237549, -0.011779785, -0.010894775,
+ -0.009552002, -0.0078430176, -0.0058288574, -0.0036315918,
+ -0.0012817383, 0.0011291504, 0.0034179688, 0.0055847168,
+ 0.0075073242, 0.0090637207, 0.010284424, 0.011047363,
+ 0.011322021, 0.011169434, 0.010528564, 0.0094604492,
+ 0.0079956055, 0.0061950684, 0.0041503906, 0.0019226074,
+ -0.00036621094, -0.0026550293, -0.0048217773, -0.0067749023,
+ -0.0084838867, -0.0098266602, -0.010803223, -0.011322021,
+ -0.011383057, -0.010986328, -0.010192871, -0.0089416504,
+ -0.0073852539, -0.0055236816, -0.0034790039, -0.0012817383,
+ 0.00094604492, 0.0030822754, 0.0050964355, 0.0068969727,
+ 0.008392334, 0.0095214844, 0.010223389, 0.010528564,
+ 0.010406494, 0.0098266602, 0.0088500977, 0.0075073242,
+ 0.0058288574, 0.0039672852, 0.0018920898, -0.00021362305,
+ -0.0023498535, -0.0043640137, -0.0061950684, -0.0077819824,
+ -0.0090637207, -0.009979248, -0.010467529, -0.010559082,
+ -0.010223389, -0.0094909668, -0.0083618164, -0.0069274902,
+ -0.0052185059, -0.0032958984, -0.0012817383, 0.00076293945,
+ 0.0027770996, 0.0046386719, 0.0063171387, 0.0076904297,
+ 0.0087585449, 0.0094604492, 0.0097351074, 0.0096435547,
+ 0.0091247559, 0.0082397461, 0.0069885254, 0.0054931641,
+ 0.0037536621, 0.0018615723, -0.00012207031, -0.0020751953,
+ -0.0039367676, -0.005645752, -0.0071411133, -0.0083312988,
+ -0.009185791, -0.0096740723, -0.009765625, -0.0094604492,
+ -0.0087890625, -0.0077819824, -0.006439209, -0.0048828125,
+ -0.0031433105, -0.0012817383, 0.00061035156, 0.0024719238,
+ 0.0042114258, 0.0057373047, 0.0070495605, 0.008026123,
+ 0.0086975098, 0.008972168, 0.0088806152, 0.0084228516,
+ 0.0076293945, 0.0065002441, 0.0051269531, 0.0035400391,
+ 0.0018005371, 0, -0.0018005371, -0.0035400391, -0.0051269531,
+ -0.0065002441, -0.007598877, -0.008392334, -0.0088500977,
+ -0.008972168, -0.0086975098, -0.0081176758, -0.0071716309,
+ -0.0059814453, -0.0045471191, -0.0029602051, -0.0012512207,
+ 0.00048828125, 0.0021972656, 0.0037841797, 0.0052185059,
+ 0.0064086914, 0.0073242188, 0.0079345703, 0.0082092285,
+ 0.0081481934, 0.0077514648, 0.007019043, 0.0060119629,
+ 0.0047607422, 0.0032958984, 0.0017089844, 6.1035156e-05,
+ -0.0015869141, -0.0031738281, -0.0046081543, -0.0058898926,
+ -0.0068969727, -0.0076293945, -0.0080566406, -0.0081787109,
+ -0.0079650879, -0.0074157715, -0.0065917969, -0.0055236816,
+ -0.0042114258, -0.0027770996, -0.0012207031, 0.00036621094,
+ 0.0019226074, 0.0033874512, 0.0046691895, 0.0057678223,
+ 0.0066223145, 0.0072021484, 0.0074768066, 0.0074157715,
+ 0.0070800781, 0.0064086914, 0.0055236816, 0.0043945312,
+ 0.0030822754, 0.0016479492, 0.00015258789, -0.001373291,
+ -0.0028076172, -0.004119873, -0.005279541, -0.0062255859,
+ -0.0068969727, -0.0072937012, -0.0074157715, -0.007232666,
+ -0.0067443848, -0.0060119629, -0.0050354004, -0.0038757324,
+ -0.0025634766, -0.001159668, 0.0002746582, 0.0016784668,
+ 0.0029907227, 0.0041809082, 0.0051879883, 0.0059509277,
+ 0.0064697266, 0.0067443848, 0.0067138672, 0.0064086914,
+ 0.0058288574, 0.0050048828, 0.0039978027, 0.0028381348,
+ 0.0015258789, 0.00018310547, -0.001159668, -0.0024719238,
+ -0.0036621094, -0.004699707, -0.0055541992, -0.0061645508,
+ -0.0065307617, -0.006652832, -0.0065002441, -0.006072998,
+ -0.0054321289, -0.0045776367, -0.0035400391, -0.0023498535,
+ -0.0010986328, 0.00018310547, 0.0014343262, 0.0026245117,
+ 0.003692627, 0.0046081543, 0.0053100586, 0.0057678223,
+ 0.0060119629, 0.0060119629, 0.0057373047, 0.0052185059,
+ 0.0045166016, 0.0036315918, 0.0025634766, 0.0014343262,
+ 0.00024414062, -0.0009765625, -0.0021362305, -0.0032043457,
+ -0.0041503906, -0.0049133301, -0.0054626465, -0.0057983398,
+ -0.0059204102, -0.0057678223, -0.0054321289, -0.0048522949,
+ -0.0040893555, -0.0031738281, -0.0021362305, -0.0010375977,
+ 9.1552734e-05, 0.0012207031, 0.0022583008, 0.0032348633,
+ 0.0040283203, 0.0046691895, 0.0050964355, 0.0053100586,
+ 0.0053100586, 0.005065918, 0.0046386719, 0.0039978027,
+ 0.0032348633, 0.0023193359, 0.0013122559, 0.00024414062,
+ -0.00079345703, -0.0018310547, -0.0027770996, -0.0036010742,
+ -0.0042724609, -0.0047607422, -0.005065918, -0.0051879883,
+ -0.005065918, -0.0047607422, -0.0042724609, -0.0036010742,
+ -0.0028076172, -0.0019226074, -0.00094604492, 3.0517578e-05,
+ 0.0010070801, 0.0019226074, 0.0027770996, 0.0034790039,
+ 0.0040283203, 0.0043945312, 0.0046081543, 0.0046081543,
+ 0.0044250488, 0.0040283203, 0.0035095215, 0.0028381348,
+ 0.0020446777, 0.001159668, 0.0002746582, -0.00064086914,
+ -0.0015258789, -0.0023498535, -0.0030517578, -0.0036621094,
+ -0.0040893555, -0.0043640137, -0.0044555664, -0.0043640137,
+ -0.0040893555, -0.003692627, -0.003112793, -0.0024414062,
+ -0.0016784668, -0.00085449219, 0, 0.00082397461, 0.0016174316,
+ 0.0023193359, 0.0029296875, 0.0034179688, 0.0037536621,
+ 0.00390625, 0.00390625, 0.0037536621, 0.0034484863,
+ 0.0029907227, 0.0024414062, 0.0017700195, 0.0010375977,
+ 0.0002746582, -0.00051879883, -0.0012512207, -0.001953125,
+ -0.0025634766, -0.0030517578, -0.0034179688, -0.0036621094,
+ -0.0037231445, -0.0036621094, -0.0034484863, -0.003112793,
+ -0.0026245117, -0.0020751953, -0.0014343262, -0.00073242188,
+ -3.0517578e-05, 0.00067138672, 0.0013122559, 0.0019226074,
+ 0.0024108887, 0.0028076172, 0.0030822754, 0.0032348633,
+ 0.0032348633, 0.003112793, 0.0028686523, 0.0025024414,
+ 0.0020141602, 0.0014648438, 0.00088500977, 0.00024414062,
+ -0.00039672852, -0.0010070801, -0.0015563965, -0.0020446777,
+ -0.0024719238, -0.0027770996, -0.0029602051, -0.0030212402,
+ -0.0029602051, -0.0028076172, -0.0025024414, -0.0021362305,
+ -0.0016784668, -0.001159668, -0.00061035156, -6.1035156e-05,
+ 0.00048828125, 0.0010375977, 0.0014953613, 0.0018920898,
+ 0.0022277832, 0.0024414062, 0.0025634766, 0.0025634766,
+ 0.0024719238, 0.0022583008, 0.0019836426, 0.0016174316,
+ 0.0011901855, 0.0007019043, 0.00021362305, -0.0002746582,
+ -0.00076293945, -0.0011901855, -0.0015563965, -0.0018920898,
+ -0.0021057129, -0.0022583008, -0.0023193359, -0.0022583008,
+ -0.0021362305, -0.0019226074, -0.0016479492, -0.0012817383,
+ -0.00091552734, -0.00048828125, -6.1035156e-05, 0.00036621094,
+ 0.00076293945, 0.0010986328, 0.0014038086, 0.0016479492,
+ 0.0018005371, 0.0018920898, 0.0018920898, 0.0018005371,
+ 0.0016479492, 0.0014343262, 0.0011901855, 0.00085449219,
+ 0.00051879883, 0.00018310547, -0.00018310547, -0.00051879883,
+ -0.00082397461, -0.0010986328, -0.0013122559, -0.0014953613,
+ -0.0015869141, -0.0016174316, -0.0015869141, -0.0014953613,
+ -0.0013427734, -0.0011291504, -0.00088500977, -0.00064086914,
+ -0.00033569336, -6.1035156e-05, 0.00021362305, 0.00048828125,
+ 0.00073242188, 0.00091552734, 0.0010681152, 0.001159668,
+ 0.0012207031, 0.0012207031, 0.001159668, 0.0010681152,
+ 0.00091552734, 0.00073242188, 0.00054931641, 0.00033569336,
+ 0.00012207031, -9.1552734e-05, -0.00030517578, -0.00048828125,
+ -0.00064086914, -0.00076293945, -0.00085449219, -0.00091552734,
+ -0.00091552734, -0.00088500977, -0.00082397461, -0.00073242188,
+ -0.00061035156, -0.00048828125, -0.00033569336, -0.00018310547,
+ -3.0517578e-05, 0.00012207031, 0.00024414062, 0.00033569336,
+ 0.00042724609, 0.00048828125, 0.00054931641, 0.00054931641,
+ 0.00054931641, 0.00051879883, 0.00045776367, 0.00039672852,
+ 0.00030517578, 0.00021362305, 0.00012207031, 6.1035156e-05,
+ -3.0517578e-05, -9.1552734e-05, -0.00015258789, -0.00018310547,
+ -0.00021362305, -0.00024414062, -0.00024414062, -0.00021362305,
+ -0.00021362305, -0.00018310547, -0.00012207031, -9.1552734e-05,
+ -6.1035156e-05, -3.0517578e-05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,
+};
+
+const jack_nframes_t Session::default_click_emphasis_length = sizeof (default_click_emphasis) / sizeof (default_click_emphasis[0]);
+
+const Sample Session::default_click[] = {
+ 0, -0.014312744, -0.03338623, 0.019165039, 0.042541504,
+ 0.08984375, 0.082611084, 0.13909912, 0.17236328,
+ 0.19238281, 0.21087646, 0.22238159, 0.23114014,
+ 0.23321533, 0.23080444, 0.222229, 0.20944214,
+ 0.19146729, 0.16964722, 0.14352417, 0.11450195,
+ 0.082489014, 0.049316406, 0.014068604, -0.020507812,
+ -0.055969238, -0.089233398, -0.1211853, -0.15036011,
+ -0.1763916, -0.19882202, -0.2170105, -0.23062134,
+ -0.23916626, -0.24279785, -0.24142456, -0.23483276,
+ -0.22357178, -0.2074585, -0.18701172, -0.16308594,
+ -0.1355896, -0.10562134, -0.073547363, -0.040100098,
+ -0.0061645508, 0.027618408, 0.060394287, 0.091796875,
+ 0.12072754, 0.14685059, 0.16949463, 0.18804932,
+ 0.20245361, 0.21212769, 0.21694946, 0.21691895,
+ 0.21191406, 0.20214844, 0.18786621, 0.16943359,
+ 0.14706421, 0.12145996, 0.093139648, 0.06262207,
+ 0.030822754, -0.0018310547, -0.034545898, -0.066467285,
+ -0.097076416, -0.12564087, -0.15155029, -0.17422485,
+ -0.19326782, -0.20812988, -0.21862793, -0.22457886,
+ -0.22567749, -0.22210693, -0.21392822, -0.20120239,
+ -0.18438721, -0.16381836, -0.13986206, -0.11315918,
+ -0.084289551, -0.053771973, -0.02243042, 0.0090942383,
+ 0.040252686, 0.07019043, 0.098297119, 0.12408447,
+ 0.14685059, 0.16616821, 0.18170166, 0.19302368,
+ 0.19985962, 0.20230103, 0.20007324, 0.19329834,
+ 0.18231201, 0.16711426, 0.14819336, 0.1260376,
+ 0.10095215, 0.073608398, 0.044647217, 0.01461792,
+ -0.015808105, -0.04586792, -0.075073242, -0.10272217,
+ -0.12811279, -0.15084839, -0.17041016, -0.1862793,
+ -0.19827271, -0.20602417, -0.20941162, -0.20846558,
+ -0.203125, -0.19348145, -0.17990112, -0.16259766,
+ -0.14190674, -0.11846924, -0.092651367, -0.065002441,
+ -0.0362854, -0.0069885254, 0.022247314, 0.05065918,
+ 0.077758789, 0.10296631, 0.12561035, 0.14532471,
+ 0.16171265, 0.17428589, 0.18289185, 0.18737793,
+ 0.18756104, 0.18353271, 0.17541504, 0.16329956,
+ 0.14755249, 0.12854004, 0.10656738, 0.082244873,
+ 0.056091309, 0.028594971, 0.00045776367, -0.027709961,
+ -0.055419922, -0.08190918, -0.10665894, -0.12924194,
+ -0.14901733, -0.1656189, -0.17877197, -0.18811035,
+ -0.19342041, -0.19473267, -0.19189453, -0.18505859,
+ -0.17440796, -0.16009521, -0.14245605, -0.12203979,
+ -0.099121094, -0.07421875, -0.048034668, -0.020935059,
+ 0.0063781738, 0.033233643, 0.059234619, 0.083709717,
+ 0.1060791, 0.12600708, 0.14294434, 0.15655518,
+ 0.16662598, 0.17288208, 0.1751709, 0.17355347,
+ 0.16802979, 0.15866089, 0.14581299, 0.12966919,
+ 0.1105957, 0.089080811, 0.065551758, 0.040466309,
+ 0.014556885, -0.011779785, -0.037963867, -0.063293457,
+ -0.087341309, -0.10958862, -0.12942505, -0.14654541,
+ -0.16061401, -0.1711731, -0.17816162, -0.18145752,
+ -0.18081665, -0.17642212, -0.16836548, -0.15673828,
+ -0.14193726, -0.12426758, -0.10400391, -0.081695557,
+ -0.057891846, -0.032928467, -0.0075073242, 0.017791748,
+ 0.042602539, 0.066192627, 0.088134766, 0.10803223,
+ 0.12530518, 0.13967896, 0.15087891, 0.15859985,
+ 0.16265869, 0.16311646, 0.15982056, 0.1529541,
+ 0.14273071, 0.12921143, 0.11282349, 0.093963623,
+ 0.072967529, 0.050323486, 0.026580811, 0.002166748,
+ -0.022369385, -0.046356201, -0.069458008, -0.091094971,
+ -0.11071777, -0.12808228, -0.1427002, -0.15420532,
+ -0.16253662, -0.16741943, -0.16867065, -0.16647339,
+ -0.1607666, -0.15164185, -0.13946533, -0.12438965,
+ -0.10675049, -0.087036133, -0.065582275, -0.042877197,
+ -0.019500732, 0.004119873, 0.027496338, 0.049957275,
+ 0.071228027, 0.090759277, 0.10803223, 0.12283325,
+ 0.13482666, 0.14364624, 0.14923096, 0.15145874,
+ 0.150177, 0.14556885, 0.13772583, 0.1267395, 0.11294556,
+ 0.096679688, 0.078155518, 0.057952881, 0.036499023,
+ 0.014099121, -0.0085449219, -0.031066895, -0.052978516,
+ -0.073730469, -0.092895508, -0.11013794, -0.125,
+ -0.13717651, -0.14648438, -0.15264893, -0.15557861,
+ -0.15527344, -0.15164185, -0.14486694, -0.13510132,
+ -0.12252808, -0.10742188, -0.090240479, -0.07119751,
+ -0.05078125, -0.029510498, -0.0077514648, 0.014007568,
+ 0.035186768, 0.055480957, 0.074401855, 0.091430664,
+ 0.10638428, 0.11880493, 0.128479, 0.13525391,
+ 0.13891602, 0.1394043, 0.1368103, 0.13113403,
+ 0.12246704, 0.11114502, 0.097320557, 0.081268311,
+ 0.06350708, 0.044281006, 0.024017334, 0.0032958984,
+ -0.017578125, -0.038116455, -0.057769775, -0.07623291,
+ -0.093109131, -0.10791016, -0.12045288, -0.13046265,
+ -0.13763428, -0.14190674, -0.14321899, -0.14141846,
+ -0.13674927, -0.12921143, -0.11895752, -0.10629272,
+ -0.091491699, -0.074798584, -0.05670166, -0.037536621,
+ -0.017700195, 0.0022888184, 0.022033691, 0.04119873,
+ 0.059234619, 0.075805664, 0.090606689, 0.10324097,
+ 0.11346436, 0.12112427, 0.12594604, 0.12796021,
+ 0.12710571, 0.12335205, 0.11682129, 0.10772705,
+ 0.096191406, 0.082519531, 0.067077637, 0.050079346,
+ 0.031982422, 0.013244629, -0.0058898926, -0.024902344,
+ -0.043334961, -0.060882568, -0.077148438, -0.091674805,
+ -0.10430908, -0.11471558, -0.12261963, -0.12799072,
+ -0.13061523, -0.13043213, -0.12759399, -0.12203979,
+ -0.11392212, -0.10351562, -0.0909729, -0.076568604,
+ -0.060699463, -0.043640137, -0.025787354, -0.0075683594,
+ 0.010650635, 0.028503418, 0.045532227, 0.061431885,
+ 0.075836182, 0.088409424, 0.098968506, 0.10720825,
+ 0.11297607, 0.11621094, 0.11682129, 0.11471558,
+ 0.11010742, 0.10305786, 0.093658447, 0.082214355,
+ 0.068969727, 0.054138184, 0.038146973, 0.021331787,
+ 0.0039978027, -0.013397217, -0.030517578, -0.047027588,
+ -0.062469482, -0.076568604, -0.089080811, -0.099609375,
+ -0.10803223, -0.11419678, -0.1178894, -0.11907959,
+ -0.11779785, -0.11398315, -0.1078186, -0.099456787,
+ -0.089019775, -0.076751709, -0.063018799, -0.047973633,
+ -0.032073975, -0.015655518, 0.0010070801, 0.017456055,
+ 0.033355713, 0.048431396, 0.062286377, 0.074615479,
+ 0.085235596, 0.09387207, 0.10031128, 0.10449219,
+ 0.10629272, 0.10565186, 0.10272217, 0.097442627,
+ 0.089996338, 0.080566406, 0.069366455, 0.05657959,
+ 0.042633057, 0.027679443, 0.012115479, -0.003692627,
+ -0.01940918, -0.034759521, -0.049316406, -0.062835693,
+ -0.075012207, -0.085510254, -0.094238281, -0.10095215,
+ -0.10543823, -0.10775757, -0.10778809, -0.10552979,
+ -0.10107422, -0.094512939, -0.085968018, -0.075714111,
+ -0.063934326, -0.050842285, -0.036834717, -0.022125244,
+ -0.0070800781, 0.0079650879, 0.022705078, 0.036865234,
+ 0.050018311, 0.061981201, 0.072479248, 0.081268311,
+ 0.088165283, 0.093109131, 0.095855713, 0.096466064,
+ 0.094940186, 0.091247559, 0.085510254, 0.077911377,
+ 0.06854248, 0.057678223, 0.045593262, 0.032440186,
+ 0.018585205, 0.0043640137, -0.010009766, -0.024169922,
+ -0.037750244, -0.050567627, -0.062286377, -0.072631836,
+ -0.081451416, -0.088500977, -0.093658447, -0.096862793,
+ -0.097961426, -0.097015381, -0.094024658, -0.089080811,
+ -0.082275391, -0.073791504, -0.063812256, -0.052520752,
+ -0.040252686, -0.027191162, -0.013641357, 0,
+ 0.013580322, 0.026733398, 0.039154053, 0.050628662,
+ 0.060913086, 0.069702148, 0.076934814, 0.082366943,
+ 0.085906982, 0.087524414, 0.087158203, 0.08480835,
+ 0.080627441, 0.074615479, 0.066925049, 0.057800293,
+ 0.047393799, 0.035888672, 0.023651123, 0.010894775,
+ -0.0021362305, -0.015106201, -0.027740479, -0.039825439,
+ -0.050994873, -0.061096191, -0.069885254, -0.077148438,
+ -0.082763672, -0.086669922, -0.088684082, -0.088806152,
+ -0.087097168, -0.083526611, -0.07824707, -0.071350098,
+ -0.062957764, -0.053314209, -0.042633057, -0.031066895,
+ -0.018981934, -0.0065917969, 0.0058288574, 0.018035889,
+ 0.029693604, 0.040649414, 0.050628662, 0.059356689,
+ 0.066741943, 0.072570801, 0.076721191, 0.079162598,
+ 0.079772949, 0.078613281, 0.075714111, 0.071105957,
+ 0.064880371, 0.057281494, 0.048400879, 0.038421631,
+ 0.027618408, 0.016204834, 0.0043945312, -0.0074768066,
+ -0.019195557, -0.030548096, -0.041168213, -0.050964355,
+ -0.059661865, -0.067047119, -0.072998047, -0.077453613,
+ -0.080169678, -0.081237793, -0.080596924, -0.078216553,
+ -0.074249268, -0.068725586, -0.061767578, -0.05355835,
+ -0.044281006, -0.034088135, -0.023284912, -0.012084961,
+ -0.00067138672, 0.010650635, 0.021606445, 0.032043457,
+ 0.041687012, 0.050292969, 0.057769775, 0.063903809,
+ 0.06854248, 0.071655273, 0.073120117, 0.072937012,
+ 0.071166992, 0.067749023, 0.062835693, 0.056549072,
+ 0.048980713, 0.040313721, 0.030792236, 0.020568848,
+ 0.0098571777, -0.0010375977, -0.011932373, -0.022613525,
+ -0.032745361, -0.042236328, -0.050811768, -0.058288574,
+ -0.064544678, -0.069396973, -0.072784424, -0.074645996,
+ -0.074890137, -0.073547363, -0.070709229, -0.066375732,
+ -0.060638428, -0.05368042, -0.045593262, -0.036590576,
+ -0.026916504, -0.016693115, -0.0061950684, 0.0043334961,
+ 0.014709473, 0.024688721, 0.0340271, 0.042572021,
+ 0.050109863, 0.05645752, 0.061553955, 0.065246582,
+ 0.06741333, 0.068115234, 0.067260742, 0.064880371,
+ 0.061065674, 0.055908203, 0.049468994, 0.04196167,
+ 0.033508301, 0.024291992, 0.014556885, 0.004486084,
+ -0.0057067871, -0.015777588, -0.025512695, -0.034759521,
+ -0.043212891, -0.050750732, -0.057250977, -0.062469482,
+ -0.066375732, -0.068908691, -0.069915771, -0.069488525,
+ -0.067596436, -0.064239502, -0.05960083, -0.053710938,
+ -0.046691895, -0.038726807, -0.029998779, -0.020690918,
+ -0.011016846, -0.001159668, 0.0086364746, 0.018188477,
+ 0.027252197, 0.035675049, 0.043243408, 0.04977417,
+ 0.055175781, 0.059326172, 0.062103271, 0.063537598,
+ 0.063476562, 0.062011719, 0.059173584, 0.054992676,
+ 0.049591064, 0.043121338, 0.035675049, 0.027404785,
+ 0.018554688, 0.0092468262, -0.00024414062,
+ -0.0097351074, -0.019042969, -0.027954102, -0.036254883,
+ -0.043792725, -0.050415039, -0.055938721, -0.060272217,
+ -0.063323975, -0.065032959, -0.065368652, -0.064300537,
+ -0.061889648, -0.058197021, -0.053283691, -0.047241211,
+ -0.040283203, -0.032470703, -0.024017334, -0.015136719,
+ -0.0059814453, 0.0032653809, 0.012329102, 0.021087646,
+ 0.029327393, 0.036834717, 0.043487549, 0.049133301,
+ 0.053649902, 0.056945801, 0.058959961, 0.059631348,
+ 0.058990479, 0.057006836, 0.053741455, 0.049316406,
+ 0.043762207, 0.037231445, 0.029876709, 0.021881104,
+ 0.013366699, 0.0045471191, -0.0043640137, -0.013214111,
+ -0.021759033, -0.029876709, -0.037353516, -0.044006348,
+ -0.049743652, -0.054412842, -0.057922363, -0.060180664,
+ -0.061187744, -0.060913086, -0.059295654, -0.056488037,
+ -0.052459717, -0.047363281, -0.041290283, -0.034362793,
+ -0.026733398, -0.018615723, -0.010131836, -0.0014953613,
+ 0.0070800781, 0.01550293, 0.023498535, 0.030883789,
+ 0.037597656, 0.043426514, 0.048217773, 0.051940918,
+ 0.054473877, 0.055786133, 0.055847168, 0.054656982,
+ 0.052215576, 0.04864502, 0.04397583, 0.038330078,
+ 0.031829834, 0.024627686, 0.016845703, 0.0087280273,
+ 0.00039672852, -0.0079650879, -0.016143799,
+ -0.024017334, -0.03137207, -0.038024902, -0.043914795,
+ -0.048828125, -0.052703857, -0.055480957, -0.057067871,
+ -0.057434082, -0.05657959, -0.05456543, -0.051361084,
+ -0.047119141, -0.041900635, -0.035797119, -0.028961182,
+ -0.021575928, -0.013763428, -0.0057067871, 0.0023803711,
+ 0.010406494, 0.018127441, 0.025390625, 0.032073975,
+ 0.037994385, 0.043029785, 0.047088623, 0.050048828,
+ 0.0519104, 0.052581787, 0.052093506, 0.050415039,
+ 0.047637939, 0.043792725, 0.03894043, 0.03326416,
+ 0.026824951, 0.019775391, 0.012329102, 0.0045776367,
+ -0.0032958984, -0.011108398, -0.018676758, -0.025878906,
+ -0.032501221, -0.038421631, -0.043548584, -0.047729492,
+ -0.05090332, -0.053009033, -0.053955078, -0.053771973,
+ -0.052459717, -0.050018311, -0.046539307, -0.042114258,
+ -0.036773682, -0.030731201, -0.024047852, -0.016876221,
+ -0.0094299316, -0.0018005371, 0.0058288574, 0.013244629,
+ 0.020324707, 0.026916504, 0.032867432, 0.03805542,
+ 0.042388916, 0.04574585, 0.048034668, 0.049285889,
+ 0.049407959, 0.048400879, 0.046356201, 0.043243408,
+ 0.03918457, 0.034240723, 0.028533936, 0.022216797,
+ 0.015380859, 0.0082092285, 0.00082397461, -0.0065612793,
+ -0.013824463, -0.020812988, -0.02734375, -0.033294678,
+ -0.038543701, -0.042938232, -0.046478271, -0.048980713,
+ -0.050445557, -0.050842285, -0.050170898, -0.048431396,
+ -0.045684814, -0.041992188, -0.037384033, -0.032073975,
+ -0.026062012, -0.01953125, -0.012634277, -0.0055236816,
+ 0.0016784668, 0.0087890625, 0.015655518, 0.022125244,
+ 0.028076172, 0.033355713, 0.037902832, 0.041534424,
+ 0.044250488, 0.045959473, 0.046630859, 0.046264648,
+ 0.04486084, 0.042419434, 0.039093018, 0.034851074,
+ 0.029846191, 0.024200439, 0.018005371, 0.011383057,
+ 0.0045166016, -0.0024719238, -0.0093994141,
+ -0.016143799, -0.022521973, -0.0284729, -0.033752441,
+ -0.038360596, -0.042144775, -0.045013428, -0.046936035,
+ -0.047851562, -0.04776001, -0.046630859, -0.044555664,
+ -0.041534424, -0.037628174, -0.032989502, -0.027618408,
+ -0.021728516, -0.015411377, -0.0087585449,
+ -0.0020141602, 0.0047607422, 0.011383057, 0.017700195,
+ 0.023590088, 0.028930664, 0.033569336, 0.037475586,
+ 0.040527344, 0.042633057, 0.043792725, 0.04397583,
+ 0.043151855, 0.041381836, 0.038696289, 0.035125732,
+ 0.030792236, 0.025756836, 0.020172119, 0.014099121,
+ 0.0077514648, 0.0011901855, -0.0053710938, -0.01184082,
+ -0.018066406, -0.023925781, -0.02923584, -0.033966064,
+ -0.037963867, -0.041107178, -0.043426514, -0.044799805,
+ -0.045196533, -0.044677734, -0.043182373, -0.040802002,
+ -0.037567139, -0.033538818, -0.028808594, -0.023498535,
+ -0.017730713, -0.01159668, -0.005279541, 0.0011291504,
+ 0.0074768066, 0.013580322, 0.019378662, 0.024719238,
+ 0.02947998, 0.033538818, 0.036865234, 0.039306641,
+ 0.040893555, 0.041564941, 0.041290283, 0.040100098,
+ 0.038024902, 0.035064697, 0.031341553, 0.026947021,
+ 0.021942139, 0.016418457, 0.0105896, 0.0044555664,
+ -0.001739502, -0.0079345703, -0.013946533, -0.019683838,
+ -0.024963379, -0.029754639, -0.033905029, -0.03729248,
+ -0.039916992, -0.041687012, -0.042572021, -0.042541504,
+ -0.041625977, -0.039794922, -0.03717041, -0.033752441,
+ -0.029632568, -0.024902344, -0.019683838, -0.014038086,
+ -0.0081787109, -0.0021362305, 0.00390625, 0.0097961426,
+ 0.015472412, 0.020751953, 0.025512695, 0.029724121,
+ 0.033233643, 0.036010742, 0.037963867, 0.039031982,
+ 0.039245605, 0.038574219, 0.03704834, 0.034698486,
+ 0.031585693, 0.027740479, 0.02331543, 0.018341064,
+ 0.012969971, 0.0073242188, 0.0014953613, -0.0043640137,
+ -0.010162354, -0.015716553, -0.020965576, -0.025756836,
+ -0.029968262, -0.033569336, -0.036468506, -0.038543701,
+ -0.039825439, -0.040283203, -0.039825439, -0.038574219,
+ -0.036499023, -0.033630371, -0.030090332, -0.025939941,
+ -0.021240234, -0.016113281, -0.010681152, -0.0050354004,
+ 0.00067138672, 0.0063171387, 0.011810303, 0.016998291,
+ 0.021759033, 0.026031494, 0.029724121, 0.032684326,
+ 0.034942627, 0.036407471, 0.03704834, 0.036865234,
+ 0.035858154, 0.0340271, 0.031463623, 0.028198242,
+ 0.024291992, 0.019866943, 0.014984131, 0.0097961426,
+ 0.0043640137, -0.0011901855, -0.0066833496,
+ -0.012054443, -0.017181396, -0.021942139, -0.0262146,
+ -0.029937744, -0.03302002, -0.035400391, -0.037017822,
+ -0.037841797, -0.037872314, -0.037109375, -0.035552979,
+ -0.033233643, -0.03024292, -0.026611328, -0.02243042,
+ -0.017791748, -0.012817383, -0.0075683594,
+ -0.0022277832, 0.0031433105, 0.0084228516, 0.013458252,
+ 0.018188477, 0.022491455, 0.026245117, 0.029418945,
+ 0.031921387, 0.033691406, 0.034729004, 0.034973145,
+ 0.034423828, 0.033111572, 0.031066895, 0.028320312,
+ 0.024932861, 0.021026611, 0.01663208, 0.011871338,
+ 0.0068664551, 0.0016784668, -0.0035400391,
+ -0.0086669922, -0.013641357, -0.018310547, -0.022613525,
+ -0.026428223, -0.029632568, -0.032226562, -0.03414917,
+ -0.035339355, -0.035766602, -0.035430908, -0.034362793,
+ -0.032531738, -0.030059814, -0.026947021, -0.023284912,
+ -0.019134521, -0.014587402, -0.009765625, -0.0047912598,
+ 0.00030517578, 0.0053405762, 0.010192871, 0.014831543,
+ 0.019104004, 0.022918701, 0.0262146, 0.028930664,
+ 0.030944824, 0.032318115, 0.032928467, 0.032806396,
+ 0.031951904, 0.030395508, 0.028137207, 0.025268555,
+ 0.021850586, 0.017944336, 0.013641357, 0.0090332031,
+ 0.0042114258, -0.0007019043, -0.0056152344,
+ -0.010406494, -0.014953613, -0.019195557, -0.023040771,
+ -0.026367188, -0.029144287, -0.031280518, -0.032775879,
+ -0.033538818, -0.033630371, -0.032958984, -0.031646729,
+ -0.029632568, -0.027008057, -0.023803711, -0.020141602,
+ -0.016052246, -0.011627197, -0.007019043, -0.0022583008,
+ 0.0025024414, 0.0072021484, 0.011688232, 0.015899658,
+ 0.019744873, 0.023132324, 0.025970459, 0.02822876,
+ 0.029846191, 0.030792236, 0.031036377, 0.030609131,
+ 0.02947998, 0.027679443, 0.025299072, 0.022338867,
+ 0.018890381, 0.015045166, 0.01083374, 0.0063781738,
+ 0.0018005371, -0.0028381348, -0.0073852539,
+ -0.011810303, -0.015991211, -0.019805908, -0.023193359,
+ -0.026092529, -0.028442383, -0.030181885, -0.031280518,
+ -0.031677246, -0.031433105, -0.030517578, -0.028961182,
+ -0.026794434, -0.024078369, -0.020843506, -0.017181396,
+ -0.013183594, -0.0089416504, -0.0045166016,
+ -3.0517578e-05, 0.0044555664, 0.0087890625, 0.012908936,
+ 0.016723633, 0.020141602, 0.023071289, 0.025512695,
+ 0.02734375, 0.028564453, 0.029174805, 0.02911377,
+ 0.028381348, 0.027038574, 0.025085449, 0.022583008,
+ 0.019561768, 0.016143799, 0.012329102, 0.0082702637,
+ 0.0040283203, -0.00033569336, -0.0046691895,
+ -0.0089111328, -0.012969971, -0.01675415, -0.020172119,
+ -0.023162842, -0.025634766, -0.027557373, -0.028930664,
+ -0.029663086, -0.029754639, -0.02923584, -0.028076172,
+ -0.02633667, -0.024047852, -0.021240234, -0.018005371,
+ -0.014434814, -0.010528564, -0.006439209, -0.0022583008,
+ 0.0019836426, 0.0061340332, 0.010101318, 0.01385498,
+ 0.017272949, 0.020294189, 0.022857666, 0.024871826,
+ 0.02633667, 0.02722168, 0.027496338, 0.027130127,
+ 0.026184082, 0.024658203, 0.02255249, 0.019989014,
+ 0.016967773, 0.013549805, 0.0098571777, 0.0059509277,
+ 0.0018920898, -0.0021972656, -0.0062561035,
+ -0.010162354, -0.01385498, -0.017272949, -0.020294189,
+ -0.022888184, -0.024993896, -0.026550293, -0.027557373,
+ -0.027954102, -0.027770996, -0.027008057, -0.025665283,
+ -0.023773193, -0.021392822, -0.018585205, -0.015380859,
+ -0.01184082, -0.0080871582, -0.0042114258,
+ -0.00021362305, 0.0037231445, 0.0075378418, 0.011199951,
+ 0.014587402, 0.01763916, 0.020263672, 0.02243042,
+ 0.024108887, 0.025238037, 0.025756836, 0.025756836,
+ 0.025146484, 0.023986816, 0.02230835, 0.020141602,
+ 0.017486572, 0.01449585, 0.011169434, 0.0075683594,
+ 0.0038146973, 0, -0.0038452148, -0.007598877,
+ -0.011169434, -0.014526367, -0.017578125, -0.020233154,
+ -0.022460938, -0.024200439, -0.025421143, -0.026123047,
+ -0.026245117, -0.025787354, -0.024810791, -0.02331543,
+ -0.021331787, -0.018890381, -0.016052246, -0.012908936,
+ -0.0094909668, -0.0058898926, -0.0021972656,
+ 0.0015258789, 0.0051879883, 0.0087280273, 0.012054443,
+ 0.015075684, 0.01776123, 0.020019531, 0.021850586,
+ 0.023193359, 0.023986816, 0.024261475, 0.023986816,
+ 0.023162842, 0.021850586, 0.020050049, 0.017791748,
+ 0.015136719, 0.012176514, 0.0089111328, 0.0054931641,
+ 0.0019226074, -0.0016784668, -0.0052490234,
+ -0.0087280273, -0.011993408, -0.014984131, -0.017700195,
+ -0.019989014, -0.021881104, -0.023284912, -0.024200439,
+ -0.024597168, -0.024475098, -0.023834229, -0.022674561,
+ -0.021026611, -0.018981934, -0.01651001, -0.013702393,
+ -0.010620117, -0.0073242188, -0.00390625,
+ -0.00042724609, 0.0030517578, 0.006439209, 0.0096740723,
+ 0.012664795, 0.015350342, 0.017700195, 0.019622803,
+ 0.021118164, 0.022125244, 0.022644043, 0.022674561,
+ 0.022155762, 0.021179199, 0.019714355, 0.017822266,
+ 0.015563965, 0.012908936, 0.010009766, 0.0068664551,
+ 0.0036010742, 0.00021362305, -0.0031433105,
+ -0.0064697266, -0.0096130371, -0.012573242,
+ -0.015258789, -0.01763916, -0.019592285, -0.021148682,
+ -0.022277832, -0.022888184, -0.023040771, -0.022674561,
+ -0.021850586, -0.020568848, -0.018829346, -0.016723633,
+ -0.014251709, -0.011505127, -0.0085144043,
+ -0.0053710938, -0.0021362305, 0.001159668, 0.0043640137,
+ 0.0074768066, 0.010406494, 0.013061523, 0.015441895,
+ 0.017456055, 0.019073486, 0.020263672, 0.020996094,
+ 0.021270752, 0.021057129, 0.020385742, 0.019226074,
+ 0.017669678, 0.01574707, 0.013427734, 0.01083374,
+ 0.008026123, 0.0050048828, 0.0018920898, -0.0012512207,
+ -0.0043945312, -0.0074462891, -0.010314941,
+ -0.012969971, -0.015350342, -0.01739502, -0.019073486,
+ -0.020324707, -0.021148682, -0.021514893, -0.02142334,
+ -0.020874023, -0.019897461, -0.018493652, -0.016723633,
+ -0.014587402, -0.012145996, -0.0094604492,
+ -0.0065917969, -0.0036010742, -0.00054931641,
+ 0.0025024414, 0.0054626465, 0.0083007812, 0.010925293,
+ 0.013305664, 0.015380859, 0.017089844, 0.0184021,
+ 0.019317627, 0.019805908, 0.019836426, 0.019439697,
+ 0.018585205, 0.017333984, 0.015716553, 0.01373291,
+ 0.011444092, 0.0089111328, 0.0061950684, 0.003326416,
+ 0.00039672852, -0.0025634766, -0.0054321289,
+ -0.0082092285, -0.010803223, -0.013183594, -0.015258789,
+ -0.016998291, -0.018371582, -0.019378662, -0.019958496,
+ -0.020080566, -0.019805908, -0.019104004, -0.018005371,
+ -0.01651001, -0.014709473, -0.012573242, -0.010162354,
+ -0.007598877, -0.0048522949, -0.0020141602,
+ 0.00082397461, 0.0036315918, 0.0063476562, 0.0089111328,
+ 0.011260986, 0.013336182, 0.015106201, 0.016571045,
+ 0.017608643, 0.018280029, 0.01852417, 0.018371582,
+ 0.017791748, 0.016845703, 0.01550293, 0.013824463,
+ 0.01184082, 0.0096130371, 0.0071411133, 0.0045471191,
+ 0.0018310547, -0.00091552734, -0.0036315918,
+ -0.0062866211, -0.0087890625, -0.011108398,
+ -0.013214111, -0.015014648, -0.016479492, -0.017578125,
+ -0.018341064, -0.018676758, -0.018615723, -0.018188477,
+ -0.017333984, -0.016143799, -0.01461792, -0.012786865,
+ -0.010681152, -0.0083618164, -0.005859375,
+ -0.0032958984, -0.00064086914, 0.0020141602,
+ 0.0045776367, 0.0070495605, 0.0093383789, 0.011413574,
+ 0.013214111, 0.01473999, 0.015899658, 0.016723633,
+ 0.017150879, 0.017211914, 0.016876221, 0.016174316,
+ 0.015106201, 0.01373291, 0.012023926, 0.010070801,
+ 0.0078735352, 0.0055236816, 0.0030517578, 0.00051879883,
+ -0.0020446777, -0.0045471191, -0.0069580078,
+ -0.0092163086, -0.011260986, -0.013061523, -0.014587402,
+ -0.015808105, -0.016693115, -0.017211914, -0.017364502,
+ -0.017150879, -0.016540527, -0.015625, -0.014343262,
+ -0.012786865, -0.010955811, -0.0089111328,
+ -0.0066833496, -0.0043334961, -0.0018920898,
+ 0.00057983398, 0.0029907227, 0.0053405762, 0.0075683594,
+ 0.0096130371, 0.011413574, 0.012969971, 0.014221191,
+ 0.015167236, 0.01574707, 0.015991211, 0.015869141,
+ 0.015411377, 0.014587402, 0.013458252, 0.012023926,
+ 0.010345459, 0.0084228516, 0.0063171387, 0.0040588379,
+ 0.001739502, -0.00061035156, -0.0029602051,
+ -0.0052490234, -0.0074157715, -0.0094299316,
+ -0.011230469, -0.012786865, -0.014068604, -0.015045166,
+ -0.015716553, -0.016021729, -0.015991211, -0.015655518,
+ -0.014953613, -0.013946533, -0.012634277, -0.011077881,
+ -0.0092773438, -0.0072937012, -0.0051574707,
+ -0.0029602051, -0.00067138672, 0.0015869141,
+ 0.0038146973, 0.0059204102, 0.0079040527, 0.0097045898,
+ 0.011260986, 0.012573242, 0.01361084, 0.014312744,
+ 0.014709473, 0.014770508, 0.01449585, 0.013916016,
+ 0.013031006, 0.01184082, 0.010406494, 0.0087585449,
+ 0.0068969727, 0.0048828125, 0.0027770996, 0.00061035156,
+ -0.0015869141, -0.0037231445, -0.0057983398,
+ -0.0077514648, -0.0094909668, -0.011077881,
+ -0.012390137, -0.013427734, -0.014221191, -0.014678955,
+ -0.014801025, -0.014648438, -0.014160156, -0.013366699,
+ -0.012329102, -0.010986328, -0.0094604492,
+ -0.0077209473, -0.0058288574, -0.0038146973,
+ -0.001739502, 0.00036621094, 0.0024414062, 0.0044250488,
+ 0.0063476562, 0.0080871582, 0.0096435547, 0.010986328,
+ 0.012054443, 0.0128479, 0.013397217, 0.01361084,
+ 0.013519287, 0.013153076, 0.012481689, 0.011505127,
+ 0.010314941, 0.0088806152, 0.0072631836, 0.0054931641,
+ 0.0036010742, 0.0016174316, -0.00039672852,
+ -0.0023803711, -0.0043334961, -0.0061645508,
+ -0.0079040527, -0.0094299316, -0.010772705,
+ -0.011871338, -0.01272583, -0.013275146, -0.013580322,
+ -0.013580322, -0.013275146, -0.012695312, -0.011871338,
+ -0.010772705, -0.0094604492, -0.0079650879,
+ -0.0062866211, -0.004486084, -0.0026245117,
+ -0.0007019043, 0.0012207031, 0.003112793, 0.0049133301,
+ 0.0065917969, 0.0080871582, 0.0094299316, 0.010559082,
+ 0.011413574, 0.012054443, 0.012390137, 0.012451172,
+ 0.012268066, 0.011779785, 0.011047363, 0.010070801,
+ 0.0088500977, 0.0074768066, 0.0059204102, 0.0042114258,
+ 0.0024414062, 0.00061035156, -0.0012207031,
+ -0.0030212402, -0.0047607422, -0.0064086914,
+ -0.0079040527, -0.0092163086, -0.010345459,
+ -0.011260986, -0.011901855, -0.012298584, -0.012451172,
+ -0.012298584, -0.011901855, -0.011260986, -0.010406494,
+ -0.0093078613, -0.008026123, -0.0065612793,
+ -0.0049743652, -0.0032958984, -0.0015563965,
+ 0.00018310547, 0.0019226074, 0.0036010742, 0.0052185059,
+ 0.0066833496, 0.0079956055, 0.0091247559, 0.010040283,
+ 0.01071167, 0.011169434, 0.011352539, 0.011322021,
+ 0.010986328, 0.010437012, 0.0096740723, 0.0086669922,
+ 0.0075073242, 0.0061645508, 0.0046691895, 0.003112793,
+ 0.0014648438, -0.00021362305, -0.0018920898,
+ -0.0035095215, -0.0050354004, -0.0064697266,
+ -0.0077514648, -0.0088806152, -0.0098266602,
+ -0.010528564, -0.011016846, -0.011260986, -0.011260986,
+ -0.011047363, -0.0105896, -0.0098876953, -0.0090026855,
+ -0.0079040527, -0.0066833496, -0.005279541,
+ -0.0038146973, -0.0022583008, -0.00067138672,
+ 0.00091552734, 0.0024719238, 0.0039672852, 0.0053710938,
+ 0.0066223145, 0.0077514648, 0.0086669922, 0.0093994141,
+ 0.0099182129, 0.010223389, 0.010314941, 0.010131836,
+ 0.009765625, 0.0091552734, 0.0083618164, 0.0073852539,
+ 0.0062255859, 0.0049438477, 0.0035705566, 0.0021057129,
+ 0.00061035156, -0.00088500977, -0.0023803711,
+ -0.0038146973, -0.0051574707, -0.0064086914,
+ -0.0075073242, -0.0084228516, -0.009185791,
+ -0.0097351074, -0.010070801, -0.010192871, -0.010101318,
+ -0.0097961426, -0.0092773438, -0.0085449219,
+ -0.0076599121, -0.0066223145, -0.0054321289,
+ -0.0041503906, -0.0027770996, -0.001373291,
+ 6.1035156e-05, 0.0014953613, 0.0028686523, 0.0041809082,
+ 0.0053710938, 0.006439209, 0.0073852539, 0.0081481934,
+ 0.0086975098, 0.0090942383, 0.0092468262, 0.0092163086,
+ 0.008972168, 0.0085449219, 0.0079040527, 0.0071105957,
+ 0.0061645508, 0.005065918, 0.0038757324, 0.0025939941,
+ 0.0012817383, -9.1552734e-05, -0.0014343262,
+ -0.002746582, -0.0039978027, -0.0051574707,
+ -0.0062255859, -0.0071105957, -0.0078735352,
+ -0.0084533691, -0.0088806152, -0.0090637207,
+ -0.0090942383, -0.0089111328, -0.0085449219,
+ -0.0079956055, -0.0072937012, -0.006439209,
+ -0.0054321289, -0.0043334961, -0.0031433105,
+ -0.0018920898, -0.00061035156, 0.00067138672,
+ 0.0019226074, 0.003112793, 0.0042419434, 0.0052490234,
+ 0.0061645508, 0.0069274902, 0.0075073242, 0.0079345703,
+ 0.0081787109, 0.0082397461, 0.0081176758, 0.0078125,
+ 0.0073547363, 0.0067138672, 0.0059509277, 0.0050354004,
+ 0.0040283203, 0.0029296875, 0.0017700195, 0.00057983398,
+ -0.00064086914, -0.0018310547, -0.0029602051,
+ -0.0040283203, -0.0050354004, -0.0058898926,
+ -0.006652832, -0.007232666, -0.0076904297,
+ -0.0079650879, -0.0080566406, -0.0079956055,
+ -0.0077514648, -0.0073547363, -0.0068054199,
+ -0.0061035156, -0.005279541, -0.0043334961,
+ -0.003326416, -0.0022583008, -0.0011291504, 0,
+ 0.0010986328, 0.0021972656, 0.0032348633, 0.0041809082,
+ 0.0050354004, 0.0057678223, 0.0063476562, 0.0068054199,
+ 0.0071105957, 0.007232666, 0.007232666, 0.0070495605,
+ 0.0067138672, 0.0062255859, 0.0056152344, 0.0048522949,
+ 0.0040283203, 0.0030822754, 0.0021057129, 0.0010681152,
+ 0, -0.0010375977, -0.0020751953, -0.0030517578,
+ -0.0039367676, -0.0047607422, -0.0054626465,
+ -0.006072998, -0.0065307617, -0.0068359375,
+ -0.007019043, -0.007019043, -0.0068969727,
+ -0.0066223145, -0.0061950684, -0.005645752,
+ -0.0049743652, -0.0042114258, -0.0033874512,
+ -0.0024719238, -0.0014953613, -0.00051879883,
+ 0.00045776367, 0.0014038086, 0.0023193359, 0.0032043457,
+ 0.0039672852, 0.0046691895, 0.0052490234, 0.0057067871,
+ 0.0060424805, 0.0062255859, 0.0062866211, 0.0061950684,
+ 0.0059814453, 0.0056152344, 0.0051269531, 0.0045471191,
+ 0.0038757324, 0.003112793, 0.0022888184, 0.0014038086,
+ 0.00048828125, -0.00042724609, -0.0013122559,
+ -0.002166748, -0.0029907227, -0.0037231445,
+ -0.0043945312, -0.0049438477, -0.0054016113,
+ -0.0057373047, -0.0059509277, -0.0060424805,
+ -0.0059814453, -0.0057983398, -0.0054931641,
+ -0.0050964355, -0.0045776367, -0.0039672852,
+ -0.0032653809, -0.002532959, -0.001739502,
+ -0.00088500977, -6.1035156e-05, 0.00076293945,
+ 0.0015869141, 0.0023498535, 0.0030517578, 0.0036621094,
+ 0.0042114258, 0.0046386719, 0.0050048828, 0.0052185059,
+ 0.0053100586, 0.0053100586, 0.0051879883, 0.0049133301,
+ 0.0045776367, 0.004119873, 0.0036010742, 0.0029602051,
+ 0.0022888184, 0.0015869141, 0.00082397461,
+ 6.1035156e-05, -0.0007019043, -0.0014343262,
+ -0.002166748, -0.0028076172, -0.0033874512, -0.00390625,
+ -0.0043334961, -0.0046691895, -0.0049133301,
+ -0.0050354004, -0.0050354004, -0.0049438477,
+ -0.0047302246, -0.0044555664, -0.0040588379,
+ -0.0035705566, -0.0030517578, -0.0024414062,
+ -0.0018005371, -0.0010986328, -0.00042724609,
+ 0.0002746582, 0.00094604492, 0.0016174316, 0.0022277832,
+ 0.0027770996, 0.0032653809, 0.0036621094, 0.0039978027,
+ 0.0042114258, 0.0043334961, 0.0043945312, 0.0043334961,
+ 0.0041809082, 0.0039367676, 0.0036010742, 0.0031738281,
+ 0.0027160645, 0.002166748, 0.0016174316, 0.0010070801,
+ 0.00039672852, -0.00024414062, -0.00085449219,
+ -0.0014343262, -0.0020141602, -0.0025024414,
+ -0.0029602051, -0.0033569336, -0.0036621094,
+ -0.0038757324, -0.0040283203, -0.0040588379,
+ -0.0040283203, -0.00390625, -0.0037231445,
+ -0.0034484863, -0.0030822754, -0.0026855469,
+ -0.0022277832, -0.0017089844, -0.0011901855,
+ -0.00064086914, -6.1035156e-05, 0.00048828125,
+ 0.0010070801, 0.0014953613, 0.0019836426, 0.0023803711,
+ 0.002746582, 0.0030212402, 0.0032348633, 0.0033874512,
+ 0.0034484863, 0.0034484863, 0.0033569336, 0.0032043457,
+ 0.0029602051, 0.0026855469, 0.0023193359, 0.0019226074,
+ 0.0014953613, 0.0010375977, 0.00054931641,
+ 6.1035156e-05, -0.00039672852, -0.00088500977,
+ -0.0013122559, -0.001739502, -0.0021057129,
+ -0.0024108887, -0.0026855469, -0.0028991699,
+ -0.0030212402, -0.003112793, -0.003112793,
+ -0.0030517578, -0.0029296875, -0.0027160645,
+ -0.0025024414, -0.0021972656, -0.0018615723,
+ -0.0014953613, -0.0010986328, -0.0007019043,
+ -0.0002746582, 0.00015258789, 0.00054931641,
+ 0.00094604492, 0.0012817383, 0.0016174316, 0.0018920898,
+ 0.0021362305, 0.0023193359, 0.0024414062, 0.002532959,
+ 0.002532959, 0.0025024414, 0.0024108887, 0.0022583008,
+ 0.0020751953, 0.0018310547, 0.0015563965, 0.0012512207,
+ 0.00091552734, 0.00057983398, 0.00024414062,
+ -0.00012207031, -0.00045776367, -0.00076293945,
+ -0.0010681152, -0.0013427734, -0.0015869141,
+ -0.0018005371, -0.001953125, -0.0020751953,
+ -0.0021362305, -0.002166748, -0.0021362305,
+ -0.0020751953, -0.001953125, -0.0018005371,
+ -0.0016174316, -0.0014038086, -0.001159668,
+ -0.00088500977, -0.00061035156, -0.00033569336,
+ -6.1035156e-05, 0.00021362305, 0.00048828125,
+ 0.00073242188, 0.00094604492, 0.001159668, 0.0013122559,
+ 0.0014648438, 0.0015563965, 0.0016174316, 0.0016479492,
+ 0.0016174316, 0.0015563965, 0.0014953613, 0.001373291,
+ 0.0012207031, 0.0010681152, 0.00088500977,
+ 0.00067138672, 0.00045776367, 0.0002746582,
+ 6.1035156e-05, -0.00015258789, -0.00036621094,
+ -0.00054931641, -0.0007019043, -0.00085449219,
+ -0.0009765625, -0.0010986328, -0.001159668,
+ -0.0012207031, -0.0012207031, -0.0012207031,
+ -0.0011901855, -0.0011291504, -0.0010375977,
+ -0.00094604492, -0.00082397461, -0.00067138672,
+ -0.00054931641, -0.00039672852, -0.00024414062,
+ -9.1552734e-05, 3.0517578e-05, 0.00018310547,
+ 0.00030517578, 0.00042724609, 0.00051879883,
+ 0.00057983398, 0.00064086914, 0.0007019043,
+ 0.00073242188, 0.00073242188, 0.00073242188,
+ 0.0007019043, 0.00067138672, 0.00061035156,
+ 0.00054931641, 0.00045776367, 0.00039672852,
+ 0.00030517578, 0.00021362305, 0.00015258789,
+ 6.1035156e-05, -3.0517578e-05, -9.1552734e-05,
+ -0.00015258789, -0.00021362305, -0.00024414062,
+ -0.0002746582, -0.00030517578, -0.00030517578,
+ -0.00030517578, -0.00030517578, -0.0002746582,
+ -0.0002746582, -0.00024414062, -0.00021362305,
+ -0.00018310547, -0.00015258789, -9.1552734e-05,
+ -9.1552734e-05, -6.1035156e-05, -3.0517578e-05, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+const jack_nframes_t Session::default_click_length = sizeof (default_click) / sizeof (default_click[0]);
diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc
new file mode 100644
index 0000000000..476c77cbec
--- /dev/null
+++ b/libs/ardour/diskstream.cc
@@ -0,0 +1,2338 @@
+/*
+ Copyright (C) 2000-2003 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.
+
+ $Id$
+*/
+
+#include <fstream>
+#include <cstdio>
+#include <unistd.h>
+#include <cmath>
+#include <cerrno>
+#include <string>
+#include <climits>
+#include <fcntl.h>
+#include <cstdlib>
+#include <ctime>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <pbd/error.h>
+#include <pbd/basename.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audioengine.h>
+#include <ardour/diskstream.h>
+#include <ardour/utils.h>
+#include <ardour/configuration.h>
+#include <ardour/filesource.h>
+#include <ardour/send.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/audioregion.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+jack_nframes_t DiskStream::disk_io_chunk_frames;
+
+sigc::signal<void,DiskStream*> DiskStream::DiskStreamCreated;
+sigc::signal<void,DiskStream*> DiskStream::CannotRecordNoInput;
+sigc::signal<void,list<Source*>*> DiskStream::DeleteSources;
+sigc::signal<void> DiskStream::DiskOverrun;
+sigc::signal<void> DiskStream::DiskUnderrun;
+
+DiskStream::DiskStream (Session &sess, const string &name, Flag flag)
+ : _name (name),
+ _session (sess)
+{
+ /* prevent any write sources from being created */
+
+ in_set_state = true;
+ init (flag);
+ use_new_playlist ();
+ in_set_state = false;
+
+ DiskStreamCreated (this); /* EMIT SIGNAL */
+}
+
+DiskStream::DiskStream (Session& sess, const XMLNode& node)
+ : _session (sess)
+
+{
+ in_set_state = true;
+ init (Recordable);
+
+ if (set_state (node)) {
+ in_set_state = false;
+ throw failed_constructor();
+ }
+
+ in_set_state = false;
+
+ DiskStreamCreated (this); /* EMIT SIGNAL */
+}
+
+void
+DiskStream::init_channel (ChannelInfo &chan)
+{
+ chan.playback_wrap_buffer = 0;
+ chan.capture_wrap_buffer = 0;
+ chan.speed_buffer = 0;
+ chan.peak_power = 0.0f;
+ chan.fades_source = 0;
+ chan.write_source = 0;
+ chan.source = 0;
+ chan.current_capture_buffer = 0;
+ chan.current_playback_buffer = 0;
+
+ chan.playback_buf = new RingBufferNPT<Sample> (_session.diskstream_buffer_size());
+ chan.capture_buf = new RingBufferNPT<Sample> (_session.diskstream_buffer_size());
+
+ /* touch the ringbuffer buffers, which will cause
+ them to be mapped into locked physical RAM if
+ we're running with mlockall(). this doesn't do
+ much if we're not.
+ */
+ memset (chan.playback_buf->buffer(), 0, sizeof (Sample) * chan.playback_buf->bufsize());
+ memset (chan.capture_buf->buffer(), 0, sizeof (Sample) * chan.capture_buf->bufsize());
+}
+
+
+void
+DiskStream::init (Flag f)
+{
+ _id = new_id();
+ _refcnt = 0;
+ _flags = f;
+ _io = 0;
+ _alignment_style = ExistingMaterial;
+ _persistent_alignment_style = ExistingMaterial;
+ first_input_change = true;
+ rec_monitoring_off_for_roll = false;
+ _playlist = 0;
+ i_am_the_modifier = 0;
+ atomic_set (&_record_enabled, 0);
+ was_recording = false;
+ capture_start_frame = 0;
+ capture_captured = 0;
+ _visible_speed = 1.0f;
+ _actual_speed = 1.0f;
+ _buffer_reallocation_required = false;
+ _seek_required = false;
+ first_recordable_frame = max_frames;
+ last_recordable_frame = max_frames;
+ _roll_delay = 0;
+ _capture_offset = 0;
+ _processed = false;
+ _slaved = false;
+ adjust_capture_position = 0;
+ last_possibly_recording = 0;
+ loop_location = 0;
+ wrap_buffer_size = 0;
+ speed_buffer_size = 0;
+ last_phase = 0;
+ phi = (uint64_t) (0x1000000);
+ file_frame = 0;
+ playback_sample = 0;
+ playback_distance = 0;
+ _read_data_count = 0;
+ _write_data_count = 0;
+ deprecated_io_node = 0;
+
+ /* there are no channels at this point, so these
+ two calls just get speed_buffer_size and wrap_buffer
+ size setup without duplicating their code.
+ */
+
+ set_block_size (_session.get_block_size());
+ allocate_temporary_buffers ();
+
+ pending_overwrite = false;
+ overwrite_frame = 0;
+ overwrite_queued = false;
+ input_change_pending = NoChange;
+
+ add_channel ();
+ _n_channels = 1;
+}
+
+void
+DiskStream::destroy_channel (ChannelInfo &chan)
+{
+ if (chan.write_source) {
+ chan.write_source->release ();
+ chan.write_source = 0;
+ }
+
+ if (chan.speed_buffer) {
+ delete [] chan.speed_buffer;
+ }
+
+ if (chan.playback_wrap_buffer) {
+ delete [] chan.playback_wrap_buffer;
+ }
+ if (chan.capture_wrap_buffer) {
+ delete [] chan.capture_wrap_buffer;
+ }
+
+ delete chan.playback_buf;
+ delete chan.capture_buf;
+
+ chan.playback_buf = 0;
+ chan.capture_buf = 0;
+}
+
+DiskStream::~DiskStream ()
+{
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ if (_playlist) {
+ _playlist->unref ();
+ }
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ destroy_channel((*chan));
+ }
+
+ channels.clear();
+}
+
+void
+DiskStream::handle_input_change (IOChange change, void *src)
+{
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ if (!(input_change_pending & change)) {
+ input_change_pending = IOChange (input_change_pending|change);
+ _session.request_input_change_handling ();
+ }
+}
+
+void
+DiskStream::non_realtime_input_change ()
+{
+ {
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ if (input_change_pending == NoChange) {
+ return;
+ }
+
+ if (input_change_pending & ConfigurationChanged) {
+
+ if (_io->n_inputs() > _n_channels) {
+
+ // we need to add new channel infos
+
+ int diff = _io->n_inputs() - channels.size();
+
+ for (int i = 0; i < diff; ++i) {
+ add_channel ();
+ }
+
+ } else if (_io->n_inputs() < _n_channels) {
+
+ // we need to get rid of channels
+
+ int diff = channels.size() - _io->n_inputs();
+
+ for (int i = 0; i < diff; ++i) {
+ remove_channel ();
+ }
+ }
+ }
+
+ get_input_sources ();
+ set_capture_offset ();
+
+ if (first_input_change) {
+ set_align_style (_persistent_alignment_style);
+ first_input_change = false;
+ } else {
+ set_align_style_from_io ();
+ }
+
+ input_change_pending = NoChange;
+ }
+
+ /* reset capture files */
+
+ reset_write_sources (false);
+
+ /* now refill channel buffers */
+
+ if (speed() != 1.0f || speed() != -1.0f) {
+ seek ((jack_nframes_t) (_session.transport_frame() * (double) speed()));
+ }
+ else {
+ seek (_session.transport_frame());
+ }
+}
+
+void
+DiskStream::get_input_sources ()
+{
+ uint32_t ni = _io->n_inputs();
+
+ for (uint32_t n = 0; n < ni; ++n) {
+
+ const char **connections = _io->input(n)->get_connections ();
+ ChannelInfo& chan = channels[n];
+
+ if (connections == 0 || connections[0] == 0) {
+
+ if (chan.source) {
+ // _source->disable_metering ();
+ }
+
+ chan.source = 0;
+
+ } else {
+ chan.source = _session.engine().get_port_by_name (connections[0]);
+ }
+
+ if (connections) {
+ free (connections);
+ }
+ }
+}
+
+int
+DiskStream::find_and_use_playlist (const string& name)
+{
+ Playlist* pl;
+ AudioPlaylist* playlist;
+
+ if ((pl = _session.get_playlist (name)) == 0) {
+ error << compose(_("DiskStream: Session doesn't know about a Playlist called \"%1\""), name) << endmsg;
+ return -1;
+ }
+
+ if ((playlist = dynamic_cast<AudioPlaylist*> (pl)) == 0) {
+ error << compose(_("DiskStream: Playlist \"%1\" isn't an audio playlist"), name) << endmsg;
+ return -1;
+ }
+
+ return use_playlist (playlist);
+}
+
+int
+DiskStream::use_playlist (AudioPlaylist* playlist)
+{
+ {
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ if (playlist == _playlist) {
+ return 0;
+ }
+
+ plstate_connection.disconnect();
+ plmod_connection.disconnect ();
+ plgone_connection.disconnect ();
+
+ if (_playlist) {
+ _playlist->unref();
+ }
+
+ _playlist = playlist;
+ _playlist->ref();
+
+ if (!in_set_state && recordable()) {
+ reset_write_sources (false);
+ }
+
+ plstate_connection = _playlist->StateChanged.connect (mem_fun (*this, &DiskStream::playlist_changed));
+ plmod_connection = _playlist->Modified.connect (mem_fun (*this, &DiskStream::playlist_modified));
+ plgone_connection = _playlist->GoingAway.connect (mem_fun (*this, &DiskStream::playlist_deleted));
+ }
+
+ if (!overwrite_queued) {
+ _session.request_overwrite_buffer (this);
+ overwrite_queued = true;
+ }
+
+ PlaylistChanged (); /* EMIT SIGNAL */
+ _session.set_dirty ();
+
+ return 0;
+}
+
+void
+DiskStream::playlist_deleted (Playlist* pl)
+{
+ /* this catches an ordering issue with session destruction. playlists
+ are destroyed before diskstreams. we have to invalidate any handles
+ we have to the playlist.
+ */
+
+ _playlist = 0;
+}
+
+int
+DiskStream::use_new_playlist ()
+{
+ string newname;
+ AudioPlaylist* playlist;
+
+ if (_playlist) {
+ newname = Playlist::bump_name (_playlist->name(), _session);
+ } else {
+ newname = Playlist::bump_name (_name, _session);
+ }
+
+ if ((playlist = new AudioPlaylist (_session, newname, hidden())) != 0) {
+ playlist->set_orig_diskstream_id (id());
+ return use_playlist (playlist);
+ } else {
+ return -1;
+ }
+}
+
+int
+DiskStream::use_copy_playlist ()
+{
+ if (_playlist == 0) {
+ error << compose(_("DiskStream %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
+ return -1;
+ }
+
+ string newname;
+ AudioPlaylist* playlist;
+
+ newname = Playlist::bump_name (_playlist->name(), _session);
+
+ if ((playlist = new AudioPlaylist (*_playlist, newname)) != 0) {
+ playlist->set_orig_diskstream_id (id());
+ return use_playlist (playlist);
+ } else {
+ return -1;
+ }
+}
+
+void
+DiskStream::set_io (IO& io)
+{
+ _io = &io;
+ set_align_style_from_io ();
+}
+
+void
+DiskStream::set_name (string str, void *src)
+{
+ if (str != _name) {
+ _playlist->set_name (str);
+ _name = str;
+
+ if (!in_set_state && recordable()) {
+
+ /* open new capture files so that they have the correct name */
+
+ reset_write_sources (false);
+ }
+ }
+}
+
+void
+DiskStream::set_speed (float sp)
+{
+ _session.request_diskstream_speed (*this, sp);
+
+ /* to force a rebuffering at the right place */
+ playlist_modified();
+}
+
+bool
+DiskStream::realtime_set_speed (float sp, bool global)
+{
+ bool changed = false;
+ float new_speed = sp * _session.transport_speed();
+
+ if (_visible_speed != sp) {
+ _visible_speed = sp;
+ changed = true;
+ }
+
+ if (new_speed != _actual_speed) {
+
+ jack_nframes_t required_wrap_size = (jack_nframes_t) floor (_session.get_block_size() *
+ fabs (new_speed)) + 1;
+
+ if (required_wrap_size > wrap_buffer_size) {
+ _buffer_reallocation_required = true;
+ }
+
+ _actual_speed = new_speed;
+ phi = (uint64_t) (0x1000000 * fabs(_actual_speed));
+ }
+
+ if (changed) {
+ if (!global) {
+ _seek_required = true;
+ }
+ speed_changed (); /* EMIT SIGNAL */
+ }
+
+ return _buffer_reallocation_required || _seek_required;
+}
+
+void
+DiskStream::non_realtime_set_speed ()
+{
+ if (_buffer_reallocation_required)
+ {
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+ allocate_temporary_buffers ();
+
+ _buffer_reallocation_required = false;
+ }
+
+ if (_seek_required) {
+ if (speed() != 1.0f || speed() != -1.0f) {
+ seek ((jack_nframes_t) (_session.transport_frame() * (double) speed()), true);
+ }
+ else {
+ seek (_session.transport_frame(), true);
+ }
+
+ _seek_required = false;
+ }
+}
+
+void
+DiskStream::prepare ()
+{
+ _processed = false;
+ playback_distance = 0;
+}
+
+void
+DiskStream::check_record_status (jack_nframes_t transport_frame, jack_nframes_t nframes, bool can_record)
+{
+ int possibly_recording;
+ int rolling;
+ int change;
+ const int transport_rolling = 0x4;
+ const int track_rec_enabled = 0x2;
+ const int global_rec_enabled = 0x1;
+
+ /* merge together the 3 factors that affect record status, and compute
+ what has changed.
+ */
+
+ rolling = _session.transport_speed() != 0.0f;
+ possibly_recording = (rolling << 2) | (record_enabled() << 1) | can_record;
+ change = possibly_recording ^ last_possibly_recording;
+
+ if (possibly_recording == last_possibly_recording) {
+ return;
+ }
+
+ /* change state */
+
+ /* if per-track or global rec-enable turned on while the other was already on, we've started recording */
+
+ if ((change & track_rec_enabled) && record_enabled() && (!(change & global_rec_enabled) && can_record) ||
+ ((change & global_rec_enabled) && can_record && (!(change & track_rec_enabled) && record_enabled()))) {
+
+ /* starting to record: compute first+last frames */
+
+ first_recordable_frame = transport_frame + _capture_offset;
+ last_recordable_frame = max_frames;
+ capture_start_frame = transport_frame;
+
+ if (!(last_possibly_recording & transport_rolling) && (possibly_recording & transport_rolling)) {
+
+ /* was stopped, now rolling (and recording) */
+
+ if (_alignment_style == ExistingMaterial) {
+ first_recordable_frame += _session.worst_output_latency();
+ } else {
+ first_recordable_frame += _roll_delay;
+ }
+
+ } else {
+
+ /* was rolling, but record state changed */
+
+ if (_alignment_style == ExistingMaterial) {
+
+
+ if (!_session.get_punch_in()) {
+
+ /* manual punch in happens at the correct transport frame
+ because the user hit a button. but to get alignment correct
+ we have to back up the position of the new region to the
+ appropriate spot given the roll delay.
+ */
+
+ capture_start_frame -= _roll_delay;
+
+ /* XXX paul notes (august 2005): i don't know why
+ this is needed.
+ */
+
+ first_recordable_frame += _capture_offset;
+
+ } else {
+
+ /* autopunch toggles recording at the precise
+ transport frame, and then the DS waits
+ to start recording for a time that depends
+ on the output latency.
+ */
+
+ first_recordable_frame += _session.worst_output_latency();
+ }
+
+ } else {
+
+ if (_session.get_punch_in()) {
+ first_recordable_frame += _roll_delay;
+ } else {
+ capture_start_frame -= _roll_delay;
+ }
+ }
+
+ }
+
+ } else if (!record_enabled() || !can_record) {
+
+ /* stop recording */
+
+ last_recordable_frame = transport_frame + _capture_offset;
+
+ if (_alignment_style == ExistingMaterial) {
+ last_recordable_frame += _session.worst_output_latency();
+ } else {
+ last_recordable_frame += _roll_delay;
+ }
+ }
+
+ last_possibly_recording = possibly_recording;
+}
+
+int
+DiskStream::process (jack_nframes_t transport_frame, jack_nframes_t nframes, jack_nframes_t offset, bool can_record, bool rec_monitors_input)
+{
+ uint32_t n;
+ ChannelList::iterator c;
+ int ret = -1;
+ jack_nframes_t rec_offset = 0;
+ jack_nframes_t rec_nframes = 0;
+ bool nominally_recording;
+ bool re = record_enabled ();
+ bool collect_playback = false;
+
+ /* if we've already processed the frames corresponding to this call,
+ just return. this allows multiple routes that are taking input
+ from this diskstream to call our ::process() method, but have
+ this stuff only happen once. more commonly, it allows both
+ the AudioTrack that is using this DiskStream *and* the Session
+ to call process() without problems.
+ */
+
+ if (_processed) {
+ return 0;
+ }
+
+ check_record_status (transport_frame, nframes, can_record);
+
+ nominally_recording = (can_record && re);
+
+ if (nframes == 0) {
+ _processed = true;
+ return 0;
+ }
+
+ /* This lock is held until the end of DiskStream::commit, so these two functions
+ must always be called as a pair. The only exception is if this function
+ returns a non-zero value, in which case, ::commit should not be called.
+ */
+
+ if (pthread_mutex_trylock (state_lock.mutex())) {
+ return 1;
+ }
+
+ adjust_capture_position = 0;
+
+ for (c = channels.begin(); c != channels.end(); ++c) {
+ (*c).current_capture_buffer = 0;
+ (*c).current_playback_buffer = 0;
+ }
+
+ if (nominally_recording || (_session.get_record_enabled() && _session.get_punch_in())) {
+ OverlapType ot;
+
+ ot = coverage (first_recordable_frame, last_recordable_frame, transport_frame, transport_frame + nframes);
+
+ switch (ot) {
+ case OverlapNone:
+ rec_nframes = 0;
+ break;
+
+ case OverlapInternal:
+ /* ---------- recrange
+ |---| transrange
+ */
+ rec_nframes = nframes;
+ rec_offset = 0;
+ break;
+
+ case OverlapStart:
+ /* |--------| recrange
+ -----| transrange
+ */
+ rec_nframes = transport_frame + nframes - first_recordable_frame;
+ if (rec_nframes) {
+ rec_offset = first_recordable_frame - transport_frame;
+ }
+ break;
+
+ case OverlapEnd:
+ /* |--------| recrange
+ |-------- transrange
+ */
+ rec_nframes = last_recordable_frame - transport_frame;
+ rec_offset = 0;
+ break;
+
+ case OverlapExternal:
+ /* |--------| recrange
+ -------------- transrange
+ */
+ rec_nframes = last_recordable_frame - last_recordable_frame;
+ rec_offset = first_recordable_frame - transport_frame;
+ break;
+ }
+
+ if (rec_nframes && !was_recording) {
+ capture_captured = 0;
+ was_recording = true;
+ }
+ }
+
+
+ if (can_record && !_last_capture_regions.empty()) {
+ _last_capture_regions.clear ();
+ }
+
+ if (rec_nframes) {
+
+ if (Config->get_use_hardware_monitoring() && re && rec_monitoring_off_for_roll && rec_monitors_input) {
+ for (c = channels.begin(); c != channels.end(); ++c) {
+ (*c).source->ensure_monitor_input (true);
+ }
+ rec_monitoring_off_for_roll = false;
+ }
+ }
+
+ if (nominally_recording || rec_nframes) {
+
+ for (n = 0, c = channels.begin(); c != channels.end(); ++c, ++n) {
+
+ ChannelInfo& chan (*c);
+
+ chan.capture_buf->get_write_vector (&chan.capture_vector);
+
+ if (rec_nframes <= chan.capture_vector.len[0]) {
+
+ chan.current_capture_buffer = chan.capture_vector.buf[0];
+
+ /* note: grab the entire port buffer, but only copy what we were supposed to for recording, and use
+ rec_offset
+ */
+
+ memcpy (chan.current_capture_buffer, _io->input(n)->get_buffer (rec_nframes) + offset + rec_offset, sizeof (Sample) * rec_nframes);
+
+ } else {
+
+ jack_nframes_t total = chan.capture_vector.len[0] + chan.capture_vector.len[1];
+
+ if (rec_nframes > total) {
+ DiskOverrun ();
+ goto out;
+ }
+
+ Sample* buf = _io->input (n)->get_buffer (nframes) + offset;
+ jack_nframes_t first = chan.capture_vector.len[0];
+
+ memcpy (chan.capture_wrap_buffer, buf, sizeof (Sample) * first);
+ memcpy (chan.capture_vector.buf[0], buf, sizeof (Sample) * first);
+ memcpy (chan.capture_wrap_buffer+first, buf + first, sizeof (Sample) * (rec_nframes - first));
+ memcpy (chan.capture_vector.buf[1], buf + first, sizeof (Sample) * (rec_nframes - first));
+
+ chan.current_capture_buffer = chan.capture_wrap_buffer;
+ }
+ }
+
+ } else {
+
+ if (was_recording) {
+ finish_capture (rec_monitors_input);
+ }
+
+ }
+
+ if (rec_nframes) {
+
+ /* data will be written to disk */
+
+ if (rec_nframes == nframes && rec_offset == 0) {
+
+ for (c = channels.begin(); c != channels.end(); ++c) {
+ (*c).current_playback_buffer = (*c).current_capture_buffer;
+ }
+
+ playback_distance = nframes;
+
+ } else {
+
+ /* we can't use the capture buffer as the playback buffer, because
+ we recorded only a part of the current process' cycle data
+ for capture.
+ */
+
+ collect_playback = true;
+ }
+
+ adjust_capture_position = rec_nframes;
+
+ } else if (nominally_recording) {
+
+ /* can't do actual capture yet - waiting for latency effects to finish before we start*/
+
+ for (c = channels.begin(); c != channels.end(); ++c) {
+ (*c).current_playback_buffer = (*c).current_capture_buffer;
+ }
+
+ playback_distance = nframes;
+
+ } else {
+
+ collect_playback = true;
+ }
+
+ if (collect_playback) {
+
+ /* we're doing playback */
+
+ jack_nframes_t necessary_samples;
+
+ /* no varispeed playback if we're recording, because the output .... TBD */
+
+ if (rec_nframes == 0 && _actual_speed != 1.0f) {
+ necessary_samples = (jack_nframes_t) floor ((nframes * fabs (_actual_speed))) + 1;
+ } else {
+ necessary_samples = nframes;
+ }
+
+ for (c = channels.begin(); c != channels.end(); ++c) {
+ (*c).playback_buf->get_read_vector (&(*c).playback_vector);
+ }
+
+ n = 0;
+
+ for (c = channels.begin(); c != channels.end(); ++c, ++n) {
+
+ ChannelInfo& chan (*c);
+
+ if (necessary_samples <= chan.playback_vector.len[0]) {
+
+ chan.current_playback_buffer = chan.playback_vector.buf[0];
+
+ } else {
+ jack_nframes_t total = chan.playback_vector.len[0] + chan.playback_vector.len[1];
+
+ if (necessary_samples > total) {
+ DiskUnderrun ();
+ goto out;
+
+ } else {
+
+ memcpy ((char *) chan.playback_wrap_buffer, chan.playback_vector.buf[0],
+ chan.playback_vector.len[0] * sizeof (Sample));
+ memcpy (chan.playback_wrap_buffer + chan.playback_vector.len[0], chan.playback_vector.buf[1],
+ (necessary_samples - chan.playback_vector.len[0]) * sizeof (Sample));
+
+ chan.current_playback_buffer = chan.playback_wrap_buffer;
+ }
+ }
+ }
+
+ if (rec_nframes == 0 && _actual_speed != 1.0f && _actual_speed != -1.0f) {
+
+ uint64_t phase = last_phase;
+ jack_nframes_t i = 0;
+
+ // Linearly interpolate into the alt buffer
+ // using 40.24 fixp maths (swh)
+
+ for (c = channels.begin(); c != channels.end(); ++c) {
+
+ float fr;
+ ChannelInfo& chan (*c);
+
+ i = 0;
+ phase = last_phase;
+
+ for (jack_nframes_t outsample = 0; outsample < nframes; ++outsample) {
+ i = phase >> 24;
+ fr = (phase & 0xFFFFFF) / 16777216.0f;
+ chan.speed_buffer[outsample] =
+ chan.current_playback_buffer[i] * (1.0f - fr) +
+ chan.current_playback_buffer[i+1] * fr;
+ phase += phi;
+ }
+
+ chan.current_playback_buffer = chan.speed_buffer;
+ }
+
+ playback_distance = i + 1;
+ last_phase = (phase & 0xFFFFFF);
+
+ } else {
+ playback_distance = nframes;
+ }
+
+ }
+
+ ret = 0;
+
+ out:
+ _processed = true;
+
+ if (ret) {
+
+ /* we're exiting with failure, so ::commit will not
+ be called. unlock the state lock.
+ */
+
+ pthread_mutex_unlock (state_lock.mutex());
+ }
+
+ return ret;
+}
+
+void
+DiskStream::recover ()
+{
+ pthread_mutex_unlock (state_lock.mutex());
+ _processed = false;
+}
+
+bool
+DiskStream::commit (jack_nframes_t nframes)
+{
+ bool need_butler = false;
+
+ if (_actual_speed < 0.0) {
+ playback_sample -= playback_distance;
+ } else {
+ playback_sample += playback_distance;
+ }
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+
+ (*chan).playback_buf->increment_read_ptr (playback_distance);
+
+ if (adjust_capture_position) {
+ (*chan).capture_buf->increment_write_ptr (adjust_capture_position);
+ }
+ }
+
+ if (adjust_capture_position != 0) {
+ capture_captured += adjust_capture_position;
+ adjust_capture_position = 0;
+ }
+
+ if (_slaved) {
+ need_butler = channels[0].playback_buf->write_space() >= channels[0].playback_buf->bufsize() / 2;
+ } else {
+ need_butler = channels[0].playback_buf->write_space() >= disk_io_chunk_frames
+ || channels[0].capture_buf->read_space() >= disk_io_chunk_frames;
+ }
+
+ pthread_mutex_unlock (state_lock.mutex());
+
+ _processed = false;
+
+ return need_butler;
+}
+
+void
+DiskStream::set_pending_overwrite (bool yn)
+{
+ /* called from audio thread, so we can use the read ptr and playback sample as we wish */
+
+ pending_overwrite = yn;
+
+ overwrite_frame = playback_sample;
+ overwrite_offset = channels.front().playback_buf->get_read_ptr();
+}
+
+int
+DiskStream::overwrite_existing_buffers ()
+{
+ Sample* mixdown_buffer;
+ float* gain_buffer;
+ int ret = -1;
+ bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+
+ overwrite_queued = false;
+
+ /* assume all are the same size */
+ jack_nframes_t size = channels[0].playback_buf->bufsize();
+
+ mixdown_buffer = new Sample[size];
+ gain_buffer = new float[size];
+
+ /* reduce size so that we can fill the buffer correctly. */
+ size--;
+
+ uint32_t n=0;
+ jack_nframes_t start;
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan, ++n) {
+
+ start = overwrite_frame;
+ jack_nframes_t cnt = size;
+
+ /* to fill the buffer without resetting the playback sample, we need to
+ do it one or two chunks (normally two).
+
+ |----------------------------------------------------------------------|
+
+ ^
+ overwrite_offset
+ |<- second chunk->||<----------------- first chunk ------------------>|
+
+ */
+
+ jack_nframes_t to_read = size - overwrite_offset;
+
+ if (read ((*chan).playback_buf->buffer() + overwrite_offset, mixdown_buffer, gain_buffer,
+ start, to_read, *chan, n, reversed)) {
+ error << compose(_("DiskStream %1: when refilling, cannot read %2 from playlist at frame %3"),
+ _id, size, playback_sample) << endmsg;
+ goto out;
+ }
+
+ if (cnt > to_read) {
+
+ cnt -= to_read;
+
+ if (read ((*chan).playback_buf->buffer(), mixdown_buffer, gain_buffer,
+ start, cnt, *chan, n, reversed)) {
+ error << compose(_("DiskStream %1: when refilling, cannot read %2 from playlist at frame %3"),
+ _id, size, playback_sample) << endmsg;
+ goto out;
+ }
+ }
+ }
+
+ ret = 0;
+
+ out:
+ pending_overwrite = false;
+ delete [] gain_buffer;
+ delete [] mixdown_buffer;
+ return ret;
+}
+
+int
+DiskStream::seek (jack_nframes_t frame, bool complete_refill)
+{
+ LockMonitor lm (state_lock, __LINE__, __FILE__);
+ uint32_t n;
+ int ret;
+ ChannelList::iterator chan;
+
+ for (n = 0, chan = channels.begin(); chan != channels.end(); ++chan, ++n) {
+ (*chan).playback_buf->reset ();
+ (*chan).capture_buf->reset ();
+ }
+
+ playback_sample = frame;
+ file_frame = frame;
+
+ if (complete_refill) {
+ while ((ret = do_refill (0, 0)) > 0);
+ } else {
+ ret = do_refill (0, 0);
+ }
+
+ return ret;
+}
+
+int
+DiskStream::can_internal_playback_seek (jack_nframes_t distance)
+{
+ ChannelList::iterator chan;
+
+ for (chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).playback_buf->read_space() < distance) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int
+DiskStream::internal_playback_seek (jack_nframes_t distance)
+{
+ ChannelList::iterator chan;
+
+ for (chan = channels.begin(); chan != channels.end(); ++chan) {
+ (*chan).playback_buf->increment_read_ptr (distance);
+ }
+
+ first_recordable_frame += distance;
+ playback_sample += distance;
+
+ return 0;
+}
+
+int
+DiskStream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, jack_nframes_t& start, jack_nframes_t cnt,
+
+ ChannelInfo& channel_info, int channel, bool reversed)
+{
+ jack_nframes_t this_read = 0;
+ bool reloop = false;
+ jack_nframes_t loop_end = 0;
+ jack_nframes_t loop_start = 0;
+ jack_nframes_t loop_length = 0;
+ jack_nframes_t offset = 0;
+ Location *loc = 0;
+
+ if (!reversed) {
+ /* Make the use of a Location atomic for this read operation.
+
+ Note: Locations don't get deleted, so all we care about
+ when I say "atomic" is that we are always pointing to
+ the same one and using a start/length values obtained
+ just once.
+ */
+
+ if ((loc = loop_location) != 0) {
+ loop_start = loc->start();
+ loop_end = loc->end();
+ loop_length = loop_end - loop_start;
+ }
+
+ /* if we are looping, ensure that the first frame we read is at the correct
+ position within the loop.
+ */
+
+ if (loc && start >= loop_end) {
+ //cerr << "start adjusted from " << start;
+ start = loop_start + ((start - loop_start) % loop_length);
+ //cerr << "to " << start << endl;
+ }
+ //cerr << "start is " << start << " loopstart: " << loop_start << " loopend: " << loop_end << endl;
+ }
+
+ while (cnt) {
+
+ /* take any loop into account. we can't read past the end of the loop. */
+
+ if (loc && (loop_end - start < cnt)) {
+ this_read = loop_end - start;
+ //cerr << "reloop true: thisread: " << this_read << " cnt: " << cnt << endl;
+ reloop = true;
+ } else {
+ reloop = false;
+ this_read = cnt;
+ }
+
+ if (this_read == 0) {
+ break;
+ }
+
+ this_read = min(cnt,this_read);
+
+ if (_playlist->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
+ error << compose(_("DiskStream %1: cannot read %2 from playlist at frame %3"), _id, this_read,
+ start) << endmsg;
+ return -1;
+ }
+
+ _read_data_count = _playlist->read_data_count();
+
+ if (reversed) {
+
+ /* don't adjust start, since caller has already done that
+ */
+
+ swap_by_ptr (buf, buf + this_read - 1);
+
+ } else {
+
+ /* if we read to the end of the loop, go back to the beginning */
+
+ if (reloop) {
+ start = loop_start;
+ } else {
+ start += this_read;
+ }
+ }
+
+ cnt -= this_read;
+ offset += this_read;
+ }
+
+ return 0;
+}
+
+int
+DiskStream::do_refill (Sample* mixdown_buffer, float* gain_buffer)
+{
+ int32_t ret = 0;
+ jack_nframes_t to_read;
+ RingBufferNPT<Sample>::rw_vector vector;
+ bool free_mixdown;
+ bool free_gain;
+ bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+ jack_nframes_t total_space;
+ jack_nframes_t zero_fill;
+ uint32_t chan_n;
+ ChannelList::iterator i;
+ jack_nframes_t ts;
+
+ channels.front().playback_buf->get_write_vector (&vector);
+
+ if ((total_space = vector.len[0] + vector.len[1]) == 0) {
+ return 0;
+ }
+
+ /* if there are 2+ chunks of disk i/o possible for
+ this track, let the caller know so that it can arrange
+ for us to be called again, ASAP.
+ */
+
+ if (total_space >= (_slaved?3:2) * disk_io_chunk_frames) {
+ ret = 1;
+ }
+
+ /* if we're running close to normal speed and there isn't enough
+ space to do disk_io_chunk_frames of I/O, then don't bother.
+
+ at higher speeds, just do it because the sync between butler
+ and audio thread may not be good enough.
+ */
+
+ if ((total_space < disk_io_chunk_frames) && fabs (_actual_speed) < 2.0f) {
+ return 0;
+ }
+
+ /* when slaved, don't try to get too close to the read pointer. this
+ leaves space for the buffer reversal to have something useful to
+ work with.
+ */
+
+ if (_slaved && total_space < (channels.front().playback_buf->bufsize() / 2)) {
+ return 0;
+ }
+
+ total_space = min (disk_io_chunk_frames, total_space);
+
+ if (reversed) {
+
+ if (file_frame == 0) {
+
+ /* at start: nothing to do but fill with silence */
+
+ for (chan_n = 0, i = channels.begin(); i != channels.end(); ++i, ++chan_n) {
+
+ ChannelInfo& chan (*i);
+ chan.playback_buf->get_write_vector (&vector);
+ memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
+ if (vector.len[1]) {
+ memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
+ }
+ chan.playback_buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+ }
+ return 0;
+ }
+
+ if (file_frame < total_space) {
+
+ /* too close to the start: read what we can,
+ and then zero fill the rest
+ */
+
+ zero_fill = total_space - file_frame;
+ total_space = file_frame;
+ file_frame = 0;
+
+ } else {
+
+ /* move read position backwards because we are going
+ to reverse the data.
+ */
+
+ file_frame -= total_space;
+ zero_fill = 0;
+ }
+
+ } else {
+
+ if (file_frame == max_frames) {
+
+ /* at end: nothing to do but fill with silence */
+
+ for (chan_n = 0, i = channels.begin(); i != channels.end(); ++i, ++chan_n) {
+
+ ChannelInfo& chan (*i);
+ chan.playback_buf->get_write_vector (&vector);
+ memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
+ if (vector.len[1]) {
+ memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
+ }
+ chan.playback_buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+ }
+ return 0;
+ }
+
+ if (file_frame > max_frames - total_space) {
+
+ /* to close to the end: read what we can, and zero fill the rest */
+
+ zero_fill = total_space - (max_frames - file_frame);
+ total_space = max_frames - file_frame;
+
+ } else {
+ zero_fill = 0;
+ }
+ }
+
+ /* Please note: the code to allocate buffers isn't run
+ during normal butler thread operation. Its there
+ for other times when we need to call do_refill()
+ from somewhere other than the butler thread.
+ */
+
+ if (mixdown_buffer == 0) {
+ mixdown_buffer = new Sample[disk_io_chunk_frames];
+ free_mixdown = true;
+ } else {
+ free_mixdown = false;
+ }
+
+ if (gain_buffer == 0) {
+ gain_buffer = new float[disk_io_chunk_frames];
+ free_gain = true;
+ } else {
+ free_gain = false;
+ }
+
+ jack_nframes_t file_frame_tmp = 0;
+
+ for (chan_n = 0, i = channels.begin(); i != channels.end(); ++i, ++chan_n) {
+
+ ChannelInfo& chan (*i);
+ Sample* buf1;
+ Sample* buf2;
+ jack_nframes_t len1, len2;
+
+ chan.playback_buf->get_write_vector (&vector);
+
+ ts = total_space;
+ file_frame_tmp = file_frame;
+
+ if (reversed) {
+ buf1 = vector.buf[1];
+ len1 = vector.len[1];
+ buf2 = vector.buf[0];
+ len2 = vector.len[0];
+ } else {
+ buf1 = vector.buf[0];
+ len1 = vector.len[0];
+ buf2 = vector.buf[1];
+ len2 = vector.len[1];
+ }
+
+
+ to_read = min (ts, len1);
+ to_read = min (to_read, disk_io_chunk_frames);
+
+ if (to_read) {
+
+ if (read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan, chan_n, reversed)) {
+ ret = -1;
+ goto out;
+ }
+
+ chan.playback_buf->increment_write_ptr (to_read);
+ ts -= to_read;
+ }
+
+ to_read = min (ts, len2);
+
+ if (to_read) {
+
+
+ /* we read all of vector.len[0], but it wasn't an entire disk_io_chunk_frames of data,
+ so read some or all of vector.len[1] as well.
+ */
+
+ if (read (buf2, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan, chan_n, reversed)) {
+ ret = -1;
+ goto out;
+ }
+
+ chan.playback_buf->increment_write_ptr (to_read);
+ }
+
+ if (zero_fill) {
+ /* do something */
+ }
+
+ }
+
+ file_frame = file_frame_tmp;
+
+ out:
+ if (free_mixdown) {
+ delete [] mixdown_buffer;
+ }
+ if (free_gain) {
+ delete [] gain_buffer;
+ }
+
+ return ret;
+}
+
+int
+DiskStream::do_flush (bool force_flush)
+{
+ uint32_t to_write;
+ int32_t ret = 0;
+ RingBufferNPT<Sample>::rw_vector vector;
+ jack_nframes_t total;
+
+ /* important note: this function will write *AT MOST*
+ disk_io_chunk_frames of data to disk. it will never
+ write more than that. if its writes that much and there
+ is more than that waiting to be written, it will return 1,
+ otherwise 0 on success or -1 on failure.
+
+ if there is less than disk_io_chunk_frames to be written,
+ no data will be written at all unless `force_flush' is true.
+ */
+
+ _write_data_count = 0;
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+
+ (*chan).capture_buf->get_read_vector (&vector);
+
+ total = vector.len[0] + vector.len[1];
+
+ if (total == 0 || (total < disk_io_chunk_frames && !force_flush && was_recording)) {
+ goto out;
+ }
+
+ /* if there are 2+ chunks of disk i/o possible for
+ this track, let the caller know so that it can arrange
+ for us to be called again, ASAP.
+
+ if we are forcing a flush, then if there is* any* extra
+ work, let the caller know.
+
+ if we are no longer recording and there is any extra work,
+ let the caller know too.
+ */
+
+ if (total >= 2 * disk_io_chunk_frames || ((force_flush || !was_recording) && total > disk_io_chunk_frames)) {
+ ret = 1;
+ }
+
+ to_write = min (disk_io_chunk_frames, (jack_nframes_t) vector.len[0]);
+
+ if ((!(*chan).write_source) || (*chan).write_source->write (vector.buf[0], to_write) != to_write) {
+ error << compose(_("DiskStream %1: cannot write to disk"), _id) << endmsg;
+ return -1;
+ }
+
+ (*chan).capture_buf->increment_read_ptr (to_write);
+
+ if ((to_write == vector.len[0]) && (total > to_write) && (to_write < disk_io_chunk_frames)) {
+
+ /* we wrote all of vector.len[0] but it wasn't an entire
+ disk_io_chunk_frames of data, so arrange for some part
+ of vector.len[1] to be flushed to disk as well.
+ */
+
+ to_write = min ((jack_nframes_t)(disk_io_chunk_frames - to_write), (jack_nframes_t) vector.len[1]);
+
+ if ((*chan).write_source->write (vector.buf[1], to_write) != to_write) {
+ error << compose(_("DiskStream %1: cannot write to disk"), _id) << endmsg;
+ return -1;
+ }
+
+ _write_data_count += (*chan).write_source->write_data_count();
+
+ (*chan).capture_buf->increment_read_ptr (to_write);
+ }
+ }
+
+ out:
+ return ret;
+}
+
+void
+DiskStream::playlist_changed (Change ignored)
+{
+ playlist_modified ();
+}
+
+void
+DiskStream::playlist_modified ()
+{
+ if (!i_am_the_modifier && !overwrite_queued) {
+ _session.request_overwrite_buffer (this);
+ overwrite_queued = true;
+ }
+}
+
+void
+DiskStream::transport_stopped (struct tm& when, time_t twhen, bool abort_capture)
+{
+ uint32_t buffer_position;
+ bool more_work = true;
+ int err = 0;
+ AudioRegion* region = 0;
+ jack_nframes_t total_capture;
+ AudioRegion::SourceList srcs;
+ AudioRegion::SourceList::iterator src;
+ ChannelList::iterator chan;
+ vector<CaptureInfo*>::iterator ci;
+ uint32_t n = 0;
+ list<Source*>* deletion_list;
+ bool mark_write_completed = false;
+
+ finish_capture (true);
+
+ /* butler is already stopped, but there may be work to do
+ to flush remaining data to disk.
+ */
+
+ while (more_work && !err) {
+ switch (do_flush (true)) {
+ case 0:
+ more_work = false;
+ break;
+ case 1:
+ break;
+ case -1:
+ error << compose(_("DiskStream \"%1\": cannot flush captured data to disk!"), _name) << endmsg;
+ err++;
+ }
+ }
+
+ /* XXX is there anything we can do if err != 0 ? */
+ LockMonitor lm (capture_info_lock, __LINE__, __FILE__);
+
+ if (capture_info.empty()) {
+ return;
+ }
+
+ if (abort_capture) {
+
+ ChannelList::iterator chan;
+
+ deletion_list = new list<Source*>;
+
+ for ( chan = channels.begin(); chan != channels.end(); ++chan) {
+
+ if ((*chan).write_source) {
+
+ (*chan).write_source->mark_for_remove ();
+ (*chan).write_source->release ();
+
+ deletion_list->push_back ((*chan).write_source);
+
+ (*chan).write_source = 0;
+ }
+
+ /* new source set up in "out" below */
+ }
+
+ if (!deletion_list->empty()) {
+ DeleteSources (deletion_list);
+ } else {
+ delete deletion_list;
+ }
+
+ goto out;
+ }
+
+ for (total_capture = 0, ci = capture_info.begin(); ci != capture_info.end(); ++ci) {
+ total_capture += (*ci)->frames;
+ }
+
+ /* figure out the name for this take */
+
+ for (n = 0, chan = channels.begin(); chan != channels.end(); ++chan, ++n) {
+
+ Source* s = (*chan).write_source;
+
+ if (s) {
+
+ FileSource* fsrc;
+
+ srcs.push_back (s);
+
+ if ((fsrc = dynamic_cast<FileSource *>(s)) != 0) {
+ fsrc->update_header (capture_info.front()->start, when, twhen);
+ }
+
+ s->set_captured_for (_name);
+
+ }
+ }
+
+ /* Register a new region with the Session that
+ describes the entire source. Do this first
+ so that any sub-regions will obviously be
+ children of this one (later!)
+ */
+
+ try {
+ region = new AudioRegion (srcs, 0, total_capture,
+ region_name_from_path (channels[0].write_source->name()),
+ 0, AudioRegion::Flag (AudioRegion::DefaultFlags|AudioRegion::Automatic|AudioRegion::WholeFile));
+
+ region->special_set_position (capture_info.front()->start);
+ }
+
+ catch (failed_constructor& err) {
+ error << compose(_("%1: could not create region for complete audio file"), _name) << endmsg;
+ /* XXX what now? */
+ }
+
+ _last_capture_regions.push_back (region);
+
+ // cerr << _name << ": there are " << capture_info.size() << " capture_info records\n";
+
+ _session.add_undo (_playlist->get_memento());
+ _playlist->freeze ();
+
+ for (buffer_position = 0, ci = capture_info.begin(); ci != capture_info.end(); ++ci) {
+
+ string region_name;
+ _session.region_name (region_name, _name, false);
+
+ // cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->frames << " add a region\n";
+
+ try {
+ region = new AudioRegion (srcs, buffer_position, (*ci)->frames, region_name);
+ }
+
+ catch (failed_constructor& err) {
+ error << _("DiskStream: could not create region for captured audio!") << endmsg;
+ continue; /* XXX is this OK? */
+ }
+
+ _last_capture_regions.push_back (region);
+
+ // cerr << "add new region, buffer position = " << buffer_position << " @ " << (*ci)->start << endl;
+
+ i_am_the_modifier++;
+ _playlist->add_region (*region, (*ci)->start);
+ i_am_the_modifier--;
+
+ buffer_position += (*ci)->frames;
+ }
+
+ _playlist->thaw ();
+ _session.add_redo_no_execute (_playlist->get_memento());
+
+ mark_write_completed = true;
+
+ reset_write_sources (mark_write_completed);
+
+ out:
+ for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) {
+ delete *ci;
+ }
+
+ capture_info.clear ();
+ capture_start_frame = 0;
+}
+
+void
+DiskStream::finish_capture (bool rec_monitors_input)
+{
+ if (Config->get_use_hardware_monitoring() && record_enabled()) {
+ if (rec_monitors_input) {
+ if (rec_monitoring_off_for_roll) {
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ (*chan).source->ensure_monitor_input (true);
+ }
+ rec_monitoring_off_for_roll = false;
+ }
+ } else {
+ if (!rec_monitoring_off_for_roll) {
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ (*chan).source->ensure_monitor_input (false);
+ }
+ rec_monitoring_off_for_roll = true;
+ }
+ }
+ }
+
+ was_recording = false;
+
+ if (capture_captured == 0) {
+ return;
+ }
+
+ CaptureInfo* ci = new CaptureInfo;
+
+ ci->start = capture_start_frame;
+ ci->frames = capture_captured;
+
+ /* XXX theoretical race condition here. Need atomic exchange ?
+ However, the circumstances when this is called right
+ now (either on record-disable or transport_stopped)
+ mean that no actual race exists. I think ...
+ We now have a capture_info_lock, but it is only to be used
+ to synchronize in the transport_stop and the capture info
+ accessors, so that invalidation will not occur (both non-realtime).
+ */
+
+ // cerr << "Finish capture, add new CI, " << ci->start << '+' << ci->frames << endl;
+
+ capture_info.push_back (ci);
+ capture_captured = 0;
+}
+
+void
+DiskStream::set_record_enabled (bool yn, void* src)
+{
+ if (!recordable() || !_session.record_enabling_legal()) {
+ return;
+ }
+
+ /* if we're turning on rec-enable, there needs to be an
+ input connection.
+ */
+
+ if (yn && channels[0].source == 0) {
+
+ /* pick up connections not initiated *from* the IO object
+ we're associated with.
+ */
+
+ get_input_sources ();
+
+ if (channels[0].source == 0) {
+
+ if (yn) {
+ CannotRecordNoInput (this); /* emit signal */
+ }
+ return;
+ }
+ }
+
+ /* yes, i know that this not proof against race conditions, but its
+ good enough. i think.
+ */
+
+ if (record_enabled() != yn) {
+ if (yn) {
+ atomic_set (&_record_enabled, 1);
+ capturing_sources.clear ();
+ if (Config->get_use_hardware_monitoring()) {
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).source) {
+ (*chan).source->request_monitor_input (true);
+ }
+ capturing_sources.push_back ((*chan).write_source);
+ }
+ } else {
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ capturing_sources.push_back ((*chan).write_source);
+ }
+ }
+
+ } else {
+ atomic_set (&_record_enabled, 0);
+ if (Config->get_use_hardware_monitoring()) {
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).source) {
+ (*chan).source->request_monitor_input (false);
+ }
+ }
+ }
+ capturing_sources.clear ();
+ }
+
+ record_enable_changed (src); /* EMIT SIGNAL */
+ }
+}
+
+XMLNode&
+DiskStream::get_state ()
+{
+ XMLNode* node = new XMLNode ("DiskStream");
+ char buf[64];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof(buf), "%d", channels.size());
+ node->add_property ("channels", buf);
+
+ node->add_property ("playlist", _playlist->name());
+
+ snprintf (buf, sizeof(buf), "%f", _visible_speed);
+ node->add_property ("speed", buf);
+
+ node->add_property("name", _name);
+ snprintf (buf, sizeof(buf), "%" PRIu64, id());
+ node->add_property("id", buf);
+
+ if (!capturing_sources.empty() && _session.get_record_enabled()) {
+
+ XMLNode* cs_child = new XMLNode (X_("CapturingSources"));
+ XMLNode* cs_grandchild;
+
+ for (vector<FileSource*>::iterator i = capturing_sources.begin(); i != capturing_sources.end(); ++i) {
+ cs_grandchild = new XMLNode (X_("file"));
+ cs_grandchild->add_property (X_("path"), (*i)->path());
+ cs_child->add_child_nocopy (*cs_grandchild);
+ }
+
+ /* store the location where capture will start */
+
+ Location* pi;
+
+ if (_session.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
+ snprintf (buf, sizeof (buf), "%" PRIu32, pi->start());
+ } else {
+ snprintf (buf, sizeof (buf), "%" PRIu32, _session.transport_frame());
+ }
+
+ cs_child->add_property (X_("at"), buf);
+ node->add_child_nocopy (*cs_child);
+ }
+
+ if (_extra_xml) {
+ node->add_child_copy (*_extra_xml);
+ }
+
+ return* node;
+}
+
+int
+DiskStream::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+ uint32_t nchans = 1;
+ XMLNode* capture_pending_node = 0;
+ LocaleGuard lg (X_("POSIX"));
+
+ in_set_state = true;
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == IO::state_node_name) {
+ deprecated_io_node = new XMLNode (**niter);
+ }
+
+ if ((*niter)->name() == X_("CapturingSources")) {
+ capture_pending_node = *niter;
+ }
+ }
+
+ /* prevent write sources from being created */
+
+ in_set_state = true;
+
+ if ((prop = node.property ("name")) != 0) {
+ _name = prop->value();
+ }
+
+ if (deprecated_io_node) {
+ if ((prop = deprecated_io_node->property ("id")) != 0) {
+ sscanf (prop->value().c_str(), "%llu", &_id);
+ }
+ } else {
+ if ((prop = node.property ("id")) != 0) {
+ sscanf (prop->value().c_str(), "%llu", &_id);
+ }
+ }
+
+ if ((prop = node.property ("channels")) != 0) {
+ nchans = atoi (prop->value().c_str());
+ }
+
+ // create necessary extra channels
+ // we are always constructed with one
+ // and we always need one
+
+ if (nchans > _n_channels) {
+
+ // we need to add new channel infos
+ //LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ int diff = nchans - channels.size();
+
+ for (int i=0; i < diff; ++i) {
+ add_channel ();
+ }
+
+ } else if (nchans < _n_channels) {
+
+ // we need to get rid of channels
+ //LockMonitor lm (state_lock, __LINE__, __FILE__);
+
+ int diff = channels.size() - nchans;
+
+ for (int i = 0; i < diff; ++i) {
+ remove_channel ();
+ }
+ }
+
+ if ((prop = node.property ("playlist")) == 0) {
+ return -1;
+ }
+
+ {
+ bool had_playlist = (_playlist != 0);
+
+ if (find_and_use_playlist (prop->value())) {
+ return -1;
+ }
+
+ if (!had_playlist) {
+ _playlist->set_orig_diskstream_id (_id);
+ }
+
+ if (capture_pending_node) {
+ use_pending_capture_data (*capture_pending_node);
+ }
+
+ }
+
+ if ((prop = node.property ("speed")) != 0) {
+ float sp = atof (prop->value().c_str());
+
+ if (realtime_set_speed (sp, false)) {
+ non_realtime_set_speed ();
+ }
+ }
+
+ _n_channels = channels.size();
+
+ in_set_state = false;
+
+ /* now that we're all done with playlist+channel set up,
+ go ahead and create write sources.
+ */
+
+
+ capturing_sources.clear ();
+
+ if (recordable()) {
+ reset_write_sources (false);
+ }
+
+ in_set_state = false;
+
+ return 0;
+}
+
+int
+DiskStream::use_new_write_source (uint32_t n)
+{
+ if (!recordable()) {
+ return 1;
+ }
+
+ if (n >= channels.size()) {
+ error << compose (_("DiskStream: channel %1 out of range"), n) << endmsg;
+ return -1;
+ }
+
+ ChannelInfo &chan = channels[n];
+
+ if (chan.write_source) {
+
+ if (FileSource::is_empty (chan.write_source->path())) {
+ chan.write_source->mark_for_remove ();
+ chan.write_source->release();
+ delete chan.write_source;
+ } else {
+ chan.write_source->release();
+ chan.write_source = 0;
+ }
+ }
+
+ try {
+ if ((chan.write_source = _session.create_file_source (*this, n)) == 0) {
+ throw failed_constructor();
+ }
+ }
+
+ catch (failed_constructor &err) {
+ error << compose (_("%1:%2 new capture file not initialized correctly"), _name, n) << endmsg;
+ chan.write_source = 0;
+ return -1;
+ }
+
+ chan.write_source->use ();
+
+ return 0;
+}
+
+void
+DiskStream::reset_write_sources (bool mark_write_complete)
+{
+ ChannelList::iterator chan;
+ uint32_t n;
+
+ if (!recordable()) {
+ return;
+ }
+
+ capturing_sources.clear ();
+
+ for (chan = channels.begin(), n = 0; chan != channels.end(); ++chan, ++n) {
+ if (mark_write_complete) {
+ (*chan).write_source->mark_streaming_write_completed ();
+ }
+ use_new_write_source (n);
+ if (record_enabled()) {
+ capturing_sources.push_back ((*chan).write_source);
+ }
+ }
+}
+
+void
+DiskStream::set_block_size (jack_nframes_t nframes)
+{
+ if (_session.get_block_size() > speed_buffer_size) {
+ speed_buffer_size = _session.get_block_size();
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).speed_buffer) delete [] (*chan).speed_buffer;
+ (*chan).speed_buffer = new Sample[speed_buffer_size];
+ }
+ }
+ allocate_temporary_buffers ();
+}
+
+void
+DiskStream::allocate_temporary_buffers ()
+{
+ /* make sure the wrap buffer is at least large enough to deal
+ with the speeds up to 1.2, to allow for micro-variation
+ when slaving to MTC, SMPTE etc.
+ */
+
+ float sp = max (fabsf (_actual_speed), 1.2f);
+ jack_nframes_t required_wrap_size = (jack_nframes_t) floor (_session.get_block_size() * sp) + 1;
+
+ if (required_wrap_size > wrap_buffer_size) {
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).playback_wrap_buffer) delete [] (*chan).playback_wrap_buffer;
+ (*chan).playback_wrap_buffer = new Sample[required_wrap_size];
+ if ((*chan).capture_wrap_buffer) delete [] (*chan).capture_wrap_buffer;
+ (*chan).capture_wrap_buffer = new Sample[required_wrap_size];
+ }
+
+ wrap_buffer_size = required_wrap_size;
+ }
+}
+
+void
+DiskStream::monitor_input (bool yn)
+{
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+
+ if ((*chan).source) {
+ (*chan).source->request_monitor_input (yn);
+ }
+ }
+}
+
+void
+DiskStream::set_capture_offset ()
+{
+ if (_io == 0) {
+ /* can't capture, so forget it */
+ return;
+ }
+
+ _capture_offset = _io->input_latency();
+}
+
+void
+DiskStream::set_persistent_align_style (AlignStyle a)
+{
+ _persistent_alignment_style = a;
+}
+
+void
+DiskStream::set_align_style_from_io ()
+{
+ bool have_physical = false;
+
+ if (_io == 0) {
+ return;
+ }
+
+ get_input_sources ();
+
+ for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+ if ((*chan).source && (*chan).source->flags() & JackPortIsPhysical) {
+ have_physical = true;
+ break;
+ }
+ }
+
+ if (have_physical) {
+ set_align_style (ExistingMaterial);
+ } else {
+ set_align_style (CaptureTime);
+ }
+}
+
+void
+DiskStream::set_align_style (AlignStyle a)
+{
+ if (record_enabled() && _session.actively_recording()) {
+ return;
+ }
+
+
+ if (a != _alignment_style) {
+ _alignment_style = a;
+ AlignmentStyleChanged ();
+ }
+}
+
+int
+DiskStream::add_channel ()
+{
+ /* XXX need to take lock??? */
+
+ ChannelInfo chan;
+
+ init_channel (chan);
+
+ chan.speed_buffer = new Sample[speed_buffer_size];
+ chan.playback_wrap_buffer = new Sample[wrap_buffer_size];
+ chan.capture_wrap_buffer = new Sample[wrap_buffer_size];
+
+ channels.push_back (chan);
+
+ _n_channels = channels.size();
+
+ return 0;
+}
+
+int
+DiskStream::remove_channel ()
+{
+ if (channels.size() > 1) {
+ /* XXX need to take lock??? */
+ ChannelInfo & chan = channels.back();
+ destroy_channel (chan);
+ channels.pop_back();
+
+ _n_channels = channels.size();
+ return 0;
+ }
+
+ return -1;
+}
+
+float
+DiskStream::playback_buffer_load () const
+{
+ return (float) ((double) channels.front().playback_buf->read_space()/
+ (double) channels.front().playback_buf->bufsize());
+}
+
+float
+DiskStream::capture_buffer_load () const
+{
+ return (float) ((double) channels.front().capture_buf->write_space()/
+ (double) channels.front().capture_buf->bufsize());
+}
+
+int
+DiskStream::set_loop (Location *location)
+{
+ if (location) {
+ if (location->start() >= location->end()) {
+ error << compose(_("Location \"%1\" not valid for track loop (start >= end)"), location->name()) << endl;
+ return -1;
+ }
+ }
+
+ loop_location = location;
+
+ LoopSet (location); /* EMIT SIGNAL */
+ return 0;
+}
+
+jack_nframes_t
+DiskStream::get_capture_start_frame (uint32_t n)
+{
+ LockMonitor lm (capture_info_lock, __LINE__, __FILE__);
+
+ if (capture_info.size() > n) {
+ return capture_info[n]->start;
+ }
+ else {
+ return capture_start_frame;
+ }
+}
+
+jack_nframes_t
+DiskStream::get_captured_frames (uint32_t n)
+{
+ LockMonitor lm (capture_info_lock, __LINE__, __FILE__);
+
+ if (capture_info.size() > n) {
+ return capture_info[n]->frames;
+ }
+ else {
+ return capture_captured;
+ }
+}
+
+void
+DiskStream::punch_in ()
+{
+}
+
+void
+DiskStream::punch_out ()
+{
+}
+
+int
+DiskStream::use_pending_capture_data (XMLNode& node)
+{
+ const XMLProperty* prop;
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+ FileSource* fs;
+ FileSource* first_fs = 0;
+ AudioRegion::SourceList pending_sources;
+ jack_nframes_t position;
+
+ if ((prop = node.property (X_("at"))) == 0) {
+ return -1;
+ }
+
+ if (sscanf (prop->value().c_str(), "%" PRIu32, &position) != 1) {
+ return -1;
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == X_("file")) {
+
+ if ((prop = (*niter)->property (X_("path"))) == 0) {
+ continue;
+ }
+
+ try {
+ fs = new FileSource (prop->value(), _session.frame_rate(), true);
+ }
+
+ catch (failed_constructor& err) {
+ error << compose (_("%1: cannot restore pending capture source file %2"),
+ _name, prop->value())
+ << endmsg;
+ return -1;
+ }
+
+ pending_sources.push_back (fs);
+
+ if (first_fs == 0) {
+ first_fs = fs;
+ }
+
+ fs->set_captured_for (_name);
+ }
+ }
+
+ if (pending_sources.size() == 0) {
+ /* nothing can be done */
+ return 1;
+ }
+
+ if (pending_sources.size() != _n_channels) {
+ error << compose (_("%1: incorrect number of pending sources listed - ignoring them all"), _name)
+ << endmsg;
+ return -1;
+ }
+
+ AudioRegion* region;
+
+ try {
+ region = new AudioRegion (pending_sources, 0, first_fs->length(),
+ region_name_from_path (first_fs->name()),
+ 0, AudioRegion::Flag (AudioRegion::DefaultFlags|AudioRegion::Automatic|AudioRegion::WholeFile));
+
+ region->special_set_position (0);
+ }
+
+ catch (failed_constructor& err) {
+ error << compose (_("%1: cannot create whole-file region from pending capture sources"),
+ _name)
+ << endmsg;
+
+ return -1;
+ }
+
+ try {
+ region = new AudioRegion (pending_sources, 0, first_fs->length(), region_name_from_path (first_fs->name()));
+ }
+
+ catch (failed_constructor& err) {
+ error << compose (_("%1: cannot create region from pending capture sources"),
+ _name)
+ << endmsg;
+
+ return -1;
+ }
+
+ _playlist->add_region (*region, position);
+
+ return 0;
+}
+
+void
+DiskStream::set_roll_delay (jack_nframes_t nframes)
+{
+ _roll_delay = nframes;
+}
diff --git a/libs/ardour/filesource.cc b/libs/ardour/filesource.cc
new file mode 100644
index 0000000000..0df4c29c3d
--- /dev/null
+++ b/libs/ardour/filesource.cc
@@ -0,0 +1,1101 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+
+/* This is is very hacky way to get pread and pwrite declarations.
+ First, include <features.h> so that we can avoid its #undef __USE_UNIX98.
+ Then define __USE_UNIX98, include <unistd.h>, and then undef it
+ again. If #define _XOPEN_SOURCE actually worked, I'd use that, but
+ despite claims in the header that it does, it doesn't.
+
+ features.h isn't available on osx and it compiles fine without it.
+*/
+
+#ifdef HAVE_FEATURES_H
+#include <features.h>
+#endif
+
+#if __GNUC__ >= 3
+// #define _XOPEN_SOURCE 500
+#include <unistd.h>
+#else
+#define __USE_UNIX98
+#include <unistd.h>
+#undef __USE_UNIX98
+#endif
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <climits>
+#include <cerrno>
+#include <sys/types.h>
+#include <pwd.h>
+#include <time.h>
+#include <sys/utsname.h>
+#include <vector>
+#include <cstdio> /* for rename(2) */
+
+#include <pbd/stl_delete.h>
+#include <pbd/basename.h>
+#include <pbd/dirname.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pathscanner.h>
+
+#include <ardour/ardour.h>
+#include <ardour/version.h>
+#include <ardour/source.h>
+#include <ardour/filesource.h>
+#include <ardour/session.h>
+#include <ardour/cycle_timer.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+string prepare_string(string& regex);
+
+char FileSource::bwf_country_code[3] = "us";
+char FileSource::bwf_organization_code[4] = "las";
+char FileSource::bwf_serial_number[13] = "000000000000";
+string FileSource::search_path;
+
+void
+FileSource::set_search_path (string p)
+{
+ search_path = p;
+}
+
+FileSource::FileSource (string pathstr, jack_nframes_t rate, bool repair_first)
+{
+ /* constructor used when the file cannot already exist or might be damaged */
+
+ if (repair_first && repair (pathstr, rate)) {
+ throw failed_constructor ();
+ }
+
+ if (init (pathstr, false, rate)) {
+ throw failed_constructor ();
+ }
+
+ SourceCreated (this); /* EMIT SIGNAL */
+}
+
+FileSource::FileSource (const XMLNode& node, jack_nframes_t rate)
+ : Source (node)
+{
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ /* constructor used when the file must already exist */
+
+ if (init (_name, true, rate)) {
+ throw failed_constructor ();
+ }
+
+ SourceCreated (this); /* EMIT SIGNAL */
+}
+
+int
+FileSource::init (string pathstr, bool must_exist, jack_nframes_t rate)
+{
+ bool new_file = false;
+ int ret = -1;
+ PathScanner scanner;
+
+ /* all native files end in .wav. this lets us discard
+ SndFileSource paths, which have ":N" at the end to
+ indicate which channel to read from, as well as any
+ other kind of non-native file. obviously, there
+ are more subtle checks later on.
+ */
+
+ if (pathstr.length() < 4 || pathstr.rfind (".wav") != pathstr.length() - 4) {
+ return ret;
+ }
+
+ is_bwf = false;
+ _length = 0;
+ fd = -1;
+ remove_at_unref = false;
+ next_peak_clear_should_notify = false;
+
+ if (pathstr[0] != '/') {
+
+ /* find pathstr in search path */
+
+ if (search_path.length() == 0) {
+ error << _("FileSource: search path not set") << endmsg;
+ goto out;
+ }
+
+ /* force exact match on the filename component by prefixing the regexp.
+ otherwise, "Drums-2.wav" matches "Comp_Drums-2.wav".
+ */
+
+ string regexp = "^";
+ regexp += prepare_string(pathstr);
+ regexp += '$';
+
+ vector<string*>* result = scanner (search_path, regexp, false, true, -1);
+
+ if (result == 0 || result->size() == 0) {
+ error << compose (_("FileSource: \"%1\" not found when searching %2 using %3"),
+ pathstr, search_path, regexp) << endmsg;
+ goto out;
+ }
+
+ if (result->size() > 1) {
+ string msg = compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, search_path);
+ vector<string*>::iterator x = result->begin();
+
+ while (true) {
+ msg += *(*x);
+ ++x;
+
+ if (x == result->end()) {
+ break;
+ }
+
+ msg += "\n\t";
+ }
+
+ error << msg << endmsg;
+ goto out;
+ }
+
+ _name = pathstr;
+ _path = *(result->front());
+
+ vector_delete (result);
+ delete result;
+
+ } else {
+
+ /* old style sessions include full paths */
+
+ _path = pathstr;
+ _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
+
+ }
+
+ if (access (_path.c_str(), F_OK) != 0) {
+ if (must_exist) {
+ error << compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
+ goto out;
+
+ }
+
+ if (errno == ENOENT) {
+ new_file = true;
+ } else {
+ error << compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
+ goto out;
+ }
+ }
+
+ if ((fd = open64 (_path.c_str(), O_RDWR|O_CREAT, 0644)) < 0) {
+ error << compose(_("FileSource: could not open \"%1\": (%2)"), _path, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ /* if there was no timestamp available via XML,
+ then get it from the filesystem.
+ */
+
+ if (_timestamp == 0) {
+ struct stat statbuf;
+
+ fstat (fd, &statbuf);
+ _timestamp = statbuf.st_mtime;
+ }
+
+ if (lseek (fd, 0, SEEK_END) == 0) {
+ new_file = true;
+ }
+
+ /* check that its a RIFF/WAVE format file */
+
+ if (new_file) {
+
+ is_bwf = Config->get_native_format_is_bwf ();
+
+ if (fill_header (rate)) {
+ error << compose (_("FileSource: cannot write header in %1"), _path) << endmsg;
+ goto out;
+ }
+
+ struct tm* now;
+ time_t xnow;
+
+ time (&xnow);
+ now = localtime (&xnow);
+
+ update_header (0, *now, xnow);
+
+ } else {
+
+ if (discover_chunks (must_exist)) {
+ error << compose (_("FileSource: cannot locate chunks in %1"), _path) << endmsg;
+ goto out;
+ }
+
+ if (read_header (must_exist)) {
+ error << compose (_("FileSource: cannot read header in %1"), _path) << endmsg;
+ goto out;
+ }
+
+ if (check_header (rate, must_exist)) {
+ error << compose (_("FileSource: cannot check header in %1"), _path) << endmsg;
+ goto out;
+ }
+
+ compute_header_size ();
+ }
+
+ if ((ret = initialize_peakfile (new_file, _path))) {
+ error << compose (_("FileSource: cannot initialize peakfile for %1"), _path) << endmsg;
+ }
+
+ out:
+ if (ret) {
+
+ if (fd >= 0) {
+ close (fd);
+ }
+
+ if (new_file) {
+ unlink (_path.c_str());
+ }
+ }
+
+ return ret;
+
+}
+
+FileSource::~FileSource ()
+{
+ GoingAway (this); /* EMIT SIGNAL */
+
+ if (fd >= 0) {
+
+ if (remove_at_unref || is_empty (_path)) {
+ unlink (_path.c_str());
+ unlink (peakpath.c_str());
+ }
+
+ close (fd);
+ }
+}
+
+int
+FileSource::discover_chunks (bool silent)
+{
+ WAVEChunk rw;
+ off64_t end;
+ off64_t offset;
+ char null_terminated_id[5];
+
+ if ((end = lseek (fd, 0, SEEK_END)) < 0) {
+ error << _("FileSource: cannot seek to end of file") << endmsg;
+ return -1;
+ }
+
+ if (::pread64 (fd, &rw, sizeof (rw), 0) != sizeof (rw)) {
+ error << _("FileSource: cannot read RIFF/WAVE chunk from file") << endmsg;
+ return -1;
+ }
+
+ if (memcmp (rw.id, "RIFF", 4) || memcmp (rw.text, "WAVE", 4)) {
+ if (!silent) {
+ error << compose (_("FileSource %1: not a RIFF/WAVE file"), _path) << endmsg;
+ }
+ return -1;
+ }
+
+ null_terminated_id[4] = '\0';
+
+ /* OK, its a RIFF/WAVE file. Find each chunk */
+
+ memcpy (null_terminated_id, rw.id, 4);
+ chunk_info.push_back (ChunkInfo (null_terminated_id, rw.size, 0));
+
+ offset = sizeof (rw);
+
+ while (offset < end) {
+
+ GenericChunk this_chunk;
+
+ if (::pread64 (fd, &this_chunk, sizeof (this_chunk), offset) != sizeof (this_chunk)) {
+ error << _("FileSource: can't read a chunk") << endmsg;
+ return -1;
+ }
+
+ memcpy (null_terminated_id, this_chunk.id, 4);
+ if (end != 44)
+ if ((memcmp(null_terminated_id, "data", 4) == 0))
+ if ((this_chunk.size == 0) || (this_chunk.size > (end - offset)))
+ this_chunk.size = end - offset;
+
+ chunk_info.push_back (ChunkInfo (null_terminated_id, this_chunk.size, offset));
+
+ /* skip to the next chunk */
+
+ offset += sizeof(GenericChunk) + this_chunk.size;
+ }
+
+ return 0;
+}
+
+FileSource::ChunkInfo*
+FileSource::lookup_chunk (string what)
+{
+ for (vector<ChunkInfo>::iterator i = chunk_info.begin(); i != chunk_info.end(); ++i) {
+ if ((*i).name == what) {
+ return &*i;
+ }
+ }
+ return 0;
+}
+
+int
+FileSource::fill_header (jack_nframes_t rate)
+{
+ /* RIFF/WAVE */
+
+ memcpy (header.wave.id, "RIFF", 4);
+ header.wave.size = 0; /* file size */
+ memcpy (header.wave.text, "WAVE", 4);
+
+ /* BROADCAST WAVE EXTENSION */
+
+ if (is_bwf) {
+
+ /* fill the entire BWF header with nulls */
+
+ memset (&header.bext, 0, sizeof (header.bext));
+
+ memcpy (header.bext.id, "bext", 4);
+
+ snprintf (header.bext.description, sizeof (header.bext.description), "%s", "ambiguity is clearer than precision.");
+
+ struct passwd *pwinfo;
+ struct utsname utsinfo;
+
+ if ((pwinfo = getpwuid (getuid())) == 0) {
+ error << compose(_("FileSource: cannot get user information for BWF header (%1)"), strerror(errno)) << endmsg;
+ return -1;
+ }
+ if (uname (&utsinfo)) {
+ error << compose(_("FileSource: cannot get host information for BWF header (%1)"), strerror(errno)) << endmsg;
+ return -1;
+ }
+
+ snprintf (header.bext.originator, sizeof (header.bext.originator), "ardour:%s:%s:%s:%s:%s)",
+ pwinfo->pw_gecos,
+ utsinfo.nodename,
+ utsinfo.sysname,
+ utsinfo.release,
+ utsinfo.version);
+
+ header.bext.version = 1;
+
+ /* XXX do something about this field */
+
+ snprintf (header.bext.umid, sizeof (header.bext.umid), "%s", "fnord");
+
+ /* add some coding history */
+
+ char buf[64];
+
+ /* encode: PCM,rate,mono,24bit,ardour-version
+
+ Note that because we use JACK, there is no way to tell
+ what the original bit depth of the signal was.
+ */
+
+ snprintf (buf, sizeof(buf), "F=%u,A=PCM,M=mono,W=24,T=ardour-%d.%d.%d",
+ rate,
+ libardour_major_version,
+ libardour_minor_version,
+ libardour_micro_version);
+
+ header.coding_history.push_back (buf);
+
+ /* initial size reflects coding history + "\r\n" */
+
+ header.bext.size = sizeof (BroadcastChunk) - sizeof (GenericChunk) + strlen (buf) + 2;
+ }
+
+ memcpy (header.format.id, "fmt ", 4);
+ header.format.size = sizeof (FMTChunk) - sizeof (GenericChunk);
+
+ header.format.formatTag = 3; /* little-endian IEEE float format */
+ header.format.nChannels = 1; /* mono */
+ header.format.nSamplesPerSec = rate;
+ header.format.nAvgBytesPerSec = rate * sizeof (Sample);
+ header.format.nBlockAlign = 4;
+ header.format.nBitsPerSample = 32;
+
+ /* DATA */
+
+ memcpy (header.data.id, "data", 4);
+ header.data.size = 0;
+
+ return 0;
+}
+
+void
+FileSource::compute_header_size ()
+{
+ off64_t end_of_file;
+ int32_t coding_history_size = 0;
+
+ end_of_file = lseek (fd, 0, SEEK_END);
+
+ if (is_bwf) {
+
+ /* include the coding history */
+
+ for (vector<string>::iterator i = header.coding_history.begin(); i != header.coding_history.end(); ++i) {
+ coding_history_size += (*i).length() + 2; // include "\r\n";
+ }
+
+ header.bext.size = sizeof (BroadcastChunk) - sizeof (GenericChunk) + coding_history_size;
+ data_offset = bwf_header_size + coding_history_size;
+
+ } else {
+ data_offset = wave_header_size;
+ }
+
+ if (end_of_file == 0) {
+
+ /* newfile condition */
+
+ if (is_bwf) {
+ /* include "WAVE" then all the chunk sizes (bext, fmt, data) */
+ header.wave.size = 4 + sizeof (BroadcastChunk) + coding_history_size + sizeof (FMTChunk) + sizeof (GenericChunk);
+ } else {
+ /* include "WAVE" then all the chunk sizes (fmt, data) */
+ header.wave.size = 4 + sizeof (FMTChunk) + sizeof (GenericChunk);
+ }
+
+ header.data.size = 0;
+
+ } else {
+
+ header.wave.size = end_of_file - 8; /* size of initial RIFF+size pseudo-chunk */
+ header.data.size = end_of_file - data_offset;
+ }
+}
+
+int
+FileSource::update_header (jack_nframes_t when, struct tm& now, time_t tnow)
+{
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+
+ if (is_bwf) {
+ /* random code is 9 digits */
+
+ int random_code = random() % 999999999;
+
+ snprintf (header.bext.originator_reference, sizeof (header.bext.originator_reference), "%2s%3s%12s%02d%02d%02d%9d",
+ bwf_country_code,
+ bwf_organization_code,
+ bwf_serial_number,
+ now.tm_hour,
+ now.tm_min,
+ now.tm_sec,
+ random_code);
+
+ snprintf (header.bext.origination_date, sizeof (header.bext.origination_date), "%4d-%02d-%02d",
+ 1900 + now.tm_year,
+ now.tm_mon,
+ now.tm_mday);
+
+ snprintf (header.bext.origination_time, sizeof (header.bext.origination_time), "%02d-%02d-%02d",
+ now.tm_hour,
+ now.tm_min,
+ now.tm_sec);
+
+ header.bext.time_reference_high = 0;
+ header.bext.time_reference_low = when;
+ }
+
+ compute_header_size ();
+
+ if (write_header()) {
+ error << compose(_("FileSource[%1]: cannot update data size: %2"), _path, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ stamp (tnow);
+
+ return 0;
+}
+
+int
+FileSource::read_header (bool silent)
+{
+ /* we already have the chunk info, so just load up whatever we have */
+
+ ChunkInfo* info;
+
+ if ((info = lookup_chunk ("RIFF")) == 0) {
+ error << _("FileSource: can't find RIFF chunk info") << endmsg;
+ return -1;
+ }
+
+ /* just fill this chunk/header ourselves, disk i/o is stupid */
+
+ memcpy (header.wave.id, "RIFF", 4);
+ header.wave.size = 0;
+ memcpy (header.wave.text, "WAVE", 4);
+
+ if ((info = lookup_chunk ("bext")) != 0) {
+
+ is_bwf = true;
+
+ if (::pread64 (fd, &header.bext, sizeof (header.bext), info->offset) != sizeof (header.bext)) {
+ error << _("FileSource: can't read RIFF chunk") << endmsg;
+ return -1;
+ }
+
+ if (read_broadcast_data (*info)) {
+ return -1;
+ }
+ }
+
+ if ((info = lookup_chunk ("fmt ")) == 0) {
+ error << _("FileSource: can't find format chunk info") << endmsg;
+ return -1;
+ }
+
+ if (::pread64 (fd, &header.format, sizeof (header.format), info->offset) != sizeof (header.format)) {
+ error << _("FileSource: can't read format chunk") << endmsg;
+ return -1;
+ }
+
+ if ((info = lookup_chunk ("data")) == 0) {
+ error << _("FileSource: can't find data chunk info") << endmsg;
+ return -1;
+ }
+
+ if (::pread (fd, &header.data, sizeof (header.data), info->offset) != sizeof (header.data)) {
+ error << _("FileSource: can't read data chunk") << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+FileSource::read_broadcast_data (ChunkInfo& info)
+{
+ int32_t coding_history_size;
+
+ if (::pread (fd, (char *) &header.bext, sizeof (header.bext), info.offset + sizeof (GenericChunk)) != sizeof (header.bext)) {
+ error << compose(_("FileSource: cannot read Broadcast Wave data from existing audio file \"%1\" (%2)"),
+ _path, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (info.size > sizeof (header.bext)) {
+
+ coding_history_size = info.size - (sizeof (header.bext) - sizeof (GenericChunk));
+
+ char data[coding_history_size];
+
+ if (::pread (fd, data, coding_history_size, info.offset + sizeof (BroadcastChunk)) != coding_history_size) {
+ error << compose(_("FileSource: cannot read Broadcast Wave coding history from audio file \"%1\" (%2)"),
+ _path, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ /* elements of the coding history are divided by \r\n */
+
+ char *p = data;
+ char *end = data + coding_history_size;
+ string tmp;
+
+ while (p < end) {
+ if (*p == '\r' && (p+1) != end && *(p+1) == '\n') {
+ if (tmp.length()) {
+ header.coding_history.push_back (tmp);
+ tmp = "";
+ }
+ p += 2;
+ } else {
+ tmp += *p;
+ p++;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+FileSource::check_header (jack_nframes_t rate, bool silent)
+{
+ if (header.format.formatTag != 3) { /* IEEE float */
+ if (!silent) {
+ error << compose(_("FileSource \"%1\" does not use floating point format.\n"
+ "This is probably a programming error."), _path) << endmsg;
+ }
+ return -1;
+ }
+
+ /* compute the apparent length of the data */
+
+ data_offset = 0;
+
+ for (vector<ChunkInfo>::iterator i = chunk_info.begin(); i != chunk_info.end();) {
+ vector<ChunkInfo>::iterator n;
+
+ n = i;
+ ++n;
+
+ if ((*i).name == "data") {
+
+ data_offset = (*i).offset + sizeof (GenericChunk);
+
+ if (n == chunk_info.end()) {
+ off64_t end_of_file;
+ end_of_file = lseek (fd, 0, SEEK_END);
+
+ _length = end_of_file - data_offset;
+
+ } else {
+ _length = (*n).offset - data_offset;
+ }
+
+ _length /= sizeof (Sample);
+
+ break;
+ }
+
+ i = n;
+ }
+
+ if (data_offset == 0) {
+ error << compose(_("FileSource \"%1\" has no \"data\" chunk"), _path) << endmsg;
+ return -1;
+ }
+
+ if (_length * sizeof (Sample) != (jack_nframes_t) header.data.size) {
+ warning << compose(_("%1: data length in header (%2) differs from implicit size in file (%3)"),
+ _path, header.data.size, _length * sizeof (Sample)) << endmsg;
+ }
+
+ if ((jack_nframes_t) header.format.nSamplesPerSec != rate) {
+ warning << compose(_("\"%1\" has a sample rate of %2 instead of %3 as used by this session"),
+ _path, header.format.nSamplesPerSec, rate) << endmsg;
+ }
+
+ return 0;
+}
+
+int
+FileSource::write_header()
+{
+ off64_t pos;
+
+ /* write RIFF/WAVE boilerplate */
+
+ pos = 0;
+
+ if (::pwrite64 (fd, (char *) &header.wave, sizeof (header.wave), pos) != sizeof (header.wave)) {
+ error << compose(_("FileSource: cannot write WAVE chunk: %1"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ pos += sizeof (header.wave);
+
+ if (is_bwf) {
+
+ /* write broadcast chunk data without copy history */
+
+ if (::pwrite64 (fd, (char *) &header.bext, sizeof (header.bext), pos) != sizeof (header.bext)) {
+ return -1;
+ }
+
+ pos += sizeof (header.bext);
+
+ /* write copy history */
+
+ for (vector<string>::iterator i = header.coding_history.begin(); i != header.coding_history.end(); ++i) {
+ string x;
+
+ x = *i;
+ x += "\r\n";
+
+ if (::pwrite64 (fd, x.c_str(), x.length(), pos) != (int32_t) x.length()) {
+ return -1;
+ }
+
+ pos += x.length();
+ }
+ }
+
+ /* write fmt and data chunks */
+
+ if (::pwrite64 (fd, (char *) &header.format, sizeof (header.format), pos) != sizeof (header.format)) {
+ error << compose(_("FileSource: cannot write format chunk: %1"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ pos += sizeof (header.format);
+
+ if (::pwrite64 (fd, (char *) &header.data, sizeof (header.data), pos) != sizeof (header.data)) {
+ error << compose(_("FileSource: cannot data chunk: %1"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+FileSource::mark_for_remove ()
+{
+ remove_at_unref = true;
+}
+
+jack_nframes_t
+FileSource::read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const
+{
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+ return read_unlocked (dst, start, cnt);
+}
+
+jack_nframes_t
+FileSource::read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const
+{
+ int32_t byte_cnt;
+ int nread;
+
+ byte_cnt = cnt * sizeof (Sample);
+
+ if ((nread = pread (fd, (char *) dst, byte_cnt, data_offset + (start * sizeof (Sample)))) != (off64_t) byte_cnt) {
+
+ cerr << "FileSource: \""
+ << _path
+ << "\" bad read at frame "
+ << start
+ << ", of "
+ << cnt
+ << " (bytes="
+ << byte_cnt
+ << ") frames [length = " << _length
+ << " eor = " << start + cnt << "] ("
+ << strerror (errno)
+ << ") (read "
+ << nread / sizeof (Sample)
+ << " (bytes=" <<nread
+ << ")) pos was"
+ << data_offset
+ << '+'
+ << start << '*' << sizeof(Sample)
+ << " = " << data_offset + (start * sizeof(Sample))
+ << endl;
+
+ return 0;
+ }
+
+ _read_data_count = byte_cnt;
+
+ return cnt;
+}
+
+jack_nframes_t
+FileSource::write (Sample *data, jack_nframes_t cnt)
+{
+ {
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+
+ int32_t byte_cnt = cnt * sizeof (Sample);
+ int32_t byte_pos = data_offset + (_length * sizeof (Sample));
+ jack_nframes_t oldlen;
+
+ if (::pwrite64 (fd, (char *) data, byte_cnt, byte_pos) != (off64_t) byte_cnt) {
+ error << compose(_("FileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
+ return 0;
+ }
+
+ oldlen = _length;
+ _length += cnt;
+ _write_data_count = byte_cnt;
+
+ if (_build_peakfiles) {
+ PeakBuildRecord *pbr = 0;
+
+ if (pending_peak_builds.size()) {
+ pbr = pending_peak_builds.back();
+ }
+
+ if (pbr && pbr->frame + pbr->cnt == oldlen) {
+
+ /* the last PBR extended to the start of the current write,
+ so just extend it again.
+ */
+
+ pbr->cnt += cnt;
+ } else {
+ pending_peak_builds.push_back (new PeakBuildRecord (oldlen, cnt));
+ }
+
+ _peaks_built = false;
+ }
+
+ }
+
+
+ if (_build_peakfiles) {
+ queue_for_peaks (*this);
+ }
+
+ return cnt;
+}
+
+bool
+FileSource::is_empty (string path)
+{
+ struct stat statbuf;
+
+ stat (path.c_str(), &statbuf);
+
+ /* its a bit of a problem if an audio file happens
+ to be a regular WAVE file with just enough data
+ to match the size of an empty BWF. hmmm. not very
+ likely however - that represents a duration of
+ less than 1msec at typical sample rates.
+ */
+
+ /* NOTE: 700 bytes is the size of a BWF header structure *plus* our minimal coding history */
+
+ return (statbuf.st_size == 0 || statbuf.st_size == wave_header_size || statbuf.st_size == 700);
+}
+
+void
+FileSource::mark_streaming_write_completed ()
+{
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+
+ next_peak_clear_should_notify = true;
+
+ if (_peaks_built || pending_peak_builds.empty()) {
+ _peaks_built = true;
+ PeaksReady (); /* EMIT SIGNAL */
+ }
+}
+
+string
+FileSource::peak_path(string audio_path)
+{
+ return Session::peak_path_from_audio_path (audio_path);
+}
+
+string
+FileSource::old_peak_path(string audio_path)
+{
+ return Session::old_peak_path_from_audio_path (audio_path);
+}
+
+void
+FileSource::mark_take (string id)
+{
+ _take_id = id;
+}
+
+int
+FileSource::move_to_trash (const string trash_dir_name)
+{
+ string newpath;
+
+ /* don't move the file across filesystems, just
+ stick it in the `trash_dir_name' directory
+ on whichever filesystem it was already on.
+ */
+
+ newpath = PBD::dirname (_path);
+ newpath = PBD::dirname (newpath);
+
+ newpath += '/';
+ newpath += trash_dir_name;
+ newpath += '/';
+ newpath += PBD::basename (_path);
+
+ if (access (newpath.c_str(), F_OK) == 0) {
+
+ /* the new path already exists, try versioning */
+
+ char buf[PATH_MAX+1];
+ int version = 1;
+ string newpath_v;
+
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
+ newpath_v = buf;
+
+ while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
+ newpath_v = buf;
+ }
+
+ if (version == 999) {
+ error << compose (_("there are already 1000 files with names like %1; versioning discontinued"),
+ newpath)
+ << endmsg;
+ } else {
+ newpath = newpath_v;
+ }
+
+ } else {
+
+ /* it doesn't exist, or we can't read it or something */
+
+ }
+
+ if (::rename (_path.c_str(), newpath.c_str()) != 0) {
+ error << compose (_("cannot rename audio file source from %1 to %2 (%3)"),
+ _path, newpath, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ if (::unlink (peakpath.c_str()) != 0) {
+ error << compose (_("cannot remove peakfile %1 for %2 (%3)"),
+ peakpath, _path, strerror (errno))
+ << endmsg;
+ /* try to back out */
+ rename (newpath.c_str(), _path.c_str());
+ return -1;
+ }
+
+ _path = newpath;
+ peakpath = "";
+ remove_at_unref = false;
+
+ return 0;
+}
+
+string
+prepare_string(string& str)
+{
+ string prepared;
+
+ for (uint32_t i = 0; i < str.size(); ++i){
+ char c = str[i];
+ if (isdigit(c) || isalpha(c)){
+ prepared += c;
+ } else {
+ prepared += '\\';
+ prepared += c;
+ }
+ }
+
+ return prepared;
+}
+
+int
+FileSource::repair (string path, jack_nframes_t rate)
+{
+ FILE* in;
+ char buf[700];
+ char* ptr;
+ struct stat statbuf;
+ size_t i;
+ int ret = -1;
+
+ if (stat (path.c_str(), &statbuf)) {
+ return -1;
+ }
+
+ if (statbuf.st_size <= (off_t) sizeof (buf)) {
+ /* nothing was ever written to the file, so there is nothing
+ really to do.
+ */
+ return 0;
+ }
+
+ if ((in = fopen (path.c_str(), "r+")) == NULL) {
+ return -1;
+ }
+
+ if (fread (buf, sizeof (buf), 1, in) != 1) {
+ goto out;
+ }
+
+ if (memcmp (&buf[0], "RIFF", 4) || memcmp (&buf[8], "WAVE", 4)) {
+ /* no header. too dangerous to proceed */
+ goto out;
+
+ }
+
+ /* reset the size of the RIFF chunk header */
+
+ *((int32_t *)&buf[4]) = statbuf.st_size - 8;
+
+ /* find data chunk and reset the size */
+
+ ptr = buf;
+
+ for (i = 0; i < sizeof (buf); ) {
+
+ if (memcmp (ptr, "fmt ", 4) == 0) {
+
+ FMTChunk fmt;
+
+ memcpy (&fmt, ptr, sizeof (fmt));
+ fmt.nSamplesPerSec = rate;
+ fmt.nAvgBytesPerSec = rate * 4;
+
+ /* put it back */
+
+ memcpy (ptr, &fmt, sizeof (fmt));
+ ptr += sizeof (fmt);
+ i += sizeof (fmt);
+
+ } else if (memcmp (ptr, "data", 4) == 0) {
+
+ *((int32_t *)&ptr[4]) = statbuf.st_size - i - 8;
+ break;
+
+ } else {
+ ++ptr;
+ ++i;
+ }
+ }
+
+ /* now flush it back to disk */
+
+ rewind (in);
+
+ if (fwrite (buf, sizeof (buf), 1, in) != 1) {
+ goto out;
+ }
+
+ ret = 0;
+ fflush (in);
+
+ out:
+ fclose (in);
+ return ret;
+}
diff --git a/libs/ardour/gain.cc b/libs/ardour/gain.cc
new file mode 100644
index 0000000000..d6fd464208
--- /dev/null
+++ b/libs/ardour/gain.cc
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <ardour/gain.h>
+
+using namespace ARDOUR;
+
+Gain::Gain ()
+ : Curve (0.0, 2.0, 1.0f) /* XXX yuck; clamps gain to -inf .. +6db */
+{
+}
+
+Gain::Gain (const Gain& other)
+ : Curve (other)
+{
+}
+
+Gain&
+Gain::operator= (const Gain& other)
+{
+ if (this != &other) {
+ Curve::operator= (other);
+ }
+ return *this;
+}
+
+void
+Gain::fill_linear_volume_fade_in (Gain& gain, jack_nframes_t frames)
+{
+}
+
+void
+Gain::fill_linear_volume_fade_out (Gain& gain, jack_nframes_t frames)
+{
+}
+
+void
+Gain::fill_linear_fade_in (Gain& gain, jack_nframes_t frames)
+{
+}
+
+void
+Gain::fill_linear_fade_out (Gain& gain, jack_nframes_t frames)
+{
+}
diff --git a/libs/ardour/gdither.cc b/libs/ardour/gdither.cc
new file mode 100644
index 0000000000..3cdd7ee89e
--- /dev/null
+++ b/libs/ardour/gdither.cc
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include <ardour/gdither_types_internal.h>
+#include <ardour/gdither.h>
+#include <ardour/noise.h>
+
+/* this monstrosity is necessary to get access to lrintf() and random().
+ whoever is writing the glibc headers <cmath> and <cstdlib> should be
+ hauled off to a programmer re-education camp. for the rest of
+ their natural lives. or longer. <paul@linuxaudiosystems.com>
+*/
+
+#define _ISOC9X_SOURCE 1
+#define _ISOC99_SOURCE 1
+#ifdef __cplusplus
+#include <cmath>
+#else
+#include <math.h>
+#endif
+
+#undef __USE_SVID
+#define __USE_SVID 1
+#ifdef __cplusplus
+#include <cstdlib>
+#else
+#include <stdlib.h>
+#endif
+
+#include <sys/types.h>
+
+/* Lipshitz's minimally audible FIR, only really works for 46kHz-ish signals */
+static const float shaped_bs[] = { 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f };
+
+/* Some useful constants */
+#define MAX_U8 255
+#define MIN_U8 0
+#define SCALE_U8 128.0f
+
+#define MAX_S16 32767
+#define MIN_S16 -32768
+#define SCALE_S16 32768.0f
+
+#define MAX_S24 8388607
+#define MIN_S24 -8388608
+#define SCALE_S24 8388608.0f
+
+GDither gdither_new(GDitherType type, uint32_t channels,
+
+ GDitherSize bit_depth, int dither_depth)
+{
+ GDither s;
+
+ s = (GDither)calloc(1, sizeof(struct GDither_s));
+ s->type = type;
+ s->channels = channels;
+ s->bit_depth = (int)bit_depth;
+
+ if (dither_depth <= 0 || dither_depth > (int)bit_depth) {
+ dither_depth = (int)bit_depth;
+ }
+ s->dither_depth = dither_depth;
+
+ s->scale = (float)(1LL << (dither_depth - 1));
+ if (bit_depth == GDitherFloat || bit_depth == GDitherDouble) {
+ s->post_scale_fp = 1.0f / s->scale;
+ s->post_scale = 0;
+ } else {
+ s->post_scale_fp = 0.0f;
+ s->post_scale = 1 << ((int)bit_depth - dither_depth);
+ }
+
+ switch (bit_depth) {
+ case GDither8bit:
+ /* Unsigned 8 bit */
+ s->bias = 1.0f;
+ s->clamp_u = 255;
+ s->clamp_l = 0;
+ break;
+ case GDither16bit:
+ /* Signed 16 bit */
+ s->bias = 0.0f;
+ s->clamp_u = 32767;
+ s->clamp_l = -32768;
+ break;
+ case GDither32bit:
+ /* Signed 24 bit, in upper 24 bits of 32 bit word */
+ s->bias = 0.0f;
+ s->clamp_u = 8388607;
+ s->clamp_l = -8388608;
+ break;
+ case GDitherFloat:
+ /* normalised float */
+ s->bias = 0.0f;
+ s->clamp_u = lrintf(s->scale);
+ s->clamp_l = lrintf(-s->scale);
+ break;
+ case GDitherDouble:
+ /* normalised float */
+ s->bias = 0.0f;
+ s->clamp_u = lrintf(s->scale);
+ s->clamp_l = lrintf(-s->scale);
+ break;
+ case 23:
+ /* special performance test case */
+ s->scale = SCALE_S24;
+ s->post_scale = 256;
+ s->bias = 0.0f;
+ s->clamp_u = 8388607;
+ s->clamp_l = -8388608;
+ break;
+ default:
+ /* Not a bit depth we can handle */
+ free(s);
+
+ return NULL;
+ break;
+ }
+
+ switch (type) {
+ case GDitherNone:
+ case GDitherRect:
+ /* No state */
+ break;
+
+ case GDitherTri:
+ /* The last whitenoise sample */
+ s->tri_state = (float *) calloc(channels, sizeof(float));
+ break;
+
+ case GDitherShaped:
+ /* The error from the last few samples encoded */
+ s->shaped_state = (GDitherShapedState*)
+ calloc(channels, sizeof(GDitherShapedState));
+ break;
+ }
+
+ return s;
+}
+
+void gdither_free(GDither s)
+{
+ if (s) {
+ free(s->tri_state);
+ free(s->shaped_state);
+ free(s);
+ }
+}
+
+inline static void gdither_innner_loop(const GDitherType dt,
+ const uint32_t stride, const float bias, const float scale,
+
+ const uint32_t post_scale, const int bit_depth,
+ const uint32_t channel, const uint32_t length, float *ts,
+
+ GDitherShapedState *ss, float *x, void *y, const int clamp_u,
+
+ const int clamp_l)
+{
+ uint32_t pos, i;
+ u_int8_t *o8 = (u_int8_t*) y;
+ int16_t *o16 = (int16_t*) y;
+ int32_t *o32 = (int32_t*) y;
+ float tmp, r, ideal;
+ int64_t clamped;
+
+ i = channel;
+ for (pos = 0; pos < length; pos++, i += stride) {
+ tmp = x[i] * scale + bias;
+
+ switch (dt) {
+ case GDitherNone:
+ break;
+ case GDitherRect:
+ tmp -= GDITHER_NOISE;
+ break;
+ case GDitherTri:
+ r = GDITHER_NOISE - 0.5f;
+ tmp -= r - ts[channel];
+ ts[channel] = r;
+ break;
+ case GDitherShaped:
+ /* Save raw value for error calculations */
+ ideal = tmp;
+
+ /* Run FIR and add white noise */
+ ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f;
+ tmp += ss->buffer[ss->phase] * shaped_bs[0]
+ + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[1]
+ + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[2]
+ + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[3]
+ + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[4];
+
+ /* Roll buffer and store last error */
+ ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK;
+ ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal;
+ break;
+ }
+
+ clamped = lrintf(tmp);
+ if (clamped > clamp_u) {
+ clamped = clamp_u;
+ } else if (clamped < clamp_l) {
+ clamped = clamp_l;
+ }
+
+ switch (bit_depth) {
+ case GDither8bit:
+ o8[i] = (u_int8_t) (clamped * post_scale);
+ break;
+ case GDither16bit:
+ o16[i] = (int16_t) (clamped * post_scale);
+ break;
+ case GDither32bit:
+ o32[i] = (int32_t) (clamped * post_scale);
+ break;
+ }
+ }
+}
+
+/* floating pint version of the inner loop function */
+inline static void gdither_innner_loop_fp(const GDitherType dt,
+ const uint32_t stride, const float bias, const float scale,
+
+ const float post_scale, const int bit_depth,
+ const uint32_t channel, const uint32_t length, float *ts,
+
+ GDitherShapedState *ss, float *x, void *y, const int clamp_u,
+
+ const int clamp_l)
+{
+ uint32_t pos, i;
+ float *oflt = (float*) y;
+ double *odbl = (double*) y;
+ float tmp, r, ideal;
+ double clamped;
+
+ i = channel;
+ for (pos = 0; pos < length; pos++, i += stride) {
+ tmp = x[i] * scale + bias;
+
+ switch (dt) {
+ case GDitherNone:
+ break;
+ case GDitherRect:
+ tmp -= GDITHER_NOISE;
+ break;
+ case GDitherTri:
+ r = GDITHER_NOISE - 0.5f;
+ tmp -= r - ts[channel];
+ ts[channel] = r;
+ break;
+ case GDitherShaped:
+ /* Save raw value for error calculations */
+ ideal = tmp;
+
+ /* Run FIR and add white noise */
+ ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f;
+ tmp += ss->buffer[ss->phase] * shaped_bs[0]
+ + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[1]
+ + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[2]
+ + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[3]
+ + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[4];
+
+ /* Roll buffer and store last error */
+ ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK;
+ ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal;
+ break;
+ }
+
+ clamped = rintf(tmp);
+ if (clamped > clamp_u) {
+ clamped = clamp_u;
+ } else if (clamped < clamp_l) {
+ clamped = clamp_l;
+ }
+
+ switch (bit_depth) {
+ case GDitherFloat:
+ oflt[i] = (float) (clamped * post_scale);
+ break;
+ case GDitherDouble:
+ odbl[i] = (double) (clamped * post_scale);
+ break;
+ }
+ }
+}
+
+#define GDITHER_CONV_BLOCK 512
+
+void gdither_run(GDither s, uint32_t channel, uint32_t length,
+ double *x, void *y)
+{
+ float conv[GDITHER_CONV_BLOCK];
+ uint32_t i, pos;
+ char *ycast = (char *)y;
+
+ int step;
+
+ switch (s->bit_depth) {
+ case GDither8bit:
+ step = 1;
+ break;
+ case GDither16bit:
+ step = 2;
+ break;
+ case GDither32bit:
+ case GDitherFloat:
+ step = 4;
+ break;
+ case GDitherDouble:
+ step = 8;
+ break;
+ default:
+ step = 0;
+ break;
+ }
+
+ pos = 0;
+ while (pos < length) {
+ for (i=0; (i + pos) < length && i < GDITHER_CONV_BLOCK; i++) {
+ conv[i] = x[pos + i];
+ }
+ gdither_runf(s, channel, i, conv, ycast + s->channels * step);
+ pos += i;
+ }
+}
+
+void gdither_runf(GDither s, uint32_t channel, uint32_t length,
+ float *x, void *y)
+{
+ uint32_t pos, i;
+ float tmp;
+ int64_t clamped;
+ GDitherShapedState *ss = NULL;
+
+ if (!s || channel >= s->channels) {
+ return;
+ }
+
+ if (s->shaped_state) {
+ ss = s->shaped_state + channel;
+ }
+
+ if (s->type == GDitherNone && s->bit_depth == 23) {
+ int32_t *o32 = (int32_t*) y;
+
+ for (pos = 0; pos < length; pos++) {
+ i = channel + (pos * s->channels);
+ tmp = x[i] * 8388608.0f;
+
+ clamped = lrintf(tmp);
+ if (clamped > 8388607) {
+ clamped = 8388607;
+ } else if (clamped < -8388608) {
+ clamped = -8388608;
+ }
+
+ o32[i] = (int32_t) (clamped * 256);
+ }
+
+ return;
+ }
+
+ /* some common case handling code - looks a bit wierd, but it allows
+ * the compiler to optiomise out the branches in the inner loop */
+ if (s->bit_depth == 8 && s->dither_depth == 8) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL, NULL, x, y,
+ MAX_U8, MIN_U8);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL, NULL, x, y,
+ MAX_U8, MIN_U8);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, s->tri_state,
+ NULL, x, y, MAX_U8, MIN_U8);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL,
+ ss, x, y, MAX_U8, MIN_U8);
+ break;
+ }
+ } else if (s->bit_depth == s->dither_depth == 16) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, NULL, NULL, x, y,
+ MAX_S16, MIN_S16);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, NULL, NULL, x, y,
+ MAX_S16, MIN_S16);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, s->tri_state,
+ NULL, x, y, MAX_S16, MIN_S16);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 0.0f,
+ SCALE_S16, 1, 16, channel, length, NULL,
+ ss, x, y, MAX_S16, MIN_S16);
+ break;
+ }
+ } else if (s->bit_depth == 32 && s->dither_depth == 24) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, NULL, NULL, x,
+ y, MAX_S24, MIN_S24);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, NULL, NULL, x,
+ y, MAX_S24, MIN_S24);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, s->tri_state,
+ NULL, x, y, MAX_S24, MIN_S24);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length,
+ NULL, ss, x, y, MAX_S24, MIN_S24);
+ break;
+ }
+ } else if (s->bit_depth == GDitherFloat || s->bit_depth == GDitherDouble) {
+ gdither_innner_loop_fp(s->type, s->channels, s->bias, s->scale,
+ s->post_scale_fp, s->bit_depth, channel, length,
+ s->tri_state, ss, x, y, s->clamp_u, s->clamp_l);
+ } else {
+ /* no special case handling, just process it from the struct */
+
+ gdither_innner_loop(s->type, s->channels, s->bias, s->scale,
+ s->post_scale, s->bit_depth, channel,
+ length, s->tri_state, ss, x, y, s->clamp_u,
+ s->clamp_l);
+ }
+}
+
+/* vi:set ts=8 sts=4 sw=4: */
diff --git a/libs/ardour/gettext.h b/libs/ardour/gettext.h
new file mode 100644
index 0000000000..339c74ffe7
--- /dev/null
+++ b/libs/ardour/gettext.h
@@ -0,0 +1,82 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 2, 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+# include <libintl.h>
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is OK. */
+#if defined(__sun)
+# include <locale.h>
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+
+/* other headers may have included libintl.h */
+
+# undef gettext
+# undef dgettext
+# undef dcgettext
+# undef ngettext
+# undef dngettext
+# undef dcngettext
+# undef textdomain
+# undef bindtextdomain
+# undef bind_textdomain_codeset
+
+# define gettext(Msgid) ((const char *) (Msgid))
+# define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+# define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define textdomain(Domainname) ((const char *) (Domainname))
+# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname))
+# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc
new file mode 100644
index 0000000000..4dd640593d
--- /dev/null
+++ b/libs/ardour/globals.cc
@@ -0,0 +1,440 @@
+/*
+ Copyright (C) 2000 Paul Davis
+ Copyright (C) 2005 Sampo Savolainen
+
+ 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.
+
+ $Id$
+*/
+
+#include <cstdio> // Needed so that libraptor (included in lrdf) won't complain
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <locale.h>
+
+#ifdef VST_SUPPORT
+#include <fst.h>
+#endif
+
+#include <lrdf.h>
+
+#include <pbd/error.h>
+
+#include <midi++/port.h>
+#include <midi++/port_request.h>
+#include <midi++/manager.h>
+#include <midi++/mmc.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audio_library.h>
+#include <ardour/configuration.h>
+#include <ardour/plugin_manager.h>
+#include <ardour/source.h>
+#include <ardour/utils.h>
+#include <ardour/session.h>
+
+#include <ardour/mix.h>
+
+#if defined (__APPLE__)
+ #include <Carbon/Carbon.h> // For Gestalt
+#endif
+
+#include "i18n.h"
+
+ARDOUR::Configuration* ARDOUR::Config = 0;
+ARDOUR::AudioLibrary* ARDOUR::Library = 0;
+
+using namespace ARDOUR;
+using namespace std;
+
+MIDI::Port *default_mmc_port = 0;
+MIDI::Port *default_mtc_port = 0;
+MIDI::Port *default_midi_port = 0;
+
+Change ARDOUR::StartChanged = ARDOUR::new_change ();
+Change ARDOUR::LengthChanged = ARDOUR::new_change ();
+Change ARDOUR::PositionChanged = ARDOUR::new_change ();
+Change ARDOUR::NameChanged = ARDOUR::new_change ();
+Change ARDOUR::BoundsChanged = Change (0); // see init(), below
+
+
+static int
+setup_midi ()
+{
+ std::map<string,Configuration::MidiPortDescriptor*>::iterator i;
+ int nports;
+
+ if ((nports = Config->midi_ports.size()) == 0) {
+ warning << _("no MIDI ports specified: no MMC or MTC control possible") << endmsg;
+ return 0;
+ }
+
+ for (i = Config->midi_ports.begin(); i != Config->midi_ports.end(); ++i) {
+ Configuration::MidiPortDescriptor* port_descriptor;
+
+ port_descriptor = (*i).second;
+
+ MIDI::PortRequest request (port_descriptor->device,
+ port_descriptor->tag,
+ port_descriptor->mode,
+ port_descriptor->type);
+
+ if (request.status != MIDI::PortRequest::OK) {
+ error << compose(_("MIDI port specifications for \"%1\" are not understandable."), port_descriptor->tag) << endmsg;
+ continue;
+ }
+
+ MIDI::Manager::instance()->add_port (request);
+ }
+
+ if (nports > 1) {
+
+ /* More than one port, so try using specific names for each port */
+
+ map<string,Configuration::MidiPortDescriptor *>::iterator i;
+
+ if (Config->get_mmc_port_name() != N_("default")) {
+ default_mmc_port = MIDI::Manager::instance()->port (Config->get_mmc_port_name());
+ }
+
+ if (Config->get_mtc_port_name() != N_("default")) {
+ default_mtc_port = MIDI::Manager::instance()->port (Config->get_mtc_port_name());
+ }
+
+ if (Config->get_midi_port_name() != N_("default")) {
+ default_midi_port = MIDI::Manager::instance()->port (Config->get_midi_port_name());
+ }
+
+ /* If that didn't work, just use the first listed port */
+
+ if (default_mmc_port == 0) {
+ default_mmc_port = MIDI::Manager::instance()->port (0);
+ }
+
+ if (default_mtc_port == 0) {
+ default_mtc_port = MIDI::Manager::instance()->port (0);
+ }
+
+ if (default_midi_port == 0) {
+ default_midi_port = MIDI::Manager::instance()->port (0);
+ }
+
+ } else {
+
+ /* Only one port described, so use it for both MTC and MMC */
+
+ default_mmc_port = MIDI::Manager::instance()->port (0);
+ default_mtc_port = default_mmc_port;
+ default_midi_port = default_mmc_port;
+ }
+
+ if (default_mmc_port == 0) {
+ warning << compose (_("No MMC control (MIDI port \"%1\" not available)"), Config->get_mmc_port_name())
+ << endmsg;
+ return 0;
+ }
+
+ if (default_mtc_port == 0) {
+ warning << compose (_("No MTC support (MIDI port \"%1\" not available)"), Config->get_mtc_port_name())
+ << endmsg;
+ }
+
+ if (default_midi_port == 0) {
+ warning << compose (_("No MIDI parameter support (MIDI port \"%1\" not available)"), Config->get_midi_port_name())
+ << endmsg;
+ }
+
+ return 0;
+}
+
+int
+ARDOUR::init (AudioEngine& engine, bool use_vst, bool try_optimization, void (*sighandler)(int,siginfo_t*,void*))
+{
+ bool generic_mix_functions = true;
+
+ (void) bindtextdomain(PACKAGE, LOCALEDIR);
+
+ Config = new Configuration;
+
+ if (Config->load_state ()) {
+ return -1;
+ }
+
+ Config->set_use_vst (use_vst);
+
+ if (setup_midi ()) {
+ return -1;
+ }
+
+#ifdef VST_SUPPORT
+ if (Config->get_use_vst() && fst_init (sighandler)) {
+ return -1;
+ }
+#endif
+
+ if (try_optimization) {
+
+#if defined (ARCH_X86) && defined (BUILD_SSE_OPTIMIZATIONS)
+
+ unsigned int use_sse = 0;
+
+ asm volatile (
+ "mov $1, %%eax\n"
+ "pushl %%ebx\n"
+ "cpuid\n"
+ "popl %%ebx\n"
+ "andl $33554432, %%edx\n"
+ "movl %%edx, %0\n"
+ : "=m" (use_sse)
+ :
+ : "%eax", "%ecx", "%edx", "memory");
+
+ if (use_sse) {
+ cerr << "Enabling SSE optimized routines" << endl;
+
+ // SSE SET
+ Session::compute_peak = x86_sse_compute_peak;
+ Session::apply_gain_to_buffer = x86_sse_apply_gain_to_buffer;
+ Session::mix_buffers_with_gain = x86_sse_mix_buffers_with_gain;
+ Session::mix_buffers_no_gain = x86_sse_mix_buffers_no_gain;
+
+ generic_mix_functions = false;
+
+ }
+
+ #elif defined (__APPLE__)
+ long sysVersion = 0;
+
+ if (noErr != Gestalt(gestaltSystemVersion, &sysVersion))
+ sysVersion = 0;
+
+ if (sysVersion >= 0x00001040) { // Tiger at least
+ Session::compute_peak = veclib_compute_peak;
+ Session::apply_gain_to_buffer = veclib_apply_gain_to_buffer;
+ Session::mix_buffers_with_gain = veclib_mix_buffers_with_gain;
+ Session::mix_buffers_no_gain = veclib_mix_buffers_no_gain;
+
+ generic_mix_functions = false;
+
+ info << "Apple VecLib H/W specific optimizations in use" << endmsg;
+ }
+#endif
+ }
+
+ if (generic_mix_functions) {
+
+ Session::compute_peak = compute_peak;
+ Session::apply_gain_to_buffer = apply_gain_to_buffer;
+ Session::mix_buffers_with_gain = mix_buffers_with_gain;
+ Session::mix_buffers_no_gain = mix_buffers_no_gain;
+
+ info << "No H/W specific optimizations in use" << endmsg;
+ }
+
+ lrdf_init();
+ Library = new AudioLibrary;
+
+ /* singleton - first object is "it" */
+ new PluginManager (engine);
+
+ BoundsChanged = Change (StartChanged|PositionChanged|LengthChanged);
+
+ return 0;
+}
+
+int
+ARDOUR::cleanup ()
+{
+ delete Library;
+ lrdf_cleanup ();
+ return 0;
+}
+
+ARDOUR::id_t
+ARDOUR::new_id ()
+{
+ return get_uid();
+}
+
+ARDOUR::Change
+ARDOUR::new_change ()
+{
+ Change c;
+ static uint32_t change_bit = 1;
+
+ /* XXX catch out-of-range */
+
+ c = Change (change_bit);
+ change_bit <<= 1;
+
+ return c;
+}
+
+static string
+find_file (string name, string dir)
+{
+ string path;
+
+ /* stop A: ~/.ardour/... */
+
+ path = getenv ("HOME");
+
+ if (path.length()) {
+
+ path += "/.ardour/";
+
+ /* try to ensure that the directory exists.
+ failure doesn't mean much here.
+ */
+
+ mkdir (path.c_str(), 0755);
+
+ path += name;
+
+ if (access (path.c_str(), R_OK) == 0) {
+ return path;
+ }
+ }
+
+ /* stop B: dir/... */
+
+ path = dir;
+ path += "/ardour/";
+ path += name;
+
+ if (access (path.c_str(), R_OK) == 0) {
+ return path;
+ }
+
+ return "";
+}
+
+string
+ARDOUR::find_config_file (string name)
+{
+ char* envvar;
+ if ((envvar = getenv("ARDOUR_CONFIG_PATH")) == 0) {
+ envvar = CONFIG_DIR;
+ }
+
+ return find_file (name, envvar);
+}
+
+string
+ARDOUR::find_data_file (string name)
+{
+ char* envvar;
+ if ((envvar = getenv("ARDOUR_DATA_PATH")) == 0) {
+ envvar = DATA_DIR;
+ }
+
+ return find_file (name, envvar);
+}
+
+ARDOUR::LocaleGuard::LocaleGuard (const char* str)
+{
+ old = strdup (setlocale (LC_NUMERIC, NULL));
+ if (strcmp (old, str)) {
+ setlocale (LC_NUMERIC, str);
+ }
+}
+
+ARDOUR::LocaleGuard::~LocaleGuard ()
+{
+ setlocale (LC_NUMERIC, old);
+ free ((char*)old);
+}
+
+ARDOUR::OverlapType
+ARDOUR::coverage (jack_nframes_t sa, jack_nframes_t ea,
+ jack_nframes_t sb, jack_nframes_t eb)
+{
+ /* OverlapType returned reflects how the second (B)
+ range overlaps the first (A).
+
+ The diagrams show various relative placements
+ of A and B for each OverlapType.
+
+ Notes:
+ Internal: the start points cannot coincide
+ External: the start and end points can coincide
+ Start: end points can coincide
+ End: start points can coincide
+
+ XXX Logically, Internal should disallow end
+ point equality.
+ */
+
+ /*
+ |--------------------| A
+ |------| B
+ |-----------------| B
+
+
+ "B is internal to A"
+
+ */
+#ifdef OLD_COVERAGE
+ if ((sb >= sa) && (eb <= ea)) {
+#else
+ if ((sb > sa) && (eb <= ea)) {
+#endif
+ return OverlapInternal;
+ }
+
+ /*
+ |--------------------| A
+ ----| B
+ -----------------------| B
+ --| B
+
+ "B overlaps the start of A"
+
+ */
+
+ if ((eb >= sa) && (eb <= ea)) {
+ return OverlapStart;
+ }
+ /*
+ |---------------------| A
+ |----------------- B
+ |----------------------- B
+ |- B
+
+ "B overlaps the end of A"
+
+ */
+ if ((sb >= sa) && (sb <= ea)) {
+ return OverlapEnd;
+ }
+ /*
+ |--------------------| A
+ -------------------------- B
+ |----------------------- B
+ ----------------------| B
+ |--------------------| B
+
+
+ "B overlaps all of A"
+ */
+ if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
+ return OverlapExternal;
+ }
+
+ return OverlapNone;
+}
+
diff --git a/libs/ardour/i18n.h b/libs/ardour/i18n.h
new file mode 100644
index 0000000000..7c79d2eb53
--- /dev/null
+++ b/libs/ardour/i18n.h
@@ -0,0 +1,11 @@
+#ifndef __i18n_h__
+#define __i18n_h__
+
+#include <pbd/compose.h>
+#include "gettext.h"
+
+#define _(Text) dgettext (PACKAGE, Text)
+#define N_(Text) gettext_noop (Text)
+#define X_(Text) (Text)
+
+#endif // __i18n_h__
diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc
new file mode 100644
index 0000000000..b9ebe5859e
--- /dev/null
+++ b/libs/ardour/import.cc
@@ -0,0 +1,380 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+#include <climits>
+#include <cerrno>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <sndfile.h>
+#include <samplerate.h>
+
+#include <pbd/basename.h>
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/filesource.h>
+#include <ardour/sndfile_helpers.h>
+#include <ardour/audioregion.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+#define BLOCKSIZE 2048U
+
+int
+Session::import_audiofile (import_status& status)
+{
+ SNDFILE *in;
+ FileSource **newfiles = 0;
+ ARDOUR::AudioRegion::SourceList sources;
+ SF_INFO info;
+ float *data = 0;
+ Sample **channel_data = 0;
+ long nfiles = 0;
+ long n;
+ string basepath;
+ string sounds_dir;
+ jack_nframes_t so_far;
+ char buf[PATH_MAX+1];
+ int ret = -1;
+ vector<AudioRegion *> new_regions;
+ vector<string> new_paths;
+ struct tm* now;
+ string tmp_convert_file;
+
+ if ((in = sf_open (status.pathname.c_str(), SFM_READ, &info)) == 0) {
+ error << compose(_("Import: cannot open input sound file \"%1\""), status.pathname) << endmsg;
+ return -1;
+ } else {
+ if ((uint32_t) info.samplerate != frame_rate()) {
+ sf_close(in);
+ status.doing_what = _("resampling audio");
+ // resample to session frame_rate
+ if (sample_rate_convert(status, status.pathname, tmp_convert_file)) {
+ if ((in = sf_open (tmp_convert_file.c_str(), SFM_READ, &info)) == 0) {
+ error << compose(_("Import: cannot open converted sound file \"%1\""), tmp_convert_file) << endmsg;
+ return -1;
+ }
+ } else if (!status.cancel){
+ // error
+ error << compose(_("Import: error while resampling sound file \"%1\""), status.pathname) << endmsg;
+ return -1;
+ } else {
+ // canceled
+ goto out;
+ }
+ }
+ }
+
+ newfiles = new FileSource *[info.channels];
+ for (n = 0; n < info.channels; ++n) {
+ newfiles[n] = 0;
+ }
+
+ sounds_dir = discover_best_sound_dir ();
+ basepath = PBD::basename_nosuffix (status.pathname);
+
+ for (n = 0; n < info.channels; ++n) {
+
+ bool goodfile = false;
+
+ do {
+ if (info.channels == 2) {
+ if (n == 0) {
+ snprintf (buf, sizeof(buf), "%s%s-L.wav", sounds_dir.c_str(), basepath.c_str());
+ } else {
+ snprintf (buf, sizeof(buf), "%s%s-R.wav", sounds_dir.c_str(), basepath.c_str());
+ }
+ } else if (info.channels > 1) {
+ snprintf (buf, sizeof(buf), "%s%s-c%lu.wav", sounds_dir.c_str(), basepath.c_str(), n+1);
+ } else {
+ snprintf (buf, sizeof(buf), "%s%s.wav", sounds_dir.c_str(), basepath.c_str());
+ }
+
+ if (::access (buf, F_OK) == 0) {
+
+ /* if the file already exists, we must come up with
+ * a new name for it. for now we just keep appending
+ * _ to basepath
+ */
+
+ basepath += "_";
+
+ } else {
+
+ goodfile = true;
+ }
+
+ } while ( !goodfile);
+
+
+ try {
+ newfiles[n] = new FileSource (buf, frame_rate());
+ }
+
+ catch (failed_constructor& err) {
+ error << compose(_("Session::import_audiofile: cannot open new file source for channel %1"), n+1) << endmsg;
+ goto out;
+ }
+
+ new_paths.push_back (buf);
+ nfiles++;
+ }
+
+
+ data = new float[BLOCKSIZE * info.channels];
+ channel_data = new Sample * [ info.channels ];
+
+ for (n = 0; n < info.channels; ++n) {
+ channel_data[n] = new Sample[BLOCKSIZE];
+ }
+
+ so_far = 0;
+
+ status.doing_what = _("converting audio");
+ status.progress = 0.0;
+
+ while (!status.cancel) {
+
+ long nread;
+ long x;
+ long chn;
+
+ if ((nread = sf_readf_float (in, data, BLOCKSIZE)) == 0) {
+ break;
+ }
+
+ /* de-interleave */
+
+ for (chn = 0; chn < info.channels; ++chn) {
+ for (x = chn, n = 0; n < nread; x += info.channels, ++n) {
+ channel_data[chn][n] = (Sample) data[x];
+ }
+ }
+
+ /* flush to disk */
+
+ for (chn = 0; chn < info.channels; ++chn) {
+ newfiles[chn]->write (channel_data[chn], nread);
+ }
+
+ so_far += nread;
+ status.progress = so_far / (float) (info.frames * info.channels);
+ }
+
+ if (status.multichan) {
+ status.doing_what = _("building region");
+ } else {
+ status.doing_what = _("building regions");
+ }
+
+ status.freeze = true;
+
+ time_t xnow;
+ time (&xnow);
+ now = localtime (&xnow);
+
+ if (status.cancel) {
+ goto out;
+ }
+
+ if (status.multichan) {
+ /* all sources are used in a single multichannel region */
+ for (n = 0; n < nfiles && !status.cancel; ++n) {
+ /* flush the final length to the header */
+ newfiles[n]->update_header(0, *now, xnow);
+ sources.push_back(newfiles[n]);
+ }
+
+ AudioRegion *r = new AudioRegion (sources, 0, newfiles[0]->length(), region_name_from_path (PBD::basename(basepath)),
+ 0, AudioRegion::Flag (AudioRegion::DefaultFlags | AudioRegion::WholeFile));
+
+ new_regions.push_back (r);
+
+ } else {
+ for (n = 0; n < nfiles && !status.cancel; ++n) {
+
+ /* flush the final length to the header */
+
+ newfiles[n]->update_header(0, *now, xnow);
+
+ /* The sources had zero-length when created, which means that the Session
+ did not bother to create whole-file AudioRegions for them. Do it now.
+ */
+
+ AudioRegion *r = new AudioRegion (*newfiles[n], 0, newfiles[n]->length(), region_name_from_path (PBD::basename (newfiles[n]->name())),
+ 0, AudioRegion::Flag (AudioRegion::DefaultFlags | AudioRegion::WholeFile | AudioRegion::Import));
+
+ new_regions.push_back (r);
+ }
+ }
+
+ /* save state so that we don't lose these new Sources */
+
+ if (!status.cancel) {
+ save_state (_name);
+ }
+
+ ret = 0;
+
+ out:
+
+ if (data) {
+ delete [] data;
+ }
+
+ if (channel_data) {
+ for (n = 0; n < info.channels; ++n) {
+ delete [] channel_data[n];
+ }
+ delete [] channel_data;
+ }
+
+ if (status.cancel) {
+ for (vector<AudioRegion *>::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
+ delete *i;
+ }
+
+ for (vector<string>::iterator i = new_paths.begin(); i != new_paths.end(); ++i) {
+ unlink ((*i).c_str());
+ }
+ }
+
+ if (newfiles) {
+ delete [] newfiles;
+ }
+
+ if (tmp_convert_file.length()) {
+ unlink(tmp_convert_file.c_str());
+ }
+
+ sf_close (in);
+ status.done = true;
+ return ret;
+}
+
+string
+Session::build_tmp_convert_name(string infile)
+{
+ string tmp_name(_path + "/." + PBD::basename (infile.c_str()) + "XXXXXX");
+ char* tmp = new char[tmp_name.length() + 1];
+ tmp_name.copy(tmp, string::npos);
+ tmp[tmp_name.length()] = 0;
+ mkstemp(tmp);
+ string outfile = tmp;
+ delete [] tmp;
+
+ return outfile;
+}
+
+bool
+Session::sample_rate_convert (import_status& status, string infile, string& outfile)
+{
+ float input [BLOCKSIZE] ;
+ float output [BLOCKSIZE] ;
+
+ SF_INFO sf_info;
+ SRC_STATE* src_state ;
+ SRC_DATA src_data ;
+ int err ;
+ sf_count_t output_count = 0 ;
+ sf_count_t input_count = 0;
+
+ SNDFILE* in = sf_open(infile.c_str(), SFM_READ, &sf_info);
+ sf_count_t total_input_frames = sf_info.frames;
+
+ outfile = build_tmp_convert_name(infile);
+ SNDFILE* out = sf_open(outfile.c_str(), SFM_RDWR, &sf_info);
+ if(!out) {
+ error << compose(_("Import: could not open temp file: %1"), outfile) << endmsg;
+ return false;
+ }
+
+ sf_seek (in, 0, SEEK_SET) ;
+ sf_seek (out, 0, SEEK_SET) ;
+
+ /* Initialize the sample rate converter. */
+ if ((src_state = src_new (SRC_SINC_BEST_QUALITY, sf_info.channels, &err)) == 0) {
+ error << compose(_("Import: src_new() failed : %1"), src_strerror (err)) << endmsg ;
+ return false ;
+ }
+
+ src_data.end_of_input = 0 ; /* Set this later. */
+
+ /* Start with zero to force load in while loop. */
+ src_data.input_frames = 0 ;
+ src_data.data_in = input ;
+
+ src_data.src_ratio = (1.0 * frame_rate()) / sf_info.samplerate ;
+
+ src_data.data_out = output ;
+ src_data.output_frames = BLOCKSIZE / sf_info.channels ;
+
+ while (!status.cancel) {
+ /* If the input buffer is empty, refill it. */
+ if (src_data.input_frames == 0) {
+ src_data.input_frames = sf_readf_float (in, input, BLOCKSIZE / sf_info.channels) ;
+ src_data.data_in = input ;
+
+ /* The last read will not be a full buffer, so snd_of_input. */
+ if (src_data.input_frames < (int)BLOCKSIZE / sf_info.channels) {
+ src_data.end_of_input = SF_TRUE ;
+ }
+ }
+
+ if ((err = src_process (src_state, &src_data))) {
+ error << compose(_("Import: %1"), src_strerror (err)) << endmsg ;
+ return false ;
+ }
+
+ /* Terminate if at end */
+ if (src_data.end_of_input && src_data.output_frames_gen == 0) {
+ break ;
+ }
+
+ /* Write output. */
+ sf_writef_float (out, output, src_data.output_frames_gen) ;
+ output_count += src_data.output_frames_gen ;
+ input_count += src_data.input_frames_used;
+
+ src_data.data_in += src_data.input_frames_used * sf_info.channels ;
+ src_data.input_frames -= src_data.input_frames_used ;
+
+ status.progress = (float) input_count / total_input_frames;
+ }
+
+ src_state = src_delete (src_state) ;
+ sf_close(in);
+ sf_close(out);
+
+ status.done = true;
+
+ if (status.cancel) {
+ return false;
+ } else {
+ return true ;
+ }
+}
diff --git a/libs/ardour/insert.cc b/libs/ardour/insert.cc
new file mode 100644
index 0000000000..80bc0ce862
--- /dev/null
+++ b/libs/ardour/insert.cc
@@ -0,0 +1,1026 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <string>
+
+#include <sigc++/bind.h>
+
+#include <pbd/failed_constructor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/insert.h>
+#include <ardour/plugin.h>
+#include <ardour/port.h>
+#include <ardour/route.h>
+#include <ardour/ladspa_plugin.h>
+#include <ardour/vst_plugin.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+Insert::Insert(Session& s, Placement p)
+ : Redirect (s, s.next_insert_name(), p)
+{
+}
+
+
+Insert::Insert(Session& s, Placement p, int imin, int imax, int omin, int omax)
+ : Redirect (s, s.next_insert_name(), p, imin, imax, omin, omax)
+{
+}
+
+Insert::Insert(Session& s, string name, Placement p)
+ : Redirect (s, name, p)
+{
+}
+
+/***************************************************************
+ Plugin inserts: send data through a plugin
+ ***************************************************************/
+
+const string PluginInsert::port_automation_node_name = "PortAutomation";
+
+PluginInsert::PluginInsert (Session& s, Plugin& plug, Placement placement)
+ : Insert (s, plug.name(), placement)
+{
+ /* the first is the master */
+
+ _plugins.push_back(&plug);
+
+ _plugins[0]->ParameterChanged.connect (mem_fun (*this, &PluginInsert::parameter_changed));
+
+ init ();
+
+ save_state (_("initial state"));
+
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+ IO::MoreOutputs (output_streams ());
+ }
+
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+PluginInsert::PluginInsert (Session& s, const XMLNode& node)
+ : Insert (s, "will change", PreFader)
+{
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ set_automatable ();
+
+ save_state (_("initial state"));
+
+ _plugins[0]->ParameterChanged.connect (mem_fun (*this, &PluginInsert::parameter_changed));
+
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+ IO::MoreOutputs (output_streams());
+ }
+}
+
+PluginInsert::PluginInsert (const PluginInsert& other)
+ : Insert (other._session, other.plugin().name(), other.placement())
+{
+ uint32_t count = other._plugins.size();
+
+ /* make as many copies as requested */
+ for (uint32_t n = 0; n < count; ++n) {
+ _plugins.push_back (plugin_factory (other.plugin()));
+ }
+
+
+ _plugins[0]->ParameterChanged.connect (mem_fun (*this, &PluginInsert::parameter_changed));
+
+ init ();
+
+ save_state (_("initial state"));
+
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+int
+PluginInsert::set_count (uint32_t num)
+{
+ bool require_state = !_plugins.empty();
+
+ /* this is a bad idea.... we shouldn't do this while active.
+ only a route holding their redirect_lock should be calling this
+ */
+
+ if (num == 0) {
+ return -1;
+ } else if (num > _plugins.size()) {
+ uint32_t diff = num - _plugins.size();
+
+ for (uint32_t n = 0; n < diff; ++n) {
+ _plugins.push_back (plugin_factory (*_plugins[0]));
+
+ if (require_state) {
+ /* XXX do something */
+ }
+ }
+
+ } else if (num < _plugins.size()) {
+ uint32_t diff = _plugins.size() - num;
+ for (uint32_t n= 0; n < diff; ++n) {
+ Plugin * plug = _plugins.back();
+ _plugins.pop_back();
+ delete plug;
+ }
+ }
+
+ return 0;
+}
+
+void
+PluginInsert::init ()
+{
+ set_automatable ();
+
+ set<uint32_t>::iterator s;
+}
+
+PluginInsert::~PluginInsert ()
+{
+ GoingAway (this); /* EMIT SIGNAL */
+
+ while (!_plugins.empty()) {
+ Plugin* p = _plugins.back();
+ _plugins.pop_back();
+ delete p;
+ }
+}
+
+void
+PluginInsert::automation_list_creation_callback (uint32_t which, AutomationList& alist)
+{
+ alist.automation_state_changed.connect (sigc::bind (mem_fun (*this, &PluginInsert::auto_state_changed), (which)));
+}
+
+void
+PluginInsert::auto_state_changed (uint32_t which)
+{
+ AutomationList& alist (automation_list (which));
+
+ if (alist.automation_state() != Off) {
+ _plugins[0]->set_parameter (which, alist.eval (_session.transport_frame()));
+ }
+}
+
+uint32_t
+PluginInsert::output_streams() const
+{
+ return _plugins[0]->get_info().n_outputs * _plugins.size();
+}
+
+uint32_t
+PluginInsert::input_streams() const
+{
+ return _plugins[0]->get_info().n_inputs * _plugins.size();
+}
+
+uint32_t
+PluginInsert::natural_output_streams() const
+{
+ return _plugins[0]->get_info().n_outputs;
+}
+
+uint32_t
+PluginInsert::natural_input_streams() const
+{
+ return _plugins[0]->get_info().n_inputs;
+}
+
+bool
+PluginInsert::is_generator() const
+{
+ /* XXX more finesse is possible here. VST plugins have a
+ a specific "instrument" flag, for example.
+ */
+
+ return _plugins[0]->get_info().n_inputs == 0;
+}
+
+void
+PluginInsert::set_automatable ()
+{
+ set<uint32_t> a;
+
+ a = _plugins.front()->automatable ();
+
+ for (set<uint32_t>::iterator i = a.begin(); i != a.end(); ++i) {
+ can_automate (*i);
+ }
+}
+
+void
+PluginInsert::parameter_changed (uint32_t which, float val)
+{
+ vector<Plugin*>::iterator i = _plugins.begin();
+
+ /* don't set the first plugin, just all the slaves */
+
+ if (i != _plugins.end()) {
+ ++i;
+ for (; i != _plugins.end(); ++i) {
+ (*i)->set_parameter (which, val);
+ }
+ }
+}
+
+void
+PluginInsert::set_block_size (jack_nframes_t nframes)
+{
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ (*i)->set_block_size (nframes);
+ }
+}
+
+void
+PluginInsert::activate ()
+{
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ (*i)->activate ();
+ }
+}
+
+void
+PluginInsert::deactivate ()
+{
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ (*i)->deactivate ();
+ }
+}
+
+void
+PluginInsert::connect_and_run (vector<Sample*>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset, bool with_auto, jack_nframes_t now)
+{
+ int32_t in_index = 0;
+ int32_t out_index = 0;
+
+ /* Note that we've already required that plugins
+ be able to handle in-place processing.
+ */
+
+ if (with_auto) {
+
+ map<uint32_t,AutomationList*>::iterator li;
+ uint32_t n;
+
+ for (n = 0, li = parameter_automation.begin(); li != parameter_automation.end(); ++li, ++n) {
+
+ AutomationList& alist (*((*li).second));
+
+ if (alist.automation_playback()) {
+ bool valid;
+
+ float val = alist.rt_safe_eval (now, valid);
+
+ if (valid) {
+ /* set the first plugin, the others will be set via signals */
+ _plugins[0]->set_parameter ((*li).first, val);
+ }
+
+ }
+ }
+ }
+
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ (*i)->connect_and_run (bufs, nbufs, in_index, out_index, nframes, offset);
+ }
+
+ /* leave remaining channel buffers alone */
+}
+
+void
+PluginInsert::automation_snapshot (jack_nframes_t now)
+{
+ map<uint32_t,AutomationList*>::iterator li;
+
+ for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) {
+
+ AutomationList& alist (*((*li).second));
+ if (alist.automation_write ()) {
+
+ float val = _plugins[0]->get_parameter ((*li).first);
+ alist.rt_add (now, val);
+ last_automation_snapshot = now;
+ }
+ }
+}
+
+void
+PluginInsert::transport_stopped (jack_nframes_t now)
+{
+ map<uint32_t,AutomationList*>::iterator li;
+
+ for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) {
+ AutomationList& alist (*(li->second));
+ alist.reposition_for_rt_add (now);
+
+ if (alist.automation_state() != Off) {
+ _plugins[0]->set_parameter (li->first, alist.eval (now));
+ }
+ }
+}
+
+void
+PluginInsert::silence (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ int32_t in_index = 0;
+ int32_t out_index = 0;
+
+ uint32_t n;
+
+ if (active()) {
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ n = (*i) -> get_info().n_inputs;
+ (*i)->connect_and_run (_session.get_silent_buffers (n), n, in_index, out_index, nframes, offset);
+ }
+ }
+}
+
+void
+PluginInsert::run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ if (active()) {
+
+ if (_session.transport_rolling()) {
+ automation_run (bufs, nbufs, nframes, offset);
+ } else {
+ connect_and_run (bufs, nbufs, nframes, offset, false);
+ }
+ } else {
+ uint32_t in = _plugins[0]->get_info().n_inputs;
+ uint32_t out = _plugins[0]->get_info().n_outputs;
+
+ if (out > in) {
+
+ /* not active, but something has make up for any channel count increase */
+
+ for (uint32_t n = out - in; n < out; ++n) {
+ memcpy (bufs[n], bufs[in - 1], sizeof (Sample) * nframes);
+ }
+ }
+ }
+}
+
+void
+PluginInsert::set_parameter (uint32_t port, float val)
+{
+ /* the others will be set from the event triggered by this */
+
+ _plugins[0]->set_parameter (port, val);
+
+ if (automation_list (port).automation_write()) {
+ automation_list (port).add (_session.audible_frame(), val);
+ }
+
+ _session.set_dirty();
+}
+
+void
+PluginInsert::automation_run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ ControlEvent next_event (0, 0.0f);
+ jack_nframes_t now = _session.transport_frame ();
+ jack_nframes_t end = now + nframes;
+
+ TentativeLockMonitor lm (_automation_lock, __LINE__, __FILE__);
+
+ if (!lm.locked()) {
+ connect_and_run (bufs, nbufs, nframes, offset, false);
+ return;
+ }
+
+ if (!find_next_event (now, end, next_event)) {
+
+ /* no events have a time within the relevant range */
+
+ connect_and_run (bufs, nbufs, nframes, offset, true, now);
+ return;
+ }
+
+ while (nframes) {
+
+ jack_nframes_t cnt = min (((jack_nframes_t) floor (next_event.when) - now), nframes);
+
+ connect_and_run (bufs, nbufs, cnt, offset, true, now);
+
+ nframes -= cnt;
+ offset += cnt;
+ now += cnt;
+
+ if (!find_next_event (now, end, next_event)) {
+ break;
+ }
+ }
+
+ /* cleanup anything that is left to do */
+
+ if (nframes) {
+ connect_and_run (bufs, nbufs, nframes, offset, true, now);
+ }
+}
+
+float
+PluginInsert::default_parameter_value (uint32_t port)
+{
+ if (_plugins.empty()) {
+ fatal << _("programming error: ") << X_("PluginInsert::default_parameter_value() called with no plugin")
+ << endmsg;
+ /*NOTREACHED*/
+ }
+
+ return _plugins[0]->default_value (port);
+}
+
+void
+PluginInsert::set_port_automation_state (uint32_t port, AutoState s)
+{
+ if (port < _plugins[0]->parameter_count()) {
+
+ AutomationList& al = automation_list (port);
+
+ if (s != al.automation_state()) {
+ al.set_automation_state (s);
+ last_automation_snapshot = 0;
+ _session.set_dirty ();
+ }
+ }
+}
+
+AutoState
+PluginInsert::get_port_automation_state (uint32_t port)
+{
+ if (port < _plugins[0]->parameter_count()) {
+ return automation_list (port).automation_state();
+ } else {
+ return Off;
+ }
+}
+
+void
+PluginInsert::protect_automation ()
+{
+ set<uint32_t> automated_params;
+
+ what_has_automation (automated_params);
+
+ for (set<uint32_t>::iterator i = automated_params.begin(); i != automated_params.end(); ++i) {
+
+ AutomationList& al = automation_list (*i);
+
+ switch (al.automation_state()) {
+ case Write:
+ case Touch:
+ al.set_automation_state (Off);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+Plugin*
+PluginInsert::plugin_factory (Plugin& other)
+{
+ LadspaPlugin* lp;
+#ifdef VST_SUPPORT
+ VSTPlugin* vp;
+#endif
+
+ if ((lp = dynamic_cast<LadspaPlugin*> (&other)) != 0) {
+ return new LadspaPlugin (*lp);
+#ifdef VST_SUPPORT
+ } else if ((vp = dynamic_cast<VSTPlugin*> (&other)) != 0) {
+ return new VSTPlugin (*vp);
+#endif
+ }
+
+ fatal << compose (_("programming error: %1"),
+ X_("unknown plugin type in PluginInsert::plugin_factory"))
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+}
+
+int32_t
+PluginInsert::compute_output_streams (int32_t cnt) const
+{
+ return _plugins[0]->get_info().n_outputs * cnt;
+}
+
+int32_t
+PluginInsert::configure_io (int32_t magic, int32_t in, int32_t out)
+{
+ return set_count (magic);
+}
+
+int32_t
+PluginInsert::can_support_input_configuration (int32_t in) const
+{
+ int32_t outputs = _plugins[0]->get_info().n_outputs;
+ int32_t inputs = _plugins[0]->get_info().n_inputs;
+
+ if (inputs == 0) {
+
+ /* instrument plugin, always legal, but it throws
+ away any existing active streams.
+ */
+
+ return 1;
+ }
+
+ if (outputs == 1 && inputs == 1) {
+ /* mono plugin, replicate as needed */
+ return in;
+ }
+
+ if (inputs == in) {
+ /* exact match */
+ return 1;
+ }
+
+ if ((inputs < in) && (inputs % in == 0)) {
+
+ /* number of inputs is a factor of the requested input
+ configuration, so we can replicate.
+ */
+
+ return in/inputs;
+ }
+
+ /* sorry */
+
+ return -1;
+}
+
+XMLNode&
+PluginInsert::get_state(void)
+{
+ return state (true);
+}
+
+XMLNode&
+PluginInsert::state (bool full)
+{
+ char buf[256];
+ XMLNode *node = new XMLNode("Insert");
+
+ node->add_child_nocopy (Redirect::state (full));
+
+ node->add_property ("type", _plugins[0]->state_node_name());
+ snprintf(buf, sizeof(buf), "%s", _plugins[0]->name());
+ node->add_property("id", string(buf));
+ node->add_property("count", compose("%1", _plugins.size()));
+ node->add_child_nocopy (_plugins[0]->get_state());
+
+ /* add port automation state */
+ XMLNode *autonode = new XMLNode(port_automation_node_name);
+ set<uint32_t> automatable = _plugins[0]->automatable();
+
+ for (set<uint32_t>::iterator x = automatable.begin(); x != automatable.end(); ++x) {
+
+ XMLNode* child = new XMLNode("port");
+ snprintf(buf, sizeof(buf), "%" PRIu32, *x);
+ child->add_property("number", string(buf));
+
+ if (full) {
+ snprintf(buf, sizeof(buf), "0x%x", automation_list (*x).automation_state ());
+ } else {
+ snprintf(buf, sizeof(buf), "0x%x", ARDOUR::Off);
+ }
+ child->add_property("auto", string(buf));
+
+ autonode->add_child_nocopy (*child);
+ }
+
+ node->add_child_nocopy (*autonode);
+
+ return *node;
+}
+
+int
+PluginInsert::set_state(const XMLNode& node)
+{
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+ XMLPropertyList plist;
+ const XMLProperty *prop;
+ PluginInfo::Type type;
+
+ if ((prop = node.property ("type")) == 0) {
+ error << _("XML node describing insert is missing the `type' field") << endmsg;
+ return -1;
+ }
+
+ if (prop->value() == X_("ladspa") || prop->value() == X_("Ladspa")) { /* handle old school sessions */
+ type = PluginInfo::LADSPA;
+ } else if (prop->value() == X_("vst")) {
+ type = PluginInfo::VST;
+ } else {
+ error << compose (_("unknown plugin type %1 in plugin insert state"),
+ prop->value())
+ << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("id")) == 0) {
+ error << _("XML node describing insert is missing the `id' field") << endmsg;
+ return -1;
+ }
+
+ Plugin* plugin;
+
+ if ((plugin = find_plugin (_session, prop->value(), type)) == 0) {
+ error << compose(_("Found a reference to a plugin (\"%1\") that is unknown.\n"
+ "Perhaps it was removed or moved since it was last used."), prop->value())
+ << endmsg;
+ return -1;
+ }
+
+ uint32_t count = 1;
+
+ if ((prop = node.property ("count")) != 0) {
+ sscanf (prop->value().c_str(), "%u", &count);
+ }
+
+ if (_plugins.size() != count) {
+
+ _plugins.push_back (plugin);
+
+ for (uint32_t n=1; n < count; ++n) {
+ _plugins.push_back (plugin_factory (*plugin));
+ }
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == plugin->state_node_name()) {
+ for (vector<Plugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ (*i)->set_state (**niter);
+ }
+ break;
+ }
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == Redirect::state_node_name) {
+ Redirect::set_state (**niter);
+ break;
+ }
+ }
+
+ if (niter == nlist.end()) {
+ error << _("XML node describing insert is missing a Redirect node") << endmsg;
+ return -1;
+ }
+
+ if (niter == nlist.end()) {
+ error << compose(_("XML node describing a plugin insert is missing the `%1' information"), plugin->state_node_name()) << endmsg;
+ return -1;
+ }
+
+ /* look for port automation node */
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == port_automation_node_name) {
+ XMLNodeList cnodes;
+ XMLProperty *cprop;
+ XMLNodeConstIterator iter;
+ XMLNode *child;
+ const char *port;
+ uint32_t port_id;
+
+ cnodes = (*niter)->children ("port");
+
+ for(iter = cnodes.begin(); iter != cnodes.end(); ++iter){
+
+ child = *iter;
+
+ if ((cprop = child->property("number")) != 0) {
+ port = cprop->value().c_str();
+ } else {
+ warning << _("PluginInsert: Auto: no ladspa port number") << endmsg;
+ continue;
+ }
+
+ sscanf (port, "%" PRIu32, &port_id);
+
+ if (port_id >= _plugins[0]->parameter_count()) {
+ warning << _("PluginInsert: Auto: port id out of range") << endmsg;
+ continue;
+ }
+
+ if ((cprop = child->property("auto")) != 0) {
+ int x;
+ sscanf (cprop->value().c_str(), "0x%x", &x);
+ automation_list (port_id).set_automation_state (AutoState (x));
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (niter == nlist.end()) {
+ warning << compose(_("XML node describing a port automation is missing the `%1' information"), port_automation_node_name) << endmsg;
+ }
+
+
+ return 0;
+}
+
+string
+PluginInsert::describe_parameter (uint32_t what)
+{
+ return _plugins[0]->describe_parameter (what);
+}
+
+void
+PluginInsert::reset_midi_control (MIDI::Port* port, bool on)
+{
+ _plugins[0]->reset_midi_control (port, on);
+}
+
+void
+PluginInsert::send_all_midi_feedback ()
+{
+ _plugins[0]->send_all_midi_feedback();
+}
+
+jack_nframes_t
+PluginInsert::latency()
+{
+ return _plugins[0]->latency ();
+}
+
+void
+PluginInsert::store_state (PluginInsertState& state) const
+{
+ Redirect::store_state (state);
+ _plugins[0]->store_state (state.plugin_state);
+}
+
+Change
+PluginInsert::restore_state (StateManager::State& state)
+{
+ PluginInsertState* pistate = dynamic_cast<PluginInsertState*> (&state);
+
+ Redirect::restore_state (state);
+
+ _plugins[0]->restore_state (pistate->plugin_state);
+
+ return Change (0);
+}
+
+StateManager::State*
+PluginInsert::state_factory (std::string why) const
+{
+ PluginInsertState* state = new PluginInsertState (why);
+
+ store_state (*state);
+
+ return state;
+}
+
+/***************************************************************
+ Port inserts: send output to a port, pick up input at a port
+ ***************************************************************/
+
+PortInsert::PortInsert (Session& s, Placement p)
+ : Insert (s, p, 1, -1, 1, -1)
+{
+ init ();
+ save_state (_("initial state"));
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+PortInsert::PortInsert (const PortInsert& other)
+ : Insert (other._session, other.placement(), 1, -1, 1, -1)
+{
+ init ();
+ save_state (_("initial state"));
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+void
+PortInsert::init ()
+{
+ if (add_input_port ("", this)) {
+ error << _("PortInsert: cannot add input port") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (add_output_port ("", this)) {
+ error << _("PortInsert: cannot add output port") << endmsg;
+ throw failed_constructor();
+ }
+}
+
+PortInsert::PortInsert (Session& s, const XMLNode& node)
+ : Insert (s, "will change", PreFader)
+{
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+PortInsert::~PortInsert ()
+{
+ GoingAway (this);
+}
+
+void
+PortInsert::run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ if (n_outputs() == 0) {
+ return;
+ }
+
+ if (!active()) {
+ /* deliver silence */
+ silence (nframes, offset);
+ return;
+ }
+
+ uint32_t n;
+ vector<Port*>::iterator o;
+ vector<Port*>::iterator i;
+
+ /* deliver output */
+
+ for (o = _outputs.begin(), n = 0; o != _outputs.end(); ++o, ++n) {
+ memcpy ((*o)->get_buffer (nframes) + offset, bufs[min(nbufs,n)], sizeof (Sample) * nframes);
+ }
+
+ /* collect input */
+
+ for (i = _inputs.begin(), n = 0; i != _inputs.end(); ++i, ++n) {
+ memcpy (bufs[min(nbufs,n)], (*i)->get_buffer (nframes) + offset, sizeof (Sample) * nframes);
+ }
+}
+
+XMLNode&
+PortInsert::get_state(void)
+{
+ return state (true);
+}
+
+XMLNode&
+PortInsert::state (bool full)
+{
+ XMLNode *node = new XMLNode("Insert");
+
+ node->add_child_nocopy (Redirect::state(full));
+ node->add_property("type", "port");
+
+ return *node;
+}
+
+int
+PortInsert::set_state(const XMLNode& node)
+{
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+ XMLPropertyList plist;
+ const XMLProperty *prop;
+
+ if ((prop = node.property ("type")) == 0) {
+ error << _("XML node describing insert is missing the `type' field") << endmsg;
+ return -1;
+ }
+
+ if (prop->value() != "port") {
+ error << _("non-port insert XML used for port plugin insert") << endmsg;
+ return -1;
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == Redirect::state_node_name) {
+ Redirect::set_state (**niter);
+ break;
+ }
+ }
+
+ if (niter == nlist.end()) {
+ error << _("XML node describing insert is missing a Redirect node") << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+jack_nframes_t
+PortInsert::latency()
+{
+ /* because we deliver and collect within the same cycle,
+ all I/O is necessarily delayed by at least frames_per_cycle().
+
+ if the return port for insert has its own latency, we
+ need to take that into account too.
+ */
+
+ return _session.engine().frames_per_cycle() + input_latency();
+}
+
+int32_t
+PortInsert::can_support_input_configuration (int32_t in) const
+{
+ if (input_maximum() == -1 && output_maximum() == -1) {
+
+ /* not configured yet */
+
+ return 1; /* we can support anything the first time we're asked */
+
+ } else {
+
+ /* the "input" config for a port insert corresponds to how
+ many output ports it will have.
+ */
+
+ if (output_maximum() == in) {
+ return 1;
+ }
+ }
+
+ return -1;
+}
+
+int32_t
+PortInsert::configure_io (int32_t ignored_magic, int32_t in, int32_t out)
+{
+ /* do not allow configuration to be changed outside the range of
+ the last request config. or something like that.
+ */
+
+
+ /* this is a bit odd:
+
+ the number of inputs we are required to handle corresponds
+ to the number of output ports we need.
+
+ the number of outputs we are required to have corresponds
+ to the number of input ports we need.
+ */
+
+ set_output_maximum (in);
+ set_output_minimum (in);
+ set_input_maximum (out);
+ set_input_minimum (out);
+
+ if (in < 0) {
+ in = n_outputs ();
+ }
+
+ if (out < 0) {
+ out = n_inputs ();
+ }
+
+ return ensure_io (out, in, false, this);
+}
+
+int32_t
+PortInsert::compute_output_streams (int32_t cnt) const
+{
+ /* puzzling, eh? think about it ... */
+ return n_inputs ();
+}
+
+uint32_t
+PortInsert::output_streams() const
+{
+ return n_inputs ();
+}
+
+uint32_t
+PortInsert::input_streams() const
+{
+ return n_outputs ();
+}
diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc
new file mode 100644
index 0000000000..efee6fc397
--- /dev/null
+++ b/libs/ardour/io.cc
@@ -0,0 +1,2821 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <fstream>
+#include <algorithm>
+#include <unistd.h>
+#include <locale.h>
+
+#include <sigc++/bind.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/audioengine.h>
+#include <ardour/io.h>
+#include <ardour/port.h>
+#include <ardour/connection.h>
+#include <ardour/session.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/panner.h>
+#include <ardour/dB.h>
+
+#include "i18n.h"
+
+#include <cmath>
+
+/*
+ A bug in OS X's cmath that causes isnan() and isinf() to be
+ "undeclared". the following works around that
+*/
+
+#if defined(__APPLE__) && defined(__MACH__)
+extern "C" int isnan (double);
+extern "C" int isinf (double);
+#endif
+
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+static float current_automation_version_number = 1.0;
+
+jack_nframes_t IO::_automation_interval = 0;
+const string IO::state_node_name = "IO";
+bool IO::connecting_legal = false;
+bool IO::ports_legal = false;
+bool IO::panners_legal = false;
+sigc::signal<void> IO::GrabPeakPower;
+sigc::signal<int> IO::ConnectingLegal;
+sigc::signal<int> IO::PortsLegal;
+sigc::signal<int> IO::PannersLegal;
+sigc::signal<void,uint32_t> IO::MoreOutputs;
+sigc::signal<int> IO::PortsCreated;
+
+/* this is a default mapper of MIDI control values to a gain coefficient.
+ others can be imagined. see IO::set_midi_to_gain_function().
+*/
+
+static gain_t direct_midi_to_gain (double fract) {
+ /* XXX Marcus writes: this doesn't seem right to me. but i don't have a better answer ... */
+ /* this maxes at +6dB */
+ return pow (2.0,(sqrt(sqrt(sqrt(fract)))*198.0-192.0)/6.0);
+}
+
+static double direct_gain_to_midi (gain_t gain) {
+ /* XXX Marcus writes: this doesn't seem right to me. but i don't have a better answer ... */
+ if (gain == 0) return 0.0;
+
+ return pow((6.0*log(gain)/log(2.0)+192.0)/198.0, 8.0);
+}
+
+static bool sort_ports_by_name (Port* a, Port* b)
+{
+ return a->name() < b->name();
+}
+
+
+IO::IO (Session& s, string name,
+
+ int input_min, int input_max, int output_min, int output_max)
+ : _session (s),
+ _name (name),
+ _midi_gain_control (*this, _session.midi_port()),
+ _gain_automation_curve (0.0, 2.0, 1.0),
+ _input_minimum (input_min),
+ _input_maximum (input_max),
+ _output_minimum (output_min),
+ _output_maximum (output_max)
+{
+ _id = new_id();
+ _panner = new Panner (name, _session);
+ _gain = 1.0;
+ _desired_gain = 1.0;
+ _input_connection = 0;
+ _output_connection = 0;
+ pending_state_node = 0;
+ _ninputs = 0;
+ _noutputs = 0;
+ no_panner_reset = false;
+ deferred_state = 0;
+
+ _midi_gain_control.midi_to_gain = direct_midi_to_gain;
+ _midi_gain_control.gain_to_midi = direct_gain_to_midi;
+
+ apply_gain_automation = false;
+
+ last_automation_snapshot = 0;
+
+ _gain_automation_state = Off;
+ _gain_automation_style = Absolute;
+
+ GrabPeakPower.connect (mem_fun (*this, &IO::grab_peak_power));
+}
+
+IO::~IO ()
+{
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+ vector<Port *>::iterator i;
+
+ for (i = _inputs.begin(); i != _inputs.end(); ++i) {
+ _session.engine().unregister_port (*i);
+ }
+
+ for (i = _outputs.begin(); i != _outputs.end(); ++i) {
+ _session.engine().unregister_port (*i);
+ }
+}
+
+void
+IO::silence (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ (*i)->silence (nframes, offset);
+ }
+}
+
+void
+IO::apply_declick (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, gain_t initial, gain_t target, bool invert_polarity)
+{
+ jack_nframes_t declick = min ((jack_nframes_t)4096, nframes);
+ gain_t delta;
+ Sample *buffer;
+ double fractional_shift;
+ double fractional_pos;
+
+ fractional_shift = -1.0/declick;
+
+ if (target < initial) {
+ /* fade out: remove more and more of delta from initial */
+ delta = -(initial - target);
+ } else {
+ /* fade in: add more and more of delta from initial */
+ delta = target - initial;
+ }
+
+ for (uint32_t n = 0; n < nbufs; ++n) {
+
+ buffer = bufs[n];
+ fractional_pos = 1.0;
+
+ if (invert_polarity) {
+ for (jack_nframes_t nx = 0; nx < declick; ++nx) {
+ buffer[nx] *= -(initial + (delta * (0.5 + 0.5 * cos (M_PI * fractional_pos))));
+ fractional_pos += fractional_shift;
+ }
+ } else {
+ for (jack_nframes_t nx = 0; nx < declick; ++nx) {
+ buffer[nx] *= (initial + (delta * (0.5 + 0.5 * cos (M_PI * fractional_pos))));
+ fractional_pos += fractional_shift;
+ }
+ }
+
+ /* now ensure the rest of the buffer has the target value
+ applied, if necessary.
+ */
+
+ if (declick != nframes) {
+
+ if (invert_polarity) {
+ target = -target;
+ }
+
+ if (target == 0.0) {
+ memset (&buffer[declick], 0, sizeof (Sample) * (nframes - declick));
+ } else if (target != 1.0) {
+ for (jack_nframes_t nx = declick; nx < nframes; ++nx) {
+ buffer[nx] *= target;
+ }
+ }
+ }
+ }
+}
+
+void
+IO::pan_automated (vector<Sample*>& bufs, uint32_t nbufs, jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ Sample* dst;
+
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ if (_noutputs == 0) {
+ return;
+ }
+
+ if (_noutputs == 1) {
+
+ dst = output(0)->get_buffer (nframes) + offset;
+
+ for (uint32_t n = 0; n < nbufs; ++n) {
+ if (bufs[n] != dst) {
+ memcpy (dst, bufs[n], sizeof (Sample) * nframes);
+ }
+ }
+
+ output(0)->mark_silence (false);
+
+ return;
+ }
+
+ uint32_t o;
+ vector<Port *>::iterator out;
+ vector<Sample *>::iterator in;
+ Panner::iterator pan;
+ Sample* obufs[_noutputs];
+
+ /* the terrible silence ... */
+
+ for (out = _outputs.begin(), o = 0; out != _outputs.end(); ++out, ++o) {
+ obufs[o] = (*out)->get_buffer (nframes) + offset;
+ memset (obufs[o], 0, sizeof (Sample) * nframes);
+ (*out)->mark_silence (false);
+ }
+
+ uint32_t n;
+
+ for (pan = _panner->begin(), n = 0; n < nbufs; ++n, ++pan) {
+ (*pan)->distribute_automated (bufs[n], obufs, start, end, nframes, _session.pan_automation_buffer());
+ }
+}
+
+void
+IO::pan (vector<Sample*>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset, gain_t gain_coeff)
+{
+ Sample* dst;
+ Sample* src;
+
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ if (_noutputs == 0) {
+ return;
+ }
+
+ /* the panner can be empty if there are no inputs to the
+ route, but still outputs
+ */
+
+ if (_panner->bypassed() || _panner->empty()) {
+ deliver_output_no_pan (bufs, nbufs, nframes, offset);
+ return;
+ }
+
+ if (_noutputs == 1) {
+
+ dst = output(0)->get_buffer (nframes) + offset;
+
+ if (gain_coeff == 0.0f) {
+
+ /* only one output, and gain was zero, so make it silent */
+
+ memset (dst, 0, sizeof (Sample) * nframes);
+
+ } else if (gain_coeff == 1.0f){
+
+ /* mix all buffers into the output */
+
+ uint32_t n;
+
+ memcpy (dst, bufs[0], sizeof (Sample) * nframes);
+
+ for (n = 1; n < nbufs; ++n) {
+ src = bufs[n];
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n];
+ }
+ }
+
+ output(0)->mark_silence (false);
+
+ } else {
+
+ /* mix all buffers into the output, scaling them all by the gain */
+
+ uint32_t n;
+
+ src = bufs[0];
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] = src[n] * gain_coeff;
+ }
+
+ for (n = 1; n < nbufs; ++n) {
+ src = bufs[n];
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n] * gain_coeff;
+ }
+ }
+
+ output(0)->mark_silence (false);
+ }
+
+ return;
+ }
+
+ uint32_t o;
+ vector<Port *>::iterator out;
+ vector<Sample *>::iterator in;
+ Panner::iterator pan;
+ Sample* obufs[_noutputs];
+
+ /* the terrible silence ... */
+
+ /* XXX this is wasteful but i see no way to avoid it */
+
+ for (out = _outputs.begin(), o = 0; out != _outputs.end(); ++out, ++o) {
+ obufs[o] = (*out)->get_buffer (nframes) + offset;
+ memset (obufs[o], 0, sizeof (Sample) * nframes);
+ (*out)->mark_silence (false);
+ }
+
+ uint32_t n;
+
+ for (pan = _panner->begin(), n = 0; n < nbufs; ++n) {
+ Panner::iterator tmp;
+
+ tmp = pan;
+ ++tmp;
+
+ (*pan)->distribute (bufs[n], obufs, gain_coeff, nframes);
+
+ if (tmp != _panner->end()) {
+ pan = tmp;
+ }
+ }
+}
+
+void
+IO::deliver_output (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ if (_noutputs == 0) {
+ return;
+ }
+
+ if (_panner->bypassed()) {
+ deliver_output_no_pan (bufs, nbufs, nframes, offset);
+ return;
+ }
+
+
+ gain_t dg;
+
+ {
+ TentativeLockMonitor dm (declick_lock, __LINE__, __FILE__);
+
+ if (dm.locked()) {
+ dg = _desired_gain;
+ } else {
+ dg = _gain;
+ }
+ }
+
+ if (dg != _gain) {
+ apply_declick (bufs, nbufs, nframes, _gain, dg, false);
+ _gain = dg;
+ }
+
+ /* simple, non-automation panning to outputs */
+
+ if (_session.transport_speed() > 1.5f || _session.transport_speed() < -1.5f) {
+ pan (bufs, nbufs, nframes, offset, _gain * speed_quietning);
+ } else {
+ pan (bufs, nbufs, nframes, offset, _gain);
+ }
+}
+
+void
+IO::deliver_output_no_pan (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ if (_noutputs == 0) {
+ return;
+ }
+
+ gain_t dg;
+ gain_t old_gain;
+
+ if (apply_gain_automation) {
+
+ /* gain has already been applied by automation code. do nothing here except
+ speed quietning.
+ */
+
+ old_gain = _gain;
+ _gain = 1.0f;
+ dg = _gain;
+
+ } else {
+
+ TentativeLockMonitor dm (declick_lock, __LINE__, __FILE__);
+
+ if (dm.locked()) {
+ dg = _desired_gain;
+ } else {
+ dg = _gain;
+ }
+ }
+
+ Sample* src;
+ Sample* dst;
+ uint32_t i;
+ vector<Port*>::iterator o;
+ vector<Sample*> outs;
+ gain_t actual_gain;
+
+ if (dg != _gain) {
+ /* unlikely condition */
+ for (o = _outputs.begin(), i = 0; o != _outputs.end(); ++o, ++i) {
+ outs.push_back ((*o)->get_buffer (nframes) + offset);
+ }
+ }
+
+ /* reduce nbufs to the index of the last input buffer */
+
+ nbufs--;
+
+ if (_session.transport_speed() > 1.5f || _session.transport_speed() < -1.5f) {
+ actual_gain = _gain * speed_quietning;
+ } else {
+ actual_gain = _gain;
+ }
+
+ for (o = _outputs.begin(), i = 0; o != _outputs.end(); ++o, ++i) {
+
+ dst = (*o)->get_buffer (nframes) + offset;
+ src = bufs[min(nbufs,i)];
+
+ if (dg != _gain || actual_gain == 1.0f) {
+ memcpy (dst, src, sizeof (Sample) * nframes);
+ } else if (actual_gain == 0.0f) {
+ memset (dst, 0, sizeof (Sample) * nframes);
+ } else {
+ for (jack_nframes_t x = 0; x < nframes; ++x) {
+ dst[x] = src[x] * actual_gain;
+ }
+ }
+
+ (*o)->mark_silence (false);
+ }
+
+ if (dg != _gain) {
+ apply_declick (outs, outs.size(), nframes, _gain, dg, false);
+ _gain = dg;
+ }
+
+ if (apply_gain_automation) {
+ _gain = old_gain;
+ }
+}
+
+void
+IO::collect_input (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ /* io_lock, not taken: function must be called from Session::process() calltree */
+
+ vector<Port *>::iterator i;
+ uint32_t n;
+ Sample *last = 0;
+
+ /* we require that bufs.size() >= 1 */
+
+ for (n = 0, i = _inputs.begin(); n < nbufs; ++i, ++n) {
+ if (i == _inputs.end()) {
+ break;
+ }
+
+ /* XXX always read the full extent of the port buffer that
+ we need. One day, we may use jack_port_get_buffer_at_offset()
+ or something similar. For now, this simple hack will
+ have to do.
+
+ Hack? Why yes .. we only need to read nframes-worth of
+ data, but the data we want is at `offset' within the
+ buffer.
+ */
+
+ last = (*i)->get_buffer (nframes+offset) + offset;
+ // the dest buffer's offset has already been applied
+ memcpy (bufs[n], last, sizeof (Sample) * nframes);
+ }
+
+ /* fill any excess outputs with the last input */
+
+ while (n < nbufs && last) {
+ // the dest buffer's offset has already been applied
+ memcpy (bufs[n], last, sizeof (Sample) * nframes);
+ ++n;
+ }
+}
+
+void
+IO::just_meter_input (jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset)
+{
+ vector<Sample*>& bufs = _session.get_passthru_buffers ();
+ uint32_t nbufs = n_process_buffers ();
+
+ collect_input (bufs, nbufs, nframes, offset);
+
+ for (uint32_t n = 0; n < nbufs; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+}
+
+void
+IO::drop_input_connection ()
+{
+ _input_connection = 0;
+ input_connection_configuration_connection.disconnect();
+ input_connection_connection_connection.disconnect();
+ _session.set_dirty ();
+}
+
+void
+IO::drop_output_connection ()
+{
+ _output_connection = 0;
+ output_connection_configuration_connection.disconnect();
+ output_connection_connection_connection.disconnect();
+ _session.set_dirty ();
+}
+
+int
+IO::disconnect_input (Port* our_port, string other_port, void* src)
+{
+ if (other_port.length() == 0 || our_port == 0) {
+ return 0;
+ }
+
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ /* check that our_port is really one of ours */
+
+ if (find (_inputs.begin(), _inputs.end(), our_port) == _inputs.end()) {
+ return -1;
+ }
+
+ /* disconnect it from the source */
+
+ if (_session.engine().disconnect (other_port, our_port->name())) {
+ error << compose(_("IO: cannot disconnect input port %1 from %2"), our_port->name(), other_port) << endmsg;
+ return -1;
+ }
+
+ drop_input_connection();
+ }
+ }
+
+ input_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+
+ return 0;
+}
+
+int
+IO::connect_input (Port* our_port, string other_port, void* src)
+{
+ if (other_port.length() == 0 || our_port == 0) {
+ return 0;
+ }
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ /* check that our_port is really one of ours */
+
+ if (find (_inputs.begin(), _inputs.end(), our_port) == _inputs.end()) {
+ return -1;
+ }
+
+ /* connect it to the source */
+
+ if (_session.engine().connect (other_port, our_port->name())) {
+ return -1;
+ }
+
+ drop_input_connection ();
+ }
+ }
+
+ input_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+}
+
+int
+IO::disconnect_output (Port* our_port, string other_port, void* src)
+{
+ if (other_port.length() == 0 || our_port == 0) {
+ return 0;
+ }
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ if (find (_outputs.begin(), _outputs.end(), our_port) == _outputs.end()) {
+ return -1;
+ }
+
+ /* disconnect it from the destination */
+
+ if (_session.engine().disconnect (our_port->name(), other_port)) {
+ error << compose(_("IO: cannot disconnect output port %1 from %2"), our_port->name(), other_port) << endmsg;
+ return -1;
+ }
+
+ drop_output_connection ();
+ }
+ }
+
+ output_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+}
+
+int
+IO::connect_output (Port* our_port, string other_port, void* src)
+{
+ if (other_port.length() == 0 || our_port == 0) {
+ return 0;
+ }
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ /* check that our_port is really one of ours */
+
+ if (find (_outputs.begin(), _outputs.end(), our_port) == _outputs.end()) {
+ return -1;
+ }
+
+ /* connect it to the destination */
+
+ if (_session.engine().connect (our_port->name(), other_port)) {
+ return -1;
+ }
+
+ drop_output_connection ();
+ }
+ }
+
+ output_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+}
+
+int
+IO::set_input (Port* other_port, void* src)
+{
+ /* this removes all but one ports, and connects that one port
+ to the specified source.
+ */
+
+ if (_input_minimum > 1 || _input_minimum == 0) {
+ /* sorry, you can't do this */
+ return -1;
+ }
+
+ if (other_port == 0) {
+ if (_input_minimum < 0) {
+ return ensure_inputs (0, false, true, src);
+ } else {
+ return -1;
+ }
+ }
+
+ if (ensure_inputs (1, true, true, src)) {
+ return -1;
+ }
+
+ return connect_input (_inputs.front(), other_port->name(), src);
+}
+
+int
+IO::remove_output_port (Port* port, void* src)
+{
+ IOChange change (NoChange);
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ if (_noutputs - 1 == (uint32_t) _output_minimum) {
+ /* sorry, you can't do this */
+ return -1;
+ }
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ if (*i == port) {
+ change = IOChange (change|ConfigurationChanged);
+ if (port->connected()) {
+ change = IOChange (change|ConnectionsChanged);
+ }
+
+ _session.engine().unregister_port (*i);
+ _outputs.erase (i);
+ _noutputs--;
+ drop_output_connection ();
+
+ break;
+ }
+ }
+
+ if (change != NoChange) {
+ setup_peak_meters ();
+ reset_panner ();
+ }
+ }
+ }
+
+ if (change != NoChange) {
+ output_changed (change, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+IO::add_output_port (string destination, void* src)
+{
+ Port* our_port;
+ char buf[64];
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ if (_output_maximum >= 0 && (int) _noutputs == _output_maximum) {
+ return -1;
+ }
+
+ /* Create a new output port */
+
+ if (_output_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/out"), _name.c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("%s/out %u"), _name.c_str(), find_output_port_hole());
+ }
+
+ if ((our_port = _session.engine().register_audio_output_port (buf)) == 0) {
+ error << compose(_("IO: cannot register output port %1"), buf) << endmsg;
+ return -1;
+ }
+
+ _outputs.push_back (our_port);
+ sort (_outputs.begin(), _outputs.end(), sort_ports_by_name);
+ ++_noutputs;
+ drop_output_connection ();
+ setup_peak_meters ();
+ reset_panner ();
+ }
+
+ MoreOutputs (_noutputs); /* EMIT SIGNAL */
+ }
+
+ if (destination.length()) {
+ if (_session.engine().connect (our_port->name(), destination)) {
+ return -1;
+ }
+ }
+
+ // pan_changed (src); /* EMIT SIGNAL */
+ output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+}
+
+int
+IO::remove_input_port (Port* port, void* src)
+{
+ IOChange change (NoChange);
+
+ {
+ LockMonitor em(_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ if (((int)_ninputs - 1) < _input_minimum) {
+ /* sorry, you can't do this */
+ return -1;
+ }
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+
+ if (*i == port) {
+ change = IOChange (change|ConfigurationChanged);
+
+ if (port->connected()) {
+ change = IOChange (change|ConnectionsChanged);
+ }
+
+ _session.engine().unregister_port (*i);
+ _inputs.erase (i);
+ _ninputs--;
+ drop_input_connection ();
+
+ break;
+ }
+ }
+
+ if (change != NoChange) {
+ setup_peak_meters ();
+ reset_panner ();
+ }
+ }
+ }
+
+ if (change != NoChange) {
+ input_changed (change, src);
+ _session.set_dirty ();
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+IO::add_input_port (string source, void* src)
+{
+ Port* our_port;
+ char buf[64];
+
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ if (_input_maximum >= 0 && (int) _ninputs == _input_maximum) {
+ return -1;
+ }
+
+ /* Create a new input port */
+
+ if (_input_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/in"), _name.c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("%s/in %u"), _name.c_str(), find_input_port_hole());
+ }
+
+ if ((our_port = _session.engine().register_audio_input_port (buf)) == 0) {
+ error << compose(_("IO: cannot register input port %1"), buf) << endmsg;
+ return -1;
+ }
+
+ _inputs.push_back (our_port);
+ sort (_inputs.begin(), _inputs.end(), sort_ports_by_name);
+ ++_ninputs;
+ drop_input_connection ();
+ setup_peak_meters ();
+ reset_panner ();
+ }
+
+ MoreOutputs (_ninputs); /* EMIT SIGNAL */
+ }
+
+ if (source.length()) {
+
+ if (_session.engine().connect (source, our_port->name())) {
+ return -1;
+ }
+ }
+
+ // pan_changed (src); /* EMIT SIGNAL */
+ input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+
+ return 0;
+}
+
+int
+IO::disconnect_inputs (void* src)
+{
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+
+ drop_input_connection ();
+ }
+ }
+ input_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+IO::disconnect_outputs (void* src)
+{
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+
+ {
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+
+ drop_output_connection ();
+ }
+ }
+
+ output_changed (ConnectionsChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ return 0;
+}
+
+bool
+IO::ensure_inputs_locked (uint32_t n, bool clear, void* src)
+{
+ Port* input_port;
+ bool changed = false;
+ bool reduced = false;
+
+ /* remove unused ports */
+
+ while (_ninputs > n) {
+ _session.engine().unregister_port (_inputs.back());
+ _inputs.pop_back();
+ _ninputs--;
+ reduced = true;
+ changed = true;
+ }
+
+ /* create any necessary new ports */
+
+ while (_ninputs < n) {
+
+ char buf[64];
+
+ /* Create a new input port */
+
+ if (_input_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/in"), _name.c_str());
+ }
+ else {
+ snprintf (buf, sizeof (buf), _("%s/in %u"), _name.c_str(), find_input_port_hole());
+ }
+
+ try {
+
+ if ((input_port = _session.engine().register_audio_input_port (buf)) == 0) {
+ error << compose(_("IO: cannot register input port %1"), buf) << endmsg;
+ return -1;
+ }
+ }
+
+ catch (AudioEngine::PortRegistrationFailure& err) {
+ setup_peak_meters ();
+ reset_panner ();
+ /* pass it on */
+ throw err;
+ }
+
+ _inputs.push_back (input_port);
+ sort (_inputs.begin(), _inputs.end(), sort_ports_by_name);
+ ++_ninputs;
+ changed = true;
+ }
+
+ if (changed) {
+ drop_input_connection ();
+ setup_peak_meters ();
+ reset_panner ();
+ MoreOutputs (_ninputs); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+
+ if (clear) {
+ /* disconnect all existing ports so that we get a fresh start */
+
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+ }
+
+ return changed;
+}
+
+int
+IO::ensure_io (uint32_t nin, uint32_t nout, bool clear, void* src)
+{
+ bool in_changed = false;
+ bool out_changed = false;
+ bool in_reduced = false;
+ bool out_reduced = false;
+ bool need_pan_reset;
+
+ if (_input_maximum >= 0) {
+ nin = min (_input_maximum, (int) nin);
+ }
+
+ if (_output_maximum >= 0) {
+ nout = min (_output_maximum, (int) nout);
+ }
+
+ if (nin == _ninputs && nout == _noutputs && !clear) {
+ return 0;
+ }
+
+ {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ Port* port;
+
+ if (_noutputs == nout) {
+ need_pan_reset = false;
+ } else {
+ need_pan_reset = true;
+ }
+
+ /* remove unused ports */
+
+ while (_ninputs > nin) {
+ _session.engine().unregister_port (_inputs.back());
+ _inputs.pop_back();
+ _ninputs--;
+ in_reduced = true;
+ in_changed = true;
+ }
+
+ while (_noutputs > nout) {
+ _session.engine().unregister_port (_outputs.back());
+ _outputs.pop_back();
+ _noutputs--;
+ out_reduced = true;
+ out_changed = true;
+ }
+
+ /* create any necessary new ports */
+
+ while (_ninputs < nin) {
+
+ char buf[64];
+
+ /* Create a new input port */
+
+ if (_input_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/in"), _name.c_str());
+ }
+ else {
+ snprintf (buf, sizeof (buf), _("%s/in %u"), _name.c_str(), find_input_port_hole());
+ }
+
+ try {
+ if ((port = _session.engine().register_audio_input_port (buf)) == 0) {
+ error << compose(_("IO: cannot register input port %1"), buf) << endmsg;
+ return -1;
+ }
+ }
+
+ catch (AudioEngine::PortRegistrationFailure& err) {
+ setup_peak_meters ();
+ reset_panner ();
+ /* pass it on */
+ throw err;
+ }
+
+ _inputs.push_back (port);
+ ++_ninputs;
+ in_changed = true;
+ }
+
+ /* create any necessary new ports */
+
+ while (_noutputs < nout) {
+
+ char buf[64];
+
+ /* Create a new output port */
+
+ if (_output_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/out"), _name.c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("%s/out %u"), _name.c_str(), find_output_port_hole());
+ }
+
+ try {
+ if ((port = _session.engine().register_audio_output_port (buf)) == 0) {
+ error << compose(_("IO: cannot register output port %1"), buf) << endmsg;
+ return -1;
+ }
+ }
+
+ catch (AudioEngine::PortRegistrationFailure& err) {
+ setup_peak_meters ();
+ reset_panner ();
+ /* pass it on */
+ throw err;
+ }
+
+ _outputs.push_back (port);
+ ++_noutputs;
+ out_changed = true;
+ }
+
+ if (clear) {
+
+ /* disconnect all existing ports so that we get a fresh start */
+
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+ }
+ }
+
+ if (in_changed || out_changed) {
+ setup_peak_meters ();
+ reset_panner ();
+ }
+
+ if (out_changed) {
+ sort (_outputs.begin(), _outputs.end(), sort_ports_by_name);
+ drop_output_connection ();
+ output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ }
+
+ if (in_changed) {
+ sort (_inputs.begin(), _inputs.end(), sort_ports_by_name);
+ drop_input_connection ();
+ input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ }
+
+ if (in_changed || out_changed) {
+ MoreOutputs (max (_noutputs, _ninputs)); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+
+ return 0;
+}
+
+int
+IO::ensure_inputs (uint32_t n, bool clear, bool lockit, void* src)
+{
+ bool changed = false;
+
+ if (_input_maximum >= 0) {
+ n = min (_input_maximum, (int) n);
+
+ if (n == _ninputs && !clear) {
+ return 0;
+ }
+ }
+
+ if (lockit) {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+ changed = ensure_inputs_locked (n, clear, src);
+ } else {
+ changed = ensure_inputs_locked (n, clear, src);
+ }
+
+ if (changed) {
+ input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+
+ return 0;
+}
+
+bool
+IO::ensure_outputs_locked (uint32_t n, bool clear, void* src)
+{
+ Port* output_port;
+ bool changed = false;
+ bool reduced = false;
+ bool need_pan_reset;
+
+ if (_noutputs == n) {
+ need_pan_reset = false;
+ } else {
+ need_pan_reset = true;
+ }
+
+ /* remove unused ports */
+
+ while (_noutputs > n) {
+
+ _session.engine().unregister_port (_outputs.back());
+ _outputs.pop_back();
+ _noutputs--;
+ reduced = true;
+ changed = true;
+ }
+
+ /* create any necessary new ports */
+
+ while (_noutputs < n) {
+
+ char buf[64];
+
+ /* Create a new output port */
+
+ if (_output_maximum == 1) {
+ snprintf (buf, sizeof (buf), _("%s/out"), _name.c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("%s/out %u"), _name.c_str(), find_output_port_hole());
+ }
+
+ if ((output_port = _session.engine().register_audio_output_port (buf)) == 0) {
+ error << compose(_("IO: cannot register output port %1"), buf) << endmsg;
+ return -1;
+ }
+
+ _outputs.push_back (output_port);
+ sort (_outputs.begin(), _outputs.end(), sort_ports_by_name);
+ ++_noutputs;
+ changed = true;
+ setup_peak_meters ();
+
+ if (need_pan_reset) {
+ reset_panner ();
+ }
+ }
+
+ if (changed) {
+ drop_output_connection ();
+ MoreOutputs (_noutputs); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+
+ if (clear) {
+ /* disconnect all existing ports so that we get a fresh start */
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ _session.engine().disconnect (*i);
+ }
+ }
+
+ return changed;
+}
+
+int
+IO::ensure_outputs (uint32_t n, bool clear, bool lockit, void* src)
+{
+ bool changed = false;
+
+ if (_output_maximum >= 0) {
+ n = min (_output_maximum, (int) n);
+ if (n == _noutputs && !clear) {
+ return 0;
+ }
+ }
+
+ /* XXX caller should hold io_lock, but generally doesn't */
+
+ if (lockit) {
+ LockMonitor em (_session.engine().process_lock(), __LINE__, __FILE__);
+ changed = ensure_outputs_locked (n, clear, src);
+ } else {
+ changed = ensure_outputs_locked (n, clear, src);
+ }
+
+ if (changed) {
+ output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
+ }
+
+ return 0;
+}
+
+gain_t
+IO::effective_gain () const
+{
+ if (gain_automation_playback()) {
+ return _effective_gain;
+ } else {
+ return _desired_gain;
+ }
+}
+
+void
+IO::reset_panner ()
+{
+ if (panners_legal) {
+ if (!no_panner_reset) {
+ _panner->reset (_noutputs, pans_required());
+ }
+ } else {
+ panner_legal_c.disconnect ();
+ panner_legal_c = PannersLegal.connect (mem_fun (*this, &IO::panners_became_legal));
+ }
+}
+
+int
+IO::panners_became_legal ()
+{
+ _panner->reset (_noutputs, pans_required());
+ _panner->load (); // automation
+ panner_legal_c.disconnect ();
+ return 0;
+}
+
+void
+IO::defer_pan_reset ()
+{
+ no_panner_reset = true;
+}
+
+void
+IO::allow_pan_reset ()
+{
+ no_panner_reset = false;
+ reset_panner ();
+}
+
+
+XMLNode&
+IO::get_state (void)
+{
+ return state (true);
+}
+
+XMLNode&
+IO::state (bool full_state)
+{
+ XMLNode* node = new XMLNode (state_node_name);
+ char buf[32];
+ string str;
+ bool need_ins = true;
+ bool need_outs = true;
+ LocaleGuard lg (X_("POSIX"));
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ node->add_property("name", _name);
+ snprintf (buf, sizeof(buf), "%" PRIu64, id());
+ node->add_property("id", buf);
+
+ str = "";
+
+ if (_input_connection) {
+ node->add_property ("input-connection", _input_connection->name());
+ need_ins = false;
+ }
+
+ if (_output_connection) {
+ node->add_property ("output-connection", _output_connection->name());
+ need_outs = false;
+ }
+
+ if (need_ins) {
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+
+ const char **connections = (*i)->get_connections();
+
+ if (connections && connections[0]) {
+ str += '{';
+
+ for (int n = 0; connections && connections[n]; ++n) {
+ if (n) {
+ str += ',';
+ }
+
+ /* if its a connection to our own port,
+ return only the port name, not the
+ whole thing. this allows connections
+ to be re-established even when our
+ client name is different.
+ */
+
+ str += _session.engine().make_port_name_relative (connections[n]);
+ }
+
+ str += '}';
+
+ free (connections);
+ }
+ else {
+ str += "{}";
+ }
+ }
+
+ node->add_property ("inputs", str);
+ }
+
+ if (need_outs) {
+ str = "";
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+
+ const char **connections = (*i)->get_connections();
+
+ if (connections && connections[0]) {
+
+ str += '{';
+
+ for (int n = 0; connections[n]; ++n) {
+ if (n) {
+ str += ',';
+ }
+
+ str += _session.engine().make_port_name_relative (connections[n]);
+ }
+
+ str += '}';
+
+ free (connections);
+ }
+ else {
+ str += "{}";
+ }
+ }
+
+ node->add_property ("outputs", str);
+ }
+
+ node->add_child_nocopy (_panner->state (full_state));
+
+ snprintf (buf, sizeof(buf), "%2.12f", gain());
+ node->add_property ("gain", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%d,%d,%d,%d",
+ _input_minimum,
+ _input_maximum,
+ _output_minimum,
+ _output_maximum);
+
+ node->add_property ("iolimits", buf);
+
+ /* MIDI control */
+
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ XMLNode* midi_node = 0;
+ XMLNode* child;
+
+ if (_midi_gain_control.get_control_info (chn, ev, additional)) {
+
+ midi_node = node->add_child ("MIDI");
+
+ child = midi_node->add_child ("gain");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+
+ /* automation */
+
+ if (full_state) {
+ snprintf (buf, sizeof (buf), "0x%x", (int) _gain_automation_curve.automation_state());
+ } else {
+ /* never store anything except Off for automation state in a template */
+ snprintf (buf, sizeof (buf), "0x%x", ARDOUR::Off);
+ }
+ node->add_property ("automation-state", buf);
+ snprintf (buf, sizeof (buf), "0x%x", (int) _gain_automation_curve.automation_style());
+ node->add_property ("automation-style", buf);
+
+ /* XXX same for pan etc. */
+
+ return *node;
+}
+
+int
+IO::connecting_became_legal ()
+{
+ int ret;
+
+ if (pending_state_node == 0) {
+ fatal << _("IO::connecting_became_legal() called without a pending state node") << endmsg;
+ /*NOTREACHED*/
+ return -1;
+ }
+
+ connection_legal_c.disconnect ();
+
+ ret = make_connections (*pending_state_node);
+
+ if (ports_legal) {
+ delete pending_state_node;
+ pending_state_node = 0;
+ }
+
+ return ret;
+}
+
+int
+IO::ports_became_legal ()
+{
+ int ret;
+
+ if (pending_state_node == 0) {
+ fatal << _("IO::ports_became_legal() called without a pending state node") << endmsg;
+ /*NOTREACHED*/
+ return -1;
+ }
+
+ port_legal_c.disconnect ();
+
+ ret = create_ports (*pending_state_node);
+
+ if (connecting_legal) {
+ delete pending_state_node;
+ pending_state_node = 0;
+ }
+
+ return ret;
+}
+
+int
+IO::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ XMLNodeConstIterator iter;
+ XMLNodeList midi_kids;
+ LocaleGuard lg (X_("POSIX"));
+
+ /* force use of non-localized representation of decimal point,
+ since we use it a lot in XML files and so forth.
+ */
+
+ if (node.name() != state_node_name) {
+ error << compose(_("incorrect XML node \"%1\" passed to IO object"), node.name()) << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("name")) != 0) {
+ _name = prop->value();
+ _panner->set_name (_name);
+ }
+
+ if ((prop = node.property ("id")) != 0) {
+ sscanf (prop->value().c_str(), "%llu", &_id);
+ }
+
+ if ((prop = node.property ("iolimits")) != 0) {
+ sscanf (prop->value().c_str(), "%d,%d,%d,%d",
+ &_input_minimum,
+ &_input_maximum,
+ &_output_minimum,
+ &_output_maximum);
+ }
+
+ if ((prop = node.property ("gain")) != 0) {
+ set_gain (atof (prop->value().c_str()), this);
+ _gain = _desired_gain;
+ }
+
+ for (iter = node.children().begin(); iter != node.children().end(); ++iter) {
+ if ((*iter)->name() == "Panner") {
+ _panner->set_state (**iter);
+ }
+ }
+
+ midi_kids = node.children ("MIDI");
+
+ for (iter = midi_kids.begin(); iter != midi_kids.end(); ++iter) {
+
+ XMLNodeList kids;
+ XMLNodeConstIterator miter;
+ XMLNode* child;
+
+ kids = (*iter)->children ();
+
+ for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+ child =* miter;
+
+ if (child->name() == "gain") {
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* ditto */
+ MIDI::channel_t chn = 0; /* ditto */
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_gain_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI gain control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+ }
+ }
+
+ if ((prop = node.property ("automation-state")) != 0) {
+
+ long int x;
+ x = strtol (prop->value().c_str(), 0, 16);
+ set_gain_automation_state (AutoState (x));
+ }
+
+ if ((prop = node.property ("automation-style")) != 0) {
+
+ long int x;
+ x = strtol (prop->value().c_str(), 0, 16);
+ set_gain_automation_style (AutoStyle (x));
+ }
+
+ if (ports_legal) {
+
+ if (create_ports (node)) {
+ return -1;
+ }
+
+ } else {
+
+ port_legal_c = PortsLegal.connect (mem_fun (*this, &IO::ports_became_legal));
+ }
+
+ if (panners_legal) {
+ reset_panner ();
+ } else {
+ panner_legal_c = PannersLegal.connect (mem_fun (*this, &IO::panners_became_legal));
+ }
+
+ if (connecting_legal) {
+
+ if (make_connections (node)) {
+ return -1;
+ }
+
+ } else {
+
+ connection_legal_c = ConnectingLegal.connect (mem_fun (*this, &IO::connecting_became_legal));
+ }
+
+ if (!ports_legal || !connecting_legal) {
+ pending_state_node = new XMLNode (node);
+ }
+
+ return 0;
+}
+
+int
+IO::create_ports (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ int num_inputs = 0;
+ int num_outputs = 0;
+
+ if ((prop = node.property ("input-connection")) != 0) {
+
+ Connection* c = _session.connection_by_name (prop->value());
+
+ if (c == 0) {
+ error << compose(_("Unknown connection \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
+
+ if ((c = _session.connection_by_name (_("in 1"))) == 0) {
+ error << _("No input connections available as a replacement")
+ << endmsg;
+ return -1;
+ } else {
+ info << compose (_("Connection %1 was not available - \"in 1\" used instead"), prop->value())
+ << endmsg;
+ }
+ }
+
+ num_inputs = c->nports();
+
+ } else if ((prop = node.property ("inputs")) != 0) {
+
+ num_inputs = count (prop->value().begin(), prop->value().end(), '{');
+ }
+
+ if ((prop = node.property ("output-connection")) != 0) {
+ Connection* c = _session.connection_by_name (prop->value());
+
+ if (c == 0) {
+ error << compose(_("Unknown connection \"%1\" listed for output of %2"), prop->value(), _name) << endmsg;
+
+ if ((c = _session.connection_by_name (_("out 1"))) == 0) {
+ error << _("No output connections available as a replacement")
+ << endmsg;
+ return -1;
+ } else {
+ info << compose (_("Connection %1 was not available - \"out 1\" used instead"), prop->value())
+ << endmsg;
+ }
+ }
+
+ num_outputs = c->nports ();
+
+ } else if ((prop = node.property ("outputs")) != 0) {
+ num_outputs = count (prop->value().begin(), prop->value().end(), '{');
+ }
+
+ no_panner_reset = true;
+
+ if (ensure_io (num_inputs, num_outputs, true, this)) {
+ error << compose(_("%1: cannot create I/O ports"), _name) << endmsg;
+ return -1;
+ }
+
+ no_panner_reset = false;
+
+ set_deferred_state ();
+
+ PortsCreated();
+ return 0;
+}
+
+bool
+IO::get_midi_node_info (XMLNode * node, MIDI::eventType & ev, MIDI::channel_t & chan, MIDI::byte & additional)
+{
+ bool ok = true;
+ const XMLProperty* prop;
+ int xx;
+
+ if ((prop = node->property ("event")) != 0) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ ev = (MIDI::eventType) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = node->property ("channel")) != 0)) {
+ sscanf (prop->value().c_str(), "%d", &xx);
+ chan = (MIDI::channel_t) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = node->property ("additional")) != 0)) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ additional = (MIDI::byte) xx;
+ }
+
+ return ok;
+}
+
+bool
+IO::set_midi_node_info (XMLNode * node, MIDI::eventType ev, MIDI::channel_t chan, MIDI::byte additional)
+{
+ char buf[32];
+
+ snprintf (buf, sizeof(buf), "0x%x", ev);
+ node->add_property ("event", buf);
+ snprintf (buf, sizeof(buf), "%d", chan);
+ node->add_property ("channel", buf);
+ snprintf (buf, sizeof(buf), "0x%x", additional);
+ node->add_property ("additional", buf);
+
+ return true;
+}
+
+
+int
+IO::make_connections (const XMLNode& node)
+{
+ const XMLProperty* prop;
+
+ if ((prop = node.property ("input-connection")) != 0) {
+ Connection* c = _session.connection_by_name (prop->value());
+
+ if (c == 0) {
+ error << compose(_("Unknown connection \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
+
+ if ((c = _session.connection_by_name (_("in 1"))) == 0) {
+ error << _("No input connections available as a replacement")
+ << endmsg;
+ return -1;
+ } else {
+ info << compose (_("Connection %1 was not available - \"in 1\" used instead"), prop->value())
+ << endmsg;
+ }
+ }
+
+ use_input_connection (*c, this);
+
+ } else if ((prop = node.property ("inputs")) != 0) {
+ if (set_inputs (prop->value())) {
+ error << compose(_("improper input channel list in XML node (%1)"), prop->value()) << endmsg;
+ return -1;
+ }
+ }
+
+ if ((prop = node.property ("output-connection")) != 0) {
+ Connection* c = _session.connection_by_name (prop->value());
+
+ if (c == 0) {
+ error << compose(_("Unknown connection \"%1\" listed for output of %2"), prop->value(), _name) << endmsg;
+
+ if ((c = _session.connection_by_name (_("out 1"))) == 0) {
+ error << _("No output connections available as a replacement")
+ << endmsg;
+ return -1;
+ } else {
+ info << compose (_("Connection %1 was not available - \"out 1\" used instead"), prop->value())
+ << endmsg;
+ }
+ }
+
+ use_output_connection (*c, this);
+
+ } else if ((prop = node.property ("outputs")) != 0) {
+ if (set_outputs (prop->value())) {
+ error << compose(_("improper output channel list in XML node (%1)"), prop->value()) << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+IO::set_inputs (const string& str)
+{
+ vector<string> ports;
+ int i;
+ int n;
+ uint32_t nports;
+
+ if ((nports = count (str.begin(), str.end(), '{')) == 0) {
+ return 0;
+ }
+
+ if (ensure_inputs (nports, true, true, this)) {
+ return -1;
+ }
+
+ string::size_type start, end, ostart;
+
+ ostart = 0;
+ start = 0;
+ end = 0;
+ i = 0;
+
+ while ((start = str.find_first_of ('{', ostart)) != string::npos) {
+ start += 1;
+
+ if ((end = str.find_first_of ('}', start)) == string::npos) {
+ error << compose(_("IO: badly formed string in XML node for inputs \"%1\""), str) << endmsg;
+ return -1;
+ }
+
+ if ((n = parse_io_string (str.substr (start, end - start), ports)) < 0) {
+ error << compose(_("bad input string in XML node \"%1\""), str) << endmsg;
+
+ return -1;
+
+ } else if (n > 0) {
+
+ for (int x = 0; x < n; ++x) {
+ connect_input (input (i), ports[x], this);
+ }
+ }
+
+ ostart = end+1;
+ i++;
+ }
+
+ return 0;
+}
+
+int
+IO::set_outputs (const string& str)
+{
+ vector<string> ports;
+ int i;
+ int n;
+ uint32_t nports;
+
+ if ((nports = count (str.begin(), str.end(), '{')) == 0) {
+ return 0;
+ }
+
+ if (ensure_outputs (nports, true, true, this)) {
+ return -1;
+ }
+
+ string::size_type start, end, ostart;
+
+ ostart = 0;
+ start = 0;
+ end = 0;
+ i = 0;
+
+ while ((start = str.find_first_of ('{', ostart)) != string::npos) {
+ start += 1;
+
+ if ((end = str.find_first_of ('}', start)) == string::npos) {
+ error << compose(_("IO: badly formed string in XML node for outputs \"%1\""), str) << endmsg;
+ return -1;
+ }
+
+ if ((n = parse_io_string (str.substr (start, end - start), ports)) < 0) {
+ error << compose(_("IO: bad output string in XML node \"%1\""), str) << endmsg;
+
+ return -1;
+
+ } else if (n > 0) {
+
+ for (int x = 0; x < n; ++x) {
+ connect_output (output (i), ports[x], this);
+ }
+ }
+
+ ostart = end+1;
+ i++;
+ }
+
+ return 0;
+}
+
+int
+IO::parse_io_string (const string& str, vector<string>& ports)
+{
+ string::size_type pos, opos;
+
+ if (str.length() == 0) {
+ return 0;
+ }
+
+ pos = 0;
+ opos = 0;
+
+ ports.clear ();
+
+ while ((pos = str.find_first_of (',', opos)) != string::npos) {
+ ports.push_back (str.substr (opos, pos - opos));
+ opos = pos + 1;
+ }
+
+ if (opos < str.length()) {
+ ports.push_back (str.substr(opos));
+ }
+
+ return ports.size();
+}
+
+int
+IO::parse_gain_string (const string& str, vector<string>& ports)
+{
+ string::size_type pos, opos;
+
+ pos = 0;
+ opos = 0;
+ ports.clear ();
+
+ while ((pos = str.find_first_of (',', opos)) != string::npos) {
+ ports.push_back (str.substr (opos, pos - opos));
+ opos = pos + 1;
+ }
+
+ if (opos < str.length()) {
+ ports.push_back (str.substr(opos));
+ }
+
+ return ports.size();
+}
+
+int
+IO::set_name (string name, void* src)
+{
+ if (name == _name) {
+ return 0;
+ }
+
+ for (vector<Port *>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ string current_name = (*i)->short_name();
+ current_name.replace (current_name.find (_name), _name.length(), name);
+ (*i)->set_name (current_name);
+ }
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ string current_name = (*i)->short_name();
+ current_name.replace (current_name.find (_name), _name.length(), name);
+ (*i)->set_name (current_name);
+ }
+
+ _name = name;
+ name_changed (src); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+void
+IO::set_input_minimum (int n)
+{
+ _input_minimum = n;
+}
+
+void
+IO::set_input_maximum (int n)
+{
+ _input_maximum = n;
+}
+
+void
+IO::set_output_minimum (int n)
+{
+ _output_minimum = n;
+}
+
+void
+IO::set_output_maximum (int n)
+{
+ _output_maximum = n;
+}
+
+void
+IO::set_port_latency (jack_nframes_t nframes)
+{
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ for (vector<Port *>::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ (*i)->set_latency (nframes);
+ }
+}
+
+jack_nframes_t
+IO::output_latency () const
+{
+ jack_nframes_t max_latency;
+ jack_nframes_t latency;
+
+ max_latency = 0;
+
+ /* io lock not taken - must be protected by other means */
+
+ for (vector<Port *>::const_iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ if ((latency = _session.engine().get_port_total_latency (*(*i))) > max_latency) {
+ max_latency = latency;
+ }
+ }
+
+ return max_latency;
+}
+
+jack_nframes_t
+IO::input_latency () const
+{
+ jack_nframes_t max_latency;
+ jack_nframes_t latency;
+
+ max_latency = 0;
+
+ /* io lock not taken - must be protected by other means */
+
+ for (vector<Port *>::const_iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ if ((latency = _session.engine().get_port_total_latency (*(*i))) > max_latency) {
+ max_latency = latency;
+ }
+ }
+
+ return max_latency;
+}
+
+int
+IO::use_input_connection (Connection& c, void* src)
+{
+ uint32_t limit;
+
+ {
+ LockMonitor lm (_session.engine().process_lock(), __LINE__, __FILE__);
+ LockMonitor lm2 (io_lock, __LINE__, __FILE__);
+
+ limit = c.nports();
+
+ drop_input_connection ();
+
+ if (ensure_inputs (limit, false, false, src)) {
+ return -1;
+ }
+
+ /* first pass: check the current state to see what's correctly
+ connected, and drop anything that we don't want.
+ */
+
+ for (uint32_t n = 0; n < limit; ++n) {
+ const Connection::PortList& pl = c.port_connections (n);
+
+ for (Connection::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
+
+ if (!_inputs[n]->connected_to ((*i))) {
+
+ /* clear any existing connections */
+
+ _session.engine().disconnect (_inputs[n]);
+
+ } else if (_inputs[n]->connected() > 1) {
+
+ /* OK, it is connected to the port we want,
+ but its also connected to other ports.
+ Change that situation.
+ */
+
+ /* XXX could be optimized to not drop
+ the one we want.
+ */
+
+ _session.engine().disconnect (_inputs[n]);
+
+ }
+ }
+ }
+
+ /* second pass: connect all requested ports where necessary */
+
+ for (uint32_t n = 0; n < limit; ++n) {
+ const Connection::PortList& pl = c.port_connections (n);
+
+ for (Connection::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
+
+ if (!_inputs[n]->connected_to ((*i))) {
+
+ if (_session.engine().connect (*i, _inputs[n]->name())) {
+ return -1;
+ }
+ }
+
+ }
+ }
+
+ _input_connection = &c;
+
+ input_connection_configuration_connection = c.ConfigurationChanged.connect
+ (mem_fun (*this, &IO::input_connection_configuration_changed));
+ input_connection_connection_connection = c.ConnectionsChanged.connect
+ (mem_fun (*this, &IO::input_connection_connection_changed));
+ }
+
+ input_changed (IOChange (ConfigurationChanged|ConnectionsChanged), src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+IO::use_output_connection (Connection& c, void* src)
+{
+ uint32_t limit;
+
+ {
+ LockMonitor lm (_session.engine().process_lock(), __LINE__, __FILE__);
+ LockMonitor lm2 (io_lock, __LINE__, __FILE__);
+
+ limit = c.nports();
+
+ drop_output_connection ();
+
+ if (ensure_outputs (limit, false, false, src)) {
+ return -1;
+ }
+
+ /* first pass: check the current state to see what's correctly
+ connected, and drop anything that we don't want.
+ */
+
+ for (uint32_t n = 0; n < limit; ++n) {
+
+ const Connection::PortList& pl = c.port_connections (n);
+
+ for (Connection::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
+
+ if (!_outputs[n]->connected_to ((*i))) {
+
+ /* clear any existing connections */
+
+ _session.engine().disconnect (_outputs[n]);
+
+ } else if (_outputs[n]->connected() > 1) {
+
+ /* OK, it is connected to the port we want,
+ but its also connected to other ports.
+ Change that situation.
+ */
+
+ /* XXX could be optimized to not drop
+ the one we want.
+ */
+
+ _session.engine().disconnect (_outputs[n]);
+ }
+ }
+ }
+
+ /* second pass: connect all requested ports where necessary */
+
+ for (uint32_t n = 0; n < limit; ++n) {
+
+ const Connection::PortList& pl = c.port_connections (n);
+
+ for (Connection::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
+
+ if (!_outputs[n]->connected_to ((*i))) {
+
+ if (_session.engine().connect (_outputs[n]->name(), *i)) {
+ return -1;
+ }
+ }
+ }
+ }
+
+ _output_connection = &c;
+
+ output_connection_configuration_connection = c.ConfigurationChanged.connect
+ (mem_fun (*this, &IO::output_connection_configuration_changed));
+ output_connection_connection_connection = c.ConnectionsChanged.connect
+ (mem_fun (*this, &IO::output_connection_connection_changed));
+ }
+
+ output_changed (IOChange (ConnectionsChanged|ConfigurationChanged), src); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+int
+IO::disable_connecting ()
+{
+ connecting_legal = false;
+ return 0;
+}
+
+int
+IO::enable_connecting ()
+{
+ connecting_legal = true;
+ return ConnectingLegal ();
+}
+
+int
+IO::disable_ports ()
+{
+ ports_legal = false;
+ return 0;
+}
+
+int
+IO::enable_ports ()
+{
+ ports_legal = true;
+ return PortsLegal ();
+}
+
+int
+IO::disable_panners (void)
+{
+ panners_legal = false;
+ return 0;
+}
+
+int
+IO::reset_panners ()
+{
+ panners_legal = true;
+ return PannersLegal ();
+}
+
+void
+IO::input_connection_connection_changed (int ignored)
+{
+ use_input_connection (*_input_connection, this);
+}
+
+void
+IO::input_connection_configuration_changed ()
+{
+ use_input_connection (*_input_connection, this);
+}
+
+void
+IO::output_connection_connection_changed (int ignored)
+{
+ use_output_connection (*_output_connection, this);
+}
+
+void
+IO::output_connection_configuration_changed ()
+{
+ use_output_connection (*_output_connection, this);
+}
+
+IO::MIDIGainControl::MIDIGainControl (IO& i, MIDI::Port* port)
+ : MIDI::Controllable (port, 0), io (i), setting(false)
+{
+ midi_to_gain = 0;
+ gain_to_midi = 0;
+ setting = false;
+ last_written = 0; /* XXX need a good out-of-bound-value */
+}
+
+void
+IO::MIDIGainControl::set_value (float val)
+{
+ if (midi_to_gain == 0) return;
+
+ setting = true;
+ io.set_gain (midi_to_gain (val), this);
+ setting = false;
+}
+
+void
+IO::MIDIGainControl::send_feedback (gain_t gain)
+{
+ if (!setting && get_midi_feedback() && gain_to_midi) {
+ MIDI::byte val = (MIDI::byte) (gain_to_midi (gain) * 127.0);
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ io._session.send_midi_message (get_port(), ev, ch, data);
+ }
+ //send_midi_feedback (gain_to_midi (gain));
+ }
+}
+
+MIDI::byte*
+IO::MIDIGainControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, gain_t val, bool force)
+{
+ if (get_midi_feedback() && gain_to_midi && bufsize > 2) {
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::byte gm;
+
+ if (get_control_info (ch, ev, additional)) {
+ gm = (MIDI::byte) (gain_to_midi (val) * 127.0);
+
+ if (gm != last_written) {
+ *buf++ = (0xF0 & ev) | (0xF & ch);
+ *buf++ = additional; /* controller number */
+ *buf++ = gm;
+ last_written = gm;
+ bufsize -= 3;
+ }
+ }
+ }
+
+ return buf;
+}
+
+void
+IO::reset_peak_meters ()
+{
+ uint32_t limit = max (_ninputs, _noutputs);
+
+ for (uint32_t i = 0; i < limit; ++i) {
+ _peak_power[i] = 0;
+ }
+}
+
+void
+IO::setup_peak_meters ()
+{
+ uint32_t limit = max (_ninputs, _noutputs);
+
+ while (_peak_power.size() < limit) {
+ _peak_power.push_back (0);
+ _stored_peak_power.push_back (0);
+ }
+}
+
+UndoAction
+IO::get_memento() const
+{
+ return sigc::bind (mem_fun (*(const_cast<IO *>(this)), &StateManager::use_state), _current_state_id);
+}
+
+Change
+IO::restore_state (StateManager::State& state)
+{
+ return Change (0);
+}
+
+StateManager::State*
+IO::state_factory (std::string why) const
+{
+ StateManager::State* state = new StateManager::State (why);
+ return state;
+}
+
+void
+IO::send_state_changed ()
+{
+ return;
+}
+
+void
+IO::grab_peak_power ()
+{
+ LockMonitor lm (io_lock, __LINE__, __FILE__);
+
+ uint32_t limit = max (_ninputs, _noutputs);
+
+ for (uint32_t n = 0; n < limit; ++n) {
+ /* XXX should we use atomic exchange here ? */
+ _stored_peak_power[n] = _peak_power[n];
+ _peak_power[n] = 0;
+ }
+}
+
+void
+IO::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ _midi_gain_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_gain_control.midi_rebind (port, chn);
+
+ _panner->reset_midi_control (port, on);
+}
+
+
+int
+IO::save_automation (const string& path)
+{
+ string fullpath;
+ ofstream out;
+
+ fullpath = _session.automation_dir();
+ fullpath += path;
+
+ out.open (fullpath.c_str());
+
+ if (!out) {
+ error << compose(_("%1: could not open automation event file \"%2\""), _name, fullpath) << endmsg;
+ return -1;
+ }
+
+ out << X_("version ") << current_automation_version_number << endl;
+
+ /* XXX use apply_to_points to get thread safety */
+
+ for (AutomationList::iterator i = _gain_automation_curve.begin(); i != _gain_automation_curve.end(); ++i) {
+ out << "g " << (jack_nframes_t) floor ((*i)->when) << ' ' << (*i)->value << endl;
+ }
+
+ _panner->save ();
+
+ return 0;
+}
+
+int
+IO::load_automation (const string& path)
+{
+ string fullpath;
+ ifstream in;
+ char line[128];
+ uint32_t linecnt = 0;
+ float version;
+ LocaleGuard lg (X_("POSIX"));
+
+ fullpath = _session.automation_dir();
+ fullpath += path;
+
+ in.open (fullpath.c_str());
+
+ if (!in) {
+ fullpath = _session.automation_dir();
+ fullpath += _session.snap_name();
+ fullpath += '-';
+ fullpath += path;
+ in.open (fullpath.c_str());
+ if (!in) {
+ error << compose(_("%1: cannot open automation event file \"%2\""), _name, fullpath) << endmsg;
+ return -1;
+ }
+ }
+
+ clear_automation ();
+
+ while (in.getline (line, sizeof(line), '\n')) {
+ char type;
+ jack_nframes_t when;
+ double value;
+
+ if (++linecnt == 1) {
+ if (memcmp (line, "version", 7) == 0) {
+ if (sscanf (line, "version %f", &version) != 1) {
+ error << compose(_("badly formed version number in automation event file \"%1\""), path) << endmsg;
+ return -1;
+ }
+ } else {
+ error << compose(_("no version information in automation event file \"%1\""), path) << endmsg;
+ return -1;
+ }
+
+ if (version != current_automation_version_number) {
+ error << compose(_("mismatched automation event file version (%1)"), version) << endmsg;
+ return -1;
+ }
+
+ continue;
+ }
+
+ if (sscanf (line, "%c %" PRIu32 " %lf", &type, &when, &value) != 3) {
+ warning << compose(_("badly formatted automation event record at line %1 of %2 (ignored)"), linecnt, path) << endmsg;
+ continue;
+ }
+
+ switch (type) {
+ case 'g':
+ _gain_automation_curve.add (when, value, true);
+ break;
+
+ case 's':
+ break;
+
+ case 'm':
+ break;
+
+ case 'p':
+ /* older (pre-1.0) versions of ardour used this */
+ break;
+
+ default:
+ warning << _("dubious automation event found (and ignored)") << endmsg;
+ }
+ }
+
+ _gain_automation_curve.save_state (_("loaded from disk"));
+
+ return 0;
+}
+
+void
+IO::clear_automation ()
+{
+ LockMonitor lm (automation_lock, __LINE__, __FILE__);
+ _gain_automation_curve.clear ();
+ _panner->clear_automation ();
+}
+
+void
+IO::set_gain_automation_state (AutoState state)
+{
+ bool changed = false;
+
+ {
+ LockMonitor lm (automation_lock, __LINE__, __FILE__);
+
+ if (state != _gain_automation_curve.automation_state()) {
+ changed = true;
+ last_automation_snapshot = 0;
+ _gain_automation_curve.set_automation_state (state);
+
+ if (state != Off) {
+ set_gain (_gain_automation_curve.eval (_session.transport_frame()), this);
+ }
+ }
+ }
+
+ if (changed) {
+ _session.set_dirty ();
+ gain_automation_state_changed (); /* EMIT SIGNAL */
+ }
+}
+
+void
+IO::set_gain_automation_style (AutoStyle style)
+{
+ bool changed = false;
+
+ {
+ LockMonitor lm (automation_lock, __LINE__, __FILE__);
+
+ if (style != _gain_automation_curve.automation_style()) {
+ changed = true;
+ _gain_automation_curve.set_automation_style (style);
+ }
+ }
+
+ if (changed) {
+ gain_automation_style_changed (); /* EMIT SIGNAL */
+ }
+}
+void
+IO::inc_gain (gain_t factor, void *src)
+{
+ if (_desired_gain == 0.0f)
+ set_gain (0.000001f + (0.000001f * factor), src);
+ else
+ set_gain (_desired_gain + (_desired_gain * factor), src);
+}
+
+void
+IO::set_gain (gain_t val, void *src)
+{
+ // max gain at about +6dB (10.0 ^ ( 6 dB * 0.05))
+ if (val>1.99526231f) val=1.99526231f;
+
+ {
+ LockMonitor dm (declick_lock, __LINE__, __FILE__);
+ _desired_gain = val;
+ }
+
+ if (_session.transport_stopped()) {
+ _effective_gain = val;
+ _gain = val;
+ }
+
+ gain_changed (src);
+
+ if (_session.get_midi_feedback()) {
+ _midi_gain_control.send_feedback (_desired_gain);
+ }
+
+ if (_session.transport_stopped() && src != 0 && src != this && gain_automation_recording()) {
+ _gain_automation_curve.add (_session.transport_frame(), val);
+
+ }
+
+ _session.set_dirty();
+}
+
+void
+IO::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+ _midi_gain_control.send_feedback (_effective_gain);
+
+ // panners
+ _panner->send_all_midi_feedback();
+ }
+}
+
+MIDI::byte*
+IO::write_midi_feedback (MIDI::byte* buf, int32_t& bufsize)
+{
+ if (_session.get_midi_feedback()) {
+ if (gain_automation_playback ()) {
+ buf = _midi_gain_control.write_feedback (buf, bufsize, _effective_gain);
+ }
+ buf = _panner->write_midi_feedback (buf, bufsize);
+ }
+
+ return buf;
+}
+
+void
+IO::start_gain_touch ()
+{
+ _gain_automation_curve.start_touch ();
+}
+
+void
+IO::end_gain_touch ()
+{
+ _gain_automation_curve.stop_touch ();
+}
+
+void
+IO::start_pan_touch (uint32_t which)
+{
+ if (which < _panner->size()) {
+ (*_panner)[which]->automation().start_touch();
+ }
+}
+
+void
+IO::end_pan_touch (uint32_t which)
+{
+ if (which < _panner->size()) {
+ (*_panner)[which]->automation().stop_touch();
+ }
+
+}
+
+void
+IO::automation_snapshot (jack_nframes_t now)
+{
+ if (last_automation_snapshot > now || (now - last_automation_snapshot) > _automation_interval) {
+
+ if (gain_automation_recording()) {
+ _gain_automation_curve.rt_add (now, gain());
+ }
+
+ _panner->snapshot (now);
+
+ last_automation_snapshot = now;
+ }
+}
+
+void
+IO::transport_stopped (jack_nframes_t frame)
+{
+ _gain_automation_curve.reposition_for_rt_add (frame);
+
+ if (_gain_automation_curve.automation_state() != Off) {
+
+ if (gain_automation_recording()) {
+ _gain_automation_curve.save_state (_("automation write/touch"));
+ }
+
+ /* the src=0 condition is a special signal to not propagate
+ automation gain changes into the mix group when locating.
+ */
+
+ set_gain (_gain_automation_curve.eval (frame), 0);
+ }
+
+ _panner->transport_stopped (frame);
+}
+
+int32_t
+IO::find_input_port_hole ()
+{
+ /* CALLER MUST HOLD IO LOCK */
+
+ uint32_t n;
+
+ if (_inputs.empty()) {
+ return 1;
+ }
+
+ for (n = 1; n < UINT_MAX; ++n) {
+ char buf[jack_port_name_size()];
+ vector<Port*>::iterator i;
+
+ snprintf (buf, jack_port_name_size(), _("%s/in %u"), _name.c_str(), n);
+
+ for (i = _inputs.begin(); i != _inputs.end(); ++i) {
+ if ((*i)->short_name() == buf) {
+ break;
+ }
+ }
+
+ if (i == _inputs.end()) {
+ break;
+ }
+ }
+ return n;
+}
+
+int32_t
+IO::find_output_port_hole ()
+{
+ /* CALLER MUST HOLD IO LOCK */
+
+ uint32_t n;
+
+ if (_outputs.empty()) {
+ return 1;
+ }
+
+ for (n = 1; n < UINT_MAX; ++n) {
+ char buf[jack_port_name_size()];
+ vector<Port*>::iterator i;
+
+ snprintf (buf, jack_port_name_size(), _("%s/out %u"), _name.c_str(), n);
+
+ for (i = _outputs.begin(); i != _outputs.end(); ++i) {
+ if ((*i)->short_name() == buf) {
+ break;
+ }
+ }
+
+ if (i == _outputs.end()) {
+ break;
+ }
+ }
+
+ return n;
+}
diff --git a/libs/ardour/jack_slave.cc b/libs/ardour/jack_slave.cc
new file mode 100644
index 0000000000..be8d585aaa
--- /dev/null
+++ b/libs/ardour/jack_slave.cc
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <iostream>
+
+#include <errno.h>
+#include <jack/jack.h>
+#include <jack/transport.h>
+#include <pbd/error.h>
+
+#include <ardour/slave.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace sigc;
+
+JACK_Slave::JACK_Slave (jack_client_t* j)
+ : jack (j)
+{
+ float x;
+ jack_nframes_t p;
+ /* call this to initialize things */
+ speed_and_position (x, p);
+}
+
+JACK_Slave::~JACK_Slave ()
+{
+}
+
+bool
+JACK_Slave::locked() const
+{
+ return true;
+}
+
+bool
+JACK_Slave::ok() const
+{
+ return true;
+}
+
+bool
+JACK_Slave::speed_and_position (float& sp, jack_nframes_t& position)
+{
+ jack_position_t pos;
+ jack_transport_state_t state;
+
+ state = jack_transport_query (jack, &pos);
+
+ switch (state) {
+ case JackTransportStopped:
+ speed = 0;
+ _starting = false;
+ break;
+ case JackTransportRolling:
+ speed = 1.0;
+ _starting = false;
+ break;
+ case JackTransportLooping:
+ speed = 1.0;
+ _starting = false;
+ break;
+ case JackTransportStarting:
+ _starting = true;
+ // don't adjust speed here, just leave it as it was
+ break;
+ }
+
+ sp = speed;
+ position = pos.frame;
+ return true;
+}
diff --git a/libs/ardour/ladspa_plugin.cc b/libs/ardour/ladspa_plugin.cc
new file mode 100644
index 0000000000..afd0db8608
--- /dev/null
+++ b/libs/ardour/ladspa_plugin.cc
@@ -0,0 +1,724 @@
+/*
+ Copyright (C) 2000-2002 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.
+
+ $Id$
+*/
+
+#include <vector>
+#include <string>
+
+#include <cstdlib>
+#include <cstdio> // so libraptor doesn't complain
+#include <cmath>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <cerrno>
+
+#include <lrdf.h>
+
+#include <pbd/compose.h>
+#include <pbd/error.h>
+#include <pbd/pathscanner.h>
+#include <pbd/xml++.h>
+
+#include <midi++/manager.h>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/audioengine.h>
+#include <ardour/ladspa_plugin.h>
+
+#include <pbd/stl_delete.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+LadspaPlugin::LadspaPlugin (void *mod, AudioEngine& e, Session& session, uint32_t index, jack_nframes_t rate)
+ : Plugin (e, session)
+{
+ init (mod, index, rate);
+}
+
+LadspaPlugin::LadspaPlugin (const LadspaPlugin &other)
+ : Plugin (other)
+{
+ init (other.module, other._index, other.sample_rate);
+
+ for (uint32_t i = 0; i < parameter_count(); ++i) {
+ control_data[i] = other.shadow_data[i];
+ shadow_data[i] = other.shadow_data[i];
+ }
+}
+
+void
+LadspaPlugin::init (void *mod, uint32_t index, jack_nframes_t rate)
+{
+ LADSPA_Descriptor_Function dfunc;
+ uint32_t i, port_cnt;
+ const char *errstr;
+
+ module = mod;
+ control_data = 0;
+ shadow_data = 0;
+ latency_control_port = 0;
+ was_activated = false;
+
+ dfunc = (LADSPA_Descriptor_Function) dlsym (module, "ladspa_descriptor");
+
+ if ((errstr = dlerror()) != NULL) {
+ error << _("LADSPA: module has no descriptor function.") << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((descriptor = dfunc (index)) == 0) {
+ error << _("LADSPA: plugin has gone away since discovery!") << endmsg;
+ throw failed_constructor();
+ }
+
+ _index = index;
+
+ if (LADSPA_IS_INPLACE_BROKEN(descriptor->Properties)) {
+ error << compose(_("LADSPA: \"%1\" cannot be used, since it cannot do inplace processing"), descriptor->Name) << endmsg;
+ throw failed_constructor();
+ }
+
+ sample_rate = rate;
+
+ if (descriptor->instantiate == 0) {
+ throw failed_constructor();
+ }
+
+ if ((handle = descriptor->instantiate (descriptor, rate)) == 0) {
+ throw failed_constructor();
+ }
+
+ port_cnt = parameter_count();
+
+ control_data = new LADSPA_Data[port_cnt];
+ shadow_data = new LADSPA_Data[port_cnt];
+
+ for (i = 0; i < port_cnt; ++i) {
+ if (LADSPA_IS_PORT_CONTROL(port_descriptor (i))) {
+ connect_port (i, &control_data[i]);
+
+ if (LADSPA_IS_PORT_OUTPUT(port_descriptor (i)) &&
+ strcmp (port_names()[i], X_("latency")) == 0) {
+ latency_control_port = &control_data[i];
+ *latency_control_port = 0;
+ }
+
+ if (!LADSPA_IS_PORT_INPUT(port_descriptor (i))) {
+ continue;
+ }
+
+ shadow_data[i] = default_value (i);
+ }
+ }
+
+ Plugin::setup_midi_controls ();
+
+ latency_compute_run ();
+
+ MIDI::Controllable *mcontrol;
+
+ for (uint32_t i = 0; i < parameter_count(); ++i) {
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (i)) &&
+ LADSPA_IS_PORT_CONTROL(port_descriptor (i))) {
+ if ((mcontrol = get_nth_midi_control (i)) != 0) {
+ mcontrol->midi_rebind (_session.midi_port(), 0);
+ }
+ }
+ }
+}
+
+LadspaPlugin::~LadspaPlugin ()
+{
+ deactivate ();
+ cleanup ();
+
+ GoingAway (this); /* EMIT SIGNAL */
+
+ /* XXX who should close a plugin? */
+
+ // dlclose (module);
+
+ if (control_data) {
+ delete [] control_data;
+ }
+
+ if (shadow_data) {
+ delete [] shadow_data;
+ }
+}
+
+void
+LadspaPlugin::store_state (PluginState& state)
+{
+ state.parameters.clear ();
+
+ for (uint32_t i = 0; i < parameter_count(); ++i){
+
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (i)) &&
+ LADSPA_IS_PORT_CONTROL(port_descriptor (i))){
+ pair<uint32_t,float> datum;
+
+ datum.first = i;
+ datum.second = shadow_data[i];
+
+ state.parameters.insert (datum);
+ }
+ }
+}
+
+void
+LadspaPlugin::restore_state (PluginState& state)
+{
+ for (map<uint32_t,float>::iterator i = state.parameters.begin(); i != state.parameters.end(); ++i) {
+ set_parameter (i->first, i->second);
+ }
+}
+
+float
+LadspaPlugin::default_value (uint32_t port)
+{
+ const LADSPA_PortRangeHint *prh = port_range_hints();
+ float ret = 0.0f;
+ bool bounds_given = false;
+ bool sr_scaling = false;
+
+ /* defaults - case 1 */
+
+ if (LADSPA_IS_HINT_HAS_DEFAULT(prh[port].HintDescriptor)) {
+ if (LADSPA_IS_HINT_DEFAULT_MINIMUM(prh[port].HintDescriptor)) {
+ ret = prh[port].LowerBound;
+ bounds_given = true;
+ sr_scaling = true;
+ }
+
+ /* FIXME: add support for logarithmic defaults */
+
+ else if (LADSPA_IS_HINT_DEFAULT_LOW(prh[port].HintDescriptor)) {
+ ret = prh[port].LowerBound * 0.75f + prh[port].UpperBound * 0.25f;
+ bounds_given = true;
+ sr_scaling = true;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(prh[port].HintDescriptor)) {
+ ret = prh[port].LowerBound * 0.50f + prh[port].UpperBound * 0.50f;
+ bounds_given = true;
+ sr_scaling = true;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_HIGH(prh[port].HintDescriptor)) {
+ ret = prh[port].LowerBound * 0.25f + prh[port].UpperBound * 0.75f;
+ bounds_given = true;
+ sr_scaling = true;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(prh[port].HintDescriptor)) {
+ ret = prh[port].UpperBound;
+ bounds_given = true;
+ sr_scaling = true;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_0(prh[port].HintDescriptor)) {
+ ret = 0.0f;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_1(prh[port].HintDescriptor)) {
+ ret = 1.0f;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_100(prh[port].HintDescriptor)) {
+ ret = 100.0f;
+ }
+ else if (LADSPA_IS_HINT_DEFAULT_440(prh[port].HintDescriptor)) {
+ ret = 440.0f;
+ }
+ else {
+ /* no hint found */
+ ret = 0.0f;
+ }
+ }
+
+ /* defaults - case 2 */
+ else if (LADSPA_IS_HINT_BOUNDED_BELOW(prh[port].HintDescriptor) &&
+ !LADSPA_IS_HINT_BOUNDED_ABOVE(prh[port].HintDescriptor)) {
+
+ if (prh[port].LowerBound < 0) {
+ ret = 0.0f;
+ } else {
+ ret = prh[port].LowerBound;
+ }
+
+ bounds_given = true;
+ sr_scaling = true;
+ }
+
+ /* defaults - case 3 */
+ else if (!LADSPA_IS_HINT_BOUNDED_BELOW(prh[port].HintDescriptor) &&
+ LADSPA_IS_HINT_BOUNDED_ABOVE(prh[port].HintDescriptor)) {
+
+ if (prh[port].UpperBound > 0) {
+ ret = 0.0f;
+ } else {
+ ret = prh[port].UpperBound;
+ }
+
+ bounds_given = true;
+ sr_scaling = true;
+ }
+
+ /* defaults - case 4 */
+ else if (LADSPA_IS_HINT_BOUNDED_BELOW(prh[port].HintDescriptor) &&
+ LADSPA_IS_HINT_BOUNDED_ABOVE(prh[port].HintDescriptor)) {
+
+ if (prh[port].LowerBound < 0 && prh[port].UpperBound > 0) {
+ ret = 0.0f;
+ } else if (prh[port].LowerBound < 0 && prh[port].UpperBound < 0) {
+ ret = prh[port].UpperBound;
+ } else {
+ ret = prh[port].LowerBound;
+ }
+ bounds_given = true;
+ sr_scaling = true;
+ }
+
+ /* defaults - case 5 */
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(prh[port].HintDescriptor)) {
+ if (bounds_given) {
+ if (sr_scaling) {
+ ret *= sample_rate;
+ }
+ } else {
+ ret = sample_rate;
+ }
+ }
+
+ return ret;
+}
+
+void
+LadspaPlugin::set_parameter (uint32_t which, float val)
+{
+ if (which < descriptor->PortCount) {
+ shadow_data[which] = (LADSPA_Data) val;
+ ParameterChanged (which, val); /* EMIT SIGNAL */
+
+ if (session().get_midi_feedback()) {
+
+ if (which < parameter_count() && midi_controls[which]) {
+ midi_controls[which]->send_feedback (val);
+ }
+ }
+
+ } else {
+ warning << compose (_("illegal parameter number used with plugin \"%1\". This may"
+ "indicate a change in the plugin design, and presets may be"
+ "invalid"), name())
+ << endmsg;
+ }
+}
+
+float
+LadspaPlugin::get_parameter (uint32_t which) const
+{
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (which))) {
+ return (float) shadow_data[which];
+ } else {
+ return (float) control_data[which];
+ }
+}
+
+uint32_t
+LadspaPlugin::nth_parameter (uint32_t n, bool& ok) const
+{
+ uint32_t x, c;
+
+ ok = false;
+
+ for (c = 0, x = 0; x < descriptor->PortCount; ++x) {
+ if (LADSPA_IS_PORT_CONTROL (port_descriptor (x))) {
+ if (c++ == n) {
+ ok = true;
+ return x;
+ }
+ }
+ }
+ return 0;
+}
+
+XMLNode&
+LadspaPlugin::get_state()
+{
+ XMLNode *root = new XMLNode(state_node_name());
+ XMLNode *child;
+ char buf[16];
+ LocaleGuard lg (X_("POSIX"));
+
+ for (uint32_t i = 0; i < parameter_count(); ++i){
+
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (i)) &&
+ LADSPA_IS_PORT_CONTROL(port_descriptor (i))){
+
+ child = new XMLNode("port");
+ snprintf(buf, sizeof(buf), "%u", i);
+ child->add_property("number", string(buf));
+ snprintf(buf, sizeof(buf), "%+f", shadow_data[i]);
+ child->add_property("value", string(buf));
+ root->add_child_nocopy (*child);
+
+ MIDI::Controllable *pcontrol = get_nth_midi_control (i);
+
+ if (pcontrol) {
+
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ MIDI::channel_t chn;
+ XMLNode* midi_node;
+
+ if (pcontrol->get_control_info (chn, ev, additional)) {
+
+ midi_node = child->add_child ("midi-control");
+
+ snprintf (buf, sizeof(buf), "0x%x", ev);
+ midi_node->add_property ("event", buf);
+ snprintf (buf, sizeof(buf), "%d", chn);
+ midi_node->add_property ("channel", buf);
+ snprintf (buf, sizeof(buf), "0x%x", additional);
+ midi_node->add_property ("additional", buf);
+ }
+ }
+ }
+ }
+
+ return *root;
+}
+
+bool
+LadspaPlugin::save_preset (string name)
+{
+ return Plugin::save_preset (name, "ladspa");
+}
+
+int
+LadspaPlugin::set_state(const XMLNode& node)
+{
+ XMLNodeList nodes;
+ XMLProperty *prop;
+ XMLNodeConstIterator iter;
+ XMLNode *child;
+ const char *port;
+ const char *data;
+ uint32_t port_id;
+ LocaleGuard lg (X_("POSIX"));
+
+ if (node.name() != state_node_name()) {
+ error << _("Bad node send to LadspaPlugin::set_state") << endmsg;
+ return -1;
+ }
+
+ nodes = node.children ("port");
+
+ for(iter = nodes.begin(); iter != nodes.end(); ++iter){
+
+ child = *iter;
+
+ if ((prop = child->property("number")) != 0) {
+ port = prop->value().c_str();
+ } else {
+ warning << _("LADSPA: no ladspa port number") << endmsg;
+ continue;
+ }
+ if ((prop = child->property("value")) != 0) {
+ data = prop->value().c_str();
+ } else {
+ warning << _("LADSPA: no ladspa port data") << endmsg;
+ continue;
+ }
+
+ sscanf (port, "%" PRIu32, &port_id);
+ set_parameter (port_id, atof(data));
+
+ XMLNodeList midi_kids;
+ XMLNodeConstIterator iter;
+
+ midi_kids = child->children ("midi-control");
+
+ for (iter = midi_kids.begin(); iter != midi_kids.end(); ++iter) {
+
+ child = *iter;
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* initialize to keep gcc happy */
+ MIDI::channel_t chn = 0; /* initialize to keep gcc happy */
+ bool ok = true;
+ int xx;
+
+ if ((prop = child->property ("event")) != 0) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ ev = (MIDI::eventType) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = child->property ("channel")) != 0)) {
+ sscanf (prop->value().c_str(), "%d", &xx);
+ chn = (MIDI::channel_t) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = child->property ("additional")) != 0)) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ additional = (MIDI::byte) xx;
+ }
+
+ if (ok) {
+ MIDI::Controllable* pcontrol = get_nth_midi_control (port_id);
+
+ if (pcontrol) {
+ pcontrol->set_control_type (chn, ev, additional);
+ }
+
+ } else {
+ error << compose(_("LADSPA LadspaPlugin MIDI control specification for port %1 is incomplete, so it has been ignored"), port) << endl;
+ }
+ }
+ }
+
+ latency_compute_run ();
+
+ return 0;
+}
+
+int
+LadspaPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) const
+{
+ LADSPA_PortRangeHint prh;
+
+ prh = port_range_hints()[which];
+
+
+ if (LADSPA_IS_HINT_BOUNDED_BELOW(prh.HintDescriptor)) {
+ desc.min_unbound = false;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(prh.HintDescriptor)) {
+ desc.lower = prh.LowerBound * _session.frame_rate();
+ } else {
+ desc.lower = prh.LowerBound;
+ }
+ } else {
+ desc.min_unbound = true;
+ desc.lower = 0;
+ }
+
+
+ if (LADSPA_IS_HINT_BOUNDED_ABOVE(prh.HintDescriptor)) {
+ desc.max_unbound = false;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(prh.HintDescriptor)) {
+ desc.upper = prh.UpperBound * _session.frame_rate();
+ } else {
+ desc.upper = prh.UpperBound;
+ }
+ } else {
+ desc.max_unbound = true;
+ desc.upper = 4; /* completely arbitrary */
+ }
+
+ if (LADSPA_IS_HINT_INTEGER (prh.HintDescriptor)) {
+ desc.step = 1.0;
+ desc.smallstep = 0.1;
+ desc.largestep = 10.0;
+ } else {
+ float delta = desc.upper - desc.lower;
+ desc.step = delta / 1000.0f;
+ desc.smallstep = delta / 10000.0f;
+ desc.largestep = delta/10.0f;
+ }
+
+ desc.toggled = LADSPA_IS_HINT_TOGGLED (prh.HintDescriptor);
+ desc.logarithmic = LADSPA_IS_HINT_LOGARITHMIC (prh.HintDescriptor);
+ desc.sr_dependent = LADSPA_IS_HINT_SAMPLE_RATE (prh.HintDescriptor);
+ desc.integer_step = LADSPA_IS_HINT_INTEGER (prh.HintDescriptor);
+
+ desc.label = port_names()[which];
+
+
+ return 0;
+}
+
+
+string
+LadspaPlugin::describe_parameter (uint32_t which)
+{
+ if (which < parameter_count()) {
+ return port_names()[which];
+ } else {
+ return "??";
+ }
+}
+
+jack_nframes_t
+LadspaPlugin::latency () const
+{
+ if (latency_control_port) {
+ return (jack_nframes_t) floor (*latency_control_port);
+ } else {
+ return 0;
+ }
+}
+
+set<uint32_t>
+LadspaPlugin::automatable () const
+{
+ set<uint32_t> ret;
+
+ for (uint32_t i = 0; i < parameter_count(); ++i){
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (i)) &&
+ LADSPA_IS_PORT_CONTROL(port_descriptor (i))){
+
+ ret.insert (ret.end(), i);
+ }
+ }
+
+ return ret;
+}
+
+int
+LadspaPlugin::connect_and_run (vector<Sample*>& bufs, uint32_t nbufs, int32_t& in_index, int32_t& out_index, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ uint32_t port_index;
+ cycles_t then, now;
+
+ port_index = 0;
+
+ then = get_cycles ();
+
+ while (port_index < parameter_count()) {
+ if (LADSPA_IS_PORT_AUDIO (port_descriptor(port_index))) {
+ if (LADSPA_IS_PORT_INPUT (port_descriptor(port_index))) {
+ connect_port (port_index, bufs[min((uint32_t) in_index,nbufs)]);
+ //cerr << this << ' ' << name() << " @ " << offset << " inport " << in_index << " = buf "
+ //<< min((uint32_t)in_index,nbufs) << " = " << &bufs[min((uint32_t)in_index,nbufs)][offset] << endl;
+ in_index++;
+
+
+ } else if (LADSPA_IS_PORT_OUTPUT (port_descriptor (port_index))) {
+ connect_port (port_index, bufs[min((uint32_t) out_index,nbufs)]);
+ //cerr << this << ' ' << name() << " @ " << offset << " outport " << out_index << " = buf "
+ //<< min((uint32_t)out_index,nbufs) << " = " << &bufs[min((uint32_t)out_index,nbufs)][offset] << endl;
+ out_index++;
+ }
+ }
+ port_index++;
+ }
+
+ run (nframes);
+ now = get_cycles ();
+ set_cycles ((uint32_t) (now - then));
+
+ return 0;
+}
+
+bool
+LadspaPlugin::parameter_is_control (uint32_t param) const
+{
+ return LADSPA_IS_PORT_CONTROL(port_descriptor (param));
+}
+
+bool
+LadspaPlugin::parameter_is_audio (uint32_t param) const
+{
+ return LADSPA_IS_PORT_AUDIO(port_descriptor (param));
+}
+
+bool
+LadspaPlugin::parameter_is_output (uint32_t param) const
+{
+ return LADSPA_IS_PORT_OUTPUT(port_descriptor (param));
+}
+
+bool
+LadspaPlugin::parameter_is_input (uint32_t param) const
+{
+ return LADSPA_IS_PORT_INPUT(port_descriptor (param));
+}
+
+void
+LadspaPlugin::print_parameter (uint32_t param, char *buf, uint32_t len) const
+{
+ if (buf && len) {
+ if (param < parameter_count()) {
+ snprintf (buf, len, "%.3f", get_parameter (param));
+ } else {
+ strcat (buf, "0");
+ }
+ }
+}
+
+void
+LadspaPlugin::run (jack_nframes_t nframes)
+{
+ for (uint32_t i = 0; i < parameter_count(); ++i) {
+ if (LADSPA_IS_PORT_INPUT(port_descriptor (i)) && LADSPA_IS_PORT_CONTROL(port_descriptor (i))) {
+ control_data[i] = shadow_data[i];
+ }
+ }
+ descriptor->run (handle, nframes);
+}
+
+void
+LadspaPlugin::latency_compute_run ()
+{
+ if (!latency_control_port) {
+ return;
+ }
+
+ /* we need to run the plugin so that it can set its latency
+ parameter.
+ */
+
+ activate ();
+
+ uint32_t port_index = 0;
+ uint32_t in_index = 0;
+ uint32_t out_index = 0;
+ const jack_nframes_t bufsize = 1024;
+ LADSPA_Data buffer[bufsize];
+
+ memset(buffer,0,sizeof(LADSPA_Data)*bufsize);
+
+ /* Note that we've already required that plugins
+ be able to handle in-place processing.
+ */
+
+ port_index = 0;
+
+ while (port_index < parameter_count()) {
+ if (LADSPA_IS_PORT_AUDIO (port_descriptor (port_index))) {
+ if (LADSPA_IS_PORT_INPUT (port_descriptor (port_index))) {
+ connect_port (port_index, buffer);
+ in_index++;
+ } else if (LADSPA_IS_PORT_OUTPUT (port_descriptor (port_index))) {
+ connect_port (port_index, buffer);
+ out_index++;
+ }
+ }
+ port_index++;
+ }
+
+ run (bufsize);
+ deactivate ();
+}
diff --git a/libs/ardour/location.cc b/libs/ardour/location.cc
new file mode 100644
index 0000000000..ce726fb453
--- /dev/null
+++ b/libs/ardour/location.cc
@@ -0,0 +1,718 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <set>
+#include <cstdio> /* for sprintf */
+#include <unistd.h>
+#include <cerrno>
+#include <ctime>
+#include <sigc++/bind.h>
+
+#include <pbd/stl_delete.h>
+#include <pbd/xml++.h>
+
+#include <ardour/location.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace sigc;
+
+Location::Location (const Location& other)
+ : _name (other._name),
+ _start (other._start),
+ _end (other._end),
+ _flags (other._flags)
+{
+}
+
+Location*
+Location::operator= (const Location& other)
+{
+ if (this == &other) {
+ return this;
+ }
+
+ _name = other._name;
+ _start = other._start;
+ _end = other._end;
+ _flags = other._flags;
+
+ /* "changed" not emitted on purpose */
+
+ return this;
+}
+
+int
+Location::set_start (jack_nframes_t s)
+{
+ if (is_mark()) {
+ if (_start != s) {
+ _start = s;
+ _end = s;
+ changed(this); /* EMIT SIGNAL */
+ }
+ return 0;
+ }
+
+ if (((is_auto_punch() || is_auto_loop()) && s >= _end) || s > _end) {
+ return -1;
+ }
+
+ if (s != _start) {
+ _start = s;
+ start_changed(this); /* EMIT SIGNAL */
+ }
+ return 0;
+}
+
+int
+Location::set_end (jack_nframes_t e)
+{
+ if (is_mark()) {
+ if (_start != e) {
+ _start = e;
+ _end = e;
+ changed(this); /* EMIT SIGNAL */
+ }
+ return 0;
+ }
+
+ if (((is_auto_punch() || is_auto_loop()) && e <= _start) || e < _start) {
+ return -1;
+ }
+
+ if (e != _end) {
+ _end = e;
+ end_changed(this); /* EMIT SIGNAL */
+ }
+ return 0;
+}
+
+int
+Location::set (jack_nframes_t start, jack_nframes_t end)
+{
+ if (is_mark() && start != end) {
+ return -1;
+ } else if (((is_auto_punch() || is_auto_loop()) && start >= end) || (start > end)) {
+ return -1;
+ }
+
+ if (_start != start || _end != end) {
+ _start = start;
+ _end = end;
+ changed(this); /* EMIT SIGNAL */
+ }
+ return 0;
+}
+
+void
+Location::set_hidden (bool yn, void *src)
+{
+ if (set_flag_internal (yn, IsHidden)) {
+ FlagsChanged (this, src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Location::set_cd (bool yn, void *src)
+{
+ if (set_flag_internal (yn, IsCDMarker)) {
+ FlagsChanged (this, src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Location::set_is_end (bool yn, void *src)
+{
+ if (set_flag_internal (yn, IsEnd)) {
+ FlagsChanged (this, src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Location::set_auto_punch (bool yn, void *src)
+{
+ if (is_mark() || _start == _end) {
+ return;
+ }
+
+ if (set_flag_internal (yn, IsAutoPunch)) {
+ FlagsChanged (this, src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Location::set_auto_loop (bool yn, void *src)
+{
+ if (is_mark() || _start == _end) {
+ return;
+ }
+
+ if (set_flag_internal (yn, IsAutoLoop)) {
+ FlagsChanged (this, src); /* EMIT SIGNAL */
+ }
+}
+
+bool
+Location::set_flag_internal (bool yn, Flags flag)
+{
+ if (yn) {
+ if (!(_flags & flag)) {
+ _flags |= flag;
+ return true;
+ }
+ } else {
+ if (_flags & flag) {
+ _flags &= ~flag;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+Location::set_mark (bool yn)
+{
+ /* This function is private, and so does not emit signals */
+
+ if (_start != _end) {
+ return;
+ }
+
+ set_flag_internal (yn, IsMark);
+}
+
+
+XMLNode&
+Location::cd_info_node(const string & name, const string & value)
+{
+ XMLNode* root = new XMLNode("CD-Info");
+
+ root->add_property("name", name);
+ root->add_property("value", value);
+
+ return *root;
+}
+
+
+XMLNode&
+Location::get_state (void)
+{
+ XMLNode *node = new XMLNode ("Location");
+ char buf[32];
+
+ typedef map<string, string>::const_iterator CI;
+ for(CI m = cd_info.begin(); m != cd_info.end(); ++m){
+ node->add_child_nocopy(cd_info_node(m->first, m->second));
+ }
+
+ node->add_property ("name", name());
+ snprintf (buf, sizeof (buf), "%u", start());
+ node->add_property ("start", buf);
+ snprintf (buf, sizeof (buf), "%u", end());
+ node->add_property ("end", buf);
+ snprintf (buf, sizeof (buf), "%" PRIu32, (uint32_t) _flags);
+ node->add_property ("flags", buf);
+
+ return *node;
+}
+
+int
+Location::set_state (const XMLNode& node)
+{
+ XMLPropertyList plist;
+ const XMLProperty *prop;
+
+ XMLNodeList cd_list = node.children();
+ XMLNodeConstIterator cd_iter;
+ XMLNode *cd_node;
+
+ string cd_name;
+ string cd_value;
+
+
+ if (node.name() != "Location") {
+ error << _("incorrect XML node passed to Location::set_state") << endmsg;
+ return -1;
+ }
+
+ plist = node.properties();
+
+ if ((prop = node.property ("name")) == 0) {
+ error << _("XML node for Location has no name information") << endmsg;
+ return -1;
+ }
+
+ set_name (prop->value());
+
+ if ((prop = node.property ("start")) == 0) {
+ error << _("XML node for Location has no start information") << endmsg;
+ return -1;
+ }
+
+ /* can't use set_start() here, because _end
+ may make the value of _start illegal.
+ */
+
+ _start = atoi (prop->value().c_str());
+
+ if ((prop = node.property ("end")) == 0) {
+ error << _("XML node for Location has no end information") << endmsg;
+ return -1;
+ }
+
+ _end = atoi (prop->value().c_str());
+
+ _flags = 0;
+
+ if ((prop = node.property ("flags")) == 0) {
+ error << _("XML node for Location has no flags information") << endmsg;
+ return -1;
+ }
+
+ _flags = Flags (atoi (prop->value().c_str()));
+
+ for (cd_iter = cd_list.begin(); cd_iter != cd_list.end(); ++cd_iter) {
+
+ cd_node = *cd_iter;
+
+ if (cd_node->name() != "CD-Info") {
+ continue;
+ }
+
+ if ((prop = cd_node->property ("name")) != 0) {
+ cd_name = prop->value();
+ } else {
+ throw failed_constructor ();
+ }
+
+ if ((prop = cd_node->property ("value")) != 0) {
+ cd_value = prop->value();
+ } else {
+ throw failed_constructor ();
+ }
+
+
+ cd_info[cd_name] = cd_value;
+
+ }
+
+ changed(this); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------- */
+
+Locations::Locations ()
+
+{
+ current_location = 0;
+ save_state (_("initial"));
+}
+
+Locations::~Locations ()
+{
+ std::set<Location*> all_locations;
+
+ for (StateMap::iterator siter = states.begin(); siter != states.end(); ++siter) {
+
+ State* lstate = dynamic_cast<State*> (*siter);
+
+ for (LocationList::iterator liter = lstate->locations.begin(); liter != lstate->locations.end(); ++liter) {
+ all_locations.insert (*liter);
+ }
+
+ for (LocationList::iterator siter = lstate->states.begin(); siter != lstate->states.end(); ++siter) {
+ all_locations.insert (*siter);
+ }
+ }
+
+ set_delete (&all_locations);
+}
+
+int
+Locations::set_current (Location *loc, bool want_lock)
+
+{
+ int ret;
+
+ if (want_lock) {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ ret = set_current_unlocked (loc);
+ } else {
+ ret = set_current_unlocked (loc);
+ }
+
+ if (ret == 0) {
+ current_changed (current_location); /* EMIT SIGNAL */
+ }
+ return ret;
+}
+
+int
+Locations::set_current_unlocked (Location *loc)
+{
+ if (find (locations.begin(), locations.end(), loc) == locations.end()) {
+ error << _("Locations: attempt to use unknown location as selected location") << endmsg;
+ return -1;
+ }
+
+ current_location = loc;
+ return 0;
+}
+
+void
+Locations::clear ()
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ LocationList::iterator tmp;
+ for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
+ tmp = i;
+ ++tmp;
+ if (!(*i)->is_end()) {
+ locations.erase (i);
+ }
+ i = tmp;
+ }
+
+ locations.clear ();
+ current_location = 0;
+ }
+
+ save_state (_("clear"));
+
+ changed (); /* EMIT SIGNAL */
+ current_changed (0); /* EMIT SIGNAL */
+}
+
+void
+Locations::clear_markers ()
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ LocationList::iterator tmp;
+
+ for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->is_mark() && !(*i)->is_end()) {
+ locations.erase (i);
+ }
+
+ i = tmp;
+ }
+ }
+
+ save_state (_("clear markers"));
+
+ changed (); /* EMIT SIGNAL */
+}
+
+void
+Locations::clear_ranges ()
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ LocationList::iterator tmp;
+
+ for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if (!(*i)->is_mark()) {
+ locations.erase (i);
+
+ }
+
+ i = tmp;
+ }
+
+ current_location = 0;
+ }
+
+ save_state (_("clear ranges"));
+
+ changed (); /* EMIT SIGNAL */
+ current_changed (0); /* EMIT SIGNAL */
+}
+
+void
+Locations::add (Location *loc, bool make_current)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ locations.push_back (loc);
+
+ loc->changed.connect (mem_fun (*this, &Locations::location_changed));
+
+ if (make_current) {
+ current_location = loc;
+ }
+ }
+
+ save_state (_("add"));
+
+ added (loc); /* EMIT SIGNAL */
+
+ if (make_current) {
+ current_changed (current_location); /* EMIT SIGNAL */
+ }
+}
+
+void
+Locations::remove (Location *loc)
+
+{
+ bool was_removed = false;
+ bool was_current = false;
+ LocationList::iterator i;
+
+ if (loc->is_end()) {
+ return;
+ }
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ for (i = locations.begin(); i != locations.end(); ++i) {
+ if ((*i) == loc) {
+ locations.erase (i);
+ was_removed = true;
+ if (current_location == loc) {
+ current_location = 0;
+ was_current = true;
+ }
+ break;
+ }
+ }
+ }
+
+ if (was_removed) {
+ save_state (_("remove"));
+
+ removed (loc); /* EMIT SIGNAL */
+
+ if (was_current) {
+ current_changed (0); /* EMIT SIGNAL */
+ }
+
+ changed (); /* EMIT_SIGNAL */
+ }
+}
+
+void
+Locations::location_changed (Location* ignored)
+{
+ save_state (X_("location changed"));
+ changed (); /* EMIT SIGNAL */
+}
+
+XMLNode&
+Locations::get_state ()
+{
+ XMLNode *node = new XMLNode ("Locations");
+ LocationList::iterator iter;
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ for (iter = locations.begin(); iter != locations.end(); ++iter) {
+ node->add_child_nocopy ((*iter)->get_state ());
+ }
+
+ return *node;
+}
+
+int
+Locations::set_state (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+
+ if (node.name() != "Locations") {
+ error << _("incorrect XML mode passed to Locations::set_state") << endmsg;
+ return -1;
+ }
+
+ nlist = node.children();
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ Location *loc = new Location;
+
+ if (loc->set_state (**niter)) {
+ delete loc;
+ } else {
+ locations.push_back (loc);
+ }
+ }
+
+ if (locations.size()) {
+ current_location = locations.front();
+ } else {
+ current_location = 0;
+ }
+ }
+
+ changed (); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+struct LocationStartEarlierComparison
+{
+ bool operator() (Location *a, Location *b) {
+ return a->start() < b->start();
+ }
+};
+
+struct LocationStartLaterComparison
+{
+ bool operator() (Location *a, Location *b) {
+ return a->start() > b->start();
+ }
+};
+
+Location *
+Locations::first_location_before (jack_nframes_t frame)
+{
+ LocationList locs;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ locs = locations;
+ }
+
+ LocationStartLaterComparison cmp;
+ locs.sort (cmp);
+
+ /* locs is now sorted latest..earliest */
+
+ for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
+ if (!(*i)->is_hidden() && (*i)->start() < frame) {
+ return (*i);
+ }
+ }
+
+ return 0;
+}
+
+Location *
+Locations::first_location_after (jack_nframes_t frame)
+{
+ LocationList locs;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ locs = locations;
+ }
+
+ LocationStartEarlierComparison cmp;
+ locs.sort (cmp);
+
+ /* locs is now sorted earliest..latest */
+
+ for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
+ if (!(*i)->is_hidden() && (*i)->start() > frame) {
+ return (*i);
+ }
+ }
+
+ return 0;
+}
+
+Location*
+Locations::end_location () const
+{
+ for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+ if ((*i)->is_end()) {
+ return const_cast<Location*> (*i);
+ }
+ }
+ return 0;
+}
+
+Location*
+Locations::auto_loop_location () const
+{
+ for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+ if ((*i)->is_auto_loop()) {
+ return const_cast<Location*> (*i);
+ }
+ }
+ return 0;
+}
+
+Location*
+Locations::auto_punch_location () const
+{
+ for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+ if ((*i)->is_auto_punch()) {
+ return const_cast<Location*> (*i);
+ }
+ }
+ return 0;
+}
+
+StateManager::State*
+Locations::state_factory (std::string why) const
+{
+ State* state = new State (why);
+
+ state->locations = locations;
+
+ for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+ state->states.push_back (new Location (**i));
+ }
+
+ return state;
+}
+
+Change
+Locations::restore_state (StateManager::State& state)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ State* lstate = dynamic_cast<State*> (&state);
+
+ locations = lstate->locations;
+ LocationList& states = lstate->states;
+ LocationList::iterator l, s;
+
+ for (l = locations.begin(), s = states.begin(); s != states.end(); ++s, ++l) {
+ (*l) = (*s);
+ }
+ }
+
+ return Change (0);
+}
+
+UndoAction
+Locations::get_memento () const
+{
+ return sigc::bind (mem_fun (*(const_cast<Locations*> (this)), &StateManager::use_state), _current_state_id);
+}
diff --git a/libs/ardour/mix.cc b/libs/ardour/mix.cc
new file mode 100644
index 0000000000..6d268dfeba
--- /dev/null
+++ b/libs/ardour/mix.cc
@@ -0,0 +1,149 @@
+/*
+ Copyright (C) 2000-2005 Paul Davis,
+ Copyright (C) 2005 Sampo Savolainen
+
+ 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <ardour/types.h>
+#include <ardour/utils.h>
+#include <ardour/mix.h>
+
+#if defined (ARCH_X86) && defined (BUILD_SSE_OPTIMIZATIONS)
+
+// Debug wrappers
+
+float
+debug_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current)
+{
+ if ( ((int)buf % 16) != 0) {
+ cerr << "compute_peak(): buffer unaligned!" << endl;
+ }
+
+ return x86_sse_compute_peak(buf, nsamples, current);
+}
+
+void
+debug_apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain)
+{
+ if ( ((int)buf % 16) != 0) {
+ cerr << "apply_gain_to_buffer(): buffer unaligned!" << endl;
+ }
+
+ x86_sse_apply_gain_to_buffer(buf, nframes, gain);
+}
+
+void
+debug_mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain)
+{
+ if ( ((int)dst & 15) != 0) {
+ cerr << "mix_buffers_with_gain(): dst unaligned!" << endl;
+ }
+
+ if ( ((int)dst & 15) != ((int)src & 15) ) {
+ cerr << "mix_buffers_with_gain(): dst & src don't have the same alignment!" << endl;
+ mix_buffers_with_gain(dst, src, nframes, gain);
+ } else {
+ x86_sse_mix_buffers_with_gain(dst, src, nframes, gain);
+ }
+}
+
+void
+debug_mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes)
+{
+ if ( ((int)dst & 15) != 0) {
+ cerr << "mix_buffers_no_gain(): dst unaligned!" << endl;
+ }
+
+ if ( ((int)dst & 15) != ((int)src & 15) ) {
+ cerr << "mix_buffers_no_gain(): dst & src don't have the same alignment!" << endl;
+ mix_buffers_no_gain(dst, src, nframes);
+ } else {
+ x86_sse_mix_buffers_no_gain(dst, src, nframes);
+ }
+}
+
+#endif
+
+
+float
+compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current)
+{
+ for (jack_nframes_t i = 0; i < nsamples; ++i) {
+ current = f_max (current, fabsf (buf[i]));
+ }
+
+ return current;
+}
+
+void
+apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain)
+{
+ for (jack_nframes_t i=0; i<nframes; i++)
+ buf[i] *= gain;
+}
+
+void
+mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain)
+{
+ for (jack_nframes_t i = 0; i < nframes; i++) {
+ dst[i] += src[i] * gain;
+ }
+}
+
+void
+mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes)
+{
+ for (jack_nframes_t i=0; i < nframes; i++) {
+ dst[i] += src[i];
+ }
+}
+
+#if defined (__APPLE__)
+#include <Accelerate/Accelerate.h>
+
+float
+veclib_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current)
+{
+ vDSP_maxv(buf, 1, &current, nsamples);
+ return current;
+}
+
+void
+veclib_apply_gain_to_buffer (ARDOUR::Sample *buf, jack_nframes_t nframes, float gain)
+{
+ vDSP_vsmul(buf, 1, &gain, buf, 1, nframes);
+}
+
+void
+veclib_mix_buffers_with_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes, float gain)
+{
+ vDSP_vsma(src, 1, &gain, dst, 1, dst, 1, nframes);
+}
+
+void
+veclib_mix_buffers_no_gain (ARDOUR::Sample *dst, ARDOUR::Sample *src, jack_nframes_t nframes)
+{
+ // It seems that a vector mult only operation does not exist...
+ float gain = 1.0f;
+ vDSP_vsma(src, 1, &gain, dst, 1, dst, 1, nframes);
+}
+
+#endif
+
+
diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc
new file mode 100644
index 0000000000..906a0372a4
--- /dev/null
+++ b/libs/ardour/mtc_slave.cc
@@ -0,0 +1,321 @@
+/*
+ Copyright (C) 2002-4 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.
+
+ $Id$
+*/
+
+#include <errno.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pbd/error.h>
+#include <pbd/failed_constructor.h>
+#include <pbd/pthread_utils.h>
+
+#include <midi++/port.h>
+#include <ardour/slave.h>
+#include <ardour/session.h>
+#include <ardour/audioengine.h>
+#include <ardour/cycles.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace sigc;
+using namespace MIDI;
+
+MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p)
+ : session (s)
+{
+ rebind (p);
+ reset ();
+}
+
+MTC_Slave::~MTC_Slave()
+{
+}
+
+void
+MTC_Slave::rebind (MIDI::Port& p)
+{
+ for (vector<sigc::connection>::iterator i = connections.begin(); i != connections.end(); ++i) {
+ (*i).disconnect ();
+ }
+
+ port = &p;
+
+ connections.push_back (port->input()->mtc_time.connect (mem_fun (*this, &MTC_Slave::update_mtc_time)));
+ connections.push_back (port->input()->mtc_qtr.connect (mem_fun (*this, &MTC_Slave::update_mtc_qtr)));
+ connections.push_back (port->input()->mtc_status.connect (mem_fun (*this, &MTC_Slave::update_mtc_status)));
+}
+
+void
+MTC_Slave::update_mtc_qtr (Parser& p)
+{
+ cycles_t cnow = get_cycles ();
+ jack_nframes_t now = session.engine().frame_time();
+ jack_nframes_t qtr;
+ static cycles_t last_qtr = 0;
+
+ qtr = (long) (session.frames_per_smpte_frame() / 4);
+ mtc_frame += qtr;
+ last_qtr = cnow;
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = now;
+ current.guard2++;
+
+ last_inbound_frame = now;
+}
+
+void
+MTC_Slave::update_mtc_time (const byte *msg, bool was_full)
+{
+ jack_nframes_t now = session.engine().frame_time();
+ SMPTE_Time smpte;
+
+ smpte.hours = msg[3];
+ smpte.minutes = msg[2];
+ smpte.seconds = msg[1];
+ smpte.frames = msg[0];
+
+ session.smpte_to_sample( smpte, mtc_frame, true, false );
+
+ if (was_full) {
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = 0;
+ current.guard2++;
+
+ session.request_locate (mtc_frame, false);
+ update_mtc_status (MIDI::Parser::MTC_Stopped);
+
+ reset ();
+
+ } else {
+
+ /* We received the last quarter frame 7 quarter frames (1.75 mtc
+ frames) after the instance when the contents of the mtc quarter
+ frames were decided. Add time to compensate for the elapsed 1.75
+ frames
+ */
+
+ mtc_frame += (long) (1.75 * session.frames_per_smpte_frame());
+
+ if (first_mtc_frame == 0) {
+ first_mtc_frame = mtc_frame;
+ first_mtc_time = now;
+ }
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = now;
+ current.guard2++;
+ }
+
+ last_inbound_frame = now;
+}
+
+void
+MTC_Slave::handle_locate (const MIDI::byte* mmc_tc)
+{
+ MIDI::byte mtc[4];
+
+ mtc[3] = mmc_tc[0] & 0xf; /* hrs only */
+ mtc[2] = mmc_tc[1];
+ mtc[1] = mmc_tc[2];
+ mtc[0] = mmc_tc[3];
+
+ update_mtc_time (mtc, true);
+}
+
+void
+MTC_Slave::update_mtc_status (MIDI::Parser::MTC_Status status)
+{
+
+ switch (status) {
+ case MTC_Stopped:
+ mtc_speed = 0.0f;
+ mtc_frame = 0;
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = 0;
+ current.guard2++;
+
+ break;
+
+ case MTC_Forward:
+ mtc_speed = 0.0f;
+ mtc_frame = 0;
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = 0;
+ current.guard2++;
+
+ break;
+
+ case MTC_Backward:
+ mtc_speed = 0.0f;
+ mtc_frame = 0;
+
+ current.guard1++;
+ current.position = mtc_frame;
+ current.timestamp = 0;
+ current.guard2++;
+
+ break;
+ }
+}
+
+void
+MTC_Slave::read_current (SafeTime *st) const
+{
+ int tries = 0;
+ do {
+ if (tries == 10) {
+ error << _("MTC Slave: atomic read of current time failed, sleeping!") << endmsg;
+ usleep (20);
+ tries = 0;
+ }
+
+ *st = current;
+ tries++;
+
+ } while (st->guard1 != st->guard2);
+}
+
+bool
+MTC_Slave::locked () const
+{
+ return port->input()->mtc_locked();
+}
+
+bool
+MTC_Slave::ok() const
+{
+ return true;
+}
+
+bool
+MTC_Slave::speed_and_position (float& speed, jack_nframes_t& pos)
+{
+ jack_nframes_t now = session.engine().frame_time();
+ SafeTime last;
+ jack_nframes_t frame_rate;
+ jack_nframes_t elapsed;
+ float speed_now;
+
+ read_current (&last);
+
+ if (first_mtc_time == 0) {
+ speed = 0;
+ pos = last.position;
+ return true;
+ }
+
+ /* no timecode for 1/4 second ? conclude that its stopped */
+
+ if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > session.frame_rate() / 4) {
+ mtc_speed = 0;
+ pos = last.position;
+ session.request_locate (pos, false);
+ update_mtc_status (MIDI::Parser::MTC_Stopped);
+ reset();
+ return false;
+ }
+
+ frame_rate = session.frame_rate();
+
+ speed_now = (float) ((last.position - first_mtc_frame) / (double) (now - first_mtc_time));
+
+ accumulator[accumulator_index++] = speed_now;
+
+ if (accumulator_index >= accumulator_size) {
+ have_first_accumulated_speed = true;
+ accumulator_index = 0;
+ }
+
+ if (have_first_accumulated_speed) {
+ float total = 0;
+
+ for (int32_t i = 0; i < accumulator_size; ++i) {
+ total += accumulator[i];
+ }
+
+ mtc_speed = total / accumulator_size;
+
+ } else {
+
+ mtc_speed = speed_now;
+
+ }
+
+ if (mtc_speed == 0.0f) {
+
+ elapsed = 0;
+
+ } else {
+
+ /* scale elapsed time by the current MTC speed */
+
+ if (last.timestamp && (now > last.timestamp)) {
+ elapsed = (jack_nframes_t) floor (mtc_speed * (now - last.timestamp));
+ } else {
+ elapsed = 0; /* XXX is this right? */
+ }
+ }
+
+ /* now add the most recent timecode value plus the estimated elapsed interval */
+
+ pos = elapsed + last.position;
+
+ speed = mtc_speed;
+ return true;
+}
+
+jack_nframes_t
+MTC_Slave::resolution() const
+{
+ return (jack_nframes_t) session.frames_per_smpte_frame();
+}
+
+void
+MTC_Slave::reset ()
+{
+ /* XXX massive thread safety issue here. MTC could
+ be being updated as we call this. but this
+ supposed to be a realtime-safe call.
+ */
+
+ port->input()->reset_mtc_state ();
+
+ last_inbound_frame = 0;
+ current.guard1++;
+ current.position = 0;
+ current.timestamp = 0;
+ current.guard2++;
+ first_mtc_frame = 0;
+ first_mtc_time = 0;
+
+ accumulator_index = 0;
+ have_first_accumulated_speed = false;
+ mtc_speed = 0;
+}
diff --git a/libs/ardour/named_selection.cc b/libs/ardour/named_selection.cc
new file mode 100644
index 0000000000..44e169edff
--- /dev/null
+++ b/libs/ardour/named_selection.cc
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#include <pbd/failed_constructor.h>
+#include <pbd/error.h>
+
+#include <ardour/session.h>
+#include <ardour/utils.h>
+#include <ardour/playlist.h>
+#include <ardour/named_selection.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+sigc::signal<void,NamedSelection*> NamedSelection::NamedSelectionCreated;
+
+NamedSelection::NamedSelection (string n, list<Playlist*>& l)
+ : name (n)
+{
+ playlists = l;
+ for (list<Playlist*>::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ (*i)->ref();
+ }
+ NamedSelectionCreated (this);
+}
+
+NamedSelection::NamedSelection (Session& session, const XMLNode& node)
+{
+ XMLNode* lists_node;
+ const XMLProperty* property;
+
+ if ((property = node.property ("name")) == 0) {
+ throw failed_constructor();
+ }
+
+ name = property->value();
+
+ if ((lists_node = find_named_node (node, "Playlists")) == 0) {
+ return;
+ }
+
+ XMLNodeList nlist = lists_node->children();
+ XMLNodeConstIterator niter;
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ const XMLNode* plnode;
+ string playlist_name;
+ Playlist* playlist;
+
+ plnode = *niter;
+
+ if ((property = plnode->property ("name")) != 0) {
+ if ((playlist = session.playlist_by_name (property->value())) != 0) {
+ playlist->ref();
+ playlists.push_back (playlist);
+ } else {
+ warning << compose (_("Chunk %1 uses an unknown playlist \"%2\""), name, property->value()) << endmsg;
+ }
+ } else {
+ error << compose (_("Chunk %1 contains misformed playlist information"), name) << endmsg;
+ throw failed_constructor();
+ }
+ }
+
+ NamedSelectionCreated (this);
+}
+
+NamedSelection::~NamedSelection ()
+{
+ for (list<Playlist*>::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ (*i)->unref();
+ }
+}
+
+int
+NamedSelection::set_state (const XMLNode& node)
+{
+ return 0;
+}
+
+XMLNode&
+NamedSelection::get_state ()
+{
+ XMLNode* root = new XMLNode ("NamedSelection");
+ XMLNode* child;
+
+ root->add_property ("name", name);
+ child = root->add_child ("Playlists");
+
+ for (list<Playlist*>::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ XMLNode* plnode = new XMLNode ("Playlist");
+
+ plnode->add_property ("name", (*i)->name());
+ child->add_child_nocopy (*plnode);
+ }
+
+ return *root;
+}
diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc
new file mode 100644
index 0000000000..fdf3e39f29
--- /dev/null
+++ b/libs/ardour/panner.cc
@@ -0,0 +1,1670 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <cerrno>
+#include <fstream>
+#include <cstdlib>
+#include <string>
+#include <cstdio>
+#include <locale.h>
+#include <unistd.h>
+#include <float.h>
+
+#include <pbd/error.h>
+#include <pbd/failed_constructor.h>
+#include <pbd/basename.h>
+#include <pbd/xml++.h>
+
+#include <ardour/session.h>
+#include <ardour/panner.h>
+#include <ardour/utils.h>
+
+#include <ardour/mix.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+float Panner::current_automation_version_number = 1.0;
+
+string EqualPowerStereoPanner::name = "Equal Power Stereo";
+string Multi2dPanner::name = "Multiple (2D)";
+
+/* this is a default mapper of MIDI control values to a pan position
+ others can be imagined. see Panner::set_midi_to_pan_function().
+*/
+
+static pan_t direct_midi_to_pan (double fract) {
+ return fract;
+}
+
+static double direct_pan_to_midi (pan_t val) {
+ return val;
+}
+
+StreamPanner::StreamPanner (Panner& p)
+ : parent (p),
+ _midi_control (*this, (MIDI::Port*) 0)
+{
+ _muted = false;
+
+ x = 0.5;
+ y = 0.5;
+ z = 0.5;
+}
+
+StreamPanner::~StreamPanner ()
+{
+}
+
+StreamPanner::MIDIControl::MIDIControl (StreamPanner& s, MIDI::Port* port)
+ : MIDI::Controllable (port, 0), sp (s), setting(false)
+{
+ midi_to_pan = direct_midi_to_pan;
+ pan_to_midi = direct_pan_to_midi;
+ last_written = 0; /* XXX need a good out-of-bound-value */
+}
+
+void
+StreamPanner::MIDIControl::set_value (float val)
+{
+ setting = true;
+ sp.set_position (midi_to_pan (val));
+ setting = false;
+}
+
+void
+StreamPanner::MIDIControl::send_feedback (pan_t value)
+{
+
+ if (!setting && get_midi_feedback() && pan_to_midi) {
+ MIDI::byte val = (MIDI::byte) (pan_to_midi (value) * 127.0f);
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ sp.get_parent().session().send_midi_message (get_port(), ev, ch, data);
+ }
+
+ // send_midi_feedback (pan_to_midi (val));
+ }
+
+}
+
+MIDI::byte*
+StreamPanner::MIDIControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, pan_t val, bool force)
+{
+ if (get_midi_feedback() && pan_to_midi && bufsize > 2) {
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::byte pm;
+ if (get_control_info (ch, ev, additional)) {
+
+ pm = (MIDI::byte) (pan_to_midi (val) * 127.0);
+
+ if (pm != last_written || force) {
+ *buf++ = (0xF0 & ev) | (0xF & ch);
+ *buf++ = additional; /* controller number */
+ *buf++ = pm;
+ last_written = pm;
+ bufsize -= 3;
+ }
+ }
+ }
+
+ return buf;
+}
+
+
+void
+StreamPanner::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ _midi_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_control.midi_rebind (port, chn);
+}
+
+void
+StreamPanner::set_muted (bool yn)
+{
+ if (yn != _muted) {
+ _muted = yn;
+ StateChanged ();
+ }
+}
+
+void
+StreamPanner::set_position (float xpos, bool link_call)
+{
+ if (!link_call && parent.linked()) {
+ parent.set_position (xpos, *this);
+ }
+
+ if (x != xpos) {
+ x = xpos;
+ update ();
+ Changed ();
+
+ if (parent.session().get_midi_feedback()) {
+ _midi_control.send_feedback (x);
+ }
+ }
+}
+
+void
+StreamPanner::set_position (float xpos, float ypos, bool link_call)
+{
+ if (!link_call && parent.linked()) {
+ parent.set_position (xpos, ypos, *this);
+ }
+
+ if (x != xpos || y != ypos) {
+
+ x = xpos;
+ y = ypos;
+ update ();
+ Changed ();
+ }
+}
+
+void
+StreamPanner::set_position (float xpos, float ypos, float zpos, bool link_call)
+{
+ if (!link_call && parent.linked()) {
+ parent.set_position (xpos, ypos, zpos, *this);
+ }
+
+ if (x != xpos || y != ypos || z != zpos) {
+ x = xpos;
+ y = ypos;
+ z = zpos;
+ update ();
+ Changed ();
+ }
+}
+
+int
+StreamPanner::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ XMLNodeConstIterator iter;
+ XMLNodeList midi_kids;
+
+ if ((prop = node.property (X_("muted")))) {
+ set_muted (prop->value() == "yes");
+ }
+
+ midi_kids = node.children ("MIDI");
+
+ for (iter = midi_kids.begin(); iter != midi_kids.end(); ++iter) {
+
+ XMLNodeList kids;
+ XMLNodeConstIterator miter;
+ XMLNode* child;
+
+ kids = (*iter)->children ();
+
+ for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+ child =* miter;
+
+ if (child->name() == "pan") {
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* ditto */
+ MIDI::channel_t chn = 0; /* ditto */
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_control.set_control_type (chn, ev, additional);
+ } else {
+ error << _("MIDI pan control specification is incomplete, so it has been ignored") << endmsg;
+ }
+ }
+ }
+ }
+
+
+ return 0;
+}
+
+void
+StreamPanner::add_state (XMLNode& node)
+{
+ node.add_property (X_("muted"), (muted() ? "yes" : "no"));
+
+ /* MIDI control */
+
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ XMLNode* midi_node = 0;
+ XMLNode* child;
+
+ if (_midi_control.get_control_info (chn, ev, additional)) {
+
+ midi_node = node.add_child ("MIDI");
+
+ child = midi_node->add_child ("pan");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+
+}
+
+
+bool
+StreamPanner::get_midi_node_info (XMLNode * node, MIDI::eventType & ev, MIDI::channel_t & chan, MIDI::byte & additional)
+{
+ bool ok = true;
+ const XMLProperty* prop;
+ int xx;
+
+ if ((prop = node->property ("event")) != 0) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ ev = (MIDI::eventType) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = node->property ("channel")) != 0)) {
+ sscanf (prop->value().c_str(), "%d", &xx);
+ chan = (MIDI::channel_t) xx;
+ } else {
+ ok = false;
+ }
+
+ if (ok && ((prop = node->property ("additional")) != 0)) {
+ sscanf (prop->value().c_str(), "0x%x", &xx);
+ additional = (MIDI::byte) xx;
+ }
+
+ return ok;
+}
+
+bool
+StreamPanner::set_midi_node_info (XMLNode * node, MIDI::eventType ev, MIDI::channel_t chan, MIDI::byte additional)
+{
+ char buf[32];
+
+ snprintf (buf, sizeof(buf), "0x%x", ev);
+ node->add_property ("event", buf);
+ snprintf (buf, sizeof(buf), "%d", chan);
+ node->add_property ("channel", buf);
+ snprintf (buf, sizeof(buf), "0x%x", additional);
+ node->add_property ("additional", buf);
+
+ return true;
+}
+
+/*---------------------------------------------------------------------- */
+
+BaseStereoPanner::BaseStereoPanner (Panner& p)
+ : StreamPanner (p), _automation (0.0, 1.0, 0.5)
+{
+}
+
+BaseStereoPanner::~BaseStereoPanner ()
+{
+}
+
+void
+BaseStereoPanner::snapshot (jack_nframes_t now)
+{
+ if (_automation.automation_state() == Write || _automation.automation_state() == Touch) {
+ _automation.rt_add (now, x);
+ }
+}
+
+void
+BaseStereoPanner::transport_stopped (jack_nframes_t frame)
+{
+ _automation.reposition_for_rt_add (frame);
+
+ if (_automation.automation_state() != Off) {
+
+ if (_automation.automation_write()) {
+ _automation.save_state (_("automation write pass"));
+ }
+
+ set_position (_automation.eval (frame));
+ }
+}
+
+void
+BaseStereoPanner::set_automation_style (AutoStyle style)
+{
+ _automation.set_automation_style (style);
+}
+
+void
+BaseStereoPanner::set_automation_state (AutoState state)
+{
+ if (state != _automation.automation_state()) {
+
+ _automation.set_automation_state (state);
+
+ if (state != Off) {
+ set_position (_automation.eval (parent.session().transport_frame()));
+ }
+ }
+}
+
+int
+BaseStereoPanner::save (ostream& out) const
+{
+ LocaleGuard lg (X_("POSIX"));
+
+ /* force a single format for numeric data to ease session interchange
+ across national boundaries.
+ */
+
+ out << "begin" << endl;
+
+ for (AutomationList::const_iterator i = _automation.const_begin(); i != _automation.const_end(); ++i) {
+ out << '\t' << (jack_nframes_t) floor ((*i)->when) << ' ' << (*i)->value << endl;
+ if (!out) {
+ error << compose (_("error writing pan automation file (%s)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+ out << "end" << endl;
+
+ return 0;
+}
+
+int
+BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt)
+{
+ char line[128];
+ LocaleGuard lg (X_("POSIX"));
+
+ _automation.clear ();
+
+ while (in.getline (line, sizeof (line), '\n')) {
+ jack_nframes_t when;
+ double value;
+
+ ++linecnt;
+
+ if (strcmp (line, "end") == 0) {
+ break;
+ }
+
+ if (sscanf (line, "%" PRIu32 " %lf", &when, &value) != 2) {
+ warning << compose(_("badly formatted pan automation event record at line %1 of %2 (ignored) [%3]"), linecnt, path, line) << endmsg;
+ continue;
+ }
+
+ _automation.add (when, value, true);
+ }
+
+ /* now that we are done loading */
+
+ _automation.save_state (_("loaded from disk"));
+ _automation.StateChanged (Change (0));
+
+ return 0;
+}
+
+void
+BaseStereoPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, jack_nframes_t nframes)
+{
+ pan_t delta;
+ Sample* dst;
+ pan_t pan;
+
+ if (_muted) {
+ return;
+ }
+
+ /* LEFT */
+
+ dst = obufs[0];
+
+ if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc
+
+ /* interpolate over 64 frames or nframes, whichever is smaller */
+
+ jack_nframes_t limit = min ((jack_nframes_t)64, nframes);
+ jack_nframes_t n;
+
+ delta = -(delta / (float) (limit));
+
+ for (n = 0; n < limit; n++) {
+ left_interp = left_interp + delta;
+ left = left_interp + 0.9 * (left - left_interp);
+ dst[n] += src[n] * left * gain_coeff;
+ }
+
+ pan = left * gain_coeff;
+
+ Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
+
+ } else {
+
+ left = desired_left;
+ left_interp = left;
+
+ if ((pan = (left * gain_coeff)) != 1.0f) {
+
+ if (pan != 0.0f) {
+
+ Session::mix_buffers_with_gain(dst,src,nframes,pan);
+
+ /* mark that we wrote into the buffer */
+
+ // obufs[0] = 0;
+
+ }
+
+ } else {
+
+ Session::mix_buffers_no_gain(dst,src,nframes);
+
+ /* mark that we wrote into the buffer */
+
+ // obufs[0] = 0;
+ }
+ }
+
+ /* RIGHT */
+
+ dst = obufs[1];
+
+ if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc
+
+ /* interpolate over 64 frames or nframes, whichever is smaller */
+
+ jack_nframes_t limit = min ((jack_nframes_t)64, nframes);
+ jack_nframes_t n;
+
+ delta = -(delta / (float) (limit));
+
+ for (n = 0; n < limit; n++) {
+ right_interp = right_interp + delta;
+ right = right_interp + 0.9 * (right - right_interp);
+ dst[n] += src[n] * right * gain_coeff;
+ }
+
+ pan = right * gain_coeff;
+
+ Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
+
+ /* XXX it would be nice to mark the buffer as written to */
+
+ } else {
+
+ right = desired_right;
+ right_interp = right;
+
+ if ((pan = (right * gain_coeff)) != 1.0f) {
+
+ if (pan != 0.0f) {
+
+ Session::mix_buffers_with_gain(dst,src,nframes,pan);
+
+ /* XXX it would be nice to mark the buffer as written to */
+ }
+
+ } else {
+
+ Session::mix_buffers_no_gain(dst,src,nframes);
+
+ /* XXX it would be nice to mark the buffer as written to */
+ }
+ }
+}
+
+/*---------------------------------------------------------------------- */
+
+EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p)
+ : BaseStereoPanner (p)
+{
+ update ();
+
+ left = desired_left;
+ right = desired_right;
+ left_interp = left;
+ right_interp = right;
+}
+
+EqualPowerStereoPanner::~EqualPowerStereoPanner ()
+{
+}
+
+void
+EqualPowerStereoPanner::update ()
+{
+ /* it would be very nice to split this out into a virtual function
+ that can be accessed from BaseStereoPanner and used in distribute_automated().
+
+ but the place where its used in distribute_automated() is a tight inner loop,
+ and making "nframes" virtual function calls to compute values is an absurd
+ overhead.
+ */
+
+ /* x == 0 => hard left
+ x == 1 => hard right
+ */
+
+ float panR = x;
+ float panL = 1 - panR;
+
+ const float pan_law_attenuation = -3.0f;
+ const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+
+ desired_left = panL * (scale * panL + 1.0f - scale);
+ desired_right = panR * (scale * panR + 1.0f - scale);
+
+ effective_x = x;
+}
+
+void
+EqualPowerStereoPanner::distribute_automated (Sample* src, Sample** obufs,
+ jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes,
+ pan_t** buffers)
+{
+ Sample* dst;
+ pan_t* pbuf;
+
+ /* fetch positional data */
+
+ if (!_automation.rt_safe_get_vector (start, end, buffers[0], nframes)) {
+ /* fallback */
+ if (!_muted) {
+ distribute (src, obufs, 1.0, nframes);
+ }
+ return;
+ }
+
+ /* store effective pan position. do this even if we are muted */
+
+ effective_x = buffers[0][nframes-1];
+
+ if (_muted) {
+ return;
+ }
+
+ /* apply pan law to convert positional data into pan coefficients for
+ each buffer (output)
+ */
+
+ const float pan_law_attenuation = -3.0f;
+ const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+
+ float panR = buffers[0][n];
+ float panL = 1 - panR;
+
+ buffers[0][n] = panL * (scale * panL + 1.0f - scale);
+ buffers[1][n] = panR * (scale * panR + 1.0f - scale);
+ }
+
+ /* LEFT */
+
+ dst = obufs[0];
+ pbuf = buffers[0];
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n] * pbuf[n];
+ }
+
+ /* XXX it would be nice to mark the buffer as written to */
+
+ /* RIGHT */
+
+ dst = obufs[1];
+ pbuf = buffers[1];
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n] * pbuf[n];
+ }
+
+ /* XXX it would be nice to mark the buffer as written to */
+}
+
+StreamPanner*
+EqualPowerStereoPanner::factory (Panner& parent)
+{
+ return new EqualPowerStereoPanner (parent);
+}
+
+XMLNode&
+EqualPowerStereoPanner::get_state (void)
+{
+ return state (true);
+}
+
+XMLNode&
+EqualPowerStereoPanner::state (bool full_state)
+{
+ XMLNode* root = new XMLNode ("StreamPanner");
+ char buf[64];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof (buf), "%f", x);
+ root->add_property (X_("x"), buf);
+ root->add_property (X_("type"), EqualPowerStereoPanner::name);
+ if (full_state) {
+ snprintf (buf, sizeof (buf), "0x%x", _automation.automation_state());
+ } else {
+ /* never store automation states other than off in a template */
+ snprintf (buf, sizeof (buf), "0x%x", ARDOUR::Off);
+ }
+ root->add_property (X_("automation-state"), buf);
+ snprintf (buf, sizeof (buf), "0x%x", _automation.automation_style());
+ root->add_property (X_("automation-style"), buf);
+
+ StreamPanner::add_state (*root);
+
+ return *root;
+}
+
+int
+EqualPowerStereoPanner::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ int x;
+ float pos;
+ LocaleGuard lg (X_("POSIX"));
+
+ if ((prop = node.property (X_("x")))) {
+ pos = atof (prop->value().c_str());
+ set_position (pos, true);
+ }
+
+ if ((prop = node.property (X_("automation-state")))) {
+ sscanf (prop->value().c_str(), "0x%x", &x);
+ _automation.set_automation_state ((AutoState) x);
+
+ if (x != Off) {
+ set_position (_automation.eval (parent.session().transport_frame()));
+ }
+ }
+
+ if ((prop = node.property (X_("automation-style")))) {
+ sscanf (prop->value().c_str(), "0x%x", &x);
+ _automation.set_automation_style ((AutoStyle) x);
+ }
+
+ StreamPanner::set_state (node);
+
+ return 0;
+}
+
+/*----------------------------------------------------------------------*/
+
+Multi2dPanner::Multi2dPanner (Panner& p)
+ : StreamPanner (p), _automation (0.0, 1.0, 0.5) // XXX useless
+{
+ update ();
+}
+
+Multi2dPanner::~Multi2dPanner ()
+{
+}
+
+void
+Multi2dPanner::snapshot (jack_nframes_t now)
+{
+ // how?
+}
+
+void
+Multi2dPanner::transport_stopped (jack_nframes_t frame)
+{
+ //what?
+}
+
+void
+Multi2dPanner::set_automation_style (AutoStyle style)
+{
+ //what?
+}
+
+void
+Multi2dPanner::set_automation_state (AutoState state)
+{
+ // what?
+}
+
+void
+Multi2dPanner::update ()
+{
+ static const float BIAS = FLT_MIN;
+ uint32_t i;
+ uint32_t nouts = parent.outputs.size();
+ float dsq[nouts];
+ float f, fr;
+ vector<pan_t> pans;
+
+ f = 0.0f;
+
+ for (i = 0; i < nouts; i++) {
+ dsq[i] = ((x - parent.outputs[i].x) * (x - parent.outputs[i].x) + (y - parent.outputs[i].y) * (y - parent.outputs[i].y) + BIAS);
+ if (dsq[i] < 0.0) {
+ dsq[i] = 0.0;
+ }
+ f += dsq[i] * dsq[i];
+ }
+ fr = 1.0f / sqrtf(f);
+
+ for (i = 0; i < nouts; ++i) {
+ parent.outputs[i].desired_pan = 1.0f - (dsq[i] * fr);
+ }
+
+ effective_x = x;
+}
+
+void
+Multi2dPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, jack_nframes_t nframes)
+{
+ Sample* dst;
+ pan_t pan;
+ vector<Panner::Output>::iterator o;
+ uint32_t n;
+
+ if (_muted) {
+ return;
+ }
+
+
+ for (n = 0, o = parent.outputs.begin(); o != parent.outputs.end(); ++o, ++n) {
+
+ dst = obufs[n];
+
+#ifdef CAN_INTERP
+ if (fabsf ((delta = (left_interp - desired_left))) > 0.002) { // about 1 degree of arc
+
+ /* interpolate over 64 frames or nframes, whichever is smaller */
+
+ jack_nframes_t limit = min ((jack_nframes_t)64, nframes);
+ jack_nframes_t n;
+
+ delta = -(delta / (float) (limit));
+
+ for (n = 0; n < limit; n++) {
+ left_interp = left_interp + delta;
+ left = left_interp + 0.9 * (left - left_interp);
+ dst[n] += src[n] * left * gain_coeff;
+ }
+
+ pan = left * gain_coeff;
+
+ for (; n < nframes; ++n) {
+ dst[n] += src[n] * pan;
+ }
+
+ } else {
+
+#else
+ pan = (*o).desired_pan;
+
+ if ((pan *= gain_coeff) != 1.0f) {
+
+ if (pan != 0.0f) {
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n] * pan;
+ }
+
+ }
+
+
+ } else {
+
+ for (jack_nframes_t n = 0; n < nframes; ++n) {
+ dst[n] += src[n];
+ }
+
+ }
+#endif
+#ifdef CAN_INTERP
+ }
+#endif
+ }
+
+ return;
+}
+
+void
+Multi2dPanner::distribute_automated (Sample* src, Sample** obufs,
+ jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes,
+ pan_t** buffers)
+{
+ if (_muted) {
+ return;
+ }
+
+ /* what ? */
+
+ return;
+}
+
+StreamPanner*
+Multi2dPanner::factory (Panner& p)
+{
+ return new Multi2dPanner (p);
+}
+
+int
+Multi2dPanner::load (istream& in, string path, uint32_t& linecnt)
+{
+ return 0;
+}
+
+int
+Multi2dPanner::save (ostream& out) const
+{
+ return 0;
+}
+
+XMLNode&
+Multi2dPanner::get_state (void)
+{
+ return state (true);
+}
+
+XMLNode&
+Multi2dPanner::state (bool full_state)
+{
+ XMLNode* root = new XMLNode ("StreamPanner");
+ char buf[64];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof (buf), "%f", x);
+ root->add_property (X_("x"), buf);
+ snprintf (buf, sizeof (buf), "%f", y);
+ root->add_property (X_("y"), buf);
+ root->add_property (X_("type"), Multi2dPanner::name);
+
+ return *root;
+}
+
+int
+Multi2dPanner::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+ float newx,newy;
+ LocaleGuard lg (X_("POSIX"));
+
+ newx = -1;
+ newy = -1;
+
+ if ((prop = node.property (X_("x")))) {
+ newx = atof (prop->value().c_str());
+ }
+
+ if ((prop = node.property (X_("y")))) {
+ newy = atof (prop->value().c_str());
+ }
+
+ if (x < 0 || y < 0) {
+ error << _("badly-formed positional data for Multi2dPanner - ignored")
+ << endmsg;
+ return -1;
+ }
+
+ set_position (newx, newy);
+ return 0;
+}
+
+/*---------------------------------------------------------------------- */
+
+Panner::Panner (string name, Session& s)
+ : _session (s)
+{
+ set_name (name);
+ _linked = false;
+ _link_direction = SameDirection;
+ _bypassed = false;
+
+ reset_midi_control (_session.mmc_port(), _session.get_mmc_control());
+}
+
+Panner::~Panner ()
+{
+}
+
+void
+Panner::set_linked (bool yn)
+{
+ if (yn != _linked) {
+ _linked = yn;
+ _session.set_dirty ();
+ LinkStateChanged (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Panner::set_link_direction (LinkDirection ld)
+{
+ if (ld != _link_direction) {
+ _link_direction = ld;
+ _session.set_dirty ();
+ LinkStateChanged (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Panner::set_name (string str)
+{
+ automation_path = _session.automation_dir();
+ automation_path += _session.snap_name();
+ automation_path += "-pan-";
+ automation_path += legalize_for_path (str);
+ automation_path += ".automation";
+}
+
+
+void
+Panner::set_bypassed (bool yn)
+{
+ if (yn != _bypassed) {
+ _bypassed = yn;
+ StateChanged ();
+ }
+}
+
+
+void
+Panner::reset (uint32_t nouts, uint32_t npans)
+{
+ uint32_t n;
+ bool changed = false;
+
+
+ if (nouts < 2 || (nouts == outputs.size() && npans == size())) {
+ return;
+ }
+
+ n = size();
+ clear ();
+
+ if (n != npans) {
+ changed = true;
+ }
+
+ n = outputs.size();
+ outputs.clear ();
+
+ if (n != nouts) {
+ changed = true;
+ }
+
+ switch (nouts) {
+ case 0:
+ break;
+
+ case 1:
+ fatal << _("programming error:")
+ << X_("Panner::reset() called with a single output")
+ << endmsg;
+ /*NOTREACHED*/
+ break;
+
+ case 2:
+ /* line */
+ outputs.push_back (Output (0, 0));
+ outputs.push_back (Output (1.0, 0));
+
+ for (n = 0; n < npans; ++n) {
+ push_back (new EqualPowerStereoPanner (*this));
+ }
+ break;
+
+ case 3: // triangle
+ outputs.push_back (Output (0.5, 0));
+ outputs.push_back (Output (0, 1.0));
+ outputs.push_back (Output (1.0, 1.0));
+
+ for (n = 0; n < npans; ++n) {
+ push_back (new Multi2dPanner (*this));
+ }
+
+ break;
+
+ case 4: // square
+ outputs.push_back (Output (0, 0));
+ outputs.push_back (Output (1.0, 0));
+ outputs.push_back (Output (1.0, 1.0));
+ outputs.push_back (Output (0, 1.0));
+
+ for (n = 0; n < npans; ++n) {
+ push_back (new Multi2dPanner (*this));
+ }
+
+ break;
+
+ case 5: //square+offcenter center
+ outputs.push_back (Output (0, 0));
+ outputs.push_back (Output (1.0, 0));
+ outputs.push_back (Output (1.0, 1.0));
+ outputs.push_back (Output (0, 1.0));
+ outputs.push_back (Output (0.5, 0.75));
+
+ for (n = 0; n < npans; ++n) {
+ push_back (new Multi2dPanner (*this));
+ }
+
+ break;
+
+ default:
+ /* XXX horrible placement. FIXME */
+ for (n = 0; n < nouts; ++n) {
+ outputs.push_back (Output (0.1 * n, 0.1 * n));
+ }
+
+ for (n = 0; n < npans; ++n) {
+ push_back (new Multi2dPanner (*this));
+ }
+
+ break;
+ }
+
+ for (iterator x = begin(); x != end(); ++x) {
+ (*x)->update ();
+ }
+
+ reset_midi_control (_session.mmc_port(), _session.get_mmc_control());
+
+ /* force hard left/right panning in a common case: 2in/2out
+ */
+
+ if (npans == 2 && outputs.size() == 2) {
+
+ /* Do this only if we changed configuration, or our configuration
+ appears to be the default set up (center).
+ */
+
+ float left;
+ float right;
+
+ front()->get_position (left);
+ back()->get_position (right);
+
+ if (changed || ((left == 0.5) && (right == 0.5))) {
+
+ front()->set_position (0.0);
+ front()->automation().reset_default (0.0);
+
+ back()->set_position (1.0);
+ back()->automation().reset_default (1.0);
+
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ Changed (); /* EMIT SIGNAL */
+ }
+
+ return;
+}
+
+void
+Panner::remove (uint32_t which)
+{
+ vector<StreamPanner*>::iterator i;
+ for (i = begin(); i != end() && which; ++i, --which);
+
+ if (i != end()) {
+ delete *i;
+ erase (i);
+ }
+}
+
+void
+Panner::clear ()
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ delete *i;
+ }
+
+ vector<StreamPanner*>::clear ();
+}
+
+void
+Panner::set_automation_style (AutoStyle style)
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->set_automation_style (style);
+ }
+ _session.set_dirty ();
+}
+
+void
+Panner::set_automation_state (AutoState state)
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->set_automation_state (state);
+ }
+ _session.set_dirty ();
+}
+
+AutoState
+Panner::automation_state () const
+{
+ if (!empty()) {
+ return front()->automation().automation_state ();
+ } else {
+ return Off;
+ }
+}
+
+AutoStyle
+Panner::automation_style () const
+{
+ if (!empty()) {
+ return front()->automation().automation_style ();
+ } else {
+ return Absolute;
+ }
+}
+
+void
+Panner::transport_stopped (jack_nframes_t frame)
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->transport_stopped (frame);
+ }
+}
+
+void
+Panner::snapshot (jack_nframes_t now)
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->snapshot (now);
+ }
+}
+
+void
+Panner::clear_automation ()
+{
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->automation().clear ();
+ }
+ _session.set_dirty ();
+}
+
+int
+Panner::save () const
+{
+ ofstream out (automation_path.c_str());
+
+ if (!out) {
+ error << compose (_("cannot open pan automation file \"%1\" for saving (%s)"), automation_path, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ out << X_("version ") << current_automation_version_number << endl;
+
+ for (vector<StreamPanner*>::const_iterator i = begin(); i != end(); ++i) {
+ if ((*i)->save (out)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Panner::load ()
+{
+ char line[128];
+ uint32_t linecnt = 0;
+ float version;
+ iterator sp;
+ LocaleGuard lg (X_("POSIX"));
+
+ if (automation_path.length() == 0) {
+ return 0;
+ }
+
+ if (access (automation_path.c_str(), F_OK)) {
+ return 0;
+ }
+
+ ifstream in (automation_path.c_str());
+
+ if (!in) {
+ error << compose (_("cannot open pan automation file %1 (%2)"),
+ automation_path, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ sp = begin();
+
+ while (in.getline (line, sizeof(line), '\n')) {
+
+ if (++linecnt == 1) {
+ if (memcmp (line, X_("version"), 7) == 0) {
+ if (sscanf (line, "version %f", &version) != 1) {
+ error << compose(_("badly formed version number in pan automation event file \"%1\""), automation_path) << endmsg;
+ return -1;
+ }
+ } else {
+ error << compose(_("no version information in pan automation event file \"%1\" (first line = %2)"),
+ automation_path, line) << endmsg;
+ return -1;
+ }
+
+ if (version != current_automation_version_number) {
+ error << compose(_("mismatched pan automation event file version (%1)"), version) << endmsg;
+ return -1;
+ }
+
+ continue;
+ }
+
+ if (strlen (line) == 0 || line[0] == '#') {
+ continue;
+ }
+
+ if (strcmp (line, "begin") == 0) {
+
+ if (sp == end()) {
+ error << compose (_("too many panner states found in pan automation file %1"),
+ automation_path)
+ << endmsg;
+ return -1;
+ }
+
+ if ((*sp)->load (in, automation_path, linecnt)) {
+ return -1;
+ }
+
+ ++sp;
+ }
+ }
+
+ return 0;
+}
+
+struct PanPlugins {
+ string name;
+ uint32_t nouts;
+ StreamPanner* (*factory)(Panner&);
+};
+
+PanPlugins pan_plugins[] = {
+ { EqualPowerStereoPanner::name, 2, EqualPowerStereoPanner::factory },
+ { Multi2dPanner::name, 3, Multi2dPanner::factory },
+ { string (""), 0 }
+};
+
+XMLNode&
+Panner::get_state (void)
+{
+ return state (true);
+}
+
+XMLNode&
+Panner::state (bool full)
+{
+ XMLNode* root = new XMLNode (X_("Panner"));
+ char buf[32];
+
+ for (iterator p = begin(); p != end(); ++p) {
+ root->add_child_nocopy ((*p)->state (full));
+ }
+
+ root->add_property (X_("linked"), (_linked ? "yes" : "no"));
+ snprintf (buf, sizeof (buf), "%d", _link_direction);
+ root->add_property (X_("link_direction"), buf);
+ root->add_property (X_("bypassed"), (bypassed() ? "yes" : "no"));
+
+ /* add each output */
+
+ for (vector<Panner::Output>::iterator o = outputs.begin(); o != outputs.end(); ++o) {
+ XMLNode* onode = new XMLNode (X_("Output"));
+ snprintf (buf, sizeof (buf), "%f", (*o).x);
+ onode->add_property (X_("x"), buf);
+ snprintf (buf, sizeof (buf), "%f", (*o).y);
+ onode->add_property (X_("y"), buf);
+ root->add_child_nocopy (*onode);
+ }
+
+ if (full) {
+ if (save () == 0) {
+ root->add_property (X_("automation"), PBD::basename (automation_path));
+ }
+ }
+
+ return *root;
+}
+
+int
+Panner::set_state (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ const XMLProperty *prop;
+ uint32_t i;
+ StreamPanner* sp;
+ LocaleGuard lg (X_("POSIX"));
+
+ clear ();
+ outputs.clear ();
+
+ if ((prop = node.property (X_("linked"))) != 0) {
+ set_linked (prop->value() == "yes");
+ }
+
+
+ if ((prop = node.property (X_("bypassed"))) != 0) {
+ set_bypassed (prop->value() == "yes");
+ }
+
+ if ((prop = node.property (X_("link_direction"))) != 0) {
+ sscanf (prop->value().c_str(), "%d", &i);
+ set_link_direction ((LinkDirection) (i));
+ }
+
+ nlist = node.children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == X_("Output")) {
+
+ float x, y;
+
+ prop = (*niter)->property (X_("x"));
+ sscanf (prop->value().c_str(), "%f", &x);
+
+ prop = (*niter)->property (X_("y"));
+ sscanf (prop->value().c_str(), "%f", &y);
+
+ outputs.push_back (Output (x, y));
+ }
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((*niter)->name() == X_("StreamPanner")) {
+
+ if ((prop = (*niter)->property (X_("type")))) {
+
+ for (i = 0; pan_plugins[i].factory; ++i) {
+ if (prop->value() == pan_plugins[i].name) {
+
+
+ /* note that we assume that all the stream panners
+ are of the same type. pretty good
+ assumption, but its still an assumption.
+ */
+
+ sp = pan_plugins[i].factory (*this);
+
+ if (sp->set_state (**niter) == 0) {
+ push_back (sp);
+ }
+
+ break;
+ }
+ }
+
+
+ if (!pan_plugins[i].factory) {
+ error << compose (_("Unknown panner plugin \"%1\" found in pan state - ignored"),
+ prop->value())
+ << endmsg;
+ }
+
+ } else {
+ error << _("panner plugin node has no type information!")
+ << endmsg;
+ return -1;
+ }
+
+ }
+ }
+
+ /* don't try to load automation if it wasn't marked as existing */
+
+ if ((prop = node.property (X_("automation")))) {
+
+ /* automation path is relative */
+
+ automation_path = _session.automation_dir();
+ automation_path += prop->value ();
+ }
+
+ return 0;
+}
+
+
+
+bool
+Panner::touching () const
+{
+ for (vector<StreamPanner*>::const_iterator i = begin(); i != end(); ++i) {
+ if ((*i)->automation().touching ()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+Panner::reset_midi_control (MIDI::Port* port, bool on)
+{
+ for (vector<StreamPanner*>::const_iterator i = begin(); i != end(); ++i) {
+ (*i)->reset_midi_control (port, on);
+ }
+}
+
+void
+Panner::set_position (float xpos, StreamPanner& orig)
+{
+ float xnow;
+ float xdelta ;
+ float xnew;
+
+ orig.get_position (xnow);
+ xdelta = xpos - xnow;
+
+ if (_link_direction == SameDirection) {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, true);
+ } else {
+ (*i)->get_position (xnow);
+ xnew = min (1.0f, xnow + xdelta);
+ xnew = max (0.0f, xnew);
+ (*i)->set_position (xnew, true);
+ }
+ }
+
+ } else {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, true);
+ } else {
+ (*i)->get_position (xnow);
+ xnew = min (1.0f, xnow - xdelta);
+ xnew = max (0.0f, xnew);
+ (*i)->set_position (xnew, true);
+ }
+ }
+ }
+}
+
+void
+Panner::set_position (float xpos, float ypos, StreamPanner& orig)
+{
+ float xnow, ynow;
+ float xdelta, ydelta;
+ float xnew, ynew;
+
+ orig.get_position (xnow, ynow);
+ xdelta = xpos - xnow;
+ ydelta = ypos - ynow;
+
+ if (_link_direction == SameDirection) {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, ypos, true);
+ } else {
+ (*i)->get_position (xnow, ynow);
+
+ xnew = min (1.0f, xnow + xdelta);
+ xnew = max (0.0f, xnew);
+
+ ynew = min (1.0f, ynow + ydelta);
+ ynew = max (0.0f, ynew);
+
+ (*i)->set_position (xnew, ynew, true);
+ }
+ }
+
+ } else {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, ypos, true);
+ } else {
+ (*i)->get_position (xnow, ynow);
+
+ xnew = min (1.0f, xnow - xdelta);
+ xnew = max (0.0f, xnew);
+
+ ynew = min (1.0f, ynow - ydelta);
+ ynew = max (0.0f, ynew);
+
+ (*i)->set_position (xnew, ynew, true);
+ }
+ }
+ }
+}
+
+void
+Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig)
+{
+ float xnow, ynow, znow;
+ float xdelta, ydelta, zdelta;
+ float xnew, ynew, znew;
+
+ orig.get_position (xnow, ynow, znow);
+ xdelta = xpos - xnow;
+ ydelta = ypos - ynow;
+ zdelta = zpos - znow;
+
+ if (_link_direction == SameDirection) {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, ypos, zpos, true);
+ } else {
+ (*i)->get_position (xnow, ynow, znow);
+
+ xnew = min (1.0f, xnow + xdelta);
+ xnew = max (0.0f, xnew);
+
+ ynew = min (1.0f, ynow + ydelta);
+ ynew = max (0.0f, ynew);
+
+ znew = min (1.0f, znow + zdelta);
+ znew = max (0.0f, znew);
+
+ (*i)->set_position (xnew, ynew, znew, true);
+ }
+ }
+
+ } else {
+
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ if (*i == &orig) {
+ (*i)->set_position (xpos, ypos, true);
+ } else {
+ (*i)->get_position (xnow, ynow, znow);
+
+ xnew = min (1.0f, xnow - xdelta);
+ xnew = max (0.0f, xnew);
+
+ ynew = min (1.0f, ynow - ydelta);
+ ynew = max (0.0f, ynew);
+
+ znew = min (1.0f, znow + zdelta);
+ znew = max (0.0f, znew);
+
+ (*i)->set_position (xnew, ynew, znew, true);
+ }
+ }
+ }
+}
+
+void
+Panner::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+ float xpos;
+
+ // do feedback for all panners
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->get_effective_position (xpos);
+
+ (*i)->midi_control().send_feedback (xpos);
+ }
+
+ }
+}
+
+MIDI::byte*
+Panner::write_midi_feedback (MIDI::byte* buf, int32_t& bufsize)
+{
+ AutoState astate = automation_state ();
+
+ if (_session.get_midi_feedback() &&
+ (astate == Play || (astate == Touch && !touching()))) {
+
+ float xpos;
+
+ // do feedback for all panners
+ for (vector<StreamPanner*>::iterator i = begin(); i != end(); ++i) {
+ (*i)->get_effective_position (xpos);
+
+ buf = (*i)->midi_control().write_feedback (buf, bufsize, xpos);
+ }
+
+ }
+
+ return buf;
+}
+
diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc
new file mode 100644
index 0000000000..b28e193c9b
--- /dev/null
+++ b/libs/ardour/playlist.cc
@@ -0,0 +1,1754 @@
+/*
+ Copyright (C) 2000-2003 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.
+
+ $Id$
+*/
+
+#include <set>
+#include <fstream>
+#include <algorithm>
+#include <unistd.h>
+#include <cerrno>
+#include <string>
+#include <climits>
+
+#include <sigc++/bind.h>
+
+#include <pbd/failed_constructor.h>
+#include <pbd/stl_delete.h>
+#include <pbd/xml++.h>
+
+#include <ardour/playlist.h>
+#include <ardour/session.h>
+#include <ardour/region.h>
+#include <ardour/region_factory.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+sigc::signal<void,Playlist*> Playlist::PlaylistCreated;
+
+struct ShowMeTheList {
+ ShowMeTheList (Playlist *pl, const string& n) : playlist (pl), name (n) {}
+ ~ShowMeTheList () {
+ cerr << ">>>>" << name << endl; playlist->dump(); cerr << "<<<<" << name << endl << endl;
+ };
+ Playlist *playlist;
+ string name;
+};
+
+struct RegionSortByLayer {
+ bool operator() (Region *a, Region *b) {
+ return a->layer() < b->layer();
+ }
+};
+
+struct RegionSortByPosition {
+ bool operator() (Region *a, Region *b) {
+ return a->position() < b->position();
+ }
+};
+
+struct RegionSortByLastLayerOp {
+ bool operator() (Region *a, Region *b) {
+ return a->last_layer_op() < b->last_layer_op();
+ }
+};
+
+Playlist::Playlist (Session& sess, string nom, bool hide)
+ : _session (sess)
+{
+ init (hide);
+ _name = nom;
+ _orig_diskstream_id = 0;
+
+}
+
+Playlist::Playlist (Session& sess, const XMLNode& node, bool hide)
+ : _session (sess)
+{
+ init (hide);
+ _name = "unnamed"; /* reset by set_state */
+ _orig_diskstream_id = 0;
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+}
+
+Playlist::Playlist (const Playlist& other, string namestr, bool hide)
+ : _name (namestr), _session (other._session), _orig_diskstream_id(other._orig_diskstream_id)
+{
+ init (hide);
+
+ other.copy_regions (regions);
+
+ for (list<Region*>::iterator x = regions.begin(); x != regions.end(); ++x) {
+ (*x)->set_playlist (this);
+ }
+}
+
+Playlist::Playlist (const Playlist& other, jack_nframes_t start, jack_nframes_t cnt, string str, bool hide)
+ : _name (str), _session (other._session), _orig_diskstream_id(other._orig_diskstream_id)
+{
+ RegionLock rlock2 (&((Playlist&)other));
+
+ jack_nframes_t end = start + cnt - 1;
+
+ init (hide);
+
+ for (RegionList::const_iterator i = other.regions.begin(); i != other.regions.end(); i++) {
+
+ Region *region;
+ Region *new_region;
+ jack_nframes_t offset = 0;
+ jack_nframes_t position = 0;
+ jack_nframes_t len = 0;
+ string new_name;
+ OverlapType overlap;
+
+ region = *i;
+
+ overlap = region->coverage (start, end);
+
+ switch (overlap) {
+ case OverlapNone:
+ continue;
+
+ case OverlapInternal:
+ offset = start - region->position();
+ position = 0;
+ len = cnt;
+ break;
+
+ case OverlapStart:
+ offset = 0;
+ position = region->position() - start;
+ len = end - region->position();
+ break;
+
+ case OverlapEnd:
+ offset = start - region->position();
+ position = 0;
+ len = region->length() - offset;
+ break;
+
+ case OverlapExternal:
+ offset = 0;
+ position = region->position() - start;
+ len = region->length();
+ break;
+ }
+
+ _session.region_name (new_name, region->name(), false);
+
+ new_region = createRegion (*region, offset, len, new_name, region->layer(), region->flags());
+
+ add_region_internal (new_region, position, true);
+ }
+
+ /* this constructor does NOT notify others (session) */
+}
+
+void
+Playlist::ref ()
+{
+ ++_refcnt;
+ InUse (this, true); /* EMIT SIGNAL */
+}
+
+void
+Playlist::unref ()
+{
+ if (_refcnt > 0) {
+ _refcnt--;
+ }
+ if (_refcnt == 0) {
+ InUse (this, false); /* EMIT SIGNAL */
+
+ if (_hidden) {
+ /* nobody knows we exist */
+ delete this;
+ }
+ }
+}
+
+
+void
+Playlist::copy_regions (RegionList& newlist) const
+{
+ RegionLock rlock (const_cast<Playlist *> (this));
+
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ newlist.push_back (createRegion (**i));
+ }
+}
+
+void
+Playlist::init (bool hide)
+{
+ atomic_set (&block_notifications, 0);
+ atomic_set (&ignore_state_changes, 0);
+ pending_modified = false;
+ pending_length = false;
+ _refcnt = 0;
+ _hidden = hide;
+ _splicing = false;
+ _nudging = false;
+ in_set_state = false;
+ _edit_mode = _session.get_edit_mode();
+ in_flush = false;
+ in_partition = false;
+ subcnt = 0;
+ _read_data_count = 0;
+ _frozen = false;
+ save_on_thaw = false;
+ layer_op_counter = 0;
+ freeze_length = 0;
+
+ // _session.LayerModelChanged.connect (slot (*this, &Playlist::relayer));
+
+ Modified.connect (mem_fun (*this, &Playlist::mark_session_dirty));
+}
+
+Playlist::Playlist (const Playlist& pl)
+ : _session (pl._session)
+{
+ fatal << _("playlist const copy constructor called") << endmsg;
+}
+
+Playlist::Playlist (Playlist& pl)
+ : _session (pl._session)
+{
+ fatal << _("playlist non-const copy constructor called") << endmsg;
+}
+
+Playlist::~Playlist ()
+{
+}
+
+void
+Playlist::set_name (const string& str)
+{
+ /* in a typical situation, a playlist is being used
+ by one diskstream and also is referenced by the
+ Session. if there are more references than that,
+ then don't change the name.
+ */
+
+ if (_refcnt > 2) {
+ return;
+ }
+
+ _name = str;
+ NameChanged(); /* EMIT SIGNAL */
+}
+
+/***********************************************************************
+ CHANGE NOTIFICATION HANDLING
+
+ Notifications must be delayed till the region_lock is released. This
+ is necessary because handlers for the signals may need to acquire
+ the lock (e.g. to read from the playlist).
+ ***********************************************************************/
+
+void
+Playlist::freeze ()
+{
+ delay_notifications ();
+ atomic_inc (&ignore_state_changes);
+}
+
+void
+Playlist::thaw ()
+{
+ atomic_dec (&ignore_state_changes);
+ release_notifications ();
+}
+
+
+void
+Playlist::delay_notifications ()
+{
+ atomic_inc (&block_notifications);
+ freeze_length = _get_maximum_extent();
+}
+
+void
+Playlist::release_notifications ()
+{
+ if (atomic_dec_and_test(&block_notifications)) {
+ flush_notifications ();
+ }
+}
+
+
+void
+Playlist::notify_modified ()
+{
+ if (holding_state ()) {
+ pending_modified = true;
+ } else {
+ pending_modified = false;
+ Modified(); /* EMIT SIGNAL */
+ }
+}
+
+void
+Playlist::notify_region_removed (Region *r)
+{
+ if (holding_state ()) {
+ pending_removals.insert (pending_removals.end(), r);
+ } else {
+ RegionRemoved (r); /* EMIT SIGNAL */
+ /* this might not be true, but we have to act
+ as though it could be.
+ */
+ LengthChanged (); /* EMIT SIGNAL */
+ Modified (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Playlist::notify_region_added (Region *r)
+{
+ if (holding_state()) {
+ pending_adds.insert (pending_adds.end(), r);
+ } else {
+ RegionAdded (r); /* EMIT SIGNAL */
+ /* this might not be true, but we have to act
+ as though it could be.
+ */
+ LengthChanged (); /* EMIT SIGNAL */
+ Modified (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Playlist::notify_length_changed ()
+{
+ if (holding_state ()) {
+ pending_length = true;
+ } else {
+ LengthChanged(); /* EMIT SIGNAL */
+ Modified (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Playlist::flush_notifications ()
+{
+ RegionList::iterator r;
+ RegionList::iterator a;
+ set<Region*> dependent_checks_needed;
+ uint32_t n = 0;
+
+ if (in_flush) {
+ return;
+ }
+
+ in_flush = true;
+
+ /* we have no idea what order the regions ended up in pending
+ bounds (it could be based on selection order, for example).
+ so, to preserve layering in the "most recently moved is higher"
+ model, sort them by existing layer, then timestamp them.
+ */
+
+ // RegionSortByLayer cmp;
+ // pending_bounds.sort (cmp);
+
+ for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
+ if (_session.get_layer_model() == Session::MoveAddHigher) {
+ timestamp_layer_op (**r);
+ }
+ pending_length = true;
+ n++;
+ }
+
+ for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
+ dependent_checks_needed.insert (*r);
+ /* don't increment n again - its the same list */
+ }
+
+ for (a = pending_adds.begin(); a != pending_adds.end(); ++a) {
+ dependent_checks_needed.insert (*a);
+ RegionAdded (*a); /* EMIT SIGNAL */
+ n++;
+ }
+
+ for (set<Region*>::iterator x = dependent_checks_needed.begin(); x != dependent_checks_needed.end(); ++x) {
+ check_dependents (**x, false);
+ }
+
+ for (r = pending_removals.begin(); r != pending_removals.end(); ++r) {
+ remove_dependents (**r);
+ RegionRemoved (*r); /* EMIT SIGNAL */
+ n++;
+ }
+
+ if ((freeze_length != _get_maximum_extent()) || pending_length) {
+ pending_length = 0;
+ LengthChanged(); /* EMIT SIGNAL */
+ n++;
+ }
+
+ if (n || pending_modified) {
+ possibly_splice ();
+ relayer ();
+ pending_modified = false;
+ Modified (); /* EMIT SIGNAL */
+ }
+
+ pending_adds.clear ();
+ pending_removals.clear ();
+ pending_bounds.clear ();
+
+ if (save_on_thaw) {
+ save_on_thaw = false;
+ save_state (last_save_reason);
+ }
+
+ in_flush = false;
+}
+
+/*************************************************************
+ PLAYLIST OPERATIONS
+ *************************************************************/
+
+void
+Playlist::add_region (const Region& region, jack_nframes_t position, float times, bool with_save)
+{
+ RegionLock rlock (this);
+
+ times = fabs (times);
+
+ int itimes = (int) floor (times);
+
+ jack_nframes_t pos = position;
+
+ if (itimes >= 1) {
+ add_region_internal (const_cast<Region*>(&region), pos, true);
+ pos += region.length();
+ --itimes;
+ }
+
+ /* later regions will all be spliced anyway */
+
+ if (!holding_state ()) {
+ possibly_splice_unlocked ();
+ }
+
+ /* note that itimes can be zero if we being asked to just
+ insert a single fraction of the region.
+ */
+
+ for (int i = 0; i < itimes; ++i) {
+ Region *copy = createRegion (region);
+ add_region_internal (copy, pos, true);
+ pos += region.length();
+ }
+
+ if (floor (times) != times) {
+ jack_nframes_t length = (jack_nframes_t) floor (region.length() * (times - floor (times)));
+ string name;
+ _session.region_name (name, region.name(), false);
+ Region *sub = createRegion (region, 0, length, name, region.layer(), region.flags());
+ add_region_internal (sub, pos, true);
+ }
+
+ if (with_save) {
+ maybe_save_state (_("add region"));
+ }
+}
+
+void
+Playlist::add_region_internal (Region *region, jack_nframes_t position, bool delay_sort)
+{
+ RegionSortByPosition cmp;
+ jack_nframes_t old_length;
+
+ // cerr << "adding region " << region->name() << " at " << position << endl;
+
+ if (!holding_state()) {
+ old_length = _get_maximum_extent();
+ }
+
+ region->set_playlist (this);
+ region->set_position (position, this);
+ region->lock_sources ();
+
+ timestamp_layer_op (*region);
+
+ regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
+
+ if (!holding_state () && !in_set_state) {
+ /* layers get assigned from XML state */
+ relayer ();
+ }
+
+ /* we need to notify the existence of new region before checking dependents. Ick. */
+
+ notify_region_added (region);
+
+ if (!holding_state ()) {
+ check_dependents (*region, false);
+ if (old_length != _get_maximum_extent()) {
+ notify_length_changed ();
+ }
+ }
+
+ region->StateChanged.connect (sigc::bind (mem_fun (this, &Playlist::region_changed_proxy), region));
+}
+
+void
+Playlist::replace_region (Region& old, Region& newr, jack_nframes_t pos)
+{
+ RegionLock rlock (this);
+
+ remove_region_internal (&old);
+ add_region_internal (&newr, pos);
+
+ if (!holding_state ()) {
+ possibly_splice_unlocked ();
+ }
+
+ maybe_save_state (_("replace region"));
+}
+
+void
+Playlist::remove_region (Region *region)
+{
+ RegionLock rlock (this);
+ remove_region_internal (region);
+
+ if (!holding_state ()) {
+ possibly_splice_unlocked ();
+ }
+
+ maybe_save_state (_("remove region"));
+}
+
+int
+Playlist::remove_region_internal (Region *region, bool delay_sort)
+{
+ RegionList::iterator i;
+ jack_nframes_t old_length;
+
+ // cerr << "removing region " << region->name() << endl;
+
+ if (!holding_state()) {
+ old_length = _get_maximum_extent();
+ }
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+ if (*i == region) {
+
+ regions.erase (i);
+
+ if (!holding_state ()) {
+ relayer ();
+ remove_dependents (*region);
+
+ if (old_length != _get_maximum_extent()) {
+ notify_length_changed ();
+ }
+ }
+
+ notify_region_removed (region);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void
+Playlist::partition (jack_nframes_t start, jack_nframes_t end, bool just_top_level)
+{
+ RegionList thawlist;
+
+ partition_internal (start, end, false, thawlist);
+
+ for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
+ (*i)->thaw ("separation");
+ }
+
+ maybe_save_state (_("separate"));
+}
+
+void
+Playlist::partition_internal (jack_nframes_t start, jack_nframes_t end, bool cutting, RegionList& thawlist)
+{
+ RegionLock rlock (this);
+ Region *region;
+ Region *current;
+ string new_name;
+ RegionList::iterator tmp;
+ OverlapType overlap;
+ jack_nframes_t pos1, pos2, pos3, pos4;
+ RegionList new_regions;
+
+ in_partition = true;
+
+ /* need to work from a copy, because otherwise the regions we add during the process
+ get operated on as well.
+ */
+
+ RegionList copy = regions;
+
+ for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
+
+ tmp = i;
+ ++tmp;
+
+ current = *i;
+
+ if (current->first_frame() == start && current->last_frame() == end) {
+ if (cutting) {
+ remove_region_internal (current);
+ }
+ continue;
+ }
+
+ if ((overlap = current->coverage (start, end)) == OverlapNone) {
+ continue;
+ }
+
+ pos1 = current->position();
+ pos2 = start;
+ pos3 = end;
+ pos4 = current->last_frame();
+
+ if (overlap == OverlapInternal) {
+
+ /* split: we need 3 new regions, the front, middle and end.
+ cut: we need 2 regions, the front and end.
+ */
+
+ /*
+ start end
+ ---------------*************************------------
+ P1 P2 P3 P4
+ SPLIT:
+ ---------------*****++++++++++++++++====------------
+ CUT
+ ---------------*****----------------====------------
+
+ */
+
+ if (!cutting) {
+
+ /* "middle" ++++++ */
+
+ _session.region_name (new_name, current->name(), false);
+ region = createRegion (*current, pos2 - pos1, pos3 - pos2, new_name,
+ regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit|Region::RightOfSplit));
+ add_region_internal (region, start, true);
+ new_regions.push_back (region);
+ }
+
+ /* "end" ====== */
+
+ _session.region_name (new_name, current->name(), false);
+ region = createRegion (*current, pos3 - pos1, pos4 - pos3, new_name,
+ regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
+
+ add_region_internal (region, end, true);
+ new_regions.push_back (region);
+
+ /* "front" ***** */
+
+ current->freeze ();
+ thawlist.push_back (current);
+ current->trim_end (pos2, this);
+
+ } else if (overlap == OverlapEnd) {
+
+ /*
+ start end
+ ---------------*************************------------
+ P1 P2 P4 P3
+ SPLIT:
+ ---------------**************+++++++++++------------
+ CUT:
+ ---------------**************-----------------------
+
+ */
+
+ if (!cutting) {
+
+ /* end +++++ */
+
+ _session.region_name (new_name, current->name(), false);
+ region = createRegion (*current, pos2 - pos1, pos4 - pos2, new_name, (layer_t) regions.size(),
+ Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit));
+ add_region_internal (region, start, true);
+ new_regions.push_back (region);
+ }
+
+ /* front ****** */
+
+ current->freeze ();
+ thawlist.push_back (current);
+ current->trim_end (pos2, this);
+
+ } else if (overlap == OverlapStart) {
+
+ /* split: we need 2 regions: the front and the end.
+ cut: just trim current to skip the cut area
+ */
+
+ /*
+ start end
+ ---------------*************************------------
+ P2 P1 P3 P4
+
+ SPLIT:
+ ---------------****+++++++++++++++++++++------------
+ CUT:
+ -------------------*********************------------
+
+ */
+
+ if (!cutting) {
+
+ /* front **** */
+ _session.region_name (new_name, current->name(), false);
+ region = createRegion (*current, 0, pos3 - pos1, new_name,
+ regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
+ add_region_internal (region, pos1, true);
+ new_regions.push_back (region);
+ }
+
+ /* end */
+
+ current->freeze ();
+ thawlist.push_back (current);
+ current->trim_front (pos3, this);
+
+ } else if (overlap == OverlapExternal) {
+
+ /* split: no split required.
+ cut: remove the region.
+ */
+
+ /*
+ start end
+ ---------------*************************------------
+ P2 P1 P3 P4
+
+ SPLIT:
+ ---------------*************************------------
+ CUT:
+ ----------------------------------------------------
+
+ */
+
+ if (cutting) {
+ remove_region_internal (current);
+ }
+ new_regions.push_back (current);
+ }
+ }
+
+ in_partition = false;
+
+ for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
+ check_dependents (**i, false);
+ }
+}
+
+Playlist*
+Playlist::cut_copy (Playlist* (Playlist::*pmf)(jack_nframes_t, jack_nframes_t,bool), list<AudioRange>& ranges, bool result_is_hidden)
+{
+ Playlist* ret;
+ Playlist* pl;
+ jack_nframes_t start;
+
+ if (ranges.empty()) {
+ return 0;
+ }
+
+ start = ranges.front().start;
+
+
+ for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+
+ pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
+
+ if (i == ranges.begin()) {
+ ret = pl;
+ } else {
+
+ /* paste the next section into the nascent playlist,
+ offset to reflect the start of the first range we
+ chopped.
+ */
+
+ ret->paste (*pl, (*i).start - start, 1.0f);
+ delete pl;
+ }
+ }
+
+ if (ret) {
+ /* manually notify session of new playlist here
+ because the playlists were constructed without notifying
+ */
+ PlaylistCreated (ret);
+ }
+
+ return ret;
+}
+
+Playlist*
+Playlist::cut (list<AudioRange>& ranges, bool result_is_hidden)
+{
+ Playlist* (Playlist::*pmf)(jack_nframes_t,jack_nframes_t,bool) = &Playlist::cut;
+ return cut_copy (pmf, ranges, result_is_hidden);
+}
+
+Playlist*
+Playlist::copy (list<AudioRange>& ranges, bool result_is_hidden)
+{
+ Playlist* (Playlist::*pmf)(jack_nframes_t,jack_nframes_t,bool) = &Playlist::copy;
+ return cut_copy (pmf, ranges, result_is_hidden);
+}
+
+Playlist *
+Playlist::cut (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden)
+{
+ Playlist *the_copy;
+ RegionList thawlist;
+ char buf[32];
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+ string new_name = _name;
+ new_name += '.';
+ new_name += buf;
+
+ if ((the_copy = copyPlaylist (*this, start, cnt, new_name, result_is_hidden)) == 0) {
+ return 0;
+ }
+
+ partition_internal (start, start+cnt-1, true, thawlist);
+ possibly_splice ();
+
+ for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
+ (*i)->thaw ("playlist cut");
+ }
+
+ maybe_save_state (_("cut"));
+
+ return the_copy;
+}
+
+Playlist *
+Playlist::copy (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden)
+{
+ char buf[32];
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+ string new_name = _name;
+ new_name += '.';
+ new_name += buf;
+
+ cnt = min (_get_maximum_extent() - start, cnt);
+ return copyPlaylist (*this, start, cnt, new_name, result_is_hidden);
+}
+
+int
+Playlist::paste (Playlist& other, jack_nframes_t position, float times)
+{
+ times = fabs (times);
+ jack_nframes_t old_length;
+
+ {
+ RegionLock rl1 (this);
+ RegionLock rl2 (&other);
+
+ old_length = _get_maximum_extent();
+
+ int itimes = (int) floor (times);
+ jack_nframes_t pos = position;
+ jack_nframes_t shift = other._get_maximum_extent();
+ layer_t top_layer = regions.size();
+
+ while (itimes--) {
+ for (RegionList::iterator i = other.regions.begin(); i != other.regions.end(); ++i) {
+ Region *copy_of_region = createRegion (**i);
+
+ /* put these new regions on top of all existing ones, but preserve
+ the ordering they had in the original playlist.
+ */
+
+ copy_of_region->set_layer (copy_of_region->layer() + top_layer);
+ add_region_internal (copy_of_region, copy_of_region->position() + pos);
+ }
+ pos += shift;
+ }
+
+ possibly_splice_unlocked ();
+
+ /* XXX shall we handle fractional cases at some point? */
+
+ if (old_length != _get_maximum_extent()) {
+ notify_length_changed ();
+ }
+
+
+ }
+
+ maybe_save_state (_("paste"));
+
+ return 0;
+}
+
+
+void
+Playlist::duplicate (Region& region, jack_nframes_t position, float times)
+{
+ times = fabs (times);
+
+ RegionLock rl (this);
+ int itimes = (int) floor (times);
+ jack_nframes_t pos = position;
+
+ while (itimes--) {
+ Region *copy = createRegion (region);
+ add_region_internal (copy, pos, true);
+ pos += region.length();
+ }
+
+ if (floor (times) != times) {
+ jack_nframes_t length = (jack_nframes_t) floor (region.length() * (times - floor (times)));
+ string name;
+ _session.region_name (name, region.name(), false);
+ Region *sub = createRegion (region, 0, length, name, region.layer(), region.flags());
+ add_region_internal (sub, pos, true);
+ }
+
+ maybe_save_state (_("duplicate"));
+}
+
+void
+Playlist::split_region (Region& region, jack_nframes_t playlist_position)
+{
+ RegionLock rl (this);
+
+ if (!region.covers (playlist_position)) {
+ return;
+ }
+
+ if (region.position() == playlist_position ||
+ region.last_frame() == playlist_position) {
+ return;
+ }
+
+ if (remove_region_internal (&region, true)) {
+ return;
+ }
+
+ Region *left;
+ Region *right;
+ jack_nframes_t before;
+ jack_nframes_t after;
+ string before_name;
+ string after_name;
+
+ before = playlist_position - region.position();
+ after = region.length() - before;
+
+ _session.region_name (before_name, region.name(), false);
+ left = createRegion (region, 0, before, before_name, region.layer(), Region::Flag (region.flags()|Region::LeftOfSplit));
+
+ _session.region_name (after_name, region.name(), false);
+ right = createRegion (region, before, after, after_name, region.layer(), Region::Flag (region.flags()|Region::RightOfSplit));
+
+ add_region_internal (left, region.position(), true);
+ add_region_internal (right, region.position() + before);
+
+ maybe_save_state (_("split"));
+}
+
+void
+Playlist::possibly_splice ()
+{
+ if (_edit_mode == Splice) {
+ splice_locked ();
+ }
+}
+
+void
+Playlist::possibly_splice_unlocked ()
+{
+ if (_edit_mode == Splice) {
+ splice_unlocked ();
+ }
+}
+
+void
+Playlist::splice_locked ()
+{
+ {
+ RegionLock rl (this);
+ core_splice ();
+ }
+
+ notify_length_changed ();
+}
+
+void
+Playlist::splice_unlocked ()
+{
+ core_splice ();
+ notify_length_changed ();
+}
+
+void
+Playlist::core_splice ()
+{
+ _splicing = true;
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ RegionList::iterator next;
+
+ next = i;
+ ++next;
+
+ if (next == regions.end()) {
+ break;
+ }
+
+ (*next)->set_position ((*i)->last_frame() + 1, this);
+ }
+
+ _splicing = false;
+}
+
+void
+Playlist::region_bounds_changed (Change what_changed, Region *region)
+{
+ if (in_set_state || _splicing || _nudging) {
+ return;
+ }
+
+ if (what_changed & ARDOUR::PositionChanged) {
+
+ /* remove it from the list then add it back in
+ the right place again.
+ */
+
+ RegionSortByPosition cmp;
+
+ RegionList::iterator i = find (regions.begin(), regions.end(), region);
+
+ if (i == regions.end()) {
+ warning << compose (_("%1: bounds changed received for region (%2)not in playlist"),
+ _name, region->name())
+ << endmsg;
+ return;
+ }
+
+ regions.erase (i);
+ regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp),
+ region);
+
+ }
+
+ if (what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged)) {
+
+ if (holding_state ()) {
+ pending_bounds.push_back (region);
+ } else {
+ if (_session.get_layer_model() == Session::MoveAddHigher) {
+ /* it moved or changed length, so change the timestamp */
+ timestamp_layer_op (*region);
+ }
+
+ possibly_splice ();
+ check_dependents (*region, false);
+ notify_length_changed ();
+ relayer ();
+ }
+ }
+}
+
+void
+Playlist::region_changed_proxy (Change what_changed, Region* region)
+{
+ /* this makes a virtual call to the right kind of playlist ... */
+
+ region_changed (what_changed, region);
+}
+
+bool
+Playlist::region_changed (Change what_changed, Region* region)
+{
+ Change our_interests = Change (Region::MuteChanged|Region::LayerChanged|Region::OpacityChanged);
+ bool save = false;
+
+ if (in_set_state || in_flush) {
+ return false;
+ }
+
+ {
+ if (what_changed & BoundsChanged) {
+ region_bounds_changed (what_changed, region);
+ save = !(_splicing || _nudging);
+ }
+
+ if ((what_changed & Region::MuteChanged) &&
+ !(what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged))) {
+ check_dependents (*region, false);
+ }
+
+ if (what_changed & our_interests) {
+ save = true;
+ }
+ }
+
+ return save;
+}
+
+void
+Playlist::clear (bool with_delete, bool with_save)
+{
+ RegionList::iterator i;
+ RegionList tmp;
+
+ {
+ RegionLock rl (this);
+ tmp = regions;
+ regions.clear ();
+ }
+
+ for (i = tmp.begin(); i != tmp.end(); ++i) {
+ notify_region_removed (*i);
+ if (with_delete) {
+ delete *i;
+ }
+ }
+
+ if (with_save) {
+ maybe_save_state (_("clear"));
+ }
+}
+
+/***********************************************************************
+ FINDING THINGS
+ **********************************************************************/
+
+Playlist::RegionList *
+Playlist::regions_at (jack_nframes_t frame)
+
+{
+ RegionLock rlock (this);
+ return find_regions_at (frame);
+}
+
+Region *
+Playlist::top_region_at (jack_nframes_t frame)
+
+{
+ RegionLock rlock (this);
+ RegionList *rlist = find_regions_at (frame);
+ Region *region = 0;
+
+ if (rlist->size()) {
+ RegionSortByLayer cmp;
+ rlist->sort (cmp);
+ region = rlist->back();
+ }
+
+ delete rlist;
+ return region;
+}
+
+Playlist::RegionList *
+Playlist::find_regions_at (jack_nframes_t frame)
+{
+ RegionList *rlist = new RegionList;
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+ if ((*i)->covers (frame)) {
+ rlist->push_back (*i);
+ }
+ }
+
+ return rlist;
+}
+
+Playlist::RegionList *
+Playlist::regions_touched (jack_nframes_t start, jack_nframes_t end)
+{
+ RegionLock rlock (this);
+ RegionList *rlist = new RegionList;
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+ if ((*i)->coverage (start, end) != OverlapNone) {
+ rlist->push_back (*i);
+ }
+ }
+
+ return rlist;
+}
+
+
+Region*
+
+Playlist::find_next_region (jack_nframes_t frame, RegionPoint point, int dir)
+{
+ RegionLock rlock (this);
+ Region* ret = 0;
+ jack_nframes_t closest = max_frames;
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ jack_nframes_t distance;
+ Region* r = (*i);
+ jack_nframes_t pos = 0;
+
+ switch (point) {
+ case Start:
+ pos = r->first_frame ();
+ break;
+ case End:
+ pos = r->last_frame ();
+ break;
+ case SyncPoint:
+ pos = r->adjust_to_sync (r->first_frame());
+ break;
+ }
+
+ switch (dir) {
+ case 1: /* forwards */
+
+ if (pos > frame) {
+ if ((distance = pos - frame) < closest) {
+ closest = distance;
+ ret = r;
+ }
+ }
+
+ break;
+
+ default: /* backwards */
+
+ if (pos < frame) {
+ if ((distance = frame - pos) < closest) {
+ closest = distance;
+ ret = r;
+ }
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/***********************************************************************/
+
+
+
+void
+Playlist::mark_session_dirty ()
+{
+ if (!in_set_state && !holding_state ()) {
+ _session.set_dirty();
+ }
+}
+
+int
+Playlist::set_state (const XMLNode& node)
+{
+ in_set_state = true;
+
+ XMLNode *child;
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ XMLPropertyList plist;
+ XMLPropertyConstIterator piter;
+ XMLProperty *prop;
+ Region *region;
+ string region_name;
+
+ clear (false, false);
+
+ if (node.name() != "Playlist") {
+ in_set_state = false;
+ return -1;
+ }
+
+ plist = node.properties();
+
+ for (piter = plist.begin(); piter != plist.end(); ++piter) {
+
+ prop = *piter;
+
+ if (prop->name() == X_("name")) {
+ _name = prop->value();
+ } else if (prop->name() == X_("orig_diskstream_id")) {
+ sscanf (prop->value().c_str(), "%" PRIu64, &_orig_diskstream_id);
+ } else if (prop->name() == X_("frozen")) {
+ _frozen = (prop->value() == X_("yes"));
+ }
+ }
+
+ nlist = node.children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ child = *niter;
+
+ if (child->name() == "Region") {
+
+ if ((region = createRegion (_session, *child, true)) == 0) {
+ error << _("Playlist: cannot create region from state file") << endmsg;
+ continue;
+ }
+
+ add_region (*region, region->position(), 1.0, false);
+
+ }
+ }
+
+ /* update dependents, which was not done during add_region_internal
+ due to in_set_state being true
+ */
+
+ for (RegionList::iterator r = regions.begin(); r != regions.end(); ++r) {
+ check_dependents (**r, false);
+ }
+
+ in_set_state = false;
+
+ return 0;
+}
+
+XMLNode&
+Playlist::get_state()
+{
+ return state(true);
+}
+
+XMLNode&
+Playlist::get_template()
+{
+ return state(false);
+}
+
+XMLNode&
+Playlist::state (bool full_state)
+{
+ XMLNode *node = new XMLNode (X_("Playlist"));
+ char buf[64];
+
+ node->add_property (X_("name"), _name);
+
+ snprintf (buf, sizeof(buf), "%" PRIu64, _orig_diskstream_id);
+ node->add_property (X_("orig_diskstream_id"), buf);
+ node->add_property (X_("frozen"), _frozen ? "yes" : "no");
+
+ if (full_state) {
+ RegionLock rlock (this, false);
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+ node->add_child_nocopy ((*i)->get_state());
+ }
+ }
+
+ if (_extra_xml) {
+ node->add_child_copy (*_extra_xml);
+ }
+
+ return *node;
+}
+
+bool
+Playlist::empty() const
+{
+ return regions.empty();
+}
+
+jack_nframes_t
+Playlist::get_maximum_extent () const
+{
+ RegionLock rlock (const_cast<Playlist *>(this));
+ return _get_maximum_extent ();
+}
+
+jack_nframes_t
+Playlist::_get_maximum_extent () const
+{
+ RegionList::const_iterator i;
+ jack_nframes_t max_extent = 0;
+ jack_nframes_t end = 0;
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+ if ((end = (*i)->position() + (*i)->length()) > max_extent) {
+ max_extent = end;
+ }
+ }
+
+ return max_extent;
+}
+
+string
+Playlist::bump_name (string name, Session &session)
+{
+ string newname = name;
+
+ do {
+ newname = Playlist::bump_name_once (newname);
+ } while (session.playlist_by_name(newname)!=NULL);
+
+ return newname;
+}
+
+string
+Playlist::bump_name_once (string name)
+{
+ string::size_type period;
+ string newname;
+
+ if ((period = name.find_last_of ('.')) == string::npos) {
+ newname = name;
+ newname += ".1";
+ } else {
+ char buf[32];
+ int version;
+
+ sscanf (name.substr (period+1).c_str(), "%d", &version);
+ snprintf (buf, sizeof(buf), "%d", version+1);
+
+ newname = name.substr (0, period+1);
+ newname += buf;
+ }
+
+ return newname;
+}
+
+layer_t
+Playlist::top_layer() const
+{
+ RegionLock rlock (const_cast<Playlist *> (this));
+ layer_t top = 0;
+
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ top = max (top, (*i)->layer());
+ }
+ return top;
+}
+
+void
+Playlist::set_edit_mode (EditMode mode)
+{
+ _edit_mode = mode;
+}
+
+/********************
+ * Region Layering
+ ********************/
+
+void
+Playlist::relayer ()
+{
+ RegionList::iterator i;
+ uint32_t layer = 0;
+
+ /* don't send multiple Modified notifications
+ when multiple regions are relayered.
+ */
+
+ freeze ();
+
+ if (_session.get_layer_model() == Session::MoveAddHigher ||
+ _session.get_layer_model() == Session::AddHigher) {
+
+ RegionSortByLastLayerOp cmp;
+ RegionList copy = regions;
+
+ copy.sort (cmp);
+
+ for (i = copy.begin(); i != copy.end(); ++i) {
+ (*i)->set_layer (layer++);
+ }
+
+ } else {
+
+ /* Session::LaterHigher model */
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+ (*i)->set_layer (layer++);
+ }
+ }
+
+ /* sending Modified means that various kinds of layering
+ models operate correctly at the GUI
+ level. slightly inefficient, but only slightly.
+
+ We force a Modified signal here in case no layers actually
+ changed.
+ */
+
+ notify_modified ();
+
+ thaw ();
+}
+
+/* XXX these layer functions are all deprecated */
+
+void
+Playlist::raise_region (Region& region)
+{
+ uint32_t rsz = regions.size();
+ layer_t target = region.layer() + 1U;
+
+ if (target >= rsz) {
+ /* its already at the effective top */
+ return;
+ }
+
+ move_region_to_layer (target, region, 1);
+}
+
+void
+Playlist::lower_region (Region& region)
+{
+ if (region.layer() == 0) {
+ /* its already at the bottom */
+ return;
+ }
+
+ layer_t target = region.layer() - 1U;
+
+ move_region_to_layer (target, region, -1);
+}
+
+void
+Playlist::raise_region_to_top (Region& region)
+{
+ /* does nothing useful if layering mode is later=higher */
+ if ((_session.get_layer_model() == Session::MoveAddHigher) ||
+ (_session.get_layer_model() == Session::AddHigher)) {
+ timestamp_layer_op (region);
+ relayer ();
+ }
+}
+
+void
+Playlist::lower_region_to_bottom (Region& region)
+{
+ /* does nothing useful if layering mode is later=higher */
+ if ((_session.get_layer_model() == Session::MoveAddHigher) ||
+ (_session.get_layer_model() == Session::AddHigher)) {
+ region.set_last_layer_op (0);
+ relayer ();
+ }
+}
+
+int
+Playlist::move_region_to_layer (layer_t target_layer, Region& region, int dir)
+{
+ RegionList::iterator i;
+ typedef pair<Region*,layer_t> LayerInfo;
+ list<LayerInfo> layerinfo;
+ layer_t dest;
+
+ {
+ RegionLock rlock (const_cast<Playlist *> (this));
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+
+ if (&region == *i) {
+ continue;
+ }
+
+ if (dir > 0) {
+
+ /* region is moving up, move all regions on intermediate layers
+ down 1
+ */
+
+ if ((*i)->layer() > region.layer() && (*i)->layer() <= target_layer) {
+ dest = (*i)->layer() - 1;
+ } else {
+ /* not affected */
+ continue;
+ }
+ } else {
+
+ /* region is moving down, move all regions on intermediate layers
+ up 1
+ */
+
+ if ((*i)->layer() < region.layer() && (*i)->layer() >= target_layer) {
+ dest = (*i)->layer() + 1;
+ } else {
+ /* not affected */
+ continue;
+ }
+ }
+
+ LayerInfo newpair;
+
+ newpair.first = *i;
+ newpair.second = dest;
+
+ layerinfo.push_back (newpair);
+ }
+ }
+
+ /* now reset the layers without holding the region lock */
+
+ for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
+ x->first->set_layer (x->second);
+ }
+
+ region.set_layer (target_layer);
+
+ /* now check all dependents */
+
+ for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
+ check_dependents (*(x->first), false);
+ }
+
+ check_dependents (region, false);
+
+ return 0;
+}
+
+void
+Playlist::nudge_after (jack_nframes_t start, jack_nframes_t distance, bool forwards)
+{
+ RegionList::iterator i;
+ jack_nframes_t new_pos;
+ bool moved = false;
+
+ _nudging = true;
+
+ {
+ RegionLock rlock (const_cast<Playlist *> (this));
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+
+ if ((*i)->position() >= start) {
+
+ if (forwards) {
+
+ if ((*i)->last_frame() > max_frames - distance) {
+ new_pos = max_frames - (*i)->length();
+ } else {
+ new_pos = (*i)->position() + distance;
+ }
+
+ } else {
+
+ if ((*i)->position() > distance) {
+ new_pos = (*i)->position() - distance;
+ } else {
+ new_pos = 0;
+ }
+ }
+
+ (*i)->set_position (new_pos, this);
+ moved = true;
+ }
+ }
+ }
+
+ if (moved) {
+ _nudging = false;
+ maybe_save_state (_("nudged"));
+ notify_length_changed ();
+ }
+
+}
+
+Region*
+Playlist::find_region (id_t id) const
+{
+ RegionLock rlock (const_cast<Playlist*> (this));
+ RegionList::const_iterator i;
+
+ for (i = regions.begin(); i != regions.end(); ++i) {
+ if ((*i)->id() == id) {
+ return (*i);
+ }
+ }
+
+ return 0;
+}
+
+void
+Playlist::save_state (std::string why)
+{
+ if (!in_set_state) {
+ StateManager::save_state (why);
+ }
+}
+
+void
+Playlist::dump () const
+{
+ Region *r;
+
+ cerr << "Playlist \"" << _name << "\" " << endl
+ << regions.size() << " regions "
+ << endl;
+
+ for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ r = *i;
+ cerr << " " << r->name() << " ["
+ << r->start() << "+" << r->length()
+ << "] at "
+ << r->position()
+ << " on layer "
+ << r->layer ()
+ << endl;
+ }
+}
+
+void
+Playlist::set_frozen (bool yn)
+{
+ _frozen = yn;
+}
+
+void
+Playlist::timestamp_layer_op (Region& region)
+{
+// struct timeval tv;
+// gettimeofday (&tv, 0);
+ region.set_last_layer_op (++layer_op_counter);
+}
+
+void
+Playlist::maybe_save_state (string why)
+{
+ if (holding_state ()) {
+ save_on_thaw = true;
+ last_save_reason = why;
+ } else {
+ save_state (why);
+ }
+}
diff --git a/libs/ardour/playlist_factory.cc b/libs/ardour/playlist_factory.cc
new file mode 100644
index 0000000000..fc4618d1dc
--- /dev/null
+++ b/libs/ardour/playlist_factory.cc
@@ -0,0 +1,68 @@
+#include <ardour/session.h>
+
+#include <ardour/playlist.h>
+#include <ardour/audioplaylist.h>
+
+#include <ardour/region_factory.h>
+#include <ardour/region.h>
+#include <ardour/audioregion.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+Region*
+ARDOUR::createRegion (const Region& region, jack_nframes_t start,
+ jack_nframes_t length, std::string name,
+ layer_t layer, Region::Flag flags)
+{
+ const AudioRegion* ar;
+
+ if ((ar = dynamic_cast<const AudioRegion*>(&region)) != 0) {
+ AudioRegion* ret;
+ ret = new AudioRegion (*ar, start, length, name, layer, flags);
+ return ret;
+ } else {
+ fatal << _("programming error: Playlist::createRegion called with unknown Region type")
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+ }
+}
+
+Region*
+ARDOUR::createRegion (const Region& region)
+{
+ const AudioRegion* ar;
+
+ if ((ar = dynamic_cast<const AudioRegion*>(&region)) != 0) {
+ return new AudioRegion (*ar);
+ } else {
+ fatal << _("programming error: Playlist::createRegion called with unknown Region type")
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+ }
+}
+
+Region*
+ARDOUR::createRegion (Session& session, XMLNode& node, bool yn)
+{
+ return session.XMLRegionFactory (node, yn);
+}
+
+Playlist*
+Playlist::copyPlaylist (const Playlist& playlist, jack_nframes_t start, jack_nframes_t length,
+ string name, bool result_is_hidden)
+{
+ const AudioPlaylist* apl;
+
+ if ((apl = dynamic_cast<const AudioPlaylist*> (&playlist)) != 0) {
+ return new AudioPlaylist (*apl, start, length, name, result_is_hidden);
+ } else {
+ fatal << _("programming error: Playlist::copyPlaylist called with unknown Playlist type")
+ << endmsg;
+ /*NOTREACHED*/
+ return 0;
+ }
+}
diff --git a/libs/ardour/plugin.cc b/libs/ardour/plugin.cc
new file mode 100644
index 0000000000..dc85e1d721
--- /dev/null
+++ b/libs/ardour/plugin.cc
@@ -0,0 +1,351 @@
+/*
+ Copyright (C) 2000-2002 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.
+
+ $Id$
+*/
+
+#include <vector>
+#include <string>
+
+#include <cstdlib>
+#include <cstdio> // so libraptor doesn't complain
+#include <cmath>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <cerrno>
+
+#include <lrdf.h>
+
+#include <pbd/compose.h>
+#include <pbd/error.h>
+#include <pbd/pathscanner.h>
+#include <pbd/xml++.h>
+
+#include <midi++/manager.h>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/audioengine.h>
+#include <ardour/plugin.h>
+
+#include <pbd/stl_delete.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace ARDOUR;
+
+Plugin::Plugin (AudioEngine& e, Session& s)
+ : _engine (e), _session (s)
+{
+}
+
+Plugin::Plugin (const Plugin& other)
+ : _engine (other._engine), _session (other._session), _info (other._info)
+{
+}
+
+void
+Plugin::setup_midi_controls ()
+{
+ uint32_t port_cnt;
+
+ port_cnt = parameter_count();
+
+ /* set up a vector of null pointers for the MIDI controls.
+ we'll fill this in on an as-needed basis.
+ */
+
+ for (uint32_t i = 0; i < port_cnt; ++i) {
+ midi_controls.push_back (0);
+ }
+}
+
+Plugin::~Plugin ()
+{
+ for (vector<MIDIPortControl*>::iterator i = midi_controls.begin(); i != midi_controls.end(); ++i) {
+ if (*i) {
+ delete *i;
+ }
+ }
+}
+
+MIDI::Controllable *
+Plugin::get_nth_midi_control (uint32_t n)
+{
+ if (n >= parameter_count()) {
+ return 0;
+ }
+
+ if (midi_controls[n] == 0) {
+
+ Plugin::ParameterDescriptor desc;
+
+ get_parameter_descriptor (n, desc);
+
+ midi_controls[n] = new MIDIPortControl (*this, n, _session.midi_port(), desc.lower, desc.upper, desc.toggled, desc.logarithmic);
+ }
+
+ return midi_controls[n];
+}
+
+Plugin::MIDIPortControl::MIDIPortControl (Plugin& p, uint32_t port_id, MIDI::Port *port,
+ float low, float up, bool t, bool loga)
+ : MIDI::Controllable (port, 0), plugin (p), absolute_port (port_id)
+{
+ toggled = t;
+ logarithmic = loga;
+ lower = low;
+ upper = up;
+ range = upper - lower;
+ last_written = 0; /* XXX need a good out-of-bound-value */
+ setting = false;
+}
+
+void
+Plugin::MIDIPortControl::set_value (float value)
+{
+ if (toggled) {
+ if (value > 0.5) {
+ value = 1.0;
+ } else {
+ value = 0.0;
+ }
+ } else {
+ value = lower + (range * value);
+
+ if (logarithmic) {
+ value = exp(value);
+ }
+ }
+
+ setting = true;
+ plugin.set_parameter (absolute_port, value);
+ setting = false;
+}
+
+void
+Plugin::MIDIPortControl::send_feedback (float value)
+{
+
+ if (!setting && get_midi_feedback()) {
+ MIDI::byte val;
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (toggled) {
+ val = (MIDI::byte) (value * 127.0f);
+ } else {
+ if (logarithmic) {
+ value = log(value);
+ }
+
+ val = (MIDI::byte) (((value - lower) / range) * 127.0f);
+ }
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ plugin.session().send_midi_message (get_port(), ev, ch, data);
+ }
+ }
+
+}
+
+MIDI::byte*
+Plugin::MIDIPortControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, float value, bool force)
+{
+ if (get_midi_feedback() && bufsize > 2) {
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+
+ if (get_control_info (ch, ev, additional)) {
+
+ MIDI::byte val;
+
+ if (toggled) {
+
+ val = (MIDI::byte) (value * 127.0f);
+
+ } else {
+
+ if (logarithmic) {
+ value = log(value);
+ }
+
+ val = (MIDI::byte) (((value - lower) / range) * 127.0f);
+ }
+
+ if (val != last_written || force) {
+ *buf++ = MIDI::controller & ch;
+ *buf++ = additional; /* controller number */
+ *buf++ = val;
+ last_written = val;
+ bufsize -= 3;
+ }
+ }
+ }
+
+ return buf;
+}
+
+
+void
+Plugin::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ for (vector<MIDIPortControl*>::iterator i = midi_controls.begin(); i != midi_controls.end(); ++i) {
+ if (*i == 0)
+ continue;
+ (*i)->get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ (*i)->midi_rebind (port, chn);
+ }
+}
+
+void
+Plugin::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+ float val = 0.0;
+ uint32_t n = 0;
+
+ for (vector<MIDIPortControl*>::iterator i = midi_controls.begin(); i != midi_controls.end(); ++i, ++n) {
+ if (*i == 0) {
+ continue;
+ }
+
+ val = (*i)->plugin.get_parameter (n);
+ (*i)->send_feedback (val);
+ }
+
+ }
+}
+
+MIDI::byte*
+Plugin::write_midi_feedback (MIDI::byte* buf, int32_t& bufsize)
+{
+ if (_session.get_midi_feedback()) {
+ float val = 0.0;
+ uint32_t n = 0;
+
+ for (vector<MIDIPortControl*>::iterator i = midi_controls.begin(); i != midi_controls.end(); ++i, ++n) {
+ if (*i == 0) {
+ continue;
+ }
+
+ val = (*i)->plugin.get_parameter (n);
+ buf = (*i)->write_feedback (buf, bufsize, val);
+ }
+ }
+
+ return buf;
+}
+
+list<string>
+Plugin::get_presets()
+{
+ list<string> labels;
+ lrdf_uris* set_uris = lrdf_get_setting_uris(unique_id());
+
+ if (set_uris) {
+ for (uint32_t i = 0; i < set_uris->count; ++i) {
+ if (char* label = lrdf_get_label(set_uris->items[i])) {
+ labels.push_back(label);
+ presets[label] = set_uris->items[i];
+ }
+ }
+ lrdf_free_uris(set_uris);
+ }
+
+ labels.unique();
+
+ return labels;
+}
+
+bool
+Plugin::load_preset(const string preset_label)
+{
+ lrdf_defaults* defs = lrdf_get_setting_values(presets[preset_label].c_str());
+
+ if (defs) {
+ for (uint32_t i = 0; i < defs->count; ++i) {
+ // The defs->items[i].pid < defs->count check is to work around
+ // a bug in liblrdf that saves invalid values into the presets file.
+ if (((uint32_t) defs->items[i].pid < defs->count) && parameter_is_input (defs->items[i].pid)) {
+ set_parameter(defs->items[i].pid, defs->items[i].value);
+ }
+ }
+ lrdf_free_setting_values(defs);
+ }
+
+ return true;
+}
+
+bool
+Plugin::save_preset (string name, string domain)
+{
+ lrdf_portvalue portvalues[parameter_count()];
+ lrdf_defaults defaults;
+ defaults.count = parameter_count();
+ defaults.items = portvalues;
+
+ for (uint32_t i = 0; i < parameter_count(); ++i) {
+ if (parameter_is_input (i)) {
+ portvalues[i].pid = i;
+ portvalues[i].value = get_parameter(i);
+ }
+ }
+
+ char* envvar;
+ if ((envvar = getenv ("HOME")) == 0) {
+ warning << _("Could not locate HOME. Preset not saved.") << endmsg;
+ return false;
+ }
+
+ string source(compose("file:%1/.%2/rdf/ardour-presets.n3", envvar, domain));
+
+ free(lrdf_add_preset(source.c_str(), name.c_str(), unique_id(), &defaults));
+
+ string path = compose("%1/.%2", envvar, domain);
+ if (mkdir(path.c_str(), 0775) && errno != EEXIST) {
+ warning << compose(_("Could not create %1. Preset not saved. (%2)"), path, strerror(errno)) << endmsg;
+ return false;
+ }
+
+ path += "/rdf";
+ if (mkdir(path.c_str(), 0775) && errno != EEXIST) {
+ warning << compose(_("Could not create %1. Preset not saved. (%2)"), path, strerror(errno)) << endmsg;
+ return false;
+ }
+
+ if (lrdf_export_by_source(source.c_str(), source.substr(5).c_str())) {
+ warning << compose(_("Error saving presets file %1."), source) << endmsg;
+ return false;
+ }
+
+ return true;
+}
diff --git a/libs/ardour/plugin_manager.cc b/libs/ardour/plugin_manager.cc
new file mode 100644
index 0000000000..e214e56ebc
--- /dev/null
+++ b/libs/ardour/plugin_manager.cc
@@ -0,0 +1,486 @@
+/*
+ Copyright (C) 2000-2004 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.
+
+ $Id$
+*/
+
+#include <sys/types.h>
+#include <cstdio>
+#include <lrdf.h>
+#include <dlfcn.h>
+
+#ifdef VST_SUPPORT
+#include <fst.h>
+#include <pbd/basename.h>
+#include <string.h>
+#endif
+
+#include <pbd/pathscanner.h>
+
+#include <ardour/ladspa.h>
+#include <ardour/session.h>
+#include <ardour/plugin_manager.h>
+#include <ardour/plugin.h>
+#include <ardour/ladspa_plugin.h>
+#include <ardour/vst_plugin.h>
+
+#include <pbd/error.h>
+#include <pbd/stl_delete.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+PluginManager* PluginManager::_manager = 0;
+
+PluginManager::PluginManager (AudioEngine& e)
+ : _engine (e)
+{
+ char* s;
+ string lrdf_path;
+
+ if ((s = getenv ("LADSPA_RDF_PATH"))){
+ lrdf_path = s;
+ }
+
+ if (lrdf_path.length() == 0) {
+ lrdf_path = "/usr/local/share/ladspa/rdf:/usr/share/ladspa/rdf";
+ }
+
+ add_lrdf_data(lrdf_path);
+ add_ladspa_presets();
+#ifdef VST_SUPPORT
+ if (Config->get_use_vst()) {
+ add_vst_presets();
+ }
+#endif /* VST_SUPPORT */
+
+ if ((s = getenv ("LADSPA_PATH"))) {
+ ladspa_path = s;
+ }
+
+ if ((s = getenv ("VST_PATH"))) {
+ vst_path = s;
+ } else if ((s = getenv ("VST_PLUGINS"))) {
+ vst_path = s;
+ }
+
+ refresh ();
+
+ if (_manager == 0) {
+ _manager = this;
+ }
+}
+
+void
+PluginManager::refresh ()
+{
+ ladspa_refresh ();
+#ifdef VST_SUPPORT
+ if (Config->get_use_vst()) {
+ vst_refresh ();
+ }
+#endif
+}
+
+void
+PluginManager::ladspa_refresh ()
+{
+ for (std::list<PluginInfo*>::iterator i = _ladspa_plugin_info.begin(); i != _ladspa_plugin_info.end(); ++i) {
+ delete *i;
+ }
+
+ _ladspa_plugin_info.clear ();
+
+ if (ladspa_path.length() == 0) {
+ ladspa_path = "/usr/local/lib/ladspa:/usr/lib/ladspa";
+ }
+
+ ladspa_discover_from_path (ladspa_path);
+}
+
+int
+PluginManager::add_ladspa_directory (string path)
+{
+ if (ladspa_discover_from_path (path) == 0) {
+ ladspa_path += ':';
+ ladspa_path += path;
+ return 0;
+ }
+ return -1;
+}
+
+static bool ladspa_filter (const string& str, void *arg)
+{
+ /* Not a dotfile, has a prefix before a period, suffix is "so" */
+
+ return str[0] != '.' && (str.length() > 3 && str.find (".so") == (str.length() - 3));
+}
+
+int
+PluginManager::ladspa_discover_from_path (string path)
+{
+ PathScanner scanner;
+ vector<string *> *plugin_objects;
+ vector<string *>::iterator x;
+ int ret = 0;
+
+ plugin_objects = scanner (ladspa_path, ladspa_filter, 0, true, true);
+
+ if (plugin_objects) {
+ for (x = plugin_objects->begin(); x != plugin_objects->end (); ++x) {
+ ladspa_discover (**x);
+ }
+ }
+
+ vector_delete (plugin_objects);
+ return ret;
+}
+
+static bool rdf_filter (const string &str, void *arg)
+{
+ return str[0] != '.' &&
+ ((str.find(".rdf") == (str.length() - 4)) ||
+ (str.find(".rdfs") == (str.length() - 5)) ||
+ (str.find(".n3") == (str.length() - 3)));
+}
+
+void
+PluginManager::add_ladspa_presets()
+{
+ add_presets ("ladspa");
+}
+
+void
+PluginManager::add_vst_presets()
+{
+ add_presets ("vst");
+}
+void
+PluginManager::add_presets(string domain)
+{
+
+ PathScanner scanner;
+ vector<string *> *presets;
+ vector<string *>::iterator x;
+
+ char* envvar;
+ if ((envvar = getenv ("HOME")) == 0) {
+ return;
+ }
+
+ string path = compose("%1/.%2/rdf", envvar, domain);
+ presets = scanner (path, rdf_filter, 0, true, true);
+
+ if (presets) {
+ for (x = presets->begin(); x != presets->end (); ++x) {
+ string file = "file:" + **x;
+ if (lrdf_read_file(file.c_str())) {
+ warning << compose(_("Could not parse rdf file: %1"), *x) << endmsg;
+ }
+ }
+ }
+
+ vector_delete (presets);
+}
+
+
+void
+PluginManager::add_lrdf_data (const string &path)
+{
+ PathScanner scanner;
+ vector<string *>* rdf_files;
+ vector<string *>::iterator x;
+ string uri;
+
+ rdf_files = scanner (path, rdf_filter, 0, true, true);
+
+ if (rdf_files) {
+ for (x = rdf_files->begin(); x != rdf_files->end (); ++x) {
+ uri = "file://" + **x;
+
+ if (lrdf_read_file(uri.c_str())) {
+ warning << "Could not parse rdf file: " << uri << endmsg;
+ }
+ }
+ }
+
+ vector_delete (rdf_files);
+}
+
+int
+PluginManager::ladspa_discover (string path)
+{
+ PluginInfo *info;
+ void *module;
+ const LADSPA_Descriptor *descriptor;
+ LADSPA_Descriptor_Function dfunc;
+ const char *errstr;
+
+ if ((module = dlopen (path.c_str(), RTLD_NOW)) == 0) {
+ error << compose(_("LADSPA: cannot load module \"%1\" (%2)"), path, dlerror()) << endmsg;
+ return -1;
+ }
+
+ dfunc = (LADSPA_Descriptor_Function) dlsym (module, "ladspa_descriptor");
+
+ if ((errstr = dlerror()) != 0) {
+ error << compose(_("LADSPA: module \"%1\" has no descriptor function."), path) << endmsg;
+ error << errstr << endmsg;
+ dlclose (module);
+ return -1;
+ }
+
+ for (uint32_t i = 0; ; ++i) {
+ if ((descriptor = dfunc (i)) == 0) {
+ break;
+ }
+
+ info = new PluginInfo;
+ info->name = descriptor->Name;
+ info->category = get_ladspa_category(descriptor->UniqueID);
+ info->path = path;
+ info->index = i;
+ info->n_inputs = 0;
+ info->n_outputs = 0;
+ info->type = PluginInfo::LADSPA;
+
+ for (uint32_t n=0; n < descriptor->PortCount; ++n) {
+ if ( LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[n]) ) {
+ if ( LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[n]) ) {
+ info->n_inputs++;
+ }
+ else if ( LADSPA_IS_PORT_OUTPUT (descriptor->PortDescriptors[n]) ) {
+ info->n_outputs++;
+ }
+ }
+ }
+
+ _ladspa_plugin_info.push_back (info);
+ }
+
+// GDB WILL NOT LIKE YOU IF YOU DO THIS
+// dlclose (module);
+
+ return 0;
+}
+
+Plugin *
+PluginManager::load (Session& session, PluginInfo *info)
+{
+ void *module;
+ Plugin *plugin = 0;
+
+ try {
+ if (info->type == PluginInfo::VST) {
+
+#ifdef VST_SUPPORT
+ if (Config->get_use_vst()) {
+ FSTHandle* handle;
+
+ if ((handle = fst_load (info->path.c_str())) == 0) {
+ error << compose(_("VST: cannot load module from \"%1\""), info->path) << endmsg;
+ } else {
+ plugin = new VSTPlugin (_engine, session, handle);
+ }
+ } else {
+ error << _("You asked ardour to not use any VST plugins") << endmsg;
+ }
+#else
+ error << _("This version of ardour has no support for VST plugins") << endmsg;
+ return 0;
+#endif
+
+ } else {
+
+ if ((module = dlopen (info->path.c_str(), RTLD_NOW)) == 0) {
+ error << compose(_("LADSPA: cannot load module from \"%1\""), info->path) << endmsg;
+ error << dlerror() << endmsg;
+ } else {
+ plugin = new LadspaPlugin (module, _engine, session, info->index, session.frame_rate());
+ }
+ }
+
+ plugin->set_info(*info);
+ }
+
+ catch (failed_constructor &err) {
+ plugin = 0;
+ }
+
+ return plugin;
+}
+
+Plugin *
+ARDOUR::find_plugin(Session& session, string name, PluginInfo::Type type)
+{
+ PluginManager *mgr = PluginManager::the_manager();
+ list<PluginInfo *>::iterator i;
+ list<PluginInfo *>* plugs = 0;
+
+ switch (type) {
+ case PluginInfo::LADSPA:
+ plugs = &mgr->ladspa_plugin_info();
+ break;
+ case PluginInfo::VST:
+ plugs = &mgr->vst_plugin_info();
+ break;
+ }
+
+ for (i = plugs->begin(); i != plugs->end(); ++i) {
+ if ((*i)->name == name) {
+ return mgr->load (session, *i);
+ }
+ }
+
+ return 0;
+}
+
+string
+PluginManager::get_ladspa_category (uint32_t plugin_id)
+{
+ char buf[256];
+ lrdf_statement pattern;
+
+ snprintf(buf, sizeof(buf), "%s%" PRIu32, LADSPA_BASE, plugin_id);
+ pattern.subject = buf;
+ pattern.predicate = RDF_TYPE;
+ pattern.object = 0;
+ pattern.object_type = lrdf_uri;
+
+ lrdf_statement* matches1 = lrdf_matches (&pattern);
+
+ if (!matches1) {
+ return _("Unknown");
+ }
+
+ pattern.subject = matches1->object;
+ pattern.predicate = LADSPA_BASE "hasLabel";
+ pattern.object = 0;
+ pattern.object_type = lrdf_literal;
+
+ lrdf_statement* matches2 = lrdf_matches (&pattern);
+ lrdf_free_statements(matches1);
+
+ if (!matches2) {
+ return _("Unknown");
+ }
+
+ string label = matches2->object;
+ lrdf_free_statements(matches2);
+
+ return label;
+}
+
+#ifdef VST_SUPPORT
+
+void
+PluginManager::vst_refresh ()
+{
+ for (std::list<PluginInfo*>::iterator i = _vst_plugin_info.begin(); i != _vst_plugin_info.end(); ++i) {
+ delete *i;
+ }
+
+ _vst_plugin_info.clear ();
+
+ if (vst_path.length() == 0) {
+ vst_path = "/usr/local/lib/vst:/usr/lib/vst";
+ }
+
+ vst_discover_from_path (vst_path);
+}
+
+int
+PluginManager::add_vst_directory (string path)
+{
+ if (vst_discover_from_path (path) == 0) {
+ vst_path += ':';
+ vst_path += path;
+ return 0;
+ }
+ return -1;
+}
+
+static bool vst_filter (const string& str, void *arg)
+{
+ /* Not a dotfile, has a prefix before a period, suffix is "dll" */
+
+ return str[0] != '.' && (str.length() > 4 && str.find (".dll") == (str.length() - 4));
+}
+
+int
+PluginManager::vst_discover_from_path (string path)
+{
+ PathScanner scanner;
+ vector<string *> *plugin_objects;
+ vector<string *>::iterator x;
+ int ret = 0;
+
+ info << "detecting VST plugins along " << path << endmsg;
+
+ plugin_objects = scanner (vst_path, vst_filter, 0, true, true);
+
+ if (plugin_objects) {
+ for (x = plugin_objects->begin(); x != plugin_objects->end (); ++x) {
+ vst_discover (**x);
+ }
+ }
+
+ vector_delete (plugin_objects);
+ return ret;
+}
+
+int
+PluginManager::vst_discover (string path)
+{
+ FSTInfo* finfo;
+ PluginInfo* info;
+
+ if ((finfo = fst_get_info (const_cast<char *> (path.c_str()))) == 0) {
+ return -1;
+ }
+
+ if (!finfo->canProcessReplacing) {
+ warning << compose (_("VST plugin %1 does not support processReplacing, and so cannot be used in ardour at this time"),
+ finfo->name)
+ << endl;
+ }
+
+ info = new PluginInfo;
+
+ /* what a goddam joke freeware VST is */
+
+ if (!strcasecmp ("The Unnamed plugin", finfo->name)) {
+ info->name = PBD::basename_nosuffix (path);
+ } else {
+ info->name = finfo->name;
+ }
+
+ info->category = "VST";
+ info->path = path;
+ info->index = 0;
+ info->n_inputs = finfo->numInputs;
+ info->n_outputs = finfo->numOutputs;
+ info->type = PluginInfo::VST;
+
+ _vst_plugin_info.push_back (info);
+ fst_free_info (finfo);
+
+ return 0;
+}
+
+#endif
diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc
new file mode 100644
index 0000000000..9d6a90aa54
--- /dev/null
+++ b/libs/ardour/port.cc
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2002 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.
+
+ $Id$
+*/
+
+#include "ardour/port.h"
+
+using namespace ARDOUR;
+
+Port::Port (jack_port_t *p)
+ : port (p)
+{
+ if (port == 0) {
+ throw failed_constructor();
+ }
+
+ _flags = JackPortFlags (jack_port_flags (port));
+ _type = jack_port_type (port);
+ _name = jack_port_name (port);
+
+ reset ();
+}
+
+void
+Port::reset ()
+{
+ reset_buffer ();
+
+ last_monitor = false;
+ silent = false;
+ metering = 0;
+
+ reset_meters ();
+}
+
+int
+Port::set_name (string str)
+{
+ int ret;
+
+ if ((ret = jack_port_set_name (port, str.c_str())) == 0) {
+ _name = str;
+ }
+
+ return ret;
+}
+
+
diff --git a/libs/ardour/recent_sessions.cc b/libs/ardour/recent_sessions.cc
new file mode 100644
index 0000000000..c27ec223d6
--- /dev/null
+++ b/libs/ardour/recent_sessions.cc
@@ -0,0 +1,127 @@
+/*
+ Copyright (C) 2004 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 <cerrno>
+#include <unistd.h>
+#include <fstream>
+#include <algorithm>
+#include <pbd/error.h>
+#include <ardour/configuration.h>
+#include <ardour/recent_sessions.h>
+#include <ardour/utils.h>
+#include "i18n.h"
+
+
+using namespace std;
+using namespace ARDOUR;
+
+int
+ARDOUR::read_recent_sessions (RecentSessions& rs)
+{
+ string path = Config->get_user_ardour_path();
+ path += "/recent";
+
+ ifstream recent (path.c_str());
+
+ if (!recent) {
+ if (errno != ENOENT) {
+ error << compose (_("cannot open recent session file %1 (%2)"), path, strerror (errno)) << endmsg;
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ while (true) {
+
+ pair<string,string> newpair;
+
+ getline(recent, newpair.first);
+
+ if (!recent.good()) {
+ break;
+ }
+
+ getline(recent, newpair.second);
+
+ if (!recent.good()) {
+ break;
+ }
+
+ if (!access(newpair.second.c_str(), R_OK)) {
+ rs.push_back (newpair);
+ }
+ }
+
+ // This deletes any missing sessions
+ ARDOUR::write_recent_sessions (rs);
+
+ /* display sorting should be done in the GUI, otherwise the
+ * natural order will be broken
+ */
+
+ sort(rs.begin(), rs.end(), cmp);
+
+ return 0;
+}
+
+int
+ARDOUR::write_recent_sessions (RecentSessions& rs)
+{
+ string path = Config->get_user_ardour_path();
+ path += "/recent";
+
+ ofstream recent (path.c_str());
+
+ if (!recent) {
+ return -1;
+ }
+
+ for (RecentSessions::iterator i = rs.begin(); i != rs.end(); ++i) {
+ recent << (*i).first << '\n' << (*i).second << endl;
+ }
+
+ return 0;
+}
+
+int
+ARDOUR::store_recent_sessions (string name, string path)
+{
+ RecentSessions rs;
+
+ if (ARDOUR::read_recent_sessions (rs) < 0) {
+ return -1;
+ }
+
+ pair<string,string> newpair;
+
+ newpair.first = name;
+ newpair.second = path;
+
+ rs.erase(remove(rs.begin(), rs.end(), newpair), rs.end());
+
+ rs.push_front (newpair);
+
+ if (rs.size() > 10) {
+ rs.erase(rs.begin()+10, rs.end());
+ }
+
+ return ARDOUR::write_recent_sessions (rs);
+}
+
diff --git a/libs/ardour/redirect.cc b/libs/ardour/redirect.cc
new file mode 100644
index 0000000000..0a44702905
--- /dev/null
+++ b/libs/ardour/redirect.cc
@@ -0,0 +1,465 @@
+/*
+ Copyright (C) 2001 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.
+
+ $Id$
+*/
+
+#include <fstream>
+#include <algorithm>
+#include <string>
+#include <cerrno>
+#include <unistd.h>
+#include <sstream>
+
+#include <sigc++/bind.h>
+
+#include <pbd/xml++.h>
+
+#include <ardour/redirect.h>
+#include <ardour/session.h>
+#include <ardour/utils.h>
+#include <ardour/send.h>
+#include <ardour/insert.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+const string Redirect::state_node_name = "Redirect";
+sigc::signal<void,Redirect*> Redirect::RedirectCreated;
+
+Redirect::Redirect (Session& s, const string& name, Placement p,
+
+ int input_min, int input_max, int output_min, int output_max)
+ : IO (s, name, input_min, input_max, output_min, output_max)
+{
+ _placement = p;
+ _active = false;
+ _sort_key = 0;
+ _gui = 0;
+ _extra_xml = 0;
+}
+
+Redirect::~Redirect ()
+{
+}
+
+Redirect*
+Redirect::clone (const Redirect& other)
+{
+ const Send *send;
+ const PortInsert *port_insert;
+ const PluginInsert *plugin_insert;
+
+ if ((send = dynamic_cast<const Send*>(&other)) != 0) {
+ return new Send (*send);
+ } else if ((port_insert = dynamic_cast<const PortInsert*>(&other)) != 0) {
+ return new PortInsert (*port_insert);
+ } else if ((plugin_insert = dynamic_cast<const PluginInsert*>(&other)) != 0) {
+ return new PluginInsert (*plugin_insert);
+ } else {
+ fatal << _("programming error: unknown Redirect type in Redirect::Clone!\n")
+ << endmsg;
+ /*NOTREACHED*/
+ }
+ return 0;
+}
+
+void
+Redirect::set_placement (Placement p, void *src)
+{
+ if (_placement != p) {
+ _placement = p;
+ placement_changed (this, src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Redirect::set_placement (const string& str, void *src)
+{
+ if (str == _("pre")) {
+ set_placement (PreFader, this);
+ } else if (str == _("post")) {
+ set_placement (PostFader, this);
+ } else {
+ error << compose(_("Redirect: unknown placement string \"%1\" (ignored)"), str) << endmsg;
+ }
+}
+
+int
+Redirect::load_automation (string path)
+{
+ string fullpath;
+
+ if (path[0] == '/') { // legacy
+ fullpath = path;
+ } else {
+ fullpath = _session.automation_dir();
+ fullpath += path;
+ }
+ ifstream in (fullpath.c_str());
+
+ if (!in) {
+ warning << compose(_("%1: cannot open %2 to load automation data (%3)"), _name, fullpath, strerror (errno)) << endmsg;
+ return 1;
+ }
+
+ LockMonitor lm (_automation_lock, __LINE__, __FILE__);
+ set<uint32_t> tosave;
+ parameter_automation.clear ();
+
+ while (in) {
+ double when;
+ double value;
+ uint32_t port;
+
+ in >> port; if (!in) break;
+ in >> when; if (!in) goto bad;
+ in >> value; if (!in) goto bad;
+
+ AutomationList& al = automation_list (port);
+ al.add (when, value);
+ tosave.insert (port);
+ }
+
+ for (set<uint32_t>::iterator i = tosave.begin(); i != tosave.end(); ++i) {
+ automation_list (*i).save_state (_("loaded from disk"));
+ }
+
+ return 0;
+
+ bad:
+ error << compose(_("%1: cannot load automation data from %2"), _name, fullpath) << endmsg;
+ parameter_automation.clear ();
+ return -1;
+}
+
+int
+Redirect::save_automation (string path)
+{
+ LockMonitor lm (_automation_lock, __LINE__, __FILE__);
+ string fullpath;
+
+ if (parameter_automation.empty()) {
+ return 1;
+ }
+
+ fullpath = _session.automation_dir();
+ fullpath += path;
+
+ ofstream out (fullpath.c_str());
+
+ if (!out) {
+ error << compose(_("%1: cannot open %2 to store automation data (%3)"), _name, fullpath, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ AutomationList::const_iterator i;
+ map<uint32_t,AutomationList*>::iterator li;
+
+ for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) {
+ for (i = (*li).second->begin(); i != (*li).second->end(); ++i) {
+
+ out << (*li).first << ' ' << (*i)->when << ' ' << (*i)->value << endl;
+
+ if (!out) {
+ break;
+ }
+ }
+
+ if (i != (*li).second->end()) {
+ unlink (fullpath.c_str());
+ error << compose(_("%1: could not save automation state to %2"), _name, fullpath) << endmsg;
+ return -1;
+ }
+ }
+
+ if (li != parameter_automation.end()) {
+ unlink (fullpath.c_str());
+ error << compose(_("%1: could not save automation state to %2"), _name, fullpath) << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+XMLNode&
+Redirect::get_state (void)
+{
+ return state (true);
+}
+
+XMLNode&
+Redirect::state (bool full_state)
+{
+ char buf[64];
+ XMLNode* node = new XMLNode (state_node_name);
+ stringstream sstr;
+
+ node->add_property("active", active() ? "yes" : "no");
+ node->add_property("placement", placement_as_string (placement()));
+ node->add_child_nocopy (IO::state (full_state));
+
+ if (_extra_xml){
+ node->add_child_copy (*_extra_xml);
+ }
+
+ if (full_state) {
+
+ string path;
+ string legal_name;
+
+ snprintf (buf, sizeof(buf), "%" PRIu64, id());
+ path = _session.snap_name();
+ path += "-redirect-";
+ path += buf;
+ path += ".automation";
+
+ /* XXX we didn't ask for a state save, we asked for the current state.
+ FIX ME!
+ */
+
+ switch (save_automation (path)) {
+ case -1:
+ error << compose(_("Could not get state from Redirect (%1). Problem with save_automation"), _name) << endmsg;
+ break;
+
+ case 0:
+ XMLNode *aevents = node->add_child("Automation");
+
+ for (set<uint32_t>::iterator x = visible_parameter_automation.begin(); x != visible_parameter_automation.end(); ++x) {
+ if (x != visible_parameter_automation.begin()) {
+ sstr << ' ';
+ }
+ sstr << *x;
+ }
+
+ aevents->add_property ("path", path);
+ aevents->add_property ("visible", sstr.str());
+ break;
+ }
+ }
+
+ return *node;
+}
+
+void
+Redirect::what_has_automation (set<uint32_t>& s) const
+{
+ LockMonitor lm (_automation_lock, __LINE__, __FILE__);
+ map<uint32_t,AutomationList*>::const_iterator li;
+
+ for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) {
+ s.insert ((*li).first);
+ }
+}
+
+void
+Redirect::what_has_visible_automation (set<uint32_t>& s) const
+{
+ LockMonitor lm (_automation_lock, __LINE__, __FILE__);
+ set<uint32_t>::const_iterator li;
+
+ for (li = visible_parameter_automation.begin(); li != visible_parameter_automation.end(); ++li) {
+ s.insert (*li);
+ }
+}
+
+int
+Redirect::set_state (const XMLNode& node)
+{
+ const XMLProperty *prop;
+
+ if (node.name() != state_node_name) {
+ error << compose(_("incorrect XML node \"%1\" passed to Redirect object"), node.name()) << endmsg;
+ return -1;
+ }
+
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+ bool have_io = false;
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((*niter)->name() == IO::state_node_name) {
+
+ IO::set_state (**niter);
+ have_io = true;
+
+ } else if ((*niter)->name() == "Automation") {
+
+ XMLProperty *prop;
+
+ if ((prop = (*niter)->property ("path")) != 0) {
+ load_automation (prop->value());
+ } else {
+ warning << compose(_("%1: Automation node has no path property"), _name) << endmsg;
+ }
+
+ if ((prop = (*niter)->property ("visible")) != 0) {
+ uint32_t what;
+ stringstream sstr;
+
+ visible_parameter_automation.clear ();
+
+ sstr << prop->value();
+ while (1) {
+ sstr >> what;
+ if (sstr.fail()) {
+ break;
+ }
+ mark_automation_visible (what, true);
+ }
+ }
+
+ } else if ((*niter)->name() == "extra") {
+ _extra_xml = new XMLNode (*(*niter));
+ }
+ }
+
+ if (!have_io) {
+ error << _("XML node describing an IO is missing an IO node") << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("active")) == 0) {
+ error << _("XML node describing a redirect is missing the `active' field") << endmsg;
+ return -1;
+ }
+
+ if (_active != (prop->value() == "yes")) {
+ _active = !_active;
+ active_changed (this, this); /* EMIT_SIGNAL */
+ }
+
+ if ((prop = node.property ("placement")) == 0) {
+ error << _("XML node describing a redirect is missing the `placement' field") << endmsg;
+ return -1;
+ }
+
+ set_placement (prop->value(), this);
+
+ return 0;
+}
+
+AutomationList&
+Redirect::automation_list (uint32_t parameter)
+{
+ AutomationList* al = parameter_automation[parameter];
+
+ if (al == 0) {
+ al = parameter_automation[parameter] = new AutomationList (default_parameter_value (parameter));
+ /* let derived classes do whatever they need with this */
+ automation_list_creation_callback (parameter, *al);
+ }
+
+ return *al;
+}
+
+string
+Redirect::describe_parameter (uint32_t which)
+{
+ /* derived classes will override this */
+ return "";
+}
+
+void
+Redirect::can_automate (uint32_t what)
+{
+ can_automate_list.insert (what);
+}
+
+void
+Redirect::mark_automation_visible (uint32_t what, bool yn)
+{
+ if (yn) {
+ visible_parameter_automation.insert (what);
+ } else {
+ set<uint32_t>::iterator i;
+
+ if ((i = visible_parameter_automation.find (what)) != visible_parameter_automation.end()) {
+ visible_parameter_automation.erase (i);
+ }
+ }
+}
+
+bool
+Redirect::find_next_event (jack_nframes_t now, jack_nframes_t end, ControlEvent& next_event) const
+{
+ map<uint32_t,AutomationList*>::const_iterator li;
+ AutomationList::TimeComparator cmp;
+
+ next_event.when = max_frames;
+
+ for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) {
+
+ AutomationList::const_iterator i;
+ const AutomationList& alist (*((*li).second));
+ ControlEvent cp (now, 0.0f);
+
+ for (i = lower_bound (alist.const_begin(), alist.const_end(), &cp, cmp); i != alist.const_end() && (*i)->when < end; ++i) {
+ if ((*i)->when > now) {
+ break;
+ }
+ }
+
+ if (i != alist.const_end() && (*i)->when < end) {
+
+ if ((*i)->when < next_event.when) {
+ next_event.when = (*i)->when;
+ }
+ }
+ }
+
+ return next_event.when != max_frames;
+}
+
+void
+Redirect::store_state (RedirectState& state) const
+{
+ state.active = _active;
+}
+
+Change
+Redirect::restore_state (StateManager::State& state)
+{
+ RedirectState* rstate = dynamic_cast<RedirectState*> (&state);
+ set_active (rstate->active, this);
+ return Change (0);
+}
+
+StateManager::State*
+Redirect::state_factory (std::string why) const
+{
+ RedirectState* state = new RedirectState (why);
+
+ store_state (*state);
+
+ return state;
+}
+
+void
+Redirect::set_active (bool yn, void* src)
+{
+ _active = yn;
+ save_state (_("active_changed"));
+ active_changed (this, src);
+ _session.set_dirty ();
+}
+
diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc
new file mode 100644
index 0000000000..4a6aea07b8
--- /dev/null
+++ b/libs/ardour/region.cc
@@ -0,0 +1,995 @@
+/*
+ Copyright (C) 2000-2003 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.
+
+ $Id$
+*/
+
+#include <iostream>
+#include <cmath>
+#include <climits>
+#include <algorithm>
+
+#include <sigc++/bind.h>
+#include <sigc++/class_slot.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+
+#include <ardour/region.h>
+#include <ardour/playlist.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+Change Region::FadeChanged = ARDOUR::new_change ();
+Change Region::SyncOffsetChanged = ARDOUR::new_change ();
+Change Region::MuteChanged = ARDOUR::new_change ();
+Change Region::OpacityChanged = ARDOUR::new_change ();
+Change Region::LockChanged = ARDOUR::new_change ();
+Change Region::LayerChanged = ARDOUR::new_change ();
+Change Region::HiddenChanged = ARDOUR::new_change ();
+
+sigc::signal<void,Region *> Region::CheckNewRegion;
+
+Region::Region (jack_nframes_t start, jack_nframes_t length, const string& name, layer_t layer, Region::Flag flags)
+{
+ /* basic Region constructor */
+
+ _id = ARDOUR::new_id();
+ _flags = flags;
+ _playlist = 0;
+ _read_data_count = 0;
+ _frozen = 0;
+ pending_changed = Change (0);
+
+ _name = name;
+ _start = start;
+ _sync_position = _start;
+ _length = length;
+ _position = 0;
+ _layer = layer;
+ _current_state_id = 0;
+ _read_data_count = 0;
+ _first_edit = EditChangesNothing;
+ _last_layer_op = 0;
+}
+
+Region::Region (const Region& other, jack_nframes_t offset, jack_nframes_t length, const string& name, layer_t layer, Flag flags)
+{
+ /* create a new Region from part of an existing one */
+
+ _id = ARDOUR::new_id();
+ _frozen = 0;
+ pending_changed = Change (0);
+ _playlist = 0;
+ _read_data_count = 0;
+
+ _start = other._start + offset;
+ if (other._sync_position < offset) {
+ _sync_position = other._sync_position;
+ } else {
+ _sync_position = _start;
+ }
+ _length = length;
+ _name = name;
+ _position = 0;
+ _layer = layer;
+ _flags = Flag (flags & ~(Locked|WholeFile|Hidden));
+ _current_state_id = 0;
+ _first_edit = EditChangesNothing;
+ _last_layer_op = 0;
+}
+
+Region::Region (const Region &other)
+{
+ /* Pure copy constructor */
+
+ _id = ARDOUR::new_id();
+ _frozen = 0;
+ pending_changed = Change (0);
+ _playlist = 0;
+ _read_data_count = 0;
+
+ _first_edit = EditChangesID;
+ other._first_edit = EditChangesName;
+
+ if (other._extra_xml) {
+ _extra_xml = new XMLNode (*other._extra_xml);
+ } else {
+ _extra_xml = 0;
+ }
+
+ _start = other._start;
+ _sync_position = other._sync_position;
+ _length = other._length;
+ _name = other._name;
+ _position = other._position;
+ _layer = other._layer;
+ _flags = Flag (other._flags & ~Locked);
+ _current_state_id = 0;
+ _last_layer_op = other._last_layer_op;
+}
+
+Region::Region (const XMLNode& node)
+{
+ _id = 0;
+ _frozen = 0;
+ pending_changed = Change (0);
+ _playlist = 0;
+ _read_data_count = 0;
+ _start = 0;
+ _sync_position = _start;
+ _length = 0;
+ _name = X_("error: XML did not reset this");
+ _position = 0;
+ _layer = 0;
+ _flags = Flag (0);
+ _current_state_id = 0;
+ _first_edit = EditChangesNothing;
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+}
+
+Region::~Region ()
+{
+}
+
+void
+Region::set_playlist (Playlist* pl)
+{
+ _playlist = pl;
+}
+
+void
+Region::store_state (RegionState& state) const
+{
+ state._start = _start;
+ state._length = _length;
+ state._position = _position;
+ state._flags = _flags;
+ state._sync_position = _sync_position;
+ state._layer = _layer;
+ state._name = _name;
+ state._first_edit = _first_edit;
+}
+
+Change
+Region::restore_and_return_flags (RegionState& state)
+{
+ Change what_changed = Change (0);
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ if (_start != state._start) {
+ what_changed = Change (what_changed|StartChanged);
+ _start = state._start;
+ }
+ if (_length != state._length) {
+ what_changed = Change (what_changed|LengthChanged);
+ _length = state._length;
+ }
+ if (_position != state._position) {
+ what_changed = Change (what_changed|PositionChanged);
+ _position = state._position;
+ }
+ if (_sync_position != state._sync_position) {
+ _sync_position = state._sync_position;
+ what_changed = Change (what_changed|SyncOffsetChanged);
+ }
+ if (_layer != state._layer) {
+ what_changed = Change (what_changed|LayerChanged);
+ _layer = state._layer;
+ }
+
+ uint32_t old_flags = _flags;
+ _flags = Flag (state._flags);
+
+ if ((old_flags ^ state._flags) & Muted) {
+ what_changed = Change (what_changed|MuteChanged);
+ }
+ if ((old_flags ^ state._flags) & Opaque) {
+ what_changed = Change (what_changed|OpacityChanged);
+ }
+ if ((old_flags ^ state._flags) & Locked) {
+ what_changed = Change (what_changed|LockChanged);
+ }
+
+ _first_edit = state._first_edit;
+ }
+
+ return what_changed;
+}
+
+void
+Region::set_name (string str)
+
+{
+ if (_name != str) {
+ _name = str;
+ send_change (NameChanged);
+ }
+}
+
+void
+Region::set_length (jack_nframes_t len, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (_length != len && len != 0) {
+
+ if (!verify_length (len)) {
+ return;
+ }
+
+ _length = len;
+
+ _flags = Region::Flag (_flags & ~WholeFile);
+
+ first_edit ();
+ maybe_uncopy ();
+
+ if (!_frozen) {
+ recompute_at_end ();
+
+ char buf[64];
+ snprintf (buf, sizeof (buf), "length set to %u", len);
+ save_state (buf);
+ }
+
+ send_change (LengthChanged);
+ }
+}
+
+void
+Region::maybe_uncopy ()
+{
+}
+
+void
+Region::first_edit ()
+{
+ if (_first_edit != EditChangesNothing && _playlist) {
+
+ _name = _playlist->session().new_region_name (_name);
+ _first_edit = EditChangesNothing;
+
+ send_change (NameChanged);
+ CheckNewRegion (this);
+ }
+}
+
+void
+Region::move_to_natural_position (void *src)
+{
+ if (!_playlist) {
+ return;
+ }
+
+ Region* whole_file_region = get_parent();
+
+ if (whole_file_region) {
+ set_position (whole_file_region->position() + _start, src);
+ }
+}
+
+void
+Region::special_set_position (jack_nframes_t pos)
+{
+ /* this is used when creating a whole file region as
+ a way to store its "natural" or "captured" position.
+ */
+
+ _position = pos;
+}
+
+void
+Region::set_position (jack_nframes_t pos, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (_position != pos) {
+ _position = pos;
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "position set to %u", pos);
+ save_state (buf);
+ }
+ }
+
+ /* do this even if the position is the same. this helps out
+ a GUI that has moved its representation already.
+ */
+
+ send_change (PositionChanged);
+}
+
+void
+Region::set_position_on_top (jack_nframes_t pos, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (_position != pos) {
+ _position = pos;
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "position set to %u", pos);
+ save_state (buf);
+ }
+ }
+
+ _playlist->raise_region_to_top (*this);
+
+ /* do this even if the position is the same. this helps out
+ a GUI that has moved its representation already.
+ */
+
+ send_change (PositionChanged);
+}
+
+void
+Region::nudge_position (long n, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (n == 0) {
+ return;
+ }
+
+ if (n > 0) {
+ if (_position > max_frames - n) {
+ _position = max_frames;
+ } else {
+ _position += n;
+ }
+ } else {
+ if (_position < (jack_nframes_t) -n) {
+ _position = 0;
+ } else {
+ _position += n;
+ }
+ }
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "position set to %u", _position);
+ save_state (buf);
+ }
+
+ send_change (PositionChanged);
+}
+
+void
+Region::set_start (jack_nframes_t pos, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+ /* This just sets the start, nothing else. It effectively shifts
+ the contents of the Region within the overall extent of the Source,
+ without changing the Region's position or length
+ */
+
+ if (_start != pos) {
+
+ if (!verify_start (pos)) {
+ return;
+ }
+
+ _start = pos;
+ _flags = Region::Flag (_flags & ~WholeFile);
+ first_edit ();
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "start set to %u", pos);
+ save_state (buf);
+ }
+
+ send_change (StartChanged);
+ }
+}
+
+void
+Region::trim_start (jack_nframes_t new_position, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+ jack_nframes_t new_start;
+ int32_t start_shift;
+
+ if (new_position > _position) {
+ start_shift = new_position - _position;
+ } else {
+ start_shift = -(_position - new_position);
+ }
+
+ if (start_shift > 0) {
+
+ if (_start > max_frames - start_shift) {
+ new_start = max_frames;
+ } else {
+ new_start = _start + start_shift;
+ }
+
+ if (!verify_start (new_start)) {
+ return;
+ }
+
+ } else if (start_shift < 0) {
+
+ if (_start < (jack_nframes_t) -start_shift) {
+ new_start = 0;
+ } else {
+ new_start = _start + start_shift;
+ }
+ } else {
+ return;
+ }
+
+ if (new_start == _start) {
+ return;
+ }
+
+ _start = new_start;
+ _flags = Region::Flag (_flags & ~WholeFile);
+ first_edit ();
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "slipped start to %u", _start);
+ save_state (buf);
+ }
+
+ send_change (StartChanged);
+}
+
+void
+Region::trim_front (jack_nframes_t new_position, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ jack_nframes_t end = _position + _length - 1;
+ jack_nframes_t source_zero;
+
+ if (_position > _start) {
+ source_zero = _position - _start;
+ } else {
+ source_zero = 0; // its actually negative, but this will work for us
+ }
+
+ if (new_position < end) { /* can't trim it zero or negative length */
+
+ jack_nframes_t newlen;
+
+ /* can't trim it back passed where source position zero is located */
+
+ new_position = max (new_position, source_zero);
+
+
+ if (new_position > _position) {
+ newlen = _length - (new_position - _position);
+ } else {
+ newlen = _length + (_position - new_position);
+ }
+
+ trim_to_internal (new_position, newlen, src);
+ if (!_frozen) {
+ recompute_at_start ();
+ }
+ }
+}
+
+void
+Region::trim_end (jack_nframes_t new_endpoint, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (new_endpoint > _position) {
+ trim_to_internal (_position, new_endpoint - _position, this);
+ if (!_frozen) {
+ recompute_at_end ();
+ }
+ }
+}
+
+void
+Region::trim_to (jack_nframes_t position, jack_nframes_t length, void *src)
+{
+ if (_flags & Locked) {
+ return;
+ }
+
+ trim_to_internal (position, length, src);
+
+ if (!_frozen) {
+ recompute_at_start ();
+ recompute_at_end ();
+ }
+}
+
+void
+Region::trim_to_internal (jack_nframes_t position, jack_nframes_t length, void *src)
+{
+ int32_t start_shift;
+ jack_nframes_t new_start;
+
+ if (_flags & Locked) {
+ return;
+ }
+
+ if (position > _position) {
+ start_shift = position - _position;
+ } else {
+ start_shift = -(_position - position);
+ }
+
+ if (start_shift > 0) {
+
+ if (_start > max_frames - start_shift) {
+ new_start = max_frames;
+ } else {
+ new_start = _start + start_shift;
+ }
+
+
+ } else if (start_shift < 0) {
+
+ if (_start < (jack_nframes_t) -start_shift) {
+ new_start = 0;
+ } else {
+ new_start = _start + start_shift;
+ }
+ } else {
+ new_start = _start;
+ }
+
+ if (!verify_start_and_length (new_start, length)) {
+ return;
+ }
+
+ Change what_changed = Change (0);
+
+ if (_start != new_start) {
+ _start = new_start;
+ what_changed = Change (what_changed|StartChanged);
+ }
+ if (_length != length) {
+ _length = length;
+ what_changed = Change (what_changed|LengthChanged);
+ }
+ if (_position != position) {
+ _position = position;
+ what_changed = Change (what_changed|PositionChanged);
+ }
+
+ _flags = Region::Flag (_flags & ~WholeFile);
+
+ if (what_changed & (StartChanged|LengthChanged)) {
+ first_edit ();
+ }
+
+ if (what_changed) {
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "trimmed to %u-%u", _position, _position+_length-1);
+ save_state (buf);
+ }
+
+ send_change (what_changed);
+ }
+}
+
+void
+Region::set_hidden (bool yn)
+{
+ if (hidden() != yn) {
+
+ if (yn) {
+ _flags = Flag (_flags|Hidden);
+ } else {
+ _flags = Flag (_flags & ~Hidden);
+ }
+
+ send_change (HiddenChanged);
+ }
+}
+
+void
+Region::set_muted (bool yn)
+{
+ if (muted() != yn) {
+
+ if (yn) {
+ _flags = Flag (_flags|Muted);
+ } else {
+ _flags = Flag (_flags & ~Muted);
+ }
+
+ if (!_frozen) {
+ char buf[64];
+ if (yn) {
+ snprintf (buf, sizeof (buf), "muted");
+ } else {
+ snprintf (buf, sizeof (buf), "unmuted");
+ }
+ save_state (buf);
+ }
+
+ send_change (MuteChanged);
+ }
+}
+
+void
+Region::set_opaque (bool yn)
+{
+ if (opaque() != yn) {
+ if (!_frozen) {
+ char buf[64];
+ if (yn) {
+ snprintf (buf, sizeof (buf), "opaque");
+ _flags = Flag (_flags|Opaque);
+ } else {
+ snprintf (buf, sizeof (buf), "translucent");
+ _flags = Flag (_flags & ~Opaque);
+ }
+ save_state (buf);
+ }
+ send_change (OpacityChanged);
+ }
+}
+
+void
+Region::set_locked (bool yn)
+{
+ if (locked() != yn) {
+ if (!_frozen) {
+ char buf[64];
+ if (yn) {
+ snprintf (buf, sizeof (buf), "locked");
+ _flags = Flag (_flags|Locked);
+ } else {
+ snprintf (buf, sizeof (buf), "unlocked");
+ _flags = Flag (_flags & ~Locked);
+ }
+ save_state (buf);
+ }
+ send_change (LockChanged);
+ }
+}
+
+void
+Region::set_sync_position (jack_nframes_t absolute_pos)
+{
+ jack_nframes_t file_pos;
+
+ file_pos = _start + (absolute_pos - _position);
+
+ if (file_pos != _sync_position) {
+
+ _sync_position = file_pos;
+ _flags = Flag (_flags|SyncMarked);
+
+ if (!_frozen) {
+ char buf[64];
+ maybe_uncopy ();
+ snprintf (buf, sizeof (buf), "sync point set to %u", _sync_position);
+ save_state (buf);
+ }
+ send_change (SyncOffsetChanged);
+ }
+}
+
+void
+Region::clear_sync_position ()
+{
+ if (_flags & SyncMarked) {
+ _flags = Flag (_flags & ~SyncMarked);
+
+ if (!_frozen) {
+ maybe_uncopy ();
+ save_state ("sync point removed");
+ }
+ send_change (SyncOffsetChanged);
+ }
+}
+
+jack_nframes_t
+Region::sync_offset (int& dir) const
+{
+ /* returns the sync point relative the first frame of the region */
+
+ if (_flags & SyncMarked) {
+ if (_sync_position > _start) {
+ dir = 1;
+ return _sync_position - _start;
+ } else {
+ dir = -1;
+ return _start - _sync_position;
+ }
+ } else {
+ dir = 0;
+ return 0;
+ }
+}
+
+jack_nframes_t
+Region::adjust_to_sync (jack_nframes_t pos)
+{
+ int sync_dir;
+ jack_nframes_t offset = sync_offset (sync_dir);
+
+ if (sync_dir > 0) {
+ if (max_frames - pos > offset) {
+ pos += offset;
+ }
+ } else {
+ if (pos > offset) {
+ pos -= offset;
+ } else {
+ pos = 0;
+ }
+ }
+
+ return pos;
+}
+
+jack_nframes_t
+Region::sync_position() const
+{
+ if (_flags & SyncMarked) {
+ return _sync_position;
+ } else {
+ return _start;
+ }
+}
+
+
+void
+Region::raise ()
+{
+ if (_playlist == 0) {
+ return;
+ }
+
+ _playlist->raise_region (*this);
+}
+
+void
+Region::lower ()
+{
+ if (_playlist == 0) {
+ return;
+ }
+
+ _playlist->lower_region (*this);
+}
+
+void
+Region::raise_to_top ()
+{
+
+ if (_playlist == 0) {
+ return;
+ }
+
+ _playlist->raise_region_to_top (*this);
+}
+
+void
+Region::lower_to_bottom ()
+{
+ if (_playlist == 0) {
+ return;
+ }
+
+ _playlist->lower_region_to_bottom (*this);
+}
+
+void
+Region::set_layer (layer_t l)
+{
+ if (_layer != l) {
+ _layer = l;
+
+ if (!_frozen) {
+ char buf[64];
+ snprintf (buf, sizeof (buf), "layer set to %" PRIu32, _layer);
+ save_state (buf);
+ }
+
+ send_change (LayerChanged);
+ }
+}
+
+XMLNode&
+Region::state (bool full_state)
+{
+ XMLNode *node = new XMLNode ("Region");
+ char buf[64];
+
+ snprintf (buf, sizeof (buf), "%llu", _id);
+ node->add_property ("id", buf);
+ node->add_property ("name", _name);
+ snprintf (buf, sizeof (buf), "%u", _start);
+ node->add_property ("start", buf);
+ snprintf (buf, sizeof (buf), "%u", _length);
+ node->add_property ("length", buf);
+ snprintf (buf, sizeof (buf), "%u", _position);
+ node->add_property ("position", buf);
+
+ /* note: flags are stored by derived classes */
+
+ snprintf (buf, sizeof (buf), "%d", (int) _layer);
+ node->add_property ("layer", buf);
+ snprintf (buf, sizeof (buf), "%u", _sync_position);
+ node->add_property ("sync-position", buf);
+
+ return *node;
+}
+
+XMLNode&
+Region::get_state ()
+{
+ return state (true);
+}
+
+int
+Region::set_state (const XMLNode& node)
+{
+ const XMLNodeList& nlist = node.children();
+ const XMLProperty *prop;
+
+ if (_extra_xml) {
+ delete _extra_xml;
+ _extra_xml = 0;
+ }
+
+ if ((prop = node.property ("id")) == 0) {
+ error << _("Session: XMLNode describing a Region is incomplete (no id)") << endmsg;
+ return -1;
+ }
+
+ sscanf (prop->value().c_str(), "%llu", &_id);
+
+ if ((prop = node.property ("name")) == 0) {
+ error << _("Session: XMLNode describing a Region is incomplete (no name)") << endmsg;
+ return -1;
+ }
+
+ _name = prop->value();
+
+ if ((prop = node.property ("start")) != 0) {
+ _start = (jack_nframes_t) atoi (prop->value().c_str());
+ }
+
+ if ((prop = node.property ("length")) != 0) {
+ _length = (jack_nframes_t) atoi (prop->value().c_str());
+ }
+
+ if ((prop = node.property ("position")) != 0) {
+ _position = (jack_nframes_t) atoi (prop->value().c_str());
+ }
+
+ if ((prop = node.property ("layer")) != 0) {
+ _layer = (layer_t) atoi (prop->value().c_str());
+ }
+
+ /* note: derived classes set flags */
+
+ if ((prop = node.property ("sync-position")) != 0) {
+ _sync_position = (jack_nframes_t) atoi (prop->value().c_str());
+ } else {
+ _sync_position = _start;
+ }
+
+ for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ XMLNode *child;
+
+ child = (*niter);
+
+ if (child->name () == "extra") {
+ _extra_xml = new XMLNode (*child);
+ break;
+ }
+ }
+
+ _first_edit = EditChangesNothing;
+
+ return 0;
+}
+
+void
+Region::freeze ()
+{
+ _frozen++;
+}
+
+void
+Region::thaw (const string& why)
+{
+ Change what_changed = Change (0);
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ if (_frozen && --_frozen > 0) {
+ return;
+ }
+
+ if (pending_changed) {
+ what_changed = pending_changed;
+ pending_changed = Change (0);
+ }
+ }
+
+ if (what_changed == Change (0)) {
+ return;
+ }
+
+ if (what_changed & LengthChanged) {
+ if (what_changed & PositionChanged) {
+ recompute_at_start ();
+ }
+ recompute_at_end ();
+ }
+
+ save_state (why);
+ StateChanged (what_changed);
+}
+
+void
+Region::send_change (Change what_changed)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ if (_frozen) {
+ pending_changed = Change (pending_changed|what_changed);
+ return;
+ }
+ }
+
+ StateManager::send_state_changed (what_changed);
+}
+
+void
+Region::set_last_layer_op (uint64_t when)
+{
+ _last_layer_op = when;
+}
diff --git a/libs/ardour/reverse.cc b/libs/ardour/reverse.cc
new file mode 100644
index 0000000000..2474e0cb8d
--- /dev/null
+++ b/libs/ardour/reverse.cc
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+
+#include <pbd/basename.h>
+
+#include <ardour/types.h>
+#include <ardour/reverse.h>
+#include <ardour/filesource.h>
+#include <ardour/session.h>
+#include <ardour/audioregion.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+Reverse::Reverse (Session& s)
+ : AudioFilter (s)
+{
+}
+
+Reverse::~Reverse ()
+{
+}
+
+int
+Reverse::run (AudioRegion& region)
+{
+ AudioRegion::SourceList nsrcs;
+ AudioRegion::SourceList::iterator si;
+ const jack_nframes_t blocksize = 256 * 1048;
+ Sample buf[blocksize];
+ jack_nframes_t fpos;
+ jack_nframes_t fend;
+ jack_nframes_t fstart;
+ jack_nframes_t to_read;
+ int ret = -1;
+
+ /* create new sources */
+
+ if (make_new_sources (region, nsrcs)) {
+ goto out;
+ }
+
+ fend = region.start() + region.length();
+ fstart = region.start();
+
+ if (blocksize < fend) {
+ fpos =max(fstart, fend - blocksize);
+ } else {
+ fpos = fstart;
+ }
+
+ to_read = min (region.length(), blocksize);
+
+ /* now read it backwards */
+
+ while (1) {
+
+ uint32_t n;
+
+ for (n = 0, si = nsrcs.begin(); n < region.n_channels(); ++n, ++si) {
+
+ /* read it in */
+
+ if (region.source (n).read (buf, fpos, to_read) != to_read) {
+ goto out;
+ }
+
+ /* swap memory order */
+
+ for (jack_nframes_t i = 0; i < to_read/2; ++i) {
+ swap (buf[i],buf[to_read-1-i]);
+ }
+
+ /* write it out */
+
+ if ((*si)->write (buf, to_read) != to_read) {
+ goto out;
+ }
+ }
+
+ if (fpos == fstart) {
+ break;
+ } else if (fpos > fstart + to_read) {
+ fpos -= to_read;
+ to_read = min (fstart - fpos, blocksize);
+ } else {
+ to_read = fpos-fstart;
+ fpos = fstart;
+ }
+ };
+
+ ret = finish (region, nsrcs);
+
+ out:
+
+ if (ret) {
+ for (si = nsrcs.begin(); si != nsrcs.end(); ++si) {
+ (*si)->mark_for_remove ();
+ delete *si;
+ }
+ }
+
+ return ret;
+}
diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc
new file mode 100644
index 0000000000..cd925e90b5
--- /dev/null
+++ b/libs/ardour/route.cc
@@ -0,0 +1,2421 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <fstream>
+
+#include <sigc++/bind.h>
+#include <pbd/xml++.h>
+
+#include <ardour/timestamps.h>
+#include <ardour/audioengine.h>
+#include <ardour/route.h>
+#include <ardour/insert.h>
+#include <ardour/send.h>
+#include <ardour/session.h>
+#include <ardour/utils.h>
+#include <ardour/configuration.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/route_group.h>
+#include <ardour/port.h>
+#include <ardour/ladspa_plugin.h>
+#include <ardour/panner.h>
+#include <ardour/dB.h>
+#include <ardour/mix.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+
+uint32_t Route::order_key_cnt = 0;
+
+
+Route::Route (Session& sess, string name, int input_min, int input_max, int output_min, int output_max, Flag flg)
+ : IO (sess, name, input_min, input_max, output_min, output_max),
+ _flags (flg),
+ _midi_solo_control (*this, MIDIToggleControl::SoloControl, _session.midi_port()),
+ _midi_mute_control (*this, MIDIToggleControl::MuteControl, _session.midi_port())
+{
+ init ();
+}
+
+Route::Route (Session& sess, const XMLNode& node)
+ : IO (sess, "route"),
+ _midi_solo_control (*this, MIDIToggleControl::SoloControl, _session.midi_port()),
+ _midi_mute_control (*this, MIDIToggleControl::MuteControl, _session.midi_port())
+{
+ init ();
+ set_state (node);
+}
+
+void
+Route::init ()
+{
+ redirect_max_outs = 0;
+ _muted = false;
+ _soloed = false;
+ _solo_safe = false;
+ _phase_invert = false;
+ order_keys[N_("signal")] = order_key_cnt++;
+ _active = true;
+ _silent = false;
+ _meter_point = MeterPostFader;
+ _initial_delay = 0;
+ _roll_delay = 0;
+ _own_latency = 0;
+ _have_internal_generator = false;
+ _declickable = false;
+ _pending_declick = true;
+
+ _edit_group = 0;
+ _mix_group = 0;
+
+ _mute_affects_pre_fader = Config->get_mute_affects_pre_fader();
+ _mute_affects_post_fader = Config->get_mute_affects_post_fader();
+ _mute_affects_control_outs = Config->get_mute_affects_control_outs();
+ _mute_affects_main_outs = Config->get_mute_affects_main_outs();
+
+ solo_gain = 1.0;
+ desired_solo_gain = 1.0;
+ mute_gain = 1.0;
+ desired_mute_gain = 1.0;
+
+ _control_outs = 0;
+
+ input_changed.connect (mem_fun (this, &Route::input_change_handler));
+ output_changed.connect (mem_fun (this, &Route::output_change_handler));
+
+ reset_midi_control (_session.midi_port(), _session.get_midi_control());
+}
+
+Route::~Route ()
+{
+ GoingAway (); /* EMIT SIGNAL */
+ clear_redirects (this);
+
+ if (_control_outs) {
+ delete _control_outs;
+ }
+}
+
+long
+Route::order_key (string name) const
+{
+ OrderKeys::const_iterator i;
+
+ if ((i = order_keys.find (name)) == order_keys.end()) {
+ return -1;
+ }
+
+ return (*i).second;
+}
+
+void
+Route::set_order_key (string name, long n)
+{
+ order_keys[name] = n;
+ _session.set_dirty ();
+}
+
+void
+Route::inc_gain (gain_t fraction, void *src)
+{
+ IO::inc_gain (fraction, src);
+}
+
+void
+Route::set_gain (gain_t val, void *src)
+{
+ if (src != 0 && _mix_group && src != _mix_group && _mix_group->is_active()) {
+
+ if (_mix_group->is_relative()) {
+
+
+ gain_t usable_gain = gain();
+ if (usable_gain < 0.000001f) {
+ usable_gain=0.000001f;
+ }
+
+ gain_t delta = val;
+ if (delta < 0.000001f) {
+ delta=0.000001f;
+ }
+
+ delta -= usable_gain;
+
+ if (delta == 0.0f) return;
+
+ gain_t factor = delta / usable_gain;
+
+ if (factor > 0.0f) {
+ factor = _mix_group->get_max_factor(factor);
+ if (factor == 0.0f) {
+ gain_changed (src);
+ return;
+ }
+ } else {
+ factor = _mix_group->get_min_factor(factor);
+ if (factor == 0.0f) {
+ gain_changed (src);
+ return;
+ }
+ }
+
+ _mix_group->apply (&Route::inc_gain, factor, _mix_group);
+
+ } else {
+
+ _mix_group->apply (&Route::set_gain, val, _mix_group);
+ }
+
+ return;
+ }
+
+ if (val == gain()) {
+ return;
+ }
+
+ IO::set_gain (val, src);
+}
+
+void
+Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
+ jack_nframes_t start_frame, jack_nframes_t end_frame,
+ jack_nframes_t nframes, jack_nframes_t offset, bool with_redirects, int declick,
+ bool meter)
+{
+ uint32_t n;
+ RedirectList::iterator i;
+ bool post_fader_work = false;
+ bool mute_declick_applied = false;
+ gain_t dmg, dsg, dg;
+ vector<Sample*>::iterator bufiter;
+ IO *co;
+ bool mute_audible;
+ bool solo_audible;
+ bool no_monitor = (Config->get_use_hardware_monitoring() || Config->get_no_sw_monitoring ());
+ gain_t* gab = _session.gain_automation_buffer();
+
+ declick = _pending_declick;
+
+ {
+ TentativeLockMonitor cm (control_outs_lock, __LINE__, __FILE__);
+
+ if (cm.locked()) {
+ co = _control_outs;
+ } else {
+ co = 0;
+ }
+ }
+
+ {
+ TentativeLockMonitor dm (declick_lock, __LINE__, __FILE__);
+
+ if (dm.locked()) {
+ dmg = desired_mute_gain;
+ dsg = desired_solo_gain;
+ dg = _desired_gain;
+ } else {
+ dmg = mute_gain;
+ dsg = solo_gain;
+ dg = _gain;
+ }
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ GLOBAL DECLICK (for transport changes etc.)
+ -------------------------------------------------------------------------------------------------- */
+
+ if (declick > 0) {
+ apply_declick (bufs, nbufs, nframes, 0.0, 1.0, _phase_invert);
+ _pending_declick = 0;
+ } else if (declick < 0) {
+ apply_declick (bufs, nbufs, nframes, 1.0, 0.0, _phase_invert);
+ _pending_declick = 0;
+ } else {
+
+ /* no global declick */
+
+ if (solo_gain != dsg) {
+ apply_declick (bufs, nbufs, nframes, solo_gain, dsg, _phase_invert);
+ solo_gain = dsg;
+ }
+ }
+
+
+ /* ----------------------------------------------------------------------------------------------------
+ INPUT METERING & MONITORING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterInput)) {
+ for (n = 0; n < nbufs; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ }
+
+ if (!_soloed && _mute_affects_pre_fader && (mute_gain != dmg)) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ if ((_meter_point == MeterInput) && co) {
+
+ solo_audible = dsg > 0;
+ mute_audible = dmg > 0;// || !_mute_affects_pre_fader;
+
+ if ( // muted by solo of another track
+
+ !solo_audible ||
+
+ // muted by mute of this track
+
+ !mute_audible ||
+
+ // rec-enabled but not s/w monitoring
+
+ // TODO: this is probably wrong
+
+ (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output (bufs, nbufs, nframes, offset);
+
+ }
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ PRE-FADER REDIRECTS
+ -------------------------------------------------------------------------------------------------- */
+
+ if (with_redirects) {
+ TentativeLockMonitor rm (redirect_lock, __LINE__, __FILE__);
+ if (rm.locked()) {
+ if (mute_gain > 0 || !_mute_affects_pre_fader) {
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ switch ((*i)->placement()) {
+ case PreFader:
+ (*i)->run (bufs, nbufs, nframes, offset);
+ break;
+ case PostFader:
+ post_fader_work = true;
+ break;
+ }
+ }
+ } else {
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ switch ((*i)->placement()) {
+ case PreFader:
+ (*i)->silence (nframes, offset);
+ break;
+ case PostFader:
+ post_fader_work = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+
+ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_post_fader) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ PRE-FADER METERING & MONITORING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterPreFader)) {
+ for (n = 0; n < nbufs; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ }
+
+
+ if ((_meter_point == MeterPreFader) && co) {
+
+ solo_audible = dsg > 0;
+ mute_audible = dmg > 0 || !_mute_affects_pre_fader;
+
+ if ( // muted by solo of another track
+
+ !solo_audible ||
+
+ // muted by mute of this track
+
+ !mute_audible ||
+
+ // rec-enabled but not s/w monitoring
+
+ (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output (bufs, nbufs, nframes, offset);
+
+ }
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ GAIN STAGE
+ -------------------------------------------------------------------------------------------------- */
+
+ /* if not recording or recording and requiring any monitor signal, then apply gain */
+
+ if ( // not recording
+
+ !(record_enabled() && _session.actively_recording()) ||
+
+ // OR recording
+
+ // h/w monitoring not in use
+
+ (!Config->get_use_hardware_monitoring() &&
+
+ // AND software monitoring required
+
+ !Config->get_no_sw_monitoring())) {
+
+ if (apply_gain_automation) {
+
+ if (_phase_invert) {
+ for (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+
+ for (jack_nframes_t nx = 0; nx < nframes; ++nx) {
+ sp[nx] *= -gab[nx];
+ }
+ }
+ } else {
+ for (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+
+ for (jack_nframes_t nx = 0; nx < nframes; ++nx) {
+ sp[nx] *= gab[nx];
+ }
+ }
+ }
+
+ if (apply_gain_automation) {
+ _effective_gain = gab[nframes-1];
+ }
+
+ } else {
+
+ /* manual (scalar) gain */
+
+ if (_gain != dg) {
+
+ apply_declick (bufs, nbufs, nframes, _gain, dg, _phase_invert);
+ _gain = dg;
+
+ } else if (_gain != 0 && (_phase_invert || _gain != 1.0)) {
+
+ /* no need to interpolate current gain value,
+ but its non-unity, so apply it. if the gain
+ is zero, do nothing because we'll ship silence
+ below.
+ */
+
+ gain_t this_gain;
+
+ if (_phase_invert) {
+ this_gain = -_gain;
+ } else {
+ this_gain = _gain;
+ }
+
+ for (n = 0; n < nbufs; ++n) {
+ Sample *sp = bufs[n];
+ apply_gain_to_buffer(sp,nframes,this_gain);
+ }
+
+ } else if (_gain == 0) {
+ for (n = 0; n < nbufs; ++n) {
+ memset (bufs[n], 0, sizeof (Sample) * nframes);
+ }
+ }
+ }
+
+ } else {
+
+ /* actively recording, no monitoring required; leave buffers as-is to save CPU cycles */
+
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ POST-FADER REDIRECTS
+ -------------------------------------------------------------------------------------------------- */
+
+ /* note that post_fader_work cannot be true unless with_redirects was also true, so don't test both */
+
+ if (post_fader_work) {
+
+ TentativeLockMonitor rm (redirect_lock, __LINE__, __FILE__);
+ if (rm.locked()) {
+ if (mute_gain > 0 || !_mute_affects_post_fader) {
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ switch ((*i)->placement()) {
+ case PreFader:
+ break;
+ case PostFader:
+ (*i)->run (bufs, nbufs, nframes, offset);
+ break;
+ }
+ }
+ } else {
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ switch ((*i)->placement()) {
+ case PreFader:
+ break;
+ case PostFader:
+ (*i)->silence (nframes, offset);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_control_outs) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ CONTROL OUTPUT STAGE
+ -------------------------------------------------------------------------------------------------- */
+
+ if ((_meter_point == MeterPostFader) && co) {
+
+ solo_audible = solo_gain > 0;
+ mute_audible = dmg > 0 || !_mute_affects_control_outs;
+
+ if ( // silent anyway
+
+ (_gain == 0 && !apply_gain_automation) ||
+
+ // muted by solo of another track
+
+ !solo_audible ||
+
+ // muted by mute of this track
+
+ !mute_audible ||
+
+ // recording but not s/w monitoring
+
+ (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording()))
+
+ ) {
+
+ co->silence (nframes, offset);
+
+ } else {
+
+ co->deliver_output_no_pan (bufs, nbufs, nframes, offset);
+ }
+ }
+
+ /* ----------------------------------------------------------------------
+ GLOBAL MUTE
+ ----------------------------------------------------------------------*/
+
+ if (!_soloed && (mute_gain != dmg) && !mute_declick_applied && _mute_affects_main_outs) {
+ apply_declick (bufs, nbufs, nframes, mute_gain, dmg, _phase_invert);
+ mute_gain = dmg;
+ mute_declick_applied = true;
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ MAIN OUTPUT STAGE
+ -------------------------------------------------------------------------------------------------- */
+
+ solo_audible = dsg > 0;
+ mute_audible = dmg > 0 || !_mute_affects_main_outs;
+
+ if (n_outputs() == 0) {
+
+ /* relax */
+
+ } else if (no_monitor && record_enabled() && (!_session.get_auto_input() || _session.actively_recording())) {
+
+ IO::silence (nframes, offset);
+
+ } else {
+
+ if ( // silent anyway
+
+ (_gain == 0 && !apply_gain_automation) ||
+
+ // muted by solo of another track, but not using control outs for solo
+
+ (!solo_audible && (_session.solo_model() != Session::SoloBus)) ||
+
+ // muted by mute of this track
+
+ !mute_audible
+
+ ) {
+
+ /* don't use Route::silence() here, because that causes
+ all outputs (sends, port inserts, etc. to be silent).
+ */
+
+ if (_meter_point == MeterPostFader) {
+ reset_peak_meters ();
+ }
+
+ IO::silence (nframes, offset);
+
+ } else {
+
+ if (_session.transport_speed() > 1.5f || _session.transport_speed() < -1.5f) {
+ pan (bufs, nbufs, nframes, offset, speed_quietning);
+ } else {
+ if (!_panner->empty() &&
+ (_panner->automation_state() & Play ||
+ ((_panner->automation_state() & Touch) && !_panner->touching()))) {
+ pan_automated (bufs, nbufs, start_frame, end_frame, nframes, offset);
+ } else {
+ pan (bufs, nbufs, nframes, offset, 1.0);
+ }
+ }
+ }
+
+ }
+
+ /* ----------------------------------------------------------------------------------------------------
+ POST-FADER METERING
+ -------------------------------------------------------------------------------------------------- */
+
+ if (meter && (_meter_point == MeterPostFader)) {
+// cerr << "meter post" << endl;
+
+ if ((_gain == 0 && !apply_gain_automation) || dmg == 0) {
+ uint32_t no = n_outputs();
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = 0;
+ }
+ } else {
+ uint32_t no = n_outputs();
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = Session::compute_peak (output(n)->get_buffer (nframes) + offset, nframes, _peak_power[n]);
+ }
+ }
+ }
+}
+
+uint32_t
+Route::n_process_buffers ()
+{
+ return max (n_inputs(), redirect_max_outs);
+}
+
+void
+
+Route::passthru (jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t nframes, jack_nframes_t offset, int declick, bool meter_first)
+{
+ vector<Sample*>& bufs = _session.get_passthru_buffers();
+ uint32_t limit = n_process_buffers ();
+
+ _silent = false;
+
+ collect_input (bufs, limit, nframes, offset);
+
+#define meter_stream meter_first
+
+ if (meter_first) {
+ for (uint32_t n = 0; n < limit; ++n) {
+ _peak_power[n] = Session::compute_peak (bufs[n], nframes, _peak_power[n]);
+ }
+ meter_stream = false;
+ } else {
+ meter_stream = true;
+ }
+
+ process_output_buffers (bufs, limit, start_frame, end_frame, nframes, offset, true, declick, meter_stream);
+
+#undef meter_stream
+}
+
+void
+Route::set_phase_invert (bool yn, void *src)
+{
+ if (_phase_invert != yn) {
+ _phase_invert = yn;
+ }
+ // phase_invert_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_solo (bool yn, void *src)
+{
+ if (_solo_safe) {
+ return;
+ }
+
+ if (_mix_group && src != _mix_group && _mix_group->is_active()) {
+ _mix_group->apply (&Route::set_solo, yn, _mix_group);
+ return;
+ }
+
+ if (_soloed != yn) {
+ _soloed = yn;
+ solo_changed (src); /* EMIT SIGNAL */
+
+ if (_session.get_midi_feedback()) {
+ _midi_solo_control.send_feedback (_soloed);
+ }
+ }
+}
+
+void
+Route::set_solo_mute (bool yn)
+{
+ LockMonitor lm (declick_lock, __LINE__, __FILE__);
+
+ /* Called by Session in response to another Route being soloed.
+ */
+
+ desired_solo_gain = (yn?0.0:1.0);
+}
+
+void
+Route::set_solo_safe (bool yn, void *src)
+{
+ if (_solo_safe != yn) {
+ _solo_safe = yn;
+ solo_safe_changed (src); /* EMIT SIGNAL */
+ }
+}
+
+void
+Route::set_mute (bool yn, void *src)
+
+{
+ if (_mix_group && src != _mix_group && _mix_group->is_active()) {
+ _mix_group->apply (&Route::set_mute, yn, _mix_group);
+ return;
+ }
+
+ if (_muted != yn) {
+ _muted = yn;
+ mute_changed (src); /* EMIT SIGNAL */
+
+ if (_session.get_midi_feedback()) {
+ _midi_mute_control.send_feedback (_muted);
+ }
+
+ LockMonitor lm (declick_lock, __LINE__, __FILE__);
+ desired_mute_gain = (yn?0.0f:1.0f);
+ }
+}
+
+int
+Route::add_redirect (Redirect *redirect, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ PluginInsert* pi;
+ PortInsert* porti;
+
+ if ((pi = dynamic_cast<PluginInsert*>(redirect)) != 0) {
+ pi->set_count (1);
+
+ if (pi->input_streams() == 0) {
+ /* instrument plugin */
+ _have_internal_generator = true;
+ }
+
+ } else if ((porti = dynamic_cast<PortInsert*>(redirect)) != 0) {
+
+ /* force new port inserts to start out with an i/o configuration
+ that matches this route's i/o configuration.
+
+ the "inputs" for the port are supposed to match the output
+ of this route.
+
+ the "outputs" of the route should match the inputs of this
+ route. XXX shouldn't they match the number of active signal
+ streams at the point of insertion?
+
+ */
+
+ porti->ensure_io (n_outputs (), n_inputs(), false, this);
+ }
+
+ _redirects.push_back (redirect);
+
+ if (_reset_plugin_counts (err_streams)) {
+ _redirects.pop_back ();
+ _reset_plugin_counts (0); // it worked before we tried to add it ...
+ return -1;
+ }
+
+ redirect->activate ();
+ redirect->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy));
+ }
+
+ if (redirect_max_outs != old_rmo || old_rmo == 0) {
+ reset_panner ();
+ }
+
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ RedirectList::iterator existing_end = _redirects.end();
+ --existing_end;
+
+ for (RedirectList::const_iterator i = others.begin(); i != others.end(); ++i) {
+
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(*i)) != 0) {
+ pi->set_count (1);
+ }
+
+ _redirects.push_back (*i);
+
+ if (_reset_plugin_counts (err_streams)) {
+ ++existing_end;
+ _redirects.erase (existing_end, _redirects.end());
+ _reset_plugin_counts (0); // it worked before we tried to add it ...
+ return -1;
+ }
+
+ (*i)->activate ();
+ (*i)->active_changed.connect (mem_fun (*this, &Route::redirect_active_proxy));
+ }
+ }
+
+ if (redirect_max_outs != old_rmo || old_rmo == 0) {
+ reset_panner ();
+ }
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+Route::clear_redirects (void *src)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return;
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ delete *i;
+ }
+
+ _redirects.clear ();
+ }
+
+ if (redirect_max_outs != old_rmo) {
+ reset_panner ();
+ }
+
+ redirect_max_outs = 0;
+ _have_internal_generator = false;
+ redirects_changed (src); /* EMIT SIGNAL */
+}
+
+int
+Route::remove_redirect (Redirect *redirect, void *src, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (!_session.engine().connected()) {
+ return 1;
+ }
+
+ redirect_max_outs = 0;
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ RedirectList::iterator i;
+ bool removed = false;
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if (*i == redirect) {
+
+ RedirectList::iterator tmp;
+
+ /* move along, see failure case for reset_plugin_counts()
+ where we may need to reinsert the redirect.
+ */
+
+ tmp = i;
+ ++tmp;
+
+ /* stop redirects that send signals to JACK ports
+ from causing noise as a result of no longer being
+ run.
+ */
+
+ Send* send;
+ PortInsert* port_insert;
+
+ if ((send = dynamic_cast<Send*> (*i)) != 0) {
+ send->disconnect_inputs (this);
+ send->disconnect_outputs (this);
+ } else if ((port_insert = dynamic_cast<PortInsert*> (*i)) != 0) {
+ port_insert->disconnect_inputs (this);
+ port_insert->disconnect_outputs (this);
+ }
+
+ _redirects.erase (i);
+
+ i = tmp;
+ removed = true;
+ break;
+ }
+ }
+
+ if (!removed) {
+ /* what? */
+ return 1;
+ }
+
+ if (_reset_plugin_counts (err_streams)) {
+ /* get back to where we where */
+ _redirects.insert (i, redirect);
+ /* we know this will work, because it worked before :) */
+ _reset_plugin_counts (0);
+ return -1;
+ }
+
+ bool foo = false;
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(*i)) != 0) {
+ if (pi->is_generator()) {
+ foo = true;
+ }
+ }
+ }
+
+ _have_internal_generator = foo;
+ }
+
+ if (old_rmo != redirect_max_outs) {
+ reset_panner ();
+ }
+
+ redirects_changed (src); /* EMIT SIGNAL */
+ return 0;
+}
+
+int
+Route::reset_plugin_counts (uint32_t* lpc)
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ return _reset_plugin_counts (lpc);
+}
+
+
+int
+Route::_reset_plugin_counts (uint32_t* err_streams)
+{
+ RedirectList::iterator r;
+ uint32_t i_cnt;
+ uint32_t s_cnt;
+ map<Placement,list<InsertCount> > insert_map;
+ jack_nframes_t initial_streams;
+
+ redirect_max_outs = 0;
+ i_cnt = 0;
+ s_cnt = 0;
+
+ /* divide inserts up by placement so we get the signal flow
+ properly modelled. we need to do this because the _redirects
+ list is not sorted by placement, and because other reasons may
+ exist now or in the future for this separate treatment.
+ */
+
+ for (r = _redirects.begin(); r != _redirects.end(); ++r) {
+
+ Insert *insert;
+
+ /* do this here in case we bomb out before we get to the end of
+ this function.
+ */
+
+ redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+
+ if ((insert = dynamic_cast<Insert*>(*r)) != 0) {
+ ++i_cnt;
+ insert_map[insert->placement()].push_back (InsertCount (*insert));
+
+ /* reset plugin counts back to one for now so
+ that we have a predictable, controlled
+ state to try to configure.
+ */
+
+ PluginInsert* pi;
+
+ if ((pi = dynamic_cast<PluginInsert*>(insert)) != 0) {
+ pi->set_count (1);
+ }
+
+ } else if (dynamic_cast<Send*> (*r) != 0) {
+ ++s_cnt;
+ }
+ }
+
+ if (i_cnt == 0) {
+ if (s_cnt) {
+ goto recompute;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Now process each placement in order, checking to see if we
+ can really do what has been requested.
+ */
+
+ /* A: PreFader */
+
+ if (check_some_plugin_counts (insert_map[PreFader], n_inputs (), err_streams)) {
+ return -1;
+ }
+
+ /* figure out the streams that will feed into PreFader */
+
+ if (!insert_map[PreFader].empty()) {
+ InsertCount& ic (insert_map[PreFader].back());
+ initial_streams = ic.insert.compute_output_streams (ic.cnt);
+ } else {
+ initial_streams = n_inputs ();
+ }
+
+ /* B: PostFader */
+
+ if (check_some_plugin_counts (insert_map[PostFader], initial_streams, err_streams)) {
+ return -1;
+ }
+
+ /* OK, everything can be set up correctly, so lets do it */
+
+ apply_some_plugin_counts (insert_map[PreFader]);
+ apply_some_plugin_counts (insert_map[PostFader]);
+
+ /* recompute max outs of any redirect */
+
+ recompute:
+
+ redirect_max_outs = 0;
+ RedirectList::iterator prev = _redirects.end();
+
+ for (r = _redirects.begin(); r != _redirects.end(); prev = r, ++r) {
+ Send* s;
+
+ if ((s = dynamic_cast<Send*> (*r)) != 0) {
+ if (r == _redirects.begin()) {
+ s->expect_inputs (n_inputs());
+ } else {
+ s->expect_inputs ((*prev)->output_streams());
+ }
+ }
+
+ redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+ }
+
+ /* we're done */
+
+ return 0;
+}
+
+int32_t
+Route::apply_some_plugin_counts (list<InsertCount>& iclist)
+{
+ list<InsertCount>::iterator i;
+
+ for (i = iclist.begin(); i != iclist.end(); ++i) {
+
+ if ((*i).insert.configure_io ((*i).cnt, (*i).in, (*i).out)) {
+ return -1;
+ }
+ /* make sure that however many we have, they are all active */
+ (*i).insert.activate ();
+ }
+
+ return 0;
+}
+
+int32_t
+Route::check_some_plugin_counts (list<InsertCount>& iclist, int32_t required_inputs, uint32_t* err_streams)
+{
+ list<InsertCount>::iterator i;
+
+ for (i = iclist.begin(); i != iclist.end(); ++i) {
+
+ if (((*i).cnt = (*i).insert.can_support_input_configuration (required_inputs)) < 0) {
+ if (err_streams) {
+ *err_streams = required_inputs;
+ }
+ return -1;
+ }
+
+ (*i).in = required_inputs;
+ (*i).out = (*i).insert.compute_output_streams ((*i).cnt);
+
+ required_inputs = (*i).out;
+ }
+
+ return 0;
+}
+
+int
+Route::copy_redirects (const Route& other, Placement placement, uint32_t* err_streams)
+{
+ uint32_t old_rmo = redirect_max_outs;
+
+ if (err_streams) {
+ *err_streams = 0;
+ }
+
+ RedirectList to_be_deleted;
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ RedirectList::iterator tmp;
+ RedirectList the_copy;
+
+ the_copy = _redirects;
+
+ /* remove all relevant redirects */
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) {
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->placement() == placement) {
+ to_be_deleted.push_back (*i);
+ _redirects.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ /* now copy the relevant ones from "other" */
+
+ for (RedirectList::const_iterator i = other._redirects.begin(); i != other._redirects.end(); ++i) {
+ if ((*i)->placement() == placement) {
+ _redirects.push_back (Redirect::clone (**i));
+ }
+ }
+
+ /* reset plugin stream handling */
+
+ if (_reset_plugin_counts (err_streams)) {
+
+ /* FAILED COPY ATTEMPT: we have to restore order */
+
+ /* delete all cloned redirects */
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->placement() == placement) {
+ delete *i;
+ _redirects.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ /* restore the natural order */
+
+ _redirects = the_copy;
+ redirect_max_outs = old_rmo;
+
+ /* we failed, even though things are OK again */
+
+ return -1;
+
+ } else {
+
+ /* SUCCESSFUL COPY ATTEMPT: delete the redirects we removed pre-copy */
+
+ for (RedirectList::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
+ delete *i;
+ }
+ }
+ }
+
+ if (redirect_max_outs != old_rmo || old_rmo == 0) {
+ reset_panner ();
+ }
+
+ redirects_changed (this); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+Route::all_redirects_flip ()
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (_redirects.empty()) {
+ return;
+ }
+
+ bool first_is_on = _redirects.front()->active();
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_active (!first_is_on, this);
+ }
+}
+
+void
+Route::all_redirects_active (bool state)
+{
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (_redirects.empty()) {
+ return;
+ }
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_active (state, this);
+ }
+}
+
+struct RedirectSorter {
+ bool operator() (const Redirect *a, const Redirect *b) {
+ return a->sort_key() < b->sort_key();
+ }
+};
+
+int
+Route::sort_redirects (uint32_t* err_streams)
+{
+ {
+ RedirectSorter comparator;
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ uint32_t old_rmo = redirect_max_outs;
+
+ /* the sweet power of C++ ... */
+
+ RedirectList as_it_was_before = _redirects;
+
+ _redirects.sort (comparator);
+
+ if (_reset_plugin_counts (err_streams)) {
+ _redirects = as_it_was_before;
+ redirect_max_outs = old_rmo;
+ return -1;
+ }
+ }
+
+ reset_panner ();
+ redirects_changed (this); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+XMLNode&
+Route::get_state()
+{
+ return state(true);
+}
+
+XMLNode&
+Route::get_template()
+{
+ return state(false);
+}
+
+XMLNode&
+Route::state(bool full_state)
+{
+ XMLNode *node = new XMLNode("Route");
+ XMLNode *aevents;
+ RedirectList:: iterator i;
+ char buf[32];
+
+ if (_flags) {
+ snprintf (buf, sizeof (buf), "0x%x", _flags);
+ node->add_property("flags", buf);
+ }
+ node->add_property("active", _active?"yes":"no");
+ node->add_property("muted", _muted?"yes":"no");
+ node->add_property("soloed", _soloed?"yes":"no");
+ node->add_property("phase-invert", _phase_invert?"yes":"no");
+ node->add_property("mute-affects-pre-fader", _mute_affects_pre_fader?"yes":"no");
+ node->add_property("mute-affects-post-fader", _mute_affects_post_fader?"yes":"no");
+ node->add_property("mute-affects-control-outs", _mute_affects_control_outs?"yes":"no");
+ node->add_property("mute-affects-main-outs", _mute_affects_main_outs?"yes":"no");
+
+ if (_edit_group) {
+ node->add_property("edit-group", _edit_group->name());
+ }
+ if (_mix_group) {
+ node->add_property("mix-group", _mix_group->name());
+ }
+
+ /* MIDI control */
+
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte additional;
+ XMLNode* midi_node = 0;
+ XMLNode* child;
+
+ midi_node = node->add_child ("MIDI");
+
+ if (_midi_mute_control.get_control_info (chn, ev, additional)) {
+ child = midi_node->add_child ("mute");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+ if (_midi_solo_control.get_control_info (chn, ev, additional)) {
+ child = midi_node->add_child ("solo");
+ set_midi_node_info (child, ev, chn, additional);
+ }
+
+
+ string order_string;
+ OrderKeys::iterator x = order_keys.begin();
+
+ while (x != order_keys.end()) {
+ order_string += (*x).first;
+ order_string += '=';
+ snprintf (buf, sizeof(buf), "%ld", (*x).second);
+ order_string += buf;
+
+ ++x;
+
+ if (x == order_keys.end()) {
+ break;
+ }
+
+ order_string += ':';
+ }
+ node->add_property ("order-keys", order_string);
+
+ node->add_child_nocopy (IO::state (full_state));
+
+ if (_control_outs) {
+ XMLNode* cnode = new XMLNode (X_("ControlOuts"));
+ cnode->add_child_nocopy (_control_outs->state (full_state));
+ node->add_child_nocopy (*cnode);
+ }
+
+ if (_comment.length()) {
+ XMLNode *cmt = node->add_child ("Comment");
+ cmt->add_content (_comment);
+ }
+
+ if (full_state) {
+ string path;
+
+ path = _session.snap_name();
+ path += "-gain-";
+ path += legalize_for_path (_name);
+ path += ".automation";
+
+ /* XXX we didn't ask for a state save, we asked for the current state.
+ FIX ME!
+ */
+
+ if (save_automation (path)) {
+ error << _("Could not get state of route. Problem with save_automation") << endmsg;
+ }
+
+ aevents = node->add_child ("Automation");
+ aevents->add_property ("path", path);
+ }
+
+ for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+ node->add_child_nocopy((*i)->state (full_state));
+ }
+
+ if (_extra_xml){
+ node->add_child_copy (*_extra_xml);
+ }
+
+ return *node;
+}
+
+void
+Route::set_deferred_state ()
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+
+ if (!deferred_state) {
+ return;
+ }
+
+ nlist = deferred_state->children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+ add_redirect_from_xml (**niter);
+ }
+
+ delete deferred_state;
+ deferred_state = 0;
+}
+
+void
+Route::add_redirect_from_xml (const XMLNode& node)
+{
+ const XMLProperty *prop;
+ Insert *insert = 0;
+ Send *send = 0;
+
+ if (node.name() == "Send") {
+
+ try {
+ send = new Send (_session, node);
+ }
+
+ catch (failed_constructor &err) {
+ error << _("Send construction failed") << endmsg;
+ return;
+ }
+
+ add_redirect (send, this);
+
+ } else if (node.name() == "Insert") {
+
+ try {
+ if ((prop = node.property ("type")) != 0) {
+
+ if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "vst") {
+
+ insert = new PluginInsert(_session, node);
+
+ } else if (prop->value() == "port") {
+
+
+ insert = new PortInsert (_session, node);
+
+ } else {
+
+ error << compose(_("unknown Insert type \"%1\"; ignored"), prop->value()) << endmsg;
+ }
+
+ add_redirect (insert, this);
+
+ } else {
+ error << _("Insert XML node has no type property") << endmsg;
+ }
+ }
+
+ catch (failed_constructor &err) {
+ warning << _("insert could not be created. Ignored.") << endmsg;
+ return;
+ }
+ }
+}
+
+int
+Route::set_state (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ XMLNode *child;
+ XMLPropertyList plist;
+ const XMLProperty *prop;
+ XMLNodeList midi_kids;
+
+
+ if (node.name() != "Route"){
+ error << compose(_("Bad node sent to Route::set_state() [%1]"), node.name()) << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("flags")) != 0) {
+ int x;
+ sscanf (prop->value().c_str(), "0x%x", &x);
+ _flags = Flag (x);
+ } else {
+ _flags = Flag (0);
+ }
+
+ if ((prop = node.property ("phase-invert")) != 0) {
+ set_phase_invert(prop->value()=="yes"?true:false, this);
+ }
+
+ if ((prop = node.property ("active")) != 0) {
+ set_active (prop->value() == "yes");
+ }
+
+ if ((prop = node.property ("muted")) != 0) {
+ bool yn = prop->value()=="yes"?true:false;
+
+ /* force reset of mute status */
+
+ _muted = !yn;
+ set_mute(yn, this);
+ mute_gain = desired_mute_gain;
+ }
+
+ if ((prop = node.property ("soloed")) != 0) {
+ bool yn = prop->value()=="yes"?true:false;
+
+ /* force reset of solo status */
+
+ _soloed = !yn;
+ set_solo (yn, this);
+ solo_gain = desired_solo_gain;
+ }
+
+ if ((prop = node.property ("mute-affects-pre-fader")) != 0) {
+ _mute_affects_pre_fader = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-post-fader")) != 0) {
+ _mute_affects_post_fader = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-control-outs")) != 0) {
+ _mute_affects_control_outs = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("mute-affects-main-outs")) != 0) {
+ _mute_affects_main_outs = (prop->value()=="yes")?true:false;
+ }
+
+ if ((prop = node.property ("edit-group")) != 0) {
+ RouteGroup* edit_group = _session.edit_group_by_name(prop->value());
+ if(edit_group == 0) {
+ error << compose(_("Route %1: unknown edit group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg;
+ } else {
+ set_edit_group(edit_group, this);
+ }
+ }
+
+ if ((prop = node.property ("order-keys")) != 0) {
+
+ long n;
+
+ string::size_type colon, equal;
+ string remaining = prop->value();
+
+ while (remaining.length()) {
+
+ if ((equal = remaining.find_first_of ('=')) == string::npos || equal == remaining.length()) {
+ error << compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining)
+ << endmsg;
+ } else {
+ if (sscanf (remaining.substr (equal+1).c_str(), "%ld", &n) != 1) {
+ error << compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining)
+ << endmsg;
+ } else {
+ set_order_key (remaining.substr (0, equal), n);
+ }
+ }
+
+ colon = remaining.find_first_of (':');
+
+ if (colon != string::npos) {
+ remaining = remaining.substr (colon+1);
+ } else {
+ break;
+ }
+ }
+ }
+
+ nlist = node.children();
+
+ if (deferred_state) {
+ delete deferred_state;
+ }
+
+ deferred_state = new XMLNode("deferred state");
+
+ /* set parent class properties before anything else */
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+
+ child = *niter;
+
+ if (child->name() == IO::state_node_name) {
+
+ IO::set_state (*child);
+ break;
+ }
+ }
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+
+ child = *niter;
+
+ if (child->name() == "Send") {
+
+
+ if (!IO::ports_legal) {
+
+ deferred_state->add_child_copy (*child);
+
+ } else {
+ add_redirect_from_xml (*child);
+ }
+
+ } else if (child->name() == "Insert") {
+
+ if (!IO::ports_legal) {
+
+ deferred_state->add_child_copy (*child);
+
+ } else {
+
+ add_redirect_from_xml (*child);
+ }
+
+ } else if (child->name() == "Automation") {
+
+ XMLPropertyList plist;
+ XMLPropertyConstIterator piter;
+ XMLProperty *prop;
+
+ plist = child->properties();
+ for (piter = plist.begin(); piter != plist.end(); ++piter) {
+ prop = *piter;
+ if (prop->name() == "path") {
+ load_automation (prop->value());
+ }
+ }
+
+ } else if (child->name() == "ControlOuts") {
+
+ string coutname = _name;
+ coutname += _("[control]");
+
+ _control_outs = new IO (_session, coutname);
+ _control_outs->set_state (**(child->children().begin()));
+
+ } else if (child->name() == "Comment") {
+
+ /* XXX this is a terrible API design in libxml++ */
+
+ XMLNode *cmt = *(child->children().begin());
+ _comment = cmt->content();
+
+ } else if (child->name() == "extra") {
+ _extra_xml = new XMLNode (*child);
+ }
+ }
+
+ if ((prop = node.property ("mix-group")) != 0) {
+ RouteGroup* mix_group = _session.mix_group_by_name(prop->value());
+ if (mix_group == 0) {
+ error << compose(_("Route %1: unknown mix group \"%2 in saved state (ignored)"), _name, prop->value()) << endmsg;
+ } else {
+ set_mix_group(mix_group, this);
+ }
+ }
+
+ midi_kids = node.children ("MIDI");
+
+ for (niter = midi_kids.begin(); niter != midi_kids.end(); ++niter) {
+
+ XMLNodeList kids;
+ XMLNodeConstIterator miter;
+ XMLNode* child;
+
+ kids = (*niter)->children ();
+
+ for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+ child =* miter;
+
+ MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+ MIDI::byte additional = 0; /* ditto */
+ MIDI::channel_t chn = 0; /* ditto */
+
+ if (child->name() == "mute") {
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_mute_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI mute control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+ else if (child->name() == "solo") {
+
+ if (get_midi_node_info (child, ev, chn, additional)) {
+ _midi_solo_control.set_control_type (chn, ev, additional);
+ } else {
+ error << compose(_("MIDI mute control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+ }
+ }
+
+ }
+ }
+
+
+ return 0;
+}
+
+void
+Route::curve_reallocate ()
+{
+// _gain_automation_curve.finish_resize ();
+// _pan_automation_curve.finish_resize ();
+}
+
+void
+Route::silence (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ if (!_silent) {
+
+ // reset_peak_meters ();
+
+ IO::silence (nframes, offset);
+
+ if (_control_outs) {
+ _control_outs->silence (nframes, offset);
+ }
+
+ {
+ TentativeLockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ if (lm.locked()) {
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->silence (nframes, offset);
+ }
+
+ if (nframes == _session.get_block_size() && offset == 0) {
+ // _silent = true;
+ }
+ }
+ }
+
+ }
+}
+
+int
+Route::set_control_outs (const vector<string>& ports)
+{
+ LockMonitor lm (control_outs_lock, __LINE__, __FILE__);
+ vector<string>::const_iterator i;
+
+ if (_control_outs) {
+ delete _control_outs;
+ _control_outs = 0;
+ }
+
+ if (ports.empty()) {
+ return 0;
+ }
+
+ string coutname = _name;
+ coutname += _("[control]");
+
+ _control_outs = new IO (_session, coutname);
+
+ /* our control outs need as many outputs as we
+ have outputs. we track the changes in ::output_change_handler().
+ */
+
+ _control_outs->ensure_io (0, n_outputs(), true, this);
+
+ return 0;
+}
+
+void
+Route::set_edit_group (RouteGroup *eg, void *src)
+
+{
+ if (_edit_group) {
+ _edit_group->remove (this);
+ }
+
+ if ((_edit_group = eg)) {
+ _edit_group->add (this);
+ }
+
+ _session.set_dirty ();
+
+ edit_group_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_mix_group (RouteGroup *mg, void *src)
+
+{
+ if (_mix_group) {
+ _mix_group->remove (this);
+ }
+
+ if ((_mix_group = mg)) {
+ _mix_group->add (this);
+ }
+
+ _session.set_dirty ();
+
+ mix_group_changed (src); /* EMIT SIGNAL */
+}
+
+void
+Route::set_comment (string cmt, void *src)
+{
+ _comment = cmt;
+ comment_changed (src);
+ _session.set_dirty ();
+}
+
+bool
+Route::feeds (Route *o)
+{
+ uint32_t i, j;
+
+ IO& other = *o;
+ IO& self = *this;
+ uint32_t no = self.n_outputs();
+ uint32_t ni = other.n_inputs ();
+
+ for (i = 0; i < no; ++i) {
+ for (j = 0; j < ni; ++j) {
+ if (self.output(i)->connected_to (other.input(j)->name())) {
+ return true;
+ }
+ }
+ }
+
+ /* check Redirects which may also interconnect Routes */
+
+ for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); r++) {
+
+ no = (*r)->n_outputs();
+
+ for (i = 0; i < no; ++i) {
+ for (j = 0; j < ni; ++j) {
+ if ((*r)->output(i)->connected_to (other.input (j)->name())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ /* check for control room outputs which may also interconnect Routes */
+
+ if (_control_outs) {
+
+ no = _control_outs->n_outputs();
+
+ for (i = 0; i < no; ++i) {
+ for (j = 0; j < ni; ++j) {
+ if (_control_outs->output(i)->connected_to (other.input (j)->name())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+Route::set_mute_config (mute_type t, bool onoff, void *src)
+{
+ switch (t) {
+ case PRE_FADER:
+ _mute_affects_pre_fader = onoff;
+ pre_fader_changed(src); /* EMIT SIGNAL */
+ break;
+
+ case POST_FADER:
+ _mute_affects_post_fader = onoff;
+ post_fader_changed(src); /* EMIT SIGNAL */
+ break;
+
+ case CONTROL_OUTS:
+ _mute_affects_control_outs = onoff;
+ control_outs_changed(src); /* EMIT SIGNAL */
+ break;
+
+ case MAIN_OUTS:
+ _mute_affects_main_outs = onoff;
+ main_outs_changed(src); /* EMIT SIGNAL */
+ break;
+ }
+}
+
+bool
+Route::get_mute_config (mute_type t)
+{
+ bool onoff = false;
+
+ switch (t){
+ case PRE_FADER:
+ onoff = _mute_affects_pre_fader;
+ break;
+ case POST_FADER:
+ onoff = _mute_affects_post_fader;
+ break;
+ case CONTROL_OUTS:
+ onoff = _mute_affects_control_outs;
+ break;
+ case MAIN_OUTS:
+ onoff = _mute_affects_main_outs;
+ break;
+ }
+
+ return onoff;
+}
+
+void
+Route::set_active (bool yn)
+{
+ _active = yn;
+ active_changed(); /* EMIT SIGNAL */
+}
+
+void
+Route::transport_stopped (bool abort_ignored, bool did_locate, bool can_flush_redirects)
+{
+ jack_nframes_t now = _session.transport_frame();
+
+ if (!did_locate) {
+ automation_snapshot (now);
+ }
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+
+ if (Config->get_plugins_stop_with_transport() && can_flush_redirects) {
+ (*i)->deactivate ();
+ (*i)->activate ();
+ }
+
+ (*i)->transport_stopped (now);
+ }
+ }
+
+ IO::transport_stopped (now);
+
+ _roll_delay = _initial_delay;
+}
+
+UndoAction
+Route::get_memento() const
+{
+ void (Route::*pmf)(state_id_t) = &Route::set_state;
+ return sigc::bind (mem_fun (*(const_cast<Route *>(this)), pmf), _current_state_id);
+}
+
+void
+Route::set_state (state_id_t id)
+{
+ return;
+}
+
+void
+Route::input_change_handler (IOChange change, void *ignored)
+{
+ if (change & ConfigurationChanged) {
+ reset_plugin_counts (0);
+ }
+}
+
+void
+Route::output_change_handler (IOChange change, void *ignored)
+{
+ if (change & ConfigurationChanged) {
+ if (_control_outs) {
+ _control_outs->ensure_io (0, n_outputs(), true, this);
+ }
+
+ reset_plugin_counts (0);
+ }
+}
+
+uint32_t
+Route::pans_required () const
+{
+ if (n_outputs() < 2) {
+ return 0;
+ }
+
+ return max (n_inputs (), redirect_max_outs);
+}
+
+int
+Route::no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool session_state_changing, bool can_record, bool rec_monitors_input)
+{
+ if (n_outputs() == 0) {
+ return 0;
+ }
+
+ if (session_state_changing || !_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ apply_gain_automation = false;
+
+ if (n_inputs()) {
+ passthru (start_frame, end_frame, nframes, offset, 0, false);
+ } else {
+ silence (nframes, offset);
+ }
+
+ return 0;
+}
+
+jack_nframes_t
+Route::check_initial_delay (jack_nframes_t nframes, jack_nframes_t& offset, jack_nframes_t& transport_frame)
+{
+ if (_roll_delay > nframes) {
+
+ _roll_delay -= nframes;
+ silence (nframes, offset);
+ /* transport frame is not legal for caller to use */
+ return 0;
+
+ } else if (_roll_delay > 0) {
+
+ nframes -= _roll_delay;
+
+ silence (_roll_delay, offset);
+
+ offset += _roll_delay;
+ transport_frame += _roll_delay;
+
+ _roll_delay = 0;
+ }
+
+ return nframes;
+}
+
+int
+Route::roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, int declick,
+ bool can_record, bool rec_monitors_input)
+{
+ automation_snapshot (_session.transport_frame());
+
+ if ((n_outputs() == 0 && _redirects.empty()) || n_inputs() == 0 || !_active) {
+ silence (nframes, offset);
+ return 0;
+ }
+
+ jack_nframes_t unused;
+
+ if ((nframes = check_initial_delay (nframes, offset, unused)) == 0) {
+ return 0;
+ }
+
+ _silent = false;
+ apply_gain_automation = false;
+
+ {
+ TentativeLockMonitor am (automation_lock, __LINE__, __FILE__);
+
+ if (am.locked()) {
+
+ jack_nframes_t start_frame = end_frame - nframes;
+
+ if (gain_automation_playback()) {
+ apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes);
+ }
+ }
+ }
+
+ passthru (start_frame, end_frame, nframes, offset, declick, false);
+
+ return 0;
+}
+
+int
+Route::silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset,
+ bool can_record, bool rec_monitors_input)
+{
+ silence (nframes, offset);
+ return 0;
+}
+
+void
+Route::toggle_monitor_input ()
+{
+ for (vector<Port*>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+ (*i)->request_monitor_input(!(*i)->monitoring_input());
+ }
+}
+
+bool
+Route::has_external_redirects () const
+{
+ const PortInsert* pi;
+
+ for (RedirectList::const_iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if ((pi = dynamic_cast<const PortInsert*>(*i)) != 0) {
+
+ uint32_t no = pi->n_outputs();
+
+ for (uint32_t n = 0; n < no; ++n) {
+
+ string port_name = pi->output(n)->name();
+ string client_name = port_name.substr (0, port_name.find(':'));
+
+ /* only say "yes" if the redirect is actually in use */
+
+ if (client_name != "ardour" && pi->active()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+Route::reset_midi_control (MIDI::Port* port, bool on)
+{
+ MIDI::channel_t chn;
+ MIDI::eventType ev;
+ MIDI::byte extra;
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->reset_midi_control (port, on);
+ }
+
+ IO::reset_midi_control (port, on);
+
+ _midi_solo_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_solo_control.midi_rebind (port, chn);
+
+ _midi_mute_control.get_control_info (chn, ev, extra);
+ if (!on) {
+ chn = -1;
+ }
+ _midi_mute_control.midi_rebind (port, chn);
+}
+
+void
+Route::send_all_midi_feedback ()
+{
+ if (_session.get_midi_feedback()) {
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->send_all_midi_feedback ();
+ }
+ }
+
+ IO::send_all_midi_feedback();
+
+ _midi_solo_control.send_feedback (_soloed);
+ _midi_mute_control.send_feedback (_muted);
+ }
+}
+
+MIDI::byte*
+Route::write_midi_feedback (MIDI::byte* buf, int32_t& bufsize)
+{
+ buf = _midi_solo_control.write_feedback (buf, bufsize, _soloed);
+ buf = _midi_mute_control.write_feedback (buf, bufsize, _muted);
+
+ {
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ buf = (*i)->write_midi_feedback (buf, bufsize);
+ }
+ }
+
+ return IO::write_midi_feedback (buf, bufsize);
+}
+
+void
+Route::flush_redirects ()
+{
+ /* XXX shouldn't really try to take this lock, since
+ this is called from the RT audio thread.
+ */
+
+ LockMonitor lm (redirect_lock, __LINE__, __FILE__);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->deactivate ();
+ (*i)->activate ();
+ }
+}
+
+void
+Route::set_meter_point (MeterPoint p, void *src)
+{
+ if (_meter_point != p) {
+ _meter_point = p;
+ meter_change (src); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+}
+
+jack_nframes_t
+Route::update_total_latency ()
+{
+ _own_latency = 0;
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ if ((*i)->active ()) {
+ _own_latency += (*i)->latency ();
+ }
+ }
+
+ set_port_latency (_own_latency);
+
+ /* this (virtual) function is used for pure Routes,
+ not derived classes like AudioTrack. this means
+ that the data processed here comes from an input
+ port, not prerecorded material, and therefore we
+ have to take into account any input latency.
+ */
+
+ _own_latency += input_latency ();
+
+ return _own_latency;
+}
+
+void
+Route::set_latency_delay (jack_nframes_t longest_session_latency)
+{
+ _initial_delay = longest_session_latency - _own_latency;
+
+ if (_session.transport_stopped()) {
+ _roll_delay = _initial_delay;
+ }
+}
+
+void
+Route::automation_snapshot (jack_nframes_t now)
+{
+ IO::automation_snapshot (now);
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->automation_snapshot (now);
+ }
+}
+
+Route::MIDIToggleControl::MIDIToggleControl (Route& s, ToggleType tp, MIDI::Port* port)
+ : MIDI::Controllable (port, true), route (s), type(tp), setting(false)
+{
+ last_written = false; /* XXX need a good out-of-bound-value */
+}
+
+void
+Route::MIDIToggleControl::set_value (float val)
+{
+ MIDI::eventType et;
+ MIDI::channel_t chn;
+ MIDI::byte additional;
+
+ get_control_info (chn, et, additional);
+
+ setting = true;
+
+#ifdef HOLD_TOGGLE_VALUES
+ if (et == MIDI::off || et == MIDI::on) {
+
+ /* literal toggle */
+
+ switch (type) {
+ case MuteControl:
+ route.set_mute (!route.muted(), this);
+ break;
+ case SoloControl:
+ route.set_solo (!route.soloed(), this);
+ break;
+ default:
+ break;
+ }
+
+ } else {
+#endif
+
+ /* map full control range to a boolean */
+
+ bool bval = ((val >= 0.5f) ? true: false);
+
+ switch (type) {
+ case MuteControl:
+ route.set_mute (bval, this);
+ break;
+ case SoloControl:
+ route.set_solo (bval, this);
+ break;
+ default:
+ break;
+ }
+
+#ifdef HOLD_TOGGLE_VALUES
+ }
+#endif
+
+ setting = false;
+}
+
+void
+Route::MIDIToggleControl::send_feedback (bool value)
+{
+
+ if (!setting && get_midi_feedback()) {
+ MIDI::byte val = (MIDI::byte) (value ? 127: 0);
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+ MIDI::EventTwoBytes data;
+
+ if (get_control_info (ch, ev, additional)) {
+ data.controller_number = additional;
+ data.value = val;
+
+ route._session.send_midi_message (get_port(), ev, ch, data);
+ }
+ }
+
+}
+
+MIDI::byte*
+Route::MIDIToggleControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force)
+{
+ if (get_midi_feedback() && bufsize > 2) {
+ MIDI::channel_t ch = 0;
+ MIDI::eventType ev = MIDI::none;
+ MIDI::byte additional = 0;
+
+ if (get_control_info (ch, ev, additional)) {
+ if (val != last_written || force) {
+ *buf++ = (0xF0 & ev) | (0xF & ch);
+ *buf++ = additional; /* controller number */
+ *buf++ = (MIDI::byte) (val ? 127 : 0);
+ bufsize -= 3;
+ last_written = val;
+ }
+ }
+ }
+
+ return buf;
+}
+
+void
+Route::set_block_size (jack_nframes_t nframes)
+{
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ (*i)->set_block_size (nframes);
+ }
+}
+
+void
+Route::redirect_active_proxy (Redirect* ignored, void* ignored_src)
+{
+ _session.update_latency_compensation (false, false);
+}
+
+void
+Route::protect_automation ()
+{
+ switch (gain_automation_state()) {
+ case Write:
+ case Touch:
+ set_gain_automation_state (Off);
+ break;
+ default:
+ break;
+ }
+
+ switch (panner().automation_state ()) {
+ case Write:
+ case Touch:
+ panner().set_automation_state (Off);
+ break;
+ default:
+ break;
+ }
+
+ for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+ PluginInsert* pi;
+ if ((pi = dynamic_cast<PluginInsert*> (*i)) != 0) {
+ pi->protect_automation ();
+ }
+ }
+}
+
+void
+Route::set_pending_declick (int declick)
+{
+ if (_declickable) {
+ /* this call is not allowed to turn off a pending declick unless "force" is true */
+ if (declick) {
+ _pending_declick = declick;
+ }
+ // cerr << _name << ": after setting to " << declick << " pending declick = " << _pending_declick << endl;
+ } else {
+ _pending_declick = 0;
+ }
+
+}
diff --git a/libs/ardour/route_group.cc b/libs/ardour/route_group.cc
new file mode 100644
index 0000000000..225224e684
--- /dev/null
+++ b/libs/ardour/route_group.cc
@@ -0,0 +1,184 @@
+/*
+ Copyright (C) 2000-2002 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.
+
+ $Id$
+*/
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <algorithm>
+
+#include <sigc++/bind.h>
+
+#include <pbd/error.h>
+
+#include <ardour/route_group.h>
+#include <ardour/diskstream.h>
+#include <ardour/configuration.h>
+
+using namespace ARDOUR;
+using namespace sigc;
+
+int
+RouteGroup::add (Route *r)
+{
+ routes.push_back (r);
+ r->GoingAway.connect (sigc::bind (mem_fun (*this, &RouteGroup::remove_when_going_away), r));
+ changed (); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+RouteGroup::remove_when_going_away (Route *r)
+{
+ remove (r);
+}
+
+int
+RouteGroup::remove (Route *r)
+{
+ list<Route *>::iterator i;
+
+ if ((i = find (routes.begin(), routes.end(), r)) != routes.end()) {
+ routes.erase (i);
+ changed (); /* EMIT SIGNAL */
+ return 0;
+ }
+ return -1;
+}
+
+
+gain_t
+RouteGroup::get_min_factor(gain_t factor)
+{
+ gain_t g;
+
+ for (list<Route *>::iterator i = routes.begin(); i != routes.end(); i++) {
+ g = (*i)->gain();
+
+ if ( (g+g*factor) >= 0.0f)
+ continue;
+
+ if ( g <= 0.0000003f )
+ return 0.0f;
+
+ factor = 0.0000003f/g - 1.0f;
+ }
+ return factor;
+}
+
+gain_t
+RouteGroup::get_max_factor(gain_t factor)
+{
+ gain_t g;
+
+ for (list<Route *>::iterator i = routes.begin(); i != routes.end(); i++) {
+ g = (*i)->gain();
+
+ // if the current factor woulnd't raise this route above maximum
+ if ( (g+g*factor) <= 1.99526231f)
+ continue;
+
+ // if route gain is already at peak, return 0.0f factor
+ if (g>=1.99526231f)
+ return 0.0f;
+
+ // factor is calculated so that it would raise current route to max
+ factor = 1.99526231f/g - 1.0f;
+ }
+
+ return factor;
+}
+
+XMLNode&
+RouteGroup::get_state (void)
+{
+ char buf[32];
+ XMLNode *node = new XMLNode ("RouteGroup");
+ node->add_property ("name", _name);
+ snprintf (buf, sizeof (buf), "%" PRIu32, (uint32_t) _flags);
+ node->add_property ("flags", buf);
+ return *node;
+}
+
+int
+RouteGroup::set_state (const XMLNode& node)
+{
+ const XMLProperty *prop;
+
+ if ((prop = node.property ("name")) != 0) {
+ _name = prop->value();
+ }
+
+ if ((prop = node.property ("flags")) != 0) {
+ _flags = atoi (prop->value().c_str());
+ }
+
+ return 0;
+}
+
+void
+RouteGroup::set_active (bool yn, void *src)
+
+{
+ if (is_active() == yn) {
+ return;
+ }
+ if (yn) {
+ _flags |= Active;
+ } else {
+ _flags &= ~Active;
+ }
+ FlagsChanged (src); /* EMIT SIGNAL */
+}
+
+void
+RouteGroup::set_relative (bool yn, void *src)
+
+{
+ if (is_relative() == yn) {
+ return;
+ }
+ if (yn) {
+ _flags |= Relative;
+ } else {
+ _flags &= ~Relative;
+ }
+ FlagsChanged (src); /* EMIT SIGNAL */
+}
+
+void
+RouteGroup::set_hidden (bool yn, void *src)
+
+{
+ if (is_hidden() == yn) {
+ return;
+ }
+ if (yn) {
+ _flags |= Hidden;
+ if (Config->does_hiding_groups_deactivates_groups()) {
+ _flags &= ~Active;
+ }
+ } else {
+ _flags &= ~Hidden;
+ if (Config->does_hiding_groups_deactivates_groups()) {
+ _flags |= Active;
+ }
+ }
+ FlagsChanged (src); /* EMIT SIGNAL */
+}
diff --git a/libs/ardour/send.cc b/libs/ardour/send.cc
new file mode 100644
index 0000000000..f0afea5be5
--- /dev/null
+++ b/libs/ardour/send.cc
@@ -0,0 +1,162 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+
+#include <pbd/xml++.h>
+
+#include <ardour/send.h>
+#include <ardour/session.h>
+#include <ardour/port.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+Send::Send (Session& s, Placement p)
+ : Redirect (s, s.next_send_name(), p)
+{
+ _metering = false;
+ expected_inputs = 0;
+ save_state (_("initial state"));
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+Send::Send (Session& s, const XMLNode& node)
+ : Redirect (s, "send", PreFader)
+{
+ _metering = false;
+ expected_inputs = 0;
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ save_state (_("initial state"));
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+Send::Send (const Send& other)
+ : Redirect (other._session, other._session.next_send_name(), other.placement())
+{
+ _metering = false;
+ expected_inputs = 0;
+ save_state (_("initial state"));
+ RedirectCreated (this); /* EMIT SIGNAL */
+}
+
+Send::~Send ()
+{
+ GoingAway (this);
+}
+
+XMLNode&
+Send::get_state(void)
+{
+ return state (true);
+}
+
+XMLNode&
+Send::state(bool full)
+{
+ XMLNode *node = new XMLNode("Send");
+ node->add_child_nocopy (Redirect::state(full));
+ return *node;
+}
+
+int
+Send::set_state(const XMLNode& node)
+{
+ XMLNodeList nlist = node.children();
+ XMLNodeIterator niter;
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == Redirect::state_node_name) {
+ Redirect::set_state (**niter);
+ break;
+ }
+ }
+
+ if (niter == nlist.end()) {
+ error << _("XML node describing a send is missing a Redirect node") << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+Send::run (vector<Sample *>& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ if (active()) {
+
+ IO::deliver_output (bufs, nbufs, nframes, offset);
+
+ if (_metering) {
+ uint32_t n;
+ uint32_t no = n_outputs();
+
+ if (_gain == 0) {
+
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = 0;
+ }
+
+ } else {
+
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = Session::compute_peak (output(n)->get_buffer(nframes+offset) + offset, nframes, _peak_power[n]) * _gain;
+ }
+ }
+ }
+
+ } else {
+ silence (nframes, offset);
+
+ if (_metering) {
+ uint32_t n;
+ uint32_t no = n_outputs();
+
+ for (n = 0; n < no; ++n) {
+ _peak_power[n] = 0;
+ }
+ }
+ }
+}
+
+void
+Send::set_metering (bool yn)
+{
+ _metering = yn;
+
+ if (!_metering) {
+ /* XXX possible thread hazard here */
+ reset_peak_meters ();
+ }
+}
+
+void
+Send::expect_inputs (uint32_t expected)
+{
+ if (expected != expected_inputs) {
+ expected_inputs = expected;
+ reset_panner ();
+ }
+}
diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc
new file mode 100644
index 0000000000..04be0feabb
--- /dev/null
+++ b/libs/ardour/session.cc
@@ -0,0 +1,3491 @@
+/*
+ Copyright (C) 1999-2004 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <sstream>
+#include <fstream>
+#include <cstdio> /* sprintf(3) ... grrr */
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
+#include <limits.h>
+
+#include <sigc++/bind.h>
+#include <sigc++/retype.h>
+
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pathscanner.h>
+#include <pbd/stl_delete.h>
+#include <pbd/basename.h>
+#include <pbd/dirname.h>
+
+#include <ardour/audioengine.h>
+#include <ardour/configuration.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/utils.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/audioregion.h>
+#include <ardour/source.h>
+#include <ardour/filesource.h>
+#include <ardour/sndfilesource.h>
+#include <ardour/auditioner.h>
+#include <ardour/recent_sessions.h>
+#include <ardour/redirect.h>
+#include <ardour/send.h>
+#include <ardour/insert.h>
+#include <ardour/connection.h>
+#include <ardour/slave.h>
+#include <ardour/tempo.h>
+#include <ardour/audio_track.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/named_selection.h>
+#include <ardour/crossfade.h>
+#include <ardour/playlist.h>
+#include <ardour/click.h>
+#include <ardour/timestamps.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+const char* Session::_template_suffix = X_(".template");
+const char* Session::_statefile_suffix = X_(".ardour");
+const char* Session::_pending_suffix = X_(".pending");
+const char* Session::sound_dir_name = X_("sounds");
+const char* Session::peak_dir_name = X_("peaks");
+const char* Session::dead_sound_dir_name = X_("dead_sounds");
+
+Session::compute_peak_t Session::compute_peak = 0;
+Session::apply_gain_to_buffer_t Session::apply_gain_to_buffer = 0;
+Session::mix_buffers_with_gain_t Session::mix_buffers_with_gain = 0;
+Session::mix_buffers_no_gain_t Session::mix_buffers_no_gain = 0;
+
+sigc::signal<int> Session::AskAboutPendingState;
+
+int
+Session::find_session (string str, string& path, string& snapshot, bool& isnew)
+{
+ struct stat statbuf;
+ char buf[PATH_MAX+1];
+
+ isnew = false;
+
+ if (!realpath (str.c_str(), buf) && (errno != ENOENT && errno != ENOTDIR)) {
+ error << compose (_("Could not resolve path: %1 (%2)"), buf, strerror(errno)) << endmsg;
+ return -1;
+ }
+
+ str = buf;
+
+ /* check to see if it exists, and what it is */
+
+ if (stat (str.c_str(), &statbuf)) {
+ if (errno == ENOENT) {
+ isnew = true;
+ } else {
+ error << compose (_("cannot check session path %1 (%2)"), str, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+ }
+
+ if (!isnew) {
+
+ /* it exists, so it must either be the name
+ of the directory, or the name of the statefile
+ within it.
+ */
+
+ if (S_ISDIR (statbuf.st_mode)) {
+
+ string::size_type slash = str.find_last_of ('/');
+
+ if (slash == string::npos) {
+
+ /* a subdirectory of cwd, so statefile should be ... */
+
+ string tmp;
+ tmp = str;
+ tmp += '/';
+ tmp += str;
+ tmp += _statefile_suffix;
+
+ /* is it there ? */
+
+ if (stat (tmp.c_str(), &statbuf)) {
+ error << compose (_("cannot check statefile %1 (%2)"), tmp, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ path = str;
+ snapshot = str;
+
+ } else {
+
+ /* some directory someplace in the filesystem.
+ the snapshot name is the directory name
+ itself.
+ */
+
+ path = str;
+ snapshot = str.substr (slash+1);
+
+ }
+
+ } else if (S_ISREG (statbuf.st_mode)) {
+
+ string::size_type slash = str.find_last_of ('/');
+ string::size_type suffix;
+
+ /* remove the suffix */
+
+ if (slash != string::npos) {
+ snapshot = str.substr (slash+1);
+ } else {
+ snapshot = str;
+ }
+
+ suffix = snapshot.find (_statefile_suffix);
+
+ if (suffix == string::npos) {
+ error << compose (_("%1 is not an Ardour snapshot file"), str) << endmsg;
+ return -1;
+ }
+
+ /* remove suffix */
+
+ snapshot = snapshot.substr (0, suffix);
+
+ if (slash == string::npos) {
+
+ /* we must be in the directory where the
+ statefile lives. get it using cwd().
+ */
+
+ char cwd[PATH_MAX+1];
+
+ if (getcwd (cwd, sizeof (cwd)) == 0) {
+ error << compose (_("cannot determine current working directory (%1)"), strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ path = cwd;
+
+ } else {
+
+ /* full path to the statefile */
+
+ path = str.substr (0, slash);
+ }
+
+ } else {
+
+ /* what type of file is it? */
+ error << compose (_("unknown file type for session %1"), str) << endmsg;
+ return -1;
+ }
+
+ } else {
+
+ /* its the name of a new directory. get the name
+ as "dirname" does.
+ */
+
+ string::size_type slash = str.find_last_of ('/');
+
+ if (slash == string::npos) {
+
+ /* no slash, just use the name, but clean it up */
+
+ path = legalize_for_path (str);
+ snapshot = path;
+
+ } else {
+
+ path = str;
+ snapshot = str.substr (slash+1);
+ }
+ }
+
+ return 0;
+}
+
+Session::Session (AudioEngine &eng,
+ string fullpath,
+ string snapshot_name,
+ string* mix_template)
+
+ : _engine (eng),
+ _mmc_port (default_mmc_port),
+ _mtc_port (default_mtc_port),
+ _midi_port (default_midi_port),
+ pending_events (2048),
+ midi_requests (128), // the size of this should match the midi request pool size
+ main_outs (0)
+{
+ bool new_session;
+
+ cerr << "Loading session " << fullpath << " using snapshot " << snapshot_name << endl;
+
+ n_physical_outputs = _engine.n_physical_outputs();
+ n_physical_inputs = _engine.n_physical_inputs();
+
+ first_stage_init (fullpath, snapshot_name);
+
+ if (create (new_session, mix_template, _engine.frame_rate() * 60 * 5)) {
+ throw failed_constructor ();
+ }
+
+ if (second_stage_init (new_session)) {
+ throw failed_constructor ();
+ }
+
+ store_recent_sessions(_name, _path);
+
+ bool was_dirty = dirty();
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+
+ if (was_dirty) {
+ DirtyChanged (); /* EMIT SIGNAL */
+ }
+}
+
+Session::Session (AudioEngine &eng,
+ string fullpath,
+ string snapshot_name,
+ AutoConnectOption input_ac,
+ AutoConnectOption output_ac,
+ uint32_t control_out_channels,
+ uint32_t master_out_channels,
+ uint32_t requested_physical_in,
+ uint32_t requested_physical_out,
+ jack_nframes_t initial_length)
+
+ : _engine (eng),
+ _mmc_port (default_mmc_port),
+ _mtc_port (default_mtc_port),
+ _midi_port (default_midi_port),
+ pending_events (2048),
+ midi_requests (16),
+ main_outs (0)
+
+{
+ bool new_session;
+
+ cerr << "Loading session " << fullpath << " using snapshot " << snapshot_name << endl;
+
+ n_physical_outputs = max (requested_physical_out, _engine.n_physical_outputs());
+ n_physical_inputs = max (requested_physical_in, _engine.n_physical_inputs());
+
+ first_stage_init (fullpath, snapshot_name);
+
+ if (create (new_session, 0, initial_length)) {
+ throw failed_constructor ();
+ }
+
+ if (control_out_channels) {
+ Route* r;
+ r = new Route (*this, _("monitor"), -1, control_out_channels, -1, control_out_channels, Route::ControlOut);
+ add_route (r);
+ _control_out = r;
+ }
+
+ if (master_out_channels) {
+ Route* r;
+ r = new Route (*this, _("master"), -1, master_out_channels, -1, master_out_channels, Route::MasterOut);
+ add_route (r);
+ _master_out = r;
+ } else {
+ /* prohibit auto-connect to master, because there isn't one */
+ output_ac = AutoConnectOption (output_ac & ~AutoConnectMaster);
+ }
+
+ input_auto_connect = input_ac;
+ output_auto_connect = output_ac;
+
+ if (second_stage_init (new_session)) {
+ throw failed_constructor ();
+ }
+
+ store_recent_sessions(_name, _path);
+
+ bool was_dirty = dirty ();
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+
+ if (was_dirty) {
+ DirtyChanged (); /* EMIT SIGNAL */
+ }
+}
+
+Session::~Session ()
+{
+ /* if we got to here, leaving pending capture state around
+ is a mistake.
+ */
+
+ remove_pending_capture_state ();
+
+ _state_of_the_state = StateOfTheState (CannotSave|Deletion);
+ _engine.remove_session ();
+
+ going_away (); /* EMIT SIGNAL */
+
+ terminate_butler_thread ();
+ terminate_midi_thread ();
+ terminate_feedback ();
+
+ if (click_data && click_data != default_click) {
+ delete [] click_data;
+ }
+
+ if (click_emphasis_data && click_emphasis_data != default_click_emphasis) {
+ delete [] click_emphasis_data;
+ }
+
+ clear_clicks ();
+
+ if (_click_io) {
+ delete _click_io;
+ }
+
+ if (auditioner) {
+ delete auditioner;
+ }
+
+ for (vector<Sample*>::iterator i = _passthru_buffers.begin(); i != _passthru_buffers.end(); ++i) {
+ free(*i);
+ }
+
+ for (vector<Sample*>::iterator i = _silent_buffers.begin(); i != _silent_buffers.end(); ++i) {
+ free(*i);
+ }
+
+#undef TRACK_DESTRUCTION
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete named selections\n";
+#endif /* TRACK_DESTRUCTION */
+ for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ) {
+ NamedSelectionList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete playlists\n";
+#endif /* TRACK_DESTRUCTION */
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ) {
+ PlaylistList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete audio regions\n";
+#endif /* TRACK_DESTRUCTION */
+ for (AudioRegionList::iterator i = audio_regions.begin(); i != audio_regions.end(); ) {
+ AudioRegionList::iterator tmp;
+
+ tmp =i;
+ ++tmp;
+
+ delete (*i).second;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete routes\n";
+#endif /* TRACK_DESTRUCTION */
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ) {
+ RouteList::iterator tmp;
+ tmp = i;
+ ++tmp;
+ delete *i;
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete diskstreams\n";
+#endif /* TRACK_DESTRUCTION */
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ) {
+ DiskStreamList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete sources\n";
+#endif /* TRACK_DESTRUCTION */
+ for (SourceList::iterator i = sources.begin(); i != sources.end(); ) {
+ SourceList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete (*i).second;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete mix groups\n";
+#endif /* TRACK_DESTRUCTION */
+ for (list<RouteGroup *>::iterator i = mix_groups.begin(); i != mix_groups.end(); ) {
+ list<RouteGroup*>::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete edit groups\n";
+#endif /* TRACK_DESTRUCTION */
+ for (list<RouteGroup *>::iterator i = edit_groups.begin(); i != edit_groups.end(); ) {
+ list<RouteGroup*>::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+
+ i = tmp;
+ }
+
+#ifdef TRACK_DESTRUCTION
+ cerr << "delete connections\n";
+#endif /* TRACK_DESTRUCTION */
+ for (ConnectionList::iterator i = _connections.begin(); i != _connections.end(); ) {
+ ConnectionList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ delete *i;
+
+ i = tmp;
+ }
+
+ if (butler_mixdown_buffer) {
+ delete [] butler_mixdown_buffer;
+ }
+
+ if (butler_gain_buffer) {
+ delete [] butler_gain_buffer;
+ }
+
+ Crossfade::set_buffer_size (0);
+
+ if (mmc) {
+ delete mmc;
+ }
+
+ if (state_tree) {
+ delete state_tree;
+ }
+}
+
+void
+Session::set_worst_io_latencies (bool take_lock)
+{
+ _worst_output_latency = 0;
+ _worst_input_latency = 0;
+
+ if (!_engine.connected()) {
+ return;
+ }
+
+ if (take_lock) {
+ route_lock.lock ();
+ }
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ _worst_output_latency = max (_worst_output_latency, (*i)->output_latency());
+ _worst_input_latency = max (_worst_input_latency, (*i)->input_latency());
+ }
+
+ if (take_lock) {
+ route_lock.unlock ();
+ }
+}
+
+void
+Session::when_engine_running ()
+{
+ string first_physical_output;
+
+ /* we don't want to run execute this again */
+
+ first_time_running.disconnect ();
+
+ /* every time we reconnect, recompute worst case output latencies */
+
+ _engine.Running.connect (sigc::bind (mem_fun (*this, &Session::set_worst_io_latencies), true));
+
+ if (synced_to_jack()) {
+ _engine.transport_stop ();
+ }
+
+ if (Config->get_jack_time_master()) {
+ _engine.transport_locate (_transport_frame);
+ }
+
+ _clicking = false;
+
+ try {
+ XMLNode* child;
+
+ _click_io = new ClickIO (*this, "click", 0, 0, -1, -1);
+
+ if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) {
+
+ /* existing state for Click */
+
+ if (_click_io->set_state (*child->children().front()) == 0) {
+
+ _clicking = click_requested;
+
+ } else {
+
+ error << _("could not setup Click I/O") << endmsg;
+ _clicking = false;
+ }
+
+ } else {
+
+ /* default state for Click */
+
+ first_physical_output = _engine.get_nth_physical_output (0);
+
+ if (first_physical_output.length()) {
+ if (_click_io->add_output_port (first_physical_output, this)) {
+ // relax, even though its an error
+ } else {
+ _clicking = click_requested;
+ }
+ }
+ }
+ }
+
+ catch (failed_constructor& err) {
+ error << _("cannot setup Click I/O") << endmsg;
+ }
+
+ set_worst_io_latencies (true);
+
+ if (_clicking) {
+ ControlChanged (Clicking); /* EMIT SIGNAL */
+ }
+
+ if (auditioner == 0) {
+
+ /* we delay creating the auditioner till now because
+ it makes its own connections to ports named
+ in the ARDOUR_RC config file. the engine has
+ to be running for this to work.
+ */
+
+ try {
+ auditioner = new Auditioner (*this);
+ }
+
+ catch (failed_constructor& err) {
+ warning << _("cannot create Auditioner: no auditioning of regions possible") << endmsg;
+ }
+ }
+
+ /* Create a set of Connection objects that map
+ to the physical outputs currently available
+ */
+
+ /* ONE: MONO */
+
+ for (uint32_t np = 0; np < n_physical_outputs; ++np) {
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1);
+
+ Connection* c = new OutputConnection (buf, true);
+
+ c->add_port ();
+ c->add_connection (0, _engine.get_nth_physical_output (np));
+
+ add_connection (c);
+ }
+
+ for (uint32_t np = 0; np < n_physical_inputs; ++np) {
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1);
+
+ Connection* c = new InputConnection (buf, true);
+
+ c->add_port ();
+ c->add_connection (0, _engine.get_nth_physical_input (np));
+
+ add_connection (c);
+ }
+
+ /* TWO: STEREO */
+
+ for (uint32_t np = 0; np < n_physical_outputs; np +=2) {
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("out %" PRIu32 "+%" PRIu32), np+1, np+2);
+
+ Connection* c = new OutputConnection (buf, true);
+
+ c->add_port ();
+ c->add_port ();
+ c->add_connection (0, _engine.get_nth_physical_output (np));
+ c->add_connection (1, _engine.get_nth_physical_output (np+1));
+
+ add_connection (c);
+ }
+
+ for (uint32_t np = 0; np < n_physical_inputs; np +=2) {
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("in %" PRIu32 "+%" PRIu32), np+1, np+2);
+
+ Connection* c = new InputConnection (buf, true);
+
+ c->add_port ();
+ c->add_port ();
+ c->add_connection (0, _engine.get_nth_physical_input (np));
+ c->add_connection (1, _engine.get_nth_physical_input (np+1));
+
+ add_connection (c);
+ }
+
+ /* THREE MASTER */
+
+ if (_master_out) {
+
+ /* create master/control ports */
+
+ if (_master_out) {
+ uint32_t n;
+
+ /* force the master to ignore any later call to this */
+
+ if (_master_out->pending_state_node) {
+ _master_out->ports_became_legal();
+ }
+
+ /* no panner resets till we are through */
+
+ _master_out->defer_pan_reset ();
+
+ while ((int) _master_out->n_inputs() < _master_out->input_maximum()) {
+ if (_master_out->add_input_port ("", this)) {
+ error << _("cannot setup master inputs")
+ << endmsg;
+ break;
+ }
+ }
+ n = 0;
+ while ((int) _master_out->n_outputs() < _master_out->output_maximum()) {
+ if (_master_out->add_output_port (_engine.get_nth_physical_output (n), this)) {
+ error << _("cannot setup master outputs")
+ << endmsg;
+ break;
+ }
+ n++;
+ }
+
+ _master_out->allow_pan_reset ();
+
+ }
+
+ Connection* c = new OutputConnection (_("Master Out"), true);
+
+ for (uint32_t n = 0; n < _master_out->n_inputs (); ++n) {
+ c->add_port ();
+ c->add_connection ((int) n, _master_out->input(n)->name());
+ }
+ add_connection (c);
+ }
+
+ hookup_io ();
+
+ /* catch up on send+insert cnts */
+
+ insert_cnt = 0;
+
+ for (slist<PortInsert*>::iterator i = _port_inserts.begin(); i != _port_inserts.end(); ++i) {
+ uint32_t id;
+
+ if (sscanf ((*i)->name().c_str(), "%*s %u", &id) == 1) {
+ if (id > insert_cnt) {
+ insert_cnt = id;
+ }
+ }
+ }
+
+ send_cnt = 0;
+
+ for (slist<Send*>::iterator i = _sends.begin(); i != _sends.end(); ++i) {
+ uint32_t id;
+
+ if (sscanf ((*i)->name().c_str(), "%*s %u", &id) == 1) {
+ if (id > send_cnt) {
+ send_cnt = id;
+ }
+ }
+ }
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
+
+ /* hook us up to the engine */
+
+ _engine.set_session (this);
+
+ _state_of_the_state = Clean;
+
+ DirtyChanged (); /* EMIT SIGNAL */
+}
+
+void
+Session::hookup_io ()
+{
+ /* stop graph reordering notifications from
+ causing resorts, etc.
+ */
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state | InitialConnecting);
+
+ /* Tell all IO objects to create their ports */
+
+ IO::enable_ports ();
+
+ if (_control_out) {
+ uint32_t n;
+
+ while ((int) _control_out->n_inputs() < _control_out->input_maximum()) {
+ if (_control_out->add_input_port ("", this)) {
+ error << _("cannot setup control inputs")
+ << endmsg;
+ break;
+ }
+ }
+ n = 0;
+ while ((int) _control_out->n_outputs() < _control_out->output_maximum()) {
+ if (_control_out->add_output_port (_engine.get_nth_physical_output (n), this)) {
+ error << _("cannot set up master outputs")
+ << endmsg;
+ break;
+ }
+ n++;
+ }
+ }
+
+ /* Tell all IO objects to connect themselves together */
+
+ IO::enable_connecting ();
+
+ /* Now reset all panners */
+
+ IO::reset_panners ();
+
+ /* Anyone who cares about input state, wake up and do something */
+
+ IOConnectionsComplete (); /* EMIT SIGNAL */
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~InitialConnecting);
+
+ /* now handle the whole enchilada as if it was one
+ graph reorder event.
+ */
+
+ graph_reordered ();
+
+ /* update mixer solo state */
+
+ catch_up_on_solo();
+}
+
+void
+Session::playlist_length_changed (Playlist* pl)
+{
+ /* we can't just increase end_location->end() if pl->get_maximum_extent()
+ if larger. if the playlist used to be the longest playlist,
+ and its now shorter, we have to decrease end_location->end(). hence,
+ we have to iterate over all diskstreams and check the
+ playlists currently in use.
+ */
+ find_current_end ();
+}
+
+void
+Session::diskstream_playlist_changed (DiskStream* dstream)
+{
+ Playlist *playlist;
+
+ if ((playlist = dstream->playlist()) != 0) {
+ playlist->LengthChanged.connect (sigc::bind (mem_fun (this, &Session::playlist_length_changed), playlist));
+ }
+
+ /* see comment in playlist_length_changed () */
+ find_current_end ();
+}
+
+bool
+Session::record_enabling_legal () const
+{
+ /* this used to be in here, but survey says.... we don't need to restrict it */
+ // if (record_status() == Recording) {
+ // return false;
+ // }
+
+ if (all_safe) {
+ return false;
+ }
+ return true;
+}
+
+void
+Session::set_auto_play (bool yn)
+{
+ if (auto_play != yn) {
+ auto_play = yn;
+ set_dirty ();
+ ControlChanged (AutoPlay);
+ }
+}
+
+void
+Session::set_auto_return (bool yn)
+{
+ if (auto_return != yn) {
+ auto_return = yn;
+ set_dirty ();
+ ControlChanged (AutoReturn);
+ }
+}
+
+void
+Session::set_crossfades_active (bool yn)
+{
+ if (crossfades_active != yn) {
+ crossfades_active = yn;
+ set_dirty ();
+ ControlChanged (CrossFadesActive);
+ }
+}
+
+void
+Session::set_recording_plugins (bool yn)
+{
+ if (recording_plugins != yn) {
+ recording_plugins = yn;
+ set_dirty ();
+ ControlChanged (RecordingPlugins);
+ }
+}
+
+void
+Session::set_auto_input (bool yn)
+{
+ if (auto_input != yn) {
+ auto_input = yn;
+ set_dirty();
+ ControlChanged (AutoInput);
+ }
+}
+
+void
+Session::set_input_auto_connect (bool yn)
+{
+ if (yn) {
+ input_auto_connect = AutoConnectOption (input_auto_connect|AutoConnectPhysical);
+ } else {
+ input_auto_connect = AutoConnectOption (input_auto_connect|~AutoConnectPhysical);
+ }
+ set_dirty ();
+}
+
+void
+Session::set_output_auto_connect (AutoConnectOption aco)
+{
+ output_auto_connect = aco;
+ set_dirty ();
+}
+
+void
+Session::auto_punch_start_changed (Location* location)
+{
+ replace_event (Event::PunchIn, location->start());
+
+ if (get_record_enabled() && get_punch_in()) {
+ /* capture start has been changed, so save new pending state */
+ save_state ("", true);
+ }
+}
+
+void
+Session::auto_punch_end_changed (Location* location)
+{
+ jack_nframes_t when_to_stop = location->end();
+ // when_to_stop += _worst_output_latency + _worst_input_latency;
+ replace_event (Event::PunchOut, when_to_stop);
+}
+
+void
+Session::auto_punch_changed (Location* location)
+{
+ jack_nframes_t when_to_stop = location->end();
+
+ replace_event (Event::PunchIn, location->start());
+ //when_to_stop += _worst_output_latency + _worst_input_latency;
+ replace_event (Event::PunchOut, when_to_stop);
+}
+
+void
+Session::auto_loop_changed (Location* location)
+{
+ replace_event (Event::AutoLoop, location->end(), location->start());
+
+ if (transport_rolling() && get_auto_loop()) {
+
+ //if (_transport_frame < location->start() || _transport_frame > location->end()) {
+
+ if (_transport_frame > location->end()) {
+ // relocate to beginning of loop
+ clear_events (Event::LocateRoll);
+
+ request_locate (location->start(), true);
+
+ }
+ else if (seamless_loop && !loop_changing) {
+
+ // schedule a locate-roll to refill the diskstreams at the
+ // previous loop end
+ loop_changing = true;
+
+ if (location->end() > last_loopend) {
+ clear_events (Event::LocateRoll);
+ Event *ev = new Event (Event::LocateRoll, Event::Add, last_loopend, last_loopend, 0, true);
+ queue_event (ev);
+ }
+
+ }
+ }
+
+ last_loopend = location->end();
+
+}
+
+void
+Session::set_auto_punch_location (Location* location)
+{
+ Location* existing;
+
+ if ((existing = _locations.auto_punch_location()) != 0 && existing != location) {
+ auto_punch_start_changed_connection.disconnect();
+ auto_punch_end_changed_connection.disconnect();
+ auto_punch_changed_connection.disconnect();
+ existing->set_auto_punch (false, this);
+ remove_event (existing->start(), Event::PunchIn);
+ clear_events (Event::PunchOut);
+ auto_punch_location_changed (0);
+ }
+
+ set_dirty();
+
+ if (location == 0) {
+ return;
+ }
+
+ if (location->end() <= location->start()) {
+ error << _("Session: you can't use that location for auto punch (start <= end)") << endmsg;
+ return;
+ }
+
+ auto_punch_start_changed_connection.disconnect();
+ auto_punch_end_changed_connection.disconnect();
+ auto_punch_changed_connection.disconnect();
+
+ auto_punch_start_changed_connection = location->start_changed.connect (mem_fun (this, &Session::auto_punch_start_changed));
+ auto_punch_end_changed_connection = location->end_changed.connect (mem_fun (this, &Session::auto_punch_end_changed));
+ auto_punch_changed_connection = location->changed.connect (mem_fun (this, &Session::auto_punch_changed));
+
+ location->set_auto_punch (true, this);
+ auto_punch_location_changed (location);
+}
+
+void
+Session::set_punch_in (bool yn)
+{
+ if (punch_in == yn) {
+ return;
+ }
+
+ Location* location;
+
+ if ((location = _locations.auto_punch_location()) != 0) {
+ if ((punch_in = yn) == true) {
+ replace_event (Event::PunchIn, location->start());
+ } else {
+ remove_event (location->start(), Event::PunchIn);
+ }
+ }
+
+ set_dirty();
+ ControlChanged (PunchIn); /* EMIT SIGNAL */
+}
+
+void
+Session::set_punch_out (bool yn)
+{
+ if (punch_out == yn) {
+ return;
+ }
+
+ Location* location;
+
+ if ((location = _locations.auto_punch_location()) != 0) {
+ if ((punch_out = yn) == true) {
+ replace_event (Event::PunchOut, location->end());
+ } else {
+ clear_events (Event::PunchOut);
+ }
+ }
+
+ set_dirty();
+ ControlChanged (PunchOut); /* EMIT SIGNAL */
+}
+
+void
+Session::set_auto_loop_location (Location* location)
+{
+ Location* existing;
+
+ if ((existing = _locations.auto_loop_location()) != 0 && existing != location) {
+ auto_loop_start_changed_connection.disconnect();
+ auto_loop_end_changed_connection.disconnect();
+ auto_loop_changed_connection.disconnect();
+ existing->set_auto_loop (false, this);
+ remove_event (existing->end(), Event::AutoLoop);
+ auto_loop_location_changed (0);
+ }
+
+ set_dirty();
+
+ if (location == 0) {
+ return;
+ }
+
+ if (location->end() <= location->start()) {
+ error << _("Session: you can't use a mark for auto loop") << endmsg;
+ return;
+ }
+
+ last_loopend = location->end();
+
+ auto_loop_start_changed_connection.disconnect();
+ auto_loop_end_changed_connection.disconnect();
+ auto_loop_changed_connection.disconnect();
+
+ auto_loop_start_changed_connection = location->start_changed.connect (mem_fun (this, &Session::auto_loop_changed));
+ auto_loop_end_changed_connection = location->end_changed.connect (mem_fun (this, &Session::auto_loop_changed));
+ auto_loop_changed_connection = location->changed.connect (mem_fun (this, &Session::auto_loop_changed));
+
+ location->set_auto_loop (true, this);
+ auto_loop_location_changed (location);
+}
+
+void
+Session::locations_added (Location* ignored)
+{
+ set_dirty ();
+}
+
+void
+Session::locations_changed ()
+{
+ _locations.apply (*this, &Session::handle_locations_changed);
+}
+
+void
+Session::handle_locations_changed (Locations::LocationList& locations)
+{
+ Locations::LocationList::iterator i;
+ Location* location;
+ bool set_loop = false;
+ bool set_punch = false;
+
+ for (i = locations.begin(); i != locations.end(); ++i) {
+
+ location =* i;
+
+ if (location->is_auto_punch()) {
+ set_auto_punch_location (location);
+ set_punch = true;
+ }
+ if (location->is_auto_loop()) {
+ set_auto_loop_location (location);
+ set_loop = true;
+ }
+
+ }
+
+ if (!set_loop) {
+ set_auto_loop_location (0);
+ }
+ if (!set_punch) {
+ set_auto_punch_location (0);
+ }
+
+ set_dirty();
+}
+
+void
+Session::enable_record ()
+{
+ /* XXX really atomic compare+swap here */
+ if (atomic_read (&_record_status) != Recording) {
+ atomic_set (&_record_status, Recording);
+ _last_record_location = _transport_frame;
+ send_mmc_in_another_thread (MIDI::MachineControl::cmdRecordStrobe);
+ RecordEnabled ();
+ }
+}
+
+void
+Session::disable_record ()
+{
+ if (atomic_read (&_record_status) != Disabled) {
+ atomic_set (&_record_status, Disabled);
+ send_mmc_in_another_thread (MIDI::MachineControl::cmdRecordExit);
+ RecordDisabled ();
+ remove_pending_capture_state ();
+ }
+}
+
+void
+Session::step_back_from_record ()
+{
+ atomic_set (&_record_status, Enabled);
+}
+
+void
+Session::maybe_enable_record ()
+{
+ atomic_set (&_record_status, Enabled);
+
+ save_state ("", true);
+
+ if (_transport_speed) {
+ if (!punch_in) {
+ enable_record ();
+ }
+ } else {
+ send_mmc_in_another_thread (MIDI::MachineControl::cmdRecordPause);
+ RecordEnabled (); /* EMIT SIGNAL */
+ }
+
+ set_dirty();
+}
+
+jack_nframes_t
+Session::audible_frame () const
+{
+ jack_nframes_t ret;
+ jack_nframes_t offset;
+ jack_nframes_t tf;
+
+ /* the first of these two possible settings for "offset"
+ mean that the audible frame is stationary until
+ audio emerges from the latency compensation
+ "pseudo-pipeline".
+
+ the second means that the audible frame is stationary
+ until audio would emerge from a physical port
+ in the absence of any plugin latency compensation
+ */
+
+ offset = _worst_output_latency;
+
+ if (offset > current_block_size) {
+ offset -= current_block_size;
+ } else {
+ /* XXX is this correct? if we have no external
+ physical connections and everything is internal
+ then surely this is zero? still, how
+ likely is that anyway?
+ */
+ offset = current_block_size;
+ }
+
+ if (synced_to_jack()) {
+ tf = _engine.transport_frame();
+ } else {
+ tf = _transport_frame;
+ }
+
+ if (_transport_speed == 0) {
+ return tf;
+ }
+
+ if (tf < offset) {
+ return 0;
+ }
+
+ ret = tf;
+
+ if (!non_realtime_work_pending()) {
+
+ /* MOVING */
+
+ /* take latency into account */
+
+ ret -= offset;
+ }
+
+ return ret;
+}
+
+void
+Session::set_frame_rate (jack_nframes_t frames_per_second)
+{
+ /** \fn void Session::set_frame_size(jack_nframes_t)
+ the AudioEngine object that calls this guarantees
+ that it will not be called while we are also in
+ ::process(). Its fine to do things that block
+ here.
+ */
+
+ _current_frame_rate = frames_per_second;
+ _frames_per_smpte_frame = (double) _current_frame_rate / (double) smpte_frames_per_second;
+
+ Route::set_automation_interval ((jack_nframes_t) ceil ((double) frames_per_second * 0.25));
+
+ set_dirty();
+
+ /* XXX need to reset/reinstantiate all LADSPA plugins */
+}
+
+void
+Session::set_block_size (jack_nframes_t nframes)
+{
+ /** \fn void Session::set_block_size(jack_nframes_t)
+ the AudioEngine object that calls this guarantees
+ that it will not be called while we are also in
+ ::process(). Its also fine to do things that block
+ here.
+ */
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ current_block_size = nframes;
+
+ for (vector<Sample*>::iterator i = _passthru_buffers.begin(); i != _passthru_buffers.end(); ++i) {
+ free(*i);
+
+ Sample *buf;
+#ifdef NO_POSIX_MEMALIGN
+ buf = (Sample *) malloc(current_block_size * sizeof(Sample));
+#else
+ posix_memalign((void **)&buf,16,current_block_size * 4);
+#endif
+ *i = buf;
+
+ memset (*i, 0, sizeof (Sample) * current_block_size);
+ }
+
+ for (vector<Sample*>::iterator i = _silent_buffers.begin(); i != _silent_buffers.end(); ++i) {
+ free(*i);
+
+ Sample *buf;
+#ifdef NO_POSIX_MEMALIGN
+ buf = (Sample *) malloc(current_block_size * sizeof(Sample));
+#else
+ posix_memalign((void **)&buf,16,current_block_size * 4);
+#endif
+ *i = buf;
+
+ memset (*i, 0, sizeof (Sample) * current_block_size);
+ }
+
+ if (_gain_automation_buffer) {
+ delete [] _gain_automation_buffer;
+ }
+ _gain_automation_buffer = new gain_t[nframes];
+
+ allocate_pan_automation_buffers (nframes, _npan_buffers, true);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->set_block_size (nframes);
+ }
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->set_block_size (nframes);
+ }
+
+ set_worst_io_latencies (false);
+ }
+}
+
+void
+Session::set_default_fade (float steepness, float fade_msecs)
+{
+#if 0
+ jack_nframes_t fade_frames;
+
+ /* Don't allow fade of less 1 frame */
+
+ if (fade_msecs < (1000.0 * (1.0/_current_frame_rate))) {
+
+ fade_msecs = 0;
+ fade_frames = 0;
+
+ } else {
+
+ fade_frames = (jack_nframes_t) floor (fade_msecs * _current_frame_rate * 0.001);
+
+ }
+
+ default_fade_msecs = fade_msecs;
+ default_fade_steepness = steepness;
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ AudioRegion::set_default_fade (steepness, fade_frames);
+ }
+
+ set_dirty();
+
+ /* XXX have to do this at some point */
+ /* foreach region using default fade, reset, then
+ refill_all_diskstream_buffers ();
+ */
+#endif
+}
+
+struct RouteSorter {
+ bool operator() (Route* r1, Route* r2) {
+ if (r1->fed_by.find (r2) != r1->fed_by.end()) {
+ return false;
+ } else if (r2->fed_by.find (r1) != r2->fed_by.end()) {
+ return true;
+ } else {
+ if (r1->fed_by.empty()) {
+ if (r2->fed_by.empty()) {
+ /* no ardour-based connections inbound to either route. just use signal order */
+ return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
+ } else {
+ /* r2 has connections, r1 does not; run r1 early */
+ return true;
+ }
+ } else {
+ return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
+ }
+ }
+ }
+};
+
+static void
+trace_terminal (Route* r1, Route* rbase)
+{
+ Route* r2;
+
+ if ((r1->fed_by.find (rbase) != r1->fed_by.end()) && (rbase->fed_by.find (r1) != rbase->fed_by.end())) {
+ info << compose(_("feedback loop setup between %1 and %2"), r1->name(), rbase->name()) << endmsg;
+ return;
+ }
+
+ /* make a copy of the existing list of routes that feed r1 */
+
+ set<Route *> existing = r1->fed_by;
+
+ /* for each route that feeds r1, recurse, marking it as feeding
+ rbase as well.
+ */
+
+ for (set<Route *>::iterator i = existing.begin(); i != existing.end(); ++i) {
+ r2 =* i;
+
+ /* r2 is a route that feeds r1 which somehow feeds base. mark
+ base as being fed by r2
+ */
+
+ rbase->fed_by.insert (r2);
+
+ if (r2 != rbase) {
+
+ /* 2nd level feedback loop detection. if r1 feeds or is fed by r2,
+ stop here.
+ */
+
+ if ((r1->fed_by.find (r2) != r1->fed_by.end()) && (r2->fed_by.find (r1) != r2->fed_by.end())) {
+ continue;
+ }
+
+ /* now recurse, so that we can mark base as being fed by
+ all routes that feed r2
+ */
+
+ trace_terminal (r2, rbase);
+ }
+
+ }
+}
+
+void
+Session::resort_routes (void* src)
+{
+ /* don't do anything here with signals emitted
+ by Routes while we are being destroyed.
+ */
+
+ if (_state_of_the_state & Deletion) {
+ return;
+ }
+
+ /* Caller MUST hold the route_lock */
+
+ RouteList::iterator i, j;
+
+ for (i = routes.begin(); i != routes.end(); ++i) {
+
+ (*i)->fed_by.clear ();
+
+ for (j = routes.begin(); j != routes.end(); ++j) {
+
+ /* although routes can feed themselves, it will
+ cause an endless recursive descent if we
+ detect it. so don't bother checking for
+ self-feeding.
+ */
+
+ if (*j == *i) {
+ continue;
+ }
+
+ if ((*j)->feeds (*i)) {
+ (*i)->fed_by.insert (*j);
+ }
+ }
+ }
+
+ for (i = routes.begin(); i != routes.end(); ++i) {
+ trace_terminal (*i, *i);
+ }
+
+ RouteSorter cmp;
+ routes.sort (cmp);
+
+#if 0
+ cerr << "finished route resort\n";
+
+ for (i = routes.begin(); i != routes.end(); ++i) {
+ cerr << " " << (*i)->name() << " signal order = " << (*i)->order_key ("signal") << endl;
+ }
+ cerr << endl;
+#endif
+
+}
+
+AudioTrack*
+Session::new_audio_track (int input_channels, int output_channels)
+{
+ AudioTrack *track;
+ char track_name[32];
+ uint32_t n = 0;
+ uint32_t channels_used = 0;
+ string port;
+ uint32_t nphysical_in;
+ uint32_t nphysical_out;
+
+ /* count existing audio tracks */
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (dynamic_cast<AudioTrack*>(*i) != 0) {
+ if (!(*i)->hidden()) {
+ n++;
+ channels_used += (*i)->n_inputs();
+ }
+ }
+ }
+ }
+
+ /* check for duplicate route names, since we might have pre-existing
+ routes with this name (e.g. create Audio1, Audio2, delete Audio1,
+ save, close,restart,add new route - first named route is now
+ Audio2)
+ */
+
+ do {
+ snprintf (track_name, sizeof(track_name), "Audio %" PRIu32, n+1);
+ if (route_by_name (track_name) == 0) {
+ break;
+ }
+ n++;
+
+ } while (n < ULONG_MAX);
+
+ if (input_auto_connect & AutoConnectPhysical) {
+ nphysical_in = n_physical_inputs;
+ } else {
+ nphysical_in = 0;
+ }
+
+ if (output_auto_connect & AutoConnectPhysical) {
+ nphysical_out = n_physical_outputs;
+ } else {
+ nphysical_out = 0;
+ }
+
+ try {
+ track = new AudioTrack (*this, track_name);
+
+ if (track->ensure_io (input_channels, output_channels, false, this)) {
+ error << compose (_("cannot configure %1 in/%2 out configuration for new audio track"),
+ input_channels, output_channels)
+ << endmsg;
+ }
+
+ if (nphysical_in) {
+ for (uint32_t x = 0; x < track->n_inputs() && x < nphysical_in; ++x) {
+
+ port = "";
+
+ if (input_auto_connect & AutoConnectPhysical) {
+ port = _engine.get_nth_physical_input ((channels_used+x)%nphysical_in);
+ }
+
+ if (port.length() && track->connect_input (track->input (x), port, this)) {
+ break;
+ }
+ }
+ }
+
+ for (uint32_t x = 0; x < track->n_outputs(); ++x) {
+
+ port = "";
+
+ if (nphysical_out && (output_auto_connect & AutoConnectPhysical)) {
+ port = _engine.get_nth_physical_output ((channels_used+x)%nphysical_out);
+ } else if (output_auto_connect & AutoConnectMaster) {
+ if (_master_out) {
+ port = _master_out->input (x%_master_out->n_inputs())->name();
+ }
+ }
+
+ if (port.length() && track->connect_output (track->output (x), port, this)) {
+ break;
+ }
+ }
+
+ if (_control_out) {
+ vector<string> cports;
+ uint32_t ni = _control_out->n_inputs();
+
+ for (n = 0; n < ni; ++n) {
+ cports.push_back (_control_out->input(n)->name());
+ }
+
+ track->set_control_outs (cports);
+ }
+
+ track->diskstream_changed.connect (mem_fun (this, &Session::resort_routes));
+
+ add_route (track);
+ }
+
+ catch (failed_constructor &err) {
+ error << _("Session: could not create new audio track.") << endmsg;
+ return 0;
+ }
+
+ return track;
+}
+
+Route*
+Session::new_audio_route (int input_channels, int output_channels)
+{
+ Route *bus;
+ char bus_name[32];
+ uint32_t n = 0;
+ string port;
+
+ /* count existing audio busses */
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (dynamic_cast<AudioTrack*>(*i) == 0) {
+ if (!(*i)->hidden()) {
+ n++;
+ }
+ }
+ }
+ }
+
+ do {
+ snprintf (bus_name, sizeof(bus_name), "Bus %" PRIu32, n+1);
+ if (route_by_name (bus_name) == 0) {
+ break;
+ }
+ n++;
+
+ } while (n < ULONG_MAX);
+
+ try {
+ bus = new Route (*this, bus_name, -1, -1, -1, -1);
+
+ if (bus->ensure_io (input_channels, output_channels, false, this)) {
+ error << compose (_("cannot configure %1 in/%2 out configuration for new audio track"),
+ input_channels, output_channels)
+ << endmsg;
+ }
+
+ for (uint32_t x = 0; x < bus->n_inputs(); ++x) {
+
+ port = "";
+
+ if (input_auto_connect & AutoConnectPhysical) {
+ port = _engine.get_nth_physical_input ((n+x)%n_physical_inputs);
+ }
+
+ if (port.length() && bus->connect_input (bus->input (x), port, this)) {
+ break;
+ }
+ }
+
+ for (uint32_t x = 0; x < bus->n_outputs(); ++x) {
+
+ port = "";
+
+ if (output_auto_connect & AutoConnectPhysical) {
+ port = _engine.get_nth_physical_input ((n+x)%n_physical_outputs);
+ } else if (output_auto_connect & AutoConnectMaster) {
+ if (_master_out) {
+ port = _master_out->input (x%_master_out->n_inputs())->name();
+ }
+ }
+
+ if (port.length() && bus->connect_output (bus->output (x), port, this)) {
+ break;
+ }
+ }
+
+ if (_control_out) {
+ vector<string> cports;
+ uint32_t ni = _control_out->n_inputs();
+
+ for (uint32_t n = 0; n < ni; ++n) {
+ cports.push_back (_control_out->input(n)->name());
+ }
+ bus->set_control_outs (cports);
+ }
+
+ add_route (bus);
+ }
+
+ catch (failed_constructor &err) {
+ error << _("Session: could not create new route.") << endmsg;
+ return 0;
+ }
+
+ return bus;
+}
+
+void
+Session::add_route (Route* route)
+{
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ routes.push_front (route);
+ resort_routes(0);
+ }
+
+ route->solo_changed.connect (sigc::bind (mem_fun (*this, &Session::route_solo_changed), route));
+ route->mute_changed.connect (mem_fun (*this, &Session::route_mute_changed));
+ route->output_changed.connect (mem_fun (*this, &Session::set_worst_io_latencies_x));
+ route->redirects_changed.connect (mem_fun (*this, &Session::update_latency_compensation_proxy));
+
+ if (route->master()) {
+ _master_out = route;
+ }
+
+ if (route->control()) {
+ _control_out = route;
+ }
+
+ set_dirty();
+ save_state (_current_snapshot_name);
+
+ RouteAdded (route); /* EMIT SIGNAL */
+}
+
+void
+Session::add_diskstream (DiskStream* dstream)
+{
+ {
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ diskstreams.push_back (dstream);
+ }
+
+ /* take a reference to the diskstream, preventing it from
+ ever being deleted until the session itself goes away,
+ or chooses to remove it for its own purposes.
+ */
+
+ dstream->ref();
+ dstream->set_block_size (current_block_size);
+
+ dstream->PlaylistChanged.connect (sigc::bind (mem_fun (*this, &Session::diskstream_playlist_changed), dstream));
+ /* this will connect to future changes, and check the current length */
+ diskstream_playlist_changed (dstream);
+
+ dstream->prepare ();
+
+ set_dirty();
+ save_state (_current_snapshot_name);
+
+ DiskStreamAdded (dstream); /* EMIT SIGNAL */
+}
+
+void
+Session::remove_route (Route& route)
+{
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ routes.remove (&route);
+
+ /* deleting the master out seems like a dumb
+ idea, but its more of a UI policy issue
+ than our concern.
+ */
+
+ if (&route == _master_out) {
+ _master_out = 0;
+ }
+
+ if (&route == _control_out) {
+ _control_out = 0;
+
+ /* cancel control outs for all routes */
+
+ vector<string> empty;
+
+ for (RouteList::iterator r = routes.begin(); r != routes.end(); ++r) {
+ (*r)->set_control_outs (empty);
+ }
+ }
+
+ update_route_solo_state ();
+ }
+
+ {
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+
+ AudioTrack* at;
+
+ if ((at = dynamic_cast<AudioTrack*>(&route)) != 0) {
+ diskstreams.remove (&at->disk_stream());
+ at->disk_stream().unref ();
+ }
+
+ find_current_end ();
+ }
+
+ update_latency_compensation (false, false);
+ set_dirty();
+
+ /* XXX should we disconnect from the Route's signals ? */
+
+ save_state (_current_snapshot_name);
+
+ delete &route;
+}
+
+void
+Session::route_mute_changed (void* src)
+{
+ set_dirty ();
+}
+
+void
+Session::route_solo_changed (void* src, Route* route)
+{
+ if (solo_update_disabled) {
+ // We know already
+ return;
+ }
+
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ bool is_track;
+
+ is_track = (dynamic_cast<AudioTrack*>(route) != 0);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ /* soloing a track mutes all other tracks, soloing a bus mutes all other busses */
+
+ if (is_track) {
+
+ /* don't mess with busses */
+
+ if (dynamic_cast<AudioTrack*>(*i) == 0) {
+ continue;
+ }
+
+ } else {
+
+ /* don't mess with tracks */
+
+ if (dynamic_cast<AudioTrack*>(*i) != 0) {
+ continue;
+ }
+ }
+
+ if ((*i) != route &&
+ ((*i)->mix_group () == 0 ||
+ (*i)->mix_group () != route->mix_group () ||
+ !route->mix_group ()->is_active())) {
+
+ if ((*i)->soloed()) {
+
+ /* if its already soloed, and solo latching is enabled,
+ then leave it as it is.
+ */
+
+ if (_solo_latched) {
+ continue;
+ }
+ }
+
+ /* do it */
+
+ solo_update_disabled = true;
+ (*i)->set_solo (false, src);
+ solo_update_disabled = false;
+ }
+ }
+
+ bool something_soloed = false;
+ bool same_thing_soloed = false;
+ bool signal = false;
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if ((*i)->soloed()) {
+ something_soloed = true;
+ if (dynamic_cast<AudioTrack*>(*i)) {
+ if (is_track) {
+ same_thing_soloed = true;
+ break;
+ }
+ } else {
+ if (!is_track) {
+ same_thing_soloed = true;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (something_soloed != currently_soloing) {
+ signal = true;
+ currently_soloing = something_soloed;
+ }
+
+ modify_solo_mute (is_track, same_thing_soloed);
+
+ if (signal) {
+ SoloActive (currently_soloing);
+ }
+
+ set_dirty();
+}
+
+void
+Session::set_solo_latched (bool yn)
+{
+ _solo_latched = yn;
+ set_dirty ();
+}
+
+void
+Session::update_route_solo_state ()
+{
+ bool mute = false;
+ bool is_track = false;
+ bool signal = false;
+
+ /* caller must hold RouteLock */
+
+ /* this is where we actually implement solo by changing
+ the solo mute setting of each track.
+ */
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if ((*i)->soloed()) {
+ mute = true;
+ if (dynamic_cast<AudioTrack*>(*i)) {
+ is_track = true;
+ }
+ break;
+ }
+ }
+
+ if (mute != currently_soloing) {
+ signal = true;
+ currently_soloing = mute;
+ }
+
+ if (!is_track && !mute) {
+
+ /* nothing is soloed */
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->set_solo_mute (false);
+ }
+
+ if (signal) {
+ SoloActive (false);
+ }
+
+ return;
+ }
+
+ modify_solo_mute (is_track, mute);
+
+ if (signal) {
+ SoloActive (currently_soloing);
+ }
+}
+
+void
+Session::modify_solo_mute (bool is_track, bool mute)
+{
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ if (is_track) {
+
+ /* only alter track solo mute */
+
+ if (dynamic_cast<AudioTrack*>(*i)) {
+ if ((*i)->soloed()) {
+ (*i)->set_solo_mute (!mute);
+ } else {
+ (*i)->set_solo_mute (mute);
+ }
+ }
+
+ } else {
+
+ /* only alter bus solo mute */
+
+ if (!dynamic_cast<AudioTrack*>(*i)) {
+
+ if ((*i)->soloed()) {
+
+ (*i)->set_solo_mute (false);
+
+ } else {
+
+ /* don't mute master or control outs
+ in response to another bus solo
+ */
+
+ if ((*i) != _master_out &&
+ (*i) != _control_out) {
+ (*i)->set_solo_mute (mute);
+ }
+ }
+ }
+
+ }
+ }
+}
+
+
+void
+Session::catch_up_on_solo ()
+{
+ /* this is called after set_state() to catch the full solo
+ state, which can't be correctly determined on a per-route
+ basis, but needs the global overview that only the session
+ has.
+ */
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ update_route_solo_state();
+}
+
+Route *
+Session::route_by_name (string name)
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::find_current_end ()
+{
+ jack_nframes_t max = 0;
+ jack_nframes_t me;
+
+ if (_state_of_the_state & Loading) {
+ return;
+ }
+
+ /* Don't take the diskstream lock. Caller must have other ways to
+ ensure atomicity.
+ */
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ Playlist* pl = (*i)->playlist();
+ if ((me = pl->get_maximum_extent()) > max) {
+ max = me;
+ }
+ }
+
+ if (max > end_location->end()) {
+ end_location->set_end (max);
+ set_dirty();
+ DurationChanged(); /* EMIT SIGNAL */
+ }
+}
+
+DiskStream *
+Session::diskstream_by_name (string name)
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+
+ return 0;
+}
+
+DiskStream *
+Session::diskstream_by_id (id_t id)
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)->id() == id) {
+ return *i;
+ }
+ }
+
+ return 0;
+}
+
+/* AudioRegion management */
+
+string
+Session::new_region_name (string old)
+{
+ string::size_type last_period;
+ uint32_t number;
+ string::size_type len = old.length() + 64;
+ char buf[len];
+
+ if ((last_period = old.find_last_of ('.')) == string::npos) {
+
+ /* no period present - add one explicitly */
+
+ old += '.';
+ last_period = old.length() - 1;
+ number = 0;
+
+ } else {
+
+ number = atoi (old.substr (last_period+1).c_str());
+
+ }
+
+ while (number < ULONG_MAX) {
+
+ AudioRegionList::const_iterator i;
+ string sbuf;
+
+ number++;
+
+ snprintf (buf, len, "%s%" PRIu32, old.substr (0, last_period + 1).c_str(), number);
+ sbuf = buf;
+
+ for (i = audio_regions.begin(); i != audio_regions.end(); ++i) {
+ if ((*i).second->name() == sbuf) {
+ break;
+ }
+ }
+
+ if (i == audio_regions.end()) {
+ break;
+ }
+ }
+
+ if (number != ULONG_MAX) {
+ return buf;
+ }
+
+ error << compose (_("cannot create new name for region \"%1\""), old) << endmsg;
+ return old;
+}
+
+int
+Session::region_name (string& result, string base, bool newlevel) const
+{
+ char buf[16];
+ string subbase;
+
+ if (base == "") {
+
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+
+ snprintf (buf, sizeof (buf), "%d", (int)audio_regions.size() + 1);
+
+
+ result = "region.";
+ result += buf;
+
+ } else {
+
+ /* XXX this is going to be slow. optimize me later */
+
+ if (newlevel) {
+ subbase = base;
+ } else {
+ string::size_type pos;
+
+ if ((pos = base.find_last_of ('-')) == string::npos) {
+ pos = base.find_last_of ('.');
+ }
+
+ /* pos may be npos, but then we just use entire base */
+
+ subbase = base.substr (0, pos);
+ }
+
+ bool name_taken = true;
+
+ {
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+
+ for (int n = 1; n < 5000; ++n) {
+
+ result = subbase;
+ snprintf (buf, sizeof (buf), ".%d", n);
+ result += buf;
+
+ name_taken = false;
+
+ for (AudioRegionList::const_iterator i = audio_regions.begin(); i != audio_regions.end(); ++i) {
+ if ((*i).second->name() == result) {
+ name_taken = true;
+ break;
+ }
+ }
+
+ if (!name_taken) {
+ break;
+ }
+ }
+ }
+
+ if (name_taken) {
+ fatal << compose(_("too many regions with names like %1"), base) << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::add_region (Region* region)
+{
+ AudioRegion* ar = 0;
+ AudioRegion* oar = 0;
+ bool added = false;
+
+ {
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+
+ if ((ar = dynamic_cast<AudioRegion*> (region)) != 0) {
+
+ AudioRegionList::iterator x;
+
+ for (x = audio_regions.begin(); x != audio_regions.end(); ++x) {
+
+ oar = dynamic_cast<AudioRegion*> (x->second);
+
+ if (ar->region_list_equivalent (*oar)) {
+ break;
+ }
+ }
+
+ if (x == audio_regions.end()) {
+
+ pair<AudioRegionList::key_type, AudioRegionList::mapped_type> entry;
+
+ entry.first = region->id();
+ entry.second = ar;
+
+ pair<AudioRegionList::iterator,bool> x = audio_regions.insert (entry);
+
+ if (!x.second) {
+ return;
+ }
+
+ added = true;
+ }
+
+ } else {
+
+ fatal << _("programming error: ")
+ << X_("unknown region type passed to Session::add_region()")
+ << endmsg;
+ /*NOTREACHED*/
+
+ }
+ }
+
+ /* mark dirty because something has changed even if we didn't
+ add the region to the region list.
+ */
+
+ set_dirty();
+
+ if (added) {
+ region->GoingAway.connect (mem_fun (*this, &Session::remove_region));
+ region->StateChanged.connect (sigc::bind (mem_fun (*this, &Session::region_changed), region));
+ AudioRegionAdded (ar); /* EMIT SIGNAL */
+ }
+}
+
+void
+Session::region_changed (Change what_changed, Region* region)
+{
+ if (what_changed & Region::HiddenChanged) {
+ /* relay hidden changes */
+ RegionHiddenChange (region);
+ }
+}
+
+void
+Session::region_renamed (Region* region)
+{
+ add_region (region);
+}
+
+void
+Session::remove_region (Region* region)
+{
+ AudioRegionList::iterator i;
+ AudioRegion* ar = 0;
+ bool removed = false;
+
+ {
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+
+ if ((ar = dynamic_cast<AudioRegion*> (region)) != 0) {
+ if ((i = audio_regions.find (region->id())) != audio_regions.end()) {
+ audio_regions.erase (i);
+ removed = true;
+ }
+ } else {
+ fatal << _("programming error: ")
+ << X_("unknown region type passed to Session::remove_region()")
+ << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+
+ /* mark dirty because something has changed even if we didn't
+ remove the region from the region list.
+ */
+
+ set_dirty();
+
+ if (removed) {
+ AudioRegionRemoved(ar); /* EMIT SIGNAL */
+ }
+}
+
+AudioRegion*
+Session::find_whole_file_parent (AudioRegion& child)
+{
+ AudioRegionList::iterator i;
+ AudioRegion* region;
+ LockMonitor lm (region_lock, __LINE__, __FILE__);
+
+ for (i = audio_regions.begin(); i != audio_regions.end(); ++i) {
+
+ region = (*i).second;
+
+ if (region->whole_file()) {
+
+ if (child.source_equivalent (*region)) {
+ return region;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::find_equivalent_playlist_regions (AudioRegion& region, vector<AudioRegion*>& result)
+{
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+
+ AudioPlaylist* pl;
+
+ if ((pl = dynamic_cast<AudioPlaylist*>(*i)) == 0) {
+ continue;
+ }
+
+ pl->get_region_list_equivalent_regions (region, result);
+ }
+}
+
+int
+Session::destroy_region (Region* region)
+{
+ AudioRegion* aregion;
+
+ if ((aregion = dynamic_cast<AudioRegion*> (region)) == 0) {
+ return 0;
+ }
+
+ if (aregion->playlist()) {
+ aregion->playlist()->destroy_region (region);
+ }
+
+ vector<Source*> srcs;
+
+ for (uint32_t n = 0; n < aregion->n_channels(); ++n) {
+ srcs.push_back (&aregion->source (n));
+ }
+
+ for (vector<Source*>::iterator i = srcs.begin(); i != srcs.end(); ++i) {
+
+ if ((*i)->use_cnt() == 0) {
+ (*i)->mark_for_remove ();
+ delete *i;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::destroy_regions (list<Region*> regions)
+{
+ for (list<Region*>::iterator i = regions.begin(); i != regions.end(); ++i) {
+ destroy_region (*i);
+ }
+ return 0;
+}
+
+int
+Session::remove_last_capture ()
+{
+ list<Region*> r;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ list<Region*>& l = (*i)->last_capture_regions();
+
+ if (!l.empty()) {
+ r.insert (r.end(), l.begin(), l.end());
+ l.clear ();
+ }
+ }
+
+ destroy_regions (r);
+ return 0;
+}
+
+int
+Session::remove_region_from_region_list (Region& r)
+{
+ remove_region (&r);
+ return 0;
+}
+
+/* Source Management */
+
+void
+Session::add_source (Source* source)
+{
+ pair<SourceList::key_type, SourceList::mapped_type> entry;
+
+ {
+ LockMonitor lm (source_lock, __LINE__, __FILE__);
+ entry.first = source->id();
+ entry.second = source;
+ sources.insert (entry);
+ }
+
+ source->GoingAway.connect (mem_fun (this, &Session::remove_source));
+ set_dirty();
+
+ SourceAdded (source); /* EMIT SIGNAL */
+}
+
+void
+Session::remove_source (Source* source)
+{
+ SourceList::iterator i;
+
+ {
+ LockMonitor lm (source_lock, __LINE__, __FILE__);
+
+ if ((i = sources.find (source->id())) != sources.end()) {
+ sources.erase (i);
+ }
+ }
+
+ if (!_state_of_the_state & InCleanup) {
+
+ /* save state so we don't end up with a session file
+ referring to non-existent sources.
+ */
+
+ save_state (_current_snapshot_name);
+ }
+
+ SourceRemoved(source); /* EMIT SIGNAL */
+}
+
+Source *
+Session::get_source (ARDOUR::id_t id)
+{
+ LockMonitor lm (source_lock, __LINE__, __FILE__);
+ SourceList::iterator i;
+ Source* source = 0;
+
+ if ((i = sources.find (id)) != sources.end()) {
+ source = (*i).second;
+ }
+
+ return source;
+}
+
+FileSource *
+Session::create_file_source (DiskStream& ds, int32_t chan)
+{
+ string spath;
+ uint32_t cnt;
+ char buf[PATH_MAX+1];
+ const uint32_t limit = 10000;
+ string legalized;
+
+ buf[0] = '\0';
+ legalized = legalize_for_path (ds.name());
+
+ /* find a "version" of the file name that doesn't exist in
+ any of the possible directories.
+ */
+
+ for (cnt = 1; cnt <= limit; ++cnt) {
+
+ vector<space_and_path>::iterator i;
+ uint32_t existing = 0;
+
+ for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+
+ spath = (*i).path;
+ spath += sound_dir_name;
+ spath += '/';
+ spath += legalized;
+
+ if (ds.n_channels() < 2) {
+ snprintf (buf, sizeof(buf), "%s-%u.wav", spath.c_str(), cnt);
+ } else if (ds.n_channels() == 2) {
+ if (chan == 0) {
+ snprintf (buf, sizeof(buf), "%s-%u%%L.wav", spath.c_str(), cnt);
+ } else {
+ snprintf (buf, sizeof(buf), "%s-%u%%R.wav", spath.c_str(), cnt);
+ }
+ } else if (ds.n_channels() < 26) {
+ snprintf (buf, sizeof(buf), "%s-%u%%%c.wav", spath.c_str(), cnt, 'a' + chan);
+ } else {
+ snprintf (buf, sizeof(buf), "%s-%u.wav", spath.c_str(), cnt);
+ }
+
+ if (access (buf, F_OK) == 0) {
+ existing++;
+ }
+ }
+
+ if (existing == 0) {
+ break;
+ }
+ }
+
+ if (cnt > limit) {
+ error << compose(_("There are already %1 recordings for %2, which I consider too many."), limit, ds.name()) << endmsg;
+ throw failed_constructor();
+ }
+
+ /* we now have a unique name for the file, but figure out where to
+ actually put it.
+ */
+
+ string foo = buf;
+
+ spath = discover_best_sound_dir ();
+
+ string::size_type pos = foo.find_last_of ('/');
+
+ if (pos == string::npos) {
+ spath += foo;
+ } else {
+ spath += foo.substr (pos + 1);
+ }
+
+ /* this might throw failed_constructor(), which is OK */
+
+ return new FileSource (spath, frame_rate());
+}
+
+/* Playlist management */
+
+Playlist *
+Session::get_playlist (string name)
+{
+ Playlist* ret = 0;
+
+ if ((ret = playlist_by_name (name)) == 0) {
+ ret = new AudioPlaylist (*this, name);
+ }
+
+ return ret;
+}
+
+Playlist *
+Session::playlist_by_name (string name)
+{
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+ for (PlaylistList::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+ return 0;
+}
+
+void
+Session::add_playlist (Playlist* playlist)
+{
+ if (playlist->hidden()) {
+ return;
+ }
+
+ {
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+ if (find (playlists.begin(), playlists.end(), playlist) == playlists.end()) {
+ playlists.insert (playlists.begin(), playlist);
+ // playlist->ref();
+ playlist->InUse.connect (mem_fun (*this, &Session::track_playlist));
+ playlist->GoingAway.connect (mem_fun (*this, &Session::remove_playlist));
+ }
+ }
+
+ set_dirty();
+
+ PlaylistAdded (playlist); /* EMIT SIGNAL */
+}
+
+void
+Session::track_playlist (Playlist* pl, bool inuse)
+{
+ PlaylistList::iterator x;
+
+ {
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+
+ if (!inuse) {
+ //cerr << "shifting playlist to unused: " << pl->name() << endl;
+
+ unused_playlists.insert (pl);
+
+ if ((x = playlists.find (pl)) != playlists.end()) {
+ playlists.erase (x);
+ }
+
+
+ } else {
+ //cerr << "shifting playlist to used: " << pl->name() << endl;
+
+ playlists.insert (pl);
+
+ if ((x = unused_playlists.find (pl)) != unused_playlists.end()) {
+ unused_playlists.erase (x);
+ }
+ }
+ }
+}
+
+void
+Session::remove_playlist (Playlist* playlist)
+{
+ if (_state_of_the_state & Deletion) {
+ return;
+ }
+
+ {
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+ // cerr << "removing playlist: " << playlist->name() << endl;
+
+ PlaylistList::iterator i;
+
+ i = find (playlists.begin(), playlists.end(), playlist);
+
+ if (i != playlists.end()) {
+ playlists.erase (i);
+ }
+
+ i = find (unused_playlists.begin(), unused_playlists.end(), playlist);
+ if (i != unused_playlists.end()) {
+ unused_playlists.erase (i);
+ }
+
+ }
+
+ set_dirty();
+
+ PlaylistRemoved (playlist); /* EMIT SIGNAL */
+}
+
+void
+Session::set_audition (AudioRegion* r)
+{
+ pending_audition_region = r;
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportAudition);
+ schedule_butler_transport_work ();
+}
+
+void
+Session::non_realtime_set_audition ()
+{
+ if (pending_audition_region == (AudioRegion*) 0xfeedface) {
+ auditioner->audition_current_playlist ();
+ } else if (pending_audition_region) {
+ auditioner->audition_region (*pending_audition_region);
+ }
+ pending_audition_region = 0;
+ AuditionActive (true); /* EMIT SIGNAL */
+}
+
+void
+Session::audition_playlist ()
+{
+ Event* ev = new Event (Event::Audition, Event::Add, Event::Immediate, 0, 0.0);
+ ev->set_ptr ((void*) 0xfeedface);
+ queue_event (ev);
+}
+
+void
+Session::audition_region (AudioRegion& r)
+{
+ Event* ev = new Event (Event::Audition, Event::Add, Event::Immediate, 0, 0.0);
+ ev->set_ptr (&r);
+ queue_event (ev);
+}
+
+void
+Session::cancel_audition ()
+{
+ if (auditioner->active()) {
+ auditioner->cancel_audition ();
+ AuditionActive (false); /* EMIT SIGNAL */
+ }
+}
+
+bool
+Session::RoutePublicOrderSorter::operator() (Route* a, Route* b)
+{
+ return a->order_key(N_("signal")) < b->order_key(N_("signal"));
+}
+
+void
+Session::remove_empty_sounds ()
+{
+
+ PathScanner scanner;
+ string dir;
+
+ dir = sound_dir ();
+
+ vector<string *>* possible_audiofiles = scanner (dir, "\\.wav$", false, true);
+
+ for (vector<string *>::iterator i = possible_audiofiles->begin(); i != possible_audiofiles->end(); ++i) {
+
+ if (FileSource::is_empty (*(*i))) {
+
+ unlink ((*i)->c_str());
+
+ string peak_path = peak_path_from_audio_path (**i);
+ unlink (peak_path.c_str());
+ }
+
+ delete* i;
+ }
+
+ delete possible_audiofiles;
+}
+
+bool
+Session::is_auditioning () const
+{
+ /* can be called before we have an auditioner object */
+ if (auditioner) {
+ return auditioner->active();
+ } else {
+ return false;
+ }
+}
+
+
+string
+Session::peak_path_from_audio_path (string audio_path)
+{
+ /* XXX hardly bombproof! fix me */
+
+ string res;
+
+ res = PBD::dirname (audio_path);
+ res = PBD::dirname (res);
+ res += '/';
+ res += peak_dir_name;
+ res += '/';
+ res += PBD::basename_nosuffix (audio_path);
+ res += ".peak";
+
+ return res;
+}
+
+string
+Session::old_peak_path_from_audio_path (string audio_path)
+{
+ /* This is a hangover from when audio and peak files
+ lived in the same directory. We need it to to
+ be able to open old sessions.
+ */
+
+ /* XXX hardly bombproof! fix me */
+
+ string res = audio_path.substr (0, audio_path.find_last_of ('.'));
+ res += ".peak";
+ return res;
+}
+
+void
+Session::set_all_solo (bool yn)
+{
+ /* XXX this copy is not safe: the Routes within the list
+ can still be deleted after the Route lock is released.
+ */
+
+ RouteList copy;
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ copy = routes;
+ }
+
+ for (RouteList::iterator i = copy.begin(); i != copy.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_solo (yn, this);
+ }
+ }
+
+ set_dirty();
+}
+
+void
+Session::set_all_mute (bool yn)
+{
+ RouteList copy;
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ copy = routes;
+ }
+
+ for (RouteList::iterator i = copy.begin(); i != copy.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_mute (yn, this);
+ }
+ }
+
+ set_dirty();
+}
+
+uint32_t
+Session::n_diskstreams () const
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ uint32_t n = 0;
+
+ for (DiskStreamList::const_iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ n++;
+ }
+ }
+ return n;
+}
+
+void
+Session::foreach_diskstream (void (DiskStream::*func)(void))
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ ((*i)->*func)();
+ }
+ }
+}
+
+void
+Session::graph_reordered ()
+{
+ /* don't do this stuff if we are setting up connections
+ from a set_state() call.
+ */
+
+ if (_state_of_the_state & InitialConnecting) {
+ return;
+ }
+
+ LockMonitor lm1 (route_lock, __LINE__, __FILE__);
+ LockMonitor lm2 (diskstream_lock, __LINE__, __FILE__);
+
+ resort_routes (0);
+
+ /* force all diskstreams to update their capture offset values to
+ reflect any changes in latencies within the graph.
+ */
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->set_capture_offset ();
+ }
+}
+
+void
+Session::record_disenable_all ()
+{
+ record_enable_change_all (false);
+}
+
+void
+Session::record_enable_all ()
+{
+ record_enable_change_all (true);
+}
+
+void
+Session::record_enable_change_all (bool yn)
+{
+ LockMonitor lm1 (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ AudioTrack* at;
+
+ if ((at = dynamic_cast<AudioTrack*>(*i)) != 0) {
+ at->set_record_enable (yn, this);
+ }
+ }
+
+ /* since we don't keep rec-enable state, don't mark session dirty */
+}
+
+void
+Session::add_redirect (Redirect* redirect)
+{
+ Send* send;
+ Insert* insert;
+ PortInsert* port_insert;
+ PluginInsert* plugin_insert;
+
+ if ((insert = dynamic_cast<Insert *> (redirect)) != 0) {
+ if ((port_insert = dynamic_cast<PortInsert *> (insert)) != 0) {
+ _port_inserts.insert (_port_inserts.begin(), port_insert);
+ } else if ((plugin_insert = dynamic_cast<PluginInsert *> (insert)) != 0) {
+ _plugin_inserts.insert (_plugin_inserts.begin(), plugin_insert);
+ } else {
+ fatal << _("programming error: unknown type of Insert created!") << endmsg;
+ /*NOTREACHED*/
+ }
+ } else if ((send = dynamic_cast<Send *> (redirect)) != 0) {
+ _sends.insert (_sends.begin(), send);
+ } else {
+ fatal << _("programming error: unknown type of Redirect created!") << endmsg;
+ /*NOTREACHED*/
+ }
+
+ redirect->GoingAway.connect (mem_fun (*this, &Session::remove_redirect));
+
+ set_dirty();
+}
+
+void
+Session::remove_redirect (Redirect* redirect)
+{
+ Send* send;
+ Insert* insert;
+ PortInsert* port_insert;
+ PluginInsert* plugin_insert;
+
+ if ((insert = dynamic_cast<Insert *> (redirect)) != 0) {
+ if ((port_insert = dynamic_cast<PortInsert *> (insert)) != 0) {
+ _port_inserts.remove (port_insert);
+ } else if ((plugin_insert = dynamic_cast<PluginInsert *> (insert)) != 0) {
+ _plugin_inserts.remove (plugin_insert);
+ } else {
+ fatal << _("programming error: unknown type of Insert deleted!") << endmsg;
+ /*NOTREACHED*/
+ }
+ } else if ((send = dynamic_cast<Send *> (redirect)) != 0) {
+ _sends.remove (send);
+ } else {
+ fatal << _("programming error: unknown type of Redirect deleted!") << endmsg;
+ /*NOTREACHED*/
+ }
+
+ set_dirty();
+}
+
+jack_nframes_t
+Session::available_capture_duration ()
+{
+ const double scale = 4096.0 / sizeof (Sample);
+
+ if (_total_free_4k_blocks * scale > (double) max_frames) {
+ return max_frames;
+ }
+
+ return (jack_nframes_t) floor (_total_free_4k_blocks * scale);
+}
+
+void
+Session::add_connection (ARDOUR::Connection* connection)
+{
+ {
+ LockMonitor (connection_lock, __LINE__, __FILE__);
+ _connections.push_back (connection);
+ }
+
+ ConnectionAdded (connection); /* EMIT SIGNAL */
+
+ set_dirty();
+}
+
+void
+Session::remove_connection (ARDOUR::Connection* connection)
+{
+ bool removed = false;
+
+ {
+ LockMonitor (connection_lock, __LINE__, __FILE__);
+ ConnectionList::iterator i = find (_connections.begin(), _connections.end(), connection);
+
+ if (i != _connections.end()) {
+ _connections.erase (i);
+ removed = true;
+ }
+ }
+
+ if (removed) {
+ ConnectionRemoved (connection); /* EMIT SIGNAL */
+ }
+
+ set_dirty();
+}
+
+ARDOUR::Connection *
+Session::connection_by_name (string name) const
+{
+ LockMonitor lm (connection_lock, __LINE__, __FILE__);
+
+ for (ConnectionList::const_iterator i = _connections.begin(); i != _connections.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::set_edit_mode (EditMode mode)
+{
+ _edit_mode = mode;
+
+ {
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ (*i)->set_edit_mode (mode);
+ }
+ }
+
+ set_dirty ();
+ ControlChanged (EditingMode); /* EMIT SIGNAL */
+}
+
+void
+Session::tempo_map_changed (Change ignored)
+{
+ clear_clicks ();
+ set_dirty ();
+}
+
+void
+Session::ensure_passthru_buffers (uint32_t howmany)
+{
+ while (howmany > _passthru_buffers.size()) {
+ Sample *p;
+#ifdef NO_POSIX_MEMALIGN
+ p = (Sample *) malloc(current_block_size * sizeof(Sample));
+#else
+ posix_memalign((void **)&p,16,current_block_size * 4);
+#endif
+ _passthru_buffers.push_back (p);
+
+ *p = 0;
+
+#ifdef NO_POSIX_MEMALIGN
+ p = (Sample *) malloc(current_block_size * sizeof(Sample));
+#else
+ posix_memalign((void **)&p,16,current_block_size * 4);
+#endif
+ memset (p, 0, sizeof (Sample) * current_block_size);
+ _silent_buffers.push_back (p);
+
+ }
+ allocate_pan_automation_buffers (current_block_size, howmany, false);
+}
+
+string
+Session::next_send_name ()
+{
+ char buf[32];
+ snprintf (buf, sizeof (buf), "send %" PRIu32, ++send_cnt);
+ return buf;
+}
+
+string
+Session::next_insert_name ()
+{
+ char buf[32];
+ snprintf (buf, sizeof (buf), "insert %" PRIu32, ++insert_cnt);
+ return buf;
+}
+
+/* Named Selection management */
+
+NamedSelection *
+Session::named_selection_by_name (string name)
+{
+ LockMonitor lm (named_selection_lock, __LINE__, __FILE__);
+ for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) {
+ if ((*i)->name == name) {
+ return* i;
+ }
+ }
+ return 0;
+}
+
+void
+Session::add_named_selection (NamedSelection* named_selection)
+{
+ {
+ LockMonitor lm (named_selection_lock, __LINE__, __FILE__);
+ named_selections.insert (named_selections.begin(), named_selection);
+ }
+
+ set_dirty();
+
+ NamedSelectionAdded (); /* EMIT SIGNAL */
+}
+
+void
+Session::remove_named_selection (NamedSelection* named_selection)
+{
+ bool removed = false;
+
+ {
+ LockMonitor lm (named_selection_lock, __LINE__, __FILE__);
+
+ NamedSelectionList::iterator i = find (named_selections.begin(), named_selections.end(), named_selection);
+
+ if (i != named_selections.end()) {
+ delete (*i);
+ named_selections.erase (i);
+ set_dirty();
+ removed = true;
+ }
+ }
+
+ if (removed) {
+ NamedSelectionRemoved (); /* EMIT SIGNAL */
+ }
+}
+
+void
+Session::reset_native_file_format ()
+{
+ LockMonitor lm1 (route_lock, __LINE__, __FILE__);
+ LockMonitor lm2 (diskstream_lock, __LINE__, __FILE__);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->reset_write_sources (false);
+ }
+}
+
+bool
+Session::route_name_unique (string n) const
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::const_iterator i = routes.begin(); i != routes.end(); ++i) {
+ if ((*i)->name() == n) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int
+Session::remove_file_source (FileSource& fs)
+{
+ return fs.move_to_trash (dead_sound_dir_name);
+}
+
+uint32_t
+Session::n_playlists () const
+{
+ LockMonitor lm (playlist_lock, __LINE__, __FILE__);
+ return playlists.size();
+}
+
+void
+Session::set_align_style (AlignStyle style)
+{
+ align_style = style;
+
+ foreach_diskstream (&DiskStream::set_capture_offset);
+
+ set_dirty ();
+ ControlChanged (AlignChoice);
+}
+
+void
+Session::set_solo_model (SoloModel sm)
+{
+ _solo_model = sm;
+ set_dirty ();
+}
+
+void
+Session::allocate_pan_automation_buffers (jack_nframes_t nframes, uint32_t howmany, bool force)
+{
+ if (!force && howmany <= _npan_buffers) {
+ return;
+ }
+
+ if (_pan_automation_buffer) {
+
+ for (uint32_t i = 0; i < _npan_buffers; ++i) {
+ delete [] _pan_automation_buffer[i];
+ }
+
+ delete [] _pan_automation_buffer;
+ }
+
+ _pan_automation_buffer = new pan_t*[howmany];
+
+ for (uint32_t i = 0; i < howmany; ++i) {
+ _pan_automation_buffer[i] = new pan_t[nframes];
+ }
+
+ _npan_buffers = howmany;
+}
+
+void
+Session::add_instant_xml (XMLNode& node, const std::string& dir)
+{
+ Stateful::add_instant_xml (node, dir);
+ Config->add_instant_xml (node, Config->get_user_ardour_path());
+}
+
+int
+Session::freeze (InterThreadInfo& itt)
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ AudioTrack *at;
+
+ if ((at = dynamic_cast<AudioTrack*>(*i)) != 0) {
+ /* XXX this is wrong because itt.progress will keep returning to zero at the start
+ of every track.
+ */
+ at->freeze (itt);
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::write_one_track (AudioTrack& track, jack_nframes_t start, jack_nframes_t len, bool overwrite, vector<Source*>& srcs,
+ InterThreadInfo& itt)
+{
+ int ret = -1;
+ Playlist* playlist;
+ FileSource* fsource;
+ uint32_t x;
+ char buf[PATH_MAX+1];
+ string dir;
+ uint32_t nchans;
+ jack_nframes_t position;
+ jack_nframes_t this_chunk;
+ jack_nframes_t to_do;
+ vector<Sample*> buffers;
+ const jack_nframes_t chunk_size = (256 * 1024)/4;
+
+ atomic_set (&processing_prohibited, 1);
+
+ /* call tree *MUST* hold route_lock */
+
+ if ((playlist = track.disk_stream().playlist()) == 0) {
+ goto out;
+ }
+
+ /* external redirects will be a problem */
+
+ if (track.has_external_redirects()) {
+ goto out;
+ }
+
+ nchans = track.disk_stream().n_channels();
+
+ dir = discover_best_sound_dir ();
+
+ for (uint32_t chan_n=0; chan_n < nchans; ++chan_n) {
+
+ for (x = 0; x < 99999; ++x) {
+ snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 ".wav", dir.c_str(), playlist->name().c_str(), chan_n, x+1);
+ if (access (buf, F_OK) != 0) {
+ break;
+ }
+ }
+
+ if (x == 99999) {
+ error << compose (_("too many bounced versions of playlist \"%1\""), playlist->name()) << endmsg;
+ goto out;
+ }
+
+ try {
+ fsource = new FileSource (buf, frame_rate());
+ }
+
+ catch (failed_constructor& err) {
+ error << compose (_("cannot create new audio file \"%1\" for %2"), buf, track.name()) << endmsg;
+ goto out;
+ }
+
+ srcs.push_back(fsource);
+ }
+
+ /* XXX need to flush all redirects */
+
+ position = start;
+ to_do = len;
+
+ /* create a set of reasonably-sized buffers */
+
+ for (vector<Sample*>::iterator i = _passthru_buffers.begin(); i != _passthru_buffers.end(); ++i) {
+ Sample* b;
+#ifdef NO_POSIX_MEMALIGN
+ b = (Sample *) malloc(chunk_size * sizeof(Sample));
+#else
+ posix_memalign((void **)&b,16,chunk_size * 4);
+#endif
+ buffers.push_back (b);
+ }
+
+ while (to_do && !itt.cancel) {
+
+ this_chunk = min (to_do, chunk_size);
+
+ if (track.export_stuff (buffers, nchans, start, this_chunk)) {
+ goto out;
+ }
+
+ uint32_t n = 0;
+ for (vector<Source*>::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
+ if ((*src)->write (buffers[n], this_chunk) != this_chunk) {
+ goto out;
+ }
+ }
+
+ start += this_chunk;
+ to_do -= this_chunk;
+
+ itt.progress = (float) (1.0 - ((double) to_do / len));
+
+ }
+
+ if (!itt.cancel) {
+
+ time_t now;
+ struct tm* xnow;
+ time (&now);
+ xnow = localtime (&now);
+
+ for (vector<Source*>::iterator src=srcs.begin(); src != srcs.end(); ++src) {
+ dynamic_cast<FileSource*>((*src))->update_header (position, *xnow, now);
+ }
+
+ /* build peakfile for new source */
+
+ for (vector<Source*>::iterator src=srcs.begin(); src != srcs.end(); ++src) {
+ dynamic_cast<FileSource*>(*src)->build_peaks ();
+ }
+
+ ret = 0;
+ }
+
+ out:
+ if (ret) {
+ for (vector<Source*>::iterator src=srcs.begin(); src != srcs.end(); ++src) {
+ dynamic_cast<FileSource*>(*src)->mark_for_remove ();
+ delete *src;
+ }
+ }
+
+ for (vector<Sample*>::iterator i = buffers.begin(); i != buffers.end(); ++i) {
+ free(*i);
+ }
+
+ atomic_set (&processing_prohibited, 0);
+
+ itt.done = true;
+
+ return ret;
+}
+
+vector<Sample*>&
+Session::get_silent_buffers (uint32_t howmany)
+{
+ for (uint32_t i = 0; i < howmany; ++i) {
+ memset (_silent_buffers[i], 0, sizeof (Sample) * current_block_size);
+ }
+ return _silent_buffers;
+}
+
+uint32_t
+Session::ntracks () const
+{
+ uint32_t n = 0;
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::const_iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (dynamic_cast<AudioTrack*> (*i)) {
+ ++n;
+ }
+ }
+
+ return n;
+}
+
+uint32_t
+Session::nbusses () const
+{
+ uint32_t n = 0;
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::const_iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (dynamic_cast<AudioTrack*> (*i) == 0) {
+ ++n;
+ }
+ }
+
+ return n;
+}
+
+void
+Session::set_layer_model (LayerModel lm)
+{
+ layer_model = lm;
+ LayerModelChanged (); /* EMIT SIGNAL */
+ set_dirty ();
+}
+
+void
+Session::set_xfade_model (CrossfadeModel xm)
+{
+ xfade_model = xm;
+ set_dirty ();
+}
+
diff --git a/libs/ardour/session_butler.cc b/libs/ardour/session_butler.cc
new file mode 100644
index 0000000000..56000b695e
--- /dev/null
+++ b/libs/ardour/session_butler.cc
@@ -0,0 +1,460 @@
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <string>
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pthread_utils.h>
+
+#include <ardour/configuration.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/crossfade.h>
+#include <ardour/timestamps.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+static float _read_data_rate;
+static float _write_data_rate;
+
+/* XXX put this in the right place */
+
+static inline uint32_t next_power_of_two (uint32_t n)
+{
+ --n;
+ n |= n >> 16;
+ n |= n >> 8;
+ n |= n >> 4;
+ n |= n >> 2;
+ n |= n >> 1;
+ ++n;
+ return n;
+}
+
+/*---------------------------------------------------------------------------
+ BUTLER THREAD
+ ---------------------------------------------------------------------------*/
+
+int
+Session::start_butler_thread ()
+{
+ /* size is in Samples, not bytes */
+
+ dstream_buffer_size = (uint32_t) floor (Config->get_track_buffer() * (float) frame_rate());
+
+ Crossfade::set_buffer_size (dstream_buffer_size);
+
+ pthread_cond_init (&butler_paused, 0);
+
+ butler_should_run = false;
+
+ if (pipe (butler_request_pipe)) {
+ error << compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (butler_request_pipe[0], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on butler request pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (butler_request_pipe[1], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on butler request pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (pthread_create_and_store ("disk butler", &butler_thread, 0, _butler_thread_work, this)) {
+ error << _("Session: could not create butler thread") << endmsg;
+ return -1;
+ }
+
+ // pthread_detach (butler_thread);
+
+ return 0;
+}
+
+void
+Session::terminate_butler_thread ()
+{
+ void* status;
+ char c = ButlerRequest::Quit;
+ ::write (butler_request_pipe[1], &c, 1);
+ pthread_join (butler_thread, &status);
+}
+
+void
+Session::schedule_butler_transport_work ()
+{
+ atomic_inc (&butler_should_do_transport_work);
+ summon_butler ();
+}
+
+void
+Session::schedule_curve_reallocation ()
+{
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportCurveRealloc);
+ schedule_butler_transport_work ();
+}
+
+void
+Session::summon_butler ()
+{
+ char c = ButlerRequest::Run;
+ ::write (butler_request_pipe[1], &c, 1);
+}
+
+void
+Session::stop_butler ()
+{
+ LockMonitor lm (butler_request_lock, __LINE__, __FILE__);
+ char c = ButlerRequest::Pause;
+ ::write (butler_request_pipe[1], &c, 1);
+ pthread_cond_wait (&butler_paused, butler_request_lock.mutex());
+}
+
+void
+Session::wait_till_butler_finished ()
+{
+ LockMonitor lm (butler_request_lock, __LINE__, __FILE__);
+ char c = ButlerRequest::Wake;
+ ::write (butler_request_pipe[1], &c, 1);
+ pthread_cond_wait (&butler_paused, butler_request_lock.mutex());
+}
+
+void *
+Session::_butler_thread_work (void* arg)
+{
+ PBD::ThreadCreated (pthread_self(), X_("Butler"));
+
+ return ((Session *) arg)->butler_thread_work ();
+ return 0;
+}
+
+#define transport_work_requested() atomic_read(&butler_should_do_transport_work)
+
+void *
+Session::butler_thread_work ()
+{
+ uint32_t err = 0;
+ int32_t bytes;
+ bool compute_io;
+ struct timeval begin, end;
+ struct pollfd pfd[1];
+ bool disk_work_outstanding = false;
+ DiskStreamList::iterator i;
+
+ butler_mixdown_buffer = new Sample[DiskStream::disk_io_frames()];
+ butler_gain_buffer = new gain_t[DiskStream::disk_io_frames()];
+
+ while (true) {
+
+ pfd[0].fd = butler_request_pipe[0];
+ pfd[0].events = POLLIN|POLLERR|POLLHUP;
+
+ if (poll (pfd, 1, (disk_work_outstanding ? 0 : -1)) < 0) {
+
+ if (errno == EINTR) {
+ continue;
+ }
+
+ error << compose (_("poll on butler request pipe failed (%1)"),
+ strerror (errno))
+ << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & ~POLLIN) {
+ error << _("Error on butler thread request pipe") << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & POLLIN) {
+
+ char req;
+
+ /* empty the pipe of all current requests */
+
+ while (1) {
+ size_t nread = ::read (butler_request_pipe[0], &req, sizeof (req));
+
+ if (nread == 1) {
+
+ switch ((ButlerRequest::Type) req) {
+
+ case ButlerRequest::Wake:
+ break;
+ case ButlerRequest::Run:
+ butler_should_run = true;
+ break;
+
+ case ButlerRequest::Pause:
+ butler_should_run = false;
+ break;
+
+ case ButlerRequest::Quit:
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ break;
+
+ default:
+ break;
+ }
+
+ } else if (nread == 0) {
+ break;
+ } else if (errno == EAGAIN) {
+ break;
+ } else {
+ fatal << _("Error reading from butler request pipe") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+ }
+
+// for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+// cerr << "BEFORE " << (*i)->name() << ": pb = " << (*i)->playback_buffer_load() << " cp = " << (*i)->capture_buffer_load() << endl;
+// }
+
+ if (transport_work_requested()) {
+ butler_transport_work ();
+ }
+
+ disk_work_outstanding = false;
+ bytes = 0;
+ compute_io = true;
+
+ gettimeofday (&begin, 0);
+
+ for (i = diskstreams.begin(); !transport_work_requested() && butler_should_run && i != diskstreams.end(); ++i) {
+
+ // cerr << "rah fondr " << (*i)->io()->name () << endl;
+
+ switch ((*i)->do_refill (butler_mixdown_buffer, butler_gain_buffer)) {
+ case 0:
+ bytes += (*i)->read_data_count();
+ break;
+ case 1:
+ bytes += (*i)->read_data_count();
+ disk_work_outstanding = true;
+ break;
+
+ default:
+ compute_io = false;
+ error << compose(_("Butler read ahead failure on dstream %1"), (*i)->name()) << endmsg;
+ break;
+ }
+
+ }
+
+ if (i != diskstreams.end()) {
+ /* we didn't get to all the streams */
+ disk_work_outstanding = true;
+ }
+
+ if (!err && transport_work_requested()) {
+ continue;
+ }
+
+ if (compute_io) {
+ gettimeofday (&end, 0);
+
+ double b = begin.tv_sec + (begin.tv_usec/1000000.0);
+ double e = end.tv_sec + (end.tv_usec / 1000000.0);
+
+ _read_data_rate = bytes / (e - b);
+ }
+
+ bytes = 0;
+ compute_io = true;
+ gettimeofday (&begin, 0);
+
+ for (i = diskstreams.begin(); !transport_work_requested() && butler_should_run && i != diskstreams.end(); ++i) {
+
+ // cerr << "write behind for " << (*i)->name () << endl;
+
+ switch ((*i)->do_flush ()) {
+ case 0:
+ bytes += (*i)->write_data_count();
+ break;
+ case 1:
+ bytes += (*i)->write_data_count();
+ disk_work_outstanding = true;
+ break;
+
+ default:
+ err++;
+ compute_io = false;
+ error << compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << endmsg;
+ /* don't break - try to flush all streams in case they
+ are split across disks.
+ */
+ }
+ }
+
+ if (err && actively_recording()) {
+ /* stop the transport and try to catch as much possible
+ captured state as we can.
+ */
+ request_stop ();
+ }
+
+ if (i != diskstreams.end()) {
+ /* we didn't get to all the streams */
+ disk_work_outstanding = true;
+ }
+
+ if (!err && transport_work_requested()) {
+ continue;
+ }
+
+ if (compute_io) {
+ gettimeofday (&end, 0);
+
+ double b = begin.tv_sec + (begin.tv_usec/1000000.0);
+ double e = end.tv_sec + (end.tv_usec / 1000000.0);
+
+ _write_data_rate = bytes / (e - b);
+ }
+
+ if (!disk_work_outstanding) {
+ refresh_disk_space ();
+ }
+
+
+ {
+ LockMonitor lm (butler_request_lock, __LINE__, __FILE__);
+
+ if (butler_should_run && (disk_work_outstanding || transport_work_requested())) {
+// for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+// cerr << "AFTER " << (*i)->name() << ": pb = " << (*i)->playback_buffer_load() << " cp = " << (*i)->capture_buffer_load() << endl;
+// }
+
+ continue;
+ }
+
+ pthread_cond_signal (&butler_paused);
+ }
+ }
+
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ return (0);
+}
+
+
+void
+Session::request_overwrite_buffer (DiskStream* stream)
+{
+ Event *ev = new Event (Event::Overwrite, Event::Add, Event::Immediate, 0, 0, 0.0);
+ ev->set_ptr (stream);
+ queue_event (ev);
+}
+
+void
+Session::overwrite_some_buffers (DiskStream* ds)
+{
+ /* executed by the audio thread */
+
+ if (actively_recording()) {
+ return;
+ }
+
+ if (ds) {
+
+ ds->set_pending_overwrite (true);
+
+ } else {
+
+ LockMonitor dm (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->set_pending_overwrite (true);
+ }
+ }
+
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportOverWrite);
+ schedule_butler_transport_work ();
+}
+
+float
+Session::read_data_rate () const
+{
+ /* disk i/o in excess of 10000MB/sec indicate the buffer cache
+ in action. ignore it.
+ */
+ return _read_data_rate > 10485760000.0f ? 0.0f : _read_data_rate;
+}
+
+float
+Session::write_data_rate () const
+{
+ /* disk i/o in excess of 10000MB/sec indicate the buffer cache
+ in action. ignore it.
+ */
+ return _write_data_rate > 10485760000.0f ? 0.0f : _write_data_rate;
+}
+
+uint32_t
+Session::playback_load ()
+{
+ return (uint32_t) atomic_read (&_playback_load);
+}
+
+uint32_t
+Session::capture_load ()
+{
+ return (uint32_t) atomic_read (&_capture_load);
+}
+
+uint32_t
+Session::playback_load_min ()
+{
+ return (uint32_t) atomic_read (&_playback_load_min);
+}
+
+uint32_t
+Session::capture_load_min ()
+{
+ return (uint32_t) atomic_read (&_capture_load_min);
+}
+
+void
+Session::reset_capture_load_min ()
+{
+ atomic_set (&_capture_load_min, 100);
+}
+
+
+void
+Session::reset_playback_load_min ()
+{
+ atomic_set (&_playback_load_min, 100);
+}
diff --git a/libs/ardour/session_click.cc b/libs/ardour/session_click.cc
new file mode 100644
index 0000000000..6bf62bb272
--- /dev/null
+++ b/libs/ardour/session_click.cc
@@ -0,0 +1,253 @@
+/*
+ Copyright (C) 20002 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.
+
+ $Id$
+*/
+
+#include <list>
+#include <cerrno>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/tempo.h>
+#include <ardour/io.h>
+
+#include <sndfile.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+Pool Session::Click::pool ("click", sizeof (Click), 128);
+
+void
+Session::click (jack_nframes_t start, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ TempoMap::BBTPointList *points;
+ jack_nframes_t end;
+ Sample *buf;
+ vector<Sample*> bufs;
+
+ if (_click_io == 0) {
+ return;
+ }
+
+ if (_transport_speed != 1.0 || !_clicking || click_data == 0) {
+ _click_io->silence (nframes, offset);
+ return;
+ }
+
+ end = start + nframes;
+
+ buf = _passthru_buffers[0];
+ points = _tempo_map->get_points (start, end);
+
+ if (points == 0) {
+ goto run_clicks;
+ }
+
+ if (points->empty()) {
+ delete points;
+ goto run_clicks;
+ }
+
+ for (TempoMap::BBTPointList::iterator i = points->begin(); i != points->end(); ++i) {
+ switch ((*i).type) {
+ case TempoMap::Beat:
+ if (click_emphasis_data == 0 || (click_emphasis_data && (*i).beat != 1)) {
+ clicks.push_back (new Click ((*i).frame, click_length, click_data));
+ }
+ break;
+
+ case TempoMap::Bar:
+ if (click_emphasis_data) {
+ clicks.push_back (new Click ((*i).frame, click_emphasis_length, click_emphasis_data));
+ }
+ break;
+ }
+ }
+
+ run_clicks:
+ memset (buf, 0, sizeof (Sample) * nframes);
+
+ for (list<Click*>::iterator i = clicks.begin(); i != clicks.end(); ) {
+
+ jack_nframes_t copy;
+ jack_nframes_t internal_offset;
+ Click *clk;
+ list<Click*>::iterator next;
+
+ clk = *i;
+ next = i;
+ ++next;
+
+ if (clk->start < start) {
+ internal_offset = 0;
+ } else {
+ internal_offset = clk->start - start;
+ }
+
+ copy = min (clk->duration - clk->offset, nframes - internal_offset);
+
+ memcpy (buf + internal_offset, &clk->data[clk->offset], copy * sizeof (Sample));
+
+ clk->offset += copy;
+
+ if (clk->offset >= clk->duration) {
+ delete clk;
+ clicks.erase (i);
+ }
+
+
+ i = next;
+ }
+
+ _click_io->deliver_output (_passthru_buffers, 1, nframes, offset);
+}
+
+void
+Session::setup_click_sounds (int which)
+{
+ SNDFILE *sndfile;
+ SF_INFO info;
+
+ clear_clicks();
+
+ if ((which == 0 || which == 1)) {
+
+ if (click_data && click_data != default_click) {
+ delete [] click_data;
+ click_data = 0;
+ }
+
+ if (click_sound.length() == 0) {
+
+ click_data = const_cast<Sample*> (default_click);
+ click_length = default_click_length;
+
+ } else {
+
+ if ((sndfile = sf_open (click_sound.c_str(), SFM_READ, &info)) == 0) {
+ char errbuf[256];
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ warning << compose (_("cannot open click soundfile %1 (%2)"), click_sound, errbuf) << endmsg;
+ _clicking = false;
+ return;
+ }
+
+ click_data = new Sample[info.frames];
+ click_length = info.frames;
+
+ if (sf_read_float (sndfile, click_data, info.frames) != info.frames) {
+ warning << _("cannot read data from click soundfile") << endmsg;
+ delete click_data;
+ click_data = 0;
+ _clicking = false;
+ }
+
+ sf_close (sndfile);
+
+ }
+ }
+
+ if ((which == 0 || which == -1)) {
+
+ if (click_emphasis_data && click_emphasis_data != default_click_emphasis) {
+ delete [] click_emphasis_data;
+ click_emphasis_data = 0;
+ }
+
+ if (click_emphasis_sound.length() == 0) {
+ click_emphasis_data = const_cast<Sample*> (default_click_emphasis);
+ click_emphasis_length = default_click_emphasis_length;
+ } else {
+ if ((sndfile = sf_open (click_emphasis_sound.c_str(), SFM_READ, &info)) == 0) {
+ char errbuf[256];
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ warning << compose (_("cannot open click emphasis soundfile %1 (%2)"), click_emphasis_sound, errbuf) << endmsg;
+ return;
+ }
+
+ click_emphasis_data = new Sample[info.frames];
+ click_emphasis_length = info.frames;
+
+ if (sf_read_float (sndfile, click_emphasis_data, info.frames) != info.frames) {
+ warning << _("cannot read data from click emphasis soundfile") << endmsg;
+ delete click_emphasis_data;
+ click_emphasis_data = 0;
+ }
+
+ sf_close (sndfile);
+ }
+ }
+}
+
+void
+Session::clear_clicks ()
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (Clicks::iterator i = clicks.begin(); i != clicks.end(); ++i) {
+ delete *i;
+ }
+
+ clicks.clear ();
+}
+
+void
+Session::set_click_sound (string path)
+{
+ if (path != click_sound) {
+ click_sound = path;
+ setup_click_sounds (1);
+ }
+}
+
+void
+Session::set_click_emphasis_sound (string path)
+{
+ if (path != click_emphasis_sound) {
+ click_emphasis_sound = path;
+ setup_click_sounds (-1);
+ }
+}
+
+void
+Session::set_clicking (bool yn)
+{
+ if (click_requested != yn) {
+ click_requested = yn;
+
+ if (yn) {
+ if (_click_io && click_data) {
+ _clicking = true;
+ }
+ } else {
+ _clicking = false;
+ }
+
+ ControlChanged (Clicking); /* EMIT SIGNAL */
+ }
+}
+
+bool
+Session::get_clicking () const
+{
+ return click_requested;
+}
+
diff --git a/libs/ardour/session_events.cc b/libs/ardour/session_events.cc
new file mode 100644
index 0000000000..2d93108038
--- /dev/null
+++ b/libs/ardour/session_events.cc
@@ -0,0 +1,440 @@
+/*
+ Copyright (C) 1999-2004 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <unistd.h>
+
+#include <ardour/timestamps.h>
+
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+//using namespace sigc;
+
+MultiAllocSingleReleasePool Session::Event::pool ("event", sizeof (Session::Event), 512);
+
+static const char* event_names[] = {
+ "SetTransportSpeed",
+ "SetDiskstreamSpeed",
+ "Locate",
+ "LocateRoll",
+ "SetLoop",
+ "PunchIn",
+ "PunchOut",
+ "RangeStop",
+ "RangeLocate",
+ "Overwrite",
+ "SetSlaveSource",
+ "Audition",
+ "InputConfigurationChange",
+ "SetAudioRange",
+ "SetMusicRange",
+ "SetPlayRange",
+ "StopOnce",
+ "AutoLoop"
+};
+
+void
+Session::add_event (jack_nframes_t frame, Event::Type type, jack_nframes_t target_frame)
+{
+ Event* ev = new Event (type, Event::Add, frame, target_frame, 0);
+ queue_event (ev);
+}
+
+void
+Session::remove_event (jack_nframes_t frame, Event::Type type)
+{
+ Event* ev = new Event (type, Event::Remove, frame, 0, 0);
+ queue_event (ev);
+}
+
+void
+Session::replace_event (Event::Type type, jack_nframes_t frame, jack_nframes_t target)
+{
+ Event* ev = new Event (type, Event::Replace, frame, target, 0);
+ queue_event (ev);
+}
+
+void
+Session::clear_events (Event::Type type)
+{
+ Event* ev = new Event (type, Event::Clear, 0, 0, 0);
+ queue_event (ev);
+}
+
+
+void
+Session::dump_events () const
+{
+ cerr << "EVENT DUMP" << endl;
+ for (Events::const_iterator i = events.begin(); i != events.end(); ++i) {
+ cerr << "\tat " << (*i)->action_frame << ' ' << (*i)->type << " target = " << (*i)->target_frame << endl;
+ }
+ cerr << "Next event: ";
+
+ if ((Events::const_iterator) next_event == events.end()) {
+ cerr << "none" << endl;
+ } else {
+ cerr << "at " << (*next_event)->action_frame << ' '
+ << (*next_event)->type << " target = "
+ << (*next_event)->target_frame << endl;
+ }
+ cerr << "Immediate events pending:\n";
+ for (Events::const_iterator i = immediate_events.begin(); i != immediate_events.end(); ++i) {
+ cerr << "\tat " << (*i)->action_frame << ' ' << (*i)->type << " target = " << (*i)->target_frame << endl;
+ }
+ cerr << "END EVENT_DUMP" << endl;
+}
+
+void
+Session::queue_event (Event* ev)
+{
+ if (_state_of_the_state & Loading) {
+ merge_event (ev);
+ } else {
+ pending_events.write (&ev, 1);
+ }
+}
+
+void
+Session::merge_event (Event* ev)
+{
+ switch (ev->action) {
+ case Event::Remove:
+ _remove_event (ev);
+ delete ev;
+ return;
+
+ case Event::Replace:
+ _replace_event (ev);
+ return;
+
+ case Event::Clear:
+ _clear_event_type (ev->type);
+ delete ev;
+ return;
+
+ case Event::Add:
+ break;
+ }
+
+ /* try to handle immediate events right here */
+
+ if (ev->action_frame == 0) {
+ process_event (ev);
+ return;
+ }
+
+ switch (ev->type) {
+ case Event::AutoLoop:
+ case Event::StopOnce:
+ _clear_event_type (ev->type);
+ break;
+
+ default:
+ for (Events::iterator i = events.begin(); i != events.end(); ++i) {
+ if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
+ error << compose(_("Session: cannot have two events of type %1 at the same frame (%2)."),
+ event_names[ev->type], ev->action_frame) << endmsg;
+ return;
+ }
+ }
+ }
+
+ events.insert (events.begin(), ev);
+ events.sort (Event::compare);
+ next_event = events.begin();
+ set_next_event ();
+}
+
+bool
+Session::_replace_event (Event* ev)
+{
+ // returns true when we deleted the passed in event
+ bool ret = false;
+ Events::iterator i;
+
+ /* private, used only for events that can only exist once in the queue */
+
+ for (i = events.begin(); i != events.end(); ++i) {
+ if ((*i)->type == ev->type) {
+ (*i)->action_frame = ev->action_frame;
+ (*i)->target_frame = ev->target_frame;
+ if ((*i) == ev) {
+ ret = true;
+ }
+ delete ev;
+ break;
+ }
+ }
+
+ if (i == events.end()) {
+ events.insert (events.begin(), ev);
+ }
+
+ events.sort (Event::compare);
+ next_event = events.end();
+ set_next_event ();
+
+ return ret;
+}
+
+bool
+Session::_remove_event (Session::Event* ev)
+{
+ // returns true when we deleted the passed in event
+ bool ret = false;
+ Events::iterator i;
+
+ for (i = events.begin(); i != events.end(); ++i) {
+ if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
+ if ((*i) == ev) {
+ ret = true;
+ }
+
+ delete *i;
+ if (i == next_event) {
+ ++next_event;
+ }
+ events.erase (i);
+ break;
+ }
+ }
+
+ if (i != events.end()) {
+ set_next_event ();
+ }
+
+ return ret;
+}
+
+void
+Session::_clear_event_type (Event::Type type)
+{
+ Events::iterator i, tmp;
+
+ for (i = events.begin(); i != events.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->type == type) {
+ delete *i;
+ if (i == next_event) {
+ ++next_event;
+ }
+ events.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ for (i = immediate_events.begin(); i != immediate_events.end(); ) {
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)->type == type) {
+ delete *i;
+ immediate_events.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ set_next_event ();
+}
+
+void
+Session::set_next_event ()
+{
+ if (events.empty()) {
+ next_event = events.end();
+ return;
+ }
+
+ if (next_event == events.end()) {
+ next_event = events.begin();
+ }
+
+ if ((*next_event)->action_frame > _transport_frame) {
+ next_event = events.begin();
+ }
+
+ for (; next_event != events.end(); ++next_event) {
+ if ((*next_event)->action_frame >= _transport_frame) {
+ break;
+ }
+ }
+}
+
+void
+Session::process_event (Event* ev)
+{
+ bool remove = true;
+ bool del = true;
+
+ /* if we're in the middle of a state change (i.e. waiting
+ for the butler thread to complete the non-realtime
+ part of the change), we'll just have to queue this
+ event for a time when the change is complete.
+ */
+
+ if (non_realtime_work_pending()) {
+ immediate_events.insert (immediate_events.end(), ev);
+ _remove_event (ev);
+ return;
+ }
+
+ switch (ev->type) {
+ case Event::SetLoop:
+ set_auto_loop (ev->yes_or_no);
+ break;
+
+ case Event::Locate:
+ if (ev->yes_or_no) {
+ // cerr << "forced locate to " << ev->target_frame << endl;
+ locate (ev->target_frame, false, true, false);
+ } else {
+ // cerr << "soft locate to " << ev->target_frame << endl;
+ start_locate (ev->target_frame, false, true, false);
+ }
+ break;
+
+ case Event::LocateRoll:
+ if (ev->yes_or_no) {
+ // cerr << "forced locate to+roll " << ev->target_frame << endl;
+ locate (ev->target_frame, true, true, false);
+ } else {
+ // cerr << "soft locate to+roll " << ev->target_frame << endl;
+ start_locate (ev->target_frame, true, true, false);
+ }
+ break;
+
+ case Event::SetTransportSpeed:
+ set_transport_speed (ev->speed, ev->yes_or_no);
+ break;
+
+ case Event::PunchIn:
+ // cerr << "PunchIN at " << transport_frame() << endl;
+ if (punch_in && record_status() == Enabled) {
+ {
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->punch_in();
+ }
+ }
+ enable_record ();
+ }
+ remove = false;
+ del = false;
+ break;
+
+ case Event::PunchOut:
+ // cerr << "PunchOUT at " << transport_frame() << endl;
+ if (punch_out) {
+ step_back_from_record ();
+ }
+ remove = false;
+ del = false;
+ break;
+
+ case Event::StopOnce:
+ if (!non_realtime_work_pending()) {
+ stop_transport (ev->yes_or_no);
+ _clear_event_type (Event::StopOnce);
+ }
+ remove = false;
+ del = false;
+ break;
+
+ case Event::RangeStop:
+ if (!non_realtime_work_pending()) {
+ stop_transport (ev->yes_or_no);
+ }
+ remove = false;
+ del = false;
+ break;
+
+ case Event::RangeLocate:
+ start_locate (ev->target_frame, true, true, false);
+ remove = false;
+ del = false;
+ break;
+
+ case Event::AutoLoop:
+ if (auto_loop) {
+ start_locate (ev->target_frame, true, false, seamless_loop);
+ }
+ remove = false;
+ del = false;
+ break;
+
+ case Event::Overwrite:
+ overwrite_some_buffers (static_cast<DiskStream*>(ev->ptr));
+ break;
+
+ case Event::SetDiskstreamSpeed:
+ set_diskstream_speed (static_cast<DiskStream*> (ev->ptr), ev->speed);
+ break;
+
+ case Event::SetSlaveSource:
+ set_slave_source (ev->slave, ev->target_frame);
+ break;
+
+ case Event::Audition:
+ set_audition (static_cast<AudioRegion*> (ev->ptr));
+ break;
+
+ case Event::InputConfigurationChange:
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportInputChange);
+ schedule_butler_transport_work ();
+ break;
+
+ case Event::SetAudioRange:
+ current_audio_range = ev->audio_range;
+ setup_auto_play ();
+ break;
+
+ case Event::SetPlayRange:
+ set_play_range (ev->yes_or_no);
+ break;
+
+ default:
+ fatal << compose(_("Programming error: illegal event type in process_event (%1)"), ev->type) << endmsg;
+ /*NOTREACHED*/
+ break;
+ };
+
+ if (remove) {
+ del = del && !_remove_event (ev);
+ }
+
+ if (del) {
+ delete ev;
+ }
+}
diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc
new file mode 100644
index 0000000000..a17dde6979
--- /dev/null
+++ b/libs/ardour/session_export.cc
@@ -0,0 +1,640 @@
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+/* see gdither.cc for why we have to do this */
+
+#define _ISOC9X_SOURCE 1
+#define _ISOC99_SOURCE 1
+#include <cmath>
+#undef _ISOC99_SOURCE
+#undef _ISOC9X_SOURCE
+#undef __USE_SVID
+#define __USE_SVID 1
+#include <cstdlib>
+#undef __USE_SVID
+
+#include <unistd.h>
+#include <inttypes.h>
+#include <float.h>
+
+#include <sigc++/bind.h>
+
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+
+#include <ardour/gdither.h>
+#include <ardour/timestamps.h>
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/export.h>
+#include <ardour/sndfile_helpers.h>
+#include <ardour/port.h>
+#include <ardour/audioengine.h>
+#include <ardour/diskstream.h>
+#include <ardour/panner.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+static int
+convert_spec_to_info (AudioExportSpecification& spec, SF_INFO& sfinfo)
+{
+ if (spec.path.length() == 0) {
+ error << _("Export: no output file specified") << endmsg;
+ return -1;
+ }
+
+ /* XXX add checks that the directory path exists, and also
+ check if we are overwriting an existing file...
+ */
+
+ sfinfo.format = spec.format;
+ sfinfo.samplerate = spec.sample_rate;
+ sfinfo.frames = spec.end_frame - spec.start_frame + 1;
+ sfinfo.channels = min (spec.channels, 2U);
+
+ return 0;
+}
+
+AudioExportSpecification::AudioExportSpecification ()
+{
+ init ();
+}
+
+AudioExportSpecification::~AudioExportSpecification ()
+{
+ clear ();
+}
+
+void
+AudioExportSpecification::init ()
+{
+ src_state = 0;
+ pos = 0;
+ total_frames = 0;
+ out = 0;
+ channels = 0;
+ running = false;
+ stop = false;
+ progress = 0.0;
+ status = 0;
+ dither = 0;
+ start_frame = 0;
+ end_frame = 0;
+ dataF = 0;
+ dataF2 = 0;
+ leftoverF = 0;
+ max_leftover_frames = 0;
+ leftover_frames = 0;
+ output_data = 0;
+ out_samples_max = 0;
+ data_width = 0;
+ do_freewheel = false;
+}
+
+void
+AudioExportSpecification::clear ()
+{
+ if (out) {
+ sf_close (out);
+ out = 0;
+ }
+
+ if (src_state) {
+ src_delete (src_state);
+ src_state = 0;
+ }
+
+ if (dither) {
+ gdither_free (dither);
+ dither = 0;
+ }
+
+ if (output_data) {
+ free (output_data);
+ output_data = 0;
+ }
+ if (dataF) {
+ delete [] dataF;
+ dataF = 0;
+ }
+ if (dataF2) {
+ delete [] dataF2;
+ dataF2 = 0;
+ }
+ if (leftoverF) {
+ delete [] leftoverF;
+ leftoverF = 0;
+ }
+
+ freewheel_connection.disconnect ();
+
+ init ();
+}
+
+int
+AudioExportSpecification::prepare (jack_nframes_t blocksize, jack_nframes_t frate)
+{
+ char errbuf[256];
+ GDitherSize dither_size;
+
+ frame_rate = frate;
+
+ if (channels == 0) {
+ error << _("illegal frame range in export specification") << endmsg;
+ return -1;
+ }
+
+ if (start_frame >= end_frame) {
+ error << _("illegal frame range in export specification") << endmsg;
+ return -1;
+ }
+
+ if ((data_width = sndfile_data_width(format)) == 0) {
+ error << _("Bad data width size. Report me!") << endmsg;
+ return -1;
+ }
+
+ switch (data_width) {
+ case 8:
+ dither_size = GDither8bit;
+ break;
+
+ case 16:
+ dither_size = GDither16bit;
+ break;
+
+ case 24:
+ dither_size = GDither32bit;
+ break;
+
+ default:
+ dither_size = GDitherFloat;
+ break;
+ }
+
+ if (convert_spec_to_info (*this, sfinfo)) {
+ return -1;
+ }
+
+ /* XXX make sure we have enough disk space for the output */
+
+ if ((out = sf_open (path.c_str(), SFM_WRITE, &sfinfo)) == 0) {
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ error << compose(_("Export: cannot open output file \"%1\" (%2)"), path, errbuf) << endmsg;
+ return -1;
+ }
+
+ dataF = new float[blocksize * channels];
+
+ if (sample_rate != frame_rate) {
+ int err;
+
+ if ((src_state = src_new (src_quality, channels, &err)) == 0) {
+ error << compose (_("cannot initialize sample rate conversion: %1"), src_strerror (err)) << endmsg;
+ return -1;
+ }
+
+ src_data.src_ratio = sample_rate / (double) frame_rate;
+ out_samples_max = (jack_nframes_t) ceil (blocksize * src_data.src_ratio * channels);
+ dataF2 = new float[out_samples_max];
+
+ max_leftover_frames = 4 * blocksize;
+ leftoverF = new float[max_leftover_frames * channels];
+ leftover_frames = 0;
+
+ } else {
+ out_samples_max = blocksize * channels;
+ }
+
+ dither = gdither_new (dither_type, channels, dither_size, data_width);
+
+ /* allocate buffers where dithering and output will occur */
+
+ switch (data_width) {
+ case 8:
+ sample_bytes = 1;
+ break;
+
+ case 16:
+ sample_bytes = 2;
+ break;
+
+ case 24:
+ case 32:
+ sample_bytes = 4;
+ break;
+
+ default:
+ sample_bytes = 0; // float format
+ break;
+ }
+
+ if (sample_bytes) {
+ output_data = (void*) malloc (sample_bytes * out_samples_max);
+ }
+
+ return 0;
+}
+
+int
+AudioExportSpecification::process (jack_nframes_t nframes)
+{
+ float* float_buffer = 0;
+ uint32_t chn;
+ uint32_t x;
+ uint32_t i;
+ sf_count_t written;
+ char errbuf[256];
+ jack_nframes_t to_write = 0;
+ int cnt = 0;
+
+ do {
+
+ /* now do sample rate conversion */
+
+ if (sample_rate != frame_rate) {
+
+ int err;
+
+ src_data.output_frames = out_samples_max / channels;
+ src_data.end_of_input = ((pos + nframes) >= end_frame);
+ src_data.data_out = dataF2;
+
+ if (leftover_frames > 0) {
+
+ /* input data will be in leftoverF rather than dataF */
+
+ src_data.data_in = leftoverF;
+
+ if (cnt == 0) {
+
+ /* first time, append new data from dataF into the leftoverF buffer */
+
+ memcpy (leftoverF + (leftover_frames * channels), dataF, nframes * channels * sizeof(float));
+ src_data.input_frames = nframes + leftover_frames;
+ } else {
+
+ /* otherwise, just use whatever is still left in leftoverF; the contents
+ were adjusted using memmove() right after the last SRC call (see
+ below)
+ */
+
+ src_data.input_frames = leftover_frames;
+ }
+
+ } else {
+
+ src_data.data_in = dataF;
+ src_data.input_frames = nframes;
+
+ }
+
+ ++cnt;
+
+ if ((err = src_process (src_state, &src_data)) != 0) {
+ error << compose (_("an error occured during sample rate conversion: %1"),
+ src_strerror (err))
+ << endmsg;
+ return -1;
+ }
+
+ to_write = src_data.output_frames_gen;
+ leftover_frames = src_data.input_frames - src_data.input_frames_used;
+
+ if (leftover_frames > 0) {
+ if (leftover_frames > max_leftover_frames) {
+ error << _("warning, leftover frames overflowed, glitches might occur in output") << endmsg;
+ leftover_frames = max_leftover_frames;
+ }
+ memmove (leftoverF, (char *) (src_data.data_in + (src_data.input_frames_used * channels)),
+ leftover_frames * channels * sizeof(float));
+ }
+
+ float_buffer = dataF2;
+
+ } else {
+
+ /* no SRC, keep it simple */
+
+ to_write = nframes;
+ leftover_frames = 0;
+ float_buffer = dataF;
+ }
+
+ if (output_data) {
+ memset (output_data, 0, sample_bytes * to_write * channels);
+ }
+
+ switch (data_width) {
+ case 8:
+ case 16:
+ case 24:
+ for (chn = 0; chn < channels; ++chn) {
+ gdither_runf (dither, chn, to_write, float_buffer, output_data);
+ }
+ break;
+
+ case 32:
+ for (chn = 0; chn < channels; ++chn) {
+
+ int *ob = (int *) output_data;
+ const double int_max = (float) INT_MAX;
+ const double int_min = (float) INT_MIN;
+
+ for (x = 0; x < to_write; ++x) {
+ i = chn + (x * channels);
+
+ if (float_buffer[i] > 1.0f) {
+ ob[i] = INT_MAX;
+ } else if (float_buffer[i] < -1.0f) {
+ ob[i] = INT_MIN;
+ } else {
+ if (float_buffer[i] >= 0.0f) {
+ ob[i] = lrintf (int_max * float_buffer[i]);
+ } else {
+ ob[i] = - lrintf (int_min * float_buffer[i]);
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ for (x = 0; x < to_write * channels; ++x) {
+ if (float_buffer[x] > 1.0f) {
+ float_buffer[x] = 1.0f;
+ } else if (float_buffer[x] < -1.0f) {
+ float_buffer[x] = -1.0f;
+ }
+ }
+ break;
+ }
+
+ /* and export to disk */
+
+ switch (data_width) {
+ case 8:
+ /* XXXX no way to deliver 8 bit audio to libsndfile */
+ written = to_write;
+ break;
+
+ case 16:
+ written = sf_writef_short (out, (short*) output_data, to_write);
+ break;
+
+ case 24:
+ case 32:
+ written = sf_writef_int (out, (int*) output_data, to_write);
+ break;
+
+ default:
+ written = sf_writef_float (out, float_buffer, to_write);
+ break;
+ }
+
+ if ((jack_nframes_t) written != to_write) {
+ sf_error_str (out, errbuf, sizeof (errbuf) - 1);
+ error << compose(_("Export: could not write data to output file (%1)"), errbuf) << endmsg;
+ return -1;
+ }
+
+
+ } while (leftover_frames >= nframes);
+
+ return 0;
+}
+
+int
+Session::start_audio_export (AudioExportSpecification& spec)
+{
+ int ret;
+
+ if (spec.prepare (current_block_size, frame_rate())) {
+ return -1;
+ }
+
+ spec.pos = spec.start_frame;
+ spec.end_frame = spec.end_frame;
+ spec.total_frames = spec.end_frame - spec.start_frame;
+
+ spec.freewheel_connection = _engine.Freewheel.connect (sigc::bind (mem_fun (*this, &Session::process_export), &spec));
+
+ if ((ret = _engine.freewheel (true)) == 0) {
+ spec.running = true;
+ spec.do_freewheel = false;
+ }
+
+ return ret;
+}
+
+int
+Session::stop_audio_export (AudioExportSpecification& spec)
+{
+ /* can't use stop_transport() here because we need
+ an immediate halt and don't require all the declick
+ stuff that stop_transport() implements.
+ */
+
+ realtime_stop (true);
+ schedule_butler_transport_work ();
+
+ /* restart slaving */
+
+ if (post_export_slave != None) {
+ set_slave_source (post_export_slave, post_export_position);
+ } else {
+ locate (post_export_position, false, false, false);
+ }
+
+ spec.clear ();
+ _exporting = false;
+
+ spec.running = false;
+
+ return 0;
+}
+
+int
+Session::prepare_to_export (AudioExportSpecification& spec)
+{
+ int ret = -1;
+
+ wait_till_butler_finished ();
+
+ /* take everyone out of awrite to avoid disasters */
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->protect_automation ();
+ }
+ }
+
+ /* get everyone to the right position */
+
+ {
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)-> seek (spec.start_frame, true)) {
+ error << compose (_("%1: cannot seek to %2 for export"),
+ (*i)->name(), spec.start_frame)
+ << endmsg;
+ goto out;
+ }
+ }
+ }
+
+ /* make sure we are actually rolling */
+
+ if (get_record_enabled()) {
+ disable_record ();
+ }
+
+ _exporting = true;
+
+ /* no slaving */
+
+ post_export_slave = _slave_type;
+ post_export_position = _transport_frame;
+
+ set_slave_source (None, 0);
+
+ /* get transport ready */
+
+ set_transport_speed (1.0, false);
+ butler_transport_work ();
+ atomic_set (&butler_should_do_transport_work, 0);
+ post_transport ();
+
+ /* we are ready to go ... */
+
+ ret = 0;
+
+ out:
+ return ret;
+}
+
+int
+Session::process_export (jack_nframes_t nframes, AudioExportSpecification* spec)
+{
+ uint32_t chn;
+ uint32_t x;
+ int ret = -1;
+ jack_nframes_t this_nframes;
+
+ /* This is not required to be RT-safe because we are running while freewheeling */
+
+ if (spec->do_freewheel == false) {
+
+ /* first time in export function: get set up */
+
+ if (prepare_to_export (*spec)) {
+ spec->running = false;
+ spec->status = -1;
+ return -1;
+ }
+
+ spec->do_freewheel = true;
+ }
+
+ if (!_exporting) {
+ /* finished, but still freewheeling */
+ process_without_events (nframes);
+ return 0;
+ }
+
+ if (!spec->running || spec->stop || (this_nframes = min ((spec->end_frame - spec->pos), nframes)) == 0) {
+ process_without_events (nframes);
+ return stop_audio_export (*spec);
+ }
+
+ /* make sure we've caught up with disk i/o, since
+ we're running faster than realtime c/o JACK.
+ */
+
+ wait_till_butler_finished ();
+
+ /* do the usual stuff */
+
+ process_without_events (nframes);
+
+ /* and now export the results */
+
+ nframes = this_nframes;
+
+ memset (spec->dataF, 0, sizeof (spec->dataF[0]) * nframes * spec->channels);
+
+ /* foreach output channel ... */
+
+ for (chn = 0; chn < spec->channels; ++chn) {
+
+ AudioExportPortMap::iterator mi = spec->port_map.find (chn);
+
+ if (mi == spec->port_map.end()) {
+ /* no ports exported to this channel */
+ continue;
+ }
+
+ vector<PortChannelPair>& mapped_ports ((*mi).second);
+
+ for (vector<PortChannelPair>::iterator t = mapped_ports.begin(); t != mapped_ports.end(); ++t) {
+
+ /* OK, this port's output is supposed to appear on this channel
+ */
+
+ Port* port = (*t).first;
+ Sample* port_buffer = port->get_buffer (nframes);
+
+ /* now interleave the data from the channel into the float buffer */
+
+ for (x = 0; x < nframes; ++x) {
+ spec->dataF[chn+(x*spec->channels)] += (float) port_buffer[x];
+ }
+ }
+ }
+
+ if (spec->process (nframes)) {
+ goto out;
+ }
+
+ spec->pos += nframes;
+ spec->progress = 1.0 - (((float) spec->end_frame - spec->pos) / spec->total_frames);
+
+ /* and we're good to go */
+
+ ret = 0;
+
+ out:
+ if (ret) {
+ sf_close (spec->out);
+ spec->out = 0;
+ unlink (spec->path.c_str());
+ spec->running = false;
+ spec->status = ret;
+ _exporting = false;
+ }
+
+ return ret;
+}
+
diff --git a/libs/ardour/session_feedback.cc b/libs/ardour/session_feedback.cc
new file mode 100644
index 0000000000..d3025cef50
--- /dev/null
+++ b/libs/ardour/session_feedback.cc
@@ -0,0 +1,245 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <string>
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <midi++/types.h>
+#include <midi++/port.h>
+#include <midi++/manager.h>
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pthread_utils.h>
+
+#include <ardour/configuration.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+#include <ardour/audio_track.h>
+#include <ardour/diskstream.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+
+int
+Session::init_feedback ()
+{
+ if (pipe (feedback_request_pipe) != 0) {
+ error << compose (_("cannot create feedback request pipe (%1)"),
+ strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ if (fcntl (feedback_request_pipe[0], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on " "signal read pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (feedback_request_pipe[1], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on " "signal write pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ active_feedback = 0;
+ midi_feedback = false;
+
+ /* add possible feedback functions here */
+
+ feedback_functions.push_back (mem_fun (*this, &Session::feedback_generic_midi_function));
+
+ if (pthread_create_and_store ("feedback", &feedback_thread, 0, _feedback_thread_work, this)) {
+ error << _("Session: could not create feedback thread") << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+Session::poke_feedback (FeedbackRequest::Type why)
+{
+ char c = (char) why;
+ return !(write (feedback_request_pipe[1], &c, 1) == 1);
+}
+
+int
+Session::start_feedback ()
+{
+ return poke_feedback (FeedbackRequest::Start);
+}
+
+int
+Session::stop_feedback ()
+{
+ return poke_feedback (FeedbackRequest::Stop);
+}
+
+void
+Session::terminate_feedback ()
+{
+ void* status;
+ poke_feedback (FeedbackRequest::Quit);
+ pthread_join (feedback_thread, &status);
+}
+
+void*
+Session::_feedback_thread_work (void* arg)
+{
+ return static_cast<Session*> (arg)->feedback_thread_work ();
+}
+
+void*
+Session::feedback_thread_work ()
+{
+ PBD::ThreadCreated (pthread_self(), X_("Feedback"));
+ struct pollfd pfd[1];
+ int timeout;
+
+ pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0);
+ pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
+
+ if (active_feedback) {
+ /* XXX use Config->feedback_interval_usecs()*/;
+ timeout = 250;
+ } else {
+ timeout = -1;
+ }
+
+ while (1) {
+
+ pfd[0].fd = feedback_request_pipe[0];
+ pfd[0].events = POLLIN|POLLHUP|POLLERR;
+
+ if (poll (pfd, 1, timeout) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ error << compose (_("Feedback thread poll failed (%1)"),
+ strerror (errno))
+ << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & ~POLLIN) {
+ error << _("Error on feedback thread request pipe") << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & POLLIN) {
+
+ char req;
+
+ /* empty the pipe of all current requests */
+
+ while (1) {
+ size_t nread = read (feedback_request_pipe[0], &req, sizeof (req));
+
+ if (nread == 1) {
+ switch ((FeedbackRequest::Type) req) {
+
+ case FeedbackRequest::Start:
+ timeout = 250;
+ active_feedback++;
+ break;
+
+ case FeedbackRequest::Stop:
+ timeout = -1;
+ if (active_feedback) {
+ active_feedback--;
+ }
+ break;
+
+ case FeedbackRequest::Quit:
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ break;
+
+ default:
+ break;
+ }
+
+ } else if (nread == 0) {
+ break;
+ } else if (errno == EAGAIN) {
+ break;
+ } else {
+ fatal << _("Error reading from feedback request pipe") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+ }
+
+ if (!active_feedback) {
+ continue;
+ }
+
+ for (list<FeedbackFunctionPtr>::iterator i = feedback_functions.begin(); i != feedback_functions.end(); ) {
+
+ list<FeedbackFunctionPtr>::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ if ((*i)) {
+ feedback_functions.erase (i);
+ }
+
+ i = tmp;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::feedback_generic_midi_function ()
+{
+ const int32_t bufsize = 16 * 1024;
+ int32_t bsize = bufsize;
+ MIDI::byte* buf = new MIDI::byte[bufsize];
+ MIDI::byte* end = buf;
+
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ end = (*i)->write_midi_feedback (end, bsize);
+ }
+ }
+
+ if (end == buf) {
+ delete [] buf;
+ return 0;
+ }
+
+ // cerr << "MIDI feedback: write " << (int32_t) (end - buf) << " of " << buf << " to midi port\n";
+
+ deliver_midi (_midi_port, buf, (int32_t) (end - buf));
+
+ return 0;
+}
+
diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc
new file mode 100644
index 0000000000..a8ed4bc0f7
--- /dev/null
+++ b/libs/ardour/session_midi.cc
@@ -0,0 +1,1498 @@
+
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+#include <string>
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <midi++/mmc.h>
+#include <midi++/port.h>
+#include <midi++/manager.h>
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pthread_utils.h>
+
+#include <ardour/configuration.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+#include <ardour/audio_track.h>
+#include <ardour/diskstream.h>
+#include <ardour/slave.h>
+#include <ardour/cycles.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+//using namespace sigc;
+using namespace MIDI;
+
+MachineControl::CommandSignature MMC_CommandSignature;
+MachineControl::ResponseSignature MMC_ResponseSignature;
+
+MultiAllocSingleReleasePool Session::MIDIRequest::pool ("midi", sizeof (Session::MIDIRequest), 256);
+
+int
+Session::use_config_midi_ports ()
+{
+ string port_name;
+
+ if (default_mmc_port) {
+ set_mmc_port (default_mmc_port->name());
+ } else {
+ set_mmc_port ("");
+ }
+
+ if (default_mtc_port) {
+ set_mtc_port (default_mtc_port->name());
+ } else {
+ set_mtc_port ("");
+ }
+
+ if (default_midi_port) {
+ set_midi_port (default_midi_port->name());
+ } else {
+ set_midi_port ("");
+ }
+
+ return 0;
+}
+
+
+/***********************************************************************
+ MTC, MMC, etc.
+**********************************************************************/
+
+void
+Session::set_mmc_control (bool yn)
+{
+ if (mmc_control == yn) {
+ return;
+ }
+
+ mmc_control = yn;
+ set_dirty();
+ poke_midi_thread ();
+
+ ControlChanged (MMCControl); /* EMIT SIGNAL */
+}
+
+void
+Session::set_midi_control (bool yn)
+{
+ if (midi_control == yn) {
+ return;
+ }
+
+ midi_control = yn;
+ set_dirty();
+ poke_midi_thread ();
+
+ if (_midi_port) {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->reset_midi_control (_midi_port, midi_control);
+ }
+ }
+
+ ControlChanged (MidiControl); /* EMIT SIGNAL */
+}
+
+void
+Session::set_send_mtc (bool yn)
+{
+ /* set the persistent option value regardless */
+
+ send_midi_timecode = yn;
+ set_dirty();
+
+ /* only set the internal flag if we have
+ a port.
+ */
+
+ if (_mtc_port == 0 || send_mtc == yn) {
+ return;
+ }
+
+ send_mtc = yn;
+ ControlChanged (SendMTC); /* EMIT SIGNAL */
+}
+
+void
+Session::set_send_mmc (bool yn)
+{
+ if (_mmc_port == 0) {
+ return;
+ }
+
+ if (send_midi_machine_control == yn) {
+ return;
+ }
+
+ /* only set the internal flag if we have
+ a port.
+ */
+
+ if (_mmc_port) {
+ send_mmc = yn;
+ }
+
+ /* set the persistent option value regardless */
+
+ send_midi_machine_control = yn;
+ set_dirty();
+
+ ControlChanged (SendMMC); /* EMIT SIGNAL */
+}
+
+bool
+Session::get_send_mtc () const
+{
+ return send_mtc;
+}
+
+bool
+Session::get_send_mmc () const
+{
+ return send_mmc;
+}
+
+int
+Session::set_mtc_port (string port_tag)
+{
+ MTC_Slave *ms;
+
+ if (port_tag.length() == 0) {
+
+ if (_slave && ((ms = dynamic_cast<MTC_Slave*> (_slave)) != 0)) {
+ error << _("Ardour is slaved to MTC - port cannot be reset") << endmsg;
+ return -1;
+ }
+
+ if (_mtc_port == 0) {
+ return 0;
+ }
+
+ _mtc_port = 0;
+ goto out;
+ }
+
+ MIDI::Port* port;
+
+ if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
+ error << compose (_("unknown port %1 requested for MTC"), port_tag) << endl;
+ return -1;
+ }
+
+ _mtc_port = port;
+
+ if (_slave && ((ms = dynamic_cast<MTC_Slave*> (_slave)) != 0)) {
+ ms->rebind (*port);
+ }
+
+ Config->set_mtc_port_name (port_tag);
+
+ out:
+ MTC_PortChanged(); /* EMIT SIGNAL */
+ change_midi_ports ();
+ set_dirty();
+ return 0;
+}
+
+int
+Session::set_mmc_port (string port_tag)
+{
+ if (port_tag.length() == 0) {
+ if (_mmc_port == 0) {
+ return 0;
+ }
+ _mmc_port = 0;
+ goto out;
+ }
+
+ MIDI::Port* port;
+
+ if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
+ return -1;
+ }
+
+ _mmc_port = port;
+
+ if (mmc) {
+ delete mmc;
+ }
+
+ mmc = new MIDI::MachineControl (*_mmc_port, 1.0,
+ MMC_CommandSignature,
+ MMC_ResponseSignature);
+
+
+ mmc->Play.connect
+ (mem_fun (*this, &Session::mmc_deferred_play));
+ mmc->DeferredPlay.connect
+ (mem_fun (*this, &Session::mmc_deferred_play));
+ mmc->Stop.connect
+ (mem_fun (*this, &Session::mmc_stop));
+ mmc->FastForward.connect
+ (mem_fun (*this, &Session::mmc_fast_forward));
+ mmc->Rewind.connect
+ (mem_fun (*this, &Session::mmc_rewind));
+ mmc->Pause.connect
+ (mem_fun (*this, &Session::mmc_pause));
+ mmc->RecordPause.connect
+ (mem_fun (*this, &Session::mmc_record_pause));
+ mmc->RecordStrobe.connect
+ (mem_fun (*this, &Session::mmc_record_strobe));
+ mmc->RecordExit.connect
+ (mem_fun (*this, &Session::mmc_record_exit));
+ mmc->Locate.connect
+ (mem_fun (*this, &Session::mmc_locate));
+ mmc->Step.connect
+ (mem_fun (*this, &Session::mmc_step));
+ mmc->Shuttle.connect
+ (mem_fun (*this, &Session::mmc_shuttle));
+ mmc->TrackRecordStatusChange.connect
+ (mem_fun (*this, &Session::mmc_record_enable));
+
+ /* also handle MIDI SPP because its so common */
+
+ _mmc_port->input()->start.connect (mem_fun (*this, &Session::spp_start));
+ _mmc_port->input()->contineu.connect (mem_fun (*this, &Session::spp_continue));
+ _mmc_port->input()->stop.connect (mem_fun (*this, &Session::spp_stop));
+
+ Config->set_mmc_port_name (port_tag);
+
+ out:
+ MMC_PortChanged(); /* EMIT SIGNAL */
+ change_midi_ports ();
+ set_dirty();
+ return 0;
+}
+
+int
+Session::set_midi_port (string port_tag)
+{
+ if (port_tag.length() == 0) {
+ if (_midi_port == 0) {
+ return 0;
+ }
+ _midi_port = 0;
+ goto out;
+ }
+
+ MIDI::Port* port;
+
+ if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
+ return -1;
+ }
+
+ _midi_port = port;
+
+ Config->set_midi_port_name (port_tag);
+
+ out:
+ MIDI_PortChanged(); /* EMIT SIGNAL */
+ change_midi_ports ();
+ set_dirty();
+ return 0;
+}
+
+void
+Session::set_trace_midi_input (bool yn, MIDI::Port* port)
+{
+ MIDI::Parser* input_parser;
+
+ if (port) {
+ if ((input_parser = port->input()) != 0) {
+ input_parser->trace (yn, &cout, "input: ");
+ }
+ } else {
+
+ if (_mmc_port) {
+ if ((input_parser = _mmc_port->input()) != 0) {
+ input_parser->trace (yn, &cout, "input: ");
+ }
+ }
+
+ if (_mtc_port && _mtc_port != _mmc_port) {
+ if ((input_parser = _mtc_port->input()) != 0) {
+ input_parser->trace (yn, &cout, "input: ");
+ }
+ }
+
+ if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port ) {
+ if ((input_parser = _midi_port->input()) != 0) {
+ input_parser->trace (yn, &cout, "input: ");
+ }
+ }
+ }
+
+ Config->set_trace_midi_input (yn);
+}
+
+void
+Session::set_trace_midi_output (bool yn, MIDI::Port* port)
+{
+ MIDI::Parser* output_parser;
+
+ if (port) {
+ if ((output_parser = port->output()) != 0) {
+ output_parser->trace (yn, &cout, "output: ");
+ }
+ } else {
+ if (_mmc_port) {
+ if ((output_parser = _mmc_port->output()) != 0) {
+ output_parser->trace (yn, &cout, "output: ");
+ }
+ }
+
+ if (_mtc_port && _mtc_port != _mmc_port) {
+ if ((output_parser = _mtc_port->output()) != 0) {
+ output_parser->trace (yn, &cout, "output: ");
+ }
+ }
+
+ if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port ) {
+ if ((output_parser = _midi_port->output()) != 0) {
+ output_parser->trace (yn, &cout, "output: ");
+ }
+ }
+
+ }
+
+ Config->set_trace_midi_output (yn);
+}
+
+bool
+Session::get_trace_midi_input(MIDI::Port *port)
+{
+ MIDI::Parser* input_parser;
+ if (port) {
+ if ((input_parser = port->input()) != 0) {
+ return input_parser->tracing();
+ }
+ }
+ else {
+ if (_mmc_port) {
+ if ((input_parser = _mmc_port->input()) != 0) {
+ return input_parser->tracing();
+ }
+ }
+
+ if (_mtc_port) {
+ if ((input_parser = _mtc_port->input()) != 0) {
+ return input_parser->tracing();
+ }
+ }
+
+ if (_midi_port) {
+ if ((input_parser = _midi_port->input()) != 0) {
+ return input_parser->tracing();
+ }
+ }
+ }
+
+ return false;
+}
+
+bool
+Session::get_trace_midi_output(MIDI::Port *port)
+{
+ MIDI::Parser* output_parser;
+ if (port) {
+ if ((output_parser = port->output()) != 0) {
+ return output_parser->tracing();
+ }
+ }
+ else {
+ if (_mmc_port) {
+ if ((output_parser = _mmc_port->output()) != 0) {
+ return output_parser->tracing();
+ }
+ }
+
+ if (_mtc_port) {
+ if ((output_parser = _mtc_port->output()) != 0) {
+ return output_parser->tracing();
+ }
+ }
+
+ if (_midi_port) {
+ if ((output_parser = _midi_port->output()) != 0) {
+ return output_parser->tracing();
+ }
+ }
+ }
+
+ return false;
+
+}
+
+
+void
+Session::set_midi_feedback (bool yn)
+{
+ if (_midi_port == 0) {
+ return;
+ }
+
+ midi_feedback = yn;
+ set_dirty();
+
+ if (yn) {
+ /* make sure the feedback thread is alive */
+ start_feedback ();
+ } else {
+ /* maybe put the feedback thread to sleep */
+ stop_feedback ();
+ }
+
+ ControlChanged (MidiFeedback); /* EMIT SIGNAL */
+
+ send_all_midi_feedback ();
+}
+
+void
+Session::send_all_midi_feedback ()
+{
+ if (midi_feedback) {
+ // send out current state of all routes
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->send_all_midi_feedback ();
+ }
+ }
+}
+
+void
+Session::setup_midi_control ()
+{
+ outbound_mtc_smpte_frame = 0;
+ next_quarter_frame_to_send = -1;
+
+ /* setup the MMC buffer */
+
+ mmc_buffer[0] = 0xf0; // SysEx
+ mmc_buffer[1] = 0x7f; // Real Time SysEx ID for MMC
+ mmc_buffer[2] = 0x7f; // "broadcast" device ID
+ mmc_buffer[3] = 0x6; // MCC
+
+ /* Set up the qtr frame message */
+
+ mtc_msg[0] = 0xf1;
+ mtc_msg[2] = 0xf1;
+ mtc_msg[4] = 0xf1;
+ mtc_msg[6] = 0xf1;
+ mtc_msg[8] = 0xf1;
+ mtc_msg[10] = 0xf1;
+ mtc_msg[12] = 0xf1;
+ mtc_msg[14] = 0xf1;
+
+ if (_mmc_port != 0) {
+
+ send_mmc = send_midi_machine_control;
+
+ } else {
+
+ mmc = 0;
+ send_mmc = false;
+ }
+
+ if (_mtc_port != 0) {
+
+ send_mtc = send_midi_timecode;
+
+ } else {
+
+ send_mtc = false;
+ }
+}
+
+int
+Session::midi_read (MIDI::Port* port)
+{
+ MIDI::byte buf[512];
+
+ /* reading from the MIDI port activates the Parser
+ that in turn generates signals that we care
+ about. the port is already set to NONBLOCK so that
+ can read freely here.
+ */
+
+ while (1) {
+
+ // cerr << "+++ READ ON " << port->name() << endl;
+
+ int nread = port->read (buf, sizeof (buf));
+
+ // cerr << "-- READ (" << nread << " ON " << port->name() << endl;
+
+ if (nread > 0) {
+ if ((size_t) nread < sizeof (buf)) {
+ break;
+ } else {
+ continue;
+ }
+ } else if (nread == 0) {
+ break;
+ } else if (errno == EAGAIN) {
+ break;
+ } else {
+ fatal << compose(_("Error reading from MIDI port %1"), port->name()) << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::spp_start (Parser& ignored)
+{
+ if (mmc_control && (_slave_type != MTC)) {
+ request_transport_speed (1.0);
+ }
+}
+
+void
+Session::spp_continue (Parser& ignored)
+{
+ spp_start (ignored);
+}
+
+void
+Session::spp_stop (Parser& ignored)
+{
+ if (mmc_control) {
+ request_stop ();
+ }
+}
+
+void
+Session::mmc_deferred_play (MIDI::MachineControl &mmc)
+{
+ if (mmc_control && (_slave_type != MTC)) {
+ request_transport_speed (1.0);
+ }
+}
+
+void
+Session::mmc_record_pause (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+ maybe_enable_record();
+ }
+}
+
+void
+Session::mmc_record_strobe (MIDI::MachineControl &mmc)
+{
+ if (!mmc_control)
+ return;
+
+ /* record strobe does an implicit "Play" command */
+
+ if (_transport_speed != 1.0) {
+
+ /* start_transport() will move from Enabled->Recording, so we
+ don't need to do anything here except enable recording.
+ its not the same as maybe_enable_record() though, because
+ that *can* switch to Recording, which we do not want.
+ */
+
+ save_state ("", true);
+ atomic_set (&_record_status, Enabled);
+ RecordEnabled (); /* EMIT SIGNAL */
+
+ request_transport_speed (1.0);
+
+ } else {
+
+ enable_record ();
+ }
+}
+
+void
+Session::mmc_record_exit (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+ disable_record ();
+ }
+}
+
+void
+Session::mmc_stop (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+ request_stop ();
+ }
+}
+
+void
+Session::mmc_pause (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+
+ /* We support RECORD_PAUSE, so the spec says that
+ we must interpret PAUSE like RECORD_PAUSE if
+ recording.
+ */
+
+ if (actively_recording()) {
+ maybe_enable_record ();
+ } else {
+ request_stop ();
+ }
+ }
+}
+
+static bool step_queued = false;
+
+void
+
+Session::mmc_step (MIDI::MachineControl &mmc, int steps)
+{
+ if (!mmc_control) {
+ return;
+ }
+
+ struct timeval now;
+ struct timeval diff = { 0, 0 };
+
+ gettimeofday (&now, 0);
+
+ timersub (&now, &last_mmc_step, &diff);
+
+ gettimeofday (&now, 0);
+ timersub (&now, &last_mmc_step, &diff);
+
+ if (last_mmc_step.tv_sec != 0 && (diff.tv_usec + (diff.tv_sec * 1000000)) < _engine.usecs_per_cycle()) {
+ return;
+ }
+
+ double diff_secs = diff.tv_sec + (diff.tv_usec / 1000000.0);
+ double cur_speed = (((steps * 0.5) * smpte_frames_per_second) / diff_secs) / smpte_frames_per_second;
+
+ if (_transport_speed == 0 || cur_speed * _transport_speed < 0) {
+ /* change direction */
+ step_speed = cur_speed;
+ } else {
+ step_speed = (0.6 * step_speed) + (0.4 * cur_speed);
+ }
+
+ step_speed *= 0.25;
+
+#if 0
+ cerr << "delta = " << diff_secs
+ << " ct = " << _transport_speed
+ << " steps = " << steps
+ << " new speed = " << cur_speed
+ << " speed = " << step_speed
+ << endl;
+#endif
+
+ request_transport_speed (step_speed);
+ last_mmc_step = now;
+
+ if (!step_queued) {
+ midi_timeouts.push_back (mem_fun (*this, &Session::mmc_step_timeout));
+ step_queued = true;
+ }
+}
+
+void
+Session::mmc_rewind (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+ request_transport_speed(-8.0f);
+ }
+}
+
+void
+Session::mmc_fast_forward (MIDI::MachineControl &mmc)
+{
+ if (mmc_control) {
+ request_transport_speed(8.0f);
+ }
+}
+
+void
+Session::mmc_locate (MIDI::MachineControl &mmc, const MIDI::byte* mmc_tc)
+{
+ if (!mmc_control) {
+ return;
+ }
+
+ jack_nframes_t target_frame;
+ SMPTE_Time smpte;
+
+ smpte.hours = mmc_tc[0] & 0xf;
+ smpte.minutes = mmc_tc[1];
+ smpte.seconds = mmc_tc[2];
+ smpte.frames = mmc_tc[3];
+
+ // Also takes smpte offset into account:
+ smpte_to_sample( smpte, target_frame, true /* use_offset */, false /* use_subframes */ );
+
+ if (target_frame > max_frames) {
+ target_frame = max_frames;
+ }
+
+ /* Some (all?) MTC/MMC devices do not send a full MTC frame
+ at the end of a locate, instead sending only an MMC
+ locate command. This causes the current position
+ of an MTC slave to become out of date. Catch this.
+ */
+
+ MTC_Slave* mtcs = dynamic_cast<MTC_Slave*> (_slave);
+
+ if (mtcs != 0) {
+ // cerr << "Locate *with* MTC slave\n";
+ mtcs->handle_locate (mmc_tc);
+ } else {
+ // cerr << "Locate without MTC slave\n";
+ request_locate (target_frame, false);
+ }
+}
+
+void
+Session::mmc_shuttle (MIDI::MachineControl &mmc, float speed, bool forw)
+{
+ cerr << "MMC shuttle, speed = " << speed << endl;
+
+ if (!mmc_control) {
+ return;
+ }
+
+ if (shuttle_speed_threshold >= 0 && speed > shuttle_speed_threshold) {
+ speed *= shuttle_speed_factor;
+ }
+
+ cerr << "requested MMC control speed = " << speed << endl;
+
+ if (forw) {
+ request_transport_speed (speed);
+ } else {
+ request_transport_speed (-speed);
+ }
+}
+
+void
+Session::mmc_record_enable (MIDI::MachineControl &mmc, size_t trk, bool enabled)
+{
+ if (mmc_control) {
+
+ /* don't take route or diskstream lock: if using dynamic punch,
+ this could cause a dropout. XXX is that really OK?
+ or should we queue a rec-enable request?
+ */
+
+ size_t n;
+ RouteList::iterator i;
+
+ for (n = 0, i = routes.begin(); i != routes.end(); ++i) {
+ AudioTrack *at;
+
+ if ((at = dynamic_cast<AudioTrack*>(*i)) != 0) {
+ if (n++ == trk) {
+ at->set_record_enable (enabled, &mmc);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+Session::send_full_time_code_in_another_thread ()
+{
+ send_time_code_in_another_thread (true);
+}
+
+void
+Session::send_midi_time_code_in_another_thread ()
+{
+ send_time_code_in_another_thread (false);
+}
+
+void
+Session::send_time_code_in_another_thread (bool full)
+{
+ jack_nframes_t two_smpte_frames_duration;
+ jack_nframes_t quarter_frame_duration;
+
+ /* Duration of two smpte frames */
+ two_smpte_frames_duration = ((long) _frames_per_smpte_frame) << 1;
+
+ /* Duration of one quarter frame */
+ quarter_frame_duration = ((long) _frames_per_smpte_frame) >> 2;
+
+ if (_transport_frame < (outbound_mtc_smpte_frame + (next_quarter_frame_to_send * quarter_frame_duration)))
+ {
+ /* There is no work to do.
+ We throttle this here so that we don't overload
+ the transport thread with requests.
+ */
+ return;
+ }
+
+ MIDIRequest* request = new MIDIRequest;
+
+ if (full) {
+ request->type = MIDIRequest::SendFullMTC;
+ } else {
+ request->type = MIDIRequest::SendMTC;
+ }
+
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+}
+
+void
+Session::change_midi_ports ()
+{
+ MIDIRequest* request = new MIDIRequest;
+
+ request->type = MIDIRequest::PortChange;
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+}
+
+int
+Session::send_full_time_code ()
+
+{
+ MIDI::byte msg[10];
+ SMPTE_Time smpte;
+
+ if (_mtc_port == 0 || !send_mtc) {
+ return 0;
+ }
+
+ // Get smpte time for this transport frame
+ sample_to_smpte(_transport_frame, smpte, true /* use_offset */, false /* no subframes */);
+
+ // Check for negative smpte time and prepare for quarter frame transmission
+ if (smpte.negative) {
+ // Negative mtc is not defined, so sync slave to smpte zero.
+ // When _transport_frame gets there we will start transmitting quarter frames
+ smpte.hours = 0;
+ smpte.minutes = 0;
+ smpte.seconds = 0;
+ smpte.frames = 0;
+ smpte.subframes = 0;
+ smpte.negative = false;
+ smpte_to_sample( smpte, outbound_mtc_smpte_frame, true, false );
+ transmitting_smpte_time = smpte;
+ } else {
+ transmitting_smpte_time = smpte;
+ outbound_mtc_smpte_frame = _transport_frame;
+ if (((mtc_smpte_bits >> 5) != MIDI::MTC_25_FPS) && (transmitting_smpte_time.frames % 2)) {
+ // start MTC quarter frame transmission on an even frame
+ smpte_increment( transmitting_smpte_time );
+ outbound_mtc_smpte_frame += (jack_nframes_t) _frames_per_smpte_frame;
+ }
+ }
+
+ next_quarter_frame_to_send = 0;
+
+ // Sync slave to the same smpte time as we are on (except if negative, see above)
+ msg[0] = 0xf0;
+ msg[1] = 0x7f;
+ msg[2] = 0x7f;
+ msg[3] = 0x1;
+ msg[4] = 0x1;
+ msg[9] = 0xf7;
+
+ msg[5] = mtc_smpte_bits | smpte.hours;
+ msg[6] = smpte.minutes;
+ msg[7] = smpte.seconds;
+ msg[8] = smpte.frames;
+
+ {
+ LockMonitor lm (midi_lock, __LINE__, __FILE__);
+
+ if (_mtc_port->midimsg (msg, sizeof (msg))) {
+ error << _("Session: could not send full MIDI time code") << endmsg;
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::send_midi_time_code ()
+{
+ if (_mtc_port == 0 || !send_mtc || transmitting_smpte_time.negative || (next_quarter_frame_to_send < 0) ) {
+ return 0;
+ }
+
+ jack_nframes_t two_smpte_frames_duration;
+ jack_nframes_t quarter_frame_duration;
+
+ /* Duration of two smpte frames */
+ two_smpte_frames_duration = ((long) _frames_per_smpte_frame) << 1;
+
+ /* Duration of one quarter frame */
+ quarter_frame_duration = ((long) _frames_per_smpte_frame) >> 2;
+
+ while (_transport_frame >= (outbound_mtc_smpte_frame + (next_quarter_frame_to_send * quarter_frame_duration))) {
+
+ // Send quarter frames up to current time
+ {
+ LockMonitor lm (midi_lock, __LINE__, __FILE__);
+
+ switch(next_quarter_frame_to_send) {
+ case 0:
+ mtc_msg[1] = 0x00 | (transmitting_smpte_time.frames & 0xf);
+ break;
+ case 1:
+ mtc_msg[1] = 0x10 | ((transmitting_smpte_time.frames & 0xf0) >> 4);
+ break;
+ case 2:
+ mtc_msg[1] = 0x20 | (transmitting_smpte_time.seconds & 0xf);
+ break;
+ case 3:
+ mtc_msg[1] = 0x30 | ((transmitting_smpte_time.seconds & 0xf0) >> 4);
+ break;
+ case 4:
+ mtc_msg[1] = 0x40 | (transmitting_smpte_time.minutes & 0xf);
+ break;
+ case 5:
+ mtc_msg[1] = 0x50 | ((transmitting_smpte_time.minutes & 0xf0) >> 4);
+ break;
+ case 6:
+ mtc_msg[1] = 0x60 | ((mtc_smpte_bits|transmitting_smpte_time.hours) & 0xf);
+ break;
+ case 7:
+ mtc_msg[1] = 0x70 | (((mtc_smpte_bits|transmitting_smpte_time.hours) & 0xf0) >> 4);
+ break;
+ }
+
+ if (_mtc_port->midimsg (mtc_msg, 2)) {
+ error << compose(_("Session: cannot send quarter-frame MTC message (%1)"), strerror (errno))
+ << endmsg;
+
+ return -1;
+ }
+
+ // cout << "smpte = " << transmitting_smpte_time.hours << ":" << transmitting_smpte_time.minutes << ":" << transmitting_smpte_time.seconds << ":" << transmitting_smpte_time.frames << ", qfm = " << next_quarter_frame_to_send << endl;
+
+ // Increment quarter frame counter
+ next_quarter_frame_to_send++;
+
+ if (next_quarter_frame_to_send >= 8) {
+ // Wrap quarter frame counter
+ next_quarter_frame_to_send = 0;
+ // Increment smpte time twice
+ smpte_increment( transmitting_smpte_time );
+ smpte_increment( transmitting_smpte_time );
+ // Re-calculate timing of first quarter frame
+ smpte_to_sample( transmitting_smpte_time, outbound_mtc_smpte_frame, true /* use_offset */, false );
+ }
+ }
+ }
+ return 0;
+}
+
+/***********************************************************************
+ OUTBOUND MMC STUFF
+**********************************************************************/
+
+void
+Session::send_mmc_in_another_thread (MIDI::MachineControl::Command cmd, jack_nframes_t target_frame)
+{
+ MIDIRequest* request;
+
+ if (_mtc_port == 0 || !send_mmc) {
+ return;
+ }
+
+ request = new MIDIRequest;
+ request->type = MIDIRequest::SendMMC;
+ request->mmc_cmd = cmd;
+ request->locate_frame = target_frame;
+
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+}
+
+void
+Session::deliver_mmc (MIDI::MachineControl::Command cmd, jack_nframes_t where)
+{
+ using namespace MIDI;
+ int nbytes = 4;
+ SMPTE_Time smpte;
+
+ if (_mmc_port == 0 || !send_mmc) {
+ return;
+ }
+
+ mmc_buffer[nbytes++] = cmd;
+
+ // cerr << "delivering MMC, cmd = " << hex << (int) cmd << dec << endl;
+
+ switch (cmd) {
+ case MachineControl::cmdLocate:
+ smpte_time_subframes (where, smpte);
+
+ mmc_buffer[nbytes++] = 0x6; // byte count
+ mmc_buffer[nbytes++] = 0x1; // "TARGET" subcommand
+ mmc_buffer[nbytes++] = smpte.hours;
+ mmc_buffer[nbytes++] = smpte.minutes;
+ mmc_buffer[nbytes++] = smpte.seconds;
+ mmc_buffer[nbytes++] = smpte.frames;
+ mmc_buffer[nbytes++] = smpte.subframes;
+ break;
+
+ case MachineControl::cmdStop:
+ break;
+
+ case MachineControl::cmdPlay:
+ /* always convert Play into Deferred Play */
+ mmc_buffer[4] = MachineControl::cmdDeferredPlay;
+ break;
+
+ case MachineControl::cmdDeferredPlay:
+ break;
+
+ case MachineControl::cmdRecordStrobe:
+ break;
+
+ case MachineControl::cmdRecordExit:
+ break;
+
+ case MachineControl::cmdRecordPause:
+ break;
+
+ default:
+ nbytes = 0;
+ };
+
+ if (nbytes) {
+
+ mmc_buffer[nbytes++] = 0xf7; // terminate SysEx/MMC message
+
+ LockMonitor lm (midi_lock, __LINE__, __FILE__);
+
+ if (_mmc_port->write (mmc_buffer, nbytes) != nbytes) {
+ error << compose(_("MMC: cannot send command %1%2%3"), &hex, cmd, &dec) << endmsg;
+ }
+ }
+}
+
+bool
+Session::mmc_step_timeout ()
+{
+ struct timeval now;
+ struct timeval diff;
+ double diff_usecs;
+ gettimeofday (&now, 0);
+
+ timersub (&now, &last_mmc_step, &diff);
+ diff_usecs = diff.tv_sec * 1000000 + diff.tv_usec;
+
+ if (diff_usecs > 1000000.0 || fabs (_transport_speed) < 0.0000001) {
+ /* too long or too slow, stop transport */
+ request_transport_speed (0.0);
+ step_queued = false;
+ return false;
+ }
+
+ if (diff_usecs < 250000.0) {
+ /* too short, just keep going */
+ return true;
+ }
+
+ /* slow it down */
+
+ request_transport_speed (_transport_speed * 0.75);
+ return true;
+}
+
+
+void
+Session::send_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t ch, MIDI::EventTwoBytes data)
+{
+ // in another thread, really
+
+ MIDIRequest* request = new MIDIRequest;
+
+ request->type = MIDIRequest::SendMessage;
+ request->port = port;
+ request->ev = ev;
+ request->chan = ch;
+ request->data = data;
+
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+}
+
+void
+Session::deliver_midi (MIDI::Port * port, MIDI::byte* buf, int32_t bufsize)
+{
+ // in another thread, really
+
+ MIDIRequest* request = new MIDIRequest;
+
+ request->type = MIDIRequest::Deliver;
+ request->port = port;
+ request->buf = buf;
+ request->size = bufsize;
+
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+}
+
+void
+Session::deliver_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t ch, MIDI::EventTwoBytes data)
+{
+ if (port == 0 || ev == MIDI::none) {
+ return;
+ }
+
+ midi_msg[0] = (ev & 0xF0) | (ch & 0xF);
+ midi_msg[1] = data.controller_number;
+ midi_msg[2] = data.value;
+
+ port->write (midi_msg, 3);
+}
+
+void
+Session::deliver_data (MIDI::Port * port, MIDI::byte* buf, int32_t size)
+{
+ if (port) {
+ port->write (buf, size);
+ }
+
+ /* this is part of the semantics of the Deliver request */
+
+ delete [] buf;
+}
+
+/*---------------------------------------------------------------------------
+ MIDI THREAD
+ ---------------------------------------------------------------------------*/
+
+int
+Session::start_midi_thread ()
+{
+ if (pipe (midi_request_pipe)) {
+ error << compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (midi_request_pipe[0], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on " "signal read pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (midi_request_pipe[1], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on " "signal write pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (pthread_create_and_store ("transport", &midi_thread, 0, _midi_thread_work, this)) {
+ error << _("Session: could not create transport thread") << endmsg;
+ return -1;
+ }
+
+ // pthread_detach (midi_thread);
+
+ return 0;
+}
+
+void
+Session::terminate_midi_thread ()
+{
+ MIDIRequest* request = new MIDIRequest;
+ void* status;
+
+ request->type = MIDIRequest::Quit;
+
+ midi_requests.write (&request, 1);
+ poke_midi_thread ();
+
+ pthread_join (midi_thread, &status);
+}
+
+void
+Session::poke_midi_thread ()
+{
+ char c;
+
+ if (write (midi_request_pipe[1], &c, 1) != 1) {
+ error << compose(_("cannot send signal to midi thread! (%1)"), strerror (errno)) << endmsg;
+ }
+}
+
+void *
+Session::_midi_thread_work (void* arg)
+{
+ pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0);
+ pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
+
+ ((Session *) arg)->midi_thread_work ();
+ return 0;
+}
+
+void
+Session::midi_thread_work ()
+{
+ MIDIRequest* request;
+ struct pollfd pfd[4];
+ int nfds = 0;
+ int timeout;
+ int fds_ready;
+ struct sched_param rtparam;
+ int x;
+ bool restart;
+ vector<MIDI::Port*> ports;
+
+ PBD::ThreadCreated (pthread_self(), X_("MIDI"));
+
+ memset (&rtparam, 0, sizeof (rtparam));
+ rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
+
+ if ((x = pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam)) != 0) {
+ // do we care? not particularly.
+ }
+
+ /* set up the port vector; 4 is the largest possible size for now */
+
+ ports.push_back (0);
+ ports.push_back (0);
+ ports.push_back (0);
+ ports.push_back (0);
+
+ while (1) {
+
+ nfds = 0;
+
+ pfd[nfds].fd = midi_request_pipe[0];
+ pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
+ nfds++;
+
+ /* if we are using MMC control, we obviously have to listen
+ on the appropriate port.
+ */
+
+ if (mmc_control && _mmc_port && _mmc_port->selectable() >= 0) {
+ pfd[nfds].fd = _mmc_port->selectable();
+ pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
+ ports[nfds] = _mmc_port;
+ nfds++;
+ }
+
+ /* if MTC is being handled on a different port from MMC
+ or we are not handling MMC at all, poll
+ the relevant port.
+ */
+
+ if (_mtc_port && (_mtc_port != _mmc_port || !mmc_control) && _mtc_port->selectable() >= 0) {
+ pfd[nfds].fd = _mtc_port->selectable();
+ pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
+ ports[nfds] = _mtc_port;
+ nfds++;
+ }
+
+ if (_midi_port && (_midi_port != _mmc_port || !mmc_control) && (_midi_port != _mtc_port) && _midi_port->selectable() >= 0) {
+ pfd[nfds].fd = _midi_port->selectable();
+ pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
+ ports[nfds] = _midi_port;
+ nfds++;
+ }
+
+ if (!midi_timeouts.empty()) {
+ timeout = 100; /* 10msecs */
+ } else {
+ timeout = -1; /* if there is no data, we don't care */
+ }
+
+ again:
+ // cerr << "MIDI poll on " << nfds << " for " << timeout << endl;
+ if (poll (pfd, nfds, timeout) < 0) {
+ if (errno == EINTR) {
+ /* gdb at work, perhaps */
+ goto again;
+ }
+
+ error << compose(_("MIDI thread poll failed (%1)"), strerror (errno)) << endmsg;
+
+ break;
+ }
+ // cerr << "MIDI thread wakes at " << get_cycles () << endl;
+
+ fds_ready = 0;
+ restart = false;
+
+ /* check the transport request pipe */
+
+ if (pfd[0].revents & ~POLLIN) {
+ error << _("Error on transport thread request pipe") << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & POLLIN) {
+
+ char foo[16];
+
+ // cerr << "MIDI request FIFO ready\n";
+ fds_ready++;
+
+ /* empty the pipe of all current requests */
+
+ while (1) {
+ size_t nread = read (midi_request_pipe[0], &foo, sizeof (foo));
+
+ if (nread > 0) {
+ if ((size_t) nread < sizeof (foo)) {
+ break;
+ } else {
+ continue;
+ }
+ } else if (nread == 0) {
+ break;
+ } else if (errno == EAGAIN) {
+ break;
+ } else {
+ fatal << _("Error reading from transport request pipe") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+
+ while (midi_requests.read (&request, 1) == 1) {
+
+ switch (request->type) {
+
+ case MIDIRequest::SendFullMTC:
+ // cerr << "send full MTC\n";
+ send_full_time_code ();
+ // cerr << "... done\n";
+ break;
+
+ case MIDIRequest::SendMTC:
+ // cerr << "send qtr MTC\n";
+ send_midi_time_code ();
+ // cerr << "... done\n";
+ break;
+
+ case MIDIRequest::SendMMC:
+ // cerr << "send MMC\n";
+ deliver_mmc (request->mmc_cmd, request->locate_frame);
+ // cerr << "... done\n";
+ break;
+
+ case MIDIRequest::SendMessage:
+ // cerr << "send Message\n";
+ deliver_midi_message (request->port, request->ev, request->chan, request->data);
+ // cerr << "... done\n";
+ break;
+
+ case MIDIRequest::Deliver:
+ // cerr << "deliver\n";
+ deliver_data (_midi_port, request->buf, request->size);
+ // cerr << "... done\n";
+ break;
+
+ case MIDIRequest::PortChange:
+ /* restart poll with new ports */
+ // cerr << "rebind\n";
+ restart = true;
+ break;
+
+ case MIDIRequest::Quit:
+ delete request;
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ break;
+
+ default:
+ break;
+ }
+
+
+ delete request;
+ }
+
+ }
+
+ if (restart) {
+ continue;
+ }
+
+ /* now read the rest of the ports */
+
+ for (int p = 1; p < nfds; ++p) {
+ if ((pfd[p].revents & ~POLLIN)) {
+ // error << compose(_("Transport: error polling MIDI port %1 (revents =%2%3%4"), p, &hex, pfd[p].revents, &dec) << endmsg;
+ break;
+ }
+
+ if (pfd[p].revents & POLLIN) {
+ fds_ready++;
+ midi_read (ports[p]);
+ }
+ }
+
+ /* timeout driven */
+
+ if (fds_ready < 2 && timeout != -1) {
+
+ for (MidiTimeoutList::iterator i = midi_timeouts.begin(); i != midi_timeouts.end(); ) {
+
+ MidiTimeoutList::iterator tmp;
+ tmp = i;
+ ++tmp;
+
+ if (!(*i)) {
+ midi_timeouts.erase (i);
+ }
+
+ i = tmp;
+ }
+ }
+ }
+}
+
+bool
+Session::get_mmc_control () const
+{
+ return mmc_control;
+}
+bool
+Session::get_midi_feedback () const
+{
+ /* since this a "write" function we have to check the port as well
+ as the control toggle.
+ */
+ return _midi_port && midi_feedback;
+}
+bool
+Session::get_midi_control () const
+{
+ return midi_control;
+}
diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc
new file mode 100644
index 0000000000..835d4ccdc2
--- /dev/null
+++ b/libs/ardour/session_process.cc
@@ -0,0 +1,816 @@
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <cerrno>
+#include <algorithm>
+#include <unistd.h>
+
+#include <ardour/timestamps.h>
+
+#include <pbd/error.h>
+#include <pbd/atomic.h>
+#include <pbd/lockmonitor.h>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/audioengine.h>
+#include <ardour/slave.h>
+#include <ardour/auditioner.h>
+#include <ardour/cycles.h>
+#include <ardour/cycle_timer.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+//using namespace sigc;
+using namespace std;
+
+void
+Session::process (jack_nframes_t nframes)
+{
+ if (synced_to_jack() && waiting_to_start) {
+ if ( _engine.transport_state() == AudioEngine::TransportRolling) {
+ actually_start_transport ();
+ }
+ }
+
+ if (non_realtime_work_pending()) {
+ if (atomic_read (&butler_should_do_transport_work) == 0) {
+ post_transport ();
+ }
+ }
+
+ (this->*process_function) (nframes);
+}
+
+void
+Session::prepare_diskstreams ()
+{
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->prepare ();
+ }
+}
+
+int
+Session::no_roll (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ jack_nframes_t end_frame = _transport_frame + nframes;
+ int ret = 0;
+ bool declick = get_transport_declick_required();
+
+ if (_click_io) {
+ _click_io->silence (nframes, offset);
+ }
+
+ /* XXX we're supposed to have the route_lock while doing this.
+ this is really bad ...
+ */
+
+ if (atomic_read (&processing_prohibited)) {
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->silence (nframes, offset);
+ }
+ return 0;
+ }
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ if ((*i)->hidden()) {
+ continue;
+ }
+
+ (*i)->set_pending_declick (declick);
+
+ if ((*i)->no_roll (nframes, _transport_frame, end_frame, offset, non_realtime_work_pending(),
+ actively_recording(), declick)) {
+ error << compose(_("Session: error in no roll for %1"), (*i)->name()) << endmsg;
+ ret = -1;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int
+Session::process_routes (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ bool record_active;
+ int declick = get_transport_declick_required();
+ bool rec_monitors = get_rec_monitors_input();
+
+ if (transport_sub_state & StopPendingCapture) {
+ /* force a declick out */
+ declick = -1;
+ }
+
+ record_active = actively_recording(); // || (get_record_enabled() && get_punch_in());
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ int ret;
+
+ if ((*i)->hidden()) {
+ continue;
+ }
+
+ (*i)->set_pending_declick (declick);
+
+ if ((ret = (*i)->roll (nframes, _transport_frame, _transport_frame + nframes, offset, declick, record_active, rec_monitors)) < 0) {
+
+ /* we have to do this here. Route::roll() for an AudioTrack will have called DiskStream::process(),
+ and the DS will expect DiskStream::commit() to be called. but we're aborting from that
+ call path, so make sure we release any outstanding locks here before we return failure.
+ */
+
+ for (DiskStreamList::iterator ids = diskstreams.begin(); ids != diskstreams.end(); ++ids) {
+ (*ids)->recover ();
+ }
+
+ stop_transport ();
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::silent_process_routes (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ bool record_active = actively_recording();
+ int declick = get_transport_declick_required();
+ bool rec_monitors = get_rec_monitors_input();
+
+ if (transport_sub_state & StopPendingCapture) {
+ /* force a declick out */
+ declick = -1;
+ }
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+
+ int ret;
+
+ if ((*i)->hidden()) {
+ continue;
+ }
+
+ if ((ret = (*i)->silent_roll (nframes, _transport_frame, _transport_frame + nframes, offset, record_active, rec_monitors)) < 0) {
+
+ /* we have to do this here. Route::roll() for an AudioTrack will have called DiskStream::process(),
+ and the DS will expect DiskStream::commit() to be called. but we're aborting from that
+ call path, so make sure we release any outstanding locks here before we return failure.
+ */
+
+ for (DiskStreamList::iterator ids = diskstreams.begin(); ids != diskstreams.end(); ++ids) {
+ (*ids)->recover ();
+ }
+
+ stop_transport ();
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::commit_diskstreams (jack_nframes_t nframes, bool &needs_butler)
+{
+ int dret;
+ float pworst = 1.0f;
+ float cworst = 1.0f;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+
+ if ((*i)->hidden()) {
+ continue;
+ }
+
+ /* force all diskstreams not handled by a Route to call do their stuff.
+ */
+
+ if ((dret = (*i)->process (_transport_frame, nframes, 0, actively_recording(), get_rec_monitors_input())) == 0) {
+ if ((*i)->commit (nframes)) {
+ needs_butler = true;
+ }
+
+ } else if (dret < 0) {
+ (*i)->recover();
+ }
+
+ pworst = min (pworst, (*i)->playback_buffer_load());
+ cworst = min (cworst, (*i)->capture_buffer_load());
+ }
+
+ uint32_t pmin = atomic_read (&_playback_load);
+ uint32_t pminold = atomic_read (&_playback_load_min);
+ uint32_t cmin = atomic_read (&_capture_load);
+ uint32_t cminold = atomic_read (&_capture_load_min);
+
+ atomic_set (&_playback_load, (uint32_t) floor (pworst * 100.0f));
+ atomic_set (&_capture_load, (uint32_t) floor (cworst * 100.0f));
+ atomic_set (&_playback_load_min, min (pmin, pminold));
+ atomic_set (&_capture_load_min, min (cmin, cminold));
+
+ if (actively_recording()) {
+ set_dirty();
+ }
+}
+
+void
+Session::process_with_events (jack_nframes_t nframes)
+{
+ Event* ev;
+ jack_nframes_t this_nframes;
+ jack_nframes_t end_frame;
+ jack_nframes_t offset;
+ bool session_needs_butler = false;
+ jack_nframes_t stop_limit;
+ long frames_moved;
+
+ if (auditioner) {
+ auditioner->silence (nframes, 0);
+ }
+
+ while (pending_events.read (&ev, 1) == 1) {
+ merge_event (ev);
+ }
+
+ /* if we are not in the middle of a state change,
+ and there are immediate events queued up,
+ process them.
+ */
+
+ while (!non_realtime_work_pending() && !immediate_events.empty()) {
+ Event *ev = immediate_events.front ();
+ immediate_events.pop_front ();
+ process_event (ev);
+ }
+
+ if (!process_can_proceed()) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ if (events.empty() || next_event == events.end()) {
+ process_without_events (nframes);
+ return;
+ }
+
+ end_frame = _transport_frame + nframes;
+
+ {
+ TentativeLockMonitor rm (route_lock, __LINE__, __FILE__);
+ Event* this_event;
+ Events::iterator the_next_one;
+
+ if (!rm.locked() || (post_transport_work & (PostTransportLocate|PostTransportStop))) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ if (!_exporting && _slave) {
+ if (!follow_slave (nframes, 0)) {
+ return;
+ }
+ }
+
+ if (_transport_speed == 0) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ if (actively_recording()) {
+ stop_limit = max_frames;
+ } else {
+
+ if (Config->get_stop_at_session_end()) {
+ stop_limit = current_end_frame();
+ } else {
+ stop_limit = max_frames;
+ }
+ }
+
+ if (maybe_stop (stop_limit)) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ this_event = *next_event;
+ the_next_one = next_event;
+ ++the_next_one;
+
+ offset = 0;
+
+ while (nframes) {
+
+ if (this_event == 0 || this_event->action_frame > end_frame || this_event->action_frame < _transport_frame) {
+
+ this_nframes = nframes;
+
+ } else {
+
+ /* compute nframes to next event */
+
+ if (this_event->action_frame < end_frame) {
+ this_nframes = nframes - (end_frame - this_event->action_frame);
+ } else {
+ this_nframes = nframes;
+ }
+ }
+
+ if (this_nframes) {
+
+ click (_transport_frame, nframes, offset);
+
+ /* now process frames between now and the first event in this block */
+ prepare_diskstreams ();
+
+ if (process_routes (this_nframes, offset)) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ commit_diskstreams (this_nframes, session_needs_butler);
+
+ nframes -= this_nframes;
+ offset += this_nframes;
+
+ frames_moved = (jack_nframes_t) floor (_transport_speed * this_nframes);
+
+ if (frames_moved < 0) {
+ decrement_transport_position (-frames_moved);
+ } else {
+ increment_transport_position (frames_moved);
+ }
+
+ maybe_stop (stop_limit);
+ check_declick_out ();
+ }
+
+ /* now handle this event and all others scheduled for the same time */
+
+ while (this_event && this_event->action_frame == _transport_frame) {
+ process_event (this_event);
+
+ if (the_next_one == events.end()) {
+ this_event = 0;
+ } else {
+ this_event = *the_next_one;
+ ++the_next_one;
+ }
+ }
+
+ /* if an event left our state changing, do the right thing */
+
+ if (non_realtime_work_pending()) {
+ no_roll (nframes, offset);
+ break;
+ }
+
+ /* this is necessary to handle the case of seamless looping */
+ /* not sure if it will work in conjuction with varispeed */
+ end_frame = _transport_frame + nframes;
+
+ }
+
+ set_next_event ();
+
+ } /* implicit release of route lock */
+
+
+ if (session_needs_butler) {
+ summon_butler ();
+ }
+
+ if (!_engine.freewheeling() && send_mtc) {
+ send_midi_time_code_in_another_thread ();
+ }
+
+ return;
+}
+
+void
+Session::reset_slave_state ()
+{
+ average_slave_delta = 1800;
+ delta_accumulator_cnt = 0;
+ have_first_delta_accumulator = false;
+ slave_state = Stopped;
+}
+
+bool
+Session::follow_slave (jack_nframes_t nframes, jack_nframes_t offset)
+{
+ float slave_speed;
+ jack_nframes_t slave_transport_frame;
+ jack_nframes_t this_delta;
+ int dir;
+ bool starting;
+
+ if (!_slave->ok()) {
+ stop_transport ();
+ set_slave_source (None, 0);
+ goto noroll;
+ }
+
+ _slave->speed_and_position (slave_speed, slave_transport_frame);
+
+ if (!_slave->locked()) {
+ goto noroll;
+ }
+
+ if (slave_transport_frame > _transport_frame) {
+ this_delta = slave_transport_frame - _transport_frame;
+ dir = 1;
+ } else {
+ this_delta = _transport_frame - slave_transport_frame;
+ dir = -1;
+ }
+
+ if ((starting = _slave->starting())) {
+ slave_speed = 0.0f;
+ }
+
+#if 0
+ cerr << "delta = " << (int) (dir * this_delta)
+ << " speed = " << slave_speed
+ << " ts = " << _transport_speed
+ << " M@"<< slave_transport_frame << " S@" << _transport_frame
+ << " avgdelta = " << average_slave_delta
+ << endl;
+#endif
+
+ if (Config->get_timecode_source_is_synced()) {
+
+ /* if the TC source is synced, then we assume that its
+ speed is binary: 0.0 or 1.0
+ */
+
+ if (slave_speed != 0.0f) {
+ slave_speed = 1.0f;
+ }
+
+ } else {
+
+ /* TC source is able to drift relative to us (slave)
+ so we need to keep track of the drift and adjust
+ our speed to remain locked.
+ */
+
+ if (delta_accumulator_cnt >= delta_accumulator_size) {
+ have_first_delta_accumulator = true;
+ delta_accumulator_cnt = 0;
+ }
+
+ if (delta_accumulator_cnt != 0 || this_delta < _current_frame_rate) {
+ delta_accumulator[delta_accumulator_cnt++] = dir*this_delta;
+ }
+
+ if (have_first_delta_accumulator) {
+ average_slave_delta = 0;
+ for (int i = 0; i < delta_accumulator_size; ++i) {
+ average_slave_delta += delta_accumulator[i];
+ }
+ average_slave_delta /= delta_accumulator_size;
+ if (average_slave_delta < 0) {
+ average_dir = -1;
+ average_slave_delta = -average_slave_delta;
+ } else {
+ average_dir = 1;
+ }
+ // cerr << "avgdelta = " << average_slave_delta*average_dir << endl;
+ }
+ }
+
+ if (slave_speed != 0.0f) {
+
+ /* slave is running */
+
+ switch (slave_state) {
+ case Stopped:
+ if (_slave->requires_seekahead()) {
+ slave_wait_end = slave_transport_frame + _current_frame_rate;
+ locate (slave_wait_end, false, false);
+ slave_state = Waiting;
+ starting = true;
+
+ } else {
+
+ slave_state = Running;
+
+ Location* al = _locations.auto_loop_location();
+
+ if (al && auto_loop && (slave_transport_frame < al->start() || slave_transport_frame > al->end())) {
+ // cancel looping
+ request_auto_loop(false);
+ }
+
+ if (slave_transport_frame != _transport_frame) {
+ locate (slave_transport_frame, false, false);
+ }
+ }
+ break;
+
+ case Waiting:
+ break;
+
+ default:
+ break;
+
+ }
+
+ if (slave_state == Waiting) {
+
+ // cerr << "waiting at " << slave_transport_frame << endl;
+
+ if (slave_transport_frame >= slave_wait_end) {
+ // cerr << "\tstart at " << _transport_frame << endl;
+ slave_state = Running;
+
+ bool ok = true;
+ jack_nframes_t frame_delta = slave_transport_frame - _transport_frame;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->can_internal_playback_seek (frame_delta)) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok) {
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->internal_playback_seek (frame_delta);
+ }
+ _transport_frame += frame_delta;
+
+ } else {
+ // cerr << "cannot micro-seek\n";
+ /* XXX what? */
+ }
+
+ memset (delta_accumulator, 0, sizeof (jack_nframes_t) * delta_accumulator_size);
+ average_slave_delta = 0;
+ this_delta = 0;
+ }
+ }
+
+ if (slave_state == Running && _transport_speed == 0.0f) {
+
+ // cerr << "slave starts transport\n";
+
+ start_transport ();
+ }
+
+ } else {
+
+ /* slave has stopped */
+
+ if (_transport_speed != 0.0f) {
+
+ // cerr << "slave stops transport: " << slave_speed << " frame: " << slave_transport_frame
+ // << " tf = " << _transport_frame
+ // << endl;
+
+ if (_slave_type == JACK) {
+ last_stop_frame = _transport_frame;
+ }
+
+ stop_transport();
+ }
+
+ if (slave_transport_frame != _transport_frame) {
+ // cerr << "slave stopped, move to " << slave_transport_frame << endl;
+ force_locate (slave_transport_frame, false);
+ }
+
+ slave_state = Stopped;
+ }
+
+ if (slave_state == Running && !Config->get_timecode_source_is_synced()) {
+
+
+ if (_transport_speed != 0.0f) {
+
+ /*
+ note that average_dir is +1 or -1
+ */
+
+ const float adjust_seconds = 1.0f;
+ float delta;
+
+ //if (average_slave_delta == 0) {
+ delta = this_delta;
+ delta *= dir;
+// } else {
+// delta = average_slave_delta;
+// delta *= average_dir;
+// }
+
+ float adjusted_speed = slave_speed +
+ (delta / (adjust_seconds * _current_frame_rate));
+
+ // cerr << "adjust using " << delta
+ // << " towards " << adjusted_speed
+ // << " ratio = " << adjusted_speed / slave_speed
+ // << " current = " << _transport_speed
+ // << " slave @ " << slave_speed
+ // << endl;
+
+ request_transport_speed (adjusted_speed);
+
+#if 1
+ if ((jack_nframes_t) average_slave_delta > _slave->resolution()) {
+ // cerr << "not locked\n";
+ goto silent_motion;
+ }
+#endif
+ }
+ }
+
+ if (!starting && !non_realtime_work_pending()) {
+ /* speed is set, we're locked, and good to go */
+ return true;
+ }
+
+ silent_motion:
+
+ if (slave_speed && _transport_speed) {
+
+ /* something isn't right, but we should move with the master
+ for now.
+ */
+
+ bool need_butler;
+
+ prepare_diskstreams ();
+ silent_process_routes (nframes, offset);
+ commit_diskstreams (nframes, need_butler);
+
+ if (need_butler) {
+ summon_butler ();
+ }
+
+ jack_nframes_t frames_moved = (long) floor (_transport_speed * nframes);
+
+ if (frames_moved < 0) {
+ decrement_transport_position (-frames_moved);
+ } else {
+ increment_transport_position (frames_moved);
+ }
+
+ jack_nframes_t stop_limit;
+
+ if (actively_recording()) {
+ stop_limit = max_frames;
+ } else {
+ if (Config->get_stop_at_session_end()) {
+ stop_limit = current_end_frame();
+ } else {
+ stop_limit = max_frames;
+ }
+ }
+
+ maybe_stop (stop_limit);
+ }
+
+ noroll:
+ /* don't move at all */
+ no_roll (nframes, 0);
+ return false;
+}
+
+void
+Session::process_without_events (jack_nframes_t nframes)
+{
+ bool session_needs_butler = false;
+ jack_nframes_t stop_limit;
+ long frames_moved;
+
+ {
+ TentativeLockMonitor rm (route_lock, __LINE__, __FILE__);
+
+ if (!rm.locked() || (post_transport_work & (PostTransportLocate|PostTransportStop))) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ if (!_exporting && _slave) {
+ if (!follow_slave (nframes, 0)) {
+ return;
+ }
+ }
+
+ if (_transport_speed == 0) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ if (actively_recording()) {
+ stop_limit = max_frames;
+ } else {
+ if (Config->get_stop_at_session_end()) {
+ stop_limit = current_end_frame();
+ } else {
+ stop_limit = max_frames;
+ }
+ }
+
+ if (maybe_stop (stop_limit)) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ click (_transport_frame, nframes, 0);
+
+ prepare_diskstreams ();
+
+ frames_moved = (long) floor (_transport_speed * nframes);
+
+ if (process_routes (nframes, 0)) {
+ no_roll (nframes, 0);
+ return;
+ }
+
+ commit_diskstreams (nframes, session_needs_butler);
+
+ if (frames_moved < 0) {
+ decrement_transport_position (-frames_moved);
+ } else {
+ increment_transport_position (frames_moved);
+ }
+
+ maybe_stop (stop_limit);
+ check_declick_out ();
+
+ } /* implicit release of route lock */
+
+ if (session_needs_butler) {
+ summon_butler ();
+ }
+
+ if (!_engine.freewheeling() && send_mtc) {
+ send_midi_time_code_in_another_thread ();
+ }
+
+ return;
+}
+
+void
+Session::process_audition (jack_nframes_t nframes)
+{
+ TentativeLockMonitor rm (route_lock, __LINE__, __FILE__);
+ Event* ev;
+
+ if (rm.locked()) {
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->silence (nframes, 0);
+ }
+ }
+ }
+
+ if (auditioner->play_audition (nframes) > 0) {
+ summon_butler ();
+ }
+
+ while (pending_events.read (&ev, 1) == 1) {
+ merge_event (ev);
+ }
+
+ /* if we are not in the middle of a state change,
+ and there are immediate events queued up,
+ process them.
+ */
+
+ while (!non_realtime_work_pending() && !immediate_events.empty()) {
+ Event *ev = immediate_events.front ();
+ immediate_events.pop_front ();
+ process_event (ev);
+ }
+
+ if (!auditioner->active()) {
+ process_function = &Session::process_with_events;
+ }
+}
+
diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc
new file mode 100644
index 0000000000..bda826508c
--- /dev/null
+++ b/libs/ardour/session_state.cc
@@ -0,0 +1,3121 @@
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+#include <cerrno>
+
+#include <sigc++/bind.h>
+
+#include <cstdio> /* snprintf(3) ... grrr */
+#include <cmath>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <climits>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <dirent.h>
+
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#else
+#include <sys/mount.h>
+#include <sys/param.h>
+#endif
+
+#include <midi++/mmc.h>
+#include <midi++/port.h>
+#include <pbd/error.h>
+#include <pbd/dirname.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pathscanner.h>
+#include <pbd/pthread_utils.h>
+#include <pbd/basename.h>
+#include <pbd/strsplit.h>
+
+#include <ardour/audioengine.h>
+#include <ardour/configuration.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/utils.h>
+#include <ardour/audioplaylist.h>
+#include <ardour/source.h>
+#include <ardour/filesource.h>
+#include <ardour/sndfilesource.h>
+#include <ardour/sndfile_helpers.h>
+#include <ardour/auditioner.h>
+#include <ardour/export.h>
+#include <ardour/redirect.h>
+#include <ardour/send.h>
+#include <ardour/insert.h>
+#include <ardour/connection.h>
+#include <ardour/slave.h>
+#include <ardour/tempo.h>
+#include <ardour/audio_track.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/utils.h>
+#include <ardour/named_selection.h>
+#include <ardour/version.h>
+#include <ardour/location.h>
+#include <ardour/audioregion.h>
+#include <ardour/crossfade.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+void
+Session::first_stage_init (string fullpath, string snapshot_name)
+{
+ if (fullpath.length() == 0) {
+ throw failed_constructor();
+ }
+
+ char buf[PATH_MAX+1];
+ if (!realpath(fullpath.c_str(), buf) && (errno != ENOENT)) {
+ error << compose(_("Could not use path %1 (%s)"), buf, strerror(errno)) << endmsg;
+ throw failed_constructor();
+ }
+ _path = string(buf);
+
+ if (_path[_path.length()-1] != '/') {
+ _path += '/';
+ }
+
+ /* these two are just provisional settings. set_state()
+ will likely override them.
+ */
+
+ _name = _current_snapshot_name = snapshot_name;
+ setup_raid_path (_path);
+
+ _current_frame_rate = _engine.frame_rate ();
+ _tempo_map = new TempoMap (_current_frame_rate);
+ _tempo_map->StateChanged.connect (mem_fun (*this, &Session::tempo_map_changed));
+
+ atomic_set (&processing_prohibited, 0);
+ send_cnt = 0;
+ insert_cnt = 0;
+ _transport_speed = 0;
+ _last_transport_speed = 0;
+ transport_sub_state = 0;
+ _transport_frame = 0;
+ last_stop_frame = 0;
+ end_location = new Location (0, 0, _("end"), Location::Flags ((Location::IsMark|Location::IsEnd)));
+ _end_location_is_free = true;
+ atomic_set (&_record_status, Disabled);
+ auto_play = false;
+ punch_in = false;
+ punch_out = false;
+ auto_loop = false;
+ seamless_loop = false;
+ loop_changing = false;
+ auto_input = true;
+ crossfades_active = false;
+ all_safe = false;
+ auto_return = false;
+ _last_roll_location = 0;
+ _last_record_location = 0;
+ pending_locate_frame = 0;
+ pending_locate_roll = false;
+ pending_locate_flush = false;
+ dstream_buffer_size = 0;
+ state_tree = 0;
+ state_was_pending = false;
+ set_next_event ();
+ outbound_mtc_smpte_frame = 0;
+ next_quarter_frame_to_send = -1;
+ current_block_size = 0;
+ _solo_latched = true;
+ _solo_model = InverseMute;
+ solo_update_disabled = false;
+ currently_soloing = false;
+ _worst_output_latency = 0;
+ _worst_input_latency = 0;
+ _worst_track_latency = 0;
+ _state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading);
+ _slave = 0;
+ _slave_type = None;
+ butler_mixdown_buffer = 0;
+ butler_gain_buffer = 0;
+ auditioner = 0;
+ mmc_control = false;
+ midi_feedback = false;
+ midi_control = true;
+ mmc = 0;
+ post_transport_work = PostTransportWork (0);
+ atomic_set (&butler_should_do_transport_work, 0);
+ atomic_set (&butler_active, 0);
+ atomic_set (&_playback_load, 100);
+ atomic_set (&_capture_load, 100);
+ atomic_set (&_playback_load_min, 100);
+ atomic_set (&_capture_load_min, 100);
+ pending_audition_region = 0;
+ _edit_mode = Slide;
+ pending_edit_mode = _edit_mode;
+ _play_range = false;
+ align_style = ExistingMaterial;
+ _control_out = 0;
+ _master_out = 0;
+ input_auto_connect = AutoConnectOption (0);
+ output_auto_connect = AutoConnectOption (0);
+ _have_captured = false;
+ waiting_to_start = false;
+ _exporting = false;
+ _gain_automation_buffer = 0;
+ _pan_automation_buffer = 0;
+ _npan_buffers = 0;
+ pending_abort = false;
+ layer_model = MoveAddHigher;
+ xfade_model = ShortCrossfade;
+
+ /* default short fade = 15ms */
+
+ Crossfade::set_short_xfade_length ((jack_nframes_t) floor ((15.0 * frame_rate()) / 1000.0));
+
+ last_mmc_step.tv_sec = 0;
+ last_mmc_step.tv_usec = 0;
+ step_speed = 0.0;
+
+ preroll.type = AnyTime::Frames;
+ preroll.frames = 0;
+ postroll.type = AnyTime::Frames;
+ postroll.frames = 0;
+
+ /* click sounds are unset by default, which causes us to internal
+ waveforms for clicks.
+ */
+
+ _click_io = 0;
+ _clicking = false;
+ click_requested = false;
+ click_data = 0;
+ click_emphasis_data = 0;
+ click_length = 0;
+ click_emphasis_length = 0;
+
+ process_function = &Session::process_with_events;
+
+ last_smpte_when = 0;
+ _smpte_offset = 0;
+ _smpte_offset_negative = true;
+ last_smpte_valid = false;
+
+ last_rr_session_dir = session_dirs.begin();
+ refresh_disk_space ();
+
+ // set_default_fade (0.2, 5.0); /* steepness, millisecs */
+
+ /* default configuration */
+
+ recording_plugins = false;
+ over_length_short = 2;
+ over_length_long = 10;
+ send_midi_timecode = false;
+ send_midi_machine_control = false;
+ shuttle_speed_factor = 1.0;
+ shuttle_speed_threshold = 5;
+ rf_speed = 2.0;
+ _meter_hold = 100; // XXX unknown units: number of calls to meter::set()
+ _meter_falloff = 1.5f; // XXX unknown units: refresh_rate
+ max_level = 0;
+ min_level = 0;
+
+ /* slave stuff */
+
+ average_slave_delta = 1800;
+ have_first_delta_accumulator = false;
+ delta_accumulator_cnt = 0;
+ slave_state = Stopped;
+
+ /* default SMPTE type is 30 FPS, non-drop */
+
+ set_smpte_type (30.0, false);
+
+ _engine.GraphReordered.connect (mem_fun (*this, &Session::graph_reordered));
+
+ /* These are all static "per-class" signals */
+
+ Region::CheckNewRegion.connect (mem_fun (*this, &Session::add_region));
+ Source::SourceCreated.connect (mem_fun (*this, &Session::add_source));
+ Playlist::PlaylistCreated.connect (mem_fun (*this, &Session::add_playlist));
+ Redirect::RedirectCreated.connect (mem_fun (*this, &Session::add_redirect));
+ DiskStream::DiskStreamCreated.connect (mem_fun (*this, &Session::add_diskstream));
+ NamedSelection::NamedSelectionCreated.connect (mem_fun (*this, &Session::add_named_selection));
+
+ IO::MoreOutputs.connect (mem_fun (*this, &Session::ensure_passthru_buffers));
+
+ /* stop IO objects from doing stuff until we're ready for them */
+
+ IO::disable_panners ();
+ IO::disable_ports ();
+ IO::disable_connecting ();
+}
+
+int
+Session::second_stage_init (bool new_session)
+{
+ SndFileSource::set_peak_dir (peak_dir());
+
+ if (!new_session) {
+ if (load_state (_current_snapshot_name)) {
+ return -1;
+ }
+ remove_empty_sounds ();
+ }
+
+ if (start_butler_thread()) {
+ return -1;
+ }
+
+ if (start_midi_thread ()) {
+ return -1;
+ }
+
+ if (init_feedback ()) {
+ return -1;
+ }
+
+ if (state_tree) {
+ if (set_state (*state_tree->root())) {
+ return -1;
+ }
+ }
+
+ /* we can't save till after ::when_engine_running() is called,
+ because otherwise we save state with no connections made.
+ therefore, we reset _state_of_the_state because ::set_state()
+ will have cleared it.
+
+ we also have to include Loading so that any events that get
+ generated between here and the end of ::when_engine_running()
+ will be processed directly rather than queued.
+ */
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading);
+
+ // set_auto_input (true);
+ _locations.changed.connect (mem_fun (this, &Session::locations_changed));
+ _locations.added.connect (mem_fun (this, &Session::locations_added));
+ setup_click_sounds (0);
+ setup_midi_control ();
+
+ /* Pay attention ... */
+
+ _engine.Halted.connect (mem_fun (*this, &Session::engine_halted));
+ _engine.Xrun.connect (mem_fun (*this, &Session::xrun_recovery));
+
+ if (_engine.running()) {
+ when_engine_running();
+ } else {
+ first_time_running = _engine.Running.connect (mem_fun (*this, &Session::when_engine_running));
+ }
+
+ send_full_time_code ();
+ _engine.transport_locate (0);
+ deliver_mmc (MIDI::MachineControl::cmdMmcReset, 0);
+ deliver_mmc (MIDI::MachineControl::cmdLocate, 0);
+ send_all_midi_feedback();
+
+ if (new_session) {
+ _end_location_is_free = true;
+ } else {
+ _end_location_is_free = false;
+ }
+
+ return 0;
+}
+
+string
+Session::raid_path () const
+{
+ string path;
+
+ for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ path += (*i).path;
+ path += ':';
+ }
+
+ return path.substr (0, path.length() - 1); // drop final colon
+}
+
+void
+Session::set_raid_path (string path)
+{
+ /* public-access to setup_raid_path() */
+
+ setup_raid_path (path);
+}
+
+void
+Session::setup_raid_path (string path)
+{
+ string::size_type colon;
+ string remaining;
+ space_and_path sp;
+ string fspath;
+ string::size_type len = path.length();
+ int colons;
+
+ colons = 0;
+
+ if (path.length() == 0) {
+ return;
+ }
+
+ session_dirs.clear ();
+
+ for (string::size_type n = 0; n < len; ++n) {
+ if (path[n] == ':') {
+ colons++;
+ }
+ }
+
+ if (colons == 0) {
+
+ /* no multiple search path, just one directory (common case) */
+
+ sp.path = path;
+ sp.blocks = 0;
+ session_dirs.push_back (sp);
+
+ FileSource::set_search_path (path + sound_dir_name);
+
+ return;
+ }
+
+ remaining = path;
+
+ while ((colon = remaining.find_first_of (':')) != string::npos) {
+
+ sp.blocks = 0;
+ sp.path = remaining.substr (0, colon);
+
+ fspath += sp.path;
+ if (fspath[fspath.length()-1] != '/') {
+ fspath += '/';
+ }
+ fspath += sound_dir_name;
+ fspath += ':';
+
+ session_dirs.push_back (sp);
+
+ remaining = remaining.substr (colon+1);
+ }
+
+ if (remaining.length()) {
+
+ sp.blocks = 0;
+ sp.path = remaining;
+
+ fspath += sp.path;
+ if (fspath[fspath.length()-1] != '/') {
+ fspath += '/';
+ }
+ fspath += sound_dir_name;
+
+ session_dirs.push_back (sp);
+ }
+
+ /* set the FileSource search path */
+
+ FileSource::set_search_path (fspath);
+
+ /* reset the round-robin soundfile path thingie */
+
+ last_rr_session_dir = session_dirs.begin();
+}
+
+int
+Session::create (bool& new_session, string* mix_template, jack_nframes_t initial_length)
+{
+ string dir;
+
+ if (mkdir (_path.c_str(), 0755) < 0) {
+ if (errno == EEXIST) {
+ new_session = false;
+ } else {
+ error << compose(_("Session: cannot create session dir \"%1\" (%2)"), _path, strerror (errno)) << endmsg;
+ return -1;
+ }
+ } else {
+ new_session = true;
+ }
+
+ dir = peak_dir ();
+
+ if (mkdir (dir.c_str(), 0755) < 0) {
+ if (errno != EEXIST) {
+ error << compose(_("Session: cannot create session peakfile dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+ dir = sound_dir ();
+
+ if (mkdir (dir.c_str(), 0755) < 0) {
+ if (errno != EEXIST) {
+ error << compose(_("Session: cannot create session sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+ dir = dead_sound_dir ();
+
+ if (mkdir (dir.c_str(), 0755) < 0) {
+ if (errno != EEXIST) {
+ error << compose(_("Session: cannot create session dead sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+ dir = automation_dir ();
+
+ if (mkdir (dir.c_str(), 0755) < 0) {
+ if (errno != EEXIST) {
+ error << compose(_("Session: cannot create session automation dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+
+ /* check new_session so we don't overwrite an existing one */
+
+ if (mix_template) {
+ if (new_session){
+
+ string lookfor = "^";
+ lookfor += *mix_template;
+ lookfor += _template_suffix;
+ lookfor += '$';
+
+ PathScanner scanner;
+ string tpath = template_path();
+ string in_path;
+ string out_path;
+
+ vector<string*>* result= scanner (tpath, lookfor, false, true);
+
+ if (result == 0) {
+ error << compose (_("Could not find a template called %1 in %2"), *mix_template, tpath)
+ << endmsg;
+ *mix_template = "";
+ }
+
+ if (result->size() == 0) {
+ delete result;
+ error << compose (_("Could not find a template called %1 in %2"), *mix_template, tpath)
+ << endmsg;
+ *mix_template = "";
+ }
+
+ in_path = *(result->front());
+
+ ifstream in(in_path.c_str());
+
+ if (in){
+ string out_path = _path;
+ out_path += _name;
+ out_path += _statefile_suffix;
+
+ ofstream out(out_path.c_str());
+
+ if (out){
+ out << in.rdbuf();
+
+ // okay, session is set up. Treat like normal saved
+ // session from now on.
+
+ new_session = false;
+ return 0;
+
+ } else {
+ error << compose (_("Could not open %1 for writing mix template"), out_path)
+ << endmsg;
+ return -1;
+ }
+
+ } else {
+ error << compose (_("Could not open mix template %1 for reading"), in_path)
+ << endmsg;
+ return -1;
+ }
+
+
+ } else {
+ warning << _("Session already exists. Not overwriting") << endmsg;
+ return -1;
+ }
+ }
+
+ if (new_session) {
+
+ /* set an initial end point */
+
+ end_location->set_end (initial_length);
+ _locations.add (end_location);
+
+ _state_of_the_state = Clean;
+
+ if (save_state (_current_snapshot_name)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::load_diskstreams (const XMLNode& node)
+{
+ XMLNodeList clist;
+ XMLNodeConstIterator citer;
+
+ clist = node.children();
+
+ for (citer = clist.begin(); citer != clist.end(); ++citer) {
+
+ DiskStream* dstream;
+
+ try {
+ dstream = new DiskStream (*this, **citer);
+ /* added automatically by DiskStreamCreated handler */
+ }
+
+ catch (failed_constructor& err) {
+ error << _("Session: could not load diskstream via XML state") << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::remove_pending_capture_state ()
+{
+ string xml_path;
+
+ xml_path = _path;
+ xml_path += _current_snapshot_name;
+ xml_path += _pending_suffix;
+
+ unlink (xml_path.c_str());
+}
+
+int
+Session::save_state (string snapshot_name, bool pending)
+{
+ XMLTree tree;
+ string xml_path;
+ string bak_path;
+
+ if (_state_of_the_state & CannotSave) {
+ return 1;
+ }
+
+ tree.set_root (&get_state());
+
+ if (snapshot_name.empty()) {
+ snapshot_name = _current_snapshot_name;
+ }
+
+ if (!pending) {
+
+ xml_path = _path;
+ xml_path += snapshot_name;
+ xml_path += _statefile_suffix;
+ bak_path = xml_path;
+ bak_path += ".bak";
+
+ // Make backup of state file
+
+ if ((access (xml_path.c_str(), F_OK) == 0) &&
+ (rename(xml_path.c_str(), bak_path.c_str()))) {
+ error << _("could not backup old state file, current state not saved.") << endmsg;
+ return -1;
+ }
+
+ } else {
+
+ xml_path = _path;
+ xml_path += snapshot_name;
+ xml_path += _pending_suffix;
+
+ }
+
+ if (!tree.write (xml_path)) {
+ error << compose (_("state could not be saved to %1"), xml_path) << endmsg;
+
+ /* don't leave a corrupt file lying around if it is
+ possible to fix.
+ */
+
+ if (unlink (xml_path.c_str())) {
+ error << compose (_("could not remove corrupt state file %1"), xml_path) << endmsg;
+ } else {
+ if (!pending) {
+ if (rename (bak_path.c_str(), xml_path.c_str())) {
+ error << compose (_("could not restore state file from backup %1"), bak_path) << endmsg;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ if (!pending) {
+
+ bool was_dirty = dirty();
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+
+ if (was_dirty) {
+ DirtyChanged (); /* EMIT SIGNAL */
+ }
+
+ StateSaved (snapshot_name); /* EMIT SIGNAL */
+ }
+
+ return 0;
+}
+
+int
+Session::restore_state (string snapshot_name)
+{
+ if (load_state (snapshot_name) == 0) {
+ set_state (*state_tree->root());
+ }
+
+ return 0;
+}
+
+int
+Session::load_state (string snapshot_name)
+{
+ if (state_tree) {
+ delete state_tree;
+ state_tree = 0;
+ }
+
+ string xmlpath;
+
+ state_was_pending = false;
+
+ /* check for leftover pending state from a crashed capture attempt */
+
+ xmlpath = _path;
+ xmlpath += snapshot_name;
+ xmlpath += _pending_suffix;
+
+ if (!access (xmlpath.c_str(), F_OK)) {
+
+ /* there is pending state from a crashed capture attempt */
+
+ if (AskAboutPendingState()) {
+ state_was_pending = true;
+ }
+ }
+
+ if (!state_was_pending) {
+
+ xmlpath = _path;
+ xmlpath += snapshot_name;
+ xmlpath += _statefile_suffix;
+ }
+
+ if (access (xmlpath.c_str(), F_OK)) {
+ error << compose(_("%1: session state information file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg;
+ return 1;
+ }
+
+ state_tree = new XMLTree;
+
+ set_dirty();
+
+ if (state_tree->read (xmlpath)) {
+ return 0;
+ } else {
+ error << compose(_("Could not understand ardour file %1"), xmlpath) << endmsg;
+ }
+
+ delete state_tree;
+ state_tree = 0;
+ return -1;
+}
+
+int
+Session::load_options (const XMLNode& node)
+{
+ XMLNode* child;
+ XMLProperty* prop;
+ bool have_fade_msecs = false;
+ bool have_fade_steepness = false;
+ float fade_msecs = 0;
+ float fade_steepness = 0;
+ SlaveSource slave_src = None;
+ int x;
+ LocaleGuard lg (X_("POSIX"));
+
+ if ((child = find_named_node (node, "input-auto-connect")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ sscanf (prop->value().c_str(), "%x", &x);
+ input_auto_connect = AutoConnectOption (x);
+ }
+ }
+
+ if ((child = find_named_node (node, "output-auto-connect")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ sscanf (prop->value().c_str(), "%x", &x);
+ output_auto_connect = AutoConnectOption (x);
+ }
+ }
+
+ if ((child = find_named_node (node, "slave")) != 0) {
+ if ((prop = child->property ("type")) != 0) {
+ if (prop->value() == "none") {
+ slave_src = None;
+ } else if (prop->value() == "mtc") {
+ slave_src = MTC;
+ } else if (prop->value() == "jack") {
+ slave_src = JACK;
+ }
+ set_slave_source (slave_src, 0);
+ }
+ }
+
+ /* we cannot set edit mode if we are loading a session,
+ because it might destroy the playlist's positioning
+ */
+
+ if ((child = find_named_node (node, "edit-mode")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ if (prop->value() == "slide") {
+ pending_edit_mode = Slide;
+ } else if (prop->value() == "splice") {
+ pending_edit_mode = Splice;
+ }
+ }
+ }
+
+ if ((child = find_named_node (node, "send-midi-timecode")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ bool x = (prop->value() == "yes");
+ send_mtc = !x; /* force change in value */
+ set_send_mtc (x);
+ }
+ }
+ if ((child = find_named_node (node, "send-midi-machine-control")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ bool x = (prop->value() == "yes");
+ send_mmc = !x; /* force change in value */
+ set_send_mmc (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "max-level")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ max_level = atoi (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "min-level")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ min_level = atoi (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "meter-hold")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ _meter_hold = atof (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "meter-falloff")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ _meter_falloff = atof (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "long-over-length")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ over_length_long = atoi (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "short-over-length")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ over_length_short = atoi (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "shuttle-speed-factor")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ shuttle_speed_factor = atof (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "shuttle-speed-threshold")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ shuttle_speed_threshold = atof (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "rf-speed")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ rf_speed = atof (prop->value().c_str());
+ }
+ }
+ if ((child = find_named_node (node, "smpte-frames-per-second")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_smpte_type( atof (prop->value().c_str()), smpte_drop_frames );
+ }
+ }
+ if ((child = find_named_node (node, "smpte-drop-frames")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_smpte_type( smpte_frames_per_second, (prop->value() == "yes") );
+ }
+ }
+ if ((child = find_named_node (node, "smpte-offset")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_smpte_offset( atoi (prop->value().c_str()) );
+ }
+ }
+ if ((child = find_named_node (node, "smpte-offset-negative")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_smpte_offset_negative( (prop->value() == "yes") );
+ }
+ }
+ if ((child = find_named_node (node, "click-sound")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ click_sound = prop->value();
+ }
+ }
+ if ((child = find_named_node (node, "click-emphasis-sound")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ click_emphasis_sound = prop->value();
+ }
+ }
+
+ if ((child = find_named_node (node, "solo-model")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ if (prop->value() == "SoloBus")
+ _solo_model = SoloBus;
+ else
+ _solo_model = InverseMute;
+ }
+ }
+
+ /* BOOLEAN OPTIONS */
+
+ if ((child = find_named_node (node, "auto-play")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_auto_play (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "auto-input")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_auto_input (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "seamless-loop")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_seamless_loop (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "punch-in")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_punch_in (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "punch-out")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_punch_out (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "auto-return")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_auto_return (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "send-mtc")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_send_mtc (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "mmc-control")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_mmc_control (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "midi-control")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_midi_control (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "midi-feedback")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_midi_feedback (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "recording-plugins")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_recording_plugins (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "crossfades-active")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_crossfades_active (prop->value() == "yes");
+ }
+ }
+ if ((child = find_named_node (node, "audible-click")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ set_clicking (prop->value() == "yes");
+ }
+ }
+
+ if ((child = find_named_node (node, "align-style")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ if (prop->value() == "capture") {
+ set_align_style (CaptureTime);
+ } else {
+ set_align_style (ExistingMaterial);
+ }
+ }
+ }
+
+ if ((child = find_named_node (node, "layer-model")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ if (prop->value() == X_("LaterHigher")) {
+ set_layer_model (LaterHigher);
+ } else if (prop->value() == X_("AddHigher")) {
+ set_layer_model (AddHigher);
+ } else {
+ set_layer_model (MoveAddHigher);
+ }
+ }
+ }
+
+ if ((child = find_named_node (node, "xfade-model")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ if (prop->value() == X_("Short")) {
+ set_xfade_model (ShortCrossfade);
+ } else {
+ set_xfade_model (FullCrossfade);
+ }
+ }
+ }
+
+ if ((child = find_named_node (node, "short-xfade-length")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ /* value is stored as a fractional seconds */
+ float secs = atof (prop->value().c_str());
+ Crossfade::set_short_xfade_length ((jack_nframes_t) floor (secs * frame_rate()));
+ }
+ }
+
+ if ((child = find_named_node (node, "full-xfades-unmuted")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ crossfades_active = (prop->value() == "yes");
+ }
+ }
+
+ /* TIED OPTIONS */
+
+ if ((child = find_named_node (node, "default-fade-steepness")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ fade_steepness = atof (prop->value().c_str());
+ have_fade_steepness = true;
+ }
+ }
+ if ((child = find_named_node (node, "default-fade-msec")) != 0) {
+ if ((prop = child->property ("val")) != 0) {
+ fade_msecs = atof (prop->value().c_str());
+ have_fade_msecs = true;
+ }
+ }
+
+ if (have_fade_steepness || have_fade_msecs) {
+ // set_default_fade (fade_steepness, fade_msecs);
+ }
+
+ return 0;
+}
+
+XMLNode&
+Session::get_options () const
+{
+ XMLNode* opthead;
+ XMLNode* child;
+ char buf[32];
+ LocaleGuard lg (X_("POSIX"));
+
+ opthead = new XMLNode ("Options");
+
+ SlaveSource src = slave_source ();
+ string src_string;
+ switch (src) {
+ case None:
+ src_string = "none";
+ break;
+ case MTC:
+ src_string = "mtc";
+ break;
+ case JACK:
+ src_string = "jack";
+ break;
+ }
+ child = opthead->add_child ("slave");
+ child->add_property ("type", src_string);
+
+ child = opthead->add_child ("send-midi-timecode");
+ child->add_property ("val", send_midi_timecode?"yes":"no");
+
+ child = opthead->add_child ("send-midi-machine-control");
+ child->add_property ("val", send_midi_machine_control?"yes":"no");
+
+ snprintf (buf, sizeof(buf)-1, "%x", (int) input_auto_connect);
+ child = opthead->add_child ("input-auto-connect");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%x", (int) output_auto_connect);
+ child = opthead->add_child ("output-auto-connect");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%d", max_level);
+ child = opthead->add_child ("max-level");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%d", min_level);
+ child = opthead->add_child ("min-level");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%f", _meter_hold);
+ child = opthead->add_child ("meter-hold");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%f", _meter_falloff);
+ child = opthead->add_child ("meter-falloff");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%u", over_length_long);
+ child = opthead->add_child ("long-over-length");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%u", over_length_short);
+ child = opthead->add_child ("short-over-length");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%f", shuttle_speed_factor);
+ child = opthead->add_child ("shuttle-speed-factor");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%f", shuttle_speed_threshold);
+ child = opthead->add_child ("shuttle-speed-threshold");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%f", rf_speed);
+ child = opthead->add_child ("rf-speed");
+ child->add_property ("val", buf);
+
+ snprintf (buf, sizeof(buf)-1, "%.2f", smpte_frames_per_second);
+ child = opthead->add_child ("smpte-frames-per-second");
+ child->add_property ("val", buf);
+
+ child = opthead->add_child ("smpte-drop-frames");
+ child->add_property ("val", smpte_drop_frames ? "yes" : "no");
+
+ snprintf (buf, sizeof(buf)-1, "%u", smpte_offset ());
+ child = opthead->add_child ("smpte-offset");
+ child->add_property ("val", buf);
+
+ child = opthead->add_child ("smpte-offset-negative");
+ child->add_property ("val", smpte_offset_negative () ? "yes" : "no");
+
+ child = opthead->add_child ("edit-mode");
+ switch (_edit_mode) {
+ case Splice:
+ child->add_property ("val", "splice");
+ break;
+
+ case Slide:
+ child->add_property ("val", "slide");
+ break;
+ }
+
+ child = opthead->add_child ("auto-play");
+ child->add_property ("val", get_auto_play () ? "yes" : "no");
+ child = opthead->add_child ("auto-input");
+ child->add_property ("val", get_auto_input () ? "yes" : "no");
+ child = opthead->add_child ("seamless-loop");
+ child->add_property ("val", get_seamless_loop () ? "yes" : "no");
+ child = opthead->add_child ("punch-in");
+ child->add_property ("val", get_punch_in () ? "yes" : "no");
+ child = opthead->add_child ("punch-out");
+ child->add_property ("val", get_punch_out () ? "yes" : "no");
+ child = opthead->add_child ("all-safe");
+ child->add_property ("val", get_all_safe () ? "yes" : "no");
+ child = opthead->add_child ("auto-return");
+ child->add_property ("val", get_auto_return () ? "yes" : "no");
+ child = opthead->add_child ("mmc-control");
+ child->add_property ("val", get_mmc_control () ? "yes" : "no");
+ child = opthead->add_child ("midi-control");
+ child->add_property ("val", get_midi_control () ? "yes" : "no");
+ child = opthead->add_child ("midi-feedback");
+ child->add_property ("val", get_midi_feedback () ? "yes" : "no");
+ child = opthead->add_child ("recording-plugins");
+ child->add_property ("val", get_recording_plugins () ? "yes" : "no");
+ child = opthead->add_child ("auto-crossfade");
+ child->add_property ("val", get_crossfades_active () ? "yes" : "no");
+ child = opthead->add_child ("audible-click");
+ child->add_property ("val", get_clicking () ? "yes" : "no");
+
+ if (click_sound.length()) {
+ child = opthead->add_child ("click-sound");
+ child->add_property ("val", click_sound);
+ }
+
+ if (click_emphasis_sound.length()) {
+ child = opthead->add_child ("click-emphasis-sound");
+ child->add_property ("val", click_emphasis_sound);
+ }
+
+ child = opthead->add_child ("solo-model");
+ child->add_property ("val", _solo_model == SoloBus ? "SoloBus" : "InverseMute");
+
+ child = opthead->add_child ("align-style");
+ child->add_property ("val", (align_style == ExistingMaterial ? "existing" : "capture"));
+
+ child = opthead->add_child ("layer-model");
+ switch (layer_model) {
+ case LaterHigher:
+ child->add_property ("val", X_("LaterHigher"));
+ break;
+ case MoveAddHigher:
+ child->add_property ("val", X_("MoveAddHigher"));
+ break;
+ case AddHigher:
+ child->add_property ("val", X_("AddHigher"));
+ break;
+ }
+
+ child = opthead->add_child ("xfade-model");
+ switch (xfade_model) {
+ case FullCrossfade:
+ child->add_property ("val", X_("Full"));
+ break;
+ case ShortCrossfade:
+ child->add_property ("val", X_("Short"));
+ }
+
+ child = opthead->add_child ("short-xfade-length");
+ /* store as fractions of a second */
+ snprintf (buf, sizeof(buf)-1, "%f",
+ (float) Crossfade::short_xfade_length() / frame_rate());
+ child->add_property ("val", buf);
+
+ child = opthead->add_child ("full-xfades-unmuted");
+ child->add_property ("val", crossfades_active ? "yes" : "no");
+
+ return *opthead;
+}
+
+XMLNode&
+Session::get_state()
+{
+ return state(true);
+}
+
+XMLNode&
+Session::get_template()
+{
+ /* if we don't disable rec-enable, diskstreams
+ will believe they need to store their capture
+ sources in their state node.
+ */
+
+ disable_record ();
+
+ cerr << "STart get template\n";
+ return state(false);
+}
+
+XMLNode&
+Session::state(bool full_state)
+{
+ XMLNode* node = new XMLNode("Session");
+ XMLNode* child;
+
+ // store libardour version, just in case
+ char buf[16];
+ snprintf(buf, sizeof(buf)-1, "%d.%d.%d",
+ libardour_major_version, libardour_minor_version, libardour_micro_version);
+ node->add_property("version", string(buf));
+
+ /* store configuration settings */
+
+ if (full_state) {
+
+ /* store the name */
+ node->add_property ("name", _name);
+
+ if (session_dirs.size() > 1) {
+
+ string p;
+
+ vector<space_and_path>::iterator i = session_dirs.begin();
+ vector<space_and_path>::iterator next;
+
+ ++i; /* skip the first one */
+ next = i;
+ ++next;
+
+ while (i != session_dirs.end()) {
+
+ p += (*i).path;
+
+ if (next != session_dirs.end()) {
+ p += ':';
+ } else {
+ break;
+ }
+
+ ++next;
+ ++i;
+ }
+
+ child = node->add_child ("Path");
+ child->add_content (p);
+ }
+ }
+
+ node->add_child_nocopy (get_options());
+
+ child = node->add_child ("Sources");
+
+ if (full_state) {
+ LockMonitor sl (source_lock, __LINE__, __FILE__);
+
+ for (SourceList::iterator siter = sources.begin(); siter != sources.end(); ++siter) {
+
+ /* Don't save information about FileSources that are empty */
+
+ FileSource* fs;
+
+ if ((fs = dynamic_cast<FileSource*> ((*siter).second)) != 0) {
+ if (fs->length() == 0) {
+ continue;
+ }
+ }
+
+ child->add_child_nocopy ((*siter).second->get_state());
+ }
+ }
+
+ child = node->add_child ("Regions");
+
+ if (full_state) {
+ LockMonitor rl (region_lock, __LINE__, __FILE__);
+
+ for (AudioRegionList::const_iterator i = audio_regions.begin(); i != audio_regions.end(); ++i) {
+
+ /* only store regions not attached to playlists */
+
+ if ((*i).second->playlist() == 0) {
+ child->add_child_nocopy (i->second->state (true));
+ }
+ }
+ }
+
+ child = node->add_child ("DiskStreams");
+
+ {
+ LockMonitor dl (diskstream_lock, __LINE__, __FILE__);
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ child->add_child_nocopy ((*i)->get_state());
+ }
+ }
+ }
+
+ node->add_child_nocopy (_locations.get_state());
+
+ child = node->add_child ("Connections");
+ {
+ LockMonitor lm (connection_lock, __LINE__, __FILE__);
+ for (ConnectionList::iterator i = _connections.begin(); i != _connections.end(); ++i) {
+ if (!(*i)->system_dependent()) {
+ child->add_child_nocopy ((*i)->get_state());
+ }
+ }
+ }
+
+ child = node->add_child ("Routes");
+ {
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ RoutePublicOrderSorter cmp;
+ RouteList public_order(routes);
+ public_order.sort (cmp);
+
+ for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if (full_state) {
+ child->add_child_nocopy ((*i)->get_state());
+ } else {
+ child->add_child_nocopy ((*i)->get_template());
+ }
+ }
+ }
+ }
+
+
+ child = node->add_child ("EditGroups");
+ for (list<RouteGroup *>::iterator i = edit_groups.begin(); i != edit_groups.end(); ++i) {
+ child->add_child_nocopy ((*i)->get_state());
+ }
+
+ child = node->add_child ("MixGroups");
+ for (list<RouteGroup *>::iterator i = mix_groups.begin(); i != mix_groups.end(); ++i) {
+ child->add_child_nocopy ((*i)->get_state());
+ }
+
+ child = node->add_child ("Playlists");
+ for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if (!(*i)->empty()) {
+ if (full_state) {
+ child->add_child_nocopy ((*i)->get_state());
+ } else {
+ child->add_child_nocopy ((*i)->get_template());
+ }
+ }
+ }
+ }
+
+ child = node->add_child ("UnusedPlaylists");
+ for (PlaylistList::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if (!(*i)->empty()) {
+ if (full_state) {
+ child->add_child_nocopy ((*i)->get_state());
+ } else {
+ child->add_child_nocopy ((*i)->get_template());
+ }
+ }
+ }
+ }
+
+
+ if (_click_io) {
+ child = node->add_child ("Click");
+ child->add_child_nocopy (_click_io->state (full_state));
+ }
+
+ if (full_state) {
+ child = node->add_child ("NamedSelections");
+ for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) {
+ if (full_state) {
+ child->add_child_nocopy ((*i)->get_state());
+ }
+ }
+ }
+
+ node->add_child_nocopy (_tempo_map->get_state());
+
+ if (_extra_xml) {
+ node->add_child_copy (*_extra_xml);
+ }
+
+ return *node;
+}
+
+int
+Session::set_state (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNode* child;
+ const XMLProperty* prop;
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave);
+
+ if (node.name() != "Session"){
+ fatal << _("programming error: Session: incorrect XML node sent to set_state()") << endmsg;
+ return -1;
+ }
+
+ if ((prop = node.property ("name")) != 0) {
+ _name = prop->value ();
+ }
+
+ IO::disable_ports ();
+ IO::disable_connecting ();
+
+ /* Object loading order:
+
+ MIDI
+ Path
+ extra
+ Options
+ Sources
+ AudioRegions
+ DiskStreams
+ Connections
+ Locations
+ Routes
+ EditGroups
+ MixGroups
+ Click
+ */
+
+ if (use_config_midi_ports ()) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Path")) != 0) {
+ /* XXX this XML content stuff horrible API design */
+ string raid_path = _path + ':' + child->children().front()->content();
+ setup_raid_path (raid_path);
+ } else {
+ /* the path is already set */
+ }
+
+ if ((child = find_named_node (node, "extra")) != 0) {
+ _extra_xml = new XMLNode (*child);
+ }
+
+ if ((child = find_named_node (node, "Options")) == 0) {
+ error << _("Session: XML state has no options section") << endmsg;
+ return -1;
+ } else if (load_options (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Sources")) == 0) {
+ error << _("Session: XML state has no sources section") << endmsg;
+ return -1;
+ } else if (load_sources (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Regions")) == 0) {
+ error << _("Session: XML state has no Regions section") << endmsg;
+ return -1;
+ } else if (load_regions (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Playlists")) == 0) {
+ error << _("Session: XML state has no playlists section") << endmsg;
+ return -1;
+ } else if (load_playlists (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "UnusedPlaylists")) == 0) {
+ // this is OK
+ } else if (load_unused_playlists (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "NamedSelections")) != 0) {
+ if (load_named_selections (*child)) {
+ return -1;
+ }
+ }
+
+ if ((child = find_named_node (node, "DiskStreams")) == 0) {
+ error << _("Session: XML state has no diskstreams section") << endmsg;
+ return -1;
+ } else if (load_diskstreams (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Connections")) == 0) {
+ error << _("Session: XML state has no connections section") << endmsg;
+ return -1;
+ } else if (load_connections (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Locations")) == 0) {
+ error << _("Session: XML state has no locations section") << endmsg;
+ return -1;
+ } else if (_locations.set_state (*child)) {
+ return -1;
+ }
+
+ Location* location;
+
+ if ((location = _locations.auto_loop_location()) != 0) {
+ set_auto_loop_location (location);
+ }
+
+ if ((location = _locations.auto_punch_location()) != 0) {
+ set_auto_punch_location (location);
+ }
+
+ if ((location = _locations.end_location()) == 0) {
+ _locations.add (end_location);
+ } else {
+ end_location = location;
+ }
+
+ _locations.save_state (_("initial state"));
+
+ if ((child = find_named_node (node, "EditGroups")) == 0) {
+ error << _("Session: XML state has no edit groups section") << endmsg;
+ return -1;
+ } else if (load_edit_groups (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "MixGroups")) == 0) {
+ error << _("Session: XML state has no mix groups section") << endmsg;
+ return -1;
+ } else if (load_mix_groups (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "TempoMap")) == 0) {
+ error << _("Session: XML state has no Tempo Map section") << endmsg;
+ return -1;
+ } else if (_tempo_map->set_state (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Routes")) == 0) {
+ error << _("Session: XML state has no routes section") << endmsg;
+ return -1;
+ } else if (load_routes (*child)) {
+ return -1;
+ }
+
+ if ((child = find_named_node (node, "Click")) == 0) {
+ warning << _("Session: XML state has no click section") << endmsg;
+ } else if (_click_io) {
+ _click_io->set_state (*child);
+ }
+
+ /* OK, now we can set edit mode */
+
+ set_edit_mode (pending_edit_mode);
+
+ /* here beginneth the second phase ... */
+
+ StateReady (); /* EMIT SIGNAL */
+
+ _state_of_the_state = Clean;
+
+ if (state_was_pending) {
+ save_state (_current_snapshot_name);
+ remove_pending_capture_state ();
+ state_was_pending = false;
+ }
+
+ return 0;
+}
+
+int
+Session::load_routes (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ Route *route;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((route = XMLRouteFactory (**niter)) == 0) {
+ error << _("Session: cannot create Route from XML description.") << endmsg;
+ return -1;
+ }
+
+ add_route (route);
+ }
+
+ return 0;
+}
+
+Route *
+Session::XMLRouteFactory (const XMLNode& node)
+{
+ if (node.name() != "Route") {
+ return 0;
+ }
+
+ if (node.property ("diskstream") != 0 || node.property ("diskstream-id") != 0) {
+ return new AudioTrack (*this, node);
+ } else {
+ return new Route (*this, node);
+ }
+}
+
+int
+Session::load_regions (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ AudioRegion* region;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((region = XMLRegionFactory (**niter, false)) == 0) {
+ error << _("Session: cannot create Region from XML description.") << endmsg;
+ }
+ }
+
+ return 0;
+}
+
+AudioRegion *
+Session::XMLRegionFactory (const XMLNode& node, bool full)
+{
+ const XMLProperty* prop;
+ id_t s_id;
+ Source* source;
+ AudioRegion::SourceList sources;
+ uint32_t nchans = 1;
+ char buf[128];
+
+ if (node.name() != X_("Region")) {
+ return 0;
+ }
+
+ if ((prop = node.property (X_("channels"))) != 0) {
+ nchans = atoi (prop->value().c_str());
+ }
+
+
+ if ((prop = node.property (X_("source-0"))) == 0) {
+ if ((prop = node.property ("source")) == 0) {
+ error << _("Session: XMLNode describing a AudioRegion is incomplete (no source)") << endmsg;
+ return 0;
+ }
+ }
+
+ sscanf (prop->value().c_str(), "%llu", &s_id);
+
+ if ((source = get_source (s_id)) == 0) {
+ error << compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), s_id) << endmsg;
+ return 0;
+ }
+
+ sources.push_back(source);
+
+ /* pickup other channels */
+
+ for (uint32_t n=1; n < nchans; ++n) {
+ snprintf (buf, sizeof(buf), X_("source-%d"), n);
+ if ((prop = node.property (buf)) != 0) {
+ sscanf (prop->value().c_str(), "%llu", &s_id);
+
+ if ((source = get_source (s_id)) == 0) {
+ error << compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), s_id) << endmsg;
+ return 0;
+ }
+ sources.push_back(source);
+ }
+ }
+
+
+ try {
+ return new AudioRegion (sources, node);
+ }
+
+ catch (failed_constructor& err) {
+ return 0;
+ }
+}
+
+XMLNode&
+Session::get_sources_as_xml ()
+
+{
+ XMLNode* node = new XMLNode (X_("Sources"));
+ LockMonitor lm (source_lock, __LINE__, __FILE__);
+
+ for (SourceList::iterator i = sources.begin(); i != sources.end(); ++i) {
+ node->add_child_nocopy ((*i).second->get_state());
+ }
+
+ return *node;
+}
+
+string
+Session::path_from_region_name (string name, string identifier)
+{
+ char buf[PATH_MAX+1];
+ uint32_t n;
+ string dir = discover_best_sound_dir ();
+
+ for (n = 0; n < 999999; ++n) {
+ if (identifier.length()) {
+ snprintf (buf, sizeof(buf), "%s/%s%s%" PRIu32 ".wav", dir.c_str(), name.c_str(),
+ identifier.c_str(), n);
+ } else {
+ snprintf (buf, sizeof(buf), "%s/%s-%" PRIu32 ".wav", dir.c_str(), name.c_str(), n);
+ }
+ if (access (buf, F_OK) != 0) {
+ return buf;
+ }
+ }
+
+ return "";
+}
+
+
+int
+Session::load_sources (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ Source* source;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((source = XMLSourceFactory (**niter)) == 0) {
+ error << _("Session: cannot create Source from XML description.") << endmsg;
+ }
+ }
+
+ return 0;
+}
+
+Source *
+Session::XMLSourceFactory (const XMLNode& node)
+{
+ Source *src;
+
+ if (node.name() != "Source") {
+ return 0;
+ }
+
+ try {
+ src = new FileSource (node, frame_rate());
+ }
+
+ catch (failed_constructor& err) {
+
+ try {
+ src = new SndFileSource (node);
+ }
+
+ catch (failed_constructor& err) {
+ error << _("Found a sound file that cannot be used by Ardour. See the progammers.") << endmsg;
+ return 0;
+ }
+ }
+
+ return src;
+}
+
+int
+Session::save_template (string template_name)
+{
+ XMLTree tree;
+ string xml_path, bak_path, template_path;
+
+ if (_state_of_the_state & CannotSave) {
+ return -1;
+ }
+
+ DIR* dp;
+ string dir = template_dir();
+
+ if ((dp = opendir (dir.c_str()))) {
+ closedir (dp);
+ } else {
+ if (mkdir (dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)<0) {
+ error << compose(_("Could not create mix templates directory \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return -1;
+ }
+ }
+
+ tree.set_root (&get_template());
+
+ xml_path = dir;
+ xml_path += template_name;
+ xml_path += _template_suffix;
+
+ ifstream in(xml_path.c_str());
+
+ if (in) {
+ warning << compose(_("Template \"%1\" already exists - new version not created"), template_name) << endmsg;
+ return -1;
+ } else {
+ in.close();
+ }
+
+ if (!tree.write (xml_path)) {
+ error << _("mix template not saved") << endmsg;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+Session::rename_template (string old_name, string new_name)
+{
+ string old_path = template_dir() + old_name + _template_suffix;
+ string new_path = template_dir() + new_name + _template_suffix;
+
+ return rename (old_path.c_str(), new_path.c_str());
+}
+
+int
+Session::delete_template (string name)
+{
+ string template_path = template_dir();
+ template_path += name;
+ template_path += _template_suffix;
+
+ return remove (template_path.c_str());
+}
+
+void
+Session::refresh_disk_space ()
+{
+#if HAVE_SYS_VFS_H
+ struct statfs statfsbuf;
+ vector<space_and_path>::iterator i;
+ LockMonitor lm (space_lock, __LINE__, __FILE__);
+ double scale;
+
+ /* get freespace on every FS that is part of the session path */
+
+ _total_free_4k_blocks = 0;
+
+ for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ statfs ((*i).path.c_str(), &statfsbuf);
+
+ scale = statfsbuf.f_bsize/4096.0;
+
+ (*i).blocks = (uint32_t) floor (statfsbuf.f_bavail * scale);
+ _total_free_4k_blocks += (*i).blocks;
+ }
+#endif
+}
+
+int
+Session::ensure_sound_dir (string path, string& result)
+{
+ string dead;
+ string peak;
+
+ /* Ensure that the parent directory exists */
+
+ if (mkdir (path.c_str(), 0775)) {
+ if (errno != EEXIST) {
+ error << compose(_("cannot create session directory \"%1\"; ignored"), path) << endmsg;
+ return -1;
+ }
+ }
+
+ /* Ensure that the sounds directory exists */
+
+ result = path;
+ result += '/';
+ result += sound_dir_name;
+
+ if (mkdir (result.c_str(), 0775)) {
+ if (errno != EEXIST) {
+ error << compose(_("cannot create sounds directory \"%1\"; ignored"), result) << endmsg;
+ return -1;
+ }
+ }
+
+ dead = path;
+ dead += '/';
+ dead += dead_sound_dir_name;
+
+ if (mkdir (dead.c_str(), 0775)) {
+ if (errno != EEXIST) {
+ error << compose(_("cannot create dead sounds directory \"%1\"; ignored"), dead) << endmsg;
+ return -1;
+ }
+ }
+
+ peak = path;
+ peak += '/';
+ peak += peak_dir_name;
+
+ if (mkdir (peak.c_str(), 0775)) {
+ if (errno != EEXIST) {
+ error << compose(_("cannot create peak file directory \"%1\"; ignored"), peak) << endmsg;
+ return -1;
+ }
+ }
+
+ /* callers expect this to be terminated ... */
+
+ result += '/';
+ return 0;
+}
+
+string
+Session::discover_best_sound_dir ()
+{
+ vector<space_and_path>::iterator i;
+ string result;
+
+ /* handle common case without system calls */
+
+ if (session_dirs.size() == 1) {
+ return sound_dir();
+ }
+
+ /* OK, here's the algorithm we're following here:
+
+ We want to select which directory to use for
+ the next file source to be created. Ideally,
+ we'd like to use a round-robin process so as to
+ get maximum performance benefits from splitting
+ the files across multiple disks.
+
+ However, in situations without much diskspace, an
+ RR approach may end up filling up a filesystem
+ with new files while others still have space.
+ Its therefore important to pay some attention to
+ the freespace in the filesystem holding each
+ directory as well. However, if we did that by
+ itself, we'd keep creating new files in the file
+ system with the most space until it was as full
+ as all others, thus negating any performance
+ benefits of this RAID-1 like approach.
+
+ So, we use a user-configurable space threshold. If
+ there are at least 2 filesystems with more than this
+ much space available, we use RR selection between them.
+ If not, then we pick the filesystem with the most space.
+
+ This gets a good balance between the two
+ approaches.
+ */
+
+ refresh_disk_space ();
+
+ int free_enough = 0;
+
+ for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ if ((*i).blocks * 4096 >= Config->get_disk_choice_space_threshold()) {
+ free_enough++;
+ }
+ }
+
+ if (free_enough >= 2) {
+
+ bool found_it = false;
+
+ /* use RR selection process, ensuring that the one
+ picked works OK.
+ */
+
+ i = last_rr_session_dir;
+
+ do {
+ if (++i == session_dirs.end()) {
+ i = session_dirs.begin();
+ }
+
+ if ((*i).blocks * 4096 >= Config->get_disk_choice_space_threshold()) {
+ if (ensure_sound_dir ((*i).path, result) == 0) {
+ last_rr_session_dir = i;
+ found_it = true;
+ break;
+ }
+ }
+
+ } while (i != last_rr_session_dir);
+
+ if (!found_it) {
+ result = sound_dir();
+ }
+
+ } else {
+
+ /* pick FS with the most freespace (and that
+ seems to actually work ...)
+ */
+
+ vector<space_and_path> sorted;
+ space_and_path_ascending_cmp cmp;
+
+ sorted = session_dirs;
+ sort (sorted.begin(), sorted.end(), cmp);
+
+ for (i = sorted.begin(); i != sorted.end(); ++i) {
+ if (ensure_sound_dir ((*i).path, result) == 0) {
+ last_rr_session_dir = i;
+ break;
+ }
+ }
+
+ /* if the above fails, fall back to the most simplistic solution */
+
+ if (i == sorted.end()) {
+ return sound_dir();
+ }
+ }
+
+ return result;
+}
+
+int
+Session::load_playlists (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ Playlist *playlist;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((playlist = XMLPlaylistFactory (**niter)) == 0) {
+ error << _("Session: cannot create Playlist from XML description.") << endmsg;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::load_unused_playlists (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ Playlist *playlist;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((playlist = XMLPlaylistFactory (**niter)) == 0) {
+ error << _("Session: cannot create Playlist from XML description.") << endmsg;
+ continue;
+ }
+
+ // now manually untrack it
+
+ track_playlist (playlist, false);
+ }
+
+ return 0;
+}
+
+
+Playlist *
+Session::XMLPlaylistFactory (const XMLNode& node)
+{
+ try {
+ return new AudioPlaylist (*this, node);
+ }
+
+ catch (failed_constructor& err) {
+ return 0;
+ }
+}
+
+int
+Session::load_named_selections (const XMLNode& node)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ NamedSelection *ns;
+
+ nlist = node.children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ if ((ns = XMLNamedSelectionFactory (**niter)) == 0) {
+ error << _("Session: cannot create Named Selection from XML description.") << endmsg;
+ }
+ }
+
+ return 0;
+}
+
+NamedSelection *
+Session::XMLNamedSelectionFactory (const XMLNode& node)
+{
+ try {
+ return new NamedSelection (*this, node);
+ }
+
+ catch (failed_constructor& err) {
+ return 0;
+ }
+}
+
+string
+Session::dead_sound_dir () const
+{
+ string res = _path;
+ res += dead_sound_dir_name;
+ res += '/';
+ return res;
+}
+
+string
+Session::sound_dir () const
+{
+ string res = _path;
+ res += sound_dir_name;
+ res += '/';
+ return res;
+}
+
+string
+Session::peak_dir () const
+{
+ string res = _path;
+ res += peak_dir_name;
+ res += '/';
+ return res;
+}
+
+string
+Session::automation_dir () const
+{
+ string res = _path;
+ res += "automation/";
+ return res;
+}
+
+string
+Session::template_dir ()
+{
+ string path = Config->get_user_ardour_path();
+ path += "templates/";
+
+ return path;
+}
+
+string
+Session::template_path ()
+{
+ string path;
+
+ path += Config->get_user_ardour_path();
+ if (path[path.length()-1] != ':') {
+ path += ':';
+ }
+ path += Config->get_system_ardour_path();
+
+ vector<string> split_path;
+
+ split (path, split_path, ':');
+ path = "";
+
+ for (vector<string>::iterator i = split_path.begin(); i != split_path.end(); ++i) {
+ path += *i;
+ path += "templates/";
+
+ if (distance (i, split_path.end()) != 1) {
+ path += ':';
+ }
+ }
+
+ return path;
+}
+
+int
+Session::load_connections (const XMLNode& node)
+{
+ XMLNodeList nlist = node.children();
+ XMLNodeConstIterator niter;
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == "InputConnection") {
+ add_connection (new ARDOUR::InputConnection (**niter));
+ } else if ((*niter)->name() == "OutputConnection") {
+ add_connection (new ARDOUR::OutputConnection (**niter));
+ } else {
+ error << compose(_("Unknown node \"%1\" found in Connections list from state file"), (*niter)->name()) << endmsg;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::load_edit_groups (const XMLNode& node)
+{
+ return load_route_groups (node, true);
+}
+
+int
+Session::load_mix_groups (const XMLNode& node)
+{
+ return load_route_groups (node, false);
+}
+
+int
+Session::load_route_groups (const XMLNode& node, bool edit)
+{
+ XMLNodeList nlist = node.children();
+ XMLNodeConstIterator niter;
+ RouteGroup* route;
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ if ((*niter)->name() == "RouteGroup") {
+ if (edit) {
+ route = add_edit_group ("");
+ route->set_state (**niter);
+ } else {
+ route = add_mix_group ("");
+ route->set_state (**niter);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+Session::swap_configuration(Configuration** new_config)
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ Configuration* tmp = *new_config;
+ *new_config = Config;
+ Config = tmp;
+ set_dirty();
+}
+
+void
+Session::copy_configuration(Configuration* new_config)
+{
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ new_config = new Configuration(*Config);
+}
+
+static bool
+state_file_filter (const string &str, void *arg)
+{
+ return (str.length() > strlen(Session::statefile_suffix()) &&
+ str.find (Session::statefile_suffix()) == (str.length() - strlen (Session::statefile_suffix())));
+}
+
+struct string_cmp {
+ bool operator()(const string* a, const string* b) {
+ return *a < *b;
+ }
+};
+
+static string*
+remove_end(string* state)
+{
+ string statename(*state);
+
+ string::size_type start,end;
+ if ((start = statename.find_last_of ('/')) != string::npos) {
+ statename = statename.substr (start+1);
+ }
+
+ if ((end = statename.rfind(".ardour")) < 0) {
+ end = statename.length();
+ }
+
+ return new string(statename.substr (0, end));
+}
+
+vector<string *> *
+Session::possible_states (string path)
+{
+ PathScanner scanner;
+ vector<string*>* states = scanner (path, state_file_filter, 0, false, false);
+
+ transform(states->begin(), states->end(), states->begin(), remove_end);
+
+ string_cmp cmp;
+ sort (states->begin(), states->end(), cmp);
+
+ return states;
+}
+
+vector<string *> *
+Session::possible_states () const
+{
+ return possible_states(_path);
+}
+
+void
+Session::auto_save()
+{
+ save_state (_current_snapshot_name);
+}
+
+RouteGroup *
+Session::add_edit_group (string name)
+{
+ RouteGroup* rg = new RouteGroup (name);
+ edit_groups.push_back (rg);
+ edit_group_added (rg); /* EMIT SIGNAL */
+ set_dirty();
+ return rg;
+}
+
+RouteGroup *
+Session::add_mix_group (string name)
+{
+ RouteGroup* rg = new RouteGroup (name, RouteGroup::Relative);
+ mix_groups.push_back (rg);
+ mix_group_added (rg); /* EMIT SIGNAL */
+ set_dirty();
+ return rg;
+}
+
+RouteGroup *
+Session::mix_group_by_name (string name)
+{
+ list<RouteGroup *>::iterator i;
+
+ for (i = mix_groups.begin(); i != mix_groups.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+ return 0;
+}
+
+RouteGroup *
+Session::edit_group_by_name (string name)
+{
+ list<RouteGroup *>::iterator i;
+
+ for (i = edit_groups.begin(); i != edit_groups.end(); ++i) {
+ if ((*i)->name() == name) {
+ return* i;
+ }
+ }
+ return 0;
+}
+
+void
+Session::set_meter_hold (float val)
+{
+ _meter_hold = val;
+ MeterHoldChanged(); // emit
+}
+
+void
+Session::set_meter_falloff (float val)
+{
+ _meter_falloff = val;
+ MeterFalloffChanged(); // emit
+}
+
+
+void
+Session::begin_reversible_command (string name, UndoAction* private_undo)
+{
+ current_cmd.clear ();
+ current_cmd.set_name (name);
+
+ if (private_undo) {
+ current_cmd.add_undo (*private_undo);
+ }
+}
+
+void
+Session::commit_reversible_command (UndoAction* private_redo)
+{
+ struct timeval now;
+
+ if (private_redo) {
+ current_cmd.add_redo_no_execute (*private_redo);
+ }
+
+ gettimeofday (&now, 0);
+ current_cmd.set_timestamp (now);
+
+ history.add (current_cmd);
+}
+
+Session::GlobalRouteBooleanState
+Session::get_global_route_boolean (bool (Route::*method)(void) const)
+{
+ GlobalRouteBooleanState s;
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (!(*i)->hidden()) {
+ RouteBooleanState v;
+
+ v.first =* i;
+ v.second = ((*i)->*method)();
+
+ s.push_back (v);
+ }
+ }
+
+ return s;
+}
+
+Session::GlobalRouteMeterState
+Session::get_global_route_metering ()
+{
+ GlobalRouteMeterState s;
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (!(*i)->hidden()) {
+ RouteMeterState v;
+
+ v.first =* i;
+ v.second = (*i)->meter_point();
+
+ s.push_back (v);
+ }
+ }
+
+ return s;
+}
+
+void
+Session::set_global_route_metering (GlobalRouteMeterState s, void* arg)
+{
+ for (GlobalRouteMeterState::iterator i = s.begin(); i != s.end(); ++i) {
+ i->first->set_meter_point (i->second, arg);
+ }
+}
+
+void
+Session::set_global_route_boolean (GlobalRouteBooleanState s, void (Route::*method)(bool, void*), void* arg)
+{
+ for (GlobalRouteBooleanState::iterator i = s.begin(); i != s.end(); ++i) {
+ (i->first->*method) (i->second, arg);
+ }
+}
+
+void
+Session::set_global_mute (GlobalRouteBooleanState s, void* src)
+{
+ set_global_route_boolean (s, &Route::set_mute, src);
+}
+
+void
+Session::set_global_solo (GlobalRouteBooleanState s, void* src)
+{
+ set_global_route_boolean (s, &Route::set_solo, src);
+}
+
+void
+Session::set_global_record_enable (GlobalRouteBooleanState s, void* src)
+{
+ set_global_route_boolean (s, &Route::set_record_enable, src);
+}
+
+UndoAction
+Session::global_mute_memento (void* src)
+{
+ return sigc::bind (mem_fun (*this, &Session::set_global_mute), get_global_route_boolean (&Route::muted), src);
+}
+
+UndoAction
+Session::global_metering_memento (void* src)
+{
+ return sigc::bind (mem_fun (*this, &Session::set_global_route_metering), get_global_route_metering (), src);
+}
+
+UndoAction
+Session::global_solo_memento (void* src)
+{
+ return sigc::bind (mem_fun (*this, &Session::set_global_solo), get_global_route_boolean (&Route::soloed), src);
+}
+
+UndoAction
+Session::global_record_enable_memento (void* src)
+{
+ return sigc::bind (mem_fun (*this, &Session::set_global_record_enable), get_global_route_boolean (&Route::record_enabled), src);
+}
+
+static bool
+template_filter (const string &str, void *arg)
+{
+ return (str.length() > strlen(Session::template_suffix()) &&
+ str.find (Session::template_suffix()) == (str.length() - strlen (Session::template_suffix())));
+}
+
+void
+Session::get_template_list (list<string> &template_names)
+{
+ vector<string *> *templates;
+ PathScanner scanner;
+ string path;
+
+ path = template_path ();
+
+ templates = scanner (path, template_filter, 0, false, true);
+
+ vector<string*>::iterator i;
+ for (i = templates->begin(); i != templates->end(); ++i) {
+ string fullpath = *(*i);
+ int start, end;
+
+ start = fullpath.find_last_of ('/') + 1;
+ if ((end = fullpath.find_last_of ('.')) <0) {
+ end = fullpath.length();
+ }
+
+ template_names.push_back(fullpath.substr(start, (end-start)));
+ }
+}
+
+int
+Session::read_favorite_dirs (FavoriteDirs & favs)
+{
+ string path = Config->get_user_ardour_path();
+ path += "/favorite_dirs";
+
+ ifstream fav (path.c_str());
+
+ favs.clear();
+
+ if (!fav) {
+ if (errno != ENOENT) {
+ //error << compose (_("cannot open favorite file %1 (%2)"), path, strerror (errno)) << endmsg;
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ while (true) {
+
+ string newfav;
+
+ getline(fav, newfav);
+
+ if (!fav.good()) {
+ break;
+ }
+
+ favs.push_back (newfav);
+ }
+
+ return 0;
+}
+
+int
+Session::write_favorite_dirs (FavoriteDirs & favs)
+{
+ string path = Config->get_user_ardour_path();
+ path += "/favorite_dirs";
+
+ ofstream fav (path.c_str());
+
+ if (!fav) {
+ return -1;
+ }
+
+ for (FavoriteDirs::iterator i = favs.begin(); i != favs.end(); ++i) {
+ fav << (*i) << endl;
+ }
+
+ return 0;
+}
+
+static bool
+accept_all_non_peak_files (const string& path, void *arg)
+{
+ return (path.length() > 5 && path.find (".peak") != (path.length() - 5));
+}
+
+static bool
+accept_all_state_files (const string& path, void *arg)
+{
+ return (path.length() > 7 && path.find (".ardour") == (path.length() - 7));
+}
+
+int
+Session::find_all_sources (string path, set<string>& result)
+{
+ XMLTree tree;
+ XMLNode* node;
+
+ if (!tree.read (path)) {
+ return -1;
+ }
+
+ if ((node = find_named_node (*tree.root(), "Sources")) == 0) {
+ return -2;
+ }
+
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+
+ nlist = node->children();
+
+ set_dirty();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ XMLProperty* prop;
+
+ if ((prop = (*niter)->property (X_("name"))) == 0) {
+ continue;
+ }
+
+ if (prop->value()[0] == '/') {
+ /* external file, ignore */
+ continue;
+ }
+
+ string path = _path; /* /-terminated */
+ path += sound_dir_name;
+ path += '/';
+ path += prop->value();
+
+ result.insert (path);
+ }
+
+ return 0;
+}
+
+int
+Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_this_snapshot)
+{
+ PathScanner scanner;
+ vector<string*>* state_files;
+ string ripped;
+ string this_snapshot_path;
+
+ result.clear ();
+
+ ripped = _path;
+
+ if (ripped[ripped.length()-1] == '/') {
+ ripped = ripped.substr (0, ripped.length() - 1);
+ }
+
+ state_files = scanner (ripped, accept_all_state_files, (void *) 0, false, true);
+
+ if (state_files == 0) {
+ /* impossible! */
+ return 0;
+ }
+
+ this_snapshot_path = _path;
+ this_snapshot_path += _current_snapshot_name;
+ this_snapshot_path += _statefile_suffix;
+
+ for (vector<string*>::iterator i = state_files->begin(); i != state_files->end(); ++i) {
+
+ if (exclude_this_snapshot && **i == this_snapshot_path) {
+ continue;
+ }
+
+ if (find_all_sources (**i, result) < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+Session::cleanup_sources (Session::cleanup_report& rep)
+{
+ vector<Source*> dead_sources;
+ vector<Playlist*> playlists_tbd;
+ PathScanner scanner;
+ string sound_path;
+ vector<space_and_path>::iterator i;
+ vector<space_and_path>::iterator nexti;
+ vector<string*>* soundfiles;
+ vector<string> unused;
+ set<string> all_sources;
+ bool used;
+ string spath;
+ int ret = -1;
+
+ _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
+
+ /* step 1: consider deleting all unused playlists */
+
+ for (PlaylistList::iterator x = unused_playlists.begin(); x != unused_playlists.end(); ++x) {
+ int status;
+
+ status = AskAboutPlaylistDeletion (*x);
+
+ switch (status) {
+ case -1:
+ ret = 0;
+ goto out;
+ break;
+
+ case 0:
+ playlists_tbd.push_back (*x);
+ break;
+
+ default:
+ /* leave it alone */
+ break;
+ }
+ }
+
+ /* now delete any that were marked for deletion */
+
+ for (vector<Playlist*>::iterator x = playlists_tbd.begin(); x != playlists_tbd.end(); ++x) {
+ PlaylistList::iterator foo;
+
+ if ((foo = unused_playlists.find (*x)) != unused_playlists.end()) {
+ unused_playlists.erase (foo);
+ }
+ delete *x;
+ }
+
+ /* step 2: clear the undo/redo history for all playlists */
+
+ for (PlaylistList::iterator x = playlists.begin(); x != playlists.end(); ++x) {
+ (*x)->drop_all_states ();
+ }
+
+ /* step 3: find all un-referenced sources */
+
+ rep.paths.clear ();
+ rep.space = 0;
+
+ for (SourceList::iterator i = sources.begin(); i != sources.end(); ) {
+
+ SourceList::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ /* only remove files that are not in use and have some size
+ to them. otherwise we remove the current "nascent"
+ capture files.
+ */
+
+ if ((*i).second->use_cnt() == 0 && (*i).second->length() > 0) {
+ dead_sources.push_back (i->second);
+
+ /* remove this source from our own list to avoid us
+ adding it to the list of all sources below
+ */
+
+ sources.erase (i);
+ }
+
+ i = tmp;
+ }
+
+ /* Step 4: get rid of all regions in the region list that use any dead sources
+ in case the sources themselves don't go away (they might be referenced in
+ other snapshots).
+ */
+
+ for (vector<Source*>::iterator i = dead_sources.begin(); i != dead_sources.end();++i) {
+
+ for (AudioRegionList::iterator r = audio_regions.begin(); r != audio_regions.end(); ) {
+ AudioRegionList::iterator tmp;
+ AudioRegion* ar;
+
+ tmp = r;
+ ++tmp;
+
+ ar = (*r).second;
+
+ for (uint32_t n = 0; n < ar->n_channels(); ++n) {
+ if (&ar->source (n) == (*i)) {
+ /* this region is dead */
+ remove_region (ar);
+ }
+ }
+
+ r = tmp;
+ }
+ }
+
+ /* build a list of all the possible sound directories for the session */
+
+ for (i = session_dirs.begin(); i != session_dirs.end(); ) {
+
+ nexti = i;
+ ++nexti;
+
+ sound_path += (*i).path;
+ sound_path += sound_dir_name;
+
+ if (nexti != session_dirs.end()) {
+ sound_path += ':';
+ }
+
+ i = nexti;
+ }
+
+ /* now do the same thing for the files that ended up in the sounds dir(s)
+ but are not referenced as sources in any snapshot.
+ */
+
+ soundfiles = scanner (sound_path, accept_all_non_peak_files, (void *) 0, false, true);
+
+ if (soundfiles == 0) {
+ return 0;
+ }
+
+ /* find all sources, but don't use this snapshot because the
+ state file on disk still references sources we may have already
+ dropped.
+ */
+
+ find_all_sources_across_snapshots (all_sources, true);
+
+ /* add our current source list
+ */
+
+ for (SourceList::iterator i = sources.begin(); i != sources.end(); ++i) {
+ FileSource* fs;
+ SndFileSource* sfs;
+
+ if ((fs = dynamic_cast<FileSource*> ((*i).second)) != 0) {
+ all_sources.insert (fs->path());
+ } else if ((sfs = dynamic_cast<SndFileSource*> ((*i).second)) != 0) {
+ all_sources.insert (sfs->path());
+ }
+ }
+
+ for (vector<string*>::iterator x = soundfiles->begin(); x != soundfiles->end(); ++x) {
+
+ used = false;
+ spath = **x;
+
+ for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+
+ if (spath == *i) {
+ used = true;
+ break;
+ }
+
+ }
+
+ if (!used) {
+ unused.push_back (spath);
+ }
+ }
+
+ /* now try to move all unused files into the "dead_sounds" directory(ies) */
+
+ for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
+ struct stat statbuf;
+
+ rep.paths.push_back (*x);
+ if (stat ((*x).c_str(), &statbuf) == 0) {
+ rep.space += statbuf.st_size;
+ }
+
+ string newpath;
+
+ /* don't move the file across filesystems, just
+ stick it in the `dead_sound_dir_name' directory
+ on whichever filesystem it was already on.
+ */
+
+ newpath = PBD::dirname (*x);
+ newpath = PBD::dirname (newpath);
+
+ newpath += '/';
+ newpath += dead_sound_dir_name;
+ newpath += '/';
+ newpath += PBD::basename ((*x));
+
+ if (access (newpath.c_str(), F_OK) == 0) {
+
+ /* the new path already exists, try versioning */
+
+ char buf[PATH_MAX+1];
+ int version = 1;
+ string newpath_v;
+
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
+ newpath_v = buf;
+
+ while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
+ newpath_v = buf;
+ }
+
+ if (version == 999) {
+ error << compose (_("there are already 1000 files with names like %1; versioning discontinued"),
+ newpath)
+ << endmsg;
+ } else {
+ newpath = newpath_v;
+ }
+
+ } else {
+
+ /* it doesn't exist, or we can't read it or something */
+
+ }
+
+ if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
+ error << compose (_("cannot rename audio file source from %1 to %2 (%3)"),
+ (*x), newpath, strerror (errno))
+ << endmsg;
+ goto out;
+ }
+
+
+ /* see if there an easy to find peakfile for this file, and remove it.
+ */
+
+ string peakpath = (*x).substr (0, (*x).find_last_of ('.'));
+ peakpath += ".peak";
+
+ if (access (peakpath.c_str(), W_OK) == 0) {
+ if (::unlink (peakpath.c_str()) != 0) {
+ error << compose (_("cannot remove peakfile %1 for %2 (%3)"),
+ peakpath, _path, strerror (errno))
+ << endmsg;
+ /* try to back out */
+ rename (newpath.c_str(), _path.c_str());
+ goto out;
+ }
+ }
+
+ }
+
+ ret = 0;
+
+ /* dump the history list */
+
+ history.clear ();
+
+ /* save state so we don't end up a session file
+ referring to non-existent sources.
+ */
+
+ save_state ("");
+
+ out:
+ _state_of_the_state = (StateOfTheState) (_state_of_the_state & ~InCleanup);
+ return ret;
+}
+
+int
+Session::cleanup_trash_sources (Session::cleanup_report& rep)
+{
+ vector<space_and_path>::iterator i;
+ string dead_sound_dir;
+ struct dirent* dentry;
+ struct stat statbuf;
+ DIR* dead;
+
+ rep.paths.clear ();
+ rep.space = 0;
+
+ for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+
+ dead_sound_dir = (*i).path;
+ dead_sound_dir += dead_sound_dir_name;
+
+ if ((dead = opendir (dead_sound_dir.c_str())) == 0) {
+ continue;
+ }
+
+ while ((dentry = readdir (dead)) != 0) {
+
+ /* avoid '.' and '..' */
+
+ if ((dentry->d_name[0] == '.' && dentry->d_name[1] == '\0') ||
+ (dentry->d_name[2] == '\0' && dentry->d_name[0] == '.' && dentry->d_name[1] == '.')) {
+ continue;
+ }
+
+ string fullpath;
+
+ fullpath = dead_sound_dir;
+ fullpath += '/';
+ fullpath += dentry->d_name;
+
+ if (stat (fullpath.c_str(), &statbuf)) {
+ continue;
+ }
+
+ if (!S_ISREG (statbuf.st_mode)) {
+ continue;
+ }
+
+ if (unlink (fullpath.c_str())) {
+ error << compose (_("cannot remove dead sound file %1 (%2)"),
+ fullpath, strerror (errno))
+ << endmsg;
+ }
+
+ rep.paths.push_back (dentry->d_name);
+ rep.space += statbuf.st_size;
+ }
+
+ closedir (dead);
+
+ }
+
+ return 0;
+}
+
+void
+Session::set_dirty ()
+{
+ bool was_dirty = dirty();
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
+
+ if (!was_dirty) {
+ DirtyChanged(); /* EMIT SIGNAL */
+ }
+}
+
+
+void
+Session::set_clean ()
+{
+ bool was_dirty = dirty();
+
+ _state_of_the_state = Clean;
+
+ if (was_dirty) {
+ DirtyChanged(); /* EMIT SIGNAL */
+ }
+}
+
diff --git a/libs/ardour/session_time.cc b/libs/ardour/session_time.cc
new file mode 100644
index 0000000000..27cc40ec47
--- /dev/null
+++ b/libs/ardour/session_time.cc
@@ -0,0 +1,853 @@
+
+/*
+ Copyright (C) 1999-2002 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.
+
+ $Id$
+*/
+
+#include <iostream>
+#include <cmath>
+#include <unistd.h>
+
+#include <ardour/timestamps.h>
+
+#include <pbd/error.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+#include <ardour/tempo.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+//using namespace sigc;
+
+/* BBT TIME*/
+
+void
+Session::bbt_time (jack_nframes_t when, BBT_Time& bbt)
+{
+ _tempo_map->bbt_time (when, bbt);
+}
+
+/* SMPTE TIME */
+
+int
+Session::set_smpte_type (float fps, bool drop_frames)
+{
+ smpte_frames_per_second = fps;
+ smpte_drop_frames = drop_frames;
+ _frames_per_smpte_frame = (double) _current_frame_rate / (double) smpte_frames_per_second;
+ _frames_per_hour = _current_frame_rate * 3600;
+ _smpte_frames_per_hour = (unsigned long) (smpte_frames_per_second * 3600.0);
+
+
+ last_smpte_valid = false;
+ // smpte type bits are the middle two in the upper nibble
+ switch ((int) ceil (fps)) {
+ case 24:
+ mtc_smpte_bits = 0;
+ break;
+
+ case 25:
+ mtc_smpte_bits = 0x20;
+ break;
+
+ case 30:
+ default:
+ if (drop_frames) {
+ mtc_smpte_bits = 0x40;
+ } else {
+ mtc_smpte_bits = 0x60;
+ }
+ break;
+ };
+
+ SMPTETypeChanged (); /* EMIT SIGNAL */
+
+ set_dirty();
+
+ return 0;
+}
+
+void
+Session::set_smpte_offset (jack_nframes_t off)
+{
+ _smpte_offset = off;
+ last_smpte_valid = false;
+ SMPTEOffsetChanged (); /* EMIT SIGNAL */
+}
+
+void
+Session::set_smpte_offset_negative (bool neg)
+{
+ _smpte_offset_negative = neg;
+ last_smpte_valid = false;
+ SMPTEOffsetChanged (); /* EMIT SIGNAL */
+}
+
+#define SMPTE_IS_AROUND_ZERO( sm ) (!(sm).frames && !(sm).seconds && !(sm).minutes && !(sm).hours)
+#define SMPTE_IS_ZERO( sm ) (!(sm).frames && !(sm).seconds && !(sm).minutes && !(sm).hours && !(sm.subframes))
+
+// Increment by exactly one frame (keep subframes value)
+// Return true if seconds wrap
+smpte_wrap_t
+Session::smpte_increment( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ if (smpte.negative) {
+ if (SMPTE_IS_AROUND_ZERO(smpte) && smpte.subframes) {
+ // We have a zero transition involving only subframes
+ smpte.subframes = 80 - smpte.subframes;
+ smpte.negative = false;
+ return smpte_wrap_seconds;
+ }
+
+ smpte.negative = false;
+ wrap = smpte_decrement( smpte );
+ if (!SMPTE_IS_ZERO( smpte )) {
+ smpte.negative = true;
+ }
+ return wrap;
+ }
+
+ switch (mtc_smpte_bits >> 5) {
+ case MIDI::MTC_24_FPS:
+ if (smpte.frames == 23) {
+ smpte.frames = 0;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_25_FPS:
+ if (smpte.frames == 24) {
+ smpte.frames = 0;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_30_FPS_DROP:
+ if (smpte.frames == 29) {
+ if ( ((smpte.minutes + 1) % 10) && (smpte.seconds == 59) ) {
+ smpte.frames = 2;
+ }
+ else {
+ smpte.frames = 0;
+ }
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_30_FPS:
+ if (smpte.frames == 29) {
+ smpte.frames = 0;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ }
+
+ if (wrap == smpte_wrap_seconds) {
+ if (smpte.seconds == 59) {
+ smpte.seconds = 0;
+ wrap = smpte_wrap_minutes;
+ if (smpte.minutes == 59) {
+ smpte.minutes = 0;
+ wrap = smpte_wrap_hours;
+ smpte.hours++;
+ } else {
+ smpte.minutes++;
+ }
+ } else {
+ smpte.seconds++;
+ }
+ } else {
+ smpte.frames++;
+ }
+
+ return wrap;
+}
+
+// Decrement by exactly one frame (keep subframes value)
+smpte_wrap_t
+Session::smpte_decrement( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+
+ if (smpte.negative || SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ wrap = smpte_increment( smpte );
+ smpte.negative = true;
+ return wrap;
+ } else if (SMPTE_IS_AROUND_ZERO(smpte) && smpte.subframes) {
+ // We have a zero transition involving only subframes
+ smpte.subframes = 80 - smpte.subframes;
+ smpte.negative = true;
+ return smpte_wrap_seconds;
+ }
+
+ switch (mtc_smpte_bits >> 5) {
+ case MIDI::MTC_24_FPS:
+ if (smpte.frames == 0) {
+ smpte.frames = 23;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_25_FPS:
+ if (smpte.frames == 0) {
+ smpte.frames = 24;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_30_FPS_DROP:
+ if ((smpte.minutes % 10) && (smpte.seconds == 0)) {
+ if (smpte.frames <= 2) {
+ smpte.frames = 29;
+ wrap = smpte_wrap_seconds;
+ }
+ } else if (smpte.frames == 0) {
+ smpte.frames = 29;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ case MIDI::MTC_30_FPS:
+ if (smpte.frames == 0) {
+ smpte.frames = 29;
+ wrap = smpte_wrap_seconds;
+ }
+ break;
+ }
+
+ if (wrap == smpte_wrap_seconds) {
+ if (smpte.seconds == 0) {
+ smpte.seconds = 59;
+ wrap = smpte_wrap_minutes;
+ if (smpte.minutes == 0) {
+ smpte.minutes = 59;
+ wrap = smpte_wrap_hours;
+ smpte.hours--;
+ }
+ else {
+ smpte.minutes--;
+ }
+ } else {
+ smpte.seconds--;
+ }
+ } else {
+ smpte.frames--;
+ }
+
+ if (SMPTE_IS_ZERO( smpte )) {
+ smpte.negative = false;
+ }
+
+ return wrap;
+}
+
+// Go to lowest absolute subframe value in this frame (set to 0 :-)
+void
+Session::smpte_frames_floor( SMPTE_Time& smpte ) const
+{
+ smpte.subframes = 0;
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+}
+
+// Increment by one subframe
+smpte_wrap_t
+Session::smpte_increment_subframes( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ if (smpte.negative) {
+ smpte.negative = false;
+ wrap = smpte_decrement_subframes( smpte );
+ if (!SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = true;
+ }
+ return wrap;
+ }
+
+ smpte.subframes++;
+ if (smpte.subframes >= 80) {
+ smpte.subframes = 0;
+ smpte_increment( smpte );
+ return smpte_wrap_frames;
+ }
+ return smpte_wrap_none;
+}
+
+
+// Decrement by one subframe
+smpte_wrap_t
+Session::smpte_decrement_subframes( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ if (smpte.negative) {
+ smpte.negative = false;
+ wrap = smpte_increment_subframes( smpte );
+ smpte.negative = true;
+ return wrap;
+ }
+
+ if (smpte.subframes <= 0) {
+ smpte.subframes = 0;
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = true;
+ smpte.subframes = 1;
+ return smpte_wrap_frames;
+ } else {
+ smpte_decrement( smpte );
+ smpte.subframes = 79;
+ return smpte_wrap_frames;
+ }
+ } else {
+ smpte.subframes--;
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+ return smpte_wrap_none;
+ }
+}
+
+
+// Go to next whole second (frames == 0 or frames == 2)
+smpte_wrap_t
+Session::smpte_increment_seconds( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ // Clear subframes
+ smpte_frames_floor( smpte );
+
+ if (smpte.negative) {
+ // Wrap second if on second boundary
+ wrap = smpte_increment(smpte);
+ // Go to lowest absolute frame value
+ smpte_seconds_floor( smpte );
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+ } else {
+ // Go to highest possible frame in this second
+ switch (mtc_smpte_bits >> 5) {
+ case MIDI::MTC_24_FPS:
+ smpte.frames = 23;
+ break;
+ case MIDI::MTC_25_FPS:
+ smpte.frames = 24;
+ break;
+ case MIDI::MTC_30_FPS_DROP:
+ case MIDI::MTC_30_FPS:
+ smpte.frames = 29;
+ break;
+ }
+
+ // Increment by one frame
+ wrap = smpte_increment( smpte );
+ }
+
+ return wrap;
+}
+
+// Go to lowest (absolute) frame value in this second
+// Doesn't care about positive/negative
+void
+Session::smpte_seconds_floor( SMPTE_Time& smpte ) const
+{
+ // Clear subframes
+ smpte_frames_floor( smpte );
+
+ // Go to lowest possible frame in this second
+ switch (mtc_smpte_bits >> 5) {
+ case MIDI::MTC_24_FPS:
+ case MIDI::MTC_25_FPS:
+ case MIDI::MTC_30_FPS:
+ smpte.frames = 0;
+ break;
+ case MIDI::MTC_30_FPS_DROP:
+ if ((smpte.minutes % 10) && (smpte.seconds == 0)) {
+ smpte.frames = 2;
+ } else {
+ smpte.frames = 0;
+ }
+ break;
+ }
+
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+}
+
+
+// Go to next whole minute (seconds == 0, frames == 0 or frames == 2)
+smpte_wrap_t
+Session::smpte_increment_minutes( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ // Clear subframes
+ smpte_frames_floor( smpte );
+
+ if (smpte.negative) {
+ // Wrap if on minute boundary
+ wrap = smpte_increment_seconds( smpte );
+ // Go to lowest possible value in this minute
+ smpte_minutes_floor( smpte );
+ } else {
+ // Go to highest possible second
+ smpte.seconds = 59;
+ // Wrap minute by incrementing second
+ wrap = smpte_increment_seconds( smpte );
+ }
+
+ return wrap;
+}
+
+// Go to lowest absolute value in this minute
+void
+Session::smpte_minutes_floor( SMPTE_Time& smpte ) const
+{
+ // Go to lowest possible second
+ smpte.seconds = 0;
+ // Go to lowest possible frame
+ smpte_seconds_floor( smpte );
+
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+}
+
+// Go to next whole hour (minute = 0, second = 0, frame = 0)
+smpte_wrap_t
+Session::smpte_increment_hours( SMPTE_Time& smpte ) const
+{
+ smpte_wrap_t wrap = smpte_wrap_none;
+
+ // Clear subframes
+ smpte_frames_floor(smpte);
+
+ if (smpte.negative) {
+ // Wrap if on hour boundary
+ wrap = smpte_increment_minutes( smpte );
+ // Go to lowest possible value in this hour
+ smpte_hours_floor( smpte );
+ } else {
+ smpte.minutes = 59;
+ wrap = smpte_increment_minutes( smpte );
+ }
+
+ return wrap;
+}
+
+// Go to lowest absolute value in this hour
+void
+Session::smpte_hours_floor( SMPTE_Time& smpte ) const
+{
+ smpte.minutes = 0;
+ smpte.seconds = 0;
+ smpte.frames = 0;
+ smpte.subframes = 0;
+
+ if (SMPTE_IS_ZERO(smpte)) {
+ smpte.negative = false;
+ }
+}
+
+
+void
+Session::smpte_to_sample( SMPTE_Time& smpte, jack_nframes_t& sample, bool use_offset, bool use_subframes ) const
+{
+ if (smpte_drop_frames) {
+ // The drop frame format was created to better approximate the 30000/1001 = 29.97002997002997....
+ // framerate of NTSC color TV. The used frame rate of drop frame is 29.97, which drifts by about
+ // 0.108 frame per hour, or about 1.3 frames per 12 hours. This is not perfect, but a lot better
+ // than using 30 non drop, which will drift with about 1.8 frame per minute.
+ // Using 29.97, drop frame real time can be accurate only every 10th minute (10 minutes of 29.97 fps
+ // is exactly 17982 frames). One minute is 1798.2 frames, but we count 30 frames per second
+ // (30 * 60 = 1800). This means that at the first minute boundary (at the end of 0:0:59:29) we
+ // are 1.8 frames too late relative to real time. By dropping 2 frames (jumping to 0:1:0:2) we are
+ // approx. 0.2 frames too early. This adds up with 0.2 too early for each minute until we are 1.8
+ // frames too early at 0:9:0:2 (9 * 0.2 = 1.8). The 10th minute brings us 1.8 frames later again
+ // (at end of 0:9:59:29), which sums up to 0 (we are back to zero at 0:10:0:0 :-).
+ //
+ // In table form:
+ //
+ // SMPTE value frames offset subframes offset seconds (rounded) 44100 sample (rounded)
+ // 0:00:00:00 0.0 0 0.000 0 (accurate)
+ // 0:00:59:29 1.8 144 60.027 2647177
+ // 0:01:00:02 -0.2 -16 60.060 2648648
+ // 0:01:59:29 1.6 128 120.020 5292883
+ // 0:02:00:02 -0.4 -32 120.053 5294354
+ // 0:02:59:29 1.4 112 180.013 7938588
+ // 0:03:00:02 -0.6 -48 180.047 7940060
+ // 0:03:59:29 1.2 96 240.007 10584294
+ // 0:04:00:02 -0.8 -64 240.040 10585766
+ // 0:04:59:29 1.0 80 300.000 13230000
+ // 0:05:00:02 -1.0 -80 300.033 13231471
+ // 0:05:59:29 0.8 64 359.993 15875706
+ // 0:06:00:02 -1.2 -96 360.027 15877177
+ // 0:06:59:29 0.6 48 419.987 18521411
+ // 0:07:00:02 -1.4 -112 420.020 18522883
+ // 0:07:59:29 0.4 32 478.980 21167117
+ // 0:08:00:02 -1.6 -128 480.013 21168589
+ // 0:08:59:29 0.2 16 539.973 23812823
+ // 0:09:00:02 -1.8 -144 540.007 23814294
+ // 0:09:59:29 0.0+ 0+ 599.967 26458529
+ // 0:10:00:00 0.0 0 600.000 26460000 (accurate)
+ //
+ // Per Sigmond <per@sigmond.no>
+
+ // Samples inside time dividable by 10 minutes (real time accurate)
+ jack_nframes_t base_samples = ((smpte.hours * 60 * 60) + ((smpte.minutes / 10) * 10 * 60)) * frame_rate();
+ // Samples inside time exceeding the nearest 10 minutes (always offset, see above)
+ long exceeding_df_minutes = smpte.minutes % 10;
+ long exceeding_df_seconds = (exceeding_df_minutes * 60) + smpte.seconds;
+ long exceeding_df_frames = (30 * exceeding_df_seconds) + smpte.frames - (2 * exceeding_df_minutes);
+ jack_nframes_t exceeding_samples = (jack_nframes_t) rint(exceeding_df_frames * _frames_per_smpte_frame);
+ sample = base_samples + exceeding_samples;
+ } else {
+ // Non drop is easy:
+ sample = (((smpte.hours * 60 * 60) + (smpte.minutes * 60) + smpte.seconds) * frame_rate()) + (jack_nframes_t)rint(smpte.frames * _frames_per_smpte_frame);
+ }
+
+ if (use_subframes) {
+ sample += (long) (((double)smpte.subframes * _frames_per_smpte_frame) / 80.0);
+ }
+
+ if (use_offset) {
+ if (smpte_offset_negative()) {
+ if (sample >= smpte_offset()) {
+ sample -= smpte_offset();
+ } else {
+ /* Prevent song-time from becoming negative */
+ sample = 0;
+ }
+ } else {
+ if (smpte.negative) {
+ if (sample <= smpte_offset()) {
+ sample = smpte_offset() - sample;
+ } else {
+ sample = 0;
+ }
+ } else {
+ sample += smpte_offset();
+ }
+ }
+ }
+}
+
+
+void
+Session::sample_to_smpte( jack_nframes_t sample, SMPTE_Time& smpte, bool use_offset, bool use_subframes ) const
+{
+ jack_nframes_t offset_sample;
+
+ if (!use_offset) {
+ offset_sample = sample;
+ smpte.negative = false;
+ } else {
+ if (_smpte_offset_negative) {
+ offset_sample = sample + _smpte_offset;
+ smpte.negative = false;
+ } else {
+ if (sample < _smpte_offset) {
+ offset_sample = (_smpte_offset - sample);
+ smpte.negative = true;
+ } else {
+ offset_sample = sample - _smpte_offset;
+ smpte.negative = false;
+ }
+ }
+ }
+
+ double smpte_frames_left_exact;
+ double smpte_frames_fraction;
+ unsigned long smpte_frames_left;
+
+ // Extract whole hours. Do this to prevent rounding errors with
+ // high sample numbers in the calculations that follow.
+ smpte.hours = offset_sample / _frames_per_hour;
+ offset_sample = offset_sample % _frames_per_hour;
+
+ // Calculate exact number of (exceeding) smpte frames and fractional frames
+ smpte_frames_left_exact = (double) offset_sample / _frames_per_smpte_frame;
+ smpte_frames_fraction = smpte_frames_left_exact - floor( smpte_frames_left_exact );
+ smpte.subframes = (long) rint(smpte_frames_fraction * 80.0);
+
+ // XXX Not sure if this is necessary anymore...
+ if (smpte.subframes == 80) {
+ // This can happen with 24 fps (and 29.97 fps ?)
+ smpte_frames_left_exact = ceil( smpte_frames_left_exact );
+ smpte.subframes = 0;
+ }
+
+ // Extract hour-exceeding frames for minute, second and frame calculations
+ smpte_frames_left = ((long) floor( smpte_frames_left_exact ));
+
+ if (smpte_drop_frames) {
+ // See long explanation in smpte_to_sample()...
+
+ // Number of 10 minute chunks
+ smpte.minutes = (smpte_frames_left / 17982) * 10; // exactly 17982 frames in 10 minutes
+ // frames exceeding the nearest 10 minute barrier
+ long exceeding_df_frames = smpte_frames_left % 17982;
+
+ // Find minutes exceeding the nearest 10 minute barrier
+ if (exceeding_df_frames >= 1800) { // nothing to do if we are inside the first minute (0-1799)
+ exceeding_df_frames -= 1800; // take away first minute (different number of frames than the others)
+ long extra_minutes_minus_1 = exceeding_df_frames / 1798; // how many minutes after the first one
+ exceeding_df_frames -= extra_minutes_minus_1 * 1798; // take away the (extra) minutes just found
+ smpte.minutes += extra_minutes_minus_1 + 1; // update with exceeding minutes
+ }
+
+ // Adjust frame numbering for dropped frames (frame 0 and 1 skipped at start of every minute except every 10th)
+ if (smpte.minutes % 10) {
+ // Every minute except every 10th
+ if (exceeding_df_frames < 28) {
+ // First second, frames 0 and 1 are skipped
+ smpte.seconds = 0;
+ smpte.frames = exceeding_df_frames + 2;
+ } else {
+ // All other seconds, all 30 frames are counted
+ exceeding_df_frames -= 28;
+ smpte.seconds = (exceeding_df_frames / 30) + 1;
+ smpte.frames = exceeding_df_frames % 30;
+ }
+ } else {
+ // Every 10th minute, all 30 frames counted in all seconds
+ smpte.seconds = exceeding_df_frames / 30;
+ smpte.frames = exceeding_df_frames % 30;
+ }
+ } else {
+ // Non drop is easy
+ smpte.minutes = smpte_frames_left / ((long) smpte_frames_per_second * 60);
+ smpte_frames_left = smpte_frames_left % ((long) smpte_frames_per_second * 60);
+ smpte.seconds = smpte_frames_left / (long) smpte_frames_per_second;
+ smpte.frames = smpte_frames_left % (long) smpte_frames_per_second;
+ }
+
+ if (!use_subframes) {
+ smpte.subframes = 0;
+ }
+}
+
+void
+Session::smpte_time (jack_nframes_t when, SMPTE_Time& smpte)
+{
+ if (last_smpte_valid && when == last_smpte_when) {
+ smpte = last_smpte;
+ return;
+ }
+
+ sample_to_smpte( when, smpte, true /* use_offset */, false /* use_subframes */ );
+
+ last_smpte_when = when;
+ last_smpte = smpte;
+ last_smpte_valid = true;
+}
+
+void
+Session::smpte_time_subframes (jack_nframes_t when, SMPTE_Time& smpte)
+{
+ if (last_smpte_valid && when == last_smpte_when) {
+ smpte = last_smpte;
+ return;
+ }
+
+ sample_to_smpte( when, smpte, true /* use_offset */, true /* use_subframes */ );
+
+ last_smpte_when = when;
+ last_smpte = smpte;
+ last_smpte_valid = true;
+}
+
+void
+Session::smpte_duration (jack_nframes_t when, SMPTE_Time& smpte) const
+{
+ sample_to_smpte( when, smpte, false /* use_offset */, true /* use_subframes */ );
+}
+
+void
+Session::smpte_duration_string (char* buf, jack_nframes_t when) const
+{
+ SMPTE_Time smpte;
+
+ smpte_duration (when, smpte);
+ snprintf (buf, sizeof (buf), "%02ld:%02ld:%02ld:%02ld", smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
+}
+
+void
+Session::smpte_time (SMPTE_Time &t)
+
+{
+ smpte_time (_transport_frame, t);
+}
+
+int
+Session::jack_sync_callback (jack_transport_state_t state,
+ jack_position_t* pos)
+{
+ bool slave = synced_to_jack();
+
+ switch (state) {
+ case JackTransportStopped:
+ if (slave && _transport_frame != pos->frame && post_transport_work == 0) {
+ request_locate (pos->frame, false);
+ // cerr << "SYNC: stopped, locate to " << pos->frame << " from " << _transport_frame << endl;
+ return false;
+ } else {
+ return true;
+ }
+
+ case JackTransportStarting:
+ // cerr << "SYNC: starting @ " << pos->frame << " a@ " << _transport_frame << " our work = " << post_transport_work << " pos matches ? " << (_transport_frame == pos->frame) << endl;
+ if (slave) {
+ return _transport_frame == pos->frame && post_transport_work == 0;
+ } else {
+ return true;
+ }
+ break;
+
+ case JackTransportRolling:
+ // cerr << "SYNC: rolling slave = " << slave << endl;
+ if (slave) {
+ start_transport ();
+ }
+ break;
+
+ default:
+ error << compose (_("Unknown JACK transport state %1 in sync callback"), state)
+ << endmsg;
+ }
+
+ return true;
+}
+
+void
+Session::jack_timebase_callback (jack_transport_state_t state,
+ jack_nframes_t nframes,
+ jack_position_t* pos,
+ int new_position)
+{
+ BBT_Time bbt;
+
+ /* frame info */
+
+ pos->frame = _transport_frame;
+ pos->valid = JackPositionTimecode;
+
+ /* BBT info */
+
+ if (_tempo_map) {
+
+ TempoMap::Metric metric (_tempo_map->metric_at (_transport_frame));
+ _tempo_map->bbt_time_with_metric (_transport_frame, bbt, metric);
+
+ pos->bar = bbt.bars;
+ pos->beat = bbt.beats;
+ pos->tick = bbt.ticks;
+
+ // XXX still need to set bar_start_tick
+
+ pos->beats_per_bar = metric.meter().beats_per_bar();
+ pos->beat_type = metric.meter().note_divisor();
+ pos->ticks_per_beat = Meter::ticks_per_beat;
+ pos->beats_per_minute = metric.tempo().beats_per_minute();
+
+ pos->valid = jack_position_bits_t (pos->valid | JackPositionBBT);
+ }
+
+#if 0
+ /* SMPTE info */
+
+ t.smpte_offset = _smpte_offset;
+ t.smpte_frame_rate = smpte_frames_per_second;
+
+ if (_transport_speed) {
+
+ if (auto_loop) {
+
+ Location* location = _locations.auto_loop_location();
+
+ if (location) {
+
+ t.transport_state = JackTransportLooping;
+ t.loop_start = location->start();
+ t.loop_end = location->end();
+ t.valid = jack_transport_bits_t (t.valid | JackTransportLoop);
+
+ } else {
+
+ t.loop_start = 0;
+ t.loop_end = 0;
+ t.transport_state = JackTransportRolling;
+
+ }
+
+ } else {
+
+ t.loop_start = 0;
+ t.loop_end = 0;
+ t.transport_state = JackTransportRolling;
+
+ }
+
+ }
+
+#endif
+}
+
+jack_nframes_t
+Session::convert_to_frames_at (jack_nframes_t position, AnyTime& any)
+{
+ double secs;
+
+ switch (any.type) {
+ case AnyTime::BBT:
+ return _tempo_map->frame_time ( any.bbt);
+ break;
+
+ case AnyTime::SMPTE:
+ /* XXX need to handle negative values */
+ secs = any.smpte.hours * 60 * 60;
+ secs += any.smpte.minutes * 60;
+ secs += any.smpte.seconds;
+ secs += any.smpte.frames / smpte_frames_per_second;
+ if (_smpte_offset_negative)
+ {
+ return (jack_nframes_t) floor (secs * frame_rate()) - _smpte_offset;
+ }
+ else
+ {
+ return (jack_nframes_t) floor (secs * frame_rate()) + _smpte_offset;
+ }
+ break;
+
+ case AnyTime::Seconds:
+ return (jack_nframes_t) floor (any.seconds * frame_rate());
+ break;
+
+ case AnyTime::Frames:
+ return any.frames;
+ break;
+ }
+
+ return any.frames;
+}
diff --git a/libs/ardour/session_timefx.cc b/libs/ardour/session_timefx.cc
new file mode 100644
index 0000000000..a200572584
--- /dev/null
+++ b/libs/ardour/session_timefx.cc
@@ -0,0 +1,187 @@
+/*
+ Copyright (C) 2003 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.
+
+ $Id$
+*/
+
+#include <cerrno>
+#include <stdexcept>
+
+#include <pbd/basename.h>
+
+#include <soundtouch/SoundTouch.h>
+
+#include <ardour/session.h>
+#include <ardour/audioregion.h>
+#include <ardour/filesource.h>
+#include <ardour/sndfilesource.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace soundtouch;
+
+AudioRegion*
+Session::tempoize_region (TimeStretchRequest& tsr)
+{
+ AudioRegion::SourceList sources;
+ AudioRegion::SourceList::iterator it;
+ AudioRegion* r = 0;
+ SoundTouch st;
+ string region_name;
+ string ident = X_("-TIMEFX-");
+ float percentage;
+ jack_nframes_t total_frames;
+ jack_nframes_t done;
+
+ /* the soundtouch code wants a *tempo* change percentage, which is
+ of opposite sign to the length change.
+ */
+
+ percentage = -tsr.fraction;
+
+ st.setSampleRate (frame_rate());
+ st.setChannels (1);
+ st.setTempoChange (percentage);
+ st.setPitchSemiTones (0);
+ st.setRateChange (0);
+
+ st.setSetting(SETTING_USE_QUICKSEEK, tsr.quick_seek);
+ st.setSetting(SETTING_USE_AA_FILTER, tsr.antialias);
+
+ vector<string> names = tsr.region->master_source_names();
+
+ tsr.progress = 0.0f;
+ total_frames = tsr.region->length() * tsr.region->n_channels();
+ done = 0;
+
+ for (uint32_t i = 0; i < tsr.region->n_channels(); ++i) {
+ string path = path_from_region_name (PBD::basename_nosuffix (names[i]), ident);
+
+ if (path.length() == 0) {
+ error << compose (_("tempoize: error creating name for new audio file based on %1"), tsr.region->name())
+ << endmsg;
+ goto out;
+ }
+
+ try {
+ sources.push_back(new FileSource (path, frame_rate()));
+ } catch (failed_constructor& err) {
+ error << compose (_("tempoize: error creating new audio file %1 (%2)"), path, strerror (errno)) << endmsg;
+ goto out;
+ }
+ }
+
+ try {
+ const jack_nframes_t bufsize = 16384;
+
+ for (uint32_t i = 0; i < sources.size(); ++i) {
+ gain_t gain_buffer[bufsize];
+ Sample buffer[bufsize];
+ jack_nframes_t pos = 0;
+ jack_nframes_t this_read = 0;
+
+ st.clear();
+ while (tsr.running && pos < tsr.region->length()) {
+ jack_nframes_t this_time;
+
+ this_time = min (bufsize, tsr.region->length() - pos);
+
+ /* read from the master (original) sources for the region,
+ not the ones currently in use, in case it's already been
+ subject to timefx. */
+
+ if ((this_read = tsr.region->master_read_at (buffer, buffer, gain_buffer, pos + tsr.region->position(), this_time)) != this_time) {
+ error << compose (_("tempoize: error reading data from %1"), sources[i]->name()) << endmsg;
+ goto out;
+ }
+
+ pos += this_read;
+ done += this_read;
+
+ tsr.progress = (float) done / total_frames;
+
+ st.putSamples (buffer, this_read);
+
+ while ((this_read = st.receiveSamples (buffer, bufsize)) > 0 && tsr.running) {
+ if (sources[i]->write (buffer, this_read) != this_read) {
+ error << compose (_("error writing tempo-adjusted data to %1"), sources[i]->name()) << endmsg;
+ goto out;
+ }
+ }
+ }
+
+ if (tsr.running) {
+ st.flush ();
+ }
+
+ while (tsr.running && (this_read = st.receiveSamples (buffer, bufsize)) > 0) {
+ if (sources[i]->write (buffer, this_read) != this_read) {
+ error << compose (_("error writing tempo-adjusted data to %1"), sources[i]->name()) << endmsg;
+ goto out;
+ }
+ }
+ }
+ } catch (runtime_error& err) {
+ error << _("timefx code failure. please notify ardour-developers.") << endmsg;
+ error << err.what() << endmsg;
+ goto out;
+ }
+
+ time_t now;
+ struct tm* xnow;
+ time (&now);
+ xnow = localtime (&now);
+
+ for (it = sources.begin(); it != sources.end(); ++it) {
+ dynamic_cast<FileSource*>(*it)->update_header (0, *xnow, now);
+ }
+
+ region_name = tsr.region->name() + X_(".t");
+
+ r = new AudioRegion (sources, 0, sources.front()->length(), region_name,
+ 0, AudioRegion::Flag (AudioRegion::DefaultFlags | AudioRegion::WholeFile));
+
+
+ out:
+
+ if (sources.size()) {
+
+ /* if we failed to complete for any reason, mark the new file
+ for deletion.
+ */
+
+ if ((r == 0 || !tsr.running)) {
+ for (it = sources.begin(); it != sources.end(); ++it) {
+ (*it)->mark_for_remove ();
+ delete *it;
+ }
+ }
+ }
+
+ /* if the process was cancelled, delete the region */
+
+ if (!tsr.running) {
+ if (r) {
+ delete r;
+ r = 0;
+ }
+ }
+
+ return r;
+}
diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc
new file mode 100644
index 0000000000..342b6e1a52
--- /dev/null
+++ b/libs/ardour/session_transport.cc
@@ -0,0 +1,1151 @@
+/*
+ Copyright (C) 1999-2003 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.
+
+ $Id$
+*/
+
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
+
+#include <sigc++/bind.h>
+#include <sigc++/retype.h>
+
+#include <pbd/undo.h>
+#include <pbd/error.h>
+#include <pbd/lockmonitor.h>
+#include <pbd/pthread_utils.h>
+
+#include <midi++/mmc.h>
+#include <midi++/port.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audioengine.h>
+#include <ardour/session.h>
+#include <ardour/diskstream.h>
+#include <ardour/auditioner.h>
+#include <ardour/slave.h>
+#include <ardour/location.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace sigc;
+
+void
+Session::request_input_change_handling ()
+{
+ Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0);
+ queue_event (ev);
+}
+
+void
+Session::request_slave_source (SlaveSource src, jack_nframes_t pos)
+{
+ Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, pos, 0.0);
+
+ if (src == Session::JACK) {
+ /* could set_seamless_loop() be disposed of entirely?*/
+ set_seamless_loop (false);
+ } else {
+
+ set_seamless_loop (true);
+ }
+ ev->slave = src;
+ queue_event (ev);
+}
+
+void
+Session::request_transport_speed (float speed)
+{
+ Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, speed);
+ queue_event (ev);
+}
+
+void
+Session::request_diskstream_speed (DiskStream& ds, float speed)
+{
+ Event* ev = new Event (Event::SetDiskstreamSpeed, Event::Add, Event::Immediate, 0, speed);
+ ev->set_ptr (&ds);
+ queue_event (ev);
+}
+
+void
+Session::request_stop (bool abort)
+{
+ Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0, abort);
+ queue_event (ev);
+}
+
+void
+Session::request_locate (jack_nframes_t target_frame, bool with_roll)
+{
+ Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, false);
+ queue_event (ev);
+}
+
+void
+Session::force_locate (jack_nframes_t target_frame, bool with_roll)
+{
+ Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true);
+ queue_event (ev);
+}
+
+void
+Session::request_auto_loop (bool yn)
+{
+ Event* ev;
+ Location *location = _locations.auto_loop_location();
+
+ if (location == 0 && yn) {
+ error << _("Cannot loop - no loop range defined")
+ << endmsg;
+ return;
+ }
+
+ ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, 0.0, yn);
+ queue_event (ev);
+
+ if (!yn && seamless_loop && transport_rolling()) {
+ // request an immediate locate to refresh the diskstreams
+ // after disabling looping
+ request_locate (_transport_frame-1, true);
+ }
+}
+
+void
+Session::set_seamless_loop (bool yn)
+{
+ if (seamless_loop != yn) {
+ seamless_loop = yn;
+
+ if (auto_loop && transport_rolling()) {
+ // to reset diskstreams etc
+ request_auto_loop (true);
+ }
+
+ ControlChanged (SeamlessLoop); /* EMIT */
+ }
+}
+
+void
+Session::realtime_stop (bool abort)
+{
+ /* assume that when we start, we'll be moving forwards */
+
+ if (_transport_speed < 0.0f) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse);
+ } else {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportStop);
+ }
+
+ if (actively_recording()) {
+
+ /* move the transport position back to where the
+ request for a stop was noticed. we rolled
+ past that point to pick up delayed input.
+ */
+
+#ifndef LEAVE_TRANSPORT_UNADJUSTED
+ decrement_transport_position (_worst_output_latency);
+#endif
+
+ if (_transport_frame > current_end_frame()) {
+
+ /* first capture resets end location; later captures can only extend the length */
+
+ if (_end_location_is_free) {
+ end_location->set_end (_transport_frame);
+ _end_location_is_free = false;
+ } else if (_transport_frame > end_location->start()) {
+ end_location->set_end (_transport_frame);
+ }
+
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportDuration);
+ }
+ }
+
+
+ if (abort) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportAbort);
+ }
+
+ _clear_event_type (Event::StopOnce);
+ _clear_event_type (Event::RangeStop);
+ _clear_event_type (Event::RangeLocate);
+
+ disable_record ();
+
+ reset_slave_state ();
+
+ _transport_speed = 0;
+ transport_sub_state = (auto_return ? AutoReturning : 0);
+}
+
+void
+Session::butler_transport_work ()
+{
+ if (post_transport_work & PostTransportCurveRealloc) {
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->curve_reallocate();
+ }
+ }
+
+ if (post_transport_work & PostTransportInputChange) {
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->non_realtime_input_change ();
+ }
+ }
+
+ if (post_transport_work & PostTransportSpeed) {
+ non_realtime_set_speed ();
+ }
+
+ if (post_transport_work & PostTransportReverse) {
+
+
+ clear_clicks();
+ cumulative_rf_motion = 0;
+ reset_rf_scale (0);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) {
+ (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed()));
+ }
+ else {
+ (*i)->seek (_transport_frame);
+ }
+ }
+ }
+ }
+
+ if (post_transport_work & (PostTransportStop|PostTransportLocate)) {
+ non_realtime_stop (post_transport_work & PostTransportAbort);
+ }
+
+ if (post_transport_work & PostTransportOverWrite) {
+ non_realtime_overwrite ();
+ }
+
+ if (post_transport_work & PostTransportAudition) {
+ non_realtime_set_audition ();
+ }
+
+ atomic_dec (&butler_should_do_transport_work);
+}
+
+void
+Session::non_realtime_set_speed ()
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->non_realtime_set_speed ();
+ }
+}
+
+void
+Session::non_realtime_overwrite ()
+{
+ LockMonitor lm (diskstream_lock, __LINE__, __FILE__);
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)->pending_overwrite) {
+ (*i)->overwrite_existing_buffers ();
+ }
+ }
+}
+
+void
+Session::non_realtime_stop (bool abort)
+{
+ struct tm* now;
+ time_t xnow;
+ bool did_record;
+
+ did_record = false;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)->get_captured_frames () != 0) {
+ did_record = true;
+ break;
+ }
+ }
+
+ /* stop and locate are merged here because they share a lot of common stuff */
+
+ time (&xnow);
+ now = localtime (&xnow);
+
+ if (auditioner) {
+ auditioner->cancel_audition ();
+ }
+
+ clear_clicks();
+ cumulative_rf_motion = 0;
+ reset_rf_scale (0);
+
+ if (did_record) {
+ begin_reversible_command ("capture");
+
+ Location* loc = _locations.end_location();
+
+ if (loc && !_have_captured) {
+
+ /* first capture.
+
+ note: later captures that extend the session length get
+ handled because of playlist length changes.
+ */
+
+ add_undo (sigc::retype_return<void>(sigc::bind (mem_fun (*loc, &Location::set_end), loc->end())));
+ add_redo (sigc::retype_return<void>(sigc::bind (mem_fun (*loc, &Location::set_end), _transport_frame)));
+ }
+
+ _have_captured = true;
+ }
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->transport_stopped (*now, xnow, abort);
+ }
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_pending_declick (0);
+ }
+ }
+
+ if (did_record) {
+ commit_reversible_command ();
+ }
+
+ if (_engine.running()) {
+ update_latency_compensation (true, abort);
+ }
+
+ if (auto_return || (post_transport_work & PostTransportLocate) || synced_to_jack()) {
+
+ if (pending_locate_flush) {
+ flush_all_redirects ();
+ }
+
+ if ((auto_return || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) {
+
+ _transport_frame = last_stop_frame;
+
+ if (synced_to_jack()) {
+ _engine.transport_locate (_transport_frame);
+ }
+ }
+
+#ifndef LEAVE_TRANSPORT_UNADJUSTED
+ }
+#endif
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) {
+ (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed()));
+ }
+ else {
+ (*i)->seek (_transport_frame);
+ }
+ }
+ }
+
+ deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
+
+#ifdef LEAVE_TRANSPORT_UNADJUSTED
+ }
+#endif
+
+ last_stop_frame = _transport_frame;
+
+ send_full_time_code ();
+ deliver_mmc (MIDI::MachineControl::cmdStop, 0);
+ deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
+
+ if (did_record) {
+ atomic_set (&_record_status, Disabled);
+ RecordDisabled (); /* EMIT SIGNAL */
+ }
+
+ if ((post_transport_work & PostTransportLocate) && get_record_enabled()) {
+ /* capture start has been changed, so save pending state */
+ save_state ("", true);
+ }
+
+ /* save the current state of things if appropriate */
+
+ if (did_record) {
+ save_state (_current_snapshot_name);
+ }
+
+ if (post_transport_work & PostTransportDuration) {
+ DurationChanged (); /* EMIT SIGNAL */
+ }
+
+ if (post_transport_work & PostTransportStop) {
+ _play_range = false;
+
+ /* do not turn off autoloop on stop */
+
+ }
+
+ PositionChanged (_transport_frame); /* EMIT SIGNAL */
+ TransportStateChange (); /* EMIT SIGNAL */
+
+ /* and start it up again if relevant */
+
+ if ((post_transport_work & PostTransportLocate) && _slave_type == None && pending_locate_roll) {
+ request_transport_speed (1.0);
+ pending_locate_roll = false;
+ }
+}
+
+void
+Session::check_declick_out ()
+{
+ bool locate_required = transport_sub_state & PendingLocate;
+
+ /* this is called after a process() iteration. if PendingDeclickOut was set,
+ it means that we were waiting to declick the output (which has just been
+ done) before doing something else. this is where we do that "something else".
+
+ note: called from the audio thread.
+ */
+
+ if (transport_sub_state & PendingDeclickOut) {
+
+ if (locate_required) {
+ start_locate (pending_locate_frame, pending_locate_roll, pending_locate_flush);
+ transport_sub_state &= ~(PendingDeclickOut|PendingLocate);
+ } else {
+ stop_transport (pending_abort);
+ transport_sub_state &= ~(PendingDeclickOut|PendingLocate);
+ }
+ }
+}
+
+void
+Session::set_auto_loop (bool yn)
+{
+ /* Called from event-handling context */
+
+ if (actively_recording() || _locations.auto_loop_location() == 0) {
+ return;
+ }
+
+ set_dirty();
+
+ if (yn && seamless_loop && synced_to_jack()) {
+ warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n"
+ "Recommend changing the configured options")
+ << endmsg;
+ return;
+ }
+
+
+ if ((auto_loop = yn)) {
+
+ Location *loc;
+
+
+ if ((loc = _locations.auto_loop_location()) != 0) {
+
+ if (seamless_loop) {
+ // set all diskstreams to use internal looping
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_loop (loc);
+ }
+ }
+ }
+ else {
+ // set all diskstreams to NOT use internal looping
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_loop (0);
+ }
+ }
+ }
+
+ /* stick in the loop event */
+
+ Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f);
+ merge_event (event);
+
+ /* locate to start of loop and roll if current pos is outside of the loop range */
+ if (_transport_frame < loc->start() || _transport_frame > loc->end()) {
+ event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, loc->start(), 0, !synced_to_jack());
+ merge_event (event);
+ }
+ else {
+ // locate to current position (+ 1 to force reload)
+ event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, _transport_frame + 1, 0, !synced_to_jack());
+ merge_event (event);
+ }
+ }
+
+
+
+ } else {
+ clear_events (Event::AutoLoop);
+
+ // set all diskstreams to NOT use internal looping
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ (*i)->set_loop (0);
+ }
+ }
+
+ }
+
+ ControlChanged (AutoLoop); /* EMIT SIGNAL */
+}
+
+void
+Session::flush_all_redirects ()
+{
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->flush_redirects ();
+ }
+}
+
+void
+Session::start_locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop)
+{
+ if (synced_to_jack()) {
+
+ float sp;
+ jack_nframes_t pos;
+
+ _slave->speed_and_position (sp, pos);
+
+ if (target_frame != pos) {
+
+ /* tell JACK to change transport position, and we will
+ follow along later in ::follow_slave()
+ */
+
+ _engine.transport_locate (target_frame);
+
+ if (sp != 1.0f && with_roll) {
+ _engine.transport_start ();
+ }
+
+ }
+
+ } else {
+
+ locate (target_frame, with_roll, with_flush, with_loop);
+ }
+}
+
+void
+Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop)
+{
+ if (actively_recording()) {
+ return;
+ }
+
+ if (_transport_frame == target_frame && !loop_changing && !with_loop) {
+ if (with_roll) {
+ set_transport_speed (1.0, false);
+ }
+ loop_changing = false;
+ return;
+ }
+
+ _transport_frame = target_frame;
+
+ if (_transport_speed && (!with_loop || loop_changing)) {
+ /* schedule a declick. we'll be called again when its done */
+
+ if (!(transport_sub_state & PendingDeclickOut)) {
+ transport_sub_state |= (PendingDeclickOut|PendingLocate);
+ pending_locate_frame = target_frame;
+ pending_locate_roll = with_roll;
+ pending_locate_flush = with_flush;
+ return;
+ }
+ }
+
+ if (transport_rolling() && !auto_play && !with_roll && !(synced_to_jack() && auto_loop)) {
+ realtime_stop (false);
+ }
+
+ if ( !with_loop || loop_changing) {
+
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate);
+
+ if (with_roll) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll);
+ }
+
+ schedule_butler_transport_work ();
+
+ } else {
+
+ /* XXX i don't know where else to put this. something has to clear the
+ current clicks, and without deadlocking. clear_clicks() takes
+ the route lock which would deadlock in this context.
+ */
+
+ for (Clicks::iterator i = clicks.begin(); i != clicks.end(); ++i) {
+ delete *i;
+ }
+
+ clicks.clear ();
+ }
+
+
+ /* cancel autoloop if transport pos outside of loop range */
+ if (auto_loop) {
+ Location* al = _locations.auto_loop_location();
+
+ if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
+ // cancel looping directly, this is called from event handling context
+ set_auto_loop(false);
+ }
+ }
+
+ loop_changing = false;
+}
+
+void
+Session::set_transport_speed (float speed, bool abort)
+{
+ if (_transport_speed == speed) {
+ return;
+ }
+
+ if (speed > 0) {
+ speed = min (8.0f, speed);
+ } else if (speed < 0) {
+ speed = max (-8.0f, speed);
+ }
+
+ if (transport_rolling() && speed == 0.0) {
+
+ if (synced_to_jack ()) {
+ _engine.transport_stop ();
+ } else {
+ stop_transport (abort);
+ }
+
+ } else if (transport_stopped() && speed == 1.0) {
+
+ if (synced_to_jack()) {
+ _engine.transport_start ();
+ } else {
+ start_transport ();
+ }
+
+ } else {
+
+ if ((synced_to_jack()) && speed != 0.0 && speed != 1.0) {
+ warning << _("Global varispeed cannot be supported while Ardour is connected to JACK transport control")
+ << endmsg;
+ return;
+ }
+
+ if (actively_recording()) {
+ return;
+ }
+
+ if (speed > 0.0f && _transport_frame == current_end_frame()) {
+ return;
+ }
+
+ if (speed < 0.0f && _transport_frame == 0) {
+ return;
+ }
+
+ clear_clicks ();
+
+ /* if we are reversing relative to the current speed, or relative to the speed
+ before the last stop, then we have to do extra work.
+ */
+
+ if ((_transport_speed && speed * _transport_speed < 0.0f) || (_last_transport_speed * speed < 0.0f)) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
+ }
+
+ _last_transport_speed = _transport_speed;
+ _transport_speed = speed;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if ((*i)->realtime_set_speed ((*i)->speed(), true)) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
+ }
+ }
+
+ if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) {
+ schedule_butler_transport_work ();
+ }
+ }
+}
+
+void
+Session::stop_transport (bool abort)
+{
+ if (_transport_speed == 0.0f) {
+ return;
+ }
+
+ if (actively_recording() && !(transport_sub_state & StopPendingCapture) &&
+ _worst_output_latency > current_block_size)
+ {
+
+ /* we need to capture the audio that has still not yet been received by the system
+ at the time the stop is requested, so we have to roll past that time.
+
+ we want to declick before stopping, so schedule the autostop for one
+ block before the actual end. we'll declick in the subsequent block,
+ and then we'll really be stopped.
+ */
+
+ Event *ev = new Event (Event::StopOnce, Event::Replace,
+ _transport_frame + _worst_output_latency - current_block_size,
+ 0, 0, abort);
+
+ merge_event (ev);
+ transport_sub_state |= StopPendingCapture;
+ pending_abort = abort;
+ return;
+ }
+
+ if ((transport_sub_state & PendingDeclickOut) == 0) {
+ transport_sub_state |= PendingDeclickOut;
+ /* we'll be called again after the declick */
+ return;
+ }
+
+ realtime_stop (abort);
+ schedule_butler_transport_work ();
+}
+
+void
+Session::start_transport ()
+{
+ _last_roll_location = _transport_frame;
+
+ /* if record status is Enabled, move it to Recording. if its
+ already Recording, move it to Disabled.
+ */
+
+ switch (record_status()) {
+ case Enabled:
+ if (!punch_in) {
+ enable_record ();
+ }
+ break;
+
+ case Recording:
+ disable_record ();
+ break;
+
+ default:
+ break;
+ }
+
+ if (!synced_to_jack() || _exporting) {
+ actually_start_transport ();
+ } else {
+ waiting_to_start = true;
+ }
+}
+
+void
+Session::actually_start_transport ()
+{
+ waiting_to_start = false;
+
+ transport_sub_state |= PendingDeclickIn;
+ _transport_speed = 1.0;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->realtime_set_speed ((*i)->speed(), true);
+ }
+
+ send_mmc_in_another_thread (MIDI::MachineControl::cmdDeferredPlay, 0);
+
+ TransportStateChange (); /* EMIT SIGNAL */
+}
+
+void
+Session::post_transport ()
+{
+ if (post_transport_work & PostTransportAudition) {
+ if (auditioner && auditioner->active()) {
+ process_function = &Session::process_audition;
+ } else {
+ process_function = &Session::process_with_events;
+ }
+ }
+
+ if (post_transport_work & PostTransportStop) {
+
+ transport_sub_state = 0;
+ }
+
+ if (post_transport_work & PostTransportLocate) {
+
+ if ((auto_play && !_exporting) || (post_transport_work & PostTransportRoll)) {
+
+ start_transport ();
+
+ } else {
+ transport_sub_state = 0;
+ }
+ }
+
+ set_next_event ();
+
+ post_transport_work = PostTransportWork (0);
+}
+
+void
+Session::set_rf_speed (float speed)
+{
+ rf_speed = speed;
+ cumulative_rf_motion = 0;
+ reset_rf_scale (0);
+}
+
+void
+Session::reset_rf_scale (jack_nframes_t motion)
+{
+ cumulative_rf_motion += motion;
+
+ if (cumulative_rf_motion < 4 * _current_frame_rate) {
+ rf_scale = 1;
+ } else if (cumulative_rf_motion < 8 * _current_frame_rate) {
+ rf_scale = 4;
+ } else if (cumulative_rf_motion < 16 * _current_frame_rate) {
+ rf_scale = 10;
+ } else {
+ rf_scale = 100;
+ }
+
+ if (motion != 0) {
+ set_dirty();
+ }
+}
+
+int
+Session::set_slave_source (SlaveSource src, jack_nframes_t frame)
+{
+ bool reverse = false;
+ bool non_rt_required = false;
+
+ if (_transport_speed) {
+ error << _("please stop the transport before adjusting slave settings") << endmsg;
+ /* help out non-MVC friendly UI's by telling them the slave type changed */
+ ControlChanged (SlaveType); /* EMIT SIGNAL */
+ return 0;
+ }
+
+ if (src == _slave_type) {
+ return 0;
+ }
+
+// if (src == JACK && Config->get_jack_time_master()) {
+// return -1;
+// }
+
+ if (_slave) {
+ delete _slave;
+ _slave = 0;
+ _slave_type = None;
+ }
+
+ if (_transport_speed < 0.0) {
+ reverse = true;
+ }
+
+ switch (src) {
+ case None:
+ stop_transport ();
+ break;
+
+ case MTC:
+ if (_mtc_port) {
+ try {
+ _slave = new MTC_Slave (*this, *_mtc_port);
+ }
+
+ catch (failed_constructor& err) {
+ return -1;
+ }
+
+ } else {
+ error << _("No MTC port defined: MTC slaving is impossible.") << endmsg;
+ return -1;
+ }
+ _desired_transport_speed = _transport_speed;
+ break;
+
+ case JACK:
+ _slave = new JACK_Slave (_engine.jack());
+ _desired_transport_speed = _transport_speed;
+ break;
+ };
+
+ _slave_type = src;
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ if (!(*i)->hidden()) {
+ if ((*i)->realtime_set_speed ((*i)->speed(), true)) {
+ non_rt_required = true;
+ }
+ (*i)->set_slaved (_slave);
+ }
+ }
+
+ if (reverse) {
+ reverse_diskstream_buffers ();
+ }
+
+ if (non_rt_required) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
+ schedule_butler_transport_work ();
+ }
+
+ set_dirty();
+ ControlChanged (SlaveType); /* EMIT SIGNAL */
+
+ return 0;
+}
+
+void
+Session::reverse_diskstream_buffers ()
+{
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
+ schedule_butler_transport_work ();
+}
+
+void
+Session::set_diskstream_speed (DiskStream* stream, float speed)
+{
+ if (stream->realtime_set_speed (speed, false)) {
+ post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
+ schedule_butler_transport_work ();
+ set_dirty ();
+ }
+}
+
+void
+Session::set_audio_range (list<AudioRange>& range)
+{
+ Event *ev = new Event (Event::SetAudioRange, Event::Add, Event::Immediate, 0, 0.0f);
+ ev->audio_range = range;
+ queue_event (ev);
+}
+
+void
+Session::request_play_range (bool yn)
+{
+ Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, 0.0f, yn);
+ queue_event (ev);
+}
+
+void
+Session::set_play_range (bool yn)
+{
+ /* Called from event-processing context */
+
+ if (_play_range != yn) {
+ _play_range = yn;
+ setup_auto_play ();
+
+ if (!_play_range) {
+ /* stop transport */
+ Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false);
+ merge_event (ev);
+ }
+
+ ControlChanged (PlayRange); /* EMIT SIGNAL */
+ }
+}
+
+void
+Session::setup_auto_play ()
+{
+ /* Called from event-processing context */
+
+ Event* ev;
+
+ _clear_event_type (Event::RangeStop);
+ _clear_event_type (Event::RangeLocate);
+
+ if (!_play_range) {
+ return;
+ }
+
+ list<AudioRange>::size_type sz = current_audio_range.size();
+
+ if (sz > 1) {
+
+ list<AudioRange>::iterator i = current_audio_range.begin();
+ list<AudioRange>::iterator next;
+
+ while (i != current_audio_range.end()) {
+
+ next = i;
+ ++next;
+
+ /* locating/stopping is subject to delays for declicking.
+ */
+
+ jack_nframes_t requested_frame = (*i).end;
+
+ if (requested_frame > current_block_size) {
+ requested_frame -= current_block_size;
+ } else {
+ requested_frame = 0;
+ }
+
+ if (next == current_audio_range.end()) {
+ ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f);
+ } else {
+ ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f);
+ }
+
+ merge_event (ev);
+
+ i = next;
+ }
+
+ } else if (sz == 1) {
+
+ ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f);
+ merge_event (ev);
+
+ }
+
+ /* now start rolling at the right place */
+
+ ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false);
+ merge_event (ev);
+}
+
+void
+Session::request_bounded_roll (jack_nframes_t start, jack_nframes_t end)
+{
+ request_stop ();
+ Event *ev = new Event (Event::StopOnce, Event::Replace, Event::Immediate, end, 0.0);
+ queue_event (ev);
+ request_locate (start, true);
+}
+
+void
+Session::engine_halted ()
+{
+ /* there will be no more calls to process(), so
+ we'd better clean up for ourselves, right now.
+
+ but first, make sure the butler is out of
+ the picture.
+ */
+
+ atomic_set (&butler_should_do_transport_work, 0);
+ post_transport_work = PostTransportWork (0);
+ stop_butler ();
+
+ realtime_stop (false);
+ non_realtime_stop (false);
+ transport_sub_state = 0;
+
+ TransportStateChange (); /* EMIT SIGNAL */
+}
+
+
+void
+Session::xrun_recovery ()
+{
+ if (Config->get_stop_recording_on_xrun() && actively_recording()) {
+
+ HaltOnXrun (); /* EMIT SIGNAL */
+
+ /* it didn't actually halt, but we need
+ to handle things in the same way.
+ */
+
+ engine_halted();
+ }
+}
+
+void
+Session::update_latency_compensation (bool with_stop, bool abort)
+{
+ bool update_jack = false;
+
+ if (_state_of_the_state & Deletion) {
+ return;
+ }
+
+ LockMonitor lm (route_lock, __LINE__, __FILE__);
+ LockMonitor lm2 (diskstream_lock, __LINE__, __FILE__);
+ _worst_track_latency = 0;
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ if (with_stop) {
+ (*i)->transport_stopped (abort, (post_transport_work & PostTransportLocate),
+ (!(post_transport_work & PostTransportLocate) || pending_locate_flush));
+ }
+
+ jack_nframes_t old_latency = (*i)->signal_latency ();
+ jack_nframes_t track_latency = (*i)->update_total_latency ();
+
+ if (old_latency != track_latency) {
+ update_jack = true;
+ }
+
+ if (!(*i)->hidden() && ((*i)->active())) {
+ _worst_track_latency = max (_worst_track_latency, track_latency);
+ }
+ }
+
+ for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
+ (*i)->set_latency_delay (_worst_track_latency);
+ }
+
+ /* tell JACK to play catch up */
+
+ if (update_jack) {
+ _engine.update_total_latencies ();
+ }
+
+ set_worst_io_latencies (false);
+
+ /* reflect any changes in latencies into capture offsets
+ */
+
+ for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) {
+ (*i)->set_capture_offset ();
+ }
+}
+
+void
+Session::update_latency_compensation_proxy (void* ignored)
+{
+ update_latency_compensation (false, false);
+}
diff --git a/libs/ardour/session_vst.cc b/libs/ardour/session_vst.cc
new file mode 100644
index 0000000000..70bf1a8042
--- /dev/null
+++ b/libs/ardour/session_vst.cc
@@ -0,0 +1,316 @@
+/*
+ Copyright (C) 2004
+
+ 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.
+
+ $Id$
+*/
+
+#include <stdbool.h>
+#include <cstdio>
+
+#include <fst.h>
+#include <vst/aeffectx.h>
+
+#include <ardour/session.h>
+#include <ardour/vst_plugin.h>
+
+#include "i18n.h"
+
+// #define DEBUG_CALLBACKS
+
+#ifdef DEBUG_CALLBACKS
+#define SHOW_CALLBACK printf
+#else
+#define SHOW_CALLBACK(...)
+#endif
+
+using namespace ARDOUR;
+
+long Session::vst_callback (AEffect* effect,
+ long opcode,
+ long index,
+ long value,
+ void* ptr,
+ float opt)
+{
+ static VstTimeInfo _timeInfo;
+ VSTPlugin* plug;
+ Session* session;
+
+ SHOW_CALLBACK ("am callback, opcode = %d", opcode);
+
+ if (effect && effect->user) {
+ plug = static_cast<VSTPlugin*> (effect->user);
+ session = &plug->session();
+ } else {
+ plug = 0;
+ session = 0;
+ }
+
+ switch(opcode){
+
+ case audioMasterAutomate:
+ SHOW_CALLBACK ("amc: audioMasterAutomate\n");
+ // index, value, returns 0
+ if (effect) {
+ effect->setParameter (effect, index, opt);
+ }
+ return 0;
+
+ case audioMasterVersion:
+ SHOW_CALLBACK ("amc: audioMasterVersion\n");
+ // vst version, currently 2 (0 for older)
+ return 2;
+
+ case audioMasterCurrentId:
+ SHOW_CALLBACK ("amc: audioMasterCurrentId\n");
+ // returns the unique id of a plug that's currently
+ // loading
+ return 0;
+
+ case audioMasterIdle:
+ SHOW_CALLBACK ("amc: audioMasterIdle\n");
+ // call application idle routine (this will
+ // call effEditIdle for all open editors too)
+ if (effect) {
+ effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f);
+ }
+ return 0;
+
+ case audioMasterPinConnected:
+ SHOW_CALLBACK ("amc: audioMasterPinConnected\n");
+ // inquire if an input or output is beeing connected;
+ // index enumerates input or output counting from zero:
+ // value is 0 for input and != 0 otherwise. note: the
+ // return value is 0 for <true> such that older versions
+ // will always return true.
+ return 1;
+
+ case audioMasterWantMidi:
+ SHOW_CALLBACK ("amc: audioMasterWantMidi\n");
+ // <value> is a filter which is currently ignored
+ return 0;
+
+ case audioMasterGetTime:
+ SHOW_CALLBACK ("amc: audioMasterGetTime\n");
+ // returns const VstTimeInfo* (or 0 if not supported)
+ // <value> should contain a mask indicating which fields are required
+ // (see valid masks above), as some items may require extensive
+ // conversions
+ memset(&_timeInfo, 0, sizeof(_timeInfo));
+ if (session) {
+ _timeInfo.samplePos = session->transport_frame();
+ _timeInfo.sampleRate = session->frame_rate();
+ }
+ return (long)&_timeInfo;
+
+ case audioMasterProcessEvents:
+ SHOW_CALLBACK ("amc: audioMasterProcessEvents\n");
+ // VstEvents* in <ptr>
+ return 0;
+
+ case audioMasterSetTime:
+ SHOW_CALLBACK ("amc: audioMasterSetTime\n");
+ // VstTimenfo* in <ptr>, filter in <value>, not supported
+
+ case audioMasterTempoAt:
+ SHOW_CALLBACK ("amc: audioMasterTempoAt\n");
+ // returns tempo (in bpm * 10000) at sample frame location passed in <value>
+ return 0;
+
+ case audioMasterGetNumAutomatableParameters:
+ SHOW_CALLBACK ("amc: audioMasterGetNumAutomatableParameters\n");
+ return 0;
+
+ case audioMasterGetParameterQuantization:
+ SHOW_CALLBACK ("amc: audioMasterGetParameterQuantization\n");
+ // returns the integer value for +1.0 representation,
+ // or 1 if full single float precision is maintained
+ // in automation. parameter index in <value> (-1: all, any)
+ return 0;
+
+ case audioMasterIOChanged:
+ SHOW_CALLBACK ("amc: audioMasterIOChanged\n");
+ // numInputs and/or numOutputs has changed
+ return 0;
+
+ case audioMasterNeedIdle:
+ SHOW_CALLBACK ("amc: audioMasterNeedIdle\n");
+ // plug needs idle calls (outside its editor window)
+ return 0;
+
+ case audioMasterSizeWindow:
+ SHOW_CALLBACK ("amc: audioMasterSizeWindow\n");
+ // index: width, value: height
+ return 0;
+
+ case audioMasterGetSampleRate:
+ SHOW_CALLBACK ("amc: audioMasterGetSampleRate\n");
+ return 0;
+
+ case audioMasterGetBlockSize:
+ SHOW_CALLBACK ("amc: audioMasterGetBlockSize\n");
+ return 0;
+
+ case audioMasterGetInputLatency:
+ SHOW_CALLBACK ("amc: audioMasterGetInputLatency\n");
+ return 0;
+
+ case audioMasterGetOutputLatency:
+ SHOW_CALLBACK ("amc: audioMasterGetOutputLatency\n");
+ return 0;
+
+ case audioMasterGetPreviousPlug:
+ SHOW_CALLBACK ("amc: audioMasterGetPreviousPlug\n");
+ // input pin in <value> (-1: first to come), returns cEffect*
+ return 0;
+
+ case audioMasterGetNextPlug:
+ SHOW_CALLBACK ("amc: audioMasterGetNextPlug\n");
+ // output pin in <value> (-1: first to come), returns cEffect*
+
+ case audioMasterWillReplaceOrAccumulate:
+ SHOW_CALLBACK ("amc: audioMasterWillReplaceOrAccumulate\n");
+ // returns: 0: not supported, 1: replace, 2: accumulate
+ return 0;
+
+ case audioMasterGetCurrentProcessLevel:
+ SHOW_CALLBACK ("amc: audioMasterGetCurrentProcessLevel\n");
+ // returns: 0: not supported,
+ // 1: currently in user thread (gui)
+ // 2: currently in audio thread (where process is called)
+ // 3: currently in 'sequencer' thread (midi, timer etc)
+ // 4: currently offline processing and thus in user thread
+ // other: not defined, but probably pre-empting user thread.
+ return 0;
+
+ case audioMasterGetAutomationState:
+ SHOW_CALLBACK ("amc: audioMasterGetAutomationState\n");
+ // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write
+ // offline
+ return 0;
+
+ case audioMasterOfflineStart:
+ SHOW_CALLBACK ("amc: audioMasterOfflineStart\n");
+ case audioMasterOfflineRead:
+ SHOW_CALLBACK ("amc: audioMasterOfflineRead\n");
+ // ptr points to offline structure, see below. return 0: error, 1 ok
+ return 0;
+
+ case audioMasterOfflineWrite:
+ SHOW_CALLBACK ("amc: audioMasterOfflineWrite\n");
+ // same as read
+ return 0;
+
+ case audioMasterOfflineGetCurrentPass:
+ SHOW_CALLBACK ("amc: audioMasterOfflineGetCurrentPass\n");
+ case audioMasterOfflineGetCurrentMetaPass:
+ SHOW_CALLBACK ("amc: audioMasterOfflineGetCurrentMetaPass\n");
+ return 0;
+
+ case audioMasterSetOutputSampleRate:
+ SHOW_CALLBACK ("amc: audioMasterSetOutputSampleRate\n");
+ // for variable i/o, sample rate in <opt>
+ return 0;
+
+ case audioMasterGetSpeakerArrangement:
+ SHOW_CALLBACK ("amc: audioMasterGetSpeakerArrangement\n");
+ // (long)input in <value>, output in <ptr>
+ return 0;
+
+ case audioMasterGetVendorString:
+ SHOW_CALLBACK ("amc: audioMasterGetVendorString\n");
+ // fills <ptr> with a string identifying the vendor (max 64 char)
+ strcpy ((char*) ptr, "Linux Audio Systems");
+ return 0;
+
+ case audioMasterGetProductString:
+ SHOW_CALLBACK ("amc: audioMasterGetProductString\n");
+ // fills <ptr> with a string with product name (max 64 char)
+ strcpy ((char*) ptr, "Ardour");
+ return 0;
+
+ case audioMasterGetVendorVersion:
+ SHOW_CALLBACK ("amc: audioMasterGetVendorVersion\n");
+ // returns vendor-specific version
+ return 900;
+
+ case audioMasterVendorSpecific:
+ SHOW_CALLBACK ("amc: audioMasterVendorSpecific\n");
+ // no definition, vendor specific handling
+ return 0;
+
+ case audioMasterSetIcon:
+ SHOW_CALLBACK ("amc: audioMasterSetIcon\n");
+ // void* in <ptr>, format not defined yet
+ return 0;
+
+ case audioMasterCanDo:
+ SHOW_CALLBACK ("amc: audioMasterCanDo\n");
+ // string in ptr, see below
+ return 0;
+
+ case audioMasterGetLanguage:
+ SHOW_CALLBACK ("amc: audioMasterGetLanguage\n");
+ // see enum
+ return 0;
+
+ case audioMasterOpenWindow:
+ SHOW_CALLBACK ("amc: audioMasterOpenWindow\n");
+ // returns platform specific ptr
+ return 0;
+
+ case audioMasterCloseWindow:
+ SHOW_CALLBACK ("amc: audioMasterCloseWindow\n");
+ // close window, platform specific handle in <ptr>
+ return 0;
+
+ case audioMasterGetDirectory:
+ SHOW_CALLBACK ("amc: audioMasterGetDirectory\n");
+ // get plug directory, FSSpec on MAC, else char*
+ return 0;
+
+ case audioMasterUpdateDisplay:
+ SHOW_CALLBACK ("amc: audioMasterUpdateDisplay\n");
+ // something has changed, update 'multi-fx' display
+ if (effect) {
+ effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f);
+ }
+ return 0;
+
+ case audioMasterBeginEdit:
+ SHOW_CALLBACK ("amc: audioMasterBeginEdit\n");
+ // begin of automation session (when mouse down), parameter index in <index>
+ return 0;
+
+ case audioMasterEndEdit:
+ SHOW_CALLBACK ("amc: audioMasterEndEdit\n");
+ // end of automation session (when mouse up), parameter index in <index>
+ return 0;
+
+ case audioMasterOpenFileSelector:
+ SHOW_CALLBACK ("amc: audioMasterOpenFileSelector\n");
+ // open a fileselector window with VstFileSelect* in <ptr>
+ return 0;
+
+ default:
+ SHOW_CALLBACK ("VST master dispatcher: undefed: %d, %d\n", opcode, effKeysRequired);
+ break;
+ }
+
+ return 0;
+}
+
diff --git a/libs/ardour/sndfile_helpers.cc b/libs/ardour/sndfile_helpers.cc
new file mode 100644
index 0000000000..d9e7aa563a
--- /dev/null
+++ b/libs/ardour/sndfile_helpers.cc
@@ -0,0 +1,162 @@
+#include <map>
+
+#include <ardour/sndfile_helpers.h>
+
+#include "i18n.h"
+
+using std::map;
+
+const char * const sndfile_header_formats_strings[SNDFILE_HEADER_FORMATS+1] = {
+ N_("WAV"),
+ N_("AIFF"),
+ N_("raw (no header)"),
+ N_("PAF (Ensoniq Paris)"),
+ N_("AU (Sun/NeXT)"),
+ N_("IRCAM"),
+ N_("W64 (64 bit WAV)"),
+ 0
+};
+
+int sndfile_header_formats[SNDFILE_HEADER_FORMATS] = {
+ SF_FORMAT_WAV,
+ SF_FORMAT_AIFF,
+ SF_FORMAT_RAW,
+ SF_FORMAT_PAF,
+ SF_FORMAT_AU,
+ SF_FORMAT_IRCAM,
+ SF_FORMAT_W64
+};
+
+const char * const sndfile_bitdepth_formats_strings[SNDFILE_BITDEPTH_FORMATS+1] = {
+ N_("16 bit"),
+ N_("24 bit"),
+ N_("32 bit"),
+ N_("8 bit"),
+ N_("float"),
+ 0
+};
+
+int sndfile_bitdepth_formats[SNDFILE_BITDEPTH_FORMATS] = {
+ SF_FORMAT_PCM_16,
+ SF_FORMAT_PCM_24,
+ SF_FORMAT_PCM_32,
+ SF_FORMAT_PCM_S8,
+ SF_FORMAT_FLOAT
+};
+
+const char * const sndfile_endian_formats_strings[SNDFILE_ENDIAN_FORMATS+1] = {
+ N_("Little-endian (Intel)"),
+ N_("Big-endian (Mac)"),
+ 0
+};
+
+int sndfile_endian_formats[SNDFILE_ENDIAN_FORMATS] = {
+ SF_ENDIAN_LITTLE,
+ SF_ENDIAN_BIG
+};
+
+int
+sndfile_header_format_from_string (string str)
+{
+ for (int n = 0; sndfile_header_formats_strings[n]; ++n) {
+ if (str == sndfile_header_formats_strings[n]) {
+ return sndfile_header_formats[n];
+ }
+ }
+ return -1;
+}
+
+int
+sndfile_bitdepth_format_from_string (string str)
+{
+ for (int n = 0; sndfile_bitdepth_formats_strings[n]; ++n) {
+ if (str == sndfile_bitdepth_formats_strings[n]) {
+ return sndfile_bitdepth_formats[n];
+ }
+ }
+ return -1;
+}
+
+int
+sndfile_endian_format_from_string (string str)
+{
+ for (int n = 0; sndfile_endian_formats_strings[n]; ++n) {
+ if (str == sndfile_endian_formats_strings[n]) {
+ return sndfile_endian_formats[n];
+ }
+ }
+ return -1;
+}
+
+int
+sndfile_data_width (int format)
+{
+ int tval = format & 0xf;
+
+ switch (tval) {
+ case SF_FORMAT_PCM_S8:
+ case SF_FORMAT_PCM_U8:
+ return 8;
+ case SF_FORMAT_PCM_16:
+ return 16;
+ case SF_FORMAT_PCM_24:
+ return 24;
+ case SF_FORMAT_PCM_32:
+ return 32;
+ case SF_FORMAT_FLOAT:
+ return 1; // heh, heh
+ default:
+ // we don't handle anything else within ardour
+ return 0;
+ }
+}
+
+string
+sndfile_major_format(int format)
+{
+ static map<int, string> m;
+
+ if(m.empty()){
+ SF_FORMAT_INFO format_info;
+ int count;
+ sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof (int));
+ for (int i = 0; i < count; ++i){
+ format_info.format = i;
+ sf_command (0, SFC_GET_FORMAT_MAJOR,
+ &format_info, sizeof (format_info));
+ m[format_info.format & SF_FORMAT_TYPEMASK] = format_info.name;
+ }
+ }
+
+ map<int, string>::iterator p = m.find(format & SF_FORMAT_TYPEMASK);
+ if(p != m.end()){
+ return m[format & SF_FORMAT_TYPEMASK];
+ } else {
+ return "-Unknown-";
+ }
+}
+
+string
+sndfile_minor_format(int format)
+{
+ static map<int, string> m;
+
+ if(m.empty()){
+ SF_FORMAT_INFO format_info;
+ int count;
+ sf_command(0, SFC_GET_FORMAT_SUBTYPE_COUNT, &count, sizeof (int));
+ for (int i = 0; i < count; ++i){
+ format_info.format = i;
+ sf_command (0, SFC_GET_FORMAT_SUBTYPE,
+ &format_info, sizeof (format_info));
+ m[format_info.format & SF_FORMAT_SUBMASK] = format_info.name;
+ }
+ }
+
+ map<int, string>::iterator p = m.find(format & SF_FORMAT_SUBMASK);
+ if(p != m.end()){
+ return m[format & SF_FORMAT_SUBMASK];
+ } else {
+ return "-Unknown-";
+ }
+}
diff --git a/libs/ardour/sndfilesource.cc b/libs/ardour/sndfilesource.cc
new file mode 100644
index 0000000000..399f632caa
--- /dev/null
+++ b/libs/ardour/sndfilesource.cc
@@ -0,0 +1,207 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <string>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include <pbd/mountpoint.h>
+#include <ardour/sndfilesource.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+string SndFileSource::peak_dir = "";
+
+SndFileSource::SndFileSource (const XMLNode& node)
+ : Source (node)
+{
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+
+ init (_name, true);
+ SourceCreated (this); /* EMIT SIGNAL */
+}
+
+SndFileSource::SndFileSource (const string& idstr, bool build_peak)
+ : Source(build_peak)
+{
+ init (idstr, build_peak);
+
+ if (build_peak) {
+ SourceCreated (this); /* EMIT SIGNAL */
+ }
+}
+
+void
+SndFileSource::init (const string& idstr, bool build_peak)
+{
+ string::size_type pos;
+ string file;
+
+ tmpbuf = 0;
+ tmpbufsize = 0;
+ sf = 0;
+
+ _name = idstr;
+
+ if ((pos = idstr.find_last_of (':')) == string::npos) {
+ channel = 0;
+ file = idstr;
+ } else {
+ channel = atoi (idstr.substr (pos+1).c_str());
+ file = idstr.substr (0, pos);
+ }
+
+ /* although libsndfile says we don't need to set this,
+ valgrind and source code shows us that we do.
+ */
+
+ memset (&_info, 0, sizeof(_info));
+
+ /* note that we temporarily truncated _id at the colon */
+
+ if ((sf = sf_open (file.c_str(), SFM_READ, &_info)) == 0) {
+ char errbuf[256];
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ error << compose(_("SndFileSource: cannot open file \"%1\" (%2)"), file, errbuf) << endmsg;
+ throw failed_constructor();
+ }
+
+ if (channel >= _info.channels) {
+ error << compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, channel) << endmsg;
+ sf_close (sf);
+ sf = 0;
+ throw failed_constructor();
+ }
+
+ _length = _info.frames;
+ _path = file;
+
+ if (build_peak) {
+ if (initialize_peakfile (false, file)) {
+ sf_close (sf);
+ sf = 0;
+ throw failed_constructor ();
+ }
+ }
+}
+
+SndFileSource::~SndFileSource ()
+
+{
+ GoingAway (this); /* EMIT SIGNAL */
+
+ if (sf) {
+ sf_close (sf);
+ }
+
+ if (tmpbuf) {
+ delete [] tmpbuf;
+ }
+}
+
+jack_nframes_t
+SndFileSource::read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const
+{
+ return read (dst, start, cnt);
+}
+
+jack_nframes_t
+SndFileSource::read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt) const
+{
+ int32_t nread;
+ float *ptr;
+ uint32_t real_cnt;
+
+ if (sf_seek (sf, (off_t) start, SEEK_SET) < 0) {
+ char errbuf[256];
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ error << compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.substr (1), errbuf) << endmsg;
+ return 0;
+ }
+
+ if (_info.channels == 1) {
+ jack_nframes_t ret = sf_read_float (sf, dst, cnt);
+ _read_data_count = cnt * sizeof(float);
+ return ret;
+ }
+
+ real_cnt = cnt * _info.channels;
+
+ {
+ LockMonitor lm (_tmpbuf_lock, __LINE__, __FILE__);
+
+ if (tmpbufsize < real_cnt) {
+
+ if (tmpbuf) {
+ delete [] tmpbuf;
+ }
+ tmpbufsize = real_cnt;
+ tmpbuf = new float[tmpbufsize];
+ }
+
+ nread = sf_read_float (sf, tmpbuf, real_cnt);
+ ptr = tmpbuf + channel;
+ nread /= _info.channels;
+
+ /* stride through the interleaved data */
+
+ for (int32_t n = 0; n < nread; ++n) {
+ dst[n] = *ptr;
+ ptr += _info.channels;
+ }
+ }
+
+ _read_data_count = cnt * sizeof(float);
+
+ return nread;
+}
+
+string
+SndFileSource::peak_path (string audio_path)
+{
+ /* XXX hardly bombproof! fix me */
+
+ struct stat stat_file;
+ struct stat stat_mount;
+
+ string mp = mountpoint (audio_path);
+
+ stat (audio_path.c_str(), &stat_file);
+ stat (mp.c_str(), &stat_mount);
+
+ char buf[32];
+ snprintf (buf, sizeof (buf), "%ld-%ld-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
+
+ string res = peak_dir;
+ res += buf;
+
+ return res;
+}
+
+string
+SndFileSource::old_peak_path (string audio_path)
+{
+ return peak_path (audio_path);
+}
diff --git a/libs/ardour/source.cc b/libs/ardour/source.cc
new file mode 100644
index 0000000000..c7db3337ca
--- /dev/null
+++ b/libs/ardour/source.cc
@@ -0,0 +1,842 @@
+/*
+ Copyright (C) 2000 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.
+
+ $Id$
+*/
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <float.h>
+#include <cerrno>
+#include <ctime>
+#include <cmath>
+#include <iomanip>
+#include <algorithm>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+#include <pbd/pthread_utils.h>
+
+#include <ardour/source.h>
+#include <ardour/filesource.h>
+#include <ardour/sndfilesource.h>
+
+#include "i18n.h"
+
+using std::min;
+using std::max;
+
+using namespace ARDOUR;
+using namespace PBD;
+
+sigc::signal<void,Source *> Source::SourceCreated;
+pthread_t Source::peak_thread;
+bool Source::have_peak_thread = false;
+vector<Source*> Source::pending_peak_sources;
+PBD::Lock Source::pending_peak_sources_lock;
+int Source::peak_request_pipe[2];
+
+bool Source::_build_missing_peakfiles = false;
+bool Source::_build_peakfiles = false;
+
+Source::Source (bool announce)
+{
+ _id = ARDOUR::new_id();
+ _use_cnt = 0;
+ _peaks_built = false;
+ next_peak_clear_should_notify = true;
+ peakfile = -1;
+ _timestamp = 0;
+ _read_data_count = 0;
+ _write_data_count = 0;
+}
+
+Source::Source (const XMLNode& node)
+{
+ _use_cnt = 0;
+ _peaks_built = false;
+ next_peak_clear_should_notify = true;
+ peakfile = -1;
+ _timestamp = 0;
+ _read_data_count = 0;
+ _write_data_count = 0;
+
+ if (set_state (node)) {
+ throw failed_constructor();
+ }
+}
+
+Source::~Source ()
+{
+ if (peakfile >= 0) {
+ close (peakfile);
+ }
+}
+
+XMLNode&
+Source::get_state ()
+{
+ XMLNode *node = new XMLNode ("Source");
+ char buf[64];
+
+ node->add_property ("name", _name);
+ snprintf (buf, sizeof(buf)-1, "%llu", _id);
+ node->add_property ("id", buf);
+
+ if (_timestamp != 0) {
+ snprintf (buf, sizeof (buf), "%ld", _timestamp);
+ node->add_property ("timestamp", buf);
+ }
+
+ if (_captured_for.length()) {
+ node->add_property ("captured-for", _captured_for);
+ }
+
+ return *node;
+}
+
+int
+Source::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+
+ if ((prop = node.property ("name")) != 0) {
+ _name = prop->value();
+ } else {
+ return -1;
+ }
+
+ if ((prop = node.property ("id")) != 0) {
+ sscanf (prop->value().c_str(), "%llu", &_id);
+ } else {
+ return -1;
+ }
+
+ if ((prop = node.property ("timestamp")) != 0) {
+ sscanf (prop->value().c_str(), "%ld", &_timestamp);
+ }
+
+ if ((prop = node.property ("captured-for")) != 0) {
+ _captured_for = prop->value();
+ }
+
+ return 0;
+}
+
+/***********************************************************************
+ PEAK FILE STUFF
+ ***********************************************************************/
+
+void*
+Source::peak_thread_work (void* arg)
+{
+ PBD::ThreadCreated (pthread_self(), X_("Peak"));
+ struct pollfd pfd[1];
+
+ LockMonitor lm (pending_peak_sources_lock, __LINE__, __FILE__);
+
+ while (true) {
+
+ pfd[0].fd = peak_request_pipe[0];
+ pfd[0].events = POLLIN|POLLERR|POLLHUP;
+
+ pthread_mutex_unlock (pending_peak_sources_lock.mutex());
+
+ if (poll (pfd, 1, -1) < 0) {
+
+ if (errno == EINTR) {
+ pthread_mutex_lock (pending_peak_sources_lock.mutex());
+ continue;
+ }
+
+ error << compose (_("poll on peak request pipe failed (%1)"),
+ strerror (errno))
+ << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & ~POLLIN) {
+ error << _("Error on peak thread request pipe") << endmsg;
+ break;
+ }
+
+ if (pfd[0].revents & POLLIN) {
+
+ char req;
+
+ /* empty the pipe of all current requests */
+
+ while (1) {
+ size_t nread = ::read (peak_request_pipe[0], &req, sizeof (req));
+
+ if (nread == 1) {
+ switch ((PeakRequest::Type) req) {
+
+ case PeakRequest::Build:
+ break;
+
+ case PeakRequest::Quit:
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ break;
+
+ default:
+ break;
+ }
+
+ } else if (nread == 0) {
+ break;
+ } else if (errno == EAGAIN) {
+ break;
+ } else {
+ fatal << _("Error reading from peak request pipe") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+ }
+
+ pthread_mutex_lock (pending_peak_sources_lock.mutex());
+
+ while (!pending_peak_sources.empty()) {
+
+ Source* s = pending_peak_sources.front();
+ pending_peak_sources.erase (pending_peak_sources.begin());
+
+ pthread_mutex_unlock (pending_peak_sources_lock.mutex());
+ s->build_peaks();
+ pthread_mutex_lock (pending_peak_sources_lock.mutex());
+ }
+ }
+
+ pthread_exit_pbd (0);
+ /*NOTREACHED*/
+ return 0;
+}
+
+int
+Source::start_peak_thread ()
+{
+ if (!_build_peakfiles) {
+ return 0;
+ }
+
+ if (pipe (peak_request_pipe)) {
+ error << compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (peak_request_pipe[0], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on peak request pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (fcntl (peak_request_pipe[1], F_SETFL, O_NONBLOCK)) {
+ error << compose(_("UI: cannot set O_NONBLOCK on peak request pipe (%1)"), strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (pthread_create_and_store ("peak file builder", &peak_thread, 0, peak_thread_work, 0)) {
+ error << _("Source: could not create peak thread") << endmsg;
+ return -1;
+ }
+
+ have_peak_thread = true;
+ return 0;
+}
+
+void
+Source::stop_peak_thread ()
+{
+ if (!have_peak_thread) {
+ return;
+ }
+
+ void* status;
+
+ char c = (char) PeakRequest::Quit;
+ ::write (peak_request_pipe[1], &c, 1);
+ pthread_join (peak_thread, &status);
+}
+
+void
+Source::queue_for_peaks (Source& source)
+{
+ if (have_peak_thread) {
+
+ LockMonitor lm (pending_peak_sources_lock, __LINE__, __FILE__);
+
+ source.next_peak_clear_should_notify = true;
+
+ if (find (pending_peak_sources.begin(),
+ pending_peak_sources.end(),
+ &source) == pending_peak_sources.end()) {
+ pending_peak_sources.push_back (&source);
+ }
+
+ char c = (char) PeakRequest::Build;
+ ::write (peak_request_pipe[1], &c, 1);
+ }
+}
+
+void Source::clear_queue_for_peaks ()
+{
+ /* this is done to cancel a group of running peak builds */
+ if (have_peak_thread) {
+ LockMonitor lm (pending_peak_sources_lock, __LINE__, __FILE__);
+ pending_peak_sources.clear ();
+ }
+}
+
+
+bool
+Source::peaks_ready (sigc::slot<void> the_slot) const
+{
+ bool ret;
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+
+ /* check to see if the peak data is ready. if not
+ connect the slot while still holding the lock.
+ */
+
+ if (!(ret = _peaks_built)) {
+ PeaksReady.connect (the_slot);
+ }
+
+ return ret;
+}
+
+int
+Source::initialize_peakfile (bool newfile, string audio_path)
+{
+ struct stat statbuf;
+
+ peakpath = peak_path (audio_path);
+
+ if (newfile) {
+
+ if (!_build_peakfiles) {
+ return 0;
+ }
+
+ _peaks_built = false;
+
+ } else {
+
+ if (stat (peakpath.c_str(), &statbuf)) {
+ if (errno != ENOENT) {
+ /* it exists in the peaks dir, but there is some kind of error */
+
+ error << compose(_("Source: cannot stat peakfile \"%1\""), peakpath) << endmsg;
+ return -1;
+ }
+
+ /* older sessions stored peaks in the same directory
+ as the audio. so check there as well.
+ */
+
+ string oldpeakpath = old_peak_path (audio_path);
+
+ if (stat (oldpeakpath.c_str(), &statbuf)) {
+
+ if (errno == ENOENT) {
+
+ statbuf.st_size = 0;
+
+ } else {
+
+ /* it exists in the audio dir , but there is some kind of error */
+
+ error << compose(_("Source: cannot stat peakfile \"%1\" or \"%2\""), peakpath, oldpeakpath) << endmsg;
+ return -1;
+ }
+
+ } else {
+
+ /* we found it in the sound dir, where they lived once upon a time, in a land ... etc. */
+
+ peakpath = oldpeakpath;
+ }
+
+ } else {
+
+ /* we found it in the peaks dir */
+ }
+
+ if (statbuf.st_size == 0) {
+ _peaks_built = false;
+ } else {
+ // Check if the audio file has changed since the peakfile was built.
+ struct stat stat_file;
+ int err = stat (audio_path.c_str(), &stat_file);
+
+ if (!err && stat_file.st_mtime > statbuf.st_mtime){
+ _peaks_built = false;
+ } else {
+ _peaks_built = true;
+ }
+ }
+ }
+
+ if ((peakfile = ::open (peakpath.c_str(), O_RDWR|O_CREAT, 0664)) < 0) {
+ error << compose(_("Source: cannot open peakpath \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (!newfile && !_peaks_built && _build_missing_peakfiles && _build_peakfiles) {
+ build_peaks_from_scratch ();
+ }
+
+ return 0;
+}
+
+int
+Source::read_peaks (PeakData *peaks, jack_nframes_t npeaks, jack_nframes_t start, jack_nframes_t cnt, double samples_per_visual_peak) const
+{
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+ double scale;
+ double expected_peaks;
+ PeakData::PeakDatum xmax;
+ PeakData::PeakDatum xmin;
+ int32_t to_read;
+ uint32_t nread;
+ jack_nframes_t zero_fill = 0;
+ int ret = -1;
+ PeakData* staging = 0;
+ Sample* raw_staging = 0;
+
+ expected_peaks = (cnt / (double) frames_per_peak);
+ scale = npeaks/expected_peaks;
+
+#if 0
+ cerr << "======>RP: npeaks = " << npeaks
+ << " start = " << start
+ << " cnt = " << cnt
+ << " len = " << _length
+ << " samples_per_visual_peak =" << samples_per_visual_peak
+ << " expected was " << expected_peaks << " ... scale = " << scale
+ << " PD ptr = " << peaks
+ <<endl;
+
+#endif
+
+ /* fix for near-end-of-file conditions */
+
+ if (cnt > _length - start) {
+ // cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << endl;
+ cnt = _length - start;
+ jack_nframes_t old = npeaks;
+ npeaks = min ((jack_nframes_t) floor (cnt / samples_per_visual_peak), npeaks);
+ zero_fill = old - npeaks;
+ }
+
+ // cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl;
+
+ if (npeaks == cnt) {
+
+ // cerr << "RAW DATA\n";
+
+ /* no scaling at all, just get the sample data and duplicate it for
+ both max and min peak values.
+ */
+
+ Sample* raw_staging = new Sample[cnt];
+
+ if (read_unlocked (raw_staging, start, cnt) != cnt) {
+ error << _("cannot read sample data for unscaled peak computation") << endmsg;
+ return -1;
+ }
+
+ for (jack_nframes_t i = 0; i < npeaks; ++i) {
+ peaks[i].max = raw_staging[i];
+ peaks[i].min = raw_staging[i];
+ }
+
+ delete [] raw_staging;
+
+ return 0;
+ }
+
+ if (scale == 1.0) {
+
+ // cerr << "DIRECT PEAKS\n";
+
+ off_t first_peak_byte = (start / frames_per_peak) * sizeof (PeakData);
+
+ if ((nread = ::pread (peakfile, peaks, sizeof (PeakData)* npeaks, first_peak_byte)) != sizeof (PeakData) * npeaks) {
+ cerr << "Source["
+ << _name
+ << "]: cannot read peaks from peakfile! (read only "
+ << nread
+ << " not "
+ << npeaks
+ << "at sample "
+ << start
+ << " = byte "
+ << first_peak_byte
+ << ')'
+ << endl;
+ return -1;
+ }
+
+ if (zero_fill) {
+ memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
+ }
+
+ return 0;
+ }
+
+
+ jack_nframes_t tnp;
+
+ if (scale < 1.0) {
+
+ // cerr << "DOWNSAMPLE\n";
+
+ /* the caller wants:
+
+ - more frames-per-peak (lower resolution) than the peakfile, or to put it another way,
+ - less peaks than the peakfile holds for the same range
+
+ So, read a block into a staging area, and then downsample from there.
+
+ to avoid confusion, I'll refer to the requested peaks as visual_peaks and the peakfile peaks as stored_peaks
+ */
+
+ const uint32_t chunksize = (uint32_t) min (expected_peaks, 4096.0);
+
+ staging = new PeakData[chunksize];
+
+ /* compute the rounded up frame position */
+
+ jack_nframes_t current_frame = start;
+ jack_nframes_t current_stored_peak = (jack_nframes_t) ceil (current_frame / (double) frames_per_peak);
+ uint32_t next_visual_peak = (uint32_t) ceil (current_frame / samples_per_visual_peak);
+ double next_visual_peak_frame = next_visual_peak * samples_per_visual_peak;
+ uint32_t stored_peak_before_next_visual_peak = (jack_nframes_t) next_visual_peak_frame / frames_per_peak;
+ uint32_t nvisual_peaks = 0;
+ uint32_t stored_peaks_read = 0;
+ uint32_t i = 0;
+
+ /* handle the case where the initial visual peak is on a pixel boundary */
+
+ current_stored_peak = min (current_stored_peak, stored_peak_before_next_visual_peak);
+
+ while (nvisual_peaks < npeaks) {
+
+ if (i == stored_peaks_read) {
+
+ uint32_t start_byte = current_stored_peak * sizeof(PeakData);
+ tnp = min ((_length/frames_per_peak - current_stored_peak), (jack_nframes_t) expected_peaks);
+ to_read = min (chunksize, tnp);
+
+ if ((nread = ::pread (peakfile, staging, sizeof (PeakData) * to_read, start_byte))
+ != sizeof (PeakData) * to_read) {
+ cerr << "Source["
+ << _name
+ << "]: cannot read peak data from peakfile ("
+ << nread
+ << " peaks instead of "
+ << to_read
+ << ") ("
+ << strerror (errno)
+ << ')'
+ << " at start_byte = " << start_byte
+ << " _length = " << _length
+ << " expected maxpeaks = " << (_length - current_frame)/frames_per_peak
+ << " npeaks was " << npeaks
+ << endl;
+ goto out;
+ }
+
+ i = 0;
+ stored_peaks_read = nread / sizeof(PeakData);
+ }
+
+ xmax = -1.0;
+ xmin = 1.0;
+
+ while ((i < stored_peaks_read) && (current_stored_peak <= stored_peak_before_next_visual_peak)) {
+
+ xmax = max (xmax, staging[i].max);
+ xmin = min (xmin, staging[i].min);
+ ++i;
+ ++current_stored_peak;
+ --expected_peaks;
+ }
+
+ peaks[nvisual_peaks].max = xmax;
+ peaks[nvisual_peaks].min = xmin;
+ ++nvisual_peaks;
+ ++next_visual_peak;
+
+ //next_visual_peak_frame = min ((next_visual_peak * samples_per_visual_peak), (next_visual_peak_frame+samples_per_visual_peak) );
+ next_visual_peak_frame = min ((double) start+cnt, (next_visual_peak_frame+samples_per_visual_peak) );
+ stored_peak_before_next_visual_peak = (uint32_t) next_visual_peak_frame / frames_per_peak;
+ }
+
+ if (zero_fill) {
+ memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
+ }
+
+ ret = 0;
+
+ } else {
+
+ // cerr << "UPSAMPLE\n";
+
+ /* the caller wants
+
+ - less frames-per-peak (more resolution)
+ - more peaks than stored in the Peakfile
+
+ So, fetch data from the raw source, and generate peak
+ data on the fly.
+ */
+
+ jack_nframes_t frames_read = 0;
+ jack_nframes_t current_frame = start;
+ jack_nframes_t i = 0;
+ jack_nframes_t nvisual_peaks = 0;
+ jack_nframes_t chunksize = (jack_nframes_t) min (cnt, (jack_nframes_t) 4096);
+ raw_staging = new Sample[chunksize];
+
+ jack_nframes_t frame_pos = start;
+ double pixel_pos = floor (frame_pos / samples_per_visual_peak);
+ double next_pixel_pos = ceil (frame_pos / samples_per_visual_peak);
+ double pixels_per_frame = 1.0 / samples_per_visual_peak;
+
+ xmin = 1.0;
+ xmax = -1.0;
+
+ while (nvisual_peaks < npeaks) {
+
+ if (i == frames_read) {
+
+ to_read = min (chunksize, (_length - current_frame));
+
+ if ((frames_read = read_unlocked (raw_staging, current_frame, to_read)) < 0) {
+ error << compose(_("Source[%1]: peak read - cannot read %2 samples at offset %3")
+ , _name, to_read, current_frame)
+ << endmsg;
+ goto out;
+ }
+
+ i = 0;
+ }
+
+ xmax = max (xmax, raw_staging[i]);
+ xmin = min (xmin, raw_staging[i]);
+ ++i;
+ ++current_frame;
+ pixel_pos += pixels_per_frame;
+
+ if (pixel_pos >= next_pixel_pos) {
+
+ peaks[nvisual_peaks].max = xmax;
+ peaks[nvisual_peaks].min = xmin;
+ ++nvisual_peaks;
+ xmin = 1.0;
+ xmax = -1.0;
+
+ next_pixel_pos = ceil (pixel_pos + 0.5);
+ }
+ }
+
+ if (zero_fill) {
+ memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
+ }
+
+ ret = 0;
+ }
+
+ out:
+ if (staging) {
+ delete [] staging;
+ }
+
+ if (raw_staging) {
+ delete [] raw_staging;
+ }
+
+ return ret;
+}
+
+#undef DEBUG_PEAK_BUILD
+
+int
+Source::build_peaks ()
+{
+ vector<PeakBuildRecord*> built;
+ int status = -1;
+ bool pr_signal = false;
+ list<PeakBuildRecord*> copy;
+
+ {
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+ copy = pending_peak_builds;
+ pending_peak_builds.clear ();
+ }
+
+
+#ifdef DEBUG_PEAK_BUILD
+ cerr << "build peaks with " << pending_peak_builds.size() << " requests pending\n";
+#endif
+
+ for (list<PeakBuildRecord *>::iterator i = copy.begin(); i != copy.end(); ++i) {
+
+ if ((status = do_build_peak ((*i)->frame, (*i)->cnt)) != 0) {
+ unlink (peakpath.c_str());
+ break;
+ }
+ built.push_back (new PeakBuildRecord (*(*i)));
+ delete *i;
+ }
+
+ {
+ LockMonitor lm (_lock, __LINE__, __FILE__);
+
+ if (status == 0) {
+ _peaks_built = true;
+
+ if (next_peak_clear_should_notify) {
+ next_peak_clear_should_notify = false;
+ pr_signal = true;
+ }
+ }
+ }
+
+ if (status == 0) {
+ for (vector<PeakBuildRecord *>::iterator i = built.begin(); i != built.end(); ++i) {
+ PeakRangeReady ((*i)->frame, (*i)->cnt); /* EMIT SIGNAL */
+ delete *i;
+ }
+
+ if (pr_signal) {
+ PeaksReady (); /* EMIT SIGNAL */
+ }
+ }
+
+ return status;
+}
+
+int
+Source::do_build_peak (jack_nframes_t first_frame, jack_nframes_t cnt)
+{
+ jack_nframes_t current_frame;
+ Sample buf[frames_per_peak];
+ Sample xmin, xmax;
+ uint32_t peaki;
+ PeakData* peakbuf;
+ jack_nframes_t frames_read;
+ jack_nframes_t frames_to_read;
+ off_t first_peak_byte;
+ int ret = -1;
+
+#ifdef DEBUG_PEAK_BUILD
+ cerr << pthread_self() << ": " << _name << ": building peaks for " << first_frame << " to " << first_frame + cnt - 1 << endl;
+#endif
+
+ first_peak_byte = (first_frame / frames_per_peak) * sizeof (PeakData);
+
+#ifdef DEBUG_PEAK_BUILD
+ cerr << "seeking to " << first_peak_byte << " before writing new peak data\n";
+#endif
+
+ current_frame = first_frame;
+ peakbuf = new PeakData[(cnt/frames_per_peak)+1];
+ peaki = 0;
+
+ while (cnt) {
+
+ frames_to_read = min (frames_per_peak, cnt);
+
+ if ((frames_read = read_unlocked (buf, current_frame, frames_to_read)) != frames_to_read) {
+ error << compose(_("%1: could not write read raw data for peak computation (%2)"), _name, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ xmin = buf[0];
+ xmax = buf[0];
+
+ for (jack_nframes_t n = 1; n < frames_read; ++n) {
+ xmax = max (xmax, buf[n]);
+ xmin = min (xmin, buf[n]);
+
+// if (current_frame < frames_read) {
+// cerr << "sample = " << buf[n] << " max = " << xmax << " min = " << xmin << " max of 2 = " << max (xmax, buf[n]) << endl;
+// }
+ }
+
+ peakbuf[peaki].max = xmax;
+ peakbuf[peaki].min = xmin;
+ peaki++;
+
+ current_frame += frames_read;
+ cnt -= frames_read;
+ }
+
+ if (::pwrite (peakfile, peakbuf, sizeof (PeakData) * peaki, first_peak_byte) != (size_t) sizeof (PeakData) * peaki) {
+ error << compose(_("%1: could not write peak file data (%2)"), _name, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ ret = 0;
+
+ out:
+ delete [] peakbuf;
+ return ret;
+}
+
+void
+Source::build_peaks_from_scratch ()
+{
+ LockMonitor lp (_lock, __LINE__, __FILE__);
+
+ next_peak_clear_should_notify = true;
+ pending_peak_builds.push_back (new PeakBuildRecord (0, _length));
+ queue_for_peaks (*this);
+}
+
+bool
+Source::file_changed (string path)
+{
+ struct stat stat_file;
+ struct stat stat_peak;
+
+ int e1 = stat (path.c_str(), &stat_file);
+ int e2 = stat (peak_path(path).c_str(), &stat_peak);
+
+ if (!e1 && !e2 && stat_file.st_mtime > stat_peak.st_mtime){
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+Source::use ()
+{
+ _use_cnt++;
+}
+
+void
+Source::release ()
+{
+ if (_use_cnt) --_use_cnt;
+}
diff --git a/libs/ardour/state_manager.cc b/libs/ardour/state_manager.cc
new file mode 100644
index 0000000000..29a32b96ee
--- /dev/null
+++ b/libs/ardour/state_manager.cc
@@ -0,0 +1,66 @@
+#include <pbd/error.h>
+#include <ardour/state_manager.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+StateManager::StateManager ()
+{
+ _current_state_id = 0;
+}
+
+StateManager::~StateManager()
+{
+}
+
+void
+StateManager::drop_all_states ()
+{
+ for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+ delete *i;
+ }
+
+ states.clear ();
+
+ save_state (_("cleared history"));
+}
+
+void
+StateManager::use_state (state_id_t id)
+{
+ Change what_changed;
+ state_id_t n;
+ StateMap::iterator i;
+
+ for (n = 0, i = states.begin(); n < id && i != states.end(); ++n, ++i);
+
+ if (n != id || i == states.end()) {
+ fatal << compose (_("programming error: illegal state ID (%1) passed to "
+ "StateManager::set_state() (range = 0-%3)"), id,
+ states.size()-1)
+ << endmsg;
+ /*NOTREACHED*/
+ return;
+ }
+
+ what_changed = restore_state (**i);
+ _current_state_id = id;
+ send_state_changed (what_changed);
+}
+
+void
+StateManager::save_state (std::string why)
+{
+ if (!should_save_state())
+ return;
+
+ states.push_back (state_factory (why));
+ _current_state_id = states.size() - 1;
+}
+
+void
+StateManager::send_state_changed (Change what_changed)
+{
+ StateChanged (what_changed);
+}
diff --git a/libs/ardour/stateful.cc b/libs/ardour/stateful.cc
new file mode 100644
index 0000000000..7b63d01aad
--- /dev/null
+++ b/libs/ardour/stateful.cc
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2000-2001 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.
+
+ $Id$
+*/
+
+#include <cstdio>
+#include <unistd.h>
+
+#include <ardour/stateful.h>
+#include <ardour/utils.h>
+#include <pbd/xml++.h>
+
+#include "i18n.h"
+
+Stateful::Stateful ()
+{
+ _extra_xml = 0;
+ _instant_xml = 0;
+}
+
+Stateful::~Stateful ()
+{
+ // Do not delete _extra_xml. The use of add_child_nocopy()
+ // means it needs to live on indefinately.
+ delete _instant_xml;
+}
+
+void
+Stateful::add_extra_xml (XMLNode& node)
+{
+ if (_extra_xml == 0) {
+ _extra_xml = new XMLNode ("extra");
+ }
+
+ _extra_xml->remove_nodes (node.name());
+ _extra_xml->add_child_nocopy (node);
+}
+
+XMLNode *
+Stateful::extra_xml (const string& str)
+{
+ if (_extra_xml == 0) {
+ return 0;
+ }
+
+ const XMLNodeList& nlist = _extra_xml->children();
+ XMLNodeConstIterator i;
+
+ for (i = nlist.begin(); i != nlist.end(); ++i) {
+ if ((*i)->name() == str) {
+ return (*i);
+ }
+ }
+
+ return 0;
+}
+
+void
+Stateful::add_instant_xml (XMLNode& node, const string& dir)
+{
+ if (_instant_xml == 0) {
+ _instant_xml = new XMLNode ("instant");
+ }
+
+ _instant_xml->remove_nodes_and_delete (node.name());
+ _instant_xml->add_child_copy (node);
+
+ XMLTree tree;
+ tree.set_filename(dir+"/instant.xml");
+
+ /* Important: the destructor for an XMLTree deletes
+ all of its nodes, starting at _root. We therefore
+ cannot simply hand it our persistent _instant_xml
+ node as its _root, because we will lose it whenever
+ the Tree goes out of scope.
+
+ So instead, copy the _instant_xml node (which does
+ a deep copy), and hand that to the tree.
+ */
+
+ XMLNode* copy = new XMLNode (*_instant_xml);
+ tree.set_root (copy);
+
+ if (!tree.write()) {
+ error << compose(_("Error: could not write %1"), dir+"/instant.xml") << endmsg;
+ }
+}
+
+XMLNode *
+Stateful::instant_xml (const string& str, const string& dir)
+{
+ if (_instant_xml == 0) {
+ string instant_file = dir + "/instant.xml";
+ if (access(instant_file.c_str(), F_OK) == 0) {
+ XMLTree tree;
+ if (tree.read(dir+"/instant.xml")) {
+ _instant_xml = new XMLNode(*(tree.root()));
+ } else {
+ warning << compose(_("Could not understand XML file %1"), instant_file) << endmsg;
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ const XMLNodeList& nlist = _instant_xml->children();
+ XMLNodeConstIterator i;
+
+ for (i = nlist.begin(); i != nlist.end(); ++i) {
+ if ((*i)->name() == str) {
+ return (*i);
+ }
+ }
+
+ return 0;
+}
+
diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc
new file mode 100644
index 0000000000..8042644d39
--- /dev/null
+++ b/libs/ardour/tempo.cc
@@ -0,0 +1,1323 @@
+/*
+ Copyright (C) 2000-2002 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <unistd.h>
+
+#include <cmath>
+
+#include <sigc++/bind.h>
+
+#include <pbd/lockmonitor.h>
+#include <pbd/xml++.h>
+#include <ardour/tempo.h>
+#include <ardour/utils.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+/* _default tempo is 4/4 qtr=120 */
+
+Meter TempoMap::_default_meter (4.0, 4.0);
+Tempo TempoMap::_default_tempo (120.0);
+
+const double Meter::ticks_per_beat = 1920.0;
+
+/***********************************************************************/
+
+double
+Meter::frames_per_bar (const Tempo& tempo, jack_nframes_t sr) const
+{
+ return ((60.0 * sr * _beats_per_bar) / tempo.beats_per_minute());
+}
+
+/***********************************************************************/
+
+const string TempoSection::xml_state_node_name = "Tempo";
+
+TempoSection::TempoSection (const XMLNode& node)
+ : MetricSection (BBT_Time()), Tempo (TempoMap::default_tempo())
+{
+ const XMLProperty *prop;
+ BBT_Time start;
+ LocaleGuard lg (X_("POSIX"));
+
+ if ((prop = node.property ("start")) == 0) {
+ error << _("TempoSection XML node has no \"start\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (sscanf (prop->value().c_str(), "%lu|%lu|%lu",
+ &start.bars,
+ &start.beats,
+ &start.ticks) < 3) {
+ error << _("TempoSection XML node has an illegal \"start\" value") << endmsg;
+ throw failed_constructor();
+ }
+
+ set_start (start);
+
+ if ((prop = node.property ("beats-per-minute")) == 0) {
+ error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (sscanf (prop->value().c_str(), "%lf", &_beats_per_minute) != 1 || _beats_per_minute < 0.0) {
+ error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((prop = node.property ("movable")) == 0) {
+ error << _("TempoSection XML node has no \"movable\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ set_movable (prop->value() == "yes");
+}
+
+XMLNode&
+TempoSection::get_state() const
+{
+ XMLNode *root = new XMLNode (xml_state_node_name);
+ char buf[256];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+ start().bars,
+ start().beats,
+ start().ticks);
+ root->add_property ("start", buf);
+ snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
+ root->add_property ("beats-per-minute", buf);
+ snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
+ root->add_property ("movable", buf);
+
+ return *root;
+}
+
+/***********************************************************************/
+
+const string MeterSection::xml_state_node_name = "Meter";
+
+MeterSection::MeterSection (const XMLNode& node)
+ : MetricSection (BBT_Time()), Meter (TempoMap::default_meter())
+{
+ const XMLProperty *prop;
+ BBT_Time start;
+ LocaleGuard lg (X_("POSIX"));
+
+ if ((prop = node.property ("start")) == 0) {
+ error << _("MeterSection XML node has no \"start\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (sscanf (prop->value().c_str(), "%lu|%lu|%lu",
+ &start.bars,
+ &start.beats,
+ &start.ticks) < 3) {
+ error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
+ throw failed_constructor();
+ }
+
+ set_start (start);
+
+ if ((prop = node.property ("beats-per-bar")) == 0) {
+ error << _("MeterSection XML node has no \"beats-per-bar\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (sscanf (prop->value().c_str(), "%lf", &_beats_per_bar) != 1 || _beats_per_bar < 0.0) {
+ error << _("MeterSection XML node has an illegal \"beats-per-bar\" value") << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((prop = node.property ("note-type")) == 0) {
+ error << _("MeterSection XML node has no \"note-type\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) {
+ error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg;
+ throw failed_constructor();
+ }
+
+ if ((prop = node.property ("movable")) == 0) {
+ error << _("MeterSection XML node has no \"movable\" property") << endmsg;
+ throw failed_constructor();
+ }
+
+ set_movable (prop->value() == "yes");
+}
+
+XMLNode&
+MeterSection::get_state() const
+{
+ XMLNode *root = new XMLNode (xml_state_node_name);
+ char buf[256];
+ LocaleGuard lg (X_("POSIX"));
+
+ snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+ start().bars,
+ start().beats,
+ start().ticks);
+ root->add_property ("start", buf);
+ snprintf (buf, sizeof (buf), "%f", _note_type);
+ root->add_property ("note-type", buf);
+ snprintf (buf, sizeof (buf), "%f", _beats_per_bar);
+ root->add_property ("beats-per-bar", buf);
+ snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
+ root->add_property ("movable", buf);
+
+ return *root;
+}
+
+/***********************************************************************/
+
+struct MetricSectionSorter {
+ bool operator() (const MetricSection* a, const MetricSection* b) {
+ return a->start() < b->start();
+ }
+};
+
+TempoMap::TempoMap (jack_nframes_t fr)
+{
+ metrics = new Metrics;
+ _frame_rate = fr;
+ last_bbt_valid = false;
+ BBT_Time start;
+ in_set_state = false;
+
+ start.bars = 1;
+ start.beats = 1;
+ start.ticks = 0;
+
+ TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute());
+ MeterSection *m = new MeterSection (start, _default_meter.beats_per_bar(), _default_meter.note_divisor());
+
+ t->set_movable (false);
+ m->set_movable (false);
+
+ /* note: frame time is correct (zero) for both of these */
+
+ metrics->push_back (t);
+ metrics->push_back (m);
+
+ save_state (_("initial"));
+}
+
+TempoMap::~TempoMap ()
+{
+}
+
+int
+TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when)
+{
+ if (when == section.start()) {
+ return -1;
+ }
+
+ if (!section.movable()) {
+ return 1;
+ }
+
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ MetricSectionSorter cmp;
+ BBT_Time corrected (when);
+
+ if (dynamic_cast<MeterSection*>(&section) != 0) {
+ if (corrected.beats > 1) {
+ corrected.beats = 1;
+ corrected.bars++;
+ }
+ }
+ corrected.ticks = 0;
+
+ section.set_start (corrected);
+ metrics->sort (cmp);
+ timestamp_metrics ();
+ save_state (_("move metric"));
+
+ return 0;
+}
+
+void
+TempoMap::move_tempo (TempoSection& tempo, const BBT_Time& when)
+{
+ if (move_metric_section (tempo, when) == 0) {
+ send_state_changed (Change (0));
+ }
+}
+
+void
+TempoMap::move_meter (MeterSection& meter, const BBT_Time& when)
+{
+ if (move_metric_section (meter, when) == 0) {
+ send_state_changed (Change (0));
+ }
+}
+
+
+void
+TempoMap::remove_tempo (const TempoSection& tempo)
+{
+ bool removed = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ Metrics::iterator i;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+ if (dynamic_cast<TempoSection*> (*i) != 0) {
+ if (tempo.frame() == (*i)->frame()) {
+ if ((*i)->movable()) {
+ metrics->erase (i);
+ removed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (removed) {
+ send_state_changed (Change (0));
+ }
+}
+
+void
+TempoMap::remove_meter (const MeterSection& tempo)
+{
+ bool removed = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ Metrics::iterator i;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+ if (dynamic_cast<MeterSection*> (*i) != 0) {
+ if (tempo.frame() == (*i)->frame()) {
+ if ((*i)->movable()) {
+ metrics->erase (i);
+ removed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (removed) {
+ save_state (_("metric removed"));
+ }
+ }
+
+ if (removed) {
+ send_state_changed (Change (0));
+ }
+}
+
+void
+TempoMap::do_insert (MetricSection* section)
+{
+ Metrics::iterator i;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+
+ if ((*i)->start() < section->start()) {
+ continue;
+ }
+
+ metrics->insert (i, section);
+ break;
+ }
+
+ if (i == metrics->end()) {
+ metrics->insert (metrics->end(), section);
+ }
+
+ timestamp_metrics ();
+}
+
+void
+TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ /* new tempos always start on a beat */
+
+ where.ticks = 0;
+
+ do_insert (new TempoSection (where, tempo.beats_per_minute()));
+
+ save_state (_("add tempo"));
+ }
+
+ send_state_changed (Change (0));
+}
+
+void
+TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement)
+{
+ bool replaced = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ Metrics::iterator i;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+ TempoSection *ts;
+
+ if ((ts = dynamic_cast<TempoSection*>(*i)) != 0 && ts == &existing) {
+
+ *((Tempo *) ts) = replacement;
+
+ replaced = true;
+ timestamp_metrics ();
+ break;
+ }
+ }
+
+ if (replaced) {
+ save_state (_("replace tempo"));
+ }
+ }
+
+ if (replaced) {
+ send_state_changed (Change (0));
+ }
+}
+
+void
+TempoMap::add_meter (const Meter& meter, BBT_Time where)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ /* a new meter always starts a new bar on the first beat. so
+ round the start time appropriately. remember that
+ `where' is based on the existing tempo map, not
+ the result after we insert the new meter.
+
+ */
+
+ if (where.beats != 1) {
+ where.beats = 1;
+ where.bars++;
+ }
+
+ /* new meters *always* start on a beat. */
+
+ where.ticks = 0;
+
+ do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()));
+
+ save_state (_("add meter"));
+ }
+
+ send_state_changed (Change (0));
+}
+
+void
+TempoMap::replace_meter (MeterSection& existing, const Meter& replacement)
+{
+ bool replaced = false;
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ Metrics::iterator i;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+ MeterSection *ms;
+ if ((ms = dynamic_cast<MeterSection*>(*i)) != 0 && ms == &existing) {
+
+ *((Meter*) ms) = replacement;
+
+ replaced = true;
+ timestamp_metrics ();
+ break;
+ }
+ }
+
+ if (replaced) {
+ save_state (_("replaced meter"));
+ }
+ }
+
+ if (replaced) {
+ send_state_changed (Change (0));
+ }
+}
+
+const MeterSection&
+TempoMap::first_meter () const
+{
+ const MeterSection *m = 0;
+
+ for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+ if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
+ return *m;
+ }
+ }
+
+ fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+ /*NOTREACHED*/
+ return *m;
+}
+
+const TempoSection&
+TempoMap::first_tempo () const
+{
+ const TempoSection *t = 0;
+
+ for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+ if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
+ return *t;
+ }
+ }
+
+ fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+ /*NOTREACHED*/
+ return *t;
+}
+
+void
+TempoMap::timestamp_metrics ()
+{
+ Metrics::iterator i;
+ const Meter* meter;
+ const Tempo* tempo;
+ Meter *m;
+ Tempo *t;
+ jack_nframes_t current;
+ jack_nframes_t section_frames;
+ BBT_Time start;
+ BBT_Time end;
+
+ meter = &first_meter ();
+ tempo = &first_tempo ();
+ current = 0;
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+
+ end = (*i)->start();
+
+ section_frames = count_frames_between_metrics (*meter, *tempo, start, end);
+
+ current += section_frames;
+
+ start = end;
+
+ (*i)->set_frame (current);
+
+ if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+ meter = m;
+ } else {
+ fatal << _("programming error: unhandled MetricSection type") << endmsg;
+ /*NOTREACHED*/
+ }
+ }
+}
+
+TempoMap::Metric
+TempoMap::metric_at (jack_nframes_t frame) const
+{
+ Metric m (first_meter(), first_tempo());
+ const Meter* meter;
+ const Tempo* tempo;
+
+ /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
+ at something, because we insert the default tempo and meter during
+ TempoMap construction.
+
+ now see if we can find better candidates.
+ */
+
+ for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+
+ if ((*i)->frame() > frame) {
+ break;
+ }
+
+ if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ m.set_tempo (*tempo);
+ } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ m.set_meter (*meter);
+ }
+
+ m.set_frame ((*i)->frame ());
+ m.set_start ((*i)->start ());
+ }
+
+ return m;
+}
+
+TempoMap::Metric
+TempoMap::metric_at (BBT_Time bbt) const
+{
+ Metric m (first_meter(), first_tempo());
+ const Meter* meter;
+ const Tempo* tempo;
+
+ /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
+ at something, because we insert the default tempo and meter during
+ TempoMap construction.
+
+ now see if we can find better candidates.
+ */
+
+ for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+
+ BBT_Time section_start ((*i)->start());
+
+ if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
+ break;
+ }
+
+ if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ m.set_tempo (*tempo);
+ } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ m.set_meter (*meter);
+ }
+
+ m.set_frame ((*i)->frame ());
+ m.set_start (section_start);
+ }
+
+ return m;
+}
+
+void
+TempoMap::bbt_time (jack_nframes_t frame, BBT_Time& bbt) const
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ bbt_time_unlocked (frame, bbt);
+}
+
+void
+TempoMap::bbt_time_unlocked (jack_nframes_t frame, BBT_Time& bbt) const
+{
+ bbt_time_with_metric (frame, bbt, metric_at (frame));
+}
+
+void
+TempoMap::bbt_time_with_metric (jack_nframes_t frame, BBT_Time& bbt, const Metric& metric) const
+{
+ jack_nframes_t frame_diff;
+
+ uint32_t xtra_bars = 0;
+ double xtra_beats = 0;
+ double beats = 0;
+
+ const double beats_per_bar = metric.meter().beats_per_bar();
+ const double frames_per_bar = metric.meter().frames_per_bar (metric.tempo(), _frame_rate);
+ const double beat_frames = metric.tempo().frames_per_beat (_frame_rate);
+
+ /* now compute how far beyond that point we actually are. */
+
+ frame_diff = frame - metric.frame();
+
+ xtra_bars = (uint32_t) floor (frame_diff / frames_per_bar);
+ frame_diff -= (uint32_t) floor (xtra_bars * frames_per_bar);
+ xtra_beats = (double) frame_diff / beat_frames;
+
+
+ /* and set the returned value */
+
+ /* and correct beat/bar shifts to match the meter.
+ remember: beat and bar counting is 1-based,
+ not zero-based
+ also the meter may contain a fraction
+ */
+
+ bbt.bars = metric.start().bars + xtra_bars;
+
+ beats = (double) metric.start().beats + xtra_beats;
+
+ bbt.bars += (uint32_t) floor(beats/ (beats_per_bar+1) );
+
+ beats = fmod(beats - 1, beats_per_bar )+ 1.0;
+ bbt.ticks = (uint32_t)( round((beats - floor(beats)) *(double) Meter::ticks_per_beat));
+ bbt.beats = (uint32_t) floor(beats);
+
+}
+
+
+jack_nframes_t
+TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) const
+{
+
+ /* for this to work with fractional measure types, start and end have to "legal" BBT types,
+ that means that the beats and ticks should be inside a bar
+ */
+
+
+ jack_nframes_t frames = 0;
+ jack_nframes_t start_frame = 0;
+ jack_nframes_t end_frame = 0;
+
+ Metric m = metric_at(start);
+
+ uint32_t bar_offset = start.bars - m.start().bars;
+
+ double beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1)
+ + start.ticks/Meter::ticks_per_beat;
+
+
+ start_frame = m.frame() + (jack_nframes_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate));
+
+ m = metric_at(end);
+
+ bar_offset = end.bars - m.start().bars;
+
+ beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1)
+ + end.ticks/Meter::ticks_per_beat;
+
+ end_frame = m.frame() + (jack_nframes_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate));
+
+ frames = end_frame - start_frame;
+
+ return frames;
+
+}
+
+jack_nframes_t
+TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const
+{
+ /*this is used in timestamping the metrics by actually counting the beats */
+
+ jack_nframes_t frames = 0;
+ uint32_t bar = start.bars;
+ double beat = (double) start.beats;
+ double beats_counted = 0;
+ double beats_per_bar = 0;
+ double beat_frames = 0;
+
+ beats_per_bar = meter.beats_per_bar();
+ beat_frames = tempo.frames_per_beat (_frame_rate);
+
+ frames = 0;
+
+ while (bar < end.bars || (bar == end.bars && beat < end.beats)) {
+
+ if (beat >= beats_per_bar) {
+ beat = 1;
+ ++bar;
+ ++beats_counted;
+ } else {
+ ++beat;
+ ++beats_counted;
+ if (beat > beats_per_bar) {
+ /* this is a fractional beat at the end of a fractional bar
+ so it should only count for the fraction */
+ beats_counted -= (ceil(beats_per_bar) - beats_per_bar);
+ }
+ }
+ }
+
+ frames = (jack_nframes_t) floor (beats_counted * beat_frames);
+
+ return frames;
+
+}
+
+jack_nframes_t
+TempoMap::frame_time (const BBT_Time& bbt) const
+{
+ BBT_Time start ; /* 1|1|0 */
+
+ return count_frames_between ( start, bbt);
+}
+
+jack_nframes_t
+TempoMap::bbt_duration_at (jack_nframes_t pos, const BBT_Time& bbt, int dir) const
+{
+ jack_nframes_t frames = 0;
+
+ BBT_Time when;
+ bbt_time(pos,when);
+
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ frames = bbt_duration_at_unlocked (when, bbt,dir);
+ }
+
+ return frames;
+}
+
+jack_nframes_t
+TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const
+{
+
+ jack_nframes_t frames = 0;
+
+ double beats_per_bar;
+ BBT_Time result;
+
+ result.bars = max(1U,when.bars + dir * bbt.bars) ;
+ result.beats = 1;
+ result.ticks = 0;
+
+ Metric metric = metric_at(result);
+ beats_per_bar = metric.meter().beats_per_bar();
+
+
+
+ /*reduce things to legal bbt values
+ we have to handle possible fractional=shorter beats at the end of measures
+ and things like 0|11|9000 as a duration in a 4.5/4 measure
+ the musical decision is that the fractional beat is also a beat , although a shorter one
+ */
+
+
+ if (dir >= 0) {
+ result.beats = when.beats + bbt.beats;
+ result.ticks = when.ticks + bbt.ticks;
+
+ while (result.beats >= (beats_per_bar+1)) {
+ result.bars++;
+ result.beats -= (uint32_t) ceil(beats_per_bar);
+ metric = metric_at(result); // maybe there is a meter change
+ beats_per_bar = metric.meter().beats_per_bar();
+
+ }
+ /*we now counted the beats and landed in the target measure, now deal with ticks
+ this seems complicated, but we want to deal with the corner case of a sequence of time signatures like 0.2/4-0.7/4
+ and with request like bbt = 3|2|9000 ,so we repeat the same loop but add ticks
+ */
+
+ /* of course gtk_ardour only allows bar with at least 1.0 beats .....
+ */
+
+ uint32_t ticks_at_beat = (uint32_t) ( result.beats == ceil(beats_per_bar) ?
+ (1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat
+ : Meter::ticks_per_beat );
+
+ while (result.ticks >= ticks_at_beat) {
+ result.beats++;
+ result.ticks -= ticks_at_beat;
+ if (result.beats >= (beats_per_bar+1)) {
+ result.bars++;
+ result.beats = 1;
+ metric = metric_at(result); // maybe there is a meter change
+ beats_per_bar = metric.meter().beats_per_bar();
+ }
+ ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ?
+ (1 - (ceil(beats_per_bar) - beats_per_bar) )* Meter::ticks_per_beat
+ : Meter::ticks_per_beat);
+
+ }
+
+
+ } else {
+ uint32_t b = bbt.beats;
+
+ /* count beats */
+ while( b > when.beats ) {
+
+ result.bars = max(1U,result.bars-- ) ;
+ metric = metric_at(result); // maybe there is a meter change
+ beats_per_bar = metric.meter().beats_per_bar();
+ if (b >= ceil(beats_per_bar)) {
+
+ b -= (uint32_t) ceil(beats_per_bar);
+ } else {
+ b = (uint32_t) ceil(beats_per_bar)- b + when.beats ;
+ }
+ }
+ result.beats = when.beats - b;
+
+ /*count ticks */
+
+ if (bbt.ticks <= when.ticks) {
+ result.ticks = when.ticks - bbt.ticks;
+ } else {
+
+ uint32_t ticks_at_beat= (uint32_t) Meter::ticks_per_beat;
+ uint32_t t = bbt.ticks - when.ticks;
+
+ do {
+
+ if (result.beats == 1) {
+ result.bars = max(1U,result.bars-- ) ;
+ metric = metric_at(result); // maybe there is a meter change
+ beats_per_bar = metric.meter().beats_per_bar();
+ result.beats = (uint32_t) ceil(beats_per_bar);
+ ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat) ;
+ } else {
+ result.beats --;
+ ticks_at_beat = (uint32_t) Meter::ticks_per_beat;
+ }
+
+ if (t <= ticks_at_beat) {
+ result.ticks = ticks_at_beat - t;
+ } else {
+ t-= ticks_at_beat;
+ }
+ } while (t > ticks_at_beat);
+
+ }
+
+
+ }
+
+ if (dir < 0 ) {
+ frames = count_frames_between( result,when);
+ } else {
+ frames = count_frames_between(when,result);
+ }
+
+ return frames;
+}
+
+
+
+jack_nframes_t
+
+TempoMap::round_to_bar (jack_nframes_t fr, int dir)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ return round_to_type (fr, dir, Bar);
+}
+
+
+jack_nframes_t
+
+TempoMap::round_to_beat (jack_nframes_t fr, int dir)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ return round_to_type (fr, dir, Beat);
+}
+
+jack_nframes_t
+
+TempoMap::round_to_beat_subdivision (jack_nframes_t fr, int sub_num)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ TempoMap::BBTPointList::iterator i;
+ TempoMap::BBTPointList *more_zoomed_bbt_points;
+ jack_nframes_t frame_one_beats_worth;
+ jack_nframes_t pos = 0;
+ jack_nframes_t next_pos = 0 ;
+ double tempo = 1;
+ double frames_one_subdivisions_worth;
+ bool fr_has_changed = false;
+
+ int n;
+
+ frame_one_beats_worth = (jack_nframes_t) ::floor ((double) _frame_rate * 60 / 20 ); //one beat @ 20 bpm
+ more_zoomed_bbt_points = get_points((fr >= frame_one_beats_worth) ?
+ fr - frame_one_beats_worth : 0, fr+frame_one_beats_worth );
+
+ if (more_zoomed_bbt_points == 0 || more_zoomed_bbt_points->empty()) {
+ return fr;
+ }
+
+ for (i = more_zoomed_bbt_points->begin(); i != more_zoomed_bbt_points->end(); i++) {
+ if ((*i).frame <= fr) {
+ pos = (*i).frame;
+ tempo = (*i).tempo->beats_per_minute();
+
+ } else {
+ i++;
+ next_pos = (*i).frame;
+ break;
+ }
+ }
+ frames_one_subdivisions_worth = ((double) _frame_rate * 60 / (sub_num * tempo));
+
+ for (n = sub_num; n > 0; n--) {
+ if (fr >= (pos + ((n - 0.5) * frames_one_subdivisions_worth))) {
+ fr = (jack_nframes_t) round(pos + (n * frames_one_subdivisions_worth));
+ if (fr > next_pos) {
+ fr = next_pos; //take care of fractional beats that don't match the subdivision asked
+ }
+ fr_has_changed = true;
+ break;
+ }
+ }
+
+ if (!fr_has_changed) {
+ fr = pos;
+ }
+
+ delete more_zoomed_bbt_points;
+ return fr ;
+}
+
+jack_nframes_t
+
+TempoMap::round_to_type (jack_nframes_t frame, int dir, BBTPointType type)
+{
+ Metric metric = metric_at (frame);
+ BBT_Time bbt;
+ BBT_Time start;
+ bbt_time_with_metric (frame, bbt, metric);
+
+ switch (type) {
+ case Bar:
+ if (dir < 0) {
+ /* relax */
+
+ } else if (dir > 0) {
+ if (bbt.beats > 0) {
+ bbt.bars++;
+ }
+ } else {
+ if (bbt.beats > metric.meter().beats_per_bar()/2) {
+ bbt.bars++;
+ }
+
+ }
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ break;
+
+ case Beat:
+ if (dir < 0) {
+ /* relax */
+ } else if (dir > 0) {
+ if (bbt.ticks > 0) {
+ bbt.beats++;
+ }
+ } else {
+ if (bbt.ticks >= (Meter::ticks_per_beat/2)) {
+ bbt.beats++;
+ }
+ }
+ if (bbt.beats > ceil(metric.meter().beats_per_bar()) ) {
+ bbt.beats = 1;
+ bbt.bars++;
+ }
+ bbt.ticks = 0;
+ break;
+
+ }
+
+ return metric.frame() + count_frames_between (metric.start(), bbt);
+}
+
+TempoMap::BBTPointList *
+TempoMap::get_points (jack_nframes_t lower, jack_nframes_t upper) const
+{
+
+ Metrics::const_iterator i;
+ BBTPointList *points;
+ double current;
+ const MeterSection* meter;
+ const MeterSection* m;
+ const TempoSection* tempo;
+ const TempoSection* t;
+ uint32_t bar;
+ uint32_t beat;
+
+ meter = &first_meter ();
+ tempo = &first_tempo ();
+
+ /* find the starting point */
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+
+ if ((*i)->frame() > lower) {
+ break;
+ }
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ }
+ }
+
+ /* We now have:
+
+ meter -> the Meter for "lower"
+ tempo -> the Tempo for "lower"
+ i -> for first new metric after "lower", possibly metrics->end()
+
+ Now start generating points.
+ */
+
+ if (meter->frame() > tempo->frame()) {
+ bar = meter->start().bars;
+ beat = meter->start().beats;
+ current = meter->frame();
+ } else {
+ bar = tempo->start().bars;
+ beat = tempo->start().beats;
+ current = tempo->frame();
+ }
+
+ points = new BBTPointList;
+
+ do {
+ double beats_per_bar;
+ double beat_frame;
+ double beat_frames;
+ double frames_per_bar;
+ jack_nframes_t limit;
+
+ beats_per_bar = meter->beats_per_bar ();
+ frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
+ beat_frames = tempo->frames_per_beat (_frame_rate);
+
+ if (i == metrics->end()) {
+ limit = upper;
+ } else {
+ limit = (*i)->frame();
+ }
+
+ limit = min (limit, upper);
+
+ while (current < limit) {
+
+ /* if we're at the start of a bar, add bar point */
+
+ if (beat == 1) {
+ if (current >= lower) {
+ points->push_back (BBTPoint (*meter, *tempo,(jack_nframes_t)rint(current), Bar, bar, 1));
+
+ }
+ }
+
+ /* add some beats if we can */
+
+ beat_frame = current;
+
+ while (beat <= ceil( beats_per_bar) && beat_frame < limit) {
+ if (beat_frame >= lower) {
+ points->push_back (BBTPoint (*meter, *tempo, (jack_nframes_t) rint(beat_frame), Beat, bar, beat));
+ }
+ beat_frame += beat_frames;
+ current+= beat_frames;
+
+ beat++;
+ }
+
+ if (beat > ceil(beats_per_bar) ) {
+
+ /* we walked an entire bar. its
+ important to move `current' forward
+ by the actual frames_per_bar, not move it to
+ an integral beat_frame, so that metrics with
+ non-integral beats-per-bar have
+ their bar positions set
+ correctly. consider a metric with
+ 9-1/2 beats-per-bar. the bar we
+ just filled had 10 beat marks,
+ but the bar end is 1/2 beat before
+ the last beat mark.
+ And it is also possible that a tempo
+ change occured in the middle of a bar,
+ so we subtract the possible extra fraction from the current
+ */
+
+ current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar);
+ bar++;
+ beat = 1;
+
+ }
+
+ }
+
+ /* if we're done, then we're done */
+
+ if (current >= upper) {
+ break;
+ }
+
+ /* i is an iterator that refers to the next metric (or none).
+ if there is a next metric, move to it, and continue.
+ */
+
+ if (i != metrics->end()) {
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ tempo = t;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ meter = m;
+ /* new MeterSection, beat always returns to 1 */
+ beat = 1;
+ }
+
+ ++i;
+ }
+
+ } while (1);
+
+ return points;
+}
+
+const Tempo&
+TempoMap::tempo_at (jack_nframes_t frame)
+{
+ Metric m (metric_at (frame));
+ return m.tempo();
+}
+
+
+const Meter&
+TempoMap::meter_at (jack_nframes_t frame)
+{
+ Metric m (metric_at (frame));
+ return m.meter();
+}
+
+XMLNode&
+TempoMap::get_state ()
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+ Metrics::const_iterator i;
+ XMLNode *root = new XMLNode ("TempoMap");
+
+ for (i = metrics->begin(); i != metrics->end(); ++i) {
+ root->add_child_nocopy ((*i)->get_state());
+ }
+
+ return *root;
+}
+
+int
+TempoMap::set_state (const XMLNode& node)
+{
+ {
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ Metrics old_metrics (*metrics);
+
+ in_set_state = true;
+
+ metrics->clear();
+
+ nlist = node.children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ XMLNode* child = *niter;
+
+ if (child->name() == TempoSection::xml_state_node_name) {
+
+ try {
+ metrics->push_back (new TempoSection (*child));
+ }
+
+ catch (failed_constructor& err){
+ error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
+ *metrics = old_metrics;
+ break;
+ }
+
+ } else if (child->name() == MeterSection::xml_state_node_name) {
+
+ try {
+ metrics->push_back (new MeterSection (*child));
+ }
+
+ catch (failed_constructor& err) {
+ error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
+ *metrics = old_metrics;
+ break;
+ }
+ }
+ }
+
+ if (niter == nlist.end()) {
+
+ MetricSectionSorter cmp;
+ metrics->sort (cmp);
+ timestamp_metrics ();
+ }
+
+ in_set_state = false;
+ }
+
+ send_state_changed (Change (0));
+
+ return 0;
+}
+
+void
+TempoMap::dump (std::ostream& o) const
+{
+ const MeterSection* m;
+ const TempoSection* t;
+
+ for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+
+ if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+ o << "Tempo @ " << *i << ' ' << t->beats_per_minute() << " BPM at " << t->start() << " frame= " << t->frame() << " (move? "
+ << t->movable() << ')' << endl;
+ } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+ o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
+ << " (move? " << m->movable() << ')' << endl;
+ }
+ }
+}
+
+UndoAction
+TempoMap::get_memento () const
+{
+ return sigc::bind (mem_fun (*(const_cast<TempoMap *> (this)), &StateManager::use_state), _current_state_id);
+}
+
+Change
+TempoMap::restore_state (StateManager::State& state)
+{
+ LockMonitor lm (lock, __LINE__, __FILE__);
+
+ TempoMapState* tmstate = dynamic_cast<TempoMapState*> (&state);
+
+ metrics = tmstate->metrics;
+ last_bbt_valid = false;
+
+ return Change (0);
+}
+
+StateManager::State*
+TempoMap::state_factory (std::string why) const
+{
+ TempoMapState* state = new TempoMapState (why);
+
+ for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+ TempoSection *ts;
+ MeterSection *ms;
+
+ if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
+ state->metrics->push_back (new TempoSection (*ts));
+ } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+ state->metrics->push_back (new MeterSection (*ms));
+ }
+ }
+
+ return state;
+}
+
+void
+TempoMap::save_state (std::string why)
+{
+ if (!in_set_state) {
+ StateManager::save_state (why);
+ }
+}
diff --git a/libs/ardour/utils.cc b/libs/ardour/utils.cc
new file mode 100644
index 0000000000..003e1c92a3
--- /dev/null
+++ b/libs/ardour/utils.cc
@@ -0,0 +1,224 @@
+/*
+ Copyright (C) 2000-2003 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.
+
+ $Id$
+*/
+
+#include <cstdio> /* for sprintf */
+#include <cmath>
+#include <cctype>
+#include <string>
+#include <cerrno>
+#include <iostream>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <pbd/error.h>
+#include <pbd/xml++.h>
+#include <ardour/utils.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+
+void
+elapsed_time_to_str (char *buf, uint32_t seconds)
+
+{
+ uint32_t days;
+ uint32_t hours;
+ uint32_t minutes;
+ uint32_t s;
+
+ s = seconds;
+ days = s / (3600 * 24);
+ s -= (days * 3600 * 24);
+ hours = s / 3600;
+ s -= (hours * 3600);
+ minutes = s / 60;
+ s -= minutes * 60;
+
+ if (days) {
+ snprintf (buf, sizeof (buf), "%" PRIu32 " day%s %" PRIu32 " hour%s",
+ days,
+ days > 1 ? "s" : "",
+ hours,
+ hours > 1 ? "s" : "");
+ } else if (hours) {
+ snprintf (buf, sizeof (buf), "%" PRIu32 " hour%s %" PRIu32 " minute%s",
+ hours,
+ hours > 1 ? "s" : "",
+ minutes,
+ minutes > 1 ? "s" : "");
+ } else if (minutes) {
+ snprintf (buf, sizeof (buf), "%" PRIu32 " minute%s",
+ minutes,
+ minutes > 1 ? "s" : "");
+ } else if (s) {
+ snprintf (buf, sizeof (buf), "%" PRIu32 " second%s",
+ seconds,
+ seconds > 1 ? "s" : "");
+ } else {
+ snprintf (buf, sizeof (buf), "no time");
+ }
+}
+
+string
+legalize_for_path (string str)
+{
+ string::size_type pos;
+ string legal_chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+=: ";
+ string legal;
+
+ legal = str;
+ pos = 0;
+
+ while ((pos = legal.find_first_not_of (legal_chars, pos)) != string::npos) {
+ legal.replace (pos, 1, "_");
+ pos += 1;
+ }
+
+ return legal;
+}
+
+ostream&
+operator<< (ostream& o, const BBT_Time& bbt)
+{
+ o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
+ return o;
+}
+
+XMLNode *
+find_named_node (const XMLNode& node, string name)
+{
+ XMLNodeList nlist;
+ XMLNodeConstIterator niter;
+ XMLNode* child;
+
+ nlist = node.children();
+
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+ child = *niter;
+
+ if (child->name() == name) {
+ return child;
+ }
+ }
+
+ return 0;
+}
+
+int
+cmp_nocase (const string& s, const string& s2)
+{
+ string::const_iterator p = s.begin();
+ string::const_iterator p2 = s2.begin();
+
+ while (p != s.end() && p2 != s2.end()) {
+ if (toupper(*p) != toupper(*p2)) {
+ return (toupper(*p) < toupper(*p2)) ? -1 : 1;
+ }
+ ++p;
+ ++p2;
+ }
+
+ return (s2.size() == s.size()) ? 0 : (s.size() < s2.size()) ? -1 : 1;
+}
+
+int
+tokenize_fullpath (string fullpath, string& path, string& name)
+{
+ string::size_type m = fullpath.find_last_of("/");
+
+ if (m == string::npos) {
+ path = fullpath;
+ name = fullpath;
+ return 1;
+ }
+
+ // does it look like just a directory?
+ if (m == fullpath.length()-1) {
+ return -1;
+ }
+ path = fullpath.substr(0, m+1);
+
+ string::size_type n = fullpath.find(".ardour", m);
+ // no .ardour?
+ if (n == string::npos) {
+ return -1;
+ }
+ name = fullpath.substr(m+1, n - m - 1);
+ return 1;
+}
+
+int
+touch_file(string path)
+{
+ FILE* file = fopen(path.c_str(), "a");
+ fclose(file);
+ return 1;
+}
+
+uint32_t long
+get_uid()
+{
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+
+ return (uint32_t long) tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+string
+placement_as_string (Placement p)
+{
+ switch (p) {
+ case PreFader:
+ return _("pre");
+ default: /* to get g++ to realize we have all the cases covered */
+ case PostFader:
+ return _("post");
+ }
+}
+
+string
+region_name_from_path (string path)
+{
+ string::size_type pos;
+
+ /* remove filename suffixes etc. */
+
+ if ((pos = path.find_last_of ('.')) != string::npos) {
+ path = path.substr (0, pos);
+ }
+
+ /* remove any "?R", "?L" or "?[a-z]" channel identifier */
+
+ string::size_type len = path.length();
+
+ if (len > 3 && (path[len-2] == '%' || path[len-2] == '?') &&
+ (path[len-1] == 'R' || path[len-1] == 'L' || (islower (path[len-1])))) {
+
+ path = path.substr (0, path.length() - 2);
+ }
+
+ return path;
+}
diff --git a/libs/ardour/vst_plugin.cc b/libs/ardour/vst_plugin.cc
new file mode 100644
index 0000000000..6465344d6e
--- /dev/null
+++ b/libs/ardour/vst_plugin.cc
@@ -0,0 +1,489 @@
+/*
+ Copyright (C) 2004 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.
+
+ $Id$
+*/
+
+#include <algorithm>
+#include <vector>
+#include <string>
+#include <ctype.h>
+
+#include <cstdlib>
+#include <cstdio> // so libraptor doesn't complain
+#include <cmath>
+#include <dirent.h>
+#include <string.h> // for memmove
+#include <sys/stat.h>
+#include <cerrno>
+
+#include <lrdf.h>
+#include <fst.h>
+
+#include <pbd/compose.h>
+#include <pbd/error.h>
+#include <pbd/pathscanner.h>
+#include <pbd/xml++.h>
+
+#include <vst/aeffectx.h>
+
+#include <midi++/manager.h>
+
+#include <ardour/ardour.h>
+#include <ardour/session.h>
+#include <ardour/audioengine.h>
+#include <ardour/vst_plugin.h>
+
+#include <pbd/stl_delete.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace ARDOUR;
+using std::min;
+using std::max;
+
+VSTPlugin::VSTPlugin (AudioEngine& e, Session& session, FSTHandle* h)
+ : Plugin (e, session)
+{
+ handle = h;
+
+ if ((_fst = fst_instantiate (handle, Session::vst_callback, this)) == 0) {
+ throw failed_constructor();
+ }
+
+ _plugin = _fst->plugin;
+ _plugin->user = this;
+
+ /* set rate and blocksize */
+
+ _plugin->dispatcher (_plugin, effSetSampleRate, 0, 0, NULL,
+ (float) session.frame_rate());
+ _plugin->dispatcher (_plugin, effSetBlockSize, 0,
+ session.get_block_size(), NULL, 0.0f);
+
+ /* set program to zero */
+
+ _plugin->dispatcher (_plugin, effSetProgram, 0, 0, NULL, 0.0f);
+
+ Plugin::setup_midi_controls ();
+}
+
+VSTPlugin::VSTPlugin (const VSTPlugin &other)
+ : Plugin (other)
+{
+ handle = other.handle;
+
+ if ((_fst = fst_instantiate (handle, Session::vst_callback, this)) == 0) {
+ throw failed_constructor();
+ }
+ _plugin = _fst->plugin;
+
+ Plugin::setup_midi_controls ();
+}
+
+VSTPlugin::~VSTPlugin ()
+{
+ deactivate ();
+ GoingAway (this); /* EMIT SIGNAL */
+ fst_close (_fst);
+}
+
+void
+VSTPlugin::set_block_size (jack_nframes_t nframes)
+{
+ deactivate ();
+ _plugin->dispatcher (_plugin, effSetBlockSize, 0, nframes, NULL, 0.0f);
+ activate ();
+}
+
+void
+VSTPlugin::store_state (PluginState& state)
+{
+}
+
+void
+VSTPlugin::restore_state (PluginState& state)
+{
+}
+
+float
+VSTPlugin::default_value (uint32_t port)
+{
+ return 0;
+}
+
+void
+VSTPlugin::set_parameter (uint32_t which, float val)
+{
+ _plugin->setParameter (_plugin, which, val);
+ ParameterChanged (which, val); /* EMIT SIGNAL */
+
+ if (session().get_midi_feedback()) {
+
+ if (which < parameter_count() && midi_controls[which]) {
+ midi_controls[which]->send_feedback (val);
+ }
+ }
+}
+
+float
+VSTPlugin::get_parameter (uint32_t which) const
+{
+ return _plugin->getParameter (_plugin, which);
+
+}
+
+uint32_t
+VSTPlugin::nth_parameter (uint32_t n, bool& ok) const
+{
+ ok = true;
+ return n;
+}
+
+XMLNode&
+VSTPlugin::get_state()
+{
+ XMLNode *root = new XMLNode (state_node_name());
+ LocaleGuard lg (X_("POSIX"));
+
+ if (_plugin->flags & effFlagsProgramChunks) {
+
+ /* fetch the current chunk */
+
+ void* data;
+ long data_size;
+
+ if ((data_size = _plugin->dispatcher (_plugin, effGetChunk, 0, 0, &data, false)) == 0) {
+ return *root;
+ }
+
+ /* save it to a file */
+
+ string path;
+ struct stat sbuf;
+
+ path = getenv ("HOME");
+ path += "/.ardour/vst";
+
+ if (stat (path.c_str(), &sbuf)) {
+ if (errno == ENOENT) {
+ if (mkdir (path.c_str(), 0600)) {
+ error << compose (_("cannot create VST chunk directory: %1"),
+ strerror (errno))
+ << endmsg;
+ return *root;
+ }
+
+ } else {
+
+ error << compose (_("cannot check VST chunk directory: %1"),
+ strerror (errno))
+ << endmsg;
+ return *root;
+ }
+
+ } else if (!S_ISDIR (sbuf.st_mode)) {
+ error << compose (_("%1 exists but is not a directory"), path)
+ << endmsg;
+ return *root;
+ }
+
+ path += "something";
+
+ /* store information */
+
+ XMLNode* chunk_node = new XMLNode (X_("chunk"));
+ chunk_node->add_property ("path", path);
+
+ root->add_child_nocopy (*chunk_node);
+
+ } else {
+
+ XMLNode* parameters = new XMLNode ("parameters");
+
+ for (long n = 0; n < _plugin->numParams; ++n) {
+ char index[64];
+ char val[32];
+ snprintf (index, sizeof (index), "param_%ld", n);
+ snprintf (val, sizeof (val), "%f", _plugin->getParameter (_plugin, n));
+ parameters->add_property (index, val);
+ }
+
+ root->add_child_nocopy (*parameters);
+ }
+
+ return *root;
+}
+
+int
+VSTPlugin::set_state(const XMLNode& node)
+{
+ LocaleGuard lg (X_("POSIX"));
+
+ if (node.name() != state_node_name()) {
+ error << _("Bad node sent to VSTPlugin::set_state") << endmsg;
+ return 0;
+ }
+
+ XMLNode* child;
+
+ if ((child = find_named_node (node, X_("chunks"))) != 0) {
+
+ return 0;
+
+ } else if ((child = find_named_node (node, X_("parameters"))) != 0) {
+
+ XMLPropertyList::const_iterator i;
+
+ for (i = child->properties().begin(); i != child->properties().end(); ++i) {
+ long param;
+ float val;
+ sscanf ((*i)->name().c_str(), "param_%ld", &param);
+ sscanf ((*i)->value().c_str(), "%f", &val);
+
+ _plugin->setParameter (_plugin, param, val);
+ }
+
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+VSTPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) const
+{
+ VstParameterProperties prop;
+
+ desc.min_unbound = false;
+ desc.max_unbound = false;
+
+ if (_plugin->dispatcher (_plugin, effGetParameterProperties, which, 0, &prop, 0)) {
+
+ /* i have yet to find or hear of a VST plugin that uses this */
+
+ if (prop.flags & kVstParameterUsesIntegerMinMax) {
+ desc.lower = prop.minInteger;
+ desc.upper = prop.maxInteger;
+ } else {
+ desc.lower = 0;
+ desc.upper = 1.0;
+ }
+
+ if (prop.flags & kVstParameterUsesIntStep) {
+
+ desc.step = prop.stepInteger;
+ desc.smallstep = prop.stepInteger;
+ desc.largestep = prop.stepInteger;
+
+ } else if (prop.flags & kVstParameterUsesFloatStep) {
+
+ desc.step = prop.stepFloat;
+ desc.smallstep = prop.smallStepFloat;
+ desc.largestep = prop.largeStepFloat;
+
+ } else {
+
+ float range = desc.upper - desc.lower;
+
+ desc.step = range / 100.0f;
+ desc.smallstep = desc.step / 2.0f;
+ desc.largestep = desc.step * 10.0f;
+ }
+
+ desc.toggled = prop.flags & kVstParameterIsSwitch;
+ desc.logarithmic = false;
+ desc.sr_dependent = false;
+ desc.label = prop.label;
+
+ } else {
+
+ /* old style */
+
+ char label[64];
+ label[0] = '\0';
+
+ _plugin->dispatcher (_plugin, effGetParamName, which, 0, label, 0);
+
+ desc.label = label;
+ desc.integer_step = false;
+ desc.lower = 0.0f;
+ desc.upper = 1.0f;
+ desc.step = 0.01f;
+ desc.smallstep = 0.005f;
+ desc.largestep = 0.1f;
+ desc.toggled = false;
+ desc.logarithmic = false;
+ desc.sr_dependent = false;
+ }
+
+ return 0;
+}
+
+bool
+VSTPlugin::load_preset (string name)
+{
+ if (_plugin->flags & effFlagsProgramChunks) {
+ error << _("no support for presets using chunks at this time")
+ << endmsg;
+ return false;
+ }
+ return Plugin::load_preset (name);
+}
+
+bool
+VSTPlugin::save_preset (string name)
+{
+ if (_plugin->flags & effFlagsProgramChunks) {
+ error << _("no support for presets using chunks at this time")
+ << endmsg;
+ return false;
+ }
+ return Plugin::save_preset (name, "vst");
+}
+
+string
+VSTPlugin::describe_parameter (uint32_t param)
+{
+ char name[64];
+ _plugin->dispatcher (_plugin, effGetParamName, param, 0, name, 0);
+ return name;
+}
+
+jack_nframes_t
+VSTPlugin::latency () const
+{
+ return _plugin->initialDelay;
+}
+
+set<uint32_t>
+VSTPlugin::automatable () const
+{
+ set<uint32_t> ret;
+
+ for (uint32_t i = 0; i < parameter_count(); ++i){
+ ret.insert (ret.end(), i);
+ }
+
+ return ret;
+}
+
+int
+VSTPlugin::connect_and_run (vector<Sample*>& bufs, uint32_t maxbuf, int32_t& in_index, int32_t& out_index, jack_nframes_t nframes, jack_nframes_t offset)
+{
+ float *ins[_plugin->numInputs];
+ float *outs[_plugin->numOutputs];
+ int32_t i;
+
+ for (i = 0; i < (int32_t) _plugin->numInputs; ++i) {
+ ins[i] = bufs[min((uint32_t) in_index,maxbuf)];
+ in_index++;
+ }
+
+ for (i = 0; i < (int32_t) _plugin->numOutputs; ++i) {
+ outs[i] = bufs[min((uint32_t) out_index,maxbuf)];
+
+ /* unbelievably, several VST plugins still rely on Cubase
+ behaviour and do not silence the buffer in processReplacing
+ when they have no output.
+ */
+
+ // memset (outs[i], 0, sizeof (Sample) * nframes);
+ out_index++;
+ }
+
+
+ /* we already know it can support processReplacing */
+
+ _plugin->processReplacing (_plugin, ins, outs, nframes);
+
+ return 0;
+}
+
+void
+VSTPlugin::deactivate ()
+{
+ _plugin->dispatcher (_plugin, effMainsChanged, 0, 0, NULL, 0.0f);
+}
+
+void
+VSTPlugin::activate ()
+{
+ _plugin->dispatcher (_plugin, effMainsChanged, 0, 1, NULL, 0.0f);
+}
+
+uint32_t
+VSTPlugin::unique_id() const
+{
+ return _plugin->uniqueID;
+}
+
+
+const char *
+VSTPlugin::name () const
+{
+ return handle->name;
+}
+
+const char *
+VSTPlugin::maker () const
+{
+ return "imadeit";
+}
+
+const char *
+VSTPlugin::label () const
+{
+ return handle->name;
+}
+
+uint32_t
+VSTPlugin::parameter_count() const
+{
+ return _plugin->numParams;
+}
+
+bool
+VSTPlugin::has_editor () const
+{
+ return _plugin->flags & effFlagsHasEditor;
+}
+
+void
+VSTPlugin::print_parameter (uint32_t param, char *buf, uint32_t len) const
+{
+ char lab[9];
+ char *first_nonws;
+
+ _plugin->dispatcher (_plugin, effGetParamLabel, param, 0, lab, 0);
+ _plugin->dispatcher (_plugin, effGetParamDisplay, param, 0, buf, 0);
+
+ if (buf[0] == '\0') {
+ return;
+ }
+
+ first_nonws = buf;
+ while (*first_nonws && isspace (*first_nonws)) {
+ first_nonws++;
+ }
+ if (*first_nonws == '\0') {
+ return;
+ }
+
+ memmove (buf, first_nonws, strlen (buf) - (first_nonws - buf) + 1);
+}