From 8af0757b61990767f2a85e68f535a5af9976fd79 Mon Sep 17 00:00:00 2001 From: Taybin Rutkin Date: Sat, 24 Sep 2005 19:13:41 +0000 Subject: libardour added. git-svn-id: svn://localhost/trunk/ardour2@17 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/.cvsignore | 7 + libs/ardour/ChangeLog | 118 + libs/ardour/SConscript | 206 ++ libs/ardour/ardour/.cvsignore | 1 + libs/ardour/ardour/ardour.h | 80 + libs/ardour/ardour/audio_library.h | 90 + libs/ardour/ardour/audio_track.h | 162 ++ libs/ardour/ardour/audioengine.h | 242 ++ libs/ardour/ardour/audiofilter.h | 53 + libs/ardour/ardour/audioplaylist.h | 121 + libs/ardour/ardour/audioregion.h | 229 ++ libs/ardour/ardour/auditioner.h | 73 + libs/ardour/ardour/automation_event.h | 247 ++ libs/ardour/ardour/click.h | 46 + libs/ardour/ardour/config.h | 57 + libs/ardour/ardour/configuration.h | 253 ++ libs/ardour/ardour/connection.h | 94 + libs/ardour/ardour/constsource.h | 60 + libs/ardour/ardour/crossfade.h | 182 ++ libs/ardour/ardour/crossfade_compare.h | 43 + libs/ardour/ardour/curve.h | 86 + libs/ardour/ardour/cycle_timer.h | 53 + libs/ardour/ardour/cycles.h | 221 ++ libs/ardour/ardour/dB.h | 34 + libs/ardour/ardour/diskstream.h | 433 ++++ libs/ardour/ardour/export.h | 88 + libs/ardour/ardour/filesource.h | 163 ++ libs/ardour/ardour/gain.h | 44 + libs/ardour/ardour/gdither.h | 93 + libs/ardour/ardour/gdither_types.h | 49 + libs/ardour/ardour/gdither_types_internal.h | 75 + libs/ardour/ardour/history.h | 153 ++ libs/ardour/ardour/insert.h | 183 ++ libs/ardour/ardour/io.h | 408 +++ libs/ardour/ardour/ladspa.h | 606 +++++ libs/ardour/ardour/ladspa_plugin.h | 142 ++ libs/ardour/ardour/location.h | 193 ++ libs/ardour/ardour/logcurve.h | 133 + libs/ardour/ardour/mix.h | 74 + libs/ardour/ardour/named_selection.h | 55 + libs/ardour/ardour/noise.h | 19 + libs/ardour/ardour/panner.h | 331 +++ libs/ardour/ardour/peak.h | 17 + libs/ardour/ardour/playlist.h | 278 ++ libs/ardour/ardour/playlist_templates.h | 49 + libs/ardour/ardour/plugin.h | 194 ++ libs/ardour/ardour/plugin_manager.h | 63 + libs/ardour/ardour/plugin_state.h | 14 + libs/ardour/ardour/port.h | 213 ++ libs/ardour/ardour/recent_sessions.h | 39 + libs/ardour/ardour/redirect.h | 153 ++ libs/ardour/ardour/region.h | 259 ++ libs/ardour/ardour/region_factory.h | 22 + libs/ardour/ardour/reverse.h | 38 + libs/ardour/ardour/route.h | 359 +++ libs/ardour/ardour/route_group.h | 111 + libs/ardour/ardour/route_group_specialized.h | 22 + libs/ardour/ardour/send.h | 63 + libs/ardour/ardour/seqsource.h | 55 + libs/ardour/ardour/session.h | 1754 +++++++++++++ libs/ardour/ardour/session_connection.h | 40 + libs/ardour/ardour/session_diskstream.h | 42 + libs/ardour/ardour/session_playlist.h | 47 + libs/ardour/ardour/session_region.h | 19 + libs/ardour/ardour/session_route.h | 89 + libs/ardour/ardour/session_selection.h | 40 + libs/ardour/ardour/silentsource.h | 56 + libs/ardour/ardour/slave.h | 151 ++ libs/ardour/ardour/sndfile_helpers.h | 37 + libs/ardour/ardour/sndfilesource.h | 63 + libs/ardour/ardour/soundseq.h | 54 + libs/ardour/ardour/source.h | 182 ++ libs/ardour/ardour/spline.h | 90 + libs/ardour/ardour/state_manager.h | 48 + libs/ardour/ardour/stateful.h | 51 + libs/ardour/ardour/tempo.h | 323 +++ libs/ardour/ardour/timestamps.h | 13 + libs/ardour/ardour/types.h | 243 ++ libs/ardour/ardour/utils.h | 59 + libs/ardour/ardour/vst_plugin.h | 112 + libs/ardour/audio_library.cc | 513 ++++ libs/ardour/audio_playlist.cc | 873 +++++++ libs/ardour/audio_track.cc | 1063 ++++++++ libs/ardour/audioengine.cc | 1115 ++++++++ libs/ardour/audiofilter.cc | 87 + libs/ardour/audioregion.cc | 1405 +++++++++++ libs/ardour/auditioner.cc | 181 ++ libs/ardour/automation.cc | 13 + libs/ardour/automation_event.cc | 1211 +++++++++ libs/ardour/configuration.cc | 1105 ++++++++ libs/ardour/connection.cc | 275 ++ libs/ardour/crossfade.cc | 877 +++++++ libs/ardour/curve.cc | 449 ++++ libs/ardour/cycle_timer.cc | 73 + libs/ardour/default_click.cc | 1175 +++++++++ libs/ardour/diskstream.cc | 2338 +++++++++++++++++ libs/ardour/filesource.cc | 1101 ++++++++ libs/ardour/gain.cc | 62 + libs/ardour/gdither.cc | 475 ++++ libs/ardour/gettext.h | 82 + libs/ardour/globals.cc | 440 ++++ libs/ardour/i18n.h | 11 + libs/ardour/import.cc | 380 +++ libs/ardour/insert.cc | 1026 ++++++++ libs/ardour/io.cc | 2821 +++++++++++++++++++++ libs/ardour/jack_slave.cc | 91 + libs/ardour/ladspa_plugin.cc | 724 ++++++ libs/ardour/location.cc | 718 ++++++ libs/ardour/mix.cc | 149 ++ libs/ardour/mtc_slave.cc | 321 +++ libs/ardour/named_selection.cc | 117 + libs/ardour/panner.cc | 1670 ++++++++++++ libs/ardour/playlist.cc | 1754 +++++++++++++ libs/ardour/playlist_factory.cc | 68 + libs/ardour/plugin.cc | 351 +++ libs/ardour/plugin_manager.cc | 486 ++++ libs/ardour/port.cc | 63 + libs/ardour/recent_sessions.cc | 127 + libs/ardour/redirect.cc | 465 ++++ libs/ardour/region.cc | 995 ++++++++ libs/ardour/reverse.cc | 125 + libs/ardour/route.cc | 2421 ++++++++++++++++++ libs/ardour/route_group.cc | 184 ++ libs/ardour/send.cc | 162 ++ libs/ardour/session.cc | 3491 ++++++++++++++++++++++++++ libs/ardour/session_butler.cc | 460 ++++ libs/ardour/session_click.cc | 253 ++ libs/ardour/session_events.cc | 440 ++++ libs/ardour/session_export.cc | 640 +++++ libs/ardour/session_feedback.cc | 245 ++ libs/ardour/session_midi.cc | 1498 +++++++++++ libs/ardour/session_process.cc | 816 ++++++ libs/ardour/session_state.cc | 3121 +++++++++++++++++++++++ libs/ardour/session_time.cc | 853 +++++++ libs/ardour/session_timefx.cc | 187 ++ libs/ardour/session_transport.cc | 1151 +++++++++ libs/ardour/session_vst.cc | 316 +++ libs/ardour/sndfile_helpers.cc | 162 ++ libs/ardour/sndfilesource.cc | 207 ++ libs/ardour/source.cc | 842 +++++++ libs/ardour/state_manager.cc | 66 + libs/ardour/stateful.cc | 133 + libs/ardour/tempo.cc | 1323 ++++++++++ libs/ardour/utils.cc | 224 ++ libs/ardour/vst_plugin.cc | 489 ++++ 145 files changed, 58521 insertions(+) create mode 100644 libs/ardour/.cvsignore create mode 100644 libs/ardour/ChangeLog create mode 100644 libs/ardour/SConscript create mode 100644 libs/ardour/ardour/.cvsignore create mode 100644 libs/ardour/ardour/ardour.h create mode 100644 libs/ardour/ardour/audio_library.h create mode 100644 libs/ardour/ardour/audio_track.h create mode 100644 libs/ardour/ardour/audioengine.h create mode 100644 libs/ardour/ardour/audiofilter.h create mode 100644 libs/ardour/ardour/audioplaylist.h create mode 100644 libs/ardour/ardour/audioregion.h create mode 100644 libs/ardour/ardour/auditioner.h create mode 100644 libs/ardour/ardour/automation_event.h create mode 100644 libs/ardour/ardour/click.h create mode 100644 libs/ardour/ardour/config.h create mode 100644 libs/ardour/ardour/configuration.h create mode 100644 libs/ardour/ardour/connection.h create mode 100644 libs/ardour/ardour/constsource.h create mode 100644 libs/ardour/ardour/crossfade.h create mode 100644 libs/ardour/ardour/crossfade_compare.h create mode 100644 libs/ardour/ardour/curve.h create mode 100644 libs/ardour/ardour/cycle_timer.h create mode 100644 libs/ardour/ardour/cycles.h create mode 100644 libs/ardour/ardour/dB.h create mode 100644 libs/ardour/ardour/diskstream.h create mode 100644 libs/ardour/ardour/export.h create mode 100644 libs/ardour/ardour/filesource.h create mode 100644 libs/ardour/ardour/gain.h create mode 100644 libs/ardour/ardour/gdither.h create mode 100644 libs/ardour/ardour/gdither_types.h create mode 100644 libs/ardour/ardour/gdither_types_internal.h create mode 100644 libs/ardour/ardour/history.h create mode 100644 libs/ardour/ardour/insert.h create mode 100644 libs/ardour/ardour/io.h create mode 100644 libs/ardour/ardour/ladspa.h create mode 100644 libs/ardour/ardour/ladspa_plugin.h create mode 100644 libs/ardour/ardour/location.h create mode 100644 libs/ardour/ardour/logcurve.h create mode 100644 libs/ardour/ardour/mix.h create mode 100644 libs/ardour/ardour/named_selection.h create mode 100644 libs/ardour/ardour/noise.h create mode 100644 libs/ardour/ardour/panner.h create mode 100644 libs/ardour/ardour/peak.h create mode 100644 libs/ardour/ardour/playlist.h create mode 100644 libs/ardour/ardour/playlist_templates.h create mode 100644 libs/ardour/ardour/plugin.h create mode 100644 libs/ardour/ardour/plugin_manager.h create mode 100644 libs/ardour/ardour/plugin_state.h create mode 100644 libs/ardour/ardour/port.h create mode 100644 libs/ardour/ardour/recent_sessions.h create mode 100644 libs/ardour/ardour/redirect.h create mode 100644 libs/ardour/ardour/region.h create mode 100644 libs/ardour/ardour/region_factory.h create mode 100644 libs/ardour/ardour/reverse.h create mode 100644 libs/ardour/ardour/route.h create mode 100644 libs/ardour/ardour/route_group.h create mode 100644 libs/ardour/ardour/route_group_specialized.h create mode 100644 libs/ardour/ardour/send.h create mode 100644 libs/ardour/ardour/seqsource.h create mode 100644 libs/ardour/ardour/session.h create mode 100644 libs/ardour/ardour/session_connection.h create mode 100644 libs/ardour/ardour/session_diskstream.h create mode 100644 libs/ardour/ardour/session_playlist.h create mode 100644 libs/ardour/ardour/session_region.h create mode 100644 libs/ardour/ardour/session_route.h create mode 100644 libs/ardour/ardour/session_selection.h create mode 100644 libs/ardour/ardour/silentsource.h create mode 100644 libs/ardour/ardour/slave.h create mode 100644 libs/ardour/ardour/sndfile_helpers.h create mode 100644 libs/ardour/ardour/sndfilesource.h create mode 100644 libs/ardour/ardour/soundseq.h create mode 100644 libs/ardour/ardour/source.h create mode 100644 libs/ardour/ardour/spline.h create mode 100644 libs/ardour/ardour/state_manager.h create mode 100644 libs/ardour/ardour/stateful.h create mode 100644 libs/ardour/ardour/tempo.h create mode 100644 libs/ardour/ardour/timestamps.h create mode 100644 libs/ardour/ardour/types.h create mode 100644 libs/ardour/ardour/utils.h create mode 100644 libs/ardour/ardour/vst_plugin.h create mode 100644 libs/ardour/audio_library.cc create mode 100644 libs/ardour/audio_playlist.cc create mode 100644 libs/ardour/audio_track.cc create mode 100644 libs/ardour/audioengine.cc create mode 100644 libs/ardour/audiofilter.cc create mode 100644 libs/ardour/audioregion.cc create mode 100644 libs/ardour/auditioner.cc create mode 100644 libs/ardour/automation.cc create mode 100644 libs/ardour/automation_event.cc create mode 100644 libs/ardour/configuration.cc create mode 100644 libs/ardour/connection.cc create mode 100644 libs/ardour/crossfade.cc create mode 100644 libs/ardour/curve.cc create mode 100644 libs/ardour/cycle_timer.cc create mode 100644 libs/ardour/default_click.cc create mode 100644 libs/ardour/diskstream.cc create mode 100644 libs/ardour/filesource.cc create mode 100644 libs/ardour/gain.cc create mode 100644 libs/ardour/gdither.cc create mode 100644 libs/ardour/gettext.h create mode 100644 libs/ardour/globals.cc create mode 100644 libs/ardour/i18n.h create mode 100644 libs/ardour/import.cc create mode 100644 libs/ardour/insert.cc create mode 100644 libs/ardour/io.cc create mode 100644 libs/ardour/jack_slave.cc create mode 100644 libs/ardour/ladspa_plugin.cc create mode 100644 libs/ardour/location.cc create mode 100644 libs/ardour/mix.cc create mode 100644 libs/ardour/mtc_slave.cc create mode 100644 libs/ardour/named_selection.cc create mode 100644 libs/ardour/panner.cc create mode 100644 libs/ardour/playlist.cc create mode 100644 libs/ardour/playlist_factory.cc create mode 100644 libs/ardour/plugin.cc create mode 100644 libs/ardour/plugin_manager.cc create mode 100644 libs/ardour/port.cc create mode 100644 libs/ardour/recent_sessions.cc create mode 100644 libs/ardour/redirect.cc create mode 100644 libs/ardour/region.cc create mode 100644 libs/ardour/reverse.cc create mode 100644 libs/ardour/route.cc create mode 100644 libs/ardour/route_group.cc create mode 100644 libs/ardour/send.cc create mode 100644 libs/ardour/session.cc create mode 100644 libs/ardour/session_butler.cc create mode 100644 libs/ardour/session_click.cc create mode 100644 libs/ardour/session_events.cc create mode 100644 libs/ardour/session_export.cc create mode 100644 libs/ardour/session_feedback.cc create mode 100644 libs/ardour/session_midi.cc create mode 100644 libs/ardour/session_process.cc create mode 100644 libs/ardour/session_state.cc create mode 100644 libs/ardour/session_time.cc create mode 100644 libs/ardour/session_timefx.cc create mode 100644 libs/ardour/session_transport.cc create mode 100644 libs/ardour/session_vst.cc create mode 100644 libs/ardour/sndfile_helpers.cc create mode 100644 libs/ardour/sndfilesource.cc create mode 100644 libs/ardour/source.cc create mode 100644 libs/ardour/state_manager.cc create mode 100644 libs/ardour/stateful.cc create mode 100644 libs/ardour/tempo.cc create mode 100644 libs/ardour/utils.cc create mode 100644 libs/ardour/vst_plugin.cc (limited to 'libs') 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 + + * configure.ac (AC_OUTPUT): Add intl/Makefile, + +2002-11-24 gettextize + + * Makefile.am (ACLOCAL_AMFLAGS): New variable. + +2001-10-26 Paul Davis + + * 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 + + 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 + + * session.cc (get_state): in get_state, use the public order for routes. + +2001-10-18 Paul Davis + + * playlist.cc (read): stop a muted region from causing a playlist + read error. + +2001-10-17 Paul Davis + + * region.cc (read_at): remove staccato noise caused by not + shifting target buffer when !opaque. + +2001-10-15 Paul Davis + + * 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 + + * session.cc (XMLRegionFactory): move most XML-based Region + constructor into region. + + +2001-10-10 Paul Davis + + * session.cc (load_sources): add whole-file regions when loading + sources. + +2001-10-09 Paul Davis + + * ardour/session.h: fix an ugly bug with a non-reference return type. + +2001-10-04 Paul Davis + + * 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 + + * 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 + + * 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 instead of GList + for diskstreams. add auditioner object. + +2001-09-30 Paul Davis + + * 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 + + * 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 + + * 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 + + * 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 +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 +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 +#include +#include + +#include +#include +#include + +#include +#include + +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 +#include +#include + +#include + +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& 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& members, string parent_uri = ""); + string get_member_filename (string uri); + + void search_members_and (list& results, + const map& fields); + void search_members_or (list& results, + const map& fields); + + void add_field (string field); + void get_fields (list& 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 added_group; // group, parent + sigc::signal added_member;// member, parent + sigc::signal removed_group; + sigc::signal removed_member; + sigc::signal 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 + +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& buffers, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t end_frame); + + sigc::signal diskstream_changed; + + enum FreezeState { + NoFreeze, + Frozen, + UnFrozen + }; + + FreezeState freeze_state() const; + + sigc::signal 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 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 Freewheel; + + sigc::signal Xrun; + + /* this signal is if JACK notifies us of a graph order event */ + + sigc::signal GraphReordered; + + /* this signal is emitted if the sample rate changes */ + + sigc::signal SampleRateChanged; + + /* this signal is sent if JACK ever disconnects us */ + + sigc::signal Halted; + + /* these two are emitted when the engine itself is + started and stopped + */ + + sigc::signal Running; + sigc::signal 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 freewheel_action; + bool reconnect_on_halt; + int _usecs_per_cycle; + + typedef std::set Ports; + Ports ports; + + int process_callback (jack_nframes_t nframes); + void remove_all_ports (); + + typedef std::pair PortConnection; + typedef std::list 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 +#include + +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 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 +#include + +#include +#include + +namespace ARDOUR { + +class Session; +class Region; +class AudioRegion; +class Source; + +class AudioPlaylist : public ARDOUR::Playlist +{ + public: + typedef std::list Crossfades; + + private: + + struct State : public ARDOUR::StateManager::State { + RegionList regions; + std::list region_states; + + Crossfades crossfades; + std::list 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 NewCrossfade; + + template void foreach_crossfade (T *t, void (T::*func)(Crossfade *)); + void crossfades_at (jack_nframes_t frame, Crossfades&); + + template 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&); + void get_region_list_equivalent_regions (const AudioRegion&, std::vector&); + + 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 + +#include +#include + +#include +#include +#include +#include +#include + +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 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 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&) 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 +#include + +#include +#include + +#include +#include + +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 +#include +#include + +#include +#include +#include +#include +#include +#include + +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 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 automation_style_changed; + + void set_automation_style (AutoStyle m); + AutoStyle automation_style() const { return _style; } + sigc::signal 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 control_points_adjacent (double when); + + template 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 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 + +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 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 header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_LADSPA_H 1 + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the 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 + +#include +#include + +#include +#include + +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 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 +#include +#include +#include +#include + +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 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 NameChanged; + sigc::signal ConfigurationChanged; + sigc::signal 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 _ports; + string _name; + bool _sysdep; + + int set_connections (const string& str); + int parse_io_string (const string& str, vector& 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 +#include + +#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 +#include + +#include + +#include + +#include +#include +#include +#include +#include + +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 == ®ion || _out == ®ion; + } + + 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 Invalidated; + sigc::signal 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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include + +#include + +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 + +#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 +#include +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 + +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 + +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 + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 record_enable_changed; + sigc::signal speed_changed; + sigc::signal reverse_changed; + sigc::signal PlaylistChanged; + sigc::signal AlignmentStyleChanged; + + static sigc::signal DiskOverrun; + static sigc::signal DiskUnderrun; + static sigc::signal DiskStreamCreated; // XXX use a ref with sigc2 + static sigc::signal CannotRecordNoInput; // XXX use a ref with sigc2 + static sigc::signal*> 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 LoopSet; + + std::list& 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 *playback_buf; + RingBufferNPT *capture_buf; + + Sample* scrub_buffer; + Sample* scrub_forward_buffer; + Sample* scrub_reverse_buffer; + + RingBufferNPT::rw_vector playback_vector; + RingBufferNPT::rw_vector capture_vector; + }; + + typedef vector 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 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 _last_capture_regions; + std::vector 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 +#include +#include + +#include + +#include +#include + +#include +#include + +using std::map; +using std::vector; +using std::string; +using std::pair; + +namespace ARDOUR +{ + class Port; + + typedef pair PortChannelPair; + typedef map > 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 +#include + +#include + +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 chunk_info; + + struct { + WAVEChunk wave; + FMTChunk format; + GenericChunk data; + BroadcastChunk bext; + vector 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 + * + * 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 + * + * 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 + * + * 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 + +#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 +#include + +template +//struct History : public SigC::Object +struct History : public sigc::trackable +{ + typedef list StateList; + + StateList states; + StateList::iterator current; + + History() { current = states.end(); } + ~History() { states.erase (states.begin(), states.end()); } + + sigc::signal 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 +#include +#include + +#include +#include +#include +#include + +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& 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& 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& 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 _plugins; + void automation_run (vector& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset); + void connect_and_run (vector& 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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& bufs, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset, gain_t gain_coeff); + void pan_automated (vector& 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&, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset); + void deliver_output (vector&, uint32_t nbufs, jack_nframes_t nframes, jack_nframes_t offset); + void deliver_output_no_pan (vector&, 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 input_changed; + sigc::signal output_changed; + + sigc::signal gain_changed; + sigc::signal 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 PortsLegal; + static sigc::signal PannersLegal; + static sigc::signal ConnectingLegal; + static sigc::signal MoreOutputs; + static sigc::signal 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 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 gain_automation_state_changed; + + virtual void set_gain_automation_style (AutoStyle); + AutoStyle gain_automation_style () const { return _gain_automation_curve.automation_style(); } + sigc::signal 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 _outputs; + vector _inputs; + vector _peak_power; + vector _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&, 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& chns); + + static int parse_gain_string (const string&, vector& chns); + + int set_sources (vector&, void *src, bool add); + int set_destinations (vector&, 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +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 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& 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 +#include +#include +#include + +#include +#include +#include + +#include +#include + +#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 name_changed; + sigc::signal end_changed; + sigc::signal start_changed; + + sigc::signal FlagsChanged; + + /* this is sent only when both start&end change at the same time */ + + sigc::signal changed; + + /* CD Track / CD-Text info */ + + std::map 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 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 current_changed; + sigc::signal changed; + sigc::signal added; + sigc::signal removed; + + template void apply (T& obj, void (T::*method)(LocationList&)) { + LockMonitor lm (lock, __LINE__, __FILE__); + (obj.*method)(locations); + } + + template 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 +#include + +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 +#include +#include + +#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 +#include + +#include + +class XMLNode; + +namespace ARDOUR +{ +class Session; +class Playlist; + +struct NamedSelection : public Stateful +{ + NamedSelection (std::string, std::list&); + NamedSelection (Session&, const XMLNode&); + virtual ~NamedSelection (); + + std::string name; + std::list playlists; + + XMLNode& get_state (void); + + int set_state (const XMLNode&); + + static sigc::signal 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 +#include +#include +#include +#include + +#include + +#include +#include +#include + +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 Changed; /* for position */ + sigc::signal 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, 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 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 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 LinkStateChanged; + sigc::signal 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 +#include +#include + +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 +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace ARDOUR { + +class Session; +class Region; + +class Playlist : public Stateful, public StateManager { + public: + typedef list 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&, bool result_is_hidden = true); + Playlist* copy (list&, 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 void foreach_region (T *t, void (T::*func)(Region *, void *), void *arg); + template void foreach_region (T *t, void (T::*func)(Region *)); + + XMLNode& get_state (); + int set_state (const XMLNode&); + XMLNode& get_template (); + + sigc::signal RegionAdded; + sigc::signal RegionRemoved; + + sigc::signal InUse; + sigc::signal Modified; + sigc::signal NameChanged; + sigc::signal LengthChanged; + sigc::signal LayeringChanged; + sigc::signal GoingAway; + sigc::signal StatePushed; + + static sigc::signal 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& 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 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 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 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 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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& bufs, uint32_t maxbuf, int32_t& in, int32_t& out, jack_nframes_t nframes, jack_nframes_t offset) = 0; + virtual std::set 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 get_presets(); + + virtual bool has_editor() const = 0; + + sigc::signal ParameterChanged; + sigc::signal 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 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 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 +#include +#include + +#include + +namespace ARDOUR { + +class PluginInfo; +class Plugin; +class Session; +class AudioEngine; + +class PluginManager { + public: + PluginManager (ARDOUR::AudioEngine&); + ~PluginManager (); + + std::list &vst_plugin_info () { return _vst_plugin_info; } + std::list &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 _vst_plugin_info; + std::list _ladspa_plugin_info; + std::map 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 + +namespace ARDOUR { + +struct PluginState { + std::map 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 +#include +#include +#include + +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 MonitorInputChanged; + sigc::signal 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 +#include +#include + +using std::deque; +using std::pair; +using std::string; + +namespace ARDOUR { + typedef deque > 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +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& 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 active_changed; + sigc::signal sort_key_changed; + sigc::signal placement_changed; + sigc::signal AutomationPlaybackChanged; + sigc::signal AutomationChanged; + sigc::signal GoingAway; + + static sigc::signal 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&) const; + void what_has_visible_automation (set&) const; + const set& 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 parameter_automation; + set visible_parameter_automation; + + mutable PBD::NonBlockingLock _automation_lock; + + void can_automate (uint32_t); + set 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 + +#include +#include +#include + +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 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 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 +#include + +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 + +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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 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 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 solo_changed; + sigc::signal solo_safe_changed; + sigc::signal comment_changed; + sigc::signal mute_changed; + sigc::signal pre_fader_changed; + sigc::signal post_fader_changed; + sigc::signal control_outs_changed; + sigc::signal main_outs_changed; + sigc::signal redirects_changed; + sigc::signal record_enable_changed; + sigc::signal edit_group_changed; + sigc::signal mix_group_changed; + sigc::signal active_changed; + sigc::signal meter_change; + + sigc::signal GoingAway; + + /* gui's call this for their own purposes. */ + + sigc::signal gui_changed; + + /* stateful */ + + XMLNode& get_state(); + int set_state(const XMLNode& node); + XMLNode& get_template(); + + sigc::signal SelectedChanged; + + /* undo */ + + UndoAction get_memento() const; + void set_state (state_id_t); + + int set_control_outs (const vector& ports); + IO* control_outs() { return _control_outs; } + + bool feeds (Route *); + set 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& 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 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& iclist); + int32_t check_some_plugin_counts (std::list& 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 +#include +#include +#include +#include +#include + +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 void apply (void (Route::*func)(T, void *), T val, void *src) { + for (list::iterator i = routes.begin(); i != routes.end(); i++) { + ((*i)->*func)(val, this); + } + } + + template void foreach_route (T *obj, void (T::*func)(Route&)) { + for (list::iterator i = routes.begin(); i != routes.end(); i++) { + (obj->*func)(**i); + } + } + + /* to use these, #include */ + + template void apply (void (AudioTrack::*func)(T, void *), T val, void *src); + + void clear () { + routes.clear (); + changed(); + } + + const list& route_list() { return routes; } + + sigc::signal changed; + sigc::signal FlagsChanged; + + XMLNode& get_state (void); + + int set_state (const XMLNode&); + + private: + list 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 +#include + +namespace ARDOUR { + +template void +RouteGroup::apply (void (AudioTrack::*func)(T, void *), T val, void *src) +{ + for (list::iterator i = routes.begin(); i != routes.end(); i++) { + AudioTrack *at; + if ((at = dynamic_cast(*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 +#include + +#include +#include + +#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 &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 + +#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 +#if __GNUC__ >= 3 +#include +using __gnu_cxx::slist; +#else +#include +#endif +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +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 RouteBooleanState; + typedef vector GlobalRouteBooleanState; + typedef std::pair RouteMeterState; + typedef vector 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 audio_range; + list 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 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&); + + static string peak_path_from_audio_path (string); + static string old_peak_path_from_audio_path (string); + + void process (jack_nframes_t nframes); + + vector& get_passthru_buffers() { return _passthru_buffers; } + vector& 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 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 void foreach_diskstream (T *obj, void (T::*func)(DiskStream&)); + + typedef slist 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 void foreach_route (T *obj, void (T::*func)(Route&)); + template void foreach_route (T *obj, void (T::*func)(Route*)); + template 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 going_away; + + /* Proxy signal for region hidden changes */ + + sigc::signal RegionHiddenChange; + + /* Emitted when all i/o connections are complete */ + + sigc::signal IOConnectionsComplete; + + /* Record status signals */ + + sigc::signal RecordEnabled; + sigc::signal RecordDisabled; + + /* Transport mechanism signals */ + + sigc::signal TransportStateChange; /* generic */ + sigc::signal PositionChanged; /* sent after any non-sequential motion */ + sigc::signal DurationChanged; + sigc::signal HaltOnXrun; + + sigc::signal RouteAdded; + sigc::signal 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 auto_loop_location_changed; + sigc::signal auto_punch_location_changed; + sigc::signal 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 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 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 StateSaved; + sigc::signal StateReady; + + vector* possible_states() const; + static vector* 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 edit_group_added; + sigc::signal mix_group_added; + + template void foreach_edit_group (T *obj, void (T::*func)(RouteGroup *)) { + list::iterator i; + for (i = edit_groups.begin(); i != edit_groups.end(); i++) { + (obj->*func)(*i); + } + } + + template void foreach_mix_group (T *obj, void (T::*func)(RouteGroup *)) { + list::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 MeterHoldChanged; + + float meter_falloff () { return _meter_falloff; } + void set_meter_falloff (float); + sigc::signal 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 SMPTEOffsetChanged; + sigc::signal 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 AudioRegionAdded; + sigc::signal 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& result); + + AudioRegion *XMLRegionFactory (const XMLNode&, bool full); + + template 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 paths; + int32_t space; + }; + + int cleanup_sources (cleanup_report&); + int cleanup_trash_sources (cleanup_report&); + + int destroy_region (Region*); + int destroy_regions (list); + + 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 AskAboutPlaylistDeletion; + + + /* handlers should return !0 for use pending state, 0 for + ignore it. + */ + + static sigc::signal AskAboutPendingState; + + sigc::signal SourceAdded; + sigc::signal 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 PlaylistAdded; + sigc::signal PlaylistRemoved; + + Playlist *get_playlist (string name); + + uint32_t n_playlists() const; + + template 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 void foreach_named_selection (T& obj, void (T::*func)(NamedSelection&)); + sigc::signal NamedSelectionAdded; + sigc::signal 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 AuditionActive; + + /* flattening stuff */ + + int write_one_track (AudioTrack&, jack_nframes_t start, jack_nframes_t cnt, bool overwrite, vector&, + 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 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 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 ConnectionAdded; + sigc::signal 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 MTC_PortChanged; + sigc::signal MMC_PortChanged; + sigc::signal 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 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&); + void set_music_range (list&); + + void request_play_range (bool yn); + bool get_play_range () const { return _play_range; } + + /* favorite dirs */ + typedef vector 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 _passthru_buffers; + vector _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 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 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 MidiTimeoutCallback; + typedef list 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 FeedbackFunctionPtr; + static void* _feedback_thread_work (void *); + void* feedback_thread_work (); + int feedback_generic_midi_function (); + std::list 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(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 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 edit_groups; + list 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 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 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 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 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 _port_inserts; + slist _plugin_inserts; + slist _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 session_dirs; + vector::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 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 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 master_outs; + + EditMode _edit_mode; + EditMode pending_edit_mode; + + /* range playback */ + + list 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& result); + int find_all_sources_across_snapshots (std::set& 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 +#include + +namespace ARDOUR { + +template 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 +#include + +namespace ARDOUR { + +template 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 +#include + +namespace ARDOUR { + +template 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 +#include + +namespace ARDOUR { + +template 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 + +#include +#include +#include + +namespace ARDOUR { + +template 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 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 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 +#include + +namespace ARDOUR { + +template 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 +#include + +#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 + +#include + +#include +#include +#include +#include +#include + +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 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 +#include + +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 + +#include + +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 +#include +#include + +#include + +#include + +#include +#include +#include + +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) const; + + static sigc::signal SourceCreated; + + sigc::signal GoingAway; + mutable sigc::signal PeaksReady; + mutable sigc::signal 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 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 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 +#include + +#include + +#include + +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 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 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 + +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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 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 BBTPointList; + + template 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 +#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; C++ requires explicit requesting of these */ +#endif + +#include +#include +#include + +#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 +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +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 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& 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 // Needed so that libraptor (included in lrdf) won't complain +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#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 items; + list::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& 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& 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& members, + const map& fields) +{ + lrdf_statement **head; + lrdf_statement* pattern = 0; + lrdf_statement* old = 0; + head = &pattern; + + map::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& members, + const map& fields) +{ + map::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& 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 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 all_xfades; + set 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 (*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::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) { + (*ar)->unlock_sources (); + delete *ar; + } + + /* delete every crossfade */ + + for (set::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 > relevant_regions; + map > relevant_xfades; + vector 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::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) { + + vector& r (relevant_regions[*l]); + vector& x (relevant_xfades[*l]); + + for (vector::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::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 (®ion); + + 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(&r); + set 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 (&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 (*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 all_xfades; + set 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 (*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::iterator i = regions.begin(); i != regions.end(); ++i) { + set::iterator x = all_regions.find (*i); + if (x != all_regions.end()) { + all_regions.erase (x); + } + } + + /* ditto for every crossfade */ + + for (list::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { + set::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::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::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); + + in_set_state = true; + + regions = apstate->regions; + + for (list::iterator s = apstate->region_states.begin(); s != apstate->region_states.end(); ++s) { + *s; + } + + _crossfades = apstate->crossfades; + + for (list::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 (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 (region); + bool changed = false; + Crossfades::iterator c, ctmp; + set 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 (*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::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::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& results) +{ + for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + + AudioRegion* ar = dynamic_cast (*i); + + if (ar && ar->equivalent (other)) { + results.push_back (ar); + } + } +} + +void +AudioPlaylist::get_region_list_equivalent_regions (const AudioRegion& other, vector& results) +{ + for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + + AudioRegion* ar = dynamic_cast (*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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#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 (&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::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::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 (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& 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::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& 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::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(*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(*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 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 srcs; + _session.write_one_track (*this, start, end, false, srcs, itt); +} + +void +AudioTrack::freeze (InterThreadInfo& itt) +{ + Insert* insert; + vector 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 ("%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(*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(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::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::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::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 +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef VST_SUPPORT +#include +#endif + +#include + +#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 (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 (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(arg)->Xrun (); /* EMIT SIGNAL */ + return 0; +} + +int +AudioEngine::_graph_order_callback (void *arg) +{ + static_cast(arg)->GraphReordered (); /* EMIT SIGNAL */ + return 0; +} + +int +AudioEngine::_process_callback (jack_nframes_t nframes, void *arg) +{ + return static_cast (arg)->process_callback (nframes); +} + +void +AudioEngine::_freewheel_callback (int onoff, void *arg) +{ + static_cast(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 (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 (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 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 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 (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 +#include + +#include +#include +#include +#include +#include + +#include "i18n.h" + +using namespace ARDOUR; + +int +AudioFilter::make_new_sources (AudioRegion& region, AudioRegion::SourceList& nsrcs) +{ + vector 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((*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 +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include + +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 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 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 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 (&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 (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& 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 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 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 +AudioRegion::master_source_names () +{ + SourceList::iterator i; + + vector 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 + +#include +#include +#include +#include +#include +#include +#include + +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 + +template +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 +#include +#include +#include +#include +#include +#include + +#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(&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 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 (*i)) != 0) { + + for (AutomationEventList::iterator x = asp->events.begin(); x != asp->events.end(); ++x) { + all_events.insert (*x); + } + } + } + + for (std::set::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::control_points_adjacent (double xval) +{ + LockMonitor lm (lock, __LINE__, __FILE__); + iterator i; + TimeComparator cmp; + ControlEvent cp (xval, 0.0f); + std::pair 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); + + 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 (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::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::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::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 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 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 +#include /* for snprintf, grrr */ + +#ifdef HAVE_WORDEXP +#include +#endif + +#include +#include + +#include +#include +#include + +#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::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 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 + +#include +#include +#include +#include + +#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::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 (this)) { + node = new XMLNode ("InputConnection"); + } else { + node = new XMLNode ("OutputConnection"); + } + + node->add_property ("name", _name); + + for (vector::iterator i = _ports.begin(); i != _ports.end(); ++i) { + + str += '{'; + + for (vector::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 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& 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include + +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 = ∈ + _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 (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 (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 (&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 (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 +#include +#include +#include +#include +#include + +#include +#include + +#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(*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 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 (*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(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 +#include +#include + +#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 +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include + +using namespace std; +using namespace ARDOUR; + +jack_nframes_t DiskStream::disk_io_chunk_frames; + +sigc::signal DiskStream::DiskStreamCreated; +sigc::signal DiskStream::CannotRecordNoInput; +sigc::signal*> DiskStream::DeleteSources; +sigc::signal DiskStream::DiskOverrun; +sigc::signal 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 (_session.diskstream_buffer_size()); + chan.capture_buf = new RingBufferNPT (_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 (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::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::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::iterator ci; + uint32_t n = 0; + list* 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; + + 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(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::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 + +/* This is is very hacky way to get pread and pwrite declarations. + First, include so that we can avoid its #undef __USE_UNIX98. + Then define __USE_UNIX98, include , 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 +#endif + +#if __GNUC__ >= 3 +// #define _XOPEN_SOURCE 500 +#include +#else +#define __USE_UNIX98 +#include +#undef __USE_UNIX98 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for rename(2) */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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* 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::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::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::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::iterator i = chunk_info.begin(); i != chunk_info.end();) { + vector::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::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=" <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 + +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 + * + * 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 +#include +#include + +/* this monstrosity is necessary to get access to lrintf() and random(). + whoever is writing the glibc headers and should be + hauled off to a programmer re-education camp. for the rest of + their natural lives. or longer. +*/ + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 +#ifdef __cplusplus +#include +#else +#include +#endif + +#undef __USE_SVID +#define __USE_SVID 1 +#ifdef __cplusplus +#include +#else +#include +#endif + +#include + +/* 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 . + 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 + +#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 a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#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 // Needed so that libraptor (included in lrdf) won't complain +#include +#include +#include +#include +#include + +#ifdef VST_SUPPORT +#include +#endif + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined (__APPLE__) + #include // 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::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::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 +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 new_regions; + vector 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::iterator i = new_regions.begin(); i != new_regions.end(); ++i) { + delete *i; + } + + for (vector::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 + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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::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 a; + + a = _plugins.front()->automatable (); + + for (set::iterator i = a.begin(); i != a.end(); ++i) { + can_automate (*i); + } +} + +void +PluginInsert::parameter_changed (uint32_t which, float val) +{ + vector::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::iterator i = _plugins.begin(); i != _plugins.end(); ++i) { + (*i)->set_block_size (nframes); + } +} + +void +PluginInsert::activate () +{ + for (vector::iterator i = _plugins.begin(); i != _plugins.end(); ++i) { + (*i)->activate (); + } +} + +void +PluginInsert::deactivate () +{ + for (vector::iterator i = _plugins.begin(); i != _plugins.end(); ++i) { + (*i)->deactivate (); + } +} + +void +PluginInsert::connect_and_run (vector& 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::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::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::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::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::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& 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& 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 automated_params; + + what_has_automation (automated_params); + + for (set::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 (&other)) != 0) { + return new LadspaPlugin (*lp); +#ifdef VST_SUPPORT + } else if ((vp = dynamic_cast (&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 automatable = _plugins[0]->automatable(); + + for (set::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::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 (&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& 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::iterator o; + vector::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 +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" + +#include + +/* + 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 IO::GrabPeakPower; +sigc::signal IO::ConnectingLegal; +sigc::signal IO::PortsLegal; +sigc::signal IO::PannersLegal; +sigc::signal IO::MoreOutputs; +sigc::signal 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::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::iterator i = _outputs.begin(); i != _outputs.end(); ++i) { + (*i)->silence (nframes, offset); + } +} + +void +IO::apply_declick (vector& 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& 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::iterator out; + vector::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& 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::iterator out; + vector::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& 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& 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::iterator o; + vector 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& 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::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& 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::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::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::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::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::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::iterator i = _inputs.begin(); i != _inputs.end(); ++i) { + _session.engine().disconnect (*i); + } + + for (vector::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::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::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::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 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 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& 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& 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::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::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::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::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::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(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::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::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 + +#include +#include +#include +#include + +#include +#include + +#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 +#include + +#include +#include // so libraptor doesn't complain +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "i18n.h" +#include + +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 datum; + + datum.first = i; + datum.second = shadow_data[i]; + + state.parameters.insert (datum); + } + } +} + +void +LadspaPlugin::restore_state (PluginState& state) +{ + for (map::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 +LadspaPlugin::automatable () const +{ + set 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& 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 +#include +#include /* for sprintf */ +#include +#include +#include +#include + +#include +#include + +#include + +#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::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 all_locations; + + for (StateMap::iterator siter = states.begin(); siter != states.end(); ++siter) { + + State* lstate = dynamic_cast (*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 (*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 (*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 (*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); + + 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 (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 +#include +#include +#include + +#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 + +float +veclib_compute_peak (ARDOUR::Sample *buf, jack_nframes_t nsamples, float current) +{ + vDSP_maxv(buf, 1, ¤t, 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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::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 +#include + +#include +#include +#include +#include + +#include "i18n.h" + +using namespace ARDOUR; + +sigc::signal NamedSelection::NamedSelectionCreated; + +NamedSelection::NamedSelection (string n, list& l) + : name (n) +{ + playlists = l; + for (list::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::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::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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 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::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::iterator i; + for (i = begin(); i != end() && which; ++i, --which); + + if (i != end()) { + delete *i; + erase (i); + } +} + +void +Panner::clear () +{ + for (vector::iterator i = begin(); i != end(); ++i) { + delete *i; + } + + vector::clear (); +} + +void +Panner::set_automation_style (AutoStyle style) +{ + for (vector::iterator i = begin(); i != end(); ++i) { + (*i)->set_automation_style (style); + } + _session.set_dirty (); +} + +void +Panner::set_automation_state (AutoState state) +{ + for (vector::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::iterator i = begin(); i != end(); ++i) { + (*i)->transport_stopped (frame); + } +} + +void +Panner::snapshot (jack_nframes_t now) +{ + for (vector::iterator i = begin(); i != end(); ++i) { + (*i)->snapshot (now); + } +} + +void +Panner::clear_automation () +{ + for (vector::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::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::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::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::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::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::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::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::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::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::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::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::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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +//using namespace sigc; + +sigc::signal 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::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 (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 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::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(®ion), 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& ranges, bool result_is_hidden) +{ + Playlist* ret; + Playlist* pl; + jack_nframes_t start; + + if (ranges.empty()) { + return 0; + } + + start = ranges.front().start; + + + for (list::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& 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& 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 (®ion, 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(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 (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 LayerInfo; + list layerinfo; + layer_t dest; + + { + RegionLock rlock (const_cast (this)); + + for (i = regions.begin(); i != regions.end(); ++i) { + + if (®ion == *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::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::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 (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 (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 + +#include +#include + +#include +#include +#include + +#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(®ion)) != 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(®ion)) != 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 (&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 +#include + +#include +#include // so libraptor doesn't complain +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "i18n.h" +#include + +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::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::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::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::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 +Plugin::get_presets() +{ + list 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 +#include +#include +#include + +#ifdef VST_SUPPORT +#include +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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::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 *plugin_objects; + vector::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 *presets; + vector::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* rdf_files; + vector::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::iterator i; + list* 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::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 *plugin_objects; + vector::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 (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 +#include +#include +#include +#include +#include +#include +#include +#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 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 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 +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +//using namespace sigc; + +const string Redirect::state_node_name = "Redirect"; +sigc::signal 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(&other)) != 0) { + return new Send (*send); + } else if ((port_insert = dynamic_cast(&other)) != 0) { + return new PortInsert (*port_insert); + } else if ((plugin_insert = dynamic_cast(&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 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::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::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::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& s) const +{ + LockMonitor lm (_automation_lock, __LINE__, __FILE__); + map::const_iterator li; + + for (li = parameter_automation.begin(); li != parameter_automation.end(); ++li) { + s.insert ((*li).first); + } +} + +void +Redirect::what_has_visible_automation (set& s) const +{ + LockMonitor lm (_automation_lock, __LINE__, __FILE__); + set::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::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::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 (&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 +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#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 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 + +#include + +#include +#include +#include +#include +#include + +#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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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& 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::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& 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(redirect)) != 0) { + pi->set_count (1); + + if (pi->input_streams() == 0) { + /* instrument plugin */ + _have_internal_generator = true; + } + + } else if ((porti = dynamic_cast(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(*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 (*i)) != 0) { + send->disconnect_inputs (this); + send->disconnect_outputs (this); + } else if ((port_insert = dynamic_cast (*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(*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 > 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(*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(insert)) != 0) { + pi->set_count (1); + } + + } else if (dynamic_cast (*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 (*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& iclist) +{ + list::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& iclist, int32_t required_inputs, uint32_t* err_streams) +{ + list::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& ports) +{ + LockMonitor lm (control_outs_lock, __LINE__, __FILE__); + vector::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(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::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(*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 (*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 + +#include + +#include + +#include + +#include +#include +#include + +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::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::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::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 + +#include + +#include +#include +#include + +#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& 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 +#include +#include +#include +#include +#include /* sprintf(3) ... grrr */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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::iterator i = _passthru_buffers.begin(); i != _passthru_buffers.end(); ++i) { + free(*i); + } + + for (vector::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::iterator i = mix_groups.begin(); i != mix_groups.end(); ) { + list::iterator tmp; + + tmp = i; + ++tmp; + + delete *i; + + i = tmp; + } + +#ifdef TRACK_DESTRUCTION + cerr << "delete edit groups\n"; +#endif /* TRACK_DESTRUCTION */ + for (list::iterator i = edit_groups.begin(); i != edit_groups.end(); ) { + list::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::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::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::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::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 existing = r1->fed_by; + + /* for each route that feeds r1, recurse, marking it as feeding + rbase as well. + */ + + for (set::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(*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 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(*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 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 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(&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(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(*i) == 0) { + continue; + } + + } else { + + /* don't mess with tracks */ + + if (dynamic_cast(*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(*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(*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(*i)) { + if ((*i)->soloed()) { + (*i)->set_solo_mute (!mute); + } else { + (*i)->set_solo_mute (mute); + } + } + + } else { + + /* only alter bus solo mute */ + + if (!dynamic_cast(*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 (region)) != 0) { + + AudioRegionList::iterator x; + + for (x = audio_regions.begin(); x != audio_regions.end(); ++x) { + + oar = dynamic_cast (x->second); + + if (ar->region_list_equivalent (*oar)) { + break; + } + } + + if (x == audio_regions.end()) { + + pair entry; + + entry.first = region->id(); + entry.second = ar; + + pair 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 (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& result) +{ + for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) { + + AudioPlaylist* pl; + + if ((pl = dynamic_cast(*i)) == 0) { + continue; + } + + pl->get_region_list_equivalent_regions (region, result); + } +} + +int +Session::destroy_region (Region* region) +{ + AudioRegion* aregion; + + if ((aregion = dynamic_cast (region)) == 0) { + return 0; + } + + if (aregion->playlist()) { + aregion->playlist()->destroy_region (region); + } + + vector srcs; + + for (uint32_t n = 0; n < aregion->n_channels(); ++n) { + srcs.push_back (&aregion->source (n)); + } + + for (vector::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 regions) +{ + for (list::iterator i = regions.begin(); i != regions.end(); ++i) { + destroy_region (*i); + } + return 0; +} + +int +Session::remove_last_capture () +{ + list r; + + for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) { + list& 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 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::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* possible_audiofiles = scanner (dir, "\\.wav$", false, true); + + for (vector::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(*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 (redirect)) != 0) { + if ((port_insert = dynamic_cast (insert)) != 0) { + _port_inserts.insert (_port_inserts.begin(), port_insert); + } else if ((plugin_insert = dynamic_cast (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 (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 (redirect)) != 0) { + if ((port_insert = dynamic_cast (insert)) != 0) { + _port_inserts.remove (port_insert); + } else if ((plugin_insert = dynamic_cast (insert)) != 0) { + _plugin_inserts.remove (plugin_insert); + } else { + fatal << _("programming error: unknown type of Insert deleted!") << endmsg; + /*NOTREACHED*/ + } + } else if ((send = dynamic_cast (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(*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& 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 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::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::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::iterator src=srcs.begin(); src != srcs.end(); ++src) { + dynamic_cast((*src))->update_header (position, *xnow, now); + } + + /* build peakfile for new source */ + + for (vector::iterator src=srcs.begin(); src != srcs.end(); ++src) { + dynamic_cast(*src)->build_peaks (); + } + + ret = 0; + } + + out: + if (ret) { + for (vector::iterator src=srcs.begin(); src != srcs.end(); ++src) { + dynamic_cast(*src)->mark_for_remove (); + delete *src; + } + } + + for (vector::iterator i = buffers.begin(); i != buffers.end(); ++i) { + free(*i); + } + + atomic_set (&processing_prohibited, 0); + + itt.done = true; + + return ret; +} + +vector& +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 (*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 (*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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include + +#include +#include +#include +#include + +#include + +#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 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::iterator i = clicks.begin(); i != clicks.end(); ) { + + jack_nframes_t copy; + jack_nframes_t internal_offset; + Click *clk; + list::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 (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 (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 +#include + +#include + +#include +#include + +#include +#include +#include + +#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(ev->ptr)); + break; + + case Event::SetDiskstreamSpeed: + set_diskstream_speed (static_cast (ev->ptr), ev->speed); + break; + + case Event::SetSlaveSource: + set_slave_source (ev->slave, ev->target_frame); + break; + + case Event::Audition: + set_audition (static_cast (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 +#undef _ISOC99_SOURCE +#undef _ISOC9X_SOURCE +#undef __USE_SVID +#define __USE_SVID 1 +#include +#undef __USE_SVID + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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& mapped_ports ((*mi).second); + + for (vector::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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 (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::iterator i = feedback_functions.begin(); i != feedback_functions.end(); ) { + + list::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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 (_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 (_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 (_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(*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 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 +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#include + +#include /* snprintf(3) ... grrr */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_VFS_H +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include + +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::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* 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::iterator i = session_dirs.begin(); + vector::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 ((*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::iterator i = edit_groups.begin(); i != edit_groups.end(); ++i) { + child->add_child_nocopy ((*i)->get_state()); + } + + child = node->add_child ("MixGroups"); + for (list::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::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::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 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 split_path; + + split (path, split_path, ':'); + path = ""; + + for (vector::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 * +Session::possible_states (string path) +{ + PathScanner scanner; + vector* 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 * +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::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::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 &template_names) +{ + vector *templates; + PathScanner scanner; + string path; + + path = template_path (); + + templates = scanner (path, template_filter, 0, false, true); + + vector::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& 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& result, bool exclude_this_snapshot) +{ + PathScanner scanner; + vector* 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::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 dead_sources; + vector playlists_tbd; + PathScanner scanner; + string sound_path; + vector::iterator i; + vector::iterator nexti; + vector* soundfiles; + vector unused; + set 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::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::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 ((*i).second)) != 0) { + all_sources.insert (fs->path()); + } else if ((sfs = dynamic_cast ((*i).second)) != 0) { + all_sources.insert (sfs->path()); + } + } + + for (vector::iterator x = soundfiles->begin(); x != soundfiles->end(); ++x) { + + used = false; + spath = **x; + + for (set::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::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::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 +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#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 + + // 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 +#include + +#include + +#include + +#include +#include +#include +#include + +#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 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(*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 +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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(sigc::bind (mem_fun (*loc, &Location::set_end), loc->end()))); + add_redo (sigc::retype_return(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& 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::size_type sz = current_audio_range.size(); + + if (sz > 1) { + + list::iterator i = current_audio_range.begin(); + list::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 +#include + +#include +#include + +#include +#include + +#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 (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 such that older versions + // will always return true. + return 1; + + case audioMasterWantMidi: + SHOW_CALLBACK ("amc: audioMasterWantMidi\n"); + // is a filter which is currently ignored + return 0; + + case audioMasterGetTime: + SHOW_CALLBACK ("amc: audioMasterGetTime\n"); + // returns const VstTimeInfo* (or 0 if not supported) + // 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 + return 0; + + case audioMasterSetTime: + SHOW_CALLBACK ("amc: audioMasterSetTime\n"); + // VstTimenfo* in , filter in , not supported + + case audioMasterTempoAt: + SHOW_CALLBACK ("amc: audioMasterTempoAt\n"); + // returns tempo (in bpm * 10000) at sample frame location passed in + 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 (-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 (-1: first to come), returns cEffect* + return 0; + + case audioMasterGetNextPlug: + SHOW_CALLBACK ("amc: audioMasterGetNextPlug\n"); + // output pin in (-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 + return 0; + + case audioMasterGetSpeakerArrangement: + SHOW_CALLBACK ("amc: audioMasterGetSpeakerArrangement\n"); + // (long)input in , output in + return 0; + + case audioMasterGetVendorString: + SHOW_CALLBACK ("amc: audioMasterGetVendorString\n"); + // fills 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 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 , 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 + 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 + return 0; + + case audioMasterEndEdit: + SHOW_CALLBACK ("amc: audioMasterEndEdit\n"); + // end of automation session (when mouse up), parameter index in + return 0; + + case audioMasterOpenFileSelector: + SHOW_CALLBACK ("amc: audioMasterOpenFileSelector\n"); + // open a fileselector window with VstFileSelect* in + 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 + +#include + +#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 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::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 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::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 +#include +#include +#include + +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "i18n.h" + +using std::min; +using std::max; + +using namespace ARDOUR; +using namespace PBD; + +sigc::signal Source::SourceCreated; +pthread_t Source::peak_thread; +bool Source::have_peak_thread = false; +vector 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 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 + < _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 built; + int status = -1; + bool pr_signal = false; + list 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::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::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 +#include + +#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 +#include + +#include +#include +#include + +#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 +#include + +#include + +#include + +#include +#include +#include +#include + +#include "i18n.h" +#include + +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(§ion) != 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 (*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 (*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(*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(*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 (*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 (*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(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*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(*i)) != 0) { + m.set_tempo (*tempo); + } else if ((meter = dynamic_cast(*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(*i)) != 0) { + m.set_tempo (*tempo); + } else if ((meter = dynamic_cast(*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(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*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(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*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(*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(*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 (this)), &StateManager::use_state), _current_state_id); +} + +Change +TempoMap::restore_state (StateManager::State& state) +{ + LockMonitor lm (lock, __LINE__, __FILE__); + + TempoMapState* tmstate = dynamic_cast (&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(*i)) != 0) { + state->metrics->push_back (new TempoSection (*ts)); + } else if ((ms = dynamic_cast(*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 /* for sprintf */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include +#include + +#include +#include // so libraptor doesn't complain +#include +#include +#include // for memmove +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +#include "i18n.h" +#include + +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", ¶m); + 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 +VSTPlugin::automatable () const +{ + set ret; + + for (uint32_t i = 0; i < parameter_count(); ++i){ + ret.insert (ret.end(), i); + } + + return ret; +} + +int +VSTPlugin::connect_and_run (vector& 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); +} -- cgit v1.2.3