diff options
92 files changed, 4424 insertions, 666 deletions
diff --git a/SConstruct b/SConstruct index 575c530300..f2a2d069b5 100644 --- a/SConstruct +++ b/SConstruct @@ -33,7 +33,7 @@ opts.AddOptions( BoolOption('NATIVE_OSX_KEYS', 'Build key bindings file that matches OS X conventions', 0), BoolOption('DEBUG', 'Set to build with debugging information and no optimizations', 0), PathOption('DESTDIR', 'Set the intermediate install "prefix"', '/'), - EnumOption('DIST_TARGET', 'Build target for cross compiling packagers', 'auto', allowed_values=('auto', 'i386', 'i686', 'x86_64', 'powerpc', 'tiger', 'panther', 'none' ), ignorecase=2), + EnumOption('DIST_TARGET', 'Build target for cross compiling packagers', 'auto', allowed_values=('auto', 'i386', 'i686', 'x86_64', 'powerpc', 'tiger', 'panther', 'leopard', 'none' ), ignorecase=2), BoolOption('DMALLOC', 'Compile and link using the dmalloc library', 0), BoolOption('EXTRA_WARN', 'Compile with -Wextra, -ansi, and -pedantic. Might break compilation. For pedants', 0), BoolOption('FFT_ANALYSIS', 'Include FFT analysis window', 0), @@ -531,15 +531,19 @@ if env['FFT_ANALYSIS']: if env['LV2']: conf = env.Configure(custom_tests = { 'CheckPKGExists' : CheckPKGExists }) - if conf.CheckPKGExists ('\"slv2 >= 0.4.4\"'): + if conf.CheckPKGExists ('\"slv2 >= 0.6.0\"'): libraries['slv2'] = LibraryInfo() libraries['slv2'].ParseConfig('pkg-config --cflags --libs slv2') else: - print 'Building Ardour with LV2 support requires SLV2 >= 0.4.4' + print 'Building Ardour with LV2 support requires SLV2 >= 0.6.0' print 'WARNING: SLV2 not found, or too old. Ardour will be built without LV2 support.' + print 'Until the 2.3 release, Ardour requires SLV2 out of SVN.' + print 'Testing would be very much appreciated! svn co http://svn.drobilla.net/lad/slv2' env['LV2'] = 0 conf.Finish() - +else: + print 'LV2 support is not enabled. Build with \'scons LV2=1\' to enable.' + libraries['jack'] = LibraryInfo() libraries['jack'].ParseConfig('pkg-config --cflags --libs jack') @@ -621,8 +625,10 @@ if env['DIST_TARGET'] == 'auto': # The [.] matches to the dot after the major version, "." would match any character if re.search ("darwin[0-7][.]", config[config_kernel]) != None: env['DIST_TARGET'] = 'panther' - else: + if re.search ("darwin8[.]", config[config_kernel]) != None: env['DIST_TARGET'] = 'tiger' + else: + env['DIST_TARGET'] = 'leopard' else: if re.search ("x86_64", config[config_cpu]) != None: env['DIST_TARGET'] = 'x86_64' @@ -688,9 +694,9 @@ elif ((re.search ("i[0-9]86", config[config_cpu]) != None) or (re.search ("x86_6 # optimization section if env['FPU_OPTIMIZATION']: - if env['DIST_TARGET'] == 'tiger': - opt_flags.append ("-DBUILD_VECLIB_OPTIMIZATIONS") - debug_flags.append ("-DBUILD_VECLIB_OPTIMIZATIONS") + if env['DIST_TARGET'] == 'tiger' or env['DIST_TARGET'] == 'leopard': + opt_flags.append ("-DBUILD_VECLIB_OPTIMIZATIONS"); + debug_flags.append ("-DBUILD_VECLIB_OPTIMIZATIONS"); libraries['core'].Append(LINKFLAGS= '-framework Accelerate') elif env['DIST_TARGET'] == 'i686' or env['DIST_TARGET'] == 'x86_64': opt_flags.append ("-DBUILD_SSE_OPTIMIZATIONS") @@ -710,6 +716,18 @@ else: env['LIBDIR']='lib' # +# a single way to test if we're on OS X +# + +if env['DIST_TARGET'] in ['panther', 'tiger', 'leopard' ]: + env['IS_OSX'] = 1 + # force tiger or later, to avoid issues on PPC which defaults + # back to 10.1 if we don't tell it otherwise. + env.Append (CCFLAGS="-DMAC_OS_X_VERSION_MIN_REQUIRED=1040") +else: + env['IS_OSX'] = 0 + +# # save off guessed arch element in an env # env.Append(CONFIG_ARCH=config[config_arch]) @@ -767,7 +785,7 @@ if env['LIBLO']: def prep_libcheck(topenv, libinfo): - if topenv['DIST_TARGET'] == 'panther' or topenv['DIST_TARGET'] == 'tiger': + if topenv['IS_OSX']: # # rationale: GTK-Quartz uses jhbuild and installs to /opt/gtk by default. # All libraries needed should be built against this location @@ -780,9 +798,16 @@ def prep_libcheck(topenv, libinfo): prep_libcheck(env, env) +# +# these are part of the Ardour source tree because they are C++ +# + libraries['vamp'] = LibraryInfo (LIBS='vampsdk', LIBPATH='#libs/vamp-sdk', - CPPPATH='#libs/vamp-sdk/vamp') + CPPPATH='#libs/vamp-sdk') +libraries['vamphost'] = LibraryInfo (LIBS='vamphostsdk', + LIBPATH='#libs/vamp-sdk', + CPPPATH='#libs/vamp-sdk') env['RUBBERBAND'] = False @@ -1007,6 +1032,7 @@ if env['SYSLIBS']: 'libs/midi++2', 'libs/ardour', 'libs/vamp-sdk', + 'libs/vamp-plugins/', # these are unconditionally included but have # tests internally to avoid compilation etc # if VST is not set @@ -1081,6 +1107,7 @@ else: 'libs/midi++2', 'libs/ardour', 'libs/vamp-sdk', + 'libs/vamp-plugins/', # these are unconditionally included but have # tests internally to avoid compilation etc # if VST is not set diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index 22ef8bdd66..6adf0ee8ae 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -24,7 +24,7 @@ gtkardour.Append(CPPPATH="#/") # for top level svn_revision.h gtkardour.Append(PACKAGE=domain) gtkardour.Append(POTFILE=domain + '.pot') -if gtkardour['DIST_TARGET'] == 'panther' or gtkardour['DIST_TARGET'] == 'tiger': +if gtkardour['IS_OSX']: gtkardour.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048") gtkardour.Merge ([ @@ -50,6 +50,10 @@ gtkardour.Merge ([ libraries['xml'], libraries['xslt'], libraries['samplerate'], + libraries['vamp'], + libraries['vamphost'], + libraries['fftw3f'], + libraries['fftw3'], libraries['jack'], libraries['cairomm'], libraries['asound'] @@ -77,7 +81,7 @@ if gtkardour['FFT_ANALYSIS']: gtkardour.Append(CCFLAGS='-DFFT_ANALYSIS') if gtkardour['RUBBERBAND']: - gtkardour.Merge ([ libraries['rubberband'], libraries['vamp'], libraries['fftw3f'], libraries['fftw3'] ]) + gtkardour.Merge ([ libraries['rubberband'] ]) else: gtkardour.Merge ([ libraries['soundtouch'] ]) @@ -101,50 +105,44 @@ x11.cc gtkardour_files=Split(""" about.cc actions.cc -add_route_dialog.cc add_midi_cc_track_dialog.cc +add_route_dialog.cc ardour_dialog.cc -ardour_ui.cc ardour_ui2.cc +ardour_ui.cc ardour_ui_dependents.cc ardour_ui_dialogs.cc ardour_ui_ed.cc ardour_ui_mixer.cc ardour_ui_options.cc audio_clock.cc -audio_time_axis.cc audio_region_editor.cc -control_point.cc -automation_line.cc -automation_time_axis.cc -automation_streamview.cc +audio_region_view.cc +audio_streamview.cc +audio_time_axis.cc automation_controller.cc +automation_line.cc automation_region_view.cc -bundle_manager.cc -midi_port_dialog.cc -midi_time_axis.cc -midi_scroomer.cc -midi_streamview.cc +automation_streamview.cc +automation_time_axis.cc axis_view.cc +bundle_manager.cc +canvas-midi-event.cc canvas-simpleline.c -simpleline.cc canvas-simplerect.c -simplerect.cc -lineset.cc canvas-waveview.c -diamond.cc -canvas-midi-event.cc +control_point.cc crossfade_edit.cc crossfade_view.cc curvetest.cc -enums.cc +diamond.cc editing.cc -editor.cc editor_actions.cc editor_audio_import.cc editor_audiotrack.cc editor_canvas.cc editor_canvas_events.cc +editor.cc editor_cursors.cc editor_edit_groups.cc editor_export_audio.cc @@ -165,66 +163,72 @@ editor_selection_list.cc editor_tempodisplay.cc editor_timefx.cc engine_dialog.cc +enums.cc export_dialog.cc -export_session_dialog.cc -export_region_dialog.cc export_range_markers_dialog.cc +export_region_dialog.cc +export_session_dialog.cc gain_meter.cc generic_pluginui.cc ghostregion.cc gtk-custom-hruler.c gtk-custom-ruler.c io_selector.cc -port_matrix.cc keyboard.cc keyeditor.cc latency_gui.cc level_meter.cc +lineset.cc location_ui.cc main.cc marker.cc +midi_port_dialog.cc +midi_region_view.cc +midi_scroomer.cc +midi_streamview.cc +midi_time_axis.cc mixer_strip.cc mixer_ui.cc new_session_dialog.cc option_editor.cc opts.cc - -panner.cc panner2d.cc +panner.cc panner_ui.cc piano_roll_header.cc playlist_selector.cc plugin_selector.cc plugin_ui.cc +port_matrix.cc +processor_box.cc prompter.cc public_editor.cc -processor_box.cc region_gain_line.cc region_selection.cc region_view.cc -audio_region_view.cc -midi_region_view.cc -tape_region_view.cc +rhythm_ferret.cc route_params_ui.cc route_processor_selection.cc +route_time_axis.cc route_ui.cc selection.cc -sfdb_ui.cc send_ui.cc +sfdb_ui.cc +simpleline.cc +simplerect.cc splash.cc streamview.cc -audio_streamview.cc +tape_region_view.cc tempo_dialog.cc +tempo_lines.cc theme_manager.cc time_axis_view.cc time_axis_view_item.cc -route_time_axis.cc time_selection.cc ui_config.cc utils.cc version.cc waveview.cc -tempo_lines.cc """) fft_analysis_files=Split(""" @@ -315,7 +319,7 @@ tt = gtkmmtests.Program(target = 'tt', source = tt_files) my_font_dict = { } -if gtkardour['DIST_TARGET'] == 'panther' or gtkardour['DIST_TARGET'] == 'tiger': +if gtkardour['IS_OSX']: # # OS X font rendering is different even with X11 # diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index d93f108143..b259eb3087 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -4,7 +4,7 @@ cd `dirname "$0"`/.. export ARDOUR_PATH=gtk2_ardour/icons:gtk2_ardour/pixmaps:gtk2_ardour:. export GTK_PATH=libs/clearlooks - +export VAMP_PATH=libs/vamp-plugins:$VAMP_PATH export LD_LIBRARY_PATH=libs/vamp-sdk:libs/surfaces/control_protocol:libs/ardour:libs/midi++2:libs/pbd:libs/rubberband:libs/soundtouch:libs/gtkmm2ext:libs/sigc++2:libs/glibmm2:libs/gtkmm2/atk:libs/gtkmm2/pango:libs/gtkmm2/gdk:libs/gtkmm2/gtk:libs/libgnomecanvasmm:libs/libsndfile:libs/appleutility:libs/cairomm:$LD_LIBRARY_PATH diff --git a/gtk2_ardour/ardour-sae-de.bindings.in b/gtk2_ardour/ardour-sae-de.bindings.in index 7feaed247b..68f9b13b0b 100644 --- a/gtk2_ardour/ardour-sae-de.bindings.in +++ b/gtk2_ardour/ardour-sae-de.bindings.in @@ -335,6 +335,8 @@ (gtk_accel_path "<Actions>/Editor/goto-mark-9" "KP_9") (gtk_accel_path "<Actions>/Transport/ToggleClick" "5") (gtk_accel_path "<Actions>/Transport/ToggleAutoReturn" "4") +(gtk_accel_path "<Actions>/Editor/set-tempo-from-region" "9") +(gtk_accel_path "<Actions>/Editor/set-tempo-from-edit-range" "0") (gtk_accel_path "<Actions>/Transport/focus-on-clock" "KP_Divide") (gtk_accel_path "<Actions>/Editor/set-loop-from-edit-range" "bracketright") (gtk_accel_path "<Actions>/Editor/set-punch-from-edit-range" "bracketleft") diff --git a/gtk2_ardour/ardour-sae.menus b/gtk2_ardour/ardour-sae.menus index e3efc7a849..d9c4f1798f 100644 --- a/gtk2_ardour/ardour-sae.menus +++ b/gtk2_ardour/ardour-sae.menus @@ -133,6 +133,9 @@ <menuitem action='cycle-edit-point'/> <menuitem action='cycle-edit-point-with-marker'/> <menuitem action='toggle-edit-mode'/> + <separator/> + <menuitem action='boost-region-gain'/> + <menuitem action='cut-region-gain'/> </menu> </menu> <menu name='Regions' action='Regions'> @@ -164,6 +167,8 @@ <menuitem action='trim-region-to-punch'/> <separator/> <menuitem action='pitch-shift-region'/> + <menuitem action='set-tempo-from-region'/> + <menuitem action='set-tempo-from-edit-range'/> </menu> <menu name='View' action = 'View'> <menuitem action='ToggleMaximalEditor'/> diff --git a/gtk2_ardour/ardour.bindings.in b/gtk2_ardour/ardour.bindings.in index 1efdb1cdd4..3d488c1ec4 100644 --- a/gtk2_ardour/ardour.bindings.in +++ b/gtk2_ardour/ardour.bindings.in @@ -10,6 +10,7 @@ (gtk_accel_path "<Actions>/Editor/edit-cursor-to-previous-region-sync" "apostrophe") (gtk_accel_path "<Actions>/Editor/edit-cursor-to-next-region-sync" "semicolon") (gtk_accel_path "<Actions>/Editor/cycle-edit-point" "grave") +(gtk_accel_path "<Actions>/Editor/cycle-edit-point-with-marker" "<%PRIMARY%>asciicircum") (gtk_accel_path "<Actions>/Editor/playhead-to-next-region-boundary" "period") (gtk_accel_path "<Actions>/Editor/playhead-to-next-region-sync" "<%PRIMARY%>period") @@ -38,6 +39,9 @@ (gtk_accel_path "<Actions>/Editor/set-punch-from-edit-range" "bracketleft") (gtk_accel_path "<Actions>/Editor/set-punch-from-region" "<%SECONDARY%>bracketleft") +(gtk_accel_path "<Actions>/Editor/boost-region-gain" "asciicircum") +(gtk_accel_path "<Actions>/Editor/cut-region-gain" "ampersand") + ;; letters ;; TOP ROW @@ -50,6 +54,7 @@ (gtk_accel_path "<Actions>/Editor/select-all-before-edit-cursor" "<%PRIMARY%>e") (gtk_accel_path "<Actions>/Editor/show-editor-mixer" "<%TERTIARY%>e") (gtk_accel_path "<Actions>/Common/goto-editor" "<%WINDOW%>e") +(gtk_accel_path "<Actions>/Editor/select-all-after-edit-cursor" "<%TERTIARY%><%PRIMARY%>e") (gtk_accel_path "<Actions>/MouseMode/set-mouse-mode-range" "r") (gtk_accel_path "<Actions>/Editor/redo" "<%PRIMARY%>r") (gtk_accel_path "<Actions>/Transport/Record" "<%TERTIARY%>r") @@ -62,22 +67,19 @@ (gtk_accel_path "<Actions>/Common/ToggleOptionsEditor" "<%WINDOW%>o") (gtk_accel_path "<Actions>/Editor/set-playhead" "p") (gtk_accel_path "<Actions>/Editor/select-all-before-playhead" "<%PRIMARY%>p") +(gtk_accel_path "<Actions>/Editor/select-all-after-playhead" "<%TERTIARY%><%PRIMARY%>p") ;; MIDDLE ROW (gtk_accel_path "<Actions>/Editor/align-regions-sync-relative" "a") -(gtk_accel_path "<Actions>/Editor/align-regions-start-relative" "<%PRIMARY%>a") +(gtk_accel_path "<Actions>/Editor/select-all" "<%PRIMARY%>a") (gtk_accel_path "<Actions>/Editor/align-regions-end" "<%SECONDARY%>a") (gtk_accel_path "<Actions>/Editor/align-regions-sync" "<%TERTIARY%>a") +(gtk_accel_path "<Actions>/Editor/align-regions-start-relative" "<%LEVEL4%>a") (gtk_accel_path "<Actions>/Editor/split-region" "s") (gtk_accel_path "<Actions>/Common/Save" "<%PRIMARY%>s") (gtk_accel_path "<Actions>/Editor/duplicate-region" "d") (gtk_accel_path "<Actions>/Editor/select-all-in-punch-range" "<%PRIMARY%>d") - -(gtk_accel_path "<Actions>/Editor/select-all" "<%PRIMARY%>a") -(gtk_accel_path "<Actions>/Editor/select-all-after-playhead" "<%TERTIARY%><%PRIMARY%>p") -(gtk_accel_path "<Actions>/Editor/select-all-after-edit-cursor" "<%TERTIARY%><%PRIMARY%>e") - (gtk_accel_path "<Actions>/Editor/toggle-follow-playhead" "f") (gtk_accel_path "<Actions>/MouseMode/set-mouse-mode-gain" "g") (gtk_accel_path "<Actions>/Editor/play-selected-regions" "h") @@ -180,6 +182,8 @@ (gtk_accel_path "<Actions>/Editor/cycle-snap-choice" "3") (gtk_accel_path "<Actions>/Transport/ToggleAutoReturn" "4") (gtk_accel_path "<Actions>/Transport/ToggleClick" "5") +(gtk_accel_path "<Actions>/Editor/set-tempo-from-region" "9") +(gtk_accel_path "<Actions>/Editor/set-tempo-from-edit-range" "0") ;; ;; unbound actions diff --git a/gtk2_ardour/ardour.menus b/gtk2_ardour/ardour.menus index 40432d5381..4c5439b68c 100644 --- a/gtk2_ardour/ardour.menus +++ b/gtk2_ardour/ardour.menus @@ -44,6 +44,9 @@ <menuitem action='GotoStart'/> <menuitem action='GotoEnd'/> <separator/> + <menuitem action='tab-to-transient-forwards'/> + <menuitem action='tab-to-transient-backwards'/> + <separator/> <menuitem action='Record'/> <separator/> <menuitem action='TransitionToRoll'/> @@ -161,6 +164,7 @@ <menuitem action='select-prev-route'/> </menu> <menu name='Regions' action='Regions'> + <menuitem action='split-region-at-transients'/> <menuitem action='crop'/> <menuitem action='duplicate-region'/> <menuitem action='multi-duplicate-region'/> @@ -194,6 +198,8 @@ <menuitem action='trim-region-to-punch'/> <separator/> <menuitem action='pitch-shift-region'/> + <menuitem action='set-tempo-from-region'/> + <menuitem action='set-tempo-from-edit-range'/> </menu> <menu name='View' action = 'View'> <menu name='ZoomFocus' action='ZoomFocus'> @@ -302,6 +308,7 @@ <menuitem action='ToggleThemeManager'/> <menuitem action='ToggleBigClock'/> <menuitem action='ToggleBundleManager'/> + <menuitem action='toggle-rhythm-ferret'/> <separator/> </menu> <menu name='Options' action='Options'> diff --git a/gtk2_ardour/ardour3_ui_default.conf b/gtk2_ardour/ardour3_ui_default.conf index bfcb616a93..b64296ae02 100644 --- a/gtk2_ardour/ardour3_ui_default.conf +++ b/gtk2_ardour/ardour3_ui_default.conf @@ -4,11 +4,11 @@ <Option name="ui-rc-file" value="ardour3_ui_dark.rc"/> </UI> <Canvas> - <Option name="waveform" value="000000d6"/> - <Option name="waveform fill" value="0b225a78"/> + <Option name="waveform outline" value="0f0f0fcc"/> + <Option name="waveform fill" value="3d475378"/> <Option name="clipped waveform" value="ff0000e5"/> - <Option name="region base" value="b2bcd3aa"/> - <Option name="selected region base" value="565693a6"/> + <Option name="region base" value="99a7b5aa"/> + <Option name="selected region base" value="b591a8ff"/> <Option name="midi frame base" value="698f9d6d"/> <Option name="audio track base" value="c6d3d868"/> <Option name="audio bus base" value="dbd1ea68"/> diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 90d0e77adc..b563fc4186 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -610,8 +610,6 @@ Please consider the possibilities, and perhaps (re)start JACK.")); win.show_all (); win.set_position (Gtk::WIN_POS_CENTER); - hide_splash (); - /* we just don't care about the result, but we want to block */ win.run (); @@ -2208,6 +2206,7 @@ ARDOUR_UI::end_loading_messages () void ARDOUR_UI::loading_message (const std::string& msg) { + cerr << "say: " << msg << endl; show_splash (); splash->message (msg); flush_pending (); @@ -2254,10 +2253,6 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be new_session_dialog->set_existing_session (existing_session); new_session_dialog->reset_recent(); - /* get this out of the way */ - - hide_splash (); - do { new_session_dialog->set_have_engine (backend_audio_is_running); new_session_dialog->present (); @@ -2622,6 +2617,7 @@ ARDOUR_UI::show_splash () } splash->show (); + splash->present (); splash->queue_draw (); splash->get_window()->process_updates (true); flush_pending (); diff --git a/gtk2_ardour/au_pluginui.h b/gtk2_ardour/au_pluginui.h index e15d48b922..1ca5dca485 100644 --- a/gtk2_ardour/au_pluginui.h +++ b/gtk2_ardour/au_pluginui.h @@ -3,6 +3,7 @@ #include <AppKit/AppKit.h> #include <Carbon/Carbon.h> +#include <AudioUnit/AudioUnitCarbonView.h> #include <AudioUnit/AudioUnit.h> /* fix up stupid apple macros */ diff --git a/gtk2_ardour/audio_region_view.cc b/gtk2_ardour/audio_region_view.cc index 89644a4ab3..efd1bdd503 100644 --- a/gtk2_ardour/audio_region_view.cc +++ b/gtk2_ardour/audio_region_view.cc @@ -796,20 +796,21 @@ AudioRegionView::set_envelope_visible (bool yn) void AudioRegionView::create_waves () { + //cerr << "AudioRegionView::create_waves() called on " << this << endl;//DEBUG RouteTimeAxisView& atv (*(dynamic_cast<RouteTimeAxisView*>(&trackview))); // ick if (!atv.get_diskstream()) { return; } - uint32_t nchans = atv.get_diskstream()->n_channels().n_audio(); + ChanCount nchans = atv.get_diskstream()->n_channels(); /* in tmp_waves, set up null pointers for each channel so the vector is allocated */ - for (uint32_t n = 0; n < nchans; ++n) { + for (uint32_t n = 0; n < nchans.n_audio(); ++n) { tmp_waves.push_back (0); } - for (uint32_t n = 0; n < nchans; ++n) { + for (uint32_t n = 0; n < nchans.n_audio(); ++n) { if (n >= audio_region()->n_channels()) { break; @@ -818,21 +819,16 @@ AudioRegionView::create_waves () wave_caches.push_back (WaveView::create_cache ()); if (wait_for_data) { - if (audio_region()->audio_source(n)->peaks_ready (bind (mem_fun(*this, &AudioRegionView::peaks_ready_handler), n), data_ready_connection)) { - create_one_wave (n, true); - } else { - // we'll get a PeaksReady signal from the source in the future - // and will call create_one_wave(n) then. - } - } else { create_one_wave (n, true); } + } } void AudioRegionView::create_one_wave (uint32_t which, bool direct) { + //cerr << "AudioRegionView::create_one_wave() called which: " << which << " this: " << this << endl;//DEBUG RouteTimeAxisView& atv (*(dynamic_cast<RouteTimeAxisView*>(&trackview))); // ick uint32_t nchans = atv.get_diskstream()->n_channels().n_audio(); uint32_t n; @@ -862,6 +858,7 @@ AudioRegionView::create_one_wave (uint32_t which, bool direct) wave->property_samples_per_unit() = samples_per_unit; wave->property_amplitude_above_axis() = _amplitude_above_axis; wave->property_wave_color() = _region->muted() ? UINT_RGBA_CHANGE_A(ARDOUR_UI::config()->canvasvar_WaveForm.get(), MUTED_ALPHA) : ARDOUR_UI::config()->canvasvar_WaveForm.get(); + wave->property_fill_color() = ARDOUR_UI::config()->canvasvar_WaveFormFill.get(); wave->property_clip_color() = ARDOUR_UI::config()->canvasvar_WaveFormClip.get(); wave->property_zero_color() = ARDOUR_UI::config()->canvasvar_ZeroLine.get(); wave->property_region_start() = _region->start(); @@ -916,7 +913,8 @@ AudioRegionView::create_one_wave (uint32_t which, bool direct) void AudioRegionView::peaks_ready_handler (uint32_t which) { - Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &AudioRegionView::create_one_wave), which, false)); + //Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &AudioRegionView::create_one_wave), which, false)); + cerr << "AudioRegionView::peaks_ready_handler() called on " << which << " this: " << this << endl; } void @@ -1110,6 +1108,7 @@ AudioRegionView::add_ghost (AutomationTimeAxisView& atv) wave->property_samples_per_unit() = samples_per_unit; wave->property_amplitude_above_axis() = _amplitude_above_axis; wave->property_wave_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWave.get(); + wave->property_fill_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWave.get(); wave->property_clip_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWaveClip.get(); wave->property_zero_color() = ARDOUR_UI::config()->canvasvar_GhostTrackZeroLine.get(); wave->property_region_start() = _region->start(); diff --git a/gtk2_ardour/canvas-waveview.c b/gtk2_ardour/canvas-waveview.c index 05d5c84b32..2f721dc8d6 100644 --- a/gtk2_ardour/canvas-waveview.c +++ b/gtk2_ardour/canvas-waveview.c @@ -368,10 +368,10 @@ gnome_canvas_waveview_init (GnomeCanvasWaveView *waveview) waveview->screen_width = gdk_screen_width (); waveview->reload_cache_in_render = FALSE; - waveview->wave_color = RGBA_TO_UINT(44,35,126,255); - waveview->clip_color = RGBA_TO_UINT(44,0,0,100); - waveview->zero_color = RGBA_TO_UINT(44,0,128,100); - waveview->fill_color = RGBA_TO_UINT(44,35,126,128); + waveview->wave_color = 0; + waveview->clip_color = 0; + waveview->zero_color = 0; + waveview->fill_color = 0; } static void diff --git a/gtk2_ardour/canvas_vars.h b/gtk2_ardour/canvas_vars.h index f79515cd28..354618e176 100644 --- a/gtk2_ardour/canvas_vars.h +++ b/gtk2_ardour/canvas_vars.h @@ -1,4 +1,4 @@ -CANVAS_VARIABLE(canvasvar_WaveForm, "waveform") +CANVAS_VARIABLE(canvasvar_WaveForm, "waveform outline") CANVAS_VARIABLE(canvasvar_WaveFormFill, "waveform fill") CANVAS_VARIABLE(canvasvar_WaveFormClip, "clipped waveform") CANVAS_VARIABLE(canvasvar_FrameBase, "region base") diff --git a/gtk2_ardour/cocoacarbon.mm b/gtk2_ardour/cocoacarbon.mm index b60352eb47..6317cec6c6 100644 --- a/gtk2_ardour/cocoacarbon.mm +++ b/gtk2_ardour/cocoacarbon.mm @@ -18,6 +18,8 @@ #include <Carbon/Carbon.h> #undef check // stupid, stupid carbon +#undef YES // stupid, stupid gtkmm and/or NSObjC +#undef NO // ditto #include "ardour_ui.h" #include "actions.h" @@ -117,6 +119,6 @@ ARDOUR_UI::platform_setup () /* if invoked from the command line, make sure we're visible */ - [NSApp activateIgnoringOtherApps:YES]; + [NSApp activateIgnoringOtherApps:1]; } } diff --git a/gtk2_ardour/crossfade_edit.cc b/gtk2_ardour/crossfade_edit.cc index b5aebdfb8d..03d03c3d10 100644 --- a/gtk2_ardour/crossfade_edit.cc +++ b/gtk2_ardour/crossfade_edit.cc @@ -982,10 +982,12 @@ CrossfadeEditor::curve_select_clicked (WhichFade wf) for (vector<ArdourCanvas::WaveView*>::iterator i = fade[In].waves.begin(); i != fade[In].waves.end(); ++i) { (*i)->property_wave_color() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorWave.get(); + (*i)->property_fill_color() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorWave.get(); } for (vector<ArdourCanvas::WaveView*>::iterator i = fade[Out].waves.begin(); i != fade[Out].waves.end(); ++i) { (*i)->property_wave_color() = ARDOUR_UI::config()->canvasvar_CrossfadeEditorWave.get(); + (*i)->property_fill_color() = ARDOUR_UI::config()->canvasvar_CrossfadeEditorWave.get(); } fade[In].line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorLine.get(); @@ -1005,10 +1007,12 @@ CrossfadeEditor::curve_select_clicked (WhichFade wf) for (vector<ArdourCanvas::WaveView*>::iterator i = fade[In].waves.begin(); i != fade[In].waves.end(); ++i) { (*i)->property_wave_color() = ARDOUR_UI::config()->canvasvar_CrossfadeEditorWave.get(); + (*i)->property_fill_color() = ARDOUR_UI::config()->canvasvar_CrossfadeEditorWave.get(); } for (vector<ArdourCanvas::WaveView*>::iterator i = fade[Out].waves.begin(); i != fade[Out].waves.end(); ++i) { (*i)->property_wave_color() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorWave.get(); + (*i)->property_fill_color() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorWave.get(); } fade[Out].line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedCrossfadeEditorLine.get(); @@ -1084,6 +1088,7 @@ CrossfadeEditor::make_waves (boost::shared_ptr<AudioRegion> region, WhichFade wh waveview->property_samples_per_unit() = spu; waveview->property_amplitude_above_axis() = 2.0; waveview->property_wave_color() = color; + waveview->property_fill_color() = color; if (which==In) waveview->property_region_start() = region->start(); diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 5fe95f013b..5c93877e07 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -83,6 +83,7 @@ #include "sfdb_ui.h" #include "gui_thread.h" #include "simpleline.h" +#include "rhythm_ferret.h" #ifdef FFT_ANALYSIS #include "analysis_window.h" @@ -341,6 +342,7 @@ Editor::Editor () _dragging_hscrollbar = false; select_new_marker = false; zoomed_to_region = false; + rhythm_ferret = 0; scrubbing_direction = 0; @@ -1190,6 +1192,10 @@ Editor::connect_to_session (Session *t) _playlist_selector->set_session (session); nudge_clock.set_session (session); + if (rhythm_ferret) { + rhythm_ferret->set_session (session); + } + #ifdef FFT_ANALYSIS if (analysis_window != 0) analysis_window->set_session (session); @@ -3297,26 +3303,32 @@ Editor::duplicate_dialog (bool with_dialog) if (with_dialog) { - ArdourDialog win ("duplicate dialog"); - Label label (_("Duplicate how many times?")); + ArdourDialog win ("Duplication Dialog"); + Label label (_("Number of Duplications:")); Adjustment adjustment (1.0, 1.0, 1000000.0, 1.0, 5.0); - SpinButton spinner (adjustment); + SpinButton spinner (adjustment, 0.0, 1); + HBox hbox; win.get_vbox()->set_spacing (12); - win.get_vbox()->pack_start (label); + win.get_vbox()->pack_start (hbox); + hbox.set_border_width (6); + hbox.pack_start (label, PACK_EXPAND_PADDING, 12); /* dialogs have ::add_action_widget() but that puts the spinner in the wrong place, visually. so do this by hand. */ - win.get_vbox()->pack_start (spinner); + hbox.pack_start (spinner, PACK_EXPAND_PADDING, 12); spinner.signal_activate().connect (sigc::bind (mem_fun (win, &ArdourDialog::response), RESPONSE_ACCEPT)); - + spinner.grab_focus(); + + hbox.show (); label.show (); spinner.show (); - win.add_button (Stock::OK, RESPONSE_ACCEPT); win.add_button (Stock::CANCEL, RESPONSE_CANCEL); + win.add_button (_("Duplicate"), RESPONSE_ACCEPT); + win.set_default_response (RESPONSE_ACCEPT); win.set_position (WIN_POS_MOUSE); @@ -4587,3 +4599,15 @@ Editor::get_regions_corresponding_to (boost::shared_ptr<Region> region, vector<R } } } + +void +Editor::show_rhythm_ferret () +{ + if (rhythm_ferret == 0) { + rhythm_ferret = new RhythmFerret(*this); + } + + rhythm_ferret->set_session (session); + rhythm_ferret->show (); + rhythm_ferret->present (); +} diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index c8e9589c02..56b0077b99 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -107,6 +107,7 @@ class StreamView; class AudioStreamView; class ControlPoint; class SoundFileOmega; +class RhythmFerret; #ifdef FFT_ANALYSIS class AnalysisWindow; #endif @@ -371,6 +372,8 @@ class Editor : public PublicEditor void toggle_meter_updating(); + void show_rhythm_ferret(); + protected: void map_transport_state (); void map_position_change (nframes_t); @@ -1024,6 +1027,8 @@ class Editor : public PublicEditor void split_region (); void split_region_at (nframes_t); void split_regions_at (nframes_t, RegionSelection&); + void split_region_at_transients (); + void split_region_at_points (boost::shared_ptr<ARDOUR::Region>, std::vector<nframes64_t>&); void crop_region_to_selection (); void crop_region_to (nframes_t start, nframes_t end); void set_sync_point (nframes64_t, const RegionSelection&); @@ -1051,6 +1056,13 @@ class Editor : public PublicEditor void adjust_region_scale_amplitude (bool up); void quantize_region (); + void tab_to_transient (bool forward); + + void use_region_as_bar (); + void use_range_as_bar (); + + void define_one_bar (nframes64_t start, nframes64_t end); + void audition_region_from_region_list (); void hide_region_from_region_list (); void remove_region_from_region_list (); @@ -2164,6 +2176,8 @@ class Editor : public PublicEditor void select_next_route (); void select_prev_route (); + + RhythmFerret* rhythm_ferret; }; #endif /* __ardour_editor_h__ */ diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index 2009b36f93..51752fb6bb 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -356,13 +356,30 @@ Editor::register_actions () ActionManager::session_sensitive_actions.push_back (act); act = ActionManager::register_action (editor_actions, "normalize-region", _("Normalize Region"), mem_fun(*this, &Editor::normalize_region)); ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "boost-region-gain", _("Boost Region Gain"), bind (mem_fun(*this, &Editor::adjust_region_scale_amplitude), true)); ActionManager::session_sensitive_actions.push_back (act); act = ActionManager::register_action (editor_actions, "cut-region-gain", _("Cut Region Gain"), bind (mem_fun(*this, &Editor::adjust_region_scale_amplitude), false)); ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "quantize-region", _("Quantize Region"), mem_fun(*this, &Editor::quantize_region)); ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "set-tempo-from-region", _("Set Tempo from Region=Bar"), mem_fun(*this, &Editor::use_region_as_bar)); + ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "set-tempo-from-edit-range", _("Set Tempo from Edit Range=Bar"), mem_fun(*this, &Editor::use_range_as_bar)); + ActionManager::session_sensitive_actions.push_back (act); + + act = ActionManager::register_action (editor_actions, "split-region-at-transients", _("Split Regions At Percussion Onsets"), mem_fun(*this, &Editor::split_region_at_transients)); + ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "toggle-rhythm-ferret", _("Rhythm Ferret"), mem_fun(*this, &Editor::show_rhythm_ferret)); + ActionManager::session_sensitive_actions.push_back (act); + + act = ActionManager::register_action (editor_actions, "tab-to-transient-forwards", _("Move Forward to Transient"), bind (mem_fun(*this, &Editor::tab_to_transient), true)); + ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "tab-to-transient-backwards", _("Move Forward to Transient"), bind (mem_fun(*this, &Editor::tab_to_transient), false)); + ActionManager::session_sensitive_actions.push_back (act); + act = ActionManager::register_action (editor_actions, "crop", _("Crop"), mem_fun(*this, &Editor::crop_region_to_selection)); ActionManager::session_sensitive_actions.push_back (act); act = ActionManager::register_action (editor_actions, "insert-chunk", _("Insert Chunk"), bind (mem_fun(*this, &Editor::paste_named_selection), 1.0f)); diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc index 98451c6b0a..aeff20f0bc 100644 --- a/gtk2_ardour/editor_canvas.cc +++ b/gtk2_ardour/editor_canvas.cc @@ -453,6 +453,8 @@ Editor::track_canvas_drag_data_received (const RefPtr<Gdk::DragContext>& context const SelectionData& data, guint info, guint time) { + cerr << "drop on canvas, target = " << data.get_target() << endl; + if (data.get_target() == "regions") { drop_regions (context, x, y, data, info, time); } else { diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 33a0e6920c..1f833e8337 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -45,10 +45,12 @@ #include <ardour/location.h> #include <ardour/named_selection.h> #include <ardour/audio_track.h> +#include <ardour/audiofilesource.h> #include <ardour/audioplaylist.h> #include <ardour/region_factory.h> #include <ardour/playlist_factory.h> #include <ardour/reverse.h> +#include <ardour/transient_detector.h> #include <ardour/dB.h> #include <ardour/quantize.h> @@ -3042,9 +3044,9 @@ Editor::align_selection_relative (RegionPoint point, nframes_t position, const R return; } - nframes_t distance; + nframes_t distance = 0; nframes_t pos = 0; - int dir; + int dir = 0; list<RegionView*> sorted; rs.by_position (sorted); @@ -4148,7 +4150,7 @@ Editor::adjust_region_scale_amplitude (bool up) return; } - ExclusiveRegionSelection (*this, entered_regionview); + ExclusiveRegionSelection esr (*this, entered_regionview); if (selection->regions.empty()) { return; @@ -4164,10 +4166,6 @@ Editor::adjust_region_scale_amplitude (bool up) double fraction = gain_to_slider_position (arv->audio_region()->scale_amplitude ()); - cerr << "slider pos for " << arv->audio_region()->scale_amplitude () - << " = " << fraction - << endl; - if (up) { fraction += 0.05; fraction = min (fraction, 1.0); @@ -4180,16 +4178,14 @@ Editor::adjust_region_scale_amplitude (bool up) continue; } - if (up && fraction >= 1.0) { - continue; - } - fraction = slider_position_to_gain (fraction); fraction = coefficient_to_dB (fraction); fraction = dB_to_coefficient (fraction); - - cerr << "set scale amp for " << arv->audio_region()->name() << " to " << fraction << endl; + if (up && fraction >= 2.0) { + continue; + } + arv->audio_region()->set_scale_amplitude (fraction); session->add_command (new MementoCommand<Region>(*(arv->region().get()), &before, &arv->region()->get_state())); } @@ -4481,7 +4477,7 @@ Editor::toggle_fade_active (bool in) const char* cmd = (in ? _("toggle fade in active") : _("toggle fade out active")); bool have_switch = false; - bool yn; + bool yn = false; begin_reversible_command (cmd); @@ -4986,3 +4982,233 @@ Editor::pitch_shift_regions () pitch_shift (selection->regions, 1.2); } +void +Editor::use_region_as_bar () +{ + if (!session) { + return; + } + + ExclusiveRegionSelection esr (*this, entered_regionview); + + if (selection->regions.empty()) { + return; + } + + RegionView* rv = selection->regions.front(); + + define_one_bar (rv->region()->position(), rv->region()->last_frame() + 1); +} + +void +Editor::use_range_as_bar () +{ + nframes64_t start, end; + if (get_edit_op_range (start, end)) { + define_one_bar (start, end); + } +} + +void +Editor::define_one_bar (nframes64_t start, nframes64_t end) +{ + nframes64_t length = end - start; + + const Meter& m (session->tempo_map().meter_at (start)); + + /* region length = 1 bar */ + + /* 1 bar = how many beats per bar */ + + double beats_per_bar = m.beats_per_bar(); + + /* now we want frames per beat. + we have frames per bar, and beats per bar, so ... + */ + + double frames_per_beat = length / beats_per_bar; + + /* beats per minute = */ + + double beats_per_minute = (session->frame_rate() * 60.0) / frames_per_beat; + + const TempoSection& t (session->tempo_map().tempo_section_at (start)); + + begin_reversible_command (_("set tempo from region")); + XMLNode& before (session->tempo_map().get_state()); + + if (t.frame() == start) { + session->tempo_map().change_existing_tempo_at (start, beats_per_minute, t.note_type()); + } else { + session->tempo_map().add_tempo (Tempo (beats_per_minute, t.note_type()), start); + } + + XMLNode& after (session->tempo_map().get_state()); + + session->add_command (new MementoCommand<TempoMap>(session->tempo_map(), &before, &after)); + commit_reversible_command (); +} + +void +Editor::split_region_at_transients () +{ + vector<nframes64_t> positions; + + if (!session) { + return; + } + + ExclusiveRegionSelection esr (*this, entered_regionview); + + if (selection->regions.empty()) { + return; + } + + session->begin_reversible_command (_("split regions")); + + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ) { + + RegionSelection::iterator tmp; + + tmp = i; + ++tmp; + + boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> ((*i)->region()); + + if (ar && (ar->get_transients (positions) == 0)) { + split_region_at_points ((*i)->region(), positions); + positions.clear (); + } + + i = tmp; + } + + session->commit_reversible_command (); + +} + +void +Editor::split_region_at_points (boost::shared_ptr<Region> r, vector<nframes64_t>& positions) +{ + boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r); + + if (!ar) { + return; + } + + boost::shared_ptr<Playlist> pl = ar->playlist(); + + if (!pl) { + return; + } + + if (positions.empty()) { + return; + } + + vector<nframes64_t>::const_iterator x; + + nframes64_t pos = ar->position(); + + XMLNode& before (pl->get_state()); + + x = positions.begin(); + + while (x != positions.end()) { + if ((*x) > pos) { + break; + } + } + + if (x == positions.end()) { + return; + } + + pl->freeze (); + pl->remove_region (ar); + + do { + + /* file start = original start + how far we from the initial position ? + */ + + nframes64_t file_start = ar->start() + (pos - ar->position()); + + /* length = next position - current position + */ + + nframes64_t len = (*x) - pos; + + string new_name; + + if (session->region_name (new_name, ar->name())) { + continue; + } + + pl->add_region (RegionFactory::create (ar->sources(), file_start, len, new_name), pos); + + pos += len; + + ++x; + + } while (x != positions.end() && (*x) < ar->last_frame()); + + pl->thaw (); + + XMLNode& after (pl->get_state()); + + session->add_command (new MementoCommand<Playlist>(*pl, &before, &after)); +} + +void +Editor::tab_to_transient (bool forward) +{ + + vector<nframes64_t> positions; + + if (!session) { + return; + } + + ExclusiveRegionSelection esr (*this, entered_regionview); + + if (selection->regions.empty()) { + return; + } + + boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (selection->regions.front()->region()); + + if (!ar) { + return; + } + + ar->get_transients (positions); + nframes64_t pos = session->audible_frame (); + + if (forward) { + vector<nframes64_t>::iterator x; + + for (x = positions.begin(); x != positions.end(); ++x) { + if ((*x) > pos) { + break; + } + } + + if (x != positions.end ()) { + session->request_locate (*x); + } + + } else { + vector<nframes64_t>::reverse_iterator x; + + for (x = positions.rbegin(); x != positions.rend(); ++x) { + if ((*x) < pos) { + break; + } + } + + if (x != positions.rend ()) { + session->request_locate (*x); + } + } +} diff --git a/gtk2_ardour/editor_tempodisplay.cc b/gtk2_ardour/editor_tempodisplay.cc index f18392ce38..591c3d12fe 100644 --- a/gtk2_ardour/editor_tempodisplay.cc +++ b/gtk2_ardour/editor_tempodisplay.cc @@ -220,7 +220,8 @@ Editor::mouse_add_new_tempo_event (nframes_t frame) TempoDialog tempo_dialog (map, frame, _("add")); tempo_dialog.set_position (Gtk::WIN_POS_MOUSE); - tempo_dialog.signal_realize().connect (bind (sigc::ptr_fun (set_decoration), &tempo_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH))); + //this causes compiz to display no border. + //tempo_dialog.signal_realize().connect (bind (sigc::ptr_fun (set_decoration), &tempo_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH))); ensure_float (tempo_dialog); @@ -247,7 +248,7 @@ Editor::mouse_add_new_tempo_event (nframes_t frame) session->add_command(new MementoCommand<TempoMap>(map, &before, &after)); commit_reversible_command (); - map.dump (cerr); + //map.dump (cerr); } void @@ -262,7 +263,9 @@ Editor::mouse_add_new_meter_event (nframes_t frame) MeterDialog meter_dialog (map, frame, _("add")); meter_dialog.set_position (Gtk::WIN_POS_MOUSE); - meter_dialog.signal_realize().connect (bind (sigc::ptr_fun (set_decoration), &meter_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH))); + + //this causes compiz to display no border.. + //meter_dialog.signal_realize().connect (bind (sigc::ptr_fun (set_decoration), &meter_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH))); ensure_float (meter_dialog); @@ -278,16 +281,16 @@ Editor::mouse_add_new_meter_event (nframes_t frame) double note_type = meter_dialog.get_note_type (); BBT_Time requested; - + meter_dialog.get_bbt_time (requested); - + begin_reversible_command (_("add meter mark")); XMLNode &before = map.get_state(); map.add_meter (Meter (bpb, note_type), requested); session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state())); commit_reversible_command (); - map.dump (cerr); + //map.dump (cerr); } void diff --git a/gtk2_ardour/engine_dialog.cc b/gtk2_ardour/engine_dialog.cc index f7f8d4a67b..9835901a24 100644 --- a/gtk2_ardour/engine_dialog.cc +++ b/gtk2_ardour/engine_dialog.cc @@ -373,7 +373,7 @@ EngineControl::build_command_line (vector<string>& cmd) str = timeout_combo.get_active_text (); if (str != _("Ignore")) { - double secs; + double secs = 0; uint32_t msecs; atof (str); msecs = (uint32_t) floor (secs * 1000.0); @@ -819,7 +819,7 @@ EngineControl::driver_changed () vector<string>& strings = devices[driver]; - if (strings.empty()) { + if (strings.empty() && driver != "FFADO") { error << string_compose (_("No devices found for driver \"%1\""), driver) << endmsg; return; } @@ -1096,7 +1096,7 @@ EngineControl::set_state (const XMLNode& root) XMLNodeList clist; XMLNodeConstIterator citer; XMLNode* child; - XMLProperty* prop; + XMLProperty* prop = NULL; bool using_dummy = false; int val; @@ -1112,7 +1112,8 @@ EngineControl::set_state (const XMLNode& root) clist = root.children(); for (citer = clist.begin(); citer != clist.end(); ++citer) { - + if ( prop && (prop->value() == "FFADO" )) + continue; child = *citer; prop = child->property ("val"); diff --git a/gtk2_ardour/generic_pluginui.cc b/gtk2_ardour/generic_pluginui.cc index 7ae3f45dfb..ed185c33bc 100644 --- a/gtk2_ardour/generic_pluginui.cc +++ b/gtk2_ardour/generic_pluginui.cc @@ -38,6 +38,7 @@ #include <ardour/plugin.h> #include <ardour/plugin_insert.h> #include <ardour/ladspa_plugin.h> +#include <ardour/lv2_plugin.h> #include <lrdf.h> @@ -384,6 +385,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat if (plugin->parameter_is_input (port_index)) { boost::shared_ptr<LadspaPlugin> lp; + boost::shared_ptr<LV2Plugin> lv2p; if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) { @@ -406,6 +408,26 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat lrdf_free_setting_values(defaults); return control_ui; } + + } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) { + + SLV2Port port = lv2p->slv2_port(port_index); + SLV2ScalePoints points = slv2_port_get_scale_points(lv2p->slv2_plugin(), port); + + if (points) { + control_ui->combo = new Gtk::ComboBoxText; + //control_ui->combo->set_value_in_list(true, false); + set_popdown_strings (*control_ui->combo, setup_scale_values(port_index, control_ui)); + control_ui->combo->signal_changed().connect (bind (mem_fun(*this, &GenericPluginUI::control_combo_changed), control_ui)); + mcontrol->Changed.connect (bind (mem_fun (*this, &GenericPluginUI::parameter_changed), control_ui)); + control_ui->pack_start(control_ui->label, true, true); + control_ui->pack_start(*control_ui->combo, false, true); + + update_control_display(control_ui); + + slv2_scale_points_free(points); + return control_ui; + } } if (desc.toggled) { @@ -734,26 +756,49 @@ vector<string> GenericPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui) { vector<string> enums; - boost::shared_ptr<LadspaPlugin> lp = boost::dynamic_pointer_cast<LadspaPlugin> (plugin); + boost::shared_ptr<LadspaPlugin> lp; + boost::shared_ptr<LV2Plugin> lv2p; + + if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) { + // all LADPSA plugins have a numeric unique ID + uint32_t id = atol (lp->unique_id().c_str()); + + cui->combo_map = new std::map<string, float>; + lrdf_defaults* defaults = lrdf_get_scale_values(id, port_index); + if (defaults) { + for (uint32_t i = 0; i < defaults->count; ++i) { + enums.push_back(defaults->items[i].label); + pair<string, float> newpair; + newpair.first = defaults->items[i].label; + newpair.second = defaults->items[i].value; + cui->combo_map->insert(newpair); + } - cui->combo_map = new std::map<string, float>; - - // FIXME: not all plugins have a numeric unique ID - uint32_t id = atol (lp->unique_id().c_str()); - lrdf_defaults* defaults = lrdf_get_scale_values(id, port_index); - - if (defaults) { - for (uint32_t i = 0; i < defaults->count; ++i) { - enums.push_back(defaults->items[i].label); - pair<string, float> newpair; - newpair.first = defaults->items[i].label; - newpair.second = defaults->items[i].value; - cui->combo_map->insert(newpair); + lrdf_free_setting_values(defaults); + } + + } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) { + + SLV2Port port = lv2p->slv2_port(port_index); + SLV2ScalePoints points = slv2_port_get_scale_points(lv2p->slv2_plugin(), port); + cui->combo_map = new std::map<string, float>; + + for (unsigned i=0; i < slv2_scale_points_size(points); ++i) { + SLV2ScalePoint p = slv2_scale_points_get_at(points, i); + SLV2Value label = slv2_scale_point_get_label(p); + SLV2Value value = slv2_scale_point_get_value(p); + if (label && (slv2_value_is_float(value) || slv2_value_is_int(value))) { + enums.push_back(slv2_value_as_string(label)); + pair<string, float> newpair; + newpair.first = slv2_value_as_string(label); + newpair.second = slv2_value_as_float(value); + cui->combo_map->insert(newpair); + } } - lrdf_free_setting_values(defaults); + slv2_scale_points_free(points); } + return enums; } - diff --git a/gtk2_ardour/ghostregion.cc b/gtk2_ardour/ghostregion.cc index 71bc8de694..b14872b357 100644 --- a/gtk2_ardour/ghostregion.cc +++ b/gtk2_ardour/ghostregion.cc @@ -98,6 +98,7 @@ GhostRegion::set_colors () for (uint32_t n=0; n < waves.size(); ++n) { waves[n]->property_wave_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWave.get(); + waves[n]->property_fill_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWave.get(); waves[n]->property_clip_color() = ARDOUR_UI::config()->canvasvar_GhostTrackWaveClip.get(); waves[n]->property_zero_color() = ARDOUR_UI::config()->canvasvar_GhostTrackZeroLine.get(); diff --git a/gtk2_ardour/latency_gui.cc b/gtk2_ardour/latency_gui.cc index 0146bfdaa8..d03ad5b9f9 100644 --- a/gtk2_ardour/latency_gui.cc +++ b/gtk2_ardour/latency_gui.cc @@ -114,7 +114,7 @@ void LatencyGUI::change_latency_from_button (int dir) { Glib::ustring unitstr = units_combo.get_active_text(); - double shift; + double shift = 0.0; if (unitstr == unit_strings[0]) { shift = 1; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 749c78dc50..f279f93c39 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -254,6 +254,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulThingWithGoingAway virtual void restore_editing_space () = 0; virtual nframes64_t get_preferred_edit_position (bool ignore_playhead = false) = 0; virtual void toggle_meter_updating() = 0; + virtual void split_region_at_points (boost::shared_ptr<ARDOUR::Region>, std::vector<nframes64_t>&) = 0; #ifdef WITH_CMT virtual void add_imageframe_time_axis(const std::string & track_name, void*) = 0; diff --git a/gtk2_ardour/rhythm_ferret.cc b/gtk2_ardour/rhythm_ferret.cc new file mode 100644 index 0000000000..980b36e1d1 --- /dev/null +++ b/gtk2_ardour/rhythm_ferret.cc @@ -0,0 +1,305 @@ +#include <gtkmm/stock.h> +#include <gtkmm2ext/utils.h> + +#include <pbd/memento_command.h> + +#include <ardour/transient_detector.h> +#include <ardour/audiosource.h> +#include <ardour/audioregion.h> +#include <ardour/playlist.h> +#include <ardour/region_factory.h> +#include <ardour/session.h> + +#include "rhythm_ferret.h" +#include "audio_region_view.h" +#include "public_editor.h" + +#include "i18n.h" + +using namespace std; +using namespace Gtk; +using namespace Gdk; +using namespace PBD; +using namespace ARDOUR; + +/* order of these must match the AnalysisMode enums + in rhythm_ferret.h +*/ +static const gchar * _analysis_mode_strings[] = { + N_("Percussive Onset"), + N_("Note Onset"), + 0 +}; + +RhythmFerret::RhythmFerret (PublicEditor& e) + : ArdourDialog (_("Rhythm Ferret")) + , editor (e) + , operation_frame (_("Operation")) + , selection_frame (_("Selection")) + , ferret_frame (_("Analysis")) + , logo (0) + , region_split_button (operation_button_group, _("Split Region")) + , tempo_button (operation_button_group, _("Set Tempo Map")) + , region_conform_button (operation_button_group, _("Conform Region")) + , analysis_mode_label (_("Mode")) + , detection_threshold_adjustment (3, 0, 20, 1, 4) + , detection_threshold_scale (detection_threshold_adjustment) + , detection_threshold_label (_("Threshold")) + , sensitivity_adjustment (40, 0, 100, 1, 10) + , sensitivity_scale (sensitivity_adjustment) + , sensitivity_label (_("Sensitivity")) + , analyze_button (_("Analyze")) + , trigger_gap_adjustment (3, 0, 100, 1, 10) + , trigger_gap_spinner (trigger_gap_adjustment) + , trigger_gap_label (_("Trigger gap (msecs)")) + , action_button (Stock::APPLY) + +{ + upper_hpacker.set_spacing (6); + + upper_hpacker.pack_start (operation_frame, true, true); + upper_hpacker.pack_start (selection_frame, true, true); + upper_hpacker.pack_start (ferret_frame, true, true); + + op_packer.pack_start (region_split_button, false, false); + op_packer.pack_start (tempo_button, false, false); + op_packer.pack_start (region_conform_button, false, false); + + operation_frame.add (op_packer); + + HBox* box; + + ferret_packer.set_spacing (6); + ferret_packer.set_border_width (6); + + vector<string> strings; + + analysis_mode_strings = I18N (_analysis_mode_strings); + Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings); + analysis_mode_selector.set_active_text (analysis_mode_strings.front()); + + box = manage (new HBox); + box->set_spacing (6); + box->pack_start (analysis_mode_label, false, false); + box->pack_start (analysis_mode_selector, true, true); + ferret_packer.pack_start (*box, false, false); + + box = manage (new HBox); + box->set_spacing (6); + box->pack_start (detection_threshold_label, false, false); + box->pack_start (detection_threshold_scale, true, true); + ferret_packer.pack_start (*box, false, false); + + box = manage (new HBox); + box->set_spacing (6); + box->pack_start (sensitivity_label, false, false); + box->pack_start (sensitivity_scale, true, true); + ferret_packer.pack_start (*box, false, false); + + box = manage (new HBox); + box->set_spacing (6); + box->pack_start (trigger_gap_label, false, false); + box->pack_start (trigger_gap_spinner, false, false); + ferret_packer.pack_start (*box, false, false); + + ferret_packer.pack_start (analyze_button, false, false); + + analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis)); + + ferret_frame.add (ferret_packer); + + // Glib::RefPtr<Pixbuf> logo_pixbuf ("somefile"); + + if (logo) { + lower_hpacker.pack_start (*logo, false, false); + } + + lower_hpacker.pack_start (operation_clarification_label, false, false); + lower_hpacker.pack_start (action_button, false, false); + + action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action)); + + get_vbox()->set_border_width (6); + get_vbox()->set_spacing (6); + get_vbox()->pack_start (upper_hpacker, true, true); + get_vbox()->pack_start (lower_hpacker, false, false); + + show_all (); +} + +RhythmFerret::~RhythmFerret() +{ + if (logo) { + delete logo; + } +} + +RhythmFerret::AnalysisMode +RhythmFerret::get_analysis_mode () const +{ + string str = analysis_mode_selector.get_active_text (); + + if (str == _(_analysis_mode_strings[(int) NoteOnset])) { + return NoteOnset; + } + + return PercussionOnset; +} + +RhythmFerret::Action +RhythmFerret::get_action () const +{ + if (tempo_button.get_active()) { + return DefineTempoMap; + } else if (region_conform_button.get_active()) { + return ConformRegion; + } + + return SplitRegion; +} + +void +RhythmFerret::run_analysis () +{ + if (!session) { + return; + } + + RegionSelection& regions (editor.get_selection().regions); + + current_results.clear (); + + if (regions.empty()) { + return; + } + + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + + boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region()); + + switch (get_analysis_mode()) { + case PercussionOnset: + run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results); + break; + default: + break; + } + + } + + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + (*i)->get_time_axis_view().show_temporary_lines (current_results); + } + +} + +int +RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, vector<nframes64_t>& results) +{ + TransientDetector t (session->frame_rate()); + + for (uint32_t i = 0; i < readable->n_channels(); ++i) { + + vector<nframes64_t> these_results; + + t.reset (); + t.set_threshold (detection_threshold_adjustment.get_value()); + t.set_sensitivity (sensitivity_adjustment.get_value()); + + if (t.run ("", readable.get(), i, these_results)) { + continue; + } + + /* translate all transients to give absolute position */ + + for (vector<nframes64_t>::iterator i = these_results.begin(); i != these_results.end(); ++i) { + (*i) += offset; + } + + /* merge */ + + results.insert (results.end(), these_results.begin(), these_results.end()); + } + + if (!results.empty()) { + + /* now resort to bring transients from different channels together */ + + sort (results.begin(), results.end()); + + /* remove duplicates or other things that are too close */ + + vector<nframes64_t>::iterator i = results.begin(); + nframes64_t curr = (*i); + nframes64_t gap_frames = (nframes64_t) floor (trigger_gap_adjustment.get_value() * (session->frame_rate() / 1000.0)); + + ++i; + + while (i != results.end()) { + if (((*i) == curr) || (((*i) - curr) < gap_frames)) { + i = results.erase (i); + } else { + ++i; + curr = *i; + } + } + + } + + return 0; +} + +void +RhythmFerret::do_action () +{ + if (!session || current_results.empty()) { + return; + } + + switch (get_action()) { + case SplitRegion: + do_split_action (); + break; + + default: + break; + } +} + +void +RhythmFerret::do_split_action () +{ + /* this can/will change the current selection, so work with a copy */ + + RegionSelection& regions (editor.get_selection().regions); + + if (regions.empty()) { + return; + } + + session->begin_reversible_command (_("split regions (rhythm ferret)")); + + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) { + + RegionSelection::iterator tmp; + + tmp = i; + ++tmp; + + (*i)->get_time_axis_view().hide_temporary_lines (); + + editor.split_region_at_points ((*i)->region(), current_results); + + /* i is invalid at this point */ + + i = tmp; + } + +} + +void +RhythmFerret::set_session (Session* s) +{ + ArdourDialog::set_session (s); + current_results.clear (); +} diff --git a/gtk2_ardour/rhythm_ferret.h b/gtk2_ardour/rhythm_ferret.h new file mode 100644 index 0000000000..36d4450939 --- /dev/null +++ b/gtk2_ardour/rhythm_ferret.h @@ -0,0 +1,100 @@ +#ifndef __gtk2_ardour_rhythm_ferret_h__ +#define __gtk2_ardour_rhythm_ferret_h__ + +#include <gtkmm/box.h> +#include <gtkmm/scale.h> +#include <gtkmm/spinbutton.h> +#include <gtkmm/radiobutton.h> +#include <gtkmm/radiobuttongroup.h> +#include <gtkmm/frame.h> +#include <gtkmm/image.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/button.h> +#include <gtkmm/label.h> + +#include "ardour_dialog.h" + +namespace ARDOUR { + class Readable; +} + +class PublicEditor; +class RegionView; + +class RhythmFerret : public ArdourDialog { + public: + /* order of these enums must match the _analyse_mode_strings + in rhythm_ferret.cc + */ + enum AnalysisMode { + PercussionOnset, + NoteOnset + }; + + enum Action { + SplitRegion, + DefineTempoMap, + ConformRegion + }; + + RhythmFerret (PublicEditor&); + ~RhythmFerret (); + + void set_session (ARDOUR::Session*); + + private: + PublicEditor& editor; + + Gtk::HBox upper_hpacker; + Gtk::HBox lower_hpacker; + + Gtk::Frame operation_frame; + Gtk::Frame selection_frame; + Gtk::Frame ferret_frame; + + Gtk::VBox op_logo_packer; + Gtk::Image* logo; + + /* operation frame */ + + Gtk::VBox op_packer; + Gtk::RadioButtonGroup operation_button_group; + Gtk::RadioButton region_split_button; + Gtk::RadioButton tempo_button; + Gtk::RadioButton region_conform_button; + + /* analysis frame */ + + Gtk::VBox ferret_packer; + Gtk::ComboBoxText analysis_mode_selector; + Gtk::Label analysis_mode_label; + Gtk::Adjustment detection_threshold_adjustment; + Gtk::HScale detection_threshold_scale; + Gtk::Label detection_threshold_label; + Gtk::Adjustment sensitivity_adjustment; + Gtk::HScale sensitivity_scale; + Gtk::Label sensitivity_label; + Gtk::Button analyze_button; + Gtk::Adjustment trigger_gap_adjustment; + Gtk::SpinButton trigger_gap_spinner; + Gtk::Label trigger_gap_label; + + Gtk::Label operation_clarification_label; + Gtk::Button action_button; + + std::vector<std::string> analysis_mode_strings; + + std::vector<nframes64_t> current_results; + + AnalysisMode get_analysis_mode () const; + Action get_action() const; + + void run_analysis (); + int run_percussion_onset_analysis (boost::shared_ptr<ARDOUR::Readable> region, nframes64_t offset, std::vector<nframes64_t>& results); + + void do_action (); + void do_split_action (); + void do_region_split (RegionView* rv, const std::vector<nframes64_t>&); +}; + +#endif /* __gtk2_ardour_rhythm_ferret_h__ */ diff --git a/gtk2_ardour/splash.cc b/gtk2_ardour/splash.cc index 404c5c64a0..9317ec1caa 100644 --- a/gtk2_ardour/splash.cc +++ b/gtk2_ardour/splash.cc @@ -29,14 +29,19 @@ Splash::Splash () catch (...) { throw failed_constructor(); } - + darea.set_size_request (pixbuf->get_width(), pixbuf->get_height()); - set_type_hint (Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN); set_keep_above (true); set_position (WIN_POS_CENTER); darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK); layout = create_pango_layout (""); + string str = "<b>"; + string i18n = _("Ardour loading ..."); + str += i18n; + str += "</b>"; + + layout->set_markup (str); darea.show (); darea.signal_expose_event().connect (mem_fun (*this, &Splash::expose)); @@ -48,6 +53,7 @@ void Splash::on_realize () { Window::on_realize (); + get_window()->set_decorations (Gdk::WMDecoration(0)); layout->set_font_description (get_style()->get_font()); } @@ -62,21 +68,19 @@ Splash::on_button_release_event (GdkEventButton* ev) bool Splash::expose (GdkEventExpose* ev) { -#if 0 RefPtr<Gdk::Window> window = darea.get_window(); - - Glib::RefPtr<Gtk::Style> style = darea.get_style(); - window->draw_pixbuf (darea.get_style()->get_bg_gc (STATE_NORMAL), pixbuf, + window->draw_pixbuf (get_style()->get_bg_gc (STATE_NORMAL), pixbuf, ev->area.x, ev->area.y, ev->area.x, ev->area.y, ev->area.width, ev->area.height, Gdk::RGB_DITHER_NONE, 0, 0); + Glib::RefPtr<Gtk::Style> style = darea.get_style(); Glib::RefPtr<Gdk::GC> white = style->get_white_gc(); window->draw_layout (white, 10, pixbuf->get_height() - 30, layout); -#endif + return true; } diff --git a/gtk2_ardour/tempo_dialog.cc b/gtk2_ardour/tempo_dialog.cc index 9d5ba926c5..0c99a4eeb0 100644 --- a/gtk2_ardour/tempo_dialog.cc +++ b/gtk2_ardour/tempo_dialog.cc @@ -36,12 +36,11 @@ TempoDialog::TempoDialog (TempoMap& map, nframes_t frame, const string & action) : ArdourDialog (_("edit tempo")), bpm_adjustment (60.0, 1.0, 999.9, 0.1, 1.0, 1.0), bpm_spinner (bpm_adjustment), - bpm_frame (_("Beats per minute")), - note_frame (_("BPM denominator")), + bpm_frame (_("Tempo")), ok_button (action), cancel_button (_("Cancel")), - when_bar_label (_("Bar")), - when_beat_label (_("Beat")), + when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER), + when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER), when_table (2, 2), when_frame (_("Location")) { @@ -56,11 +55,11 @@ TempoDialog::TempoDialog (TempoSection& section, const string & action) : ArdourDialog ("tempo dialog"), bpm_adjustment (60.0, 1.0, 999.9, 0.1, 1.0, 1.0), bpm_spinner (bpm_adjustment), - bpm_frame (_("Beats per minute")), + bpm_frame (_("Tempo")), ok_button (action), cancel_button (_("Cancel")), - when_bar_label (_("Bar")), - when_beat_label (_("Beat")), + when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER), + when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER), when_table (2, 2), when_frame (_("Location")) { @@ -108,18 +107,18 @@ TempoDialog::init (const BBT_Time& when, double bpm, double note_type, bool mova else note_types.set_active_text (_("quarter (4)")); - hspacer1.set_border_width (5); - hspacer1.pack_start (bpm_spinner, false, false); - vspacer1.set_border_width (5); - vspacer1.pack_start (hspacer1, false, false); + Label* bpm_label = manage(new Label(_("Beats Per Minute:"), ALIGN_LEFT, ALIGN_CENTER)); - hspacer2.set_border_width (5); - hspacer2.pack_start (note_types, false, false); - vspacer2.set_border_width (5); - vspacer2.pack_start (hspacer2, false, false); + hspacer1.set_border_width (6); + hspacer1.pack_end (bpm_spinner, PACK_EXPAND_PADDING); + hspacer1.pack_start (*bpm_label, PACK_EXPAND_PADDING); + vspacer1.set_border_width (6); + vspacer1.pack_start (hspacer1, PACK_EXPAND_PADDING); + + hspacer2.set_border_width (6); + hspacer2.pack_start (note_types, PACK_EXPAND_PADDING); bpm_frame.add (vspacer1); - note_frame.add (vspacer2); if (movable) { snprintf (buf, sizeof (buf), "%" PRIu32, when.bars); @@ -139,35 +138,40 @@ TempoDialog::init (const BBT_Time& when, double bpm, double note_type, bool mova when_table.set_homogeneous (true); when_table.set_row_spacings (2); when_table.set_col_spacings (2); - when_table.set_border_width (5); + when_table.set_border_width (6); - when_table.attach (when_bar_label, 0, 1, 0, 1, Gtk::AttachOptions(0), Gtk::FILL|Gtk::EXPAND); - when_table.attach (when_bar_entry, 0, 1, 1, 2, Gtk::AttachOptions(0), Gtk::FILL|Gtk::EXPAND); + when_table.attach (when_bar_label, 0, 1, 0, 1, AttachOptions(0), FILL|EXPAND); + when_table.attach (when_bar_entry, 1, 2, 0, 1, AttachOptions(0), FILL|EXPAND); - when_table.attach (when_beat_label, 1, 2, 0, 1, Gtk::AttachOptions(0), Gtk::AttachOptions(0)); - when_table.attach (when_beat_entry, 1, 2, 1, 2, Gtk::AttachOptions(0), Gtk::AttachOptions(0)); + when_table.attach (when_beat_label, 0, 1, 1, 2, AttachOptions(0), AttachOptions(0)); + when_table.attach (when_beat_entry, 1, 2, 1, 2, AttachOptions(0), AttachOptions(0)); + HBox* when_hbox = manage (new HBox()); + Label* when_label = manage(new Label(_("Tempo Begins at:"), ALIGN_LEFT, ALIGN_TOP)); + when_hbox->pack_end(when_table, PACK_EXPAND_PADDING, 6); + when_hbox->pack_start(*when_label, PACK_EXPAND_PADDING, 6); + when_frame.set_name ("MetricDialogFrame"); - when_frame.add (when_table); + when_frame.add (*when_hbox); + + get_vbox()->pack_end (when_frame, false, false); + when_frame.show_all(); - get_vbox()->pack_start (when_frame, false, false); } bpm_frame.set_name ("MetricDialogFrame"); bpm_spinner.set_name ("MetricEntry"); - note_frame.set_name ("MetricDialogFrame"); get_vbox()->set_border_width (12); - get_vbox()->pack_start (bpm_frame, false, false); - get_vbox()->pack_start (note_frame, false, false); + get_vbox()->pack_end (bpm_frame, false, false); add_button (Stock::CANCEL, RESPONSE_CANCEL); add_button (Stock::APPLY, RESPONSE_ACCEPT); - set_response_sensitive (Gtk::RESPONSE_ACCEPT, false); + set_response_sensitive (RESPONSE_ACCEPT, false); set_default_response (RESPONSE_ACCEPT); - get_vbox()->show_all(); - bpm_spinner.show(); + bpm_frame.show_all (); + bpm_spinner.show (); set_name ("MetricDialog"); @@ -188,7 +192,7 @@ TempoDialog::bpm_button_release (GdkEventButton* ev) { /* the value has been modified, accept should work now */ - set_response_sensitive (Gtk::RESPONSE_ACCEPT, true); + set_response_sensitive (RESPONSE_ACCEPT, true); return false; } @@ -209,6 +213,8 @@ TempoDialog::get_bbt_time (BBT_Time& requested) return false; } + requested.ticks = 0; + return true; } @@ -244,18 +250,17 @@ TempoDialog::get_note_type () void TempoDialog::note_types_change () { - set_response_sensitive (Gtk::RESPONSE_ACCEPT, true); + set_response_sensitive (RESPONSE_ACCEPT, true); } MeterDialog::MeterDialog (TempoMap& map, nframes_t frame, const string & action) : ArdourDialog ("meter dialog"), - note_frame (_("Meter denominator")), - bpb_frame (_("Beats per bar")), + bpb_frame (_("Meter")), ok_button (action), cancel_button (_("Cancel")), - when_bar_label (_("Bar")), - when_beat_label (_("Beat")), + when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER), + when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER), when_frame (_("Location")) { BBT_Time when; @@ -268,12 +273,11 @@ MeterDialog::MeterDialog (TempoMap& map, nframes_t frame, const string & action) MeterDialog::MeterDialog (MeterSection& section, const string & action) : ArdourDialog ("meter dialog"), - note_frame (_("Meter denominator")), - bpb_frame (_("Beats per bar")), + bpb_frame (_("Meter")), ok_button (action), cancel_button (_("Cancel")), - when_bar_label (_("Bar")), - when_beat_label (_("Beat")), + when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER), + when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER), when_frame (_("Location")) { init (section.start(), section.beats_per_bar(), section.note_divisor(), section.movable()); @@ -320,19 +324,16 @@ MeterDialog::init (const BBT_Time& when, double bpb, double note_type, bool mova note_types.set_active_text (_("thirty-second (32)")); else note_types.set_active_text (_("quarter (4)")); - - hspacer1.set_border_width (5); - hspacer1.pack_start (note_types, false, false); - vspacer1.set_border_width (5); - vspacer1.pack_start (hspacer1, false, false); - hspacer2.set_border_width (5); - hspacer2.pack_start (bpb_entry, false, false); - vspacer2.set_border_width (5); - vspacer2.pack_start (hspacer2, false, false); + Label* note_label = manage(new Label(_("Note Value:"), ALIGN_LEFT, ALIGN_CENTER)); + Label* bpb_label = manage(new Label(_("Beats Per Bar:"), ALIGN_LEFT, ALIGN_CENTER)); + Table* bpb_table = manage (new Table(2, 2)); - note_frame.add (vspacer1); - bpb_frame.add (vspacer2); + bpb_table->attach (*bpb_label, 0, 1, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6); + bpb_table->attach (bpb_entry, 1, 2, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6); + bpb_table->attach (*note_label, 0, 1, 1, 2, FILL|EXPAND, FILL|EXPAND, 6, 6); + bpb_table->attach (note_types, 1, 2, 1, 2, FILL|EXPAND, SHRINK, 6, 6); + bpb_frame.add (*bpb_table); if (movable) { snprintf (buf, sizeof (buf), "%" PRIu32, when.bars); @@ -352,26 +353,29 @@ MeterDialog::init (const BBT_Time& when, double bpb, double note_type, bool mova when_table.set_homogeneous (true); when_table.set_row_spacings (2); when_table.set_col_spacings (2); - when_table.set_border_width (5); + when_table.set_border_width (6); - when_table.attach (when_bar_label, 0, 1, 0, 1, Gtk::AttachOptions(0), Gtk::FILL|Gtk::EXPAND); - when_table.attach (when_bar_entry, 0, 1, 1, 2, Gtk::AttachOptions(0), Gtk::FILL|Gtk::EXPAND); - - when_table.attach (when_beat_label, 1, 2, 0, 1, Gtk::AttachOptions(0), Gtk::AttachOptions(0)); - when_table.attach (when_beat_entry, 1, 2, 1, 2, Gtk::AttachOptions(0), Gtk::AttachOptions(0)); + when_table.attach (when_bar_label, 0, 1, 0, 1, AttachOptions(0), FILL|EXPAND); + when_table.attach (when_bar_entry, 1, 2, 0, 1, AttachOptions(0), FILL|EXPAND); + when_table.attach (when_beat_label, 0, 1, 1, 2, AttachOptions(0), AttachOptions(0)); + when_table.attach (when_beat_entry, 1, 2, 1, 2, AttachOptions(0), AttachOptions(0)); + + HBox* when_hbox = manage (new HBox()); + Label* when_label = manage(new Label(_("Meter Begins at:"), ALIGN_LEFT, ALIGN_TOP)); + when_hbox->pack_end(when_table, PACK_EXPAND_PADDING, 6); + when_hbox->pack_start(*when_label, PACK_EXPAND_PADDING, 6); + when_frame.set_name ("MetricDialogFrame"); - when_frame.add (when_table); + when_frame.add (*when_hbox); - get_vbox()->pack_start (when_frame, false, false); + get_vbox()->pack_end (when_frame, false, false); } get_vbox()->set_border_width (12); get_vbox()->pack_start (bpb_frame, false, false); - get_vbox()->pack_start (note_frame, false, false); - + bpb_frame.set_name ("MetricDialogFrame"); - note_frame.set_name ("MetricDialogFrame"); bpb_entry.set_name ("MetricEntry"); add_button (Stock::CANCEL, RESPONSE_CANCEL); @@ -380,7 +384,6 @@ MeterDialog::init (const BBT_Time& when, double bpb, double note_type, bool mova set_default_response (RESPONSE_ACCEPT); get_vbox()->show_all (); - bpb_entry.show (); set_name ("MetricDialog"); bpb_entry.signal_activate().connect (bind (mem_fun (*this, &MeterDialog::response), RESPONSE_ACCEPT)); @@ -393,48 +396,48 @@ bool MeterDialog::bpb_key_press (GdkEventKey* ev) { -switch (ev->keyval) { - - case GDK_0: - case GDK_1: - case GDK_2: - case GDK_3: - case GDK_4: - case GDK_5: - case GDK_6: - case GDK_7: - case GDK_8: - case GDK_9: - case GDK_KP_0: - case GDK_KP_1: - case GDK_KP_2: - case GDK_KP_3: - case GDK_KP_4: - case GDK_KP_5: - case GDK_KP_6: - case GDK_KP_7: - case GDK_KP_8: - case GDK_KP_9: - case GDK_period: - case GDK_comma: - case GDK_KP_Delete: - case GDK_KP_Enter: - case GDK_Delete: - case GDK_BackSpace: - case GDK_Escape: - case GDK_Return: - case GDK_Home: - case GDK_End: - case GDK_Left: - case GDK_Right: - case GDK_Num_Lock: - case GDK_Tab: - return FALSE; - default: - break; - } - - return TRUE; + switch (ev->keyval) { + + case GDK_0: + case GDK_1: + case GDK_2: + case GDK_3: + case GDK_4: + case GDK_5: + case GDK_6: + case GDK_7: + case GDK_8: + case GDK_9: + case GDK_KP_0: + case GDK_KP_1: + case GDK_KP_2: + case GDK_KP_3: + case GDK_KP_4: + case GDK_KP_5: + case GDK_KP_6: + case GDK_KP_7: + case GDK_KP_8: + case GDK_KP_9: + case GDK_period: + case GDK_comma: + case GDK_KP_Delete: + case GDK_KP_Enter: + case GDK_Delete: + case GDK_BackSpace: + case GDK_Escape: + case GDK_Return: + case GDK_Home: + case GDK_End: + case GDK_Left: + case GDK_Right: + case GDK_Num_Lock: + case GDK_Tab: + return FALSE; + default: + break; + } + + return TRUE; } bool @@ -451,7 +454,7 @@ MeterDialog::bpb_key_release (GdkEventKey* ev) void MeterDialog::note_types_change () { - set_response_sensitive (Gtk::RESPONSE_ACCEPT, true); + set_response_sensitive (RESPONSE_ACCEPT, true); } double @@ -497,7 +500,6 @@ MeterDialog::get_note_type () bool MeterDialog::get_bbt_time (BBT_Time& requested) { - requested.ticks = 0; if (sscanf (when_bar_entry.get_text().c_str(), "%" PRIu32, &requested.bars) != 1) { return false; @@ -507,5 +509,7 @@ MeterDialog::get_bbt_time (BBT_Time& requested) return false; } + requested.ticks = 0; + return true; } diff --git a/gtk2_ardour/tempo_dialog.h b/gtk2_ardour/tempo_dialog.h index 47baa0cd42..c2053644a1 100644 --- a/gtk2_ardour/tempo_dialog.h +++ b/gtk2_ardour/tempo_dialog.h @@ -37,74 +37,66 @@ struct TempoDialog : public ArdourDialog { - Gtk::ComboBoxText note_types; - vector<string> strings; - Gtk::Adjustment bpm_adjustment; - Gtk::SpinButton bpm_spinner; - Gtk::Frame bpm_frame; - Gtk::Frame note_frame; - Gtk::VBox vpacker; - Gtk::Button ok_button; - Gtk::Button cancel_button; - Gtk::HBox button_box; - Gtk::HBox hspacer1, hspacer2; - Gtk::VBox vspacer1, vspacer2; - Gtk::Entry when_bar_entry; - Gtk::Entry when_beat_entry; - Gtk::Label when_bar_label; - Gtk::Label when_beat_label; - Gtk::Table when_table; - Gtk::Frame when_frame; - char buf[64]; + Gtk::ComboBoxText note_types; + vector<string> strings; + Gtk::Adjustment bpm_adjustment; + Gtk::SpinButton bpm_spinner; + Gtk::Frame bpm_frame; + Gtk::Button ok_button; + Gtk::Button cancel_button; + Gtk::HBox hspacer1, hspacer2; + Gtk::VBox vspacer1; + Gtk::Entry when_bar_entry; + Gtk::Entry when_beat_entry; + Gtk::Label when_bar_label; + Gtk::Label when_beat_label; + Gtk::Table when_table; + Gtk::Frame when_frame; + char buf[64]; - TempoDialog (ARDOUR::TempoMap&, nframes_t, const string & action); - TempoDialog (ARDOUR::TempoSection&, const string & action); + TempoDialog (ARDOUR::TempoMap&, nframes_t, const string & action); + TempoDialog (ARDOUR::TempoSection&, const string & action); - double get_bpm (); - double get_note_type (); - bool get_bbt_time (ARDOUR::BBT_Time&); + double get_bpm (); + double get_note_type (); + bool get_bbt_time (ARDOUR::BBT_Time&); - private: - void init (const ARDOUR::BBT_Time& start, double, double, bool); - void bpm_changed (); - bool bpm_button_press (GdkEventButton* ); - bool bpm_button_release (GdkEventButton* ); - void note_types_change (); +private: + void init (const ARDOUR::BBT_Time& start, double, double, bool); + void bpm_changed (); + bool bpm_button_press (GdkEventButton* ); + bool bpm_button_release (GdkEventButton* ); + void note_types_change (); }; struct MeterDialog : public ArdourDialog { - Gtk::Entry bpb_entry; - Gtk::ComboBoxText note_types; - vector<string> strings; - Gtk::Frame note_frame; - Gtk::Frame bpb_frame; - Gtk::VBox vpacker; - Gtk::Button ok_button; - Gtk::Button cancel_button; - Gtk::HBox button_box; - Gtk::HBox hspacer1, hspacer2; - Gtk::VBox vspacer1, vspacer2; - Gtk::Entry when_bar_entry; - Gtk::Entry when_beat_entry; - Gtk::Label when_bar_label; - Gtk::Label when_beat_label; - Gtk::Table when_table; - Gtk::Frame when_frame; - char buf[64]; + Gtk::Entry bpb_entry; + Gtk::ComboBoxText note_types; + vector<string> strings; + Gtk::Frame bpb_frame; + Gtk::Button ok_button; + Gtk::Button cancel_button; + Gtk::Entry when_bar_entry; + Gtk::Entry when_beat_entry; + Gtk::Label when_bar_label; + Gtk::Label when_beat_label; + Gtk::Table when_table; + Gtk::Frame when_frame; + char buf[64]; - MeterDialog (ARDOUR::TempoMap&, nframes_t, const string & action); - MeterDialog (ARDOUR::MeterSection&, const string & action); + MeterDialog (ARDOUR::TempoMap&, nframes_t, const string & action); + MeterDialog (ARDOUR::MeterSection&, const string & action); - double get_bpb (); - double get_note_type (); - bool get_bbt_time (ARDOUR::BBT_Time&); + double get_bpb (); + double get_note_type (); + bool get_bbt_time (ARDOUR::BBT_Time&); - private: - void init (const ARDOUR::BBT_Time&, double, double, bool); - bool bpb_key_press (GdkEventKey* ); - bool bpb_key_release (GdkEventKey* ); - void note_types_change (); +private: + void init (const ARDOUR::BBT_Time&, double, double, bool); + bool bpb_key_press (GdkEventKey* ); + bool bpb_key_release (GdkEventKey* ); + void note_types_change (); }; #endif /* __ardour_gtk_tempo_dialog_h__ */ diff --git a/gtk2_ardour/theme_manager.cc b/gtk2_ardour/theme_manager.cc index f529405878..9968fa9ea0 100644 --- a/gtk2_ardour/theme_manager.cc +++ b/gtk2_ardour/theme_manager.cc @@ -50,7 +50,8 @@ sigc::signal<void,uint32_t> ColorChanged; ThemeManager::ThemeManager() : ArdourDialog ("ThemeManager"), dark_button ("Dark Theme"), - light_button ("Light Theme") + light_button ("Light Theme"), + reset_button ("Restore Defaults") { Gtkmm2ext::WindowTitle title (Glib::get_application_name ()); title += _("Theme Manager"); @@ -81,6 +82,7 @@ ThemeManager::ThemeManager() get_vbox()->set_homogeneous(false); get_vbox()->pack_start (theme_selection_hbox, PACK_SHRINK); + get_vbox()->pack_start (reset_button, PACK_SHRINK); get_vbox()->pack_start (scroller); color_display.signal_button_press_event().connect (mem_fun (*this, &ThemeManager::button_press_event), false); @@ -92,6 +94,7 @@ ThemeManager::ThemeManager() color_dialog.get_cancel_button()->signal_clicked().connect (bind (mem_fun (color_dialog, &Gtk::Dialog::response), RESPONSE_CANCEL)); dark_button.signal_toggled().connect (mem_fun (*this, &ThemeManager::on_dark_theme_button_toggled)); light_button.signal_toggled().connect (mem_fun (*this, &ThemeManager::on_light_theme_button_toggled)); + reset_button.signal_clicked().connect (mem_fun (*this, &ThemeManager::reset_canvas_colors)); set_size_request (-1, 400); setup_theme (); @@ -226,6 +229,8 @@ void ThemeManager::setup_theme () { int r, g, b, a; + color_list->clear(); + for (std::vector<UIConfigVariable<uint32_t> *>::iterator i = ARDOUR_UI::config()->canvas_colors.begin(); i != ARDOUR_UI::config()->canvas_colors.end(); i++) { TreeModel::Row row = *(color_list->append()); @@ -262,3 +267,10 @@ ThemeManager::setup_theme () load_rc_file(rcfile, false); } +void +ThemeManager::reset_canvas_colors() +{ + ARDOUR_UI::config()->load_defaults(); + setup_theme (); +} + diff --git a/gtk2_ardour/theme_manager.h b/gtk2_ardour/theme_manager.h index 7ac3111b2a..3419cf0768 100644 --- a/gtk2_ardour/theme_manager.h +++ b/gtk2_ardour/theme_manager.h @@ -25,6 +25,7 @@ #include <gtkmm/scrolledwindow.h> #include <gtkmm/colorselection.h> #include <gtkmm/radiobutton.h> +#include <gtkmm/button.h> #include <gtkmm/rc.h> #include "ardour_dialog.h" #include "ui_config.h" @@ -37,6 +38,7 @@ class ThemeManager : public ArdourDialog int save (std::string path); void setup_theme (); + void reset_canvas_colors(); void on_dark_theme_button_toggled (); void on_light_theme_button_toggled (); @@ -66,6 +68,7 @@ class ThemeManager : public ArdourDialog Gtk::HBox theme_selection_hbox; Gtk::RadioButton dark_button; Gtk::RadioButton light_button; + Gtk::Button reset_button; bool button_press_event (GdkEventButton*); }; diff --git a/gtk2_ardour/time_axis_view.cc b/gtk2_ardour/time_axis_view.cc index a9956f554e..a3262baec5 100644 --- a/gtk2_ardour/time_axis_view.cc +++ b/gtk2_ardour/time_axis_view.cc @@ -43,6 +43,7 @@ #include "public_editor.h" #include "time_axis_view.h" #include "simplerect.h" +#include "simpleline.h" #include "selection.h" #include "keyboard.h" #include "rgb_macros.h" @@ -1101,3 +1102,37 @@ TimeAxisView::covers_y_position (double y) return 0; } + +void +TimeAxisView::show_temporary_lines (const vector<nframes64_t>& pos) +{ + while (temp_lines.size()< pos.size()) { + ArdourCanvas::SimpleLine* l = new ArdourCanvas::SimpleLine (*canvas_display); + l->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get(); + l->property_y1() = 0; + l->property_y2() = height; + temp_lines.push_back (l); + } + + while (temp_lines.size() > pos.size()) { + ArdourCanvas::SimpleLine *line = temp_lines.back(); + temp_lines.pop_back (); + delete line; + } + + vector<nframes64_t>::const_iterator i; + list<ArdourCanvas::SimpleLine*>::iterator l; + + for (i = pos.begin(), l = temp_lines.begin(); i != pos.end() && l != temp_lines.end(); ++i, ++l) { + (*l)->property_x1() = editor.frame_to_pixel (*i); + (*l)->property_x2() = editor.frame_to_pixel (*i); + } +} + +void +TimeAxisView::hide_temporary_lines () +{ + for (list<ArdourCanvas::SimpleLine*>::iterator l = temp_lines.begin(); l != temp_lines.end(); ++l) { + (*l)->hide (); + } +} diff --git a/gtk2_ardour/time_axis_view.h b/gtk2_ardour/time_axis_view.h index 24c91e7ba4..f3bdbec706 100644 --- a/gtk2_ardour/time_axis_view.h +++ b/gtk2_ardour/time_axis_view.h @@ -172,6 +172,9 @@ class TimeAxisView : public virtual AxisView virtual ARDOUR::RouteGroup* edit_group() const { return 0; } virtual boost::shared_ptr<ARDOUR::Playlist> playlist() const { return boost::shared_ptr<ARDOUR::Playlist> (); } + virtual void show_temporary_lines (const std::vector<nframes64_t>&); + virtual void hide_temporary_lines (); + virtual void set_samples_per_unit (double); virtual void show_selection (TimeSelection&); virtual void hide_selection (); @@ -309,6 +312,8 @@ class TimeAxisView : public virtual AxisView void set_height_pixels (uint32_t h); void color_handler (); + list<ArdourCanvas::SimpleLine*> temp_lines; + }; /* class TimeAxisView */ #endif /* __ardour_gtk_time_axis_h__ */ diff --git a/gtk2_ardour/ui_config.cc b/gtk2_ardour/ui_config.cc index 80cd612ee7..6ebedaa129 100644 --- a/gtk2_ardour/ui_config.cc +++ b/gtk2_ardour/ui_config.cc @@ -57,10 +57,39 @@ UIConfiguration::~UIConfiguration () } int +UIConfiguration::load_defaults () +{ + int found = 0; + sys::path default_ui_rc_file; + + if ( find_file_in_search_path (ardour_search_path() + system_config_search_path(), + "ardour3_ui_default.conf", default_ui_rc_file) ) + { + XMLTree tree; + found = 1; + + string rcfile = default_ui_rc_file.to_string(); + + cerr << string_compose (_("loading default ui configuration file %1"), rcfile) << endl; + + if (!tree.read (rcfile.c_str())) { + error << string_compose(_("Ardour: cannot read default ui configuration file \"%1\""), rcfile) << endmsg; + return -1; + } + + if (set_state (*tree.root())) { + error << string_compose(_("Ardour: default ui configuration file \"%1\" not loaded successfully."), rcfile) << endmsg; + return -1; + } + } + return found; +} + +int UIConfiguration::load_state () { bool found = false; - + sys::path default_ui_rc_file; if ( find_file_in_search_path (ardour_search_path() + system_config_search_path(), diff --git a/gtk2_ardour/ui_config.h b/gtk2_ardour/ui_config.h index 6e7946b32b..5d4b72056d 100644 --- a/gtk2_ardour/ui_config.h +++ b/gtk2_ardour/ui_config.h @@ -108,6 +108,7 @@ class UIConfiguration : public PBD::Stateful int load_state (); int save_state (); + int load_defaults (); int set_state (const XMLNode&); XMLNode& get_state (void); diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index bb66118b53..c407917263 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -31,6 +31,7 @@ amp.cc audio_buffer.cc auto_bundle.cc user_bundle.cc +audioanalyser.cc audio_diskstream.cc audio_library.cc audio_playlist.cc @@ -134,6 +135,7 @@ tape_file_matcher.cc template_utils.cc tempo.cc track.cc +transient_detector.cc utils.cc version.cc """) @@ -162,6 +164,7 @@ if ardour['LIBLO']: ardour.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE") ardour.Append(CXXFLAGS="-DDATA_DIR=\\\"" + os.path.join (final_prefix, 'share') + "\\\"") ardour.Append(CXXFLAGS="-DMODULE_DIR=\\\"" + os.path.join (final_prefix, env['LIBDIR']) + "\\\"") +ardour.Append(CXXFLAGS="-DVAMP_DIR=\\\"" + os.path.join (final_prefix, env['LIBDIR'], 'ardour2', 'vamp') + "\\\"") ardour.Append(CXXFLAGS="-DCONFIG_DIR=\\\"" + final_config_prefix + "\\\"") ardour.Append(CXXFLAGS="-DLOCALEDIR=\\\"" + os.path.join (final_prefix, 'share', 'locale') + "\\\"") @@ -304,15 +307,19 @@ ardour.Merge ([ libraries['pbd'], libraries['midi++2'], libraries['glib2'], - libraries['glibmm2'] + libraries['glibmm2'], + libraries['vamp'], + libraries['vamphost'], + libraries['fftw3f'], + libraries['fftw3'], ]) -#if ardour['RUBBERBAND']: -# ardour.Merge ([ libraries['rubberband'], libraries['vamp'], libraries['fftw3f'] ]) -# timefx_sources += [ 'rb_effect.cc' ] -#else: -ardour.Merge ([ libraries['soundtouch'] ]) -timefx_sources += [ 'st_stretch.cc', 'st_pitch.cc' ] +if ardour['RUBBERBAND']: + ardour.Merge ([ libraries['rubberband']]) + timefx_sources += [ 'rb_effect.cc' ] +else: + ardour.Merge ([ libraries['soundtouch'] ]) + timefx_sources += [ 'st_stretch.cc', 'st_pitch.cc' ] if ardour['LV2']: ardour.Merge ([ libraries['slv2'] ]) diff --git a/libs/ardour/ardour/audioanalyser.h b/libs/ardour/ardour/audioanalyser.h new file mode 100644 index 0000000000..dbd8a52d5a --- /dev/null +++ b/libs/ardour/ardour/audioanalyser.h @@ -0,0 +1,74 @@ +/* + Copyright (C) 2008 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_audioanalyser_h__ +#define __ardour_audioanalyser_h__ + +#include <vector> +#include <string> +#include <ostream> +#include <fstream> +#include <vamp-sdk/Plugin.h> +#include <ardour/audioregion.h> + +namespace ARDOUR { + +class Readable; +class Session; + +class AudioAnalyser { + + public: + typedef Vamp::Plugin AnalysisPlugin; + typedef std::string AnalysisPluginKey; + + AudioAnalyser (float sample_rate, AnalysisPluginKey key); + virtual ~AudioAnalyser(); + + /* analysis object should provide a run method + that accepts a path to write the results to (optionally empty) + a Readable* to read data from + and a reference to a type-specific container to return the + results. + */ + + void reset (); + + protected: + float sample_rate; + AnalysisPlugin* plugin; + AnalysisPluginKey plugin_key; + + nframes64_t bufsize; + nframes64_t stepsize; + + int initialize_plugin (AnalysisPluginKey name, float sample_rate); + int analyse (const std::string& path, Readable*, uint32_t channel); + + /* instances of an analysis object will have this method called + whenever there are results to process. if out is non-null, + the data should be written to the stream it points to. + */ + + virtual int use_features (Vamp::Plugin::FeatureSet&, std::ostream*) = 0; +}; + +} /* namespace */ + +#endif /* __ardour_audioanalyser_h__ */ diff --git a/libs/ardour/ardour/audioregion.h b/libs/ardour/ardour/audioregion.h index b84d197c3f..2c5630aec0 100644 --- a/libs/ardour/ardour/audioregion.h +++ b/libs/ardour/ardour/audioregion.h @@ -21,6 +21,7 @@ #define __ardour_audio_region_h__ #include <vector> +#include <list> #include <pbd/fastlog.h> #include <pbd/undo.h> @@ -75,6 +76,11 @@ class AudioRegion : public Region nframes_t offset, nframes_t cnt, uint32_t chan_n=0, double samples_per_unit= 1.0) const; + /* Readable interface */ + + virtual nframes64_t read (Sample*, nframes64_t pos, nframes64_t cnt, int channel) const; + virtual nframes64_t readable_length() const { return length(); } + virtual nframes_t read_at (Sample *buf, Sample *mixdown_buf, float *gain_buf, nframes_t position, nframes_t cnt, uint32_t chan_n = 0, @@ -128,12 +134,14 @@ class AudioRegion : public Region void resume_fade_in (); void resume_fade_out (); + int get_transients (std::vector<nframes64_t>&, bool force_new = false); + private: friend class RegionFactory; AudioRegion (boost::shared_ptr<AudioSource>, nframes_t start, nframes_t length); AudioRegion (boost::shared_ptr<AudioSource>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); - AudioRegion (SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); + AudioRegion (const SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); AudioRegion (boost::shared_ptr<const AudioRegion>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); AudioRegion (boost::shared_ptr<AudioSource>, const XMLNode&); AudioRegion (SourceList &, const XMLNode&); @@ -148,10 +156,11 @@ class AudioRegion : public Region void recompute_gain_at_start (); nframes_t _read_at (const SourceList&, Sample *buf, Sample *mixdown_buffer, - float *gain_buffer, nframes_t position, nframes_t cnt, - uint32_t chan_n = 0, - nframes_t read_frames = 0, - nframes_t skip_frames = 0) const; + float *gain_buffer, nframes_t position, nframes_t cnt, + uint32_t chan_n = 0, + nframes_t read_frames = 0, + nframes_t skip_frames = 0, + bool raw = false) const; void recompute_at_start (); void recompute_at_end (); @@ -178,6 +187,11 @@ class AudioRegion : public Region AudioRegion (boost::shared_ptr<const AudioRegion>); int set_live_state (const XMLNode&, Change&, bool send); + + std::vector<nframes64_t> _transients; + bool valid_transients; + void invalidate_transients (); + void cleanup_transients (std::vector<nframes64_t>&); }; } /* namespace ARDOUR */ diff --git a/libs/ardour/ardour/audiosource.h b/libs/ardour/ardour/audiosource.h index 93708a5b07..b11174abe8 100644 --- a/libs/ardour/ardour/audiosource.h +++ b/libs/ardour/ardour/audiosource.h @@ -50,10 +50,20 @@ class AudioSource : public Source, public boost::enable_shared_from_this<ARDOUR: AudioSource (Session&, const XMLNode&); virtual ~AudioSource (); - /* returns the number of items in this `audio_source' */ + nframes64_t readable_length() const { return _length; } + uint32_t n_channels() const { return 1; } virtual nframes_t available_peaks (double zoom) const; + /* stopgap until nframes_t becomes nframes64_t. this function is needed by the Readable interface */ + + virtual nframes64_t read (Sample *dst, nframes64_t start, nframes64_t cnt, int channel) const { + /* XXX currently ignores channel, assuming that source is always mono, which + historically has been true. + */ + return read (dst, (nframes_t) start, (nframes_t) cnt); + } + virtual nframes_t read (Sample *dst, nframes_t start, nframes_t cnt) const; virtual nframes_t write (Sample *src, nframes_t cnt); @@ -101,6 +111,9 @@ class AudioSource : public Source, public boost::enable_shared_from_this<ARDOUR: int prepare_for_peakfile_writes (); void done_with_peakfile_writes (bool done = true); + std::vector<nframes64_t> transients; + std::string get_transients_path() const; + protected: static bool _build_missing_peakfiles; static bool _build_peakfiles; @@ -134,6 +147,8 @@ class AudioSource : public Source, public boost::enable_shared_from_this<ARDOUR: int compute_and_write_peaks (Sample* buf, nframes_t first_frame, nframes_t cnt, bool force, bool intermediate_peaks_ready_signal, nframes_t frames_per_peak); + int load_transients (const std::string&); + private: int peakfile; nframes_t peak_leftover_cnt; diff --git a/libs/ardour/ardour/cycles.h b/libs/ardour/ardour/cycles.h index f1422880b8..0d1ac154dd 100644 --- a/libs/ardour/ardour/cycles.h +++ b/libs/ardour/ardour/cycles.h @@ -186,17 +186,7 @@ static inline cycles_t get_cycles (void) /* begin mach */ #elif defined(__APPLE__) -#ifdef HAVE_WEAK_COREAUDIO #include <CoreAudio/HostTime.h> -#else // Due to MacTypes.h and libgnomecanvasmm Rect conflict -typedef unsigned long long UInt64; - -extern UInt64 -AudioGetCurrentHostTime(); - -extern UInt64 -AudioConvertHostTimeToNanos(UInt64 inHostTime); -#endif typedef UInt64 cycles_t; static inline cycles_t get_cycles (void) diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index 40b3c669fa..777f285e9d 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -37,20 +37,21 @@ namespace ARDOUR { class AudioEngine; class Session; +struct LV2World; class LV2Plugin : public ARDOUR::Plugin { public: - LV2Plugin (ARDOUR::AudioEngine&, ARDOUR::Session&, SLV2Plugin plugin, nframes_t sample_rate); + LV2Plugin (ARDOUR::AudioEngine&, ARDOUR::Session&, ARDOUR::LV2World&, SLV2Plugin plugin, nframes_t sample_rate); LV2Plugin (const LV2Plugin &); ~LV2Plugin (); /* Plugin interface */ std::string unique_id() const; - const char* label() const { return slv2_plugin_get_name(_plugin); } - const char* name() const { return slv2_plugin_get_name(_plugin); } - const char* maker() const { return slv2_plugin_get_author_name(_plugin); } + const char* label() const { return slv2_value_as_string(_name); } + const char* name() const { return slv2_value_as_string(_name); } + const char* maker() const { return _author ? slv2_value_as_string(_author) : "Unknown"; } uint32_t parameter_count() const { return slv2_plugin_get_num_ports(_plugin); } float default_value (uint32_t port); nframes_t signal_latency() const; @@ -58,6 +59,9 @@ class LV2Plugin : public ARDOUR::Plugin float get_parameter (uint32_t port) const; int get_parameter_descriptor (uint32_t which, ParameterDescriptor&) const; uint32_t nth_parameter (uint32_t port, bool& ok) const; + + SLV2Plugin slv2_plugin() { return _plugin; } + SLV2Port slv2_port(uint32_t i) { return slv2_plugin_get_port_by_index(_plugin, i); } std::set<Parameter> automatable() const; @@ -105,29 +109,56 @@ class LV2Plugin : public ARDOUR::Plugin private: void* _module; + LV2World& _world; SLV2Plugin _plugin; - SLV2Template _template; + SLV2Value _name; + SLV2Value _author; SLV2Instance _instance; nframes_t _sample_rate; float* _control_data; float* _shadow_data; + float* _defaults; float* _latency_control_port; bool _was_activated; vector<bool> _port_is_input; - void init (SLV2Plugin plugin, nframes_t rate); + void init (LV2World& world, SLV2Plugin plugin, nframes_t rate); void run (nframes_t nsamples); void latency_compute_run (); }; + +/** The SLV2World, and various cached (as symbols, fast) URIs. + * + * This object represents everything ardour 'knows' about LV2 + * (ie understood extensions/features/etc) + */ +struct LV2World { + LV2World(); + ~LV2World(); + + SLV2World world; + SLV2Value input_class; + SLV2Value output_class; + SLV2Value audio_class; + SLV2Value control_class; + SLV2Value event_class; + SLV2Value in_place_broken; + SLV2Value integer; + SLV2Value toggled; + SLV2Value srate; +}; + + class LV2PluginInfo : public PluginInfo { public: - LV2PluginInfo (void* slv2_plugin);; + LV2PluginInfo (void* slv2_world, void* slv2_plugin);; ~LV2PluginInfo ();; static PluginInfoList discover (void* slv2_world); PluginPtr load (Session& session); + void* _lv2_world; void* _slv2_plugin; }; diff --git a/libs/ardour/ardour/midi_region.h b/libs/ardour/ardour/midi_region.h index 869436e423..50c6b8bce7 100644 --- a/libs/ardour/ardour/midi_region.h +++ b/libs/ardour/ardour/midi_region.h @@ -50,6 +50,10 @@ class MidiRegion : public Region ~MidiRegion(); boost::shared_ptr<MidiSource> midi_source (uint32_t n=0) const; + + /* Stub Readable interface */ + virtual nframes64_t read (Sample*, nframes64_t pos, nframes64_t cnt, int channel) const { return 0; } + virtual nframes64_t readable_length() const { return length(); } nframes_t read_at (MidiRingBuffer& dst, nframes_t position, @@ -86,11 +90,11 @@ class MidiRegion : public Region MidiRegion (boost::shared_ptr<MidiSource>, nframes_t start, nframes_t length); MidiRegion (boost::shared_ptr<MidiSource>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); - MidiRegion (SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); + MidiRegion (const SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); MidiRegion (boost::shared_ptr<const MidiRegion>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags); MidiRegion (boost::shared_ptr<const MidiRegion>); MidiRegion (boost::shared_ptr<MidiSource>, const XMLNode&); - MidiRegion (SourceList &, const XMLNode&); + MidiRegion (const SourceList &, const XMLNode&); private: nframes_t _read_at (const SourceList&, MidiRingBuffer& dst, diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index c83debec3d..323fc8b5a1 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -49,8 +49,15 @@ class MidiSource : public Source MidiSource (Session& session, const XMLNode&); virtual ~MidiSource (); - virtual nframes_t read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const; - virtual nframes_t write (MidiRingBuffer& src, nframes_t cnt); + /* Stub Readable interface */ + virtual nframes64_t read (Sample*, nframes64_t pos, nframes64_t cnt, int channel) const { return 0; } + virtual nframes64_t readable_length() const { return length(); } + virtual uint32_t n_channels () const { return 1; } + + // FIXME: integrate this with the Readable::read interface somehow + virtual nframes_t midi_read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const; + virtual nframes_t midi_write (MidiRingBuffer& src, nframes_t cnt); + virtual void append_event_unlocked(const MidiEvent& ev) = 0; virtual void mark_for_remove() = 0; diff --git a/libs/ardour/ardour/plugin_manager.h b/libs/ardour/ardour/plugin_manager.h index dd50a079e3..892c8bd75a 100644 --- a/libs/ardour/ardour/plugin_manager.h +++ b/libs/ardour/ardour/plugin_manager.h @@ -28,7 +28,7 @@ #include <ardour/plugin.h> #ifdef HAVE_SLV2 -#include <slv2/slv2.h> +#include <ardour/lv2_plugin.h> #endif namespace ARDOUR { @@ -40,6 +40,8 @@ class PluginManager { PluginManager (); ~PluginManager (); + /* realtime plugin APIs */ + ARDOUR::PluginInfoList &vst_plugin_info () { return _vst_plugin_info; } ARDOUR::PluginInfoList &ladspa_plugin_info () { return _ladspa_plugin_info; } ARDOUR::PluginInfoList &lv2_plugin_info () { return _lv2_plugin_info; } @@ -59,7 +61,7 @@ class PluginManager { ARDOUR::PluginInfoList _au_plugin_info; #ifdef HAVE_SLV2 - SLV2World _lv2_world; + LV2World* _lv2_world; #endif std::map<uint32_t, std::string> rdf_type; diff --git a/libs/ardour/ardour/readable.h b/libs/ardour/ardour/readable.h new file mode 100644 index 0000000000..e072a1c95e --- /dev/null +++ b/libs/ardour/ardour/readable.h @@ -0,0 +1,20 @@ +#ifndef __ardour_readable_h__ +#define __ardour_readable_h__ + +#include <ardour/types.h> + +namespace ARDOUR { + +class Readable { + public: + Readable () {} + virtual ~Readable() {} + + virtual nframes64_t read (Sample*, nframes64_t pos, nframes64_t cnt, int channel) const = 0; + virtual nframes64_t readable_length() const = 0; + virtual uint32_t n_channels () const = 0; +}; + +} + +#endif /* __ardour_readable_h__ */ diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index 76b41a04cb..da07c580b4 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -29,6 +29,7 @@ #include <ardour/ardour.h> #include <ardour/data_type.h> #include <ardour/automatable.h> +#include <ardour/readable.h> class XMLNode; @@ -43,7 +44,7 @@ enum RegionEditState { EditChangesID = 2 }; -class Region : public Automatable, public boost::enable_shared_from_this<Region> +class Region : public Automatable, public boost::enable_shared_from_this<Region>, public Readable { public: typedef std::vector<boost::shared_ptr<Source> > SourceList; @@ -217,13 +218,13 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length, const string& name, DataType type, layer_t = 0, Flag flags = DefaultFlags); - Region (SourceList& srcs, nframes_t start, nframes_t length, + Region (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, DataType type, layer_t = 0, Flag flags = DefaultFlags); Region (boost::shared_ptr<const Region>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Flag flags = DefaultFlags); Region (boost::shared_ptr<const Region>); Region (boost::shared_ptr<Source> src, const XMLNode&); - Region (SourceList& srcs, const XMLNode&); + Region (const SourceList& srcs, const XMLNode&); /* this one is for derived types of derived types */ diff --git a/libs/ardour/ardour/region_factory.h b/libs/ardour/ardour/region_factory.h index e6b9e5dde6..59749613ac 100644 --- a/libs/ardour/ardour/region_factory.h +++ b/libs/ardour/ardour/region_factory.h @@ -48,7 +48,7 @@ class RegionFactory { nframes_t length, std::string name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true); static boost::shared_ptr<Region> create (boost::shared_ptr<Source>, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true); - static boost::shared_ptr<Region> create (SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true); + static boost::shared_ptr<Region> create (const SourceList &, nframes_t start, nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true); static boost::shared_ptr<Region> create (boost::shared_ptr<Region>); static boost::shared_ptr<Region> create (Session&, XMLNode&, bool); static boost::shared_ptr<Region> create (SourceList &, const XMLNode&); diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index c6b913b979..d27df2f7ea 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -261,6 +261,9 @@ class Session : public PBD::StatefulDestructible const SessionDirectory& session_directory () const { return *(_session_dir.get()); } std::string automation_dir () const; + std::string analysis_dir() const; + + int ensure_subdirs (); Glib::ustring peak_path (Glib::ustring) const; diff --git a/libs/ardour/ardour/source.h b/libs/ardour/ardour/source.h index aa8bc0ca1f..174e58c61b 100644 --- a/libs/ardour/ardour/source.h +++ b/libs/ardour/ardour/source.h @@ -30,13 +30,14 @@ #include <ardour/ardour.h> #include <ardour/session_object.h> #include <ardour/data_type.h> +#include <ardour/readable.h> namespace ARDOUR { class Session; class Playlist; -class Source : public SessionObject +class Source : public SessionObject, public ARDOUR::Readable { public: Source (Session&, const std::string& name, DataType type); diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index 72f24c1054..dc49f5cdef 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -109,6 +109,9 @@ class MetricSection { public: MetricSection (const BBT_Time& start) : _start (start), _frame (0), _movable (true) {} + MetricSection (nframes_t start) + : _frame (start), _movable (true) {} + virtual ~MetricSection() {} const BBT_Time& start() const { return _start; } @@ -142,6 +145,8 @@ class MeterSection : public MetricSection, public Meter { public: MeterSection (const BBT_Time& start, double bpb, double note_type) : MetricSection (start), Meter (bpb, note_type) {} + MeterSection (nframes_t start, double bpb, double note_type) + : MetricSection (start), Meter (bpb, note_type) {} MeterSection (const XMLNode&); static const string xml_state_node_name; @@ -153,6 +158,8 @@ class TempoSection : public MetricSection, public Tempo { public: TempoSection (const BBT_Time& start, double qpm, double note_type) : MetricSection (start), Tempo (qpm, note_type) {} + TempoSection (nframes_t start, double qpm, double note_type) + : MetricSection (start), Tempo (qpm, note_type) {} TempoSection (const XMLNode&); static const string xml_state_node_name; @@ -165,7 +172,6 @@ typedef list<MetricSection*> Metrics; class TempoMap : public PBD::StatefulDestructible { public: - TempoMap (nframes_t frame_rate); ~TempoMap(); @@ -207,9 +213,14 @@ class TempoMap : public PBD::StatefulDestructible const Tempo& tempo_at (nframes_t); const Meter& meter_at (nframes_t); + const TempoSection& tempo_section_at (nframes_t); + void add_tempo(const Tempo&, BBT_Time where); void add_meter(const Meter&, BBT_Time where); + void add_tempo(const Tempo&, nframes_t where); + void add_meter(const Meter&, nframes_t where); + void move_tempo (TempoSection&, const BBT_Time& to); void move_meter (MeterSection&, const BBT_Time& to); @@ -267,6 +278,8 @@ class TempoMap : public PBD::StatefulDestructible Metric metric_at (nframes_t) const; void bbt_time_with_metric (nframes_t, BBT_Time&, const Metric&) const; + void change_existing_tempo_at (nframes_t, double bpm, double note_type); + sigc::signal<void,ARDOUR::Change> StateChanged; private: @@ -280,8 +293,7 @@ class TempoMap : public PBD::StatefulDestructible BBT_Time last_bbt; mutable Glib::RWLock lock; - void timestamp_metrics (); - + void timestamp_metrics (bool use_bbt); nframes_t round_to_type (nframes_t fr, int dir, BBTPointType); @@ -298,7 +310,7 @@ class TempoMap : public PBD::StatefulDestructible 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); + void do_insert (MetricSection* section, bool with_bbt); }; }; /* namespace ARDOUR */ diff --git a/libs/ardour/ardour/transient_detector.h b/libs/ardour/ardour/transient_detector.h new file mode 100644 index 0000000000..c65bae3ed5 --- /dev/null +++ b/libs/ardour/ardour/transient_detector.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2008 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_transient_detector_h__ +#define __ardour_transient_detector_h__ + +#include <ardour/audioanalyser.h> + +namespace ARDOUR { + +class AudioSource; +class Session; + +class TransientDetector : public AudioAnalyser +{ + + public: + TransientDetector (float sample_rate); + ~TransientDetector(); + + void set_threshold (float); + void set_sensitivity (float); + + float get_threshold () const; + float get_sensitivity () const; + + int run (const std::string& path, Readable*, uint32_t channel, std::vector<nframes64_t>& results); + + protected: + std::vector<nframes64_t>* current_results; + int use_features (Vamp::Plugin::FeatureSet&, std::ostream*); +}; + +} /* namespace */ + +#endif /* __ardour_audioanalyser_h__ */ diff --git a/libs/ardour/audioanalyser.cc b/libs/ardour/audioanalyser.cc new file mode 100644 index 0000000000..4cc99a5d5e --- /dev/null +++ b/libs/ardour/audioanalyser.cc @@ -0,0 +1,157 @@ +#include <vamp-sdk/hostext/PluginLoader.h> +#include <glibmm/miscutils.h> +#include <glibmm/fileutils.h> +#include <glib/gstdio.h> // for g_remove() + +#include <pbd/error.h> + +#include <ardour/audioanalyser.h> +#include <ardour/readable.h> +#include <ardour/readable.h> + +#include "i18n.h" + +using namespace std; +using namespace Vamp; +using namespace PBD; +using namespace ARDOUR; + +AudioAnalyser::AudioAnalyser (float sr, AnalysisPluginKey key) + : sample_rate (sr) + , plugin (0) + , plugin_key (key) +{ +} + +AudioAnalyser::~AudioAnalyser () +{ +} + +int +AudioAnalyser::initialize_plugin (AnalysisPluginKey key, float sr) +{ + using namespace Vamp::HostExt; + + PluginLoader* loader (PluginLoader::getInstance()); + + plugin = loader->loadPlugin (key, sr, PluginLoader::ADAPT_ALL); + + if (!plugin) { + error << string_compose (_("VAMP Plugin \"%1\" could not be loaded"), key) << endmsg; + return -1; + } + + /* we asked for the buffering adapter, so set the blocksize to + something that makes for efficient disk i/o + */ + + bufsize = 65536; + stepsize = bufsize; + + if (plugin->getMinChannelCount() > 1) { + delete plugin; + return -1; + } + + if (!plugin->initialise (1, stepsize, bufsize)) { + delete plugin; + return -1; + } + + return 0; +} + +void +AudioAnalyser::reset () +{ + if (plugin) { + plugin->reset (); + } +} + +int +AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) +{ + ofstream ofile; + Plugin::FeatureSet onsets; + int ret = -1; + bool done = false; + Sample* data = 0; + nframes64_t len = src->readable_length(); + nframes64_t pos = 0; + float* bufs[1] = { 0 }; + + if (!path.empty()) { + ofile.open (path.c_str()); + if (!ofile) { + goto out; + } + } + + /* create VAMP percussion onset plugin and initialize */ + + if (plugin == 0) { + if (initialize_plugin (plugin_key, sample_rate)) { + goto out; + } + } + + data = new Sample[bufsize]; + bufs[0] = data; + + while (!done) { + + nframes64_t to_read; + + /* read from source */ + + to_read = min ((len - pos), bufsize); + + if (src->read (data, pos, to_read, channel) != to_read) { + cerr << "bad read\n"; + goto out; + } + + /* zero fill buffer if necessary */ + + if (to_read != bufsize) { + memset (data + to_read, 0, (bufsize - to_read)); + } + + onsets = plugin->process (bufs, RealTime::fromSeconds ((double) pos / sample_rate)); + + if (use_features (onsets, (path.empty() ? &ofile : 0))) { + goto out; + } + + pos += stepsize; + + if (pos >= len) { + done = true; + } + } + + /* finish up VAMP plugin */ + + onsets = plugin->getRemainingFeatures (); + + if (use_features (onsets, (path.empty() ? &ofile : 0))) { + goto out; + } + + ret = 0; + + out: + /* works even if it has not been opened */ + ofile.close (); + + if (ret) { + g_remove (path.c_str()); + } + if (data) { + delete data; + } + + return ret; +} + diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 86abd4beaa..822fe2cb72 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -20,6 +20,7 @@ #include <cmath> #include <climits> #include <cfloat> +#include <algorithm> #include <set> @@ -42,6 +43,7 @@ #include <ardour/audiofilesource.h> #include <ardour/region_factory.h> #include <ardour/runtime_functions.h> +#include <ardour/transient_detector.h> #include "i18n.h" #include <locale.h> @@ -64,6 +66,7 @@ void AudioRegion::init () { _scale_amplitude = 1.0; + valid_transients = false; set_default_fades (); set_default_envelope (); @@ -112,7 +115,7 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, nframes_t start, n } /* Basic AudioRegion constructor (many channels) */ -AudioRegion::AudioRegion (SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) +AudioRegion::AudioRegion (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (srcs, start, length, name, DataType::AUDIO, layer, flags) , _fade_in (new AutomationList(Parameter(FadeInAutomation), 0.0, 2.0, 1.0)) , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) @@ -121,7 +124,6 @@ AudioRegion::AudioRegion (SourceList& srcs, nframes_t start, nframes_t length, c init (); } - /** Create a new AudioRegion, that is part of an existing one */ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, nframes_t offset, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (other, offset, length, name, layer, flags) @@ -170,6 +172,7 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, nframes_t } _scale_amplitude = other->_scale_amplitude; + valid_transients = false; assert(_type == DataType::AUDIO); } @@ -181,6 +184,7 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other) , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) { _scale_amplitude = other->_scale_amplitude; + valid_transients = false; _envelope = other->_envelope; set_default_fades (); @@ -202,6 +206,7 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, const XMLNode& nod } init (); + valid_transients = false; if (set_state (node)) { throw failed_constructor(); @@ -230,6 +235,13 @@ AudioRegion::~AudioRegion () } void +AudioRegion::invalidate_transients () +{ + valid_transients = false; + _transients.clear (); +} + +void AudioRegion::listen_to_my_curves () { _envelope->StateChanged.connect (mem_fun (*this, &AudioRegion::envelope_changed)); @@ -273,12 +285,20 @@ AudioRegion::read_peaks (PeakData *buf, nframes_t npeaks, nframes_t offset, nfra } } +nframes64_t +AudioRegion::read (Sample* buf, nframes64_t position, nframes64_t cnt, int channel) const +{ + /* raw read, no fades, no gain, nada */ + return _read_at (_sources, buf, 0, 0, _position + position, cnt, channel, 0, 0, true); +} + nframes_t AudioRegion::read_at (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t position, nframes_t cnt, uint32_t chan_n, nframes_t read_frames, nframes_t skip_frames) const { - return _read_at (_sources, buf, mixdown_buffer, gain_buffer, position, cnt, chan_n, read_frames, skip_frames); + /* regular diskstream/butler read complete with fades etc */ + return _read_at (_sources, buf, mixdown_buffer, gain_buffer, position, cnt, chan_n, read_frames, skip_frames, false); } nframes_t @@ -291,13 +311,16 @@ AudioRegion::master_read_at (Sample *buf, Sample *mixdown_buffer, float *gain_bu nframes_t AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t position, nframes_t cnt, - uint32_t chan_n, nframes_t read_frames, nframes_t skip_frames) const + uint32_t chan_n, + nframes_t read_frames, + nframes_t skip_frames, + bool raw) const { nframes_t internal_offset; nframes_t buf_offset; nframes_t to_read; - if (muted()) { + if (muted() && !raw) { return 0; /* read nothing */ } @@ -320,14 +343,16 @@ AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buff return 0; /* read nothing */ } - if (opaque()) { + if (opaque() || raw) { /* overwrite whatever is there */ mixdown_buffer = buf + buf_offset; } else { mixdown_buffer += buf_offset; } - _read_data_count = 0; + if (!raw) { + _read_data_count = 0; + } if (chan_n < n_channels()) { @@ -336,7 +361,9 @@ AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buff return 0; /* "read nothing" */ } - _read_data_count += src->read_data_count(); + if (!raw) { + _read_data_count += src->read_data_count(); + } } else { @@ -348,37 +375,41 @@ AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buff /* no fades required */ - goto merge; + if (!raw) { + goto merge; + } } /* fade in */ - if (_flags & FadeIn) { - - nframes_t fade_in_length = (nframes_t) _fade_in->back()->when; - - /* see if this read is within the fade in */ - - if (internal_offset < fade_in_length) { - - nframes_t limit; - - limit = min (to_read, fade_in_length - internal_offset); - - _fade_in->curve().get_vector (internal_offset, internal_offset+limit, gain_buffer, limit); - - for (nframes_t n = 0; n < limit; ++n) { - mixdown_buffer[n] *= gain_buffer[n]; + if (!raw) { + + if (_flags & FadeIn) { + + nframes_t fade_in_length = (nframes_t) _fade_in->back()->when; + + /* see if this read is within the fade in */ + + if (internal_offset < fade_in_length) { + + nframes_t limit; + + limit = min (to_read, fade_in_length - internal_offset); + + _fade_in->curve().get_vector (internal_offset, internal_offset+limit, gain_buffer, limit); + + for (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 */ - + + /* fade out */ + + if (_flags & FadeOut) { + + /* see if some part of this read is within the fade out */ + /* ................. >| REGION _length @@ -389,65 +420,66 @@ AudioRegion::_read_at (const SourceList& srcs, Sample *buf, Sample *mixdown_buff |--------------| ^internal_offset ^internal_offset + to_read - - we need the intersection of [internal_offset,internal_offset+to_read] with - [_length - fade_out_length, _length] - + + we need the intersection of [internal_offset,internal_offset+to_read] with + [_length - fade_out_length, _length] + */ - nframes_t fade_out_length = (nframes_t) _fade_out->back()->when; - nframes_t fade_interval_start = max(internal_offset, _length-fade_out_length); - 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 */ + nframes_t fade_out_length = (nframes_t) _fade_out->back()->when; + nframes_t fade_interval_start = max(internal_offset, _length-fade_out_length); + nframes_t fade_interval_end = min(internal_offset + to_read, _length); - nframes_t limit = fade_interval_end - fade_interval_start; - nframes_t curve_offset = fade_interval_start - (_length-fade_out_length); - nframes_t fade_offset = fade_interval_start - internal_offset; - - _fade_out->curve().get_vector (curve_offset,curve_offset+limit, gain_buffer, limit); - - for (nframes_t n = 0, m = fade_offset; n < limit; ++n, ++m) { - mixdown_buffer[m] *= gain_buffer[n]; - } - } - - } - - /* Regular gain curves */ - - if (envelope_active()) { - _envelope->curve().get_vector (internal_offset, internal_offset + to_read, gain_buffer, to_read); + if (fade_interval_end > fade_interval_start) { + /* (part of the) the fade out is in this buffer */ + + nframes_t limit = fade_interval_end - fade_interval_start; + nframes_t curve_offset = fade_interval_start - (_length-fade_out_length); + nframes_t fade_offset = fade_interval_start - internal_offset; + + _fade_out->curve().get_vector (curve_offset,curve_offset+limit, gain_buffer, limit); + + for (nframes_t n = 0, m = fade_offset; n < limit; ++n, ++m) { + mixdown_buffer[m] *= gain_buffer[n]; + } + } + + } - if (_scale_amplitude != 1.0f) { - for (nframes_t n = 0; n < to_read; ++n) { - mixdown_buffer[n] *= gain_buffer[n] * _scale_amplitude; + /* Regular gain curves */ + + if (envelope_active()) { + _envelope->curve().get_vector (internal_offset, internal_offset + to_read, gain_buffer, to_read); + + if (_scale_amplitude != 1.0f) { + for (nframes_t n = 0; n < to_read; ++n) { + mixdown_buffer[n] *= gain_buffer[n] * _scale_amplitude; + } + } else { + for (nframes_t n = 0; n < to_read; ++n) { + mixdown_buffer[n] *= gain_buffer[n]; + } } - } else { + } else if (_scale_amplitude != 1.0f) { + Session::apply_gain_to_buffer (mixdown_buffer, to_read, _scale_amplitude); + } + + merge: + + if (!opaque()) { + + /* gack. the things we do for users. + */ + + buf += buf_offset; + for (nframes_t n = 0; n < to_read; ++n) { - mixdown_buffer[n] *= gain_buffer[n]; + buf[n] += mixdown_buffer[n]; } - } - } else if (_scale_amplitude != 1.0f) { - apply_gain_to_buffer (mixdown_buffer, to_read, _scale_amplitude); + } } - merge: - - if (!opaque()) { - - /* gack. the things we do for users. - */ - - buf += buf_offset; - - for (nframes_t n = 0; n < to_read; ++n) { - buf[n] += mixdown_buffer[n]; - } - } - return to_read; } @@ -1224,6 +1256,93 @@ AudioRegion::audio_source (uint32_t n) const return boost::dynamic_pointer_cast<AudioSource>(source(n)); } +void +AudioRegion::cleanup_transients (vector<nframes64_t>& t) +{ + sort (t.begin(), t.end()); + + /* remove duplicates or other things that are too close */ + + vector<nframes64_t>::iterator i = t.begin(); + nframes64_t curr = (*i); + + /* XXX force a 3msec gap - use a config variable */ + + nframes64_t gap_frames = (nframes64_t) floor (3.0 * (playlist()->session().frame_rate() / 1000.0)); + + ++i; + + while (i != t.end()) { + if (((*i) == curr) || (((*i) - curr) < gap_frames)) { + i = t.erase (i); + } else { + ++i; + curr = *i; + } + } +} + +int +AudioRegion::get_transients (vector<nframes64_t>& results, bool force_new) +{ + if (!playlist()) { + return -1; + } + + if (valid_transients && !force_new) { + results = _transients; + return 0; + } + + TransientDetector t (playlist()->session().frame_rate()); + bool existing_results = !results.empty(); + + _transients.clear (); + valid_transients = false; + + for (uint32_t i = 0; i < n_channels(); ++i) { + + vector<nframes64_t> these_results; + + t.reset (); + + if (t.run ("", this, i, these_results)) { + return -1; + } + + /* translate all transients to give absolute position */ + + for (vector<nframes64_t>::iterator i = these_results.begin(); i != these_results.end(); ++i) { + (*i) += _position; + } + + /* merge */ + + _transients.insert (_transients.end(), these_results.begin(), these_results.end()); + } + + if (!results.empty()) { + if (existing_results) { + + /* merge our transients into the existing ones, then clean up + those. + */ + + results.insert (results.end(), _transients.begin(), _transients.end()); + cleanup_transients (results); + } + + /* make sure ours are clean too */ + + cleanup_transients (_transients); + } + + valid_transients = true; + + return 0; +} + + 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) diff --git a/libs/ardour/audiosource.cc b/libs/ardour/audiosource.cc index ce8aa95964..80116988d5 100644 --- a/libs/ardour/audiosource.cc +++ b/libs/ardour/audiosource.cc @@ -27,10 +27,12 @@ #include <ctime> #include <cmath> #include <iomanip> +#include <fstream> #include <algorithm> #include <vector> #include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> #include <pbd/xml++.h> #include <pbd/pthread_utils.h> @@ -38,6 +40,7 @@ #include <ardour/audiosource.h> #include <ardour/cycle_timer.h> #include <ardour/session.h> +#include <ardour/transient_detector.h> #include "i18n.h" @@ -916,3 +919,50 @@ AudioSource::update_length (nframes_t pos, nframes_t cnt) } } +int +AudioSource::load_transients (const string& path) +{ + ifstream file (path.c_str()); + + if (!file) { + return -1; + } + + transients.clear (); + + stringstream strstr; + double val; + + while (file.good()) { + file >> val; + + if (!file.fail()) { + nframes64_t frame = (nframes64_t) floor (val * _session.frame_rate()); + transients.push_back (frame); + } + } + + return 0; +} + +string +AudioSource::get_transients_path () const +{ + vector<string> parts; + string s; + + /* old sessions may not have the analysis directory */ + + _session.ensure_subdirs (); + + s = _session.analysis_dir (); + parts.push_back (s); + + s = _id.to_s(); + s += '.'; + s += X_("transients"); + parts.push_back (s); + + return Glib::build_filename (parts); +} + diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 7405077cf3..6bb21a419c 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -285,6 +285,17 @@ ARDOUR::init (bool use_vst, bool try_optimization) return -1; } #endif + + /* Make VAMP look in our library ahead of anything else */ + + char *p = getenv ("VAMP_PATH"); + string vamppath = VAMP_DIR; + if (p) { + vamppath += ':'; + vamppath += p; + } + setenv ("VAMP_PATH", vamppath.c_str(), 1); + setup_hardware_optimization (try_optimization); diff --git a/libs/ardour/ladspa_plugin.cc b/libs/ardour/ladspa_plugin.cc index ccc12f8cf8..29f2d16767 100644 --- a/libs/ardour/ladspa_plugin.cc +++ b/libs/ardour/ladspa_plugin.cc @@ -467,7 +467,6 @@ LadspaPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& des desc.label = port_names()[which]; - return 0; } diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 5df3756364..e58e5ed140 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -43,16 +43,18 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -LV2Plugin::LV2Plugin (AudioEngine& e, Session& session, SLV2Plugin plugin, nframes_t rate) +LV2Plugin::LV2Plugin (AudioEngine& e, Session& session, LV2World& world, SLV2Plugin plugin, nframes_t rate) : Plugin (e, session) + , _world(world) { - init (plugin, rate); + init (world, plugin, rate); } LV2Plugin::LV2Plugin (const LV2Plugin &other) : Plugin (other) + , _world(other._world) { - init (other._plugin, other._sample_rate); + init (other._world, other._plugin, other._sample_rate); for (uint32_t i = 0; i < parameter_count(); ++i) { _control_data[i] = other._shadow_data[i]; @@ -61,24 +63,30 @@ LV2Plugin::LV2Plugin (const LV2Plugin &other) } void -LV2Plugin::init (SLV2Plugin plugin, nframes_t rate) +LV2Plugin::init (LV2World& world, SLV2Plugin plugin, nframes_t rate) { + _world = world; _plugin = plugin; - _template = slv2_plugin_get_template(plugin); _control_data = 0; _shadow_data = 0; _latency_control_port = 0; _was_activated = false; _instance = slv2_plugin_instantiate(plugin, rate, NULL); + _name = slv2_plugin_get_name(plugin); + assert(_name); + _author = slv2_plugin_get_author_name(plugin); if (_instance == 0) { error << _("LV2: Failed to instantiate plugin ") << slv2_plugin_get_uri(plugin) << endl; throw failed_constructor(); } - if (slv2_plugin_has_feature(plugin, "http://lv2plug.in/ns/lv2core#inPlaceBroken")) { - error << string_compose(_("LV2: \"%1\" cannot be used, since it cannot do inplace processing"), slv2_plugin_get_name(plugin)) << endmsg; + if (slv2_plugin_has_feature(plugin, world.in_place_broken)) { + error << string_compose(_("LV2: \"%1\" cannot be used, since it cannot do inplace processing"), + slv2_value_as_string(_name)); + slv2_value_free(_name); + slv2_value_free(_author); throw failed_constructor(); } @@ -88,14 +96,21 @@ LV2Plugin::init (SLV2Plugin plugin, nframes_t rate) _control_data = new float[num_ports]; _shadow_data = new float[num_ports]; + _defaults = new float[num_ports]; const bool latent = slv2_plugin_has_latency(plugin); - uint32_t latency_port = (latent ? slv2_plugin_get_latency_port(plugin) : 0); + uint32_t latency_port = (latent ? slv2_plugin_get_latency_port_index(plugin) : 0); for (uint32_t i = 0; i < num_ports; ++i) { if (parameter_is_control(i)) { + SLV2Port port = slv2_plugin_get_port_by_index(plugin, i); + SLV2Value def; + slv2_port_get_range(plugin, port, &def, NULL, NULL); + _defaults[i] = def ? slv2_value_as_float(def) : 0.0f; + slv2_value_free(def); + slv2_instance_connect_port (_instance, i, &_control_data[i]); - + if (latent && i == latency_port) { _latency_control_port = &_control_data[i]; *_latency_control_port = 0; @@ -104,6 +119,8 @@ LV2Plugin::init (SLV2Plugin plugin, nframes_t rate) if (parameter_is_input(i)) { _shadow_data[i] = default_value (i); } + } else { + _defaults[i] = 0.0f; } } @@ -118,6 +135,8 @@ LV2Plugin::~LV2Plugin () GoingAway (); /* EMIT SIGNAL */ slv2_instance_free(_instance); + slv2_value_free(_name); + slv2_value_free(_author); if (_control_data) { delete [] _control_data; @@ -131,15 +150,14 @@ LV2Plugin::~LV2Plugin () string LV2Plugin::unique_id() const { - return slv2_plugin_get_uri(_plugin); + return slv2_value_as_uri(slv2_plugin_get_uri(_plugin)); } float LV2Plugin::default_value (uint32_t port) { - return slv2_port_get_default_value(_plugin, - slv2_plugin_get_port_by_index(_plugin, port)); + return _defaults[port]; } void @@ -276,15 +294,16 @@ LV2Plugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) { SLV2Port port = slv2_plugin_get_port_by_index(_plugin, which); - #define LV2_URI "http://lv2plug.in/ns/lv2core#" + SLV2Value def, min, max; + slv2_port_get_range(_plugin, port, &def, &min, &max); - desc.integer_step = slv2_port_has_property(_plugin, port, LV2_URI "integer"); - desc.toggled = slv2_port_has_property(_plugin, port, LV2_URI "toggled"); + desc.integer_step = slv2_port_has_property(_plugin, port, _world.integer); + desc.toggled = slv2_port_has_property(_plugin, port, _world.toggled); desc.logarithmic = false; // TODO (LV2 extension) - desc.sr_dependent = slv2_port_has_property(_plugin, port, LV2_URI "sampleRate"); - desc.label = slv2_port_get_name(_plugin, port); - desc.lower = slv2_port_get_minimum_value(_plugin, port); - desc.upper = slv2_port_get_maximum_value(_plugin, port); + desc.sr_dependent = slv2_port_has_property(_plugin, port, _world.srate); + desc.label = slv2_value_as_string(slv2_port_get_name(_plugin, port)); + desc.lower = min ? slv2_value_as_float(min) : 0.0f; + desc.upper = max ? slv2_value_as_float(max) : 1.0f; desc.min_unbound = false; // TODO (LV2 extension) desc.max_unbound = false; // TODO (LV2 extension) @@ -299,6 +318,10 @@ LV2Plugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) desc.largestep = delta/10.0f; } + slv2_value_free(def); + slv2_value_free(min); + slv2_value_free(max); + return 0; } @@ -307,8 +330,11 @@ string LV2Plugin::describe_parameter (Parameter which) { if (which.type() == PluginAutomation && which.id() < parameter_count()) { - return slv2_port_get_name(_plugin, - slv2_plugin_get_port_by_index(_plugin, which)); + SLV2Value name = slv2_port_get_name(_plugin, + slv2_plugin_get_port_by_index(_plugin, which)); + string ret(slv2_value_as_string(name)); + slv2_value_free(name); + return ret; } else { return "??"; } @@ -377,29 +403,29 @@ LV2Plugin::connect_and_run (BufferSet& bufs, uint32_t& in_index, uint32_t& out_i bool LV2Plugin::parameter_is_control (uint32_t param) const { - SLV2PortSignature sig = slv2_template_get_port(_template, param); - return (slv2_port_signature_get_type(sig) == SLV2_PORT_DATA_TYPE_CONTROL); + SLV2Port port = slv2_plugin_get_port_by_index(_plugin, param); + return slv2_port_is_a(_plugin, port, _world.control_class); } bool LV2Plugin::parameter_is_audio (uint32_t param) const { - SLV2PortSignature sig = slv2_template_get_port(_template, param); - return (slv2_port_signature_get_type(sig) == SLV2_PORT_DATA_TYPE_AUDIO); + SLV2Port port = slv2_plugin_get_port_by_index(_plugin, param); + return slv2_port_is_a(_plugin, port, _world.audio_class); } bool LV2Plugin::parameter_is_output (uint32_t param) const { - SLV2PortSignature sig = slv2_template_get_port(_template, param); - return (slv2_port_signature_get_direction(sig) == SLV2_PORT_DIRECTION_OUTPUT); + SLV2Port port = slv2_plugin_get_port_by_index(_plugin, param); + return slv2_port_is_a(_plugin, port, _world.output_class); } bool LV2Plugin::parameter_is_input (uint32_t param) const { - SLV2PortSignature sig = slv2_template_get_port(_template, param); - return (slv2_port_signature_get_direction(sig) == SLV2_PORT_DIRECTION_INPUT); + SLV2Port port = slv2_plugin_get_port_by_index(_plugin, param); + return slv2_port_is_a(_plugin, port, _world.input_class); } void @@ -418,9 +444,7 @@ void LV2Plugin::run (nframes_t nframes) { for (uint32_t i = 0; i < parameter_count(); ++i) { - SLV2PortSignature sig = slv2_template_get_port(_template, i); - if (slv2_port_signature_get_type(sig) == SLV2_PORT_DATA_TYPE_CONTROL - && slv2_port_signature_get_direction(sig) == SLV2_PORT_DIRECTION_INPUT) { + if (parameter_is_control(i) && parameter_is_input(i)) { _control_data[i] = _shadow_data[i]; } } @@ -472,8 +496,34 @@ LV2Plugin::latency_compute_run () deactivate (); } -LV2PluginInfo::LV2PluginInfo (void* slv2_plugin) - : _slv2_plugin(slv2_plugin) +LV2World::LV2World() + : world(slv2_world_new()) +{ + slv2_world_load_all(world); + input_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_INPUT); + output_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_OUTPUT); + control_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_CONTROL); + audio_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_AUDIO); + event_class = slv2_value_new_uri(world, SLV2_PORT_CLASS_EVENT); + in_place_broken = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "inPlaceBroken"); + integer = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "integer"); + toggled = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "toggled"); + srate = slv2_value_new_uri(world, SLV2_NAMESPACE_LV2 "sampleRate"); +} + +LV2World::~LV2World() +{ + slv2_value_free(input_class); + slv2_value_free(output_class); + slv2_value_free(control_class); + slv2_value_free(audio_class); + slv2_value_free(event_class); + slv2_value_free(in_place_broken); +} + +LV2PluginInfo::LV2PluginInfo (void* lv2_world, void* slv2_plugin) + : _lv2_world(lv2_world) + , _slv2_plugin(slv2_plugin) { } @@ -484,12 +534,11 @@ LV2PluginInfo::~LV2PluginInfo() PluginPtr LV2PluginInfo::load (Session& session) { - SLV2Plugin p = (SLV2Plugin)_slv2_plugin; - try { PluginPtr plugin; - plugin.reset (new LV2Plugin (session.engine(), session, p, session.frame_rate())); + plugin.reset (new LV2Plugin (session.engine(), session, + *(LV2World*)_lv2_world, (SLV2Plugin)_slv2_plugin, session.frame_rate())); plugin->set_info(PluginInfoPtr(new LV2PluginInfo(*this))); return plugin; @@ -503,40 +552,42 @@ LV2PluginInfo::load (Session& session) } PluginInfoList -LV2PluginInfo::discover (void* slv2_world) +LV2PluginInfo::discover (void* lv2_world) { PluginInfoList plugs; - SLV2Plugins plugins = slv2_world_get_all_plugins((SLV2World)slv2_world); + LV2World* world = (LV2World*)lv2_world; + SLV2Plugins plugins = slv2_world_get_all_plugins(world->world); for (unsigned i=0; i < slv2_plugins_size(plugins); ++i) { SLV2Plugin p = slv2_plugins_get_at(plugins, i); - LV2PluginInfoPtr info (new LV2PluginInfo(p)); + LV2PluginInfoPtr info (new LV2PluginInfo(lv2_world, p)); - info->name = slv2_plugin_get_name(p); + SLV2Value name = slv2_plugin_get_name(p); + info->name = string(slv2_value_as_string(name)); + slv2_value_free(name); SLV2PluginClass pclass = slv2_plugin_get_class(p); - info->category = slv2_plugin_class_get_label(pclass); + SLV2Value label = slv2_plugin_class_get_label(pclass); + info->category = slv2_value_as_string(label); - char* author_name = slv2_plugin_get_author_name(p); - info->creator = author_name ? string(author_name) : "Unknown"; - free(author_name); + SLV2Value author_name = slv2_plugin_get_author_name(p); + info->creator = author_name ? string(slv2_value_as_string(author_name)) : "Unknown"; + slv2_value_free(author_name); info->path = "/NOPATH"; // Meaningless for LV2 - SLV2Template io = slv2_plugin_get_template(p); - - info->n_inputs.set_audio(slv2_template_get_num_ports_of_type(io, - SLV2_PORT_DIRECTION_INPUT, SLV2_PORT_DATA_TYPE_AUDIO)); - info->n_outputs.set_audio(slv2_template_get_num_ports_of_type(io, - SLV2_PORT_DIRECTION_OUTPUT, SLV2_PORT_DATA_TYPE_AUDIO)); + info->n_inputs.set_audio(slv2_plugin_get_num_ports_of_class(p, + world->input_class, world->audio_class, NULL)); + info->n_inputs.set_midi(slv2_plugin_get_num_ports_of_class(p, + world->input_class, world->event_class, NULL)); - info->n_inputs.set_midi(slv2_template_get_num_ports_of_type(io, - SLV2_PORT_DIRECTION_INPUT, SLV2_PORT_DATA_TYPE_MIDI)); - info->n_outputs.set_midi(slv2_template_get_num_ports_of_type(io, - SLV2_PORT_DIRECTION_OUTPUT, SLV2_PORT_DATA_TYPE_MIDI)); + info->n_outputs.set_audio(slv2_plugin_get_num_ports_of_class(p, + world->output_class, world->audio_class, NULL)); + info->n_outputs.set_midi(slv2_plugin_get_num_ports_of_class(p, + world->output_class, world->event_class, NULL)); - info->unique_id = slv2_plugin_get_uri(p); + info->unique_id = slv2_value_as_uri(slv2_plugin_get_uri(p)); info->index = 0; // Meaningless for LV2 plugs.push_back (info); diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index 3a2842be7a..ea340598ac 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -933,7 +933,7 @@ MidiDiskstream::do_flush (Session::RunContext context, bool force_flush) assert(!destructive()); if (record_enabled() && _session.transport_frame() - _last_flush_frame > disk_io_chunk_frames) { - if ((!_write_source) || _write_source->write (*_capture_buf, to_write) != to_write) { + if ((!_write_source) || _write_source->midi_write (*_capture_buf, to_write) != to_write) { error << string_compose(_("MidiDiskstream %1: cannot write to disk"), _id) << endmsg; return -1; } else { diff --git a/libs/ardour/midi_region.cc b/libs/ardour/midi_region.cc index cc1ba4b2a8..b0b7e4575f 100644 --- a/libs/ardour/midi_region.cc +++ b/libs/ardour/midi_region.cc @@ -65,7 +65,7 @@ MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, nframes_t start, nfra } /* Basic MidiRegion constructor (many channels) */ -MidiRegion::MidiRegion (SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) +MidiRegion::MidiRegion (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Flag flags) : Region (srcs, start, length, name, DataType::MIDI, layer, flags) { assert(_name.find("/") == string::npos); @@ -100,7 +100,7 @@ MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, const XMLNode& node) assert(_type == DataType::MIDI); } -MidiRegion::MidiRegion (SourceList& srcs, const XMLNode& node) +MidiRegion::MidiRegion (const SourceList& srcs, const XMLNode& node) : Region (srcs, node) { if (set_state (node)) { @@ -171,7 +171,7 @@ MidiRegion::_read_at (const SourceList& srcs, MidiRingBuffer& dst, nframes_t pos boost::shared_ptr<MidiSource> src = midi_source(chan_n); src->set_note_mode(mode); - if (src->read (dst, _start + internal_offset, to_read, _position) != to_read) { + if (src->midi_read (dst, _start + internal_offset, to_read, _position) != to_read) { return 0; /* "read nothing" */ } diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc index f072c2a7ef..794959328a 100644 --- a/libs/ardour/midi_source.cc +++ b/libs/ardour/midi_source.cc @@ -101,7 +101,7 @@ MidiSource::set_state (const XMLNode& node) } nframes_t -MidiSource::read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const +MidiSource::midi_read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const { Glib::Mutex::Lock lm (_lock); if (_model) { @@ -114,7 +114,7 @@ MidiSource::read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t } nframes_t -MidiSource::write (MidiRingBuffer& dst, nframes_t cnt) +MidiSource::midi_write (MidiRingBuffer& dst, nframes_t cnt) { Glib::Mutex::Lock lm (_lock); return write_unlocked (dst, cnt); diff --git a/libs/ardour/plugin_manager.cc b/libs/ardour/plugin_manager.cc index ae5d6f52ee..6ff780a25f 100644 --- a/libs/ardour/plugin_manager.cc +++ b/libs/ardour/plugin_manager.cc @@ -110,8 +110,7 @@ PluginManager::PluginManager () } #ifdef HAVE_SLV2 - _lv2_world = slv2_world_new(); - slv2_world_load_all(_lv2_world); + _lv2_world = new LV2World(); #endif refresh (); diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index 1c79e0c438..7aadb9183f 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -296,8 +296,8 @@ PortFacade::disconnect (Port& other) int PortFacade::disconnect_all () { - int reta; - int retb; + int reta = 0; + int retb = 0; if (_ext_port) { reta = _ext_port->disconnect_all (); diff --git a/libs/ardour/quantize.cc b/libs/ardour/quantize.cc index de3ed4ef22..ccbda9711a 100644 --- a/libs/ardour/quantize.cc +++ b/libs/ardour/quantize.cc @@ -71,9 +71,9 @@ Quantize::run (boost::shared_ptr<Region> r) for (MidiModel::Notes::iterator i = model->notes().begin(); i != model->notes().end(); ++i) { const double new_time = lrint((*i)->time() / q_frames) * q_frames; - const double new_dur = (((*i)->time() != 0 && new_dur < (q_frames * 1.5)) - ? q_frames - : lrint((*i)->duration() / q_frames) * q_frames); + double new_dur = lrint((*i)->duration() / q_frames) * q_frames; + if (new_dur == 0.0) + new_dur = q_frames; (*i)->set_time(new_time); (*i)->set_duration(new_dur); diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index 6d92c0bc88..054e85cd2f 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -99,7 +99,7 @@ Region::Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length } /** Basic Region constructor (many sources) */ -Region::Region (SourceList& srcs, nframes_t start, nframes_t length, const string& name, DataType type, layer_t layer, Region::Flag flags) +Region::Region (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, DataType type, layer_t layer, Region::Flag flags) : Automatable(srcs.front()->session(), name) , _type(type) , _flags(flags) @@ -117,13 +117,13 @@ Region::Region (SourceList& srcs, nframes_t start, nframes_t length, const strin set<boost::shared_ptr<Source> > unique_srcs; - for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) { + for (SourceList::const_iterator i=srcs.begin(); i != srcs.end(); ++i) { _sources.push_back (*i); (*i)->GoingAway.connect (bind (mem_fun (*this, &Region::source_deleted), (*i))); unique_srcs.insert (*i); } - for (SourceList::iterator i = srcs.begin(); i != srcs.end(); ++i) { + for (SourceList::const_iterator i = srcs.begin(); i != srcs.end(); ++i) { _master_sources.push_back (*i); if (unique_srcs.find (*i) == unique_srcs.end()) { (*i)->GoingAway.connect (bind (mem_fun (*this, &Region::source_deleted), (*i))); @@ -222,7 +222,7 @@ Region::Region (boost::shared_ptr<const Region> other) assert(_sources.size() > 0); } -Region::Region (SourceList& srcs, const XMLNode& node) +Region::Region (const SourceList& srcs, const XMLNode& node) : Automatable(srcs.front()->session(), X_("error: XML did not reset this")) , _type(DataType::NIL) // to be loaded from XML , _flags(Flag(0)) @@ -239,13 +239,13 @@ Region::Region (SourceList& srcs, const XMLNode& node) { set<boost::shared_ptr<Source> > unique_srcs; - for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) { + for (SourceList::const_iterator i=srcs.begin(); i != srcs.end(); ++i) { _sources.push_back (*i); (*i)->GoingAway.connect (bind (mem_fun (*this, &Region::source_deleted), (*i))); unique_srcs.insert (*i); } - for (SourceList::iterator i = srcs.begin(); i != srcs.end(); ++i) { + for (SourceList::const_iterator i = srcs.begin(); i != srcs.end(); ++i) { _master_sources.push_back (*i); if (unique_srcs.find (*i) == unique_srcs.end()) { (*i)->GoingAway.connect (bind (mem_fun (*this, &Region::source_deleted), (*i))); @@ -974,9 +974,9 @@ Region::state (bool full_state) node->add_property ("length", buf); snprintf (buf, sizeof (buf), "%u", _position); node->add_property ("position", buf); - snprintf (buf, sizeof (buf), "%lu", _ancestral_start); + snprintf (buf, sizeof (buf), "%Ld", _ancestral_start); node->add_property ("ancestral-start", buf); - snprintf (buf, sizeof (buf), "%lu", _ancestral_length); + snprintf (buf, sizeof (buf), "%Ld", _ancestral_length); node->add_property ("ancestral-length", buf); snprintf (buf, sizeof (buf), "%.12g", _stretch); node->add_property ("stretch", buf); diff --git a/libs/ardour/region_factory.cc b/libs/ardour/region_factory.cc index a0aa3be759..bd4b0873a7 100644 --- a/libs/ardour/region_factory.cc +++ b/libs/ardour/region_factory.cc @@ -106,7 +106,7 @@ RegionFactory::create (Session& session, XMLNode& node, bool yn) } boost::shared_ptr<Region> -RegionFactory::create (SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Region::Flag flags, bool announce) +RegionFactory::create (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Region::Flag flags, bool announce) { if (srcs.empty()) { return boost::shared_ptr<Region>(); diff --git a/libs/ardour/resampled_source.cc b/libs/ardour/resampled_source.cc index 8330196d8a..b5d23fb4a2 100644 --- a/libs/ardour/resampled_source.cc +++ b/libs/ardour/resampled_source.cc @@ -38,7 +38,7 @@ ResampledImportableSource::ResampledImportableSource (const std::string& path, /* Initialize the sample rate converter. */ - int src_type; + int src_type = SRC_LINEAR; switch (srcq) { case SrcBest: diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index dbec881b0b..c6ace87b73 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -33,6 +33,7 @@ #include <glibmm/thread.h> #include <glibmm/miscutils.h> +#include <glibmm/fileutils.h> #include <pbd/error.h> #include <glibmm/thread.h> @@ -148,7 +149,8 @@ Session::Session (AudioEngine &eng, first_stage_init (fullpath, snapshot_name); - new_session = !g_file_test (_path.c_str(), GFileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + new_session = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + if (new_session) { if (create (new_session, mix_template, compute_initial_length())) { destroy (); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 4b7e0875ee..d18b9cedd7 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -432,19 +432,14 @@ Session::setup_raid_path (string path) } int -Session::create (bool& new_session, const string& mix_template, nframes_t initial_length) +Session::ensure_subdirs () { string dir; - if (g_mkdir_with_parents (_path.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session dir \"%1\" (%2)"), _path, strerror (errno)) << endmsg; - return -1; - } - dir = session_directory().peak_path().to_string(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session peakfile dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } @@ -465,17 +460,39 @@ Session::create (bool& new_session, const string& mix_template, nframes_t initia dir = session_directory().dead_sound_path().to_string(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session dead sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + error << string_compose(_("Session: cannot create session dead sounds folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; return -1; } dir = session_directory().export_path().to_string(); if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session export dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + error << string_compose(_("Session: cannot create session export folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + dir = analysis_dir (); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session analysis folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + return 0; +} + +int +Session::create (bool& new_session, const string& mix_template, nframes_t initial_length) +{ + + if (g_mkdir_with_parents (_path.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session folder \"%1\" (%2)"), _path, strerror (errno)) << endmsg; return -1; } + if (ensure_subdirs ()) { + return -1; + } /* check new_session so we don't overwrite an existing one */ @@ -524,7 +541,6 @@ Session::create (bool& new_session, const string& mix_template, nframes_t initia _state_of_the_state = Clean; - save_state (""); return 0; @@ -1979,6 +1995,14 @@ Session::automation_dir () const return res; } +string +Session::analysis_dir () const +{ + string res = _path; + res += "analysis/"; + return res; +} + int Session::load_bundles (XMLNode const & node) { @@ -2544,7 +2568,7 @@ Session::cleanup_sources (Session::cleanup_report& rep) newpath += dead_sound_dir_name; if (g_mkdir_with_parents (newpath.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session peakfile dir \"%1\" (%2)"), newpath, strerror (errno)) << endmsg; + error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), newpath, strerror (errno)) << endmsg; return -1; } diff --git a/libs/ardour/source_factory.cc b/libs/ardour/source_factory.cc index 661beef40b..02c35d2188 100644 --- a/libs/ardour/source_factory.cc +++ b/libs/ardour/source_factory.cc @@ -40,7 +40,7 @@ using namespace PBD; sigc::signal<void,boost::shared_ptr<Source> > SourceFactory::SourceCreated; Glib::Cond* SourceFactory::PeaksToBuild; -Glib::StaticMutex SourceFactory::peak_building_lock; +Glib::StaticMutex SourceFactory::peak_building_lock = GLIBMM_STATIC_MUTEX_INIT; std::list<boost::weak_ptr<AudioSource> > SourceFactory::files_with_peaks; static void diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 780f5c6a5d..b2865fc399 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -268,7 +268,7 @@ TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when) section.set_start (corrected); metrics->sort (cmp); - timestamp_metrics (); + timestamp_metrics (true); return 0; } @@ -345,16 +345,22 @@ TempoMap::remove_meter (const MeterSection& tempo) } void -TempoMap::do_insert (MetricSection* section) +TempoMap::do_insert (MetricSection* section, bool with_bbt) { Metrics::iterator i; for (i = metrics->begin(); i != metrics->end(); ++i) { - if ((*i)->start() < section->start()) { - continue; + if (with_bbt) { + if ((*i)->start() < section->start()) { + continue; + } + } else { + if ((*i)->frame() < section->frame()) { + continue; + } } - + metrics->insert (i, section); break; } @@ -363,7 +369,7 @@ TempoMap::do_insert (MetricSection* section) metrics->insert (metrics->end(), section); } - timestamp_metrics (); + timestamp_metrics (with_bbt); } void @@ -376,7 +382,18 @@ TempoMap::add_tempo (const Tempo& tempo, BBT_Time where) where.ticks = 0; - do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type())); + do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true); + } + + StateChanged (Change (0)); +} + +void +TempoMap::add_tempo (const Tempo& tempo, nframes_t where) +{ + { + Glib::RWLock::WriterLock lm (lock); + do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false); } StateChanged (Change (0)); @@ -399,7 +416,7 @@ TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement) *((Tempo *) ts) = replacement; replaced = true; - timestamp_metrics (); + timestamp_metrics (true); break; } } @@ -432,7 +449,18 @@ TempoMap::add_meter (const Meter& meter, BBT_Time where) where.ticks = 0; - do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor())); + do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true); + } + + StateChanged (Change (0)); +} + +void +TempoMap::add_meter (const Meter& meter, nframes_t where) +{ + { + Glib::RWLock::WriterLock lm (lock); + do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false); } StateChanged (Change (0)); @@ -454,7 +482,7 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) *((Meter*) ms) = replacement; replaced = true; - timestamp_metrics (); + timestamp_metrics (true); break; } } @@ -465,6 +493,49 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) } } +void +TempoMap::change_existing_tempo_at (nframes_t where, double beats_per_minute, double note_type) +{ + Tempo newtempo (beats_per_minute, note_type); + + TempoSection* prev; + TempoSection* first; + Metrics::iterator i; + + /* find the TempoSection immediately preceding "where" + */ + + for (first = 0, i = metrics->begin(), prev = 0; i != metrics->end(); ++i) { + + if ((*i)->frame() > where) { + break; + } + + TempoSection* t; + + if ((t = dynamic_cast<TempoSection*>(*i)) != 0) { + if (!first) { + first = t; + } + prev = t; + } + } + + if (!prev) { + if (!first) { + error << string_compose (_("no tempo sections defined in tempo map - cannot change tempo @ %1"), where) << endmsg; + return; + } + + prev = first; + } + + /* reset */ + + *((Tempo*)prev) = newtempo; + StateChanged (Change (0)); +} + const MeterSection& TempoMap::first_meter () const { @@ -498,43 +569,84 @@ TempoMap::first_tempo () const } void -TempoMap::timestamp_metrics () +TempoMap::timestamp_metrics (bool use_bbt) { Metrics::iterator i; const Meter* meter; const Tempo* tempo; Meter *m; Tempo *t; - nframes_t current; - 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(); + if (use_bbt) { - section_frames = count_frames_between_metrics (*meter, *tempo, start, end); + nframes_t current = 0; + nframes_t section_frames; + BBT_Time start; + BBT_Time end; - current += section_frames; + for (i = metrics->begin(); i != metrics->end(); ++i) { + + end = (*i)->start(); + + section_frames = count_frames_between_metrics (*meter, *tempo, start, end); + + current += section_frames; + + start = end; + + (*i)->set_frame (current); + + if ((t = dynamic_cast<TempoSection*>(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) { + meter = m; + } else { + fatal << _("programming error: unhandled MetricSection type") << endmsg; + /*NOTREACHED*/ + } + } - start = end; + } else { - (*i)->set_frame (current); + bool first = true; - if ((t = dynamic_cast<TempoSection*>(*i)) != 0) { - tempo = t; - } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) { - meter = m; - } else { - fatal << _("programming error: unhandled MetricSection type") << endmsg; - /*NOTREACHED*/ + for (i = metrics->begin(); i != metrics->end(); ++i) { + + BBT_Time bbt; + + bbt_time_with_metric ((*i)->frame(), bbt, Metric (*meter, *tempo)); + + // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; + + if (first) { + first = false; + } else { + if (bbt.beats != 1 || bbt.ticks != 0) { + bbt.bars += 1; + bbt.beats = 1; + bbt.ticks = 0; + } + } + + // cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl; + + (*i)->set_start (bbt); + + if ((t = dynamic_cast<TempoSection*>(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) { + meter = m; + } else { + fatal << _("programming error: unhandled MetricSection type") << endmsg; + /*NOTREACHED*/ + } } } + + // dump (cerr); } TempoMap::Metric @@ -666,17 +778,15 @@ TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const Metric& me 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 + /* for this to work with fractional measure types, start and end have to be "legal" BBT types, + that means that the beats and ticks should be inside a bar */ - nframes_t frames = 0; nframes_t start_frame = 0; nframes_t end_frame = 0; - Metric m = metric_at(start); + Metric m = metric_at (start); uint32_t bar_offset = start.bars - m.start().bars; @@ -939,67 +1049,6 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num) } return frame_time (the_beat); - - - - /***************************** - XXX just keeping this for reference - - TempoMap::BBTPointList::iterator i; - TempoMap::BBTPointList *more_zoomed_bbt_points; - nframes_t frame_one_beats_worth; - nframes_t pos = 0; - nframes_t next_pos = 0 ; - double tempo = 1; - double frames_one_subdivisions_worth; - bool fr_has_changed = false; - - int n; - - frame_one_beats_worth = (nframes_t) ::floor ((double) _frame_rate * 60 / 20 ); //one beat @ 20 bpm - { - Glib::RWLock::ReaderLock lm (lock); - 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 = (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 ; - - ******************************/ - - } nframes_t @@ -1051,6 +1100,12 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) } + /* + cerr << "for " << frame << " round to " << bbt << " using " + << metric.start() + << endl; + */ + return metric.frame() + count_frames_between (metric.start(), bbt); } @@ -1148,6 +1203,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const if (beat == 1) { if (current >= lower) { + // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl; points->push_back (BBTPoint (*meter, *tempo,(nframes_t)rint(current), Bar, bar, 1)); } @@ -1159,6 +1215,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const while (beat <= ceil( beats_per_bar) && beat_frame < limit) { if (beat_frame >= lower) { + // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl; points->push_back (BBTPoint (*meter, *tempo, (nframes_t) rint(beat_frame), Beat, bar, beat)); } beat_frame += beat_frames; @@ -1167,7 +1224,7 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat++; } - if (beat > ceil(beats_per_bar) ) { + if (beat > ceil(beats_per_bar) || i != metrics->end()) { /* we walked an entire bar. its important to move `current' forward @@ -1185,10 +1242,15 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const so we subtract the possible extra fraction from the current */ - current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); + if (beat > ceil (beats_per_bar)) { + /* next bar goes where the numbers suggest */ + current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); + } else { + /* next bar goes where the next metric is */ + current = limit; + } bar++; beat = 1; - } } @@ -1225,6 +1287,33 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const return points; } +const TempoSection& +TempoMap::tempo_section_at (nframes_t frame) +{ + Glib::RWLock::ReaderLock lm (lock); + Metrics::iterator i; + TempoSection* prev = 0; + + for (i = metrics->begin(); i != metrics->end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast<TempoSection*> (*i)) != 0) { + + if ((*i)->frame() > frame) { + break; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + } + + return *prev; +} + const Tempo& TempoMap::tempo_at (nframes_t frame) { @@ -1303,7 +1392,7 @@ TempoMap::set_state (const XMLNode& node) MetricSectionSorter cmp; metrics->sort (cmp); - timestamp_metrics (); + timestamp_metrics (true); } } diff --git a/libs/ardour/transient_detector.cc b/libs/ardour/transient_detector.cc new file mode 100644 index 0000000000..b85700dd90 --- /dev/null +++ b/libs/ardour/transient_detector.cc @@ -0,0 +1,61 @@ +#include <ardour/transient_detector.h> + +#include "i18n.h" + +using namespace Vamp; +using namespace ARDOUR; +using namespace std; + +TransientDetector::TransientDetector (float sr) + : AudioAnalyser (sr, X_("libardourvampplugins:percussiononsets")) +{ +} + +TransientDetector::~TransientDetector() +{ +} + +int +TransientDetector::run (const std::string& path, Readable* src, uint32_t channel, vector<nframes64_t>& results) +{ + current_results = &results; + int ret = analyse (path, src, channel); + current_results = 0; + return ret; +} + +int +TransientDetector::use_features (Plugin::FeatureSet& features, ostream* out) +{ + const Plugin::FeatureList& fl (features[0]); + + for (Plugin::FeatureList::const_iterator f = fl.begin(); f != fl.end(); ++f) { + + if ((*f).hasTimestamp) { + + if (out) { + (*out) << (*f).timestamp.toString() << endl; + } + + current_results->push_back (RealTime::realTime2Frame ((*f).timestamp, (nframes_t) floor(sample_rate))); + } + } + + return 0; +} + +void +TransientDetector::set_threshold (float val) +{ + if (plugin) { + plugin->setParameter ("threshold", val); + } +} + +void +TransientDetector::set_sensitivity (float val) +{ + if (plugin) { + plugin->setParameter ("sensitivity", val); + } +} diff --git a/libs/gtkmm2/gtk/SConscript b/libs/gtkmm2/gtk/SConscript index fe0fc610cf..d4fc325c29 100644 --- a/libs/gtkmm2/gtk/SConscript +++ b/libs/gtkmm2/gtk/SConscript @@ -11,6 +11,9 @@ gtkmm2 = env.Copy() gtkmm2.Merge([libraries['glibmm2'], libraries['gtk2'], libraries['sigc2'], libraries['pangomm'], libraries['atkmm'], libraries['gdkmm2'], libraries['cairomm'], libraries['gtk2-unix-print'] ]) gtkmm2.Append(CXXFLAGS = ['-DGLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED', '-DGLIBMM_PROPERTIES_ENABLED', '-DGLIBMM_EXCEPTIONS_ENABLED']) +if gtkmm2['IS_OSX']: + gtkmm2.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048") + libgtkmm2 = gtkmm2.SharedLibrary('gtkmm2', gtkmm2_files) Default(libgtkmm2) diff --git a/libs/surfaces/mackie/SConscript b/libs/surfaces/mackie/SConscript index 7bd1357cb3..4590279807 100644 --- a/libs/surfaces/mackie/SConscript +++ b/libs/surfaces/mackie/SConscript @@ -20,6 +20,9 @@ mackie.Append(CXXFLAGS="-DLIBSIGC_DISABLE_DEPRECATED") mackie.Append(PACKAGE = domain) mackie.Append(POTFILE = domain + '.pot') +if mackie['IS_OSX']: + mackie.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048") + mackie_files=Split(""" interface.cc midi_byte_array.cc diff --git a/libs/vamp-plugins/AmplitudeFollower.cpp b/libs/vamp-plugins/AmplitudeFollower.cpp new file mode 100644 index 0000000000..7023297d33 --- /dev/null +++ b/libs/vamp-plugins/AmplitudeFollower.cpp @@ -0,0 +1,247 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Dan Stowell. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "AmplitudeFollower.h" + +#include <cmath> + +#include <string> +#include <vector> +#include <iostream> + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +/** + * An implementation of SuperCollider's amplitude-follower algorithm + * as a simple Vamp plugin. + */ + +AmplitudeFollower::AmplitudeFollower(float inputSampleRate) : + Plugin(inputSampleRate), + m_stepSize(0), + m_previn(0.0f), + m_clampcoef(0.01f), + m_relaxcoef(0.01f) +{ +} + +AmplitudeFollower::~AmplitudeFollower() +{ +} + +string +AmplitudeFollower::getIdentifier() const +{ + return "amplitudefollower"; +} + +string +AmplitudeFollower::getName() const +{ + return "Amplitude Follower"; +} + +string +AmplitudeFollower::getDescription() const +{ + return "Track the amplitude of the audio signal"; +} + +string +AmplitudeFollower::getMaker() const +{ + return "Vamp SDK Example Plugins"; +} + +int +AmplitudeFollower::getPluginVersion() const +{ + return 1; +} + +string +AmplitudeFollower::getCopyright() const +{ + return "Code copyright 2006 Dan Stowell; method from SuperCollider. Freely redistributable (BSD license)"; +} + +bool +AmplitudeFollower::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = std::min(stepSize, blockSize); + + // Translate the coefficients + // from their "convenient" 60dB convergence-time values + // to real coefficients + m_clampcoef = m_clampcoef==0.0 ? 0.0 : exp(log(0.1)/(m_clampcoef * m_inputSampleRate)); + m_relaxcoef = m_relaxcoef==0.0 ? 0.0 : exp(log(0.1)/(m_relaxcoef * m_inputSampleRate)); + + return true; +} + +void +AmplitudeFollower::reset() +{ + m_previn = 0.0f; +} + +AmplitudeFollower::OutputList +AmplitudeFollower::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor sca; + sca.identifier = "amplitude"; + sca.name = "Amplitude"; + sca.description = ""; + sca.unit = "V"; + sca.hasFixedBinCount = true; + sca.binCount = 1; + sca.hasKnownExtents = false; + sca.isQuantized = false; + sca.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(sca); + + return list; +} + +AmplitudeFollower::ParameterList +AmplitudeFollower::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor att; + att.identifier = "attack"; + att.name = "Attack time"; + att.description = ""; + att.unit = "s"; + att.minValue = 0.0f; + att.maxValue = 1.f; + att.defaultValue = 0.01f; + att.isQuantized = false; + + list.push_back(att); + + ParameterDescriptor dec; + dec.identifier = "release"; + dec.name = "Release time"; + dec.description = ""; + dec.unit = "s"; + dec.minValue = 0.0f; + dec.maxValue = 1.f; + dec.defaultValue = 0.01f; + dec.isQuantized = false; + + list.push_back(dec); + + return list; +} + +void AmplitudeFollower::setParameter(std::string paramid, float newval) +{ + if (paramid == "attack") { + m_clampcoef = newval; + } else if (paramid == "release") { + m_relaxcoef = newval; + } +} + +float AmplitudeFollower::getParameter(std::string paramid) const +{ + if (paramid == "attack") { + return m_clampcoef; + } else if (paramid == "release") { + return m_relaxcoef; + } + + return 0.0f; +} + +AmplitudeFollower::FeatureSet +AmplitudeFollower::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (m_stepSize == 0) { + cerr << "ERROR: AmplitudeFollower::process: " + << "AmplitudeFollower has not been initialised" + << endl; + return FeatureSet(); + } + + float previn = m_previn; + + FeatureSet returnFeatures; + + float val; + float peak = 0.0f; + + for (size_t i = 0; i < m_stepSize; ++i) { + + val = fabs(inputBuffers[0][i]); + + if (val < previn) { + val = val + (previn - val) * m_relaxcoef; + } else { + val = val + (previn - val) * m_clampcoef; + } + + if (val > peak) peak = val; + previn = val; + } + + m_previn = previn; + + // Now store the "feature" (peak amp) for this sample + Feature feature; + feature.hasTimestamp = false; + feature.values.push_back(peak); + returnFeatures[0].push_back(feature); + + return returnFeatures; +} + +AmplitudeFollower::FeatureSet +AmplitudeFollower::getRemainingFeatures() +{ + return FeatureSet(); +} + diff --git a/libs/vamp-plugins/AmplitudeFollower.h b/libs/vamp-plugins/AmplitudeFollower.h new file mode 100644 index 0000000000..6c3426e324 --- /dev/null +++ b/libs/vamp-plugins/AmplitudeFollower.h @@ -0,0 +1,84 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Dan Stowell. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _AMPLITUDE_FOLLOWER_PLUGIN_H_ +#define _AMPLITUDE_FOLLOWER_PLUGIN_H_ + +#include "vamp-sdk/Plugin.h" + +/** + * Example plugin implementing the SuperCollider amplitude follower + * function. + */ + +class AmplitudeFollower : public Vamp::Plugin +{ +public: + AmplitudeFollower(float inputSampleRate); + virtual ~AmplitudeFollower(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + OutputList getOutputDescriptors() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string paramid) const; + void setParameter(std::string paramid, float newval); + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + size_t m_stepSize; + float m_previn; + float m_clampcoef; + float m_relaxcoef; +}; + + +#endif diff --git a/libs/vamp-plugins/PercussionOnsetDetector.cpp b/libs/vamp-plugins/PercussionOnsetDetector.cpp new file mode 100644 index 0000000000..447eb19a28 --- /dev/null +++ b/libs/vamp-plugins/PercussionOnsetDetector.cpp @@ -0,0 +1,285 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "PercussionOnsetDetector.h" + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +#include <cmath> + + +PercussionOnsetDetector::PercussionOnsetDetector(float inputSampleRate) : + Plugin(inputSampleRate), + m_stepSize(0), + m_blockSize(0), + m_threshold(3), + m_sensitivity(40), + m_priorMagnitudes(0), + m_dfMinus1(0), + m_dfMinus2(0) +{ +} + +PercussionOnsetDetector::~PercussionOnsetDetector() +{ + delete[] m_priorMagnitudes; +} + +string +PercussionOnsetDetector::getIdentifier() const +{ + return "percussiononsets"; +} + +string +PercussionOnsetDetector::getName() const +{ + return "Simple Percussion Onset Detector"; +} + +string +PercussionOnsetDetector::getDescription() const +{ + return "Detect percussive note onsets by identifying broadband energy rises"; +} + +string +PercussionOnsetDetector::getMaker() const +{ + return "Vamp SDK Example Plugins"; +} + +int +PercussionOnsetDetector::getPluginVersion() const +{ + return 2; +} + +string +PercussionOnsetDetector::getCopyright() const +{ + return "Code copyright 2006 Queen Mary, University of London, after Dan Barry et al 2005. Freely redistributable (BSD license)"; +} + +size_t +PercussionOnsetDetector::getPreferredStepSize() const +{ + return 0; +} + +size_t +PercussionOnsetDetector::getPreferredBlockSize() const +{ + return 1024; +} + +bool +PercussionOnsetDetector::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = stepSize; + m_blockSize = blockSize; + + m_priorMagnitudes = new float[m_blockSize/2]; + + for (size_t i = 0; i < m_blockSize/2; ++i) { + m_priorMagnitudes[i] = 0.f; + } + + m_dfMinus1 = 0.f; + m_dfMinus2 = 0.f; + + return true; +} + +void +PercussionOnsetDetector::reset() +{ + for (size_t i = 0; i < m_blockSize/2; ++i) { + m_priorMagnitudes[i] = 0.f; + } + + m_dfMinus1 = 0.f; + m_dfMinus2 = 0.f; +} + +PercussionOnsetDetector::ParameterList +PercussionOnsetDetector::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor d; + d.identifier = "threshold"; + d.name = "Energy rise threshold"; + d.description = "Energy rise within a frequency bin necessary to count toward broadband total"; + d.unit = "dB"; + d.minValue = 0; + d.maxValue = 20; + d.defaultValue = 3; + d.isQuantized = false; + list.push_back(d); + + d.identifier = "sensitivity"; + d.name = "Sensitivity"; + d.description = "Sensitivity of peak detector applied to broadband detection function"; + d.unit = "%"; + d.minValue = 0; + d.maxValue = 100; + d.defaultValue = 40; + d.isQuantized = false; + list.push_back(d); + + return list; +} + +float +PercussionOnsetDetector::getParameter(std::string id) const +{ + if (id == "threshold") return m_threshold; + if (id == "sensitivity") return m_sensitivity; + return 0.f; +} + +void +PercussionOnsetDetector::setParameter(std::string id, float value) +{ + if (id == "threshold") { + if (value < 0) value = 0; + if (value > 20) value = 20; + m_threshold = value; + } else if (id == "sensitivity") { + if (value < 0) value = 0; + if (value > 100) value = 100; + m_sensitivity = value; + } +} + +PercussionOnsetDetector::OutputList +PercussionOnsetDetector::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "onsets"; + d.name = "Onsets"; + d.description = "Percussive note onset locations"; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 0; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::VariableSampleRate; + d.sampleRate = m_inputSampleRate; + list.push_back(d); + + d.identifier = "detectionfunction"; + d.name = "Detection Function"; + d.description = "Broadband energy rise detection function"; + d.binCount = 1; + d.isQuantized = true; + d.quantizeStep = 1.0; + d.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(d); + + return list; +} + +PercussionOnsetDetector::FeatureSet +PercussionOnsetDetector::process(const float *const *inputBuffers, + Vamp::RealTime ts) +{ + if (m_stepSize == 0) { + cerr << "ERROR: PercussionOnsetDetector::process: " + << "PercussionOnsetDetector has not been initialised" + << endl; + return FeatureSet(); + } + + int count = 0; + + for (size_t i = 1; i < m_blockSize/2; ++i) { + + float real = inputBuffers[0][i*2]; + float imag = inputBuffers[0][i*2 + 1]; + + float sqrmag = real * real + imag * imag; + + if (m_priorMagnitudes[i] > 0.f) { + float diff = 10.f * log10f(sqrmag / m_priorMagnitudes[i]); + +// std::cout << "i=" << i << ", mag=" << mag << ", prior=" << m_priorMagnitudes[i] << ", diff=" << diff << ", threshold=" << m_threshold << std::endl; + + if (diff >= m_threshold) ++count; + } + + m_priorMagnitudes[i] = sqrmag; + } + + FeatureSet returnFeatures; + + Feature detectionFunction; + detectionFunction.hasTimestamp = false; + detectionFunction.values.push_back(count); + returnFeatures[1].push_back(detectionFunction); + + if (m_dfMinus2 < m_dfMinus1 && + m_dfMinus1 >= count && + m_dfMinus1 > ((100 - m_sensitivity) * m_blockSize) / 200) { + + Feature onset; + onset.hasTimestamp = true; + onset.timestamp = ts - Vamp::RealTime::frame2RealTime + (m_stepSize, lrintf(m_inputSampleRate)); + returnFeatures[0].push_back(onset); + } + + m_dfMinus2 = m_dfMinus1; + m_dfMinus1 = count; + + return returnFeatures; +} + +PercussionOnsetDetector::FeatureSet +PercussionOnsetDetector::getRemainingFeatures() +{ + return FeatureSet(); +} + diff --git a/libs/vamp-plugins/PercussionOnsetDetector.h b/libs/vamp-plugins/PercussionOnsetDetector.h new file mode 100644 index 0000000000..d54c0cfa13 --- /dev/null +++ b/libs/vamp-plugins/PercussionOnsetDetector.h @@ -0,0 +1,90 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _PERCUSSION_ONSET_DETECTOR_PLUGIN_H_ +#define _PERCUSSION_ONSET_DETECTOR_PLUGIN_H_ + +#include "vamp-sdk/Plugin.h" + +/** + * Example plugin that detects percussive events. + */ + +class PercussionOnsetDetector : public Vamp::Plugin +{ +public: + PercussionOnsetDetector(float inputSampleRate); + virtual ~PercussionOnsetDetector(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return FrequencyDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string id) const; + void setParameter(std::string id, float value); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + size_t m_stepSize; + size_t m_blockSize; + + float m_threshold; + float m_sensitivity; + float *m_priorMagnitudes; + float m_dfMinus1; + float m_dfMinus2; +}; + + +#endif diff --git a/libs/vamp-plugins/SConscript b/libs/vamp-plugins/SConscript new file mode 100644 index 0000000000..592fca3768 --- /dev/null +++ b/libs/vamp-plugins/SConscript @@ -0,0 +1,26 @@ +# -*- python -*- + +import os +import os.path +import glob + +plugin_files = glob.glob ("*.cpp") + +Import('env install_prefix libraries') +vampplugs = env.Copy() + +vampplugs.Append (CPPATH='#libs/vamp-sdk/vamp', CXXFLAGS="-Ilibs/vamp-sdk") +vampplugs.Merge ([libraries['vamp'], + libraries['vamphost'] + ]) + +libvampplugins = vampplugs.SharedLibrary('ardourvampplugins', plugin_files) + +Default(libvampplugins) + +env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2', 'vamp'), libvampplugins)) + +env.Alias('tarball', env.Distribute (env['DISTTREE'], + [ 'SConscript', 'COPYING', 'README' ] + + plugin_files + + glob.glob('*.h'))) diff --git a/libs/vamp-plugins/SpectralCentroid.cpp b/libs/vamp-plugins/SpectralCentroid.cpp new file mode 100644 index 0000000000..82d80b8100 --- /dev/null +++ b/libs/vamp-plugins/SpectralCentroid.cpp @@ -0,0 +1,188 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "SpectralCentroid.h" + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +#include <cmath> + + +SpectralCentroid::SpectralCentroid(float inputSampleRate) : + Plugin(inputSampleRate), + m_stepSize(0), + m_blockSize(0) +{ +} + +SpectralCentroid::~SpectralCentroid() +{ +} + +string +SpectralCentroid::getIdentifier() const +{ + return "spectralcentroid"; +} + +string +SpectralCentroid::getName() const +{ + return "Spectral Centroid"; +} + +string +SpectralCentroid::getDescription() const +{ + return "Calculate the centroid frequency of the spectrum of the input signal"; +} + +string +SpectralCentroid::getMaker() const +{ + return "Vamp SDK Example Plugins"; +} + +int +SpectralCentroid::getPluginVersion() const +{ + return 2; +} + +string +SpectralCentroid::getCopyright() const +{ + return "Freely redistributable (BSD license)"; +} + +bool +SpectralCentroid::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = stepSize; + m_blockSize = blockSize; + + return true; +} + +void +SpectralCentroid::reset() +{ +} + +SpectralCentroid::OutputList +SpectralCentroid::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "logcentroid"; + d.name = "Log Frequency Centroid"; + d.description = "Centroid of the log weighted frequency spectrum"; + d.unit = "Hz"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(d); + + d.identifier = "linearcentroid"; + d.name = "Linear Frequency Centroid"; + d.description = "Centroid of the linear frequency spectrum"; + list.push_back(d); + + return list; +} + +SpectralCentroid::FeatureSet +SpectralCentroid::process(const float *const *inputBuffers, Vamp::RealTime) +{ + if (m_stepSize == 0) { + cerr << "ERROR: SpectralCentroid::process: " + << "SpectralCentroid has not been initialised" + << endl; + return FeatureSet(); + } + + double numLin = 0.0, numLog = 0.0, denom = 0.0; + + for (size_t i = 1; i <= m_blockSize/2; ++i) { + double freq = (double(i) * m_inputSampleRate) / m_blockSize; + double real = inputBuffers[0][i*2]; + double imag = inputBuffers[0][i*2 + 1]; + double power = sqrt(real * real + imag * imag) / (m_blockSize/2); + numLin += freq * power; + numLog += log10f(freq) * power; + denom += power; + } + + FeatureSet returnFeatures; + +// std::cerr << "power " << denom << ", block size " << m_blockSize << std::endl; + + if (denom != 0.0) { + float centroidLin = float(numLin / denom); + float centroidLog = powf(10, float(numLog / denom)); + + Feature feature; + feature.hasTimestamp = false; + if (!std::isnan(centroidLog) && !std::isinf(centroidLog)) { + feature.values.push_back(centroidLog); + } + returnFeatures[0].push_back(feature); + + feature.values.clear(); + if (!std::isnan(centroidLin) && !std::isinf(centroidLin)) { + feature.values.push_back(centroidLin); + } + returnFeatures[1].push_back(feature); + } + + return returnFeatures; +} + +SpectralCentroid::FeatureSet +SpectralCentroid::getRemainingFeatures() +{ + return FeatureSet(); +} + diff --git a/libs/vamp-plugins/SpectralCentroid.h b/libs/vamp-plugins/SpectralCentroid.h new file mode 100644 index 0000000000..02cc8d981d --- /dev/null +++ b/libs/vamp-plugins/SpectralCentroid.h @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _SPECTRAL_CENTROID_PLUGIN_H_ +#define _SPECTRAL_CENTROID_PLUGIN_H_ + +#include "vamp-sdk/Plugin.h" + +/** + * Example plugin that calculates the centre of gravity of the + * frequency domain representation of each block of audio. + */ + +class SpectralCentroid : public Vamp::Plugin +{ +public: + SpectralCentroid(float inputSampleRate); + virtual ~SpectralCentroid(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return FrequencyDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + size_t m_stepSize; + size_t m_blockSize; +}; + + +#endif diff --git a/libs/vamp-plugins/ZeroCrossing.cpp b/libs/vamp-plugins/ZeroCrossing.cpp new file mode 100644 index 0000000000..4b678e3f8f --- /dev/null +++ b/libs/vamp-plugins/ZeroCrossing.cpp @@ -0,0 +1,194 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "ZeroCrossing.h" + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + + +ZeroCrossing::ZeroCrossing(float inputSampleRate) : + Plugin(inputSampleRate), + m_stepSize(0), + m_previousSample(0.0f) +{ +} + +ZeroCrossing::~ZeroCrossing() +{ +} + +string +ZeroCrossing::getIdentifier() const +{ + return "zerocrossing"; +} + +string +ZeroCrossing::getName() const +{ + return "Zero Crossings"; +} + +string +ZeroCrossing::getDescription() const +{ + return "Detect and count zero crossing points"; +} + +string +ZeroCrossing::getMaker() const +{ + return "Vamp SDK Example Plugins"; +} + +int +ZeroCrossing::getPluginVersion() const +{ + return 2; +} + +string +ZeroCrossing::getCopyright() const +{ + return "Freely redistributable (BSD license)"; +} + +bool +ZeroCrossing::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = std::min(stepSize, blockSize); + + return true; +} + +void +ZeroCrossing::reset() +{ + m_previousSample = 0.0f; +} + +ZeroCrossing::OutputList +ZeroCrossing::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor zc; + zc.identifier = "counts"; + zc.name = "Zero Crossing Counts"; + zc.description = "The number of zero crossing points per processing block"; + zc.unit = "crossings"; + zc.hasFixedBinCount = true; + zc.binCount = 1; + zc.hasKnownExtents = false; + zc.isQuantized = true; + zc.quantizeStep = 1.0; + zc.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(zc); + + zc.identifier = "zerocrossings"; + zc.name = "Zero Crossings"; + zc.description = "The locations of zero crossing points"; + zc.unit = ""; + zc.hasFixedBinCount = true; + zc.binCount = 0; + zc.sampleType = OutputDescriptor::VariableSampleRate; + zc.sampleRate = m_inputSampleRate; + list.push_back(zc); + + return list; +} + +ZeroCrossing::FeatureSet +ZeroCrossing::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (m_stepSize == 0) { + cerr << "ERROR: ZeroCrossing::process: " + << "ZeroCrossing has not been initialised" + << endl; + return FeatureSet(); + } + + float prev = m_previousSample; + size_t count = 0; + + FeatureSet returnFeatures; + + for (size_t i = 0; i < m_stepSize; ++i) { + + float sample = inputBuffers[0][i]; + bool crossing = false; + + if (sample <= 0.0) { + if (prev > 0.0) crossing = true; + } else if (sample > 0.0) { + if (prev <= 0.0) crossing = true; + } + + if (crossing) { + ++count; + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = timestamp + + Vamp::RealTime::frame2RealTime(i, (size_t)m_inputSampleRate); + returnFeatures[1].push_back(feature); + } + + prev = sample; + } + + m_previousSample = prev; + + Feature feature; + feature.hasTimestamp = false; + feature.values.push_back(count); + + returnFeatures[0].push_back(feature); + return returnFeatures; +} + +ZeroCrossing::FeatureSet +ZeroCrossing::getRemainingFeatures() +{ + return FeatureSet(); +} + diff --git a/libs/vamp-plugins/ZeroCrossing.h b/libs/vamp-plugins/ZeroCrossing.h new file mode 100644 index 0000000000..ede2a74492 --- /dev/null +++ b/libs/vamp-plugins/ZeroCrossing.h @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _ZERO_CROSSING_PLUGIN_H_ +#define _ZERO_CROSSING_PLUGIN_H_ + +#include "vamp-sdk/Plugin.h" + +/** + * Example plugin that calculates the positions and density of + * zero-crossing points in an audio waveform. +*/ + +class ZeroCrossing : public Vamp::Plugin +{ +public: + ZeroCrossing(float inputSampleRate); + virtual ~ZeroCrossing(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + size_t m_stepSize; + float m_previousSample; +}; + + +#endif diff --git a/libs/vamp-plugins/plugins.cpp b/libs/vamp-plugins/plugins.cpp new file mode 100644 index 0000000000..25c6e6c0d4 --- /dev/null +++ b/libs/vamp-plugins/plugins.cpp @@ -0,0 +1,63 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "vamp/vamp.h" +#include "vamp-sdk/PluginAdapter.h" + +#include "ZeroCrossing.h" +#include "SpectralCentroid.h" +#include "PercussionOnsetDetector.h" +#include "AmplitudeFollower.h" + +static Vamp::PluginAdapter<ZeroCrossing> zeroCrossingAdapter; +static Vamp::PluginAdapter<SpectralCentroid> spectralCentroidAdapter; +static Vamp::PluginAdapter<PercussionOnsetDetector> percussionOnsetAdapter; +static Vamp::PluginAdapter<AmplitudeFollower> amplitudeAdapter; + +const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, + unsigned int index) +{ + if (version < 1) return 0; + + switch (index) { + case 0: return zeroCrossingAdapter.getDescriptor(); + case 1: return spectralCentroidAdapter.getDescriptor(); + case 2: return percussionOnsetAdapter.getDescriptor(); + case 3: return amplitudeAdapter.getDescriptor(); + default: return 0; + } +} + diff --git a/libs/vamp-sdk/SConscript b/libs/vamp-sdk/SConscript index ddd3d8ebbc..abf9d86534 100644 --- a/libs/vamp-sdk/SConscript +++ b/libs/vamp-sdk/SConscript @@ -11,6 +11,7 @@ vamp-sdk/RealTime.cpp vamphostsdk_files = Split (""" vamp-sdk/PluginHostAdapter.cpp +vamp-sdk/hostext/PluginBufferingAdapter.cpp vamp-sdk/hostext/PluginChannelAdapter.cpp vamp-sdk/hostext/PluginInputDomainAdapter.cpp vamp-sdk/hostext/PluginLoader.cpp @@ -21,7 +22,11 @@ vamp-sdk/RealTime.cpp Import('env install_prefix libraries') vampsdk = env.Copy() -vampsdk.Append (CPPATH='#libs/vamp-sdk/vamp', CXXFLAGS="-Ilibs/vamp-sdk") +vampsdk.Merge ([libraries['fftw3'], libraries['fftw3f']]) + +# HAVE_FFTW3 is used to help improve some performance aspects of VAMP's InputDomainAdapter + +vampsdk.Append (CPPATH='#libs/vamp-sdk/vamp', CXXFLAGS="-Ilibs/vamp-sdk -DHAVE_FFTW3") libvampsdk = vampsdk.SharedLibrary('vampsdk', vampsdk_files) libvamphostsdk = vampsdk.SharedLibrary('vamphostsdk', vamphostsdk_files) @@ -30,6 +35,8 @@ Default(libvampsdk) Default(libvamphostsdk) env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2'), libvampsdk)) +env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2'), libvamphostsdk)) + env.Alias('tarball', env.Distribute (env['DISTTREE'], [ 'SConscript', 'COPYING', 'README' ] + vampsdk_files + diff --git a/libs/vamp-sdk/vamp-sdk/RealTime.h b/libs/vamp-sdk/vamp-sdk/RealTime.h index 4dd78fd209..6b88ed53f0 100644 --- a/libs/vamp-sdk/vamp-sdk/RealTime.h +++ b/libs/vamp-sdk/vamp-sdk/RealTime.h @@ -125,23 +125,31 @@ struct RealTime RealTime operator/(int d) const; - // Find the fractional difference between times - // + /** + * Return the ratio of two times. + */ double operator/(const RealTime &r) const; - // Return a human-readable debug-type string to full precision - // (probably not a format to show to a user directly) - // + /** + * Return a human-readable debug-type string to full precision + * (probably not a format to show to a user directly) + */ std::string toString() const; - // Return a user-readable string to the nearest millisecond - // in a form like HH:MM:SS.mmm - // + /** + * Return a user-readable string to the nearest millisecond + * in a form like HH:MM:SS.mmm + */ std::string toText(bool fixedDp = false) const; - // Convenience functions for handling sample frames - // + /** + * Convert a RealTime into a sample frame at the given sample rate. + */ static long realTime2Frame(const RealTime &r, unsigned int sampleRate); + + /** + * Convert a sample frame at the given sample rate into a RealTime. + */ static RealTime frame2RealTime(long frame, unsigned int sampleRate); static const RealTime zeroTime; diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp new file mode 100644 index 0000000000..406d4978c4 --- /dev/null +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp @@ -0,0 +1,490 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2007 Chris Cannam and QMUL. + This file by Mark Levy and Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include <vector> +#include <map> + +#include "PluginBufferingAdapter.h" + +using std::vector; +using std::map; + +namespace Vamp { + +namespace HostExt { + +class PluginBufferingAdapter::Impl +{ +public: + Impl(Plugin *plugin, float inputSampleRate); + ~Impl(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class RingBuffer + { + public: + RingBuffer(int n) : + m_buffer(new float[n+1]), m_writer(0), m_reader(0), m_size(n+1) { } + virtual ~RingBuffer() { delete[] m_buffer; } + + int getSize() const { return m_size-1; } + void reset() { m_writer = 0; m_reader = 0; } + + int getReadSpace() const { + int writer = m_writer, reader = m_reader, space; + if (writer > reader) space = writer - reader; + else if (writer < reader) space = (writer + m_size) - reader; + else space = 0; + return space; + } + + int getWriteSpace() const { + int writer = m_writer; + int reader = m_reader; + int space = (reader + m_size - writer - 1); + if (space >= m_size) space -= m_size; + return space; + } + + int peek(float *destination, int n) const { + + int available = getReadSpace(); + + if (n > available) { + for (int i = available; i < n; ++i) { + destination[i] = 0.f; + } + n = available; + } + if (n == 0) return n; + + int reader = m_reader; + int here = m_size - reader; + const float *const bufbase = m_buffer + reader; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + destination[i] = bufbase[i]; + } + } else { + for (int i = 0; i < here; ++i) { + destination[i] = bufbase[i]; + } + float *const destbase = destination + here; + const int nh = n - here; + for (int i = 0; i < nh; ++i) { + destbase[i] = m_buffer[i]; + } + } + + return n; + } + + int skip(int n) { + + int available = getReadSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int reader = m_reader; + reader += n; + while (reader >= m_size) reader -= m_size; + m_reader = reader; + return n; + } + + int write(const float *source, int n) { + + int available = getWriteSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int writer = m_writer; + int here = m_size - writer; + float *const bufbase = m_buffer + writer; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + bufbase[i] = source[i]; + } + } else { + for (int i = 0; i < here; ++i) { + bufbase[i] = source[i]; + } + const int nh = n - here; + const float *const srcbase = source + here; + float *const buf = m_buffer; + for (int i = 0; i < nh; ++i) { + buf[i] = srcbase[i]; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + + return n; + } + + int zero(int n) { + + int available = getWriteSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int writer = m_writer; + int here = m_size - writer; + float *const bufbase = m_buffer + writer; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + bufbase[i] = 0.f; + } + } else { + for (int i = 0; i < here; ++i) { + bufbase[i] = 0.f; + } + const int nh = n - here; + for (int i = 0; i < nh; ++i) { + m_buffer[i] = 0.f; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + + return n; + } + + protected: + float *m_buffer; + int m_writer; + int m_reader; + int m_size; + + private: + RingBuffer(const RingBuffer &); // not provided + RingBuffer &operator=(const RingBuffer &); // not provided + }; + + Plugin *m_plugin; + size_t m_inputStepSize; + size_t m_inputBlockSize; + size_t m_stepSize; + size_t m_blockSize; + size_t m_channels; + vector<RingBuffer *> m_queue; + float **m_buffers; + float m_inputSampleRate; + RealTime m_timestamp; + OutputList m_outputs; + + void processBlock(FeatureSet& allFeatureSets, RealTime timestamp); +}; + +PluginBufferingAdapter::PluginBufferingAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin, m_inputSampleRate); +} + +PluginBufferingAdapter::~PluginBufferingAdapter() +{ + delete m_impl; +} + +bool +PluginBufferingAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_impl->initialise(channels, stepSize, blockSize); +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::getOutputDescriptors() const +{ + return m_impl->getOutputDescriptors(); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::process(const float *const *inputBuffers, + RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::getRemainingFeatures() +{ + return m_impl->getRemainingFeatures(); +} + +PluginBufferingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : + m_plugin(plugin), + m_inputStepSize(0), + m_inputBlockSize(0), + m_stepSize(0), + m_blockSize(0), + m_channels(0), + m_queue(0), + m_buffers(0), + m_inputSampleRate(inputSampleRate), + m_timestamp() +{ + m_outputs = plugin->getOutputDescriptors(); +} + +PluginBufferingAdapter::Impl::~Impl() +{ + // the adapter will delete the plugin + + for (size_t i = 0; i < m_channels; ++i) { + delete m_queue[i]; + delete[] m_buffers[i]; + } + delete[] m_buffers; +} + +size_t +PluginBufferingAdapter::getPreferredStepSize() const +{ + return getPreferredBlockSize(); +} + +bool +PluginBufferingAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (stepSize != blockSize) { + std::cerr << "PluginBufferingAdapter::initialise: input stepSize must be equal to blockSize for this adapter (stepSize = " << stepSize << ", blockSize = " << blockSize << ")" << std::endl; + return false; + } + + m_channels = channels; + m_inputStepSize = stepSize; + m_inputBlockSize = blockSize; + + // use the step and block sizes which the plugin prefers + m_stepSize = m_plugin->getPreferredStepSize(); + m_blockSize = m_plugin->getPreferredBlockSize(); + + // or sensible defaults if it has no preference + if (m_blockSize == 0) { + m_blockSize = 1024; + } + if (m_stepSize == 0) { + if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { + m_stepSize = m_blockSize/2; + } else { + m_stepSize = m_blockSize; + } + } else if (m_stepSize > m_blockSize) { + if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { + m_blockSize = m_stepSize * 2; + } else { + m_blockSize = m_stepSize; + } + } + + std::cerr << "PluginBufferingAdapter::initialise: stepSize " << m_inputStepSize << " -> " << m_stepSize + << ", blockSize " << m_inputBlockSize << " -> " << m_blockSize << std::endl; + + // current implementation breaks if step is greater than block + if (m_stepSize > m_blockSize) { + std::cerr << "PluginBufferingAdapter::initialise: plugin's preferred stepSize greater than blockSize, giving up!" << std::endl; + return false; + } + + m_buffers = new float *[m_channels]; + + for (size_t i = 0; i < m_channels; ++i) { + m_queue.push_back(new RingBuffer(m_blockSize + m_inputBlockSize)); + m_buffers[i] = new float[m_blockSize]; + } + + return m_plugin->initialise(m_channels, m_stepSize, m_blockSize); +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::Impl::getOutputDescriptors() const +{ + OutputList outs = m_plugin->getOutputDescriptors(); + for (size_t i = 0; i < outs.size(); ++i) { + if (outs[i].sampleType == OutputDescriptor::OneSamplePerStep) { + outs[i].sampleRate = 1.f / m_stepSize; + } + outs[i].sampleType = OutputDescriptor::VariableSampleRate; + } + return outs; +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ + FeatureSet allFeatureSets; + + // queue the new input + + for (size_t i = 0; i < m_channels; ++i) { + int written = m_queue[i]->write(inputBuffers[i], m_inputBlockSize); + if (written < int(m_inputBlockSize) && i == 0) { + std::cerr << "WARNING: PluginBufferingAdapter::Impl::process: " + << "Buffer overflow: wrote " << written + << " of " << m_inputBlockSize + << " input samples (for plugin step size " + << m_stepSize << ", block size " << m_blockSize << ")" + << std::endl; + } + } + + // process as much as we can + + while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { + processBlock(allFeatureSets, timestamp); + } + + return allFeatureSets; +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::getRemainingFeatures() +{ + FeatureSet allFeatureSets; + + // process remaining samples in queue + while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { + processBlock(allFeatureSets, m_timestamp); + } + + // pad any last samples remaining and process + if (m_queue[0]->getReadSpace() > 0) { + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->zero(m_blockSize - m_queue[i]->getReadSpace()); + } + processBlock(allFeatureSets, m_timestamp); + } + + // get remaining features + + FeatureSet featureSet = m_plugin->getRemainingFeatures(); + + for (map<int, FeatureList>::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) { + FeatureList featureList = iter->second; + for (size_t i = 0; i < featureList.size(); ++i) { + allFeatureSets[iter->first].push_back(featureList[i]); + } + } + + return allFeatureSets; +} + +void +PluginBufferingAdapter::Impl::processBlock(FeatureSet& allFeatureSets, + RealTime timestamp) +{ + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->peek(m_buffers[i], m_blockSize); + } + + FeatureSet featureSet = m_plugin->process(m_buffers, m_timestamp); + + for (map<int, FeatureList>::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) { + + FeatureList featureList = iter->second; + int outputNo = iter->first; + + for (size_t i = 0; i < featureList.size(); ++i) { + + // make sure the timestamp is set + switch (m_outputs[outputNo].sampleType) { + + case OutputDescriptor::OneSamplePerStep: + // use our internal timestamp - OK???? + featureList[i].timestamp = m_timestamp; + break; + + case OutputDescriptor::FixedSampleRate: + // use our internal timestamp + featureList[i].timestamp = m_timestamp; + break; + + case OutputDescriptor::VariableSampleRate: + break; // plugin must set timestamp + + default: + break; + } + + allFeatureSets[outputNo].push_back(featureList[i]); + } + } + + // step forward + + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->skip(m_stepSize); + } + + // fake up the timestamp each time we step forward + + long frame = RealTime::realTime2Frame(m_timestamp, + int(m_inputSampleRate + 0.5)); + m_timestamp = RealTime::frame2RealTime(frame + m_stepSize, + int(m_inputSampleRate + 0.5)); +} + +} + +} + + diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h new file mode 100644 index 0000000000..96a958b9e5 --- /dev/null +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2007 Chris Cannam and QMUL. + This file by Mark Levy, Copyright 2007 QMUL. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _VAMP_PLUGIN_BUFFERING_ADAPTER_H_ +#define _VAMP_PLUGIN_BUFFERING_ADAPTER_H_ + +#include "PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +/** + * \class PluginBufferingAdapter PluginBufferingAdapter.h <vamp-sdk/hostext/PluginBufferingAdapter.h> + * + * PluginBufferingAdapter is a Vamp plugin adapter that allows plugins + * to be used by a host supplying an audio stream in non-overlapping + * buffers of arbitrary size. + * + * A host using PluginBufferingAdapter may ignore the preferred step + * and block size reported by the plugin, and still expect the plugin + * to run. The value of blockSize and stepSize passed to initialise + * should be the size of the buffer which the host will supply; the + * stepSize should be equal to the blockSize. + * + * If the internal step size used for the plugin differs from that + * supplied by the host, the adapter will modify the sample rate + * specifications for the plugin outputs (setting them all to + * VariableSampleRate) and set timestamps on the output features for + * outputs that formerly used a different sample rate specification. + * This is necessary in order to obtain correct time stamping. + * + * In other respects, the PluginBufferingAdapter behaves identically + * to the plugin that it wraps. The wrapped plugin will be deleted + * when the wrapper is deleted. + */ + +class PluginBufferingAdapter : public PluginWrapper +{ +public: + PluginBufferingAdapter(Plugin *plugin); // I take ownership of plugin + virtual ~PluginBufferingAdapter(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + size_t getPreferredStepSize() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class Impl; + Impl *m_impl; +}; + +} + +} + +#endif diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp index 3fc0d74720..e706414ae2 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp @@ -38,6 +38,28 @@ #include <cmath> + +/** + * If you want to compile using FFTW instead of the built-in FFT + * implementation for the PluginInputDomainAdapter, define HAVE_FFTW3 + * in the Makefile. + * + * Remember that FFTW is licensed under the GPL (unlike this SDK, which + * is licensed liberally in order to permit closed-source usage), so + * you should not define this symbol unless your code is also under the + * GPL. Also, parties redistributing this SDK for use in other + * programs should be careful _not_ to define this symbol in order not + * to affect the stated license of this SDK. + * + * Note: This code uses FFTW_MEASURE, and will perform badly on its + * first invocation unless the host has saved and restored FFTW wisdom + * (see the FFTW documentation). + */ +#ifdef HAVE_FFTW3 +#include <fftw3.h> +#endif + + namespace Vamp { namespace HostExt { @@ -58,15 +80,22 @@ public: protected: Plugin *m_plugin; float m_inputSampleRate; - size_t m_channels; - size_t m_blockSize; + int m_channels; + int m_blockSize; float **m_freqbuf; + double *m_ri; + double *m_window; + +#ifdef HAVE_FFTW3 + fftw_plan m_plan; + fftw_complex *m_cbuf; +#else double *m_ro; double *m_io; - void fft(unsigned int n, bool inverse, double *ri, double *ii, double *ro, double *io); +#endif size_t makeBlockSizeAcceptable(size_t) const; }; @@ -112,12 +141,21 @@ PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime tim return m_impl->process(inputBuffers, timestamp); } - PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : +PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : m_plugin(plugin), m_inputSampleRate(inputSampleRate), m_channels(0), m_blockSize(0), - m_freqbuf(0) + m_freqbuf(0), + m_ri(0), + m_window(0), +#ifdef HAVE_FFTW3 + m_plan(0), + m_cbuf(0) +#else + m_ro(0), + m_io(0) +#endif { } @@ -126,23 +164,38 @@ PluginInputDomainAdapter::Impl::~Impl() // the adapter will delete the plugin if (m_channels > 0) { - for (size_t c = 0; c < m_channels; ++c) { + for (int c = 0; c < m_channels; ++c) { delete[] m_freqbuf[c]; } delete[] m_freqbuf; +#ifdef HAVE_FFTW3 + if (m_plan) { + fftw_destroy_plan(m_plan); + fftw_free(m_ri); + fftw_free(m_cbuf); + m_plan = 0; + } +#else delete[] m_ri; delete[] m_ro; delete[] m_io; +#endif + delete[] m_window; } } + +// for some visual studii apparently +#ifndef M_PI +#define M_PI 3.14159265358979232846 +#endif bool PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) { if (m_plugin->getInputDomain() == TimeDomain) { - m_blockSize = blockSize; - m_channels = channels; + m_blockSize = int(blockSize); + m_channels = int(channels); return m_plugin->initialise(channels, stepSize, blockSize); } @@ -158,25 +211,48 @@ PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, siz } if (m_channels > 0) { - for (size_t c = 0; c < m_channels; ++c) { + for (int c = 0; c < m_channels; ++c) { delete[] m_freqbuf[c]; } delete[] m_freqbuf; +#ifdef HAVE_FFTW3 + if (m_plan) { + fftw_destroy_plan(m_plan); + fftw_free(m_ri); + fftw_free(m_cbuf); + m_plan = 0; + } +#else delete[] m_ri; delete[] m_ro; delete[] m_io; +#endif + delete[] m_window; } - m_blockSize = blockSize; - m_channels = channels; + m_blockSize = int(blockSize); + m_channels = int(channels); m_freqbuf = new float *[m_channels]; - for (size_t c = 0; c < m_channels; ++c) { + for (int c = 0; c < m_channels; ++c) { m_freqbuf[c] = new float[m_blockSize + 2]; } + m_window = new double[m_blockSize]; + + for (int i = 0; i < m_blockSize; ++i) { + // Hanning window + m_window[i] = (0.50 - 0.50 * cos((2.0 * M_PI * i) / m_blockSize)); + } + +#ifdef HAVE_FFTW3 + m_ri = (double *)fftw_malloc(blockSize * sizeof(double)); + m_cbuf = (fftw_complex *)fftw_malloc((blockSize/2 + 1) * sizeof(fftw_complex)); + m_plan = fftw_plan_dft_r2c_1d(blockSize, m_ri, m_cbuf, FFTW_MEASURE); +#else m_ri = new double[m_blockSize]; m_ro = new double[m_blockSize]; m_io = new double[m_blockSize]; +#endif return m_plugin->initialise(channels, stepSize, blockSize); } @@ -220,7 +296,11 @@ PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const } else if (blockSize & (blockSize-1)) { - // not a power of two, can't handle that with our current fft +#ifdef HAVE_FFTW3 + // not an issue with FFTW +#else + + // not a power of two, can't handle that with our built-in FFT // implementation size_t nearest = blockSize; @@ -241,16 +321,13 @@ PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: non-power-of-two\nblocksize " << blockSize << " not supported, using blocksize " << nearest << " instead" << std::endl; blockSize = nearest; + +#endif } return blockSize; } -// for some visual studii apparently -#ifndef M_PI -#define M_PI 3.14159265358979232846 -#endif - Plugin::FeatureSet PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp) @@ -308,33 +385,45 @@ PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers, // std::cerr << " to " << timestamp << std::endl; - for (size_t c = 0; c < m_channels; ++c) { + for (int c = 0; c < m_channels; ++c) { - for (size_t i = 0; i < m_blockSize; ++i) { - // Hanning window - m_ri[i] = double(inputBuffers[c][i]) - * (0.50 - 0.50 * cos((2 * M_PI * i) - / m_blockSize)); + for (int i = 0; i < m_blockSize; ++i) { + m_ri[i] = double(inputBuffers[c][i]) * m_window[i]; } - for (size_t i = 0; i < m_blockSize/2; ++i) { + for (int i = 0; i < m_blockSize/2; ++i) { // FFT shift double value = m_ri[i]; m_ri[i] = m_ri[i + m_blockSize/2]; m_ri[i + m_blockSize/2] = value; } +#ifdef HAVE_FFTW3 + + fftw_execute(m_plan); + + for (int i = 0; i <= m_blockSize/2; ++i) { + m_freqbuf[c][i * 2] = float(m_cbuf[i][0]); + m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i][1]); + } + +#else + fft(m_blockSize, false, m_ri, 0, m_ro, m_io); - for (size_t i = 0; i <= m_blockSize/2; ++i) { - m_freqbuf[c][i * 2] = m_ro[i]; - m_freqbuf[c][i * 2 + 1] = m_io[i]; + for (int i = 0; i <= m_blockSize/2; ++i) { + m_freqbuf[c][i * 2] = float(m_ro[i]); + m_freqbuf[c][i * 2 + 1] = float(m_io[i]); } + +#endif } return m_plugin->process(m_freqbuf, timestamp); } +#ifndef HAVE_FFTW3 + void PluginInputDomainAdapter::Impl::fft(unsigned int n, bool inverse, double *ri, double *ii, double *ro, double *io) @@ -452,6 +541,8 @@ PluginInputDomainAdapter::Impl::fft(unsigned int n, bool inverse, } } +#endif + } } diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.cpp index cb71fc4750..99baac3b72 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.cpp +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.cpp @@ -38,6 +38,7 @@ #include "PluginLoader.h" #include "PluginInputDomainAdapter.h" #include "PluginChannelAdapter.h" +#include "PluginBufferingAdapter.h" #include <fstream> #include <cctype> // tolower @@ -85,6 +86,8 @@ public: string getLibraryPathForPlugin(PluginKey key); + static void setInstanceToClean(PluginLoader *instance); + protected: class PluginDeletionNotifyAdapter : public PluginWrapper { public: @@ -94,6 +97,15 @@ protected: Impl *m_loader; }; + class InstanceCleaner { + public: + InstanceCleaner() : m_instance(0) { } + ~InstanceCleaner() { delete m_instance; } + void setInstance(PluginLoader *instance) { m_instance = instance; } + protected: + PluginLoader *m_instance; + }; + virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter); map<PluginKey, string> m_pluginLibraryNameMap; @@ -114,11 +126,16 @@ protected: string splicePath(string a, string b); vector<string> listFiles(string dir, string ext); + + static InstanceCleaner m_cleaner; }; PluginLoader * PluginLoader::m_instance = 0; +PluginLoader::Impl::InstanceCleaner +PluginLoader::Impl::m_cleaner; + PluginLoader::PluginLoader() { m_impl = new Impl(); @@ -132,7 +149,13 @@ PluginLoader::~PluginLoader() PluginLoader * PluginLoader::getInstance() { - if (!m_instance) m_instance = new PluginLoader(); + if (!m_instance) { + // The cleaner doesn't own the instance, because we leave the + // instance pointer in the base class for binary backwards + // compatibility reasons and to avoid waste + m_instance = new PluginLoader(); + Impl::setInstanceToClean(m_instance); + } return m_instance; } @@ -177,6 +200,12 @@ PluginLoader::Impl::~Impl() { } +void +PluginLoader::Impl::setInstanceToClean(PluginLoader *instance) +{ + m_cleaner.setInstance(instance); +} + vector<PluginLoader::PluginKey> PluginLoader::Impl::listPlugins() { @@ -366,6 +395,10 @@ PluginLoader::Impl::loadPlugin(PluginKey key, } } + if (adapterFlags & ADAPT_BUFFER_SIZE) { + adapter = new PluginBufferingAdapter(adapter); + } + if (adapterFlags & ADAPT_CHANNEL_COUNT) { adapter = new PluginChannelAdapter(adapter); } @@ -549,9 +582,11 @@ PluginLoader::Impl::listFiles(string dir, string extension) struct dirent *e = 0; while ((e = readdir(d))) { + + if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue; - if (!(e->d_type & DT_REG) || !e->d_name) continue; - + if (!e->d_name) continue; + size_t len = strlen(e->d_name); if (len < extlen + 2 || e->d_name + len - extlen - 1 != "." + extension) { diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.h b/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.h index 82ae22b930..f48143c11e 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.h +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginLoader.h @@ -142,15 +142,35 @@ public: * to be mixed down to mono, etc., without having to worry about * doing that itself. * - * ADAPT_ALL - Perform all available adaptations, where meaningful. + * ADAPT_BUFFER_SIZE - Wrap the plugin in a PluginBufferingAdapter + * permitting the host to provide audio input using any block + * size, with no overlap, regardless of the plugin's preferred + * block size (suitable for hosts that read from non-seekable + * streaming media, for example). This adapter introduces some + * run-time overhead and also changes the semantics of the plugin + * slightly (see the PluginBufferingAdapter header documentation + * for details). + * + * ADAPT_ALL_SAFE - Perform all available adaptations that are + * meaningful for the plugin and "safe". Currently this means to + * ADAPT_INPUT_DOMAIN if the plugin wants FrequencyDomain input; + * ADAPT_CHANNEL_COUNT always; and ADAPT_BUFFER_SIZE never. + * + * ADAPT_ALL - Perform all available adaptations that are + * meaningful for the plugin. * - * See PluginInputDomainAdapter and PluginChannelAdapter for more - * details of the classes that the loader may use if these flags - * are set. + * See PluginInputDomainAdapter, PluginChannelAdapter and + * PluginBufferingAdapter for more details of the classes that the + * loader may use if these flags are set. */ enum AdapterFlags { + ADAPT_INPUT_DOMAIN = 0x01, ADAPT_CHANNEL_COUNT = 0x02, + ADAPT_BUFFER_SIZE = 0x04, + + ADAPT_ALL_SAFE = 0x03, + ADAPT_ALL = 0xff }; |