diff options
70 files changed, 1437 insertions, 393 deletions
diff --git a/SConstruct b/SConstruct index f2a2d069b5..33cb8b7feb 100644 --- a/SConstruct +++ b/SConstruct @@ -534,6 +534,7 @@ if env['LV2']: if conf.CheckPKGExists ('\"slv2 >= 0.6.0\"'): libraries['slv2'] = LibraryInfo() libraries['slv2'].ParseConfig('pkg-config --cflags --libs slv2') + env.Append (CCFLAGS="-DHAVE_LV2") else: 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.' diff --git a/gtk2_ardour/ardour.bindings.in b/gtk2_ardour/ardour.bindings.in index 3d488c1ec4..0465889f74 100644 --- a/gtk2_ardour/ardour.bindings.in +++ b/gtk2_ardour/ardour.bindings.in @@ -182,9 +182,12 @@ (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/tab-to-transient-forwards" "7") +(gtk_accel_path "<Actions>/Editor/tab-to-transient-backwards" "8") (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/ardour3_ui_default.conf b/gtk2_ardour/ardour3_ui_default.conf index b64296ae02..aed0f07bca 100644 --- a/gtk2_ardour/ardour3_ui_default.conf +++ b/gtk2_ardour/ardour3_ui_default.conf @@ -4,11 +4,13 @@ <Option name="ui-rc-file" value="ardour3_ui_dark.rc"/> </UI> <Canvas> - <Option name="waveform outline" value="0f0f0fcc"/> - <Option name="waveform fill" value="3d475378"/> + <Option name="waveform outline" value="0f0f0fc8"/> + <Option name="waveform fill" value="3d4753dc"/> + <Option name="selected waveform outline" value="0f0f0fcc"/> + <Option name="selected waveform fill" value="51518ac8"/> <Option name="clipped waveform" value="ff0000e5"/> - <Option name="region base" value="99a7b5aa"/> - <Option name="selected region base" value="b591a8ff"/> + <Option name="region base" value="99a7b5a0"/> + <Option name="selected region base" value="51518aa0"/> <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_dialog.cc b/gtk2_ardour/ardour_dialog.cc index 795b924075..bc24389948 100644 --- a/gtk2_ardour/ardour_dialog.cc +++ b/gtk2_ardour/ardour_dialog.cc @@ -24,7 +24,7 @@ #include "ardour_dialog.h" #include "keyboard.h" #include "ardour_ui.h" - +#include "splash.h" ArdourDialog::ArdourDialog (string title, bool modal, bool use_seperator) : Dialog (title, modal, use_seperator) @@ -66,3 +66,17 @@ ArdourDialog::on_unmap () { Dialog::on_unmap (); } + +void +ArdourDialog::on_show () +{ + // never allow the splash screen to obscure any dialog + + Splash* spl = Splash::instance(); + + if (spl) { + spl->pop_back (); + } + + Dialog::on_show (); +} diff --git a/gtk2_ardour/ardour_dialog.h b/gtk2_ardour/ardour_dialog.h index 13248e14de..264dcfcd58 100644 --- a/gtk2_ardour/ardour_dialog.h +++ b/gtk2_ardour/ardour_dialog.h @@ -43,6 +43,7 @@ class ArdourDialog : public Gtk::Dialog bool on_enter_notify_event (GdkEventCrossing*); bool on_leave_notify_event (GdkEventCrossing*); void on_unmap (); + void on_show (); ARDOUR::Session *session; diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index b563fc4186..271cbab85b 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -242,6 +242,10 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[]) ARDOUR::Session::AskAboutPendingState.connect (mem_fun(*this, &ARDOUR_UI::pending_state_dialog)); + /* handle sr mismatch with a dialog */ + + ARDOUR::Session::AskAboutSampleRateMismatch.connect (mem_fun(*this, &ARDOUR_UI::sr_mismatch_dialog)); + /* lets get this party started */ try { @@ -403,6 +407,15 @@ ARDOUR_UI::~ARDOUR_UI () } } +void +ARDOUR_UI::pop_back_splash () +{ + if (Splash::instance()) { + // Splash::instance()->pop_back(); + Splash::instance()->hide (); + } +} + gint ARDOUR_UI::configure_timeout () { @@ -609,6 +622,7 @@ Please consider the possibilities, and perhaps (re)start JACK.")); win.show_all (); win.set_position (Gtk::WIN_POS_CENTER); + pop_back_splash (); /* we just don't care about the result, but we want to block */ @@ -691,6 +705,8 @@ ARDOUR_UI::check_memory_locking () cb.show(); vbox->show(); hbox.show (); + + pop_back_splash (); editor->ensure_float (msg); msg.run (); @@ -725,6 +741,7 @@ ARDOUR_UI::finish() Ardour was unable to save your session.\n\n\ If you still wish to quit, please use the\n\n\ \"Just quit\" option.")); + pop_back_splash(); msg.run (); return; } @@ -997,8 +1014,27 @@ ARDOUR_UI::redisplay_recent_sessions () get_state_files_in_directory (*i, state_file_paths); - if (state_file_paths.empty()) { - // no state file? + vector<string*>* states; + vector<const gchar*> item; + string fullpath = (*i).to_string(); + + /* remove any trailing / */ + + if (fullpath[fullpath.length()-1] == '/') { + fullpath = fullpath.substr (0, fullpath.length()-1); + } + + /* check whether session still exists */ + if (!Glib::file_test(fullpath.c_str(), Glib::FILE_TEST_EXISTS)) { + /* session doesn't exist */ + cerr << "skipping non-existent session " << fullpath << endl; + continue; + } + + /* now get available states for this session */ + + if ((states = Session::possible_states (fullpath)) == 0) { + /* no state file? */ continue; } @@ -1006,8 +1042,6 @@ ARDOUR_UI::redisplay_recent_sessions () Gtk::TreeModel::Row row = *(recent_session_model->append()); - const string fullpath = (*i).to_string(); - row[recent_session_columns.visible_name] = Glib::path_get_basename (fullpath); row[recent_session_columns.fullpath] = fullpath; @@ -1153,6 +1187,7 @@ ARDOUR_UI::check_audioengine () if (!engine->connected()) { MessageDialog msg (_("Ardour is not connected to JACK\n" "You cannot open or close sessions in this condition")); + pop_back_splash (); msg.run (); return false; } @@ -1309,6 +1344,7 @@ ARDOUR_UI::session_add_audio_route (bool track, int32_t input_channels, int32_t to create a new track or bus.\n\ You should save Ardour, exit and\n\ restart JACK with more ports.")); + pop_back_splash (); msg.run (); } } @@ -1685,6 +1721,7 @@ JACK has either been shutdown or it\n\ disconnected Ardour because Ardour\n\ was not fast enough. You can save the\n\ session and/or try to reconnect to JACK .")); + pop_back_splash (); msg.run (); } @@ -2026,6 +2063,7 @@ ARDOUR_UI::fontconfig_dialog () true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK); + pop_back_splash (); msg.show_all (); msg.present (); msg.run (); @@ -2116,7 +2154,8 @@ ARDOUR_UI::ask_about_loading_existing_session (const Glib::ustring& session_path msg.set_name (X_("CleanupDialog")); msg.set_wmclass (X_("existing_session"), "Ardour"); msg.set_position (Gtk::WIN_POS_MOUSE); - + pop_back_splash (); + switch (msg.run()) { case RESPONSE_YES: return true; @@ -2325,6 +2364,7 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be session_path = new_session_dialog->session_folder(); } + template_name = Glib::ustring(); switch (new_session_dialog->which_page()) { case NewSessionDialog::OpenPage: @@ -2374,7 +2414,7 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be loadit: new_session_dialog->hide (); - if (load_session (session_path, session_name)) { + if (load_session (session_path, session_name, template_name)) { /* force a retry */ response = Gtk::RESPONSE_NONE; } @@ -2435,6 +2475,7 @@ ARDOUR_UI::load_session (const Glib::ustring& path, const Glib::ustring& snap_na if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS) && ::access (path.c_str(), W_OK)) { MessageDialog msg (*editor, _("You do not have write access to this session.\n" "This prevents the session from being loaded.")); + pop_back_splash (); msg.run (); goto out; } @@ -2458,6 +2499,7 @@ ARDOUR_UI::load_session (const Glib::ustring& path, const Glib::ustring& snap_na msg.set_title (_("Loading Error")); msg.set_secondary_text (_("Click the OK button to try again.")); msg.set_position (Gtk::WIN_POS_CENTER); + pop_back_splash (); msg.present (); int response = msg.run (); @@ -2483,6 +2525,7 @@ ARDOUR_UI::load_session (const Glib::ustring& path, const Glib::ustring& snap_na msg.set_title (_("Loading Error")); msg.set_secondary_text (_("Click the OK button to try again.")); msg.set_position (Gtk::WIN_POS_CENTER); + pop_back_splash (); msg.present (); int response = msg.run (); @@ -2555,6 +2598,7 @@ ARDOUR_UI::build_session (const Glib::ustring& path, const Glib::ustring& snap_n catch (...) { MessageDialog msg (string_compose(_("Could not create session in \"%1\""), path)); + pop_back_splash (); msg.run (); return -1; } @@ -3030,6 +3074,38 @@ what you would like to do.\n")); return 0; } } + +int +ARDOUR_UI::sr_mismatch_dialog (nframes_t desired, nframes_t actual) +{ + HBox* hbox = new HBox(); + Image* image = new Image (Stock::DIALOG_QUESTION, ICON_SIZE_DIALOG); + ArdourDialog dialog (_("Sample Rate Mismatch"), true); + Label message (string_compose (_("\ +This session was created with a sample rate of %1 Hz\n\ +\n\ +The audioengine is currently running at %2 Hz\n"), desired, actual)); + + image->set_alignment(ALIGN_CENTER, ALIGN_TOP); + hbox->pack_start (*image, PACK_EXPAND_WIDGET, 12); + hbox->pack_end (message, PACK_EXPAND_PADDING, 12); + dialog.get_vbox()->pack_start(*hbox, PACK_EXPAND_PADDING, 6); + dialog.add_button (_("Load session anyway"), RESPONSE_ACCEPT); + dialog.add_button (_("Do not load session"), RESPONSE_REJECT); + dialog.set_default_response (RESPONSE_ACCEPT); + dialog.set_position (WIN_POS_CENTER); + message.show(); + image->show(); + hbox->show(); + + switch (dialog.run ()) { + case RESPONSE_ACCEPT: + return 0; + default: + return 1; + } +} + void ARDOUR_UI::disconnect_from_jack () diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index 247d4cf6b3..01e53c7d54 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -660,7 +660,9 @@ class ARDOUR_UI : public Gtkmm2ext::UI About* about; Splash* splash; + void pop_back_splash (); bool shown_flag; + /* cleanup */ Gtk::MenuItem *cleanup_item; @@ -682,6 +684,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI void disk_underrun_handler (); int pending_state_dialog (); + int sr_mismatch_dialog (nframes_t, nframes_t); void disconnect_from_jack (); void reconnect_to_jack (); diff --git a/gtk2_ardour/ardour_ui_dependents.cc b/gtk2_ardour/ardour_ui_dependents.cc index d4dda9c540..a2ac809203 100644 --- a/gtk2_ardour/ardour_ui_dependents.cc +++ b/gtk2_ardour/ardour_ui_dependents.cc @@ -52,6 +52,7 @@ ARDOUR_UI::shutdown () session->remove_pending_capture_state (); session = 0; } + ui_config->save_state(); } diff --git a/gtk2_ardour/audio_region_view.cc b/gtk2_ardour/audio_region_view.cc index efd1bdd503..d19abf2ce5 100644 --- a/gtk2_ardour/audio_region_view.cc +++ b/gtk2_ardour/audio_region_view.cc @@ -1220,14 +1220,15 @@ AudioRegionView::set_frame_color () uint32_t r,g,b,a; if (_selected && should_show_selection) { - frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_WaveForm.get(); + UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(), &r, &g, &b, &a); + frame->property_fill_color_rgba() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a); - UINT_TO_RGBA(ARDOUR_UI::config()->canvasvar_FrameBase.get(), &r, &g, &b, &a); for (vector<ArdourCanvas::WaveView*>::iterator w = waves.begin(); w != waves.end(); ++w) { if (_region->muted()) { - (*w)->property_wave_color() = RGBA_TO_UINT(r, g, b, MUTED_ALPHA); + (*w)->property_wave_color() = UINT_RGBA_CHANGE_A(ARDOUR_UI::config()->canvasvar_SelectedWaveForm.get(), MUTED_ALPHA); } else { - (*w)->property_wave_color() = RGBA_TO_UINT(r, g, b, fill_opacity ? fill_opacity : a);// Lets still use the theme's opacity value if Opaque is not set + (*w)->property_wave_color() = ARDOUR_UI::config()->canvasvar_SelectedWaveForm.get(); + (*w)->property_fill_color() = ARDOUR_UI::config()->canvasvar_SelectedWaveFormFill.get(); } } } else { diff --git a/gtk2_ardour/canvas_vars.h b/gtk2_ardour/canvas_vars.h index 354618e176..8085ec0227 100644 --- a/gtk2_ardour/canvas_vars.h +++ b/gtk2_ardour/canvas_vars.h @@ -1,5 +1,7 @@ CANVAS_VARIABLE(canvasvar_WaveForm, "waveform outline") CANVAS_VARIABLE(canvasvar_WaveFormFill, "waveform fill") +CANVAS_VARIABLE(canvasvar_SelectedWaveForm, "selected waveform outline") +CANVAS_VARIABLE(canvasvar_SelectedWaveFormFill, "selected waveform fill") CANVAS_VARIABLE(canvasvar_WaveFormClip, "clipped waveform") CANVAS_VARIABLE(canvasvar_FrameBase, "region base") CANVAS_VARIABLE(canvasvar_SelectedFrameBase, "selected region base") diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 5c93877e07..d26f01f76b 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -1803,12 +1803,25 @@ Editor::add_region_context_items (StreamView* sv, boost::shared_ptr<Region> regi items.push_back (CheckMenuElem (_("Lock"))); CheckMenuItem* region_lock_item = static_cast<CheckMenuItem*>(&items.back()); - fooc = region_lock_item->signal_activate().connect (mem_fun(*this, &Editor::toggle_region_lock)); if (region->locked()) { - fooc.block (true); region_lock_item->set_active(); - fooc.block (false); } + region_lock_item->signal_activate().connect (mem_fun(*this, &Editor::toggle_region_lock)); + + items.push_back (CheckMenuElem (_("Glue to Bars&Beats"))); + CheckMenuItem* bbt_glue_item = static_cast<CheckMenuItem*>(&items.back()); + + switch (region->positional_lock_style()) { + case Region::MusicTime: + bbt_glue_item->set_active (true); + break; + default: + bbt_glue_item->set_active (true); + break; + } + + bbt_glue_item->signal_activate().connect (bind (mem_fun (*this, &Editor::set_region_lock_style), Region::MusicTime)); + items.push_back (CheckMenuElem (_("Mute"))); CheckMenuItem* region_mute_item = static_cast<CheckMenuItem*>(&items.back()); fooc = region_mute_item->signal_activate().connect (mem_fun(*this, &Editor::toggle_region_mute)); @@ -3366,15 +3379,25 @@ Editor::hide_verbose_canvas_cursor () verbose_cursor_visible = false; } +double +Editor::clamp_verbose_cursor_x (double x) +{ + return min (horizontal_adjustment.get_value() + canvas_width - 75.0, x); +} + +double +Editor::clamp_verbose_cursor_y (double y) +{ + return min (vertical_adjustment.get_value() + canvas_height - 50.0, y); +} + void Editor::set_verbose_canvas_cursor (const string & txt, double x, double y) { - /* XXX get origin of canvas relative to root window, - add x and y and check compared to gdk_screen_{width,height} - */ verbose_canvas_cursor->property_text() = txt.c_str(); - verbose_canvas_cursor->property_x() = x; - verbose_canvas_cursor->property_y() = y; + /* don't get too close to the edge */ + verbose_canvas_cursor->property_x() = clamp_verbose_cursor_x (x); + verbose_canvas_cursor->property_y() = clamp_verbose_cursor_x (y); } void diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 56b0077b99..5b4e54f4f6 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -1019,7 +1019,7 @@ class Editor : public PublicEditor void toggle_region_mute (); void toggle_region_lock (); void toggle_region_opaque (); - void toggle_region_position_lock (); + void set_region_lock_style (ARDOUR::Region::PositionLockStyle); void raise_region (); void raise_region_to_top (); void lower_region (); @@ -1028,7 +1028,7 @@ class Editor : public PublicEditor 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 split_region_at_points (boost::shared_ptr<ARDOUR::Region>, ARDOUR::AnalysisFeatureList&); void crop_region_to_selection (); void crop_region_to (nframes_t start, nframes_t end); void set_sync_point (nframes64_t, const RegionSelection&); @@ -1334,6 +1334,8 @@ class Editor : public PublicEditor void show_verbose_time_cursor (nframes_t frame, double offset = 0, double xpos=-1, double ypos=-1); void show_verbose_duration_cursor (nframes_t start, nframes_t end, double offset = 0, double xpos=-1, double ypos=-1); + double clamp_verbose_cursor_x (double); + double clamp_verbose_cursor_y (double); /* Canvas event handlers */ diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index 51752fb6bb..3627a7fe23 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -377,7 +377,7 @@ Editor::register_actions () 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)); + act = ActionManager::register_action (editor_actions, "tab-to-transient-backwards", _("Move Backwards 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)); diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc index a69dcf935a..3de7dc5c21 100644 --- a/gtk2_ardour/editor_canvas_events.cc +++ b/gtk2_ardour/editor_canvas_events.cc @@ -19,6 +19,7 @@ #include <cstdlib> #include <cmath> +#include <algorithm> #include <pbd/stacktrace.h> @@ -43,6 +44,7 @@ #include "i18n.h" using namespace sigc; +using namespace std; using namespace ARDOUR; using namespace PBD; using namespace Gtk; @@ -245,8 +247,8 @@ bool Editor::track_canvas_motion (GdkEvent *ev) { if (verbose_cursor_visible) { - verbose_canvas_cursor->property_x() = ev->motion.x + 20; - verbose_canvas_cursor->property_y() = ev->motion.y + 20; + verbose_canvas_cursor->property_x() = clamp_verbose_cursor_x (ev->motion.x + 20); + verbose_canvas_cursor->property_y() = clamp_verbose_cursor_y (ev->motion.y + 20); } #ifdef GTKOSX diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 9689cdc64a..4234368acd 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -3382,7 +3382,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event) } if (sync_frame - sync_offset <= sync_frame) { - pending_region_position = sync_frame + (sync_dir*sync_offset); + pending_region_position = sync_frame - (sync_dir*sync_offset); } else { pending_region_position = 0; } diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 1f833e8337..77b0883fc1 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -57,6 +57,7 @@ #include "ardour_ui.h" #include "editor.h" #include "time_axis_view.h" +#include "route_time_axis.h" #include "audio_time_axis.h" #include "automation_time_axis.h" #include "streamview.h" @@ -1659,7 +1660,6 @@ Editor::temporal_zoom_region () ensure_entered_region_selected (true); if (selection->regions.empty()) { - info << _("cannot set loop: no region selected") << endmsg; return; } @@ -3540,13 +3540,11 @@ Editor::cut_copy (CutCopyOp op) switch (current_mouse_mode()) { case MouseObject: - cerr << "cutting in object mode\n"; if (!selection->regions.empty() || !selection->points.empty()) { begin_reversible_command (opname + _(" objects")); if (!selection->regions.empty()) { - cerr << "have regions to cut" << endl; cut_copy_regions (op); if (op == Cut) { @@ -3565,7 +3563,6 @@ Editor::cut_copy (CutCopyOp op) commit_reversible_command (); break; // terminate case statement here } - cerr << "nope, now cutting time range" << endl; if (!selection->time.empty()) { /* don't cause suprises */ break; @@ -3575,9 +3572,7 @@ Editor::cut_copy (CutCopyOp op) case MouseRange: if (selection->time.empty()) { nframes64_t start, end; - cerr << "no time selection, get edit op range" << endl; if (!get_edit_op_range (start, end)) { - cerr << "no edit op range" << endl; return; } selection->set ((TimeAxisView*) 0, start, end); @@ -4376,6 +4371,15 @@ Editor::toggle_region_lock () } void +Editor::set_region_lock_style (Region::PositionLockStyle ps) +{ + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + (*i)->region()->set_position_lock_style (ps); + } +} + + +void Editor::toggle_region_mute () { for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { @@ -5013,31 +5017,70 @@ 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 */ + /* 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; + double frames_per_beat = length / m.beats_per_bar(); /* beats per minute = */ double beats_per_minute = (session->frame_rate() * 60.0) / frames_per_beat; + /* now decide whether to: + + (a) set global tempo + (b) add a new tempo marker + + */ + const TempoSection& t (session->tempo_map().tempo_section_at (start)); + bool do_global = false; + + if ((session->tempo_map().n_tempos() == 1) && (session->tempo_map().n_meters() == 1)) { + + /* only 1 tempo & 1 meter: ask if the user wants to set the tempo + at the start, or create a new marker + */ + + vector<string> options; + options.push_back (_("Set global tempo")); + options.push_back (_("Add new marker")); + options.push_back (_("Cancel")); + Choice c (_("Do you want to set the global tempo or add new tempo marker?"), + options); + + switch (c.run()) { + case 0: + do_global = true; + break; + case 2: + return; + + default: + do_global = false; + } + + } else { + + /* more than 1 tempo and/or meter section already, go ahead do the "usual": + if the marker is at the region starter, change it, otherwise add + a new tempo marker + */ + } + begin_reversible_command (_("set tempo from region")); XMLNode& before (session->tempo_map().get_state()); - if (t.frame() == start) { + if (do_global) { + session->tempo_map().change_initial_tempo (beats_per_minute, t.note_type()); + } else 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); @@ -5052,7 +5095,7 @@ Editor::define_one_bar (nframes64_t start, nframes64_t end) void Editor::split_region_at_transients () { - vector<nframes64_t> positions; + AnalysisFeatureList positions; if (!session) { return; @@ -5088,7 +5131,7 @@ Editor::split_region_at_transients () } void -Editor::split_region_at_points (boost::shared_ptr<Region> r, vector<nframes64_t>& positions) +Editor::split_region_at_points (boost::shared_ptr<Region> r, AnalysisFeatureList& positions) { boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r); @@ -5106,7 +5149,7 @@ Editor::split_region_at_points (boost::shared_ptr<Region> r, vector<nframes64_t> return; } - vector<nframes64_t>::const_iterator x; + AnalysisFeatureList::const_iterator x; nframes64_t pos = ar->position(); @@ -5163,30 +5206,52 @@ Editor::split_region_at_points (boost::shared_ptr<Region> r, vector<nframes64_t> void Editor::tab_to_transient (bool forward) { - - vector<nframes64_t> positions; + AnalysisFeatureList positions; if (!session) { return; } - ExclusiveRegionSelection esr (*this, entered_regionview); + nframes64_t pos = session->audible_frame (); - if (selection->regions.empty()) { - return; - } - - boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (selection->regions.front()->region()); + if (!selection->tracks.empty()) { - if (!ar) { - return; + for (TrackSelection::iterator t = selection->tracks.begin(); t != selection->tracks.end(); ++t) { + + RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*t); + + if (rtv) { + boost::shared_ptr<Diskstream> ds = rtv->get_diskstream(); + if (ds) { + boost::shared_ptr<Playlist> pl = rtv->get_diskstream()->playlist (); + if (pl) { + nframes64_t result = pl->find_next_transient (pos, forward ? 1 : -1); + + if (result >= 0) { + positions.push_back (result); + } + } + } + } + } + + } else { + + ExclusiveRegionSelection esr (*this, entered_regionview); + + if (selection->regions.empty()) { + return; + } + + for (RegionSelection::iterator r = selection->regions.begin(); r != selection->regions.end(); ++r) { + (*r)->region()->get_transients (positions); + } } - ar->get_transients (positions); - nframes64_t pos = session->audible_frame (); + TransientDetector::cleanup_transients (positions, session->frame_rate(), 3.0); if (forward) { - vector<nframes64_t>::iterator x; + AnalysisFeatureList::iterator x; for (x = positions.begin(); x != positions.end(); ++x) { if ((*x) > pos) { @@ -5199,7 +5264,7 @@ Editor::tab_to_transient (bool forward) } } else { - vector<nframes64_t>::reverse_iterator x; + AnalysisFeatureList::reverse_iterator x; for (x = positions.rbegin(); x != positions.rend(); ++x) { if ((*x) < pos) { diff --git a/gtk2_ardour/editor_tempodisplay.cc b/gtk2_ardour/editor_tempodisplay.cc index 591c3d12fe..39c74323f3 100644 --- a/gtk2_ardour/editor_tempodisplay.cc +++ b/gtk2_ardour/editor_tempodisplay.cc @@ -365,10 +365,14 @@ Editor::edit_tempo_section (TempoSection* section) tempo_dialog.get_bbt_time(when); bpm = max (0.01, bpm); + cerr << "Editing tempo section to be at " << when << endl; + session->tempo_map().dump (cerr); begin_reversible_command (_("replace tempo mark")); XMLNode &before = session->tempo_map().get_state(); session->tempo_map().replace_tempo (*section, Tempo (bpm,nt)); + session->tempo_map().dump (cerr); session->tempo_map().move_tempo (*section, when); + session->tempo_map().dump (cerr); XMLNode &after = session->tempo_map().get_state(); session->add_command (new MementoCommand<TempoMap>(session->tempo_map(), &before, &after)); commit_reversible_command (); diff --git a/gtk2_ardour/engine_dialog.cc b/gtk2_ardour/engine_dialog.cc index 9835901a24..4e5e068f65 100644 --- a/gtk2_ardour/engine_dialog.cc +++ b/gtk2_ardour/engine_dialog.cc @@ -553,10 +553,12 @@ EngineControl::setup_engine () error << string_compose (_("cannot open JACK rc file %1 to store parameters"), jackdrc_path) << endmsg; return -1; } - + cerr << "JACK COMMAND: "; for (vector<string>::iterator i = args.begin(); i != args.end(); ++i) { + cerr << (*i) << ' '; jackdrc << (*i) << ' '; } + cerr << endl; jackdrc << endl; jackdrc.close (); @@ -915,7 +917,7 @@ EngineControl::find_jack_servers (vector<string>& strings) _NSGetExecutablePath (execpath, &pathsz); - Glib::ustring path (Glib::path_get_dirname (execpath)); + string path (Glib::path_get_dirname (execpath)); path += "/jackd"; if (Glib::file_test (path, FILE_TEST_EXISTS)) { @@ -937,8 +939,36 @@ EngineControl::find_jack_servers (vector<string>& strings) PathScanner scanner; vector<string *> *jack_servers; std::map<string,int> un; - - path = getenv ("PATH"); + char *p; + bool need_minimal_path = false; + + p = getenv ("PATH"); + + if (p && *p) { + path = p; + } else { + need_minimal_path = true; + } + +#ifdef __APPLE__ + // many mac users don't have PATH set up to include + // likely installed locations of JACK + need_minimal_path = true; +#endif + + if (need_minimal_path) { + if (path.empty()) { + path = "/usr/bin:/bin:/usr/local/bin:/opt/local/bin"; + } else { + path += ":/usr/local/bin:/opt/local/bin"; + } + } + +#ifdef __APPLE__ + // push it back into the environment so that auto-started JACK can find it. + // XXX why can't we just expect OS X users to have PATH set correctly? we can't ... + setenv ("PATH", path.c_str(), 1); +#endif jack_servers = scanner (path, jack_server_filter, 0, false, true); diff --git a/gtk2_ardour/generic_pluginui.cc b/gtk2_ardour/generic_pluginui.cc index ed185c33bc..6e3119b373 100644 --- a/gtk2_ardour/generic_pluginui.cc +++ b/gtk2_ardour/generic_pluginui.cc @@ -38,7 +38,9 @@ #include <ardour/plugin.h> #include <ardour/plugin_insert.h> #include <ardour/ladspa_plugin.h> +#ifdef HAVE_LV2 #include <ardour/lv2_plugin.h> +#endif #include <lrdf.h> @@ -385,8 +387,9 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat if (plugin->parameter_is_input (port_index)) { boost::shared_ptr<LadspaPlugin> lp; +#ifdef HAVE_LV2 boost::shared_ptr<LV2Plugin> lv2p; - +#endif if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) { // FIXME: not all plugins have a numeric unique ID @@ -409,6 +412,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat return control_ui; } +#ifdef HAVE_LV2 } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) { SLV2Port port = lv2p->slv2_port(port_index); @@ -428,6 +432,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat slv2_scale_points_free(points); return control_ui; } +#endif } if (desc.toggled) { @@ -757,7 +762,9 @@ GenericPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui) { vector<string> enums; boost::shared_ptr<LadspaPlugin> lp; +#ifdef HAVE_LV2 boost::shared_ptr<LV2Plugin> lv2p; +#endif if ((lp = boost::dynamic_pointer_cast<LadspaPlugin>(plugin)) != 0) { // all LADPSA plugins have a numeric unique ID @@ -777,6 +784,7 @@ GenericPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui) lrdf_free_setting_values(defaults); } +#ifdef HAVE_LV2 } else if ((lv2p = boost::dynamic_pointer_cast<LV2Plugin>(plugin)) != 0) { SLV2Port port = lv2p->slv2_port(port_index); @@ -797,6 +805,7 @@ GenericPluginUI::setup_scale_values(guint32 port_index, ControlUI* cui) } slv2_scale_points_free(points); +#endif } diff --git a/gtk2_ardour/icons/ferret_02.png b/gtk2_ardour/icons/ferret_02.png Binary files differnew file mode 100644 index 0000000000..a6d625b64e --- /dev/null +++ b/gtk2_ardour/icons/ferret_02.png diff --git a/gtk2_ardour/new_session_dialog.cc b/gtk2_ardour/new_session_dialog.cc index 1b9d81df22..e995ff1a8e 100644 --- a/gtk2_ardour/new_session_dialog.cc +++ b/gtk2_ardour/new_session_dialog.cc @@ -971,6 +971,8 @@ NewSessionDialog::reset_recent() i != session_directories.end(); ++i) { std::vector<sys::path> state_file_paths; + std::vector<std::string*>* states; + const string fullpath = (*i).to_string(); // now get available states for this session @@ -980,28 +982,37 @@ NewSessionDialog::reset_recent() // no state file? continue; } - - std::vector<string> state_file_names(get_file_names_no_extension (state_file_paths)); - + + /* check whether session still exists */ + if (!Glib::file_test(fullpath, Glib::FILE_TEST_EXISTS)) { + /* session doesn't exist */ + continue; + } + + /* now get available states for this session */ + + if ((states = ARDOUR::Session::possible_states (fullpath)) == 0) { + /* no state file? */ + continue; + } + Gtk::TreeModel::Row row = *(recent_model->append()); - - const string fullpath = (*i).to_string(); row[recent_columns.visible_name] = Glib::path_get_basename (fullpath); row[recent_columns.fullpath] = fullpath; - if (state_file_names.size() > 1) { - - // add the children - - for (std::vector<std::string>::iterator i2 = state_file_names.begin(); - i2 != state_file_names.end(); ++i2) - { - - Gtk::TreeModel::Row child_row = *(recent_model->append (row.children())); - - child_row[recent_columns.visible_name] = *i2; + if (states->size()) { + + /* add the children */ + + for (std::vector<std::string*>::iterator i2 = states->begin(); i2 != states->end(); ++i2) { + + Gtk::TreeModel::Row child_row = *(recent_model->append (row.children())); + + child_row[recent_columns.visible_name] = **i2; child_row[recent_columns.fullpath] = fullpath; + + delete *i2; } } } diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index f279f93c39..3f2c90e05a 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -254,7 +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; + virtual void split_region_at_points (boost::shared_ptr<ARDOUR::Region>, ARDOUR::AnalysisFeatureList&) = 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 index 980b36e1d1..d8d2f3c28b 100644 --- a/gtk2_ardour/rhythm_ferret.cc +++ b/gtk2_ardour/rhythm_ferret.cc @@ -13,6 +13,7 @@ #include "rhythm_ferret.h" #include "audio_region_view.h" #include "public_editor.h" +#include "utils.h" #include "i18n.h" @@ -57,9 +58,9 @@ RhythmFerret::RhythmFerret (PublicEditor& e) { 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); + upper_hpacker.pack_start (selection_frame, true, true); + upper_hpacker.pack_start (operation_frame, true, true); op_packer.pack_start (region_split_button, false, false); op_packer.pack_start (tempo_button, false, false); @@ -107,15 +108,17 @@ RhythmFerret::RhythmFerret (PublicEditor& e) analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis)); ferret_frame.add (ferret_packer); - - // Glib::RefPtr<Pixbuf> logo_pixbuf ("somefile"); + logo = manage (new Gtk::Image (::get_icon (X_("ferret_02")))); + if (logo) { lower_hpacker.pack_start (*logo, false, false); } - lower_hpacker.pack_start (operation_clarification_label, false, false); + lower_hpacker.pack_start (operation_clarification_label, true, true); lower_hpacker.pack_start (action_button, false, false); + lower_hpacker.set_border_width (6); + lower_hpacker.set_spacing (6); action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action)); @@ -194,13 +197,13 @@ RhythmFerret::run_analysis () } int -RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, vector<nframes64_t>& results) +RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results) { TransientDetector t (session->frame_rate()); for (uint32_t i = 0; i < readable->n_channels(); ++i) { - vector<nframes64_t> these_results; + AnalysisFeatureList these_results; t.reset (); t.set_threshold (detection_threshold_adjustment.get_value()); @@ -212,38 +215,18 @@ RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readabl /* translate all transients to give absolute position */ - for (vector<nframes64_t>::iterator i = these_results.begin(); i != these_results.end(); ++i) { - (*i) += offset; + for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) { + (*x) += offset; } /* merge */ results.insert (results.end(), these_results.begin(), these_results.end()); + these_results.clear (); } - - 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; - } - } + if (!results.empty()) { + TransientDetector::cleanup_transients (results, session->frame_rate(), trigger_gap_adjustment.get_value()); } return 0; diff --git a/gtk2_ardour/rhythm_ferret.h b/gtk2_ardour/rhythm_ferret.h index 36d4450939..891b447a11 100644 --- a/gtk2_ardour/rhythm_ferret.h +++ b/gtk2_ardour/rhythm_ferret.h @@ -84,17 +84,17 @@ class RhythmFerret : public ArdourDialog { std::vector<std::string> analysis_mode_strings; - std::vector<nframes64_t> current_results; + ARDOUR::AnalysisFeatureList 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); + int run_percussion_onset_analysis (boost::shared_ptr<ARDOUR::Readable> region, nframes64_t offset, ARDOUR::AnalysisFeatureList& results); void do_action (); void do_split_action (); - void do_region_split (RegionView* rv, const std::vector<nframes64_t>&); + void do_region_split (RegionView* rv, const ARDOUR::AnalysisFeatureList&); }; #endif /* __gtk2_ardour_rhythm_ferret_h__ */ diff --git a/gtk2_ardour/splash.cc b/gtk2_ardour/splash.cc index 9317ec1caa..e901521e37 100644 --- a/gtk2_ardour/splash.cc +++ b/gtk2_ardour/splash.cc @@ -14,6 +14,8 @@ using namespace Glib; using namespace std; using namespace ARDOUR; +Splash* Splash::the_splash = 0; + Splash::Splash () { sys::path splash_file; @@ -47,6 +49,14 @@ Splash::Splash () darea.signal_expose_event().connect (mem_fun (*this, &Splash::expose)); add (darea); + + the_splash = this; +} + +void +Splash::pop_back () +{ + set_keep_above (false); } void diff --git a/gtk2_ardour/splash.h b/gtk2_ardour/splash.h index 5ba5478941..07532d9344 100644 --- a/gtk2_ardour/splash.h +++ b/gtk2_ardour/splash.h @@ -34,13 +34,19 @@ class Splash : public Gtk::Window Splash (); ~Splash () {} + static Splash* instance() { return the_splash; } + + void pop_back (); + bool expose (GdkEventExpose*); bool on_button_release_event (GdkEventButton*); void on_realize (); - + void message (const std::string& msg); private: + static Splash* the_splash; + Glib::RefPtr<Gdk::Pixbuf> pixbuf; Gtk::DrawingArea darea; Glib::RefPtr<Pango::Layout> layout; diff --git a/gtk2_ardour/tempo_dialog.cc b/gtk2_ardour/tempo_dialog.cc index 0c99a4eeb0..6f46ec1a9d 100644 --- a/gtk2_ardour/tempo_dialog.cc +++ b/gtk2_ardour/tempo_dialog.cc @@ -243,7 +243,6 @@ TempoDialog::get_note_type () } } - cerr << "returning " << note_type << " based on " << text << endl; return note_type; } diff --git a/gtk2_ardour/time_axis_view.cc b/gtk2_ardour/time_axis_view.cc index a3262baec5..abe4e93043 100644 --- a/gtk2_ardour/time_axis_view.cc +++ b/gtk2_ardour/time_axis_view.cc @@ -1104,7 +1104,7 @@ TimeAxisView::covers_y_position (double y) } void -TimeAxisView::show_temporary_lines (const vector<nframes64_t>& pos) +TimeAxisView::show_temporary_lines (const AnalysisFeatureList& pos) { while (temp_lines.size()< pos.size()) { ArdourCanvas::SimpleLine* l = new ArdourCanvas::SimpleLine (*canvas_display); @@ -1120,7 +1120,7 @@ TimeAxisView::show_temporary_lines (const vector<nframes64_t>& pos) delete line; } - vector<nframes64_t>::const_iterator i; + AnalysisFeatureList::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) { diff --git a/gtk2_ardour/time_axis_view.h b/gtk2_ardour/time_axis_view.h index f3bdbec706..c210d6deb0 100644 --- a/gtk2_ardour/time_axis_view.h +++ b/gtk2_ardour/time_axis_view.h @@ -172,7 +172,7 @@ 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 show_temporary_lines (const ARDOUR::AnalysisFeatureList&); virtual void hide_temporary_lines (); virtual void set_samples_per_unit (double); diff --git a/gtk2_ardour/utils.cc b/gtk2_ardour/utils.cc index a523878446..3a61a8676a 100644 --- a/gtk2_ardour/utils.cc +++ b/gtk2_ardour/utils.cc @@ -466,6 +466,16 @@ key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev) int ret = false; switch (ev->keyval) { + case GDK_Tab: + ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_nabla, GdkModifierType(ev->state)); + break; + + // some X and/or GDK implementations do Shift-Tab -> GDK_ISO_Left_Tab + + case GDK_ISO_Left_Tab: + ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_nabla, GdkModifierType(ev->state)); + break; + case GDK_Up: ret = gtk_accel_groups_activate(G_OBJECT(win), GDK_uparrow, GdkModifierType(ev->state)); break; diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index c407917263..a4ffa4034e 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -31,6 +31,7 @@ amp.cc audio_buffer.cc auto_bundle.cc user_bundle.cc +analyser.cc audioanalyser.cc audio_diskstream.cc audio_library.cc diff --git a/libs/ardour/analyser.cc b/libs/ardour/analyser.cc new file mode 100644 index 0000000000..7ddb5428e9 --- /dev/null +++ b/libs/ardour/analyser.cc @@ -0,0 +1,119 @@ +/* + 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. + +*/ + +#include <ardour/analyser.h> +#include <ardour/audiofilesource.h> +#include <ardour/transient_detector.h> + +#include <pbd/pthread_utils.h> +#include <pbd/convert.h> + +using namespace std; +using namespace sigc; +using namespace ARDOUR; +using namespace PBD; + +Analyser* Analyser::the_analyser = 0; +Glib::StaticMutex Analyser::analysis_queue_lock = GLIBMM_STATIC_MUTEX_INIT; +Glib::Cond* Analyser::SourcesToAnalyse = 0; +list<boost::weak_ptr<Source> > Analyser::analysis_queue; + +Analyser::Analyser () +{ + +} + +Analyser::~Analyser () +{ +} + +static void +analyser_work () +{ + Analyser::work (); +} + +void +Analyser::init () +{ + SourcesToAnalyse = new Glib::Cond(); + Glib::Thread::create (sigc::ptr_fun (analyser_work), false); +} + +void +Analyser::queue_source_for_analysis (boost::shared_ptr<Source> src, bool force) +{ + if (!src->can_be_analysed()) { + return; + } + + if (!force && src->has_been_analysed()) { + return; + } + + Glib::Mutex::Lock lm (analysis_queue_lock); + analysis_queue.push_back (boost::weak_ptr<Source>(src)); + SourcesToAnalyse->broadcast (); +} + +void +Analyser::work () +{ + PBD::ThreadCreated (pthread_self(), string ("analyser-") + to_string (pthread_self(), std::dec)); + + while (true) { + analysis_queue_lock.lock (); + + wait: + if (analysis_queue.empty()) { + SourcesToAnalyse->wait (analysis_queue_lock); + } + + if (analysis_queue.empty()) { + goto wait; + } + + boost::shared_ptr<Source> src (analysis_queue.front().lock()); + analysis_queue.pop_front(); + analysis_queue_lock.unlock (); + + boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (src); + + if (afs) { + analyse_audio_file_source (afs); + } + } +} + +void +Analyser::analyse_audio_file_source (boost::shared_ptr<AudioFileSource> src) +{ + AnalysisFeatureList results; + + TransientDetector td (src->sample_rate()); + + if (td.run (src->get_transients_path(), src.get(), 0, results) == 0) { + src->set_been_analysed (true); + } else { + src->set_been_analysed (false); + } + +} + + diff --git a/libs/ardour/ardour/analyser.h b/libs/ardour/ardour/analyser.h new file mode 100644 index 0000000000..8771cab6b0 --- /dev/null +++ b/libs/ardour/ardour/analyser.h @@ -0,0 +1,35 @@ +#ifndef __ardour_analyser_h__ +#define __ardour_analyser_h__ + +#include <glibmm/thread.h> +#include <boost/shared_ptr.hpp> + +namespace ARDOUR { + +class AudioFileSource; +class Source; +class TransientDetector; + +class Analyser { + + public: + Analyser(); + ~Analyser (); + + static void init (); + static void queue_source_for_analysis (boost::shared_ptr<Source>, bool force); + static void work (); + + private: + static Analyser* the_analyser; + static Glib::StaticMutex analysis_queue_lock; + static Glib::Cond* SourcesToAnalyse; + static std::list<boost::weak_ptr<Source> > analysis_queue; + + static void analyse_audio_file_source (boost::shared_ptr<AudioFileSource>); +}; + + +} + +#endif /* __ardour_analyser_h__ */ diff --git a/libs/ardour/ardour/audioanalyser.h b/libs/ardour/ardour/audioanalyser.h index dbd8a52d5a..06b841990a 100644 --- a/libs/ardour/ardour/audioanalyser.h +++ b/libs/ardour/ardour/audioanalyser.h @@ -40,7 +40,7 @@ class AudioAnalyser { 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 diff --git a/libs/ardour/ardour/audiofilesource.h b/libs/ardour/ardour/audiofilesource.h index 4d80c8ddf5..0e8b6e4fde 100644 --- a/libs/ardour/ardour/audiofilesource.h +++ b/libs/ardour/ardour/audiofilesource.h @@ -125,6 +125,8 @@ class AudioFileSource : public AudioSource { virtual void handle_header_position_change () {} + bool can_be_analysed() const { return _length > 0; } + protected: /* constructor to be called for existing external-to-session files */ diff --git a/libs/ardour/ardour/audioregion.h b/libs/ardour/ardour/audioregion.h index 2c5630aec0..3fddfc55b4 100644 --- a/libs/ardour/ardour/audioregion.h +++ b/libs/ardour/ardour/audioregion.h @@ -134,7 +134,7 @@ class AudioRegion : public Region void resume_fade_in (); void resume_fade_out (); - int get_transients (std::vector<nframes64_t>&, bool force_new = false); + int get_transients (AnalysisFeatureList&, bool force_new = false); private: friend class RegionFactory; @@ -170,6 +170,7 @@ class AudioRegion : public Region void fade_out_changed (); void source_offset_changed (); void listen_to_my_curves (); + void listen_to_my_sources (); boost::shared_ptr<AutomationList> _fade_in; FadeShape _fade_in_shape; @@ -187,11 +188,6 @@ 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 b11174abe8..1589841baa 100644 --- a/libs/ardour/ardour/audiosource.h +++ b/libs/ardour/ardour/audiosource.h @@ -111,14 +111,12 @@ 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; bool _peaks_built; + bool _analysed; mutable Glib::Mutex _lock; mutable Glib::Mutex _peaks_ready_lock; Glib::ustring peakpath; @@ -145,9 +143,7 @@ class AudioSource : public Source, public boost::enable_shared_from_this<ARDOUR: double samples_per_visual_peak, nframes_t fpp) const; 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&); + bool intermediate_peaks_ready_signal, nframes_t frames_per_peak); private: int peakfile; diff --git a/libs/ardour/ardour/configuration_vars.h b/libs/ardour/ardour/configuration_vars.h index 99776929a4..134557daeb 100644 --- a/libs/ardour/ardour/configuration_vars.h +++ b/libs/ardour/ardour/configuration_vars.h @@ -53,6 +53,7 @@ CONFIG_VARIABLE (float, track_buffer_seconds, "track-buffer-seconds", 5.0) CONFIG_VARIABLE (uint32_t, disk_choice_space_threshold, "disk-choice-space-threshold", 57600000) CONFIG_VARIABLE (SampleFormat, native_file_data_format, "native-file-data-format", ARDOUR::FormatFloat) CONFIG_VARIABLE (HeaderFormat, native_file_header_format, "native-file-header-format", ARDOUR::WAVE) +CONFIG_VARIABLE (bool, auto_analyse_audio, "auto-analyse-audio", true) /* OSC */ diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h index 3f328de005..d8a38841f8 100644 --- a/libs/ardour/ardour/playlist.h +++ b/libs/ardour/ardour/playlist.h @@ -96,6 +96,7 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla void duplicate (boost::shared_ptr<Region>, nframes_t position, float times); void nudge_after (nframes_t start, nframes_t distance, bool forwards); void shuffle (boost::shared_ptr<Region>, int dir); + void update_after_tempo_map_change (); boost::shared_ptr<Playlist> cut (list<AudioRange>&, bool result_is_hidden = true); boost::shared_ptr<Playlist> copy (list<AudioRange>&, bool result_is_hidden = true); @@ -110,6 +111,8 @@ class Playlist : public SessionObject, public boost::enable_shared_from_this<Pla nframes64_t find_next_region_boundary (nframes64_t frame, int dir); bool region_is_shuffle_constrained (boost::shared_ptr<Region>); + nframes64_t find_next_transient (nframes64_t position, int dir); + template<class T> void foreach_region (T *t, void (T::*func)(boost::shared_ptr<Region>, void *), void *arg); template<class T> void foreach_region (T *t, void (T::*func)(boost::shared_ptr<Region>)); diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index da07c580b4..d793e8ca23 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -73,6 +73,11 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> range_guarantoor = USHRT_MAX }; + enum PositionLockStyle { + AudioTime, + MusicTime + }; + static const Flag DefaultFlags = Flag (Opaque|DefaultFadeIn|DefaultFadeOut|FadeIn|FadeOut); static Change FadeChanged; @@ -130,6 +135,9 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> bool captured() const { return !(_flags & (Region::Flag (Region::Import|Region::External))); } bool can_move() const { return !(_flags & (Locked|PositionLocked)); } + PositionLockStyle positional_lock_style() const { return _positional_lock_style; } + void set_position_lock_style (PositionLockStyle ps); + virtual bool should_save_state () const { return !(_flags & DoNotSaveState); }; void freeze (); @@ -156,6 +164,7 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> void set_position (nframes_t, void *src); void set_position_on_top (nframes_t, void *src); void special_set_position (nframes_t); + void update_position_after_tempo_map_change (); void nudge_position (nframes64_t, void *src); bool at_natural_position () const; @@ -213,6 +222,13 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> virtual bool is_dependent() const { return false; } virtual bool depends_on (boost::shared_ptr<Region> other) const { return false; } + virtual int get_transients (AnalysisFeatureList&, bool force_new = false) { + // no transients, but its OK + return 0; + } + + void invalidate_transients (); + protected: friend class RegionFactory; @@ -226,8 +242,6 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> Region (boost::shared_ptr<Source> src, const XMLNode&); Region (const SourceList& srcs, const XMLNode&); - /* this one is for derived types of derived types */ - Region (Session& s, nframes_t start, nframes_t length, const string& name, DataType, layer_t = 0, Flag flags = DefaultFlags); protected: @@ -236,6 +250,7 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> void send_change (Change); void trim_to_internal (nframes_t position, nframes_t length, void *src); + void set_position_internal (nframes_t pos, bool allow_bbt_recompute); bool copied() const { return _flags & Copied; } void maybe_uncopy (); @@ -256,6 +271,7 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> nframes_t _last_length; nframes_t _position; nframes_t _last_position; + PositionLockStyle _positional_lock_style; nframes_t _sync_position; layer_t _layer; mutable RegionEditState _first_edit; @@ -264,6 +280,9 @@ class Region : public Automatable, public boost::enable_shared_from_this<Region> nframes64_t _ancestral_length; float _stretch; float _shift; + BBT_Time _bbt_time; + AnalysisFeatureList _transients; + bool _valid_transients; mutable uint32_t _read_data_count; ///< modified in read() Change _pending_changed; uint64_t _last_layer_op; ///< timestamp diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index d27df2f7ea..c0c178eb02 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -389,7 +389,10 @@ class Session : public PBD::StatefulDestructible nframes_t get_maximum_extent () const; nframes_t current_end_frame() const { return end_location->start(); } nframes_t current_start_frame() const { return start_location->start(); } + // "actual" sample rate of session, set by current audioengine rate, pullup/down etc. nframes_t frame_rate() const { return _current_frame_rate; } + // "native" sample rate of session, regardless of current audioengine rate, pullup/down etc + nframes_t nominal_frame_rate() const { return _nominal_frame_rate; } nframes_t frames_per_hour() const { return _frames_per_hour; } double frames_per_smpte_frame() const { return _frames_per_smpte_frame; } @@ -432,6 +435,9 @@ class Session : public PBD::StatefulDestructible sigc::signal<void,string> StateSaved; sigc::signal<void> StateReady; + + vector<string*>* possible_states() const; + static vector<string*>* possible_states(string path); XMLNode& get_state(); int set_state(const XMLNode& node); // not idempotent @@ -624,6 +630,12 @@ class Session : public PBD::StatefulDestructible sigc::signal<int,boost::shared_ptr<ARDOUR::Playlist> > AskAboutPlaylistDeletion; + /* handlers should return 0 for "ignore the rate mismatch" + and !0 for "do not use this session" + */ + + static sigc::signal<int,nframes_t, nframes_t> AskAboutSampleRateMismatch; + /* handlers should return !0 for use pending state, 0 for ignore it. */ @@ -981,6 +993,7 @@ class Session : public PBD::StatefulDestructible bool waiting_for_sync_offset; nframes_t _base_frame_rate; nframes_t _current_frame_rate; //this includes video pullup offset + nframes_t _nominal_frame_rate; //ignores audioengine setting, "native" SR int transport_sub_state; mutable gint _record_status; volatile nframes_t _transport_frame; diff --git a/libs/ardour/ardour/silentfilesource.h b/libs/ardour/ardour/silentfilesource.h index e0103185c2..87065c1cd0 100644 --- a/libs/ardour/ardour/silentfilesource.h +++ b/libs/ardour/ardour/silentfilesource.h @@ -35,6 +35,7 @@ class SilentFileSource : public AudioFileSource { void set_length (nframes_t len); bool destructive() const { return false; } + bool can_be_analysed() const { return false; } protected: diff --git a/libs/ardour/ardour/source.h b/libs/ardour/ardour/source.h index 174e58c61b..3109cb00ff 100644 --- a/libs/ardour/ardour/source.h +++ b/libs/ardour/ardour/source.h @@ -78,14 +78,27 @@ class Source : public SessionObject, public ARDOUR::Readable static sigc::signal<void,Source*> SourceCreated; sigc::signal<void,boost::shared_ptr<Source> > Switched; + bool has_been_analysed() const; + virtual bool can_be_analysed() const { return false; } + virtual void set_been_analysed (bool yn); + virtual bool check_for_analysis_data_on_disk(); + + sigc::signal<void> AnalysisChanged; + + AnalysisFeatureList transients; + std::string get_transients_path() const; + int load_transients (const std::string&); + protected: void update_length (nframes_t pos, nframes_t cnt); - DataType _type; - time_t _timestamp; - nframes_t _length; - - Glib::Mutex playlist_lock; + DataType _type; + time_t _timestamp; + nframes_t _length; + bool _analysed; + mutable Glib::Mutex _analysis_lock; + Glib::Mutex _playlist_lock; + typedef std::map<boost::shared_ptr<ARDOUR::Playlist>, uint32_t > PlaylistMap; PlaylistMap _playlists; diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index dc49f5cdef..c4915072c5 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -279,6 +279,10 @@ class TempoMap : public PBD::StatefulDestructible void bbt_time_with_metric (nframes_t, BBT_Time&, const Metric&) const; void change_existing_tempo_at (nframes_t, double bpm, double note_type); + void change_initial_tempo (double bpm, double note_type); + + int n_tempos () const; + int n_meters () const; sigc::signal<void,ARDOUR::Change> StateChanged; @@ -286,12 +290,12 @@ class TempoMap : public PBD::StatefulDestructible static Tempo _default_tempo; static Meter _default_meter; - Metrics *metrics; - nframes_t _frame_rate; - nframes_t last_bbt_when; - bool last_bbt_valid; - BBT_Time last_bbt; - mutable Glib::RWLock lock; + Metrics* metrics; + nframes_t _frame_rate; + nframes_t last_bbt_when; + bool last_bbt_valid; + BBT_Time last_bbt; + mutable Glib::RWLock lock; void timestamp_metrics (bool use_bbt); diff --git a/libs/ardour/ardour/transient_detector.h b/libs/ardour/ardour/transient_detector.h index c65bae3ed5..259b79176f 100644 --- a/libs/ardour/ardour/transient_detector.h +++ b/libs/ardour/ardour/transient_detector.h @@ -34,17 +34,23 @@ class TransientDetector : public AudioAnalyser TransientDetector (float sample_rate); ~TransientDetector(); + static std::string operational_identifier(); + 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); + int run (const std::string& path, Readable*, uint32_t channel, AnalysisFeatureList& results); - protected: - std::vector<nframes64_t>* current_results; + static void cleanup_transients (AnalysisFeatureList&, float sr, float gap_msecs); + + protected: + AnalysisFeatureList* current_results; int use_features (Vamp::Plugin::FeatureSet&, std::ostream*); + + static std::string _op_id; }; } /* namespace */ diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index cc756b0ec6..fdf8d0b439 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -392,6 +392,8 @@ namespace ARDOUR { int opts; // really RubberBandStretcher::Options }; + typedef std::list<nframes64_t> AnalysisFeatureList; + } // namespace ARDOUR std::istream& operator>>(std::istream& o, ARDOUR::SampleFormat& sf); diff --git a/libs/ardour/audio_buffer.cc b/libs/ardour/audio_buffer.cc index 8444304832..915fdeb948 100644 --- a/libs/ardour/audio_buffer.cc +++ b/libs/ardour/audio_buffer.cc @@ -17,6 +17,10 @@ */ #include <ardour/audio_buffer.h> +#include <pbd/error.h> +#include <errno.h> + +#include "i18n.h" #ifdef __x86_64__ static const int CPU_CACHE_ALIGN = 64; @@ -24,6 +28,8 @@ static const int CPU_CACHE_ALIGN = 64; static const int CPU_CACHE_ALIGN = 16; /* arguably 32 on most arches, but it matters less */ #endif +using namespace PBD; + namespace ARDOUR { @@ -63,7 +69,10 @@ AudioBuffer::resize (size_t size) #ifdef NO_POSIX_MEMALIGN _data = (Sample *) malloc(sizeof(Sample) * _capacity); #else - posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Sample) * _capacity); + if (posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Sample) * _capacity)) { + fatal << string_compose (_("Memory allocation error: posix_memalign (%1 * %2) failed (%3)"), + CPU_CACHE_ALIGN, sizeof (Sample) * _capacity, strerror (errno)) << endmsg; + } #endif _owns_data = true; diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index d65183a84f..71ff814900 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -39,6 +39,7 @@ #include <ardour/ardour.h> #include <ardour/audioengine.h> +#include <ardour/analyser.h> #include <ardour/audio_diskstream.h> #include <ardour/utils.h> #include <ardour/configuration.h> @@ -1569,6 +1570,7 @@ AudioDiskstream::transport_stopped (struct tm& when, time_t twhen, bool abort_ca s->update_header (capture_info.front()->start, when, twhen); s->set_captured_for (_name); s->mark_immutable (); + Analyser::queue_source_for_analysis (s, true); } } diff --git a/libs/ardour/audioanalyser.cc b/libs/ardour/audioanalyser.cc index 4cc99a5d5e..9da404e5bf 100644 --- a/libs/ardour/audioanalyser.cc +++ b/libs/ardour/audioanalyser.cc @@ -18,13 +18,19 @@ using namespace ARDOUR; AudioAnalyser::AudioAnalyser (float sr, AnalysisPluginKey key) : sample_rate (sr) - , plugin (0) , plugin_key (key) { + /* create VAMP plugin and initialize */ + + if (initialize_plugin (plugin_key, sample_rate)) { + error << string_compose (_("cannot load VAMP plugin \"%1\""), key) << endmsg; + throw failed_constructor(); + } } AudioAnalyser::~AudioAnalyser () { + delete plugin; } int @@ -73,29 +79,28 @@ int AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) { ofstream ofile; - Plugin::FeatureSet onsets; + Plugin::FeatureSet features; int ret = -1; bool done = false; Sample* data = 0; nframes64_t len = src->readable_length(); nframes64_t pos = 0; float* bufs[1] = { 0 }; + string tmp_path; if (!path.empty()) { - ofile.open (path.c_str()); + + /* store data in tmp file, not the real one */ + + tmp_path = path; + tmp_path += ".tmp"; + + ofile.open (tmp_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; @@ -108,7 +113,6 @@ AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) to_read = min ((len - pos), bufsize); if (src->read (data, pos, to_read, channel) != to_read) { - cerr << "bad read\n"; goto out; } @@ -118,14 +122,14 @@ AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) memset (data + to_read, 0, (bufsize - to_read)); } - onsets = plugin->process (bufs, RealTime::fromSeconds ((double) pos / sample_rate)); + features = plugin->process (bufs, RealTime::fromSeconds ((double) pos / sample_rate)); - if (use_features (onsets, (path.empty() ? &ofile : 0))) { + if (use_features (features, (path.empty() ? 0 : &ofile))) { goto out; } pos += stepsize; - + if (pos >= len) { done = true; } @@ -133,9 +137,9 @@ AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) /* finish up VAMP plugin */ - onsets = plugin->getRemainingFeatures (); + features = plugin->getRemainingFeatures (); - if (use_features (onsets, (path.empty() ? &ofile : 0))) { + if (use_features (features, (path.empty() ? &ofile : 0))) { goto out; } @@ -146,10 +150,14 @@ AudioAnalyser::analyse (const string& path, Readable* src, uint32_t channel) ofile.close (); if (ret) { - g_remove (path.c_str()); + g_remove (tmp_path.c_str()); + } else if (!path.empty()) { + /* move the data file to the requested path */ + g_rename (tmp_path.c_str(), path.c_str()); } + if (data) { - delete data; + delete [] data; } return ret; diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 69502f9922..da4f8a642c 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -857,13 +857,16 @@ void AudioEngine::halted (void *arg) { AudioEngine* ae = static_cast<AudioEngine *> (arg); + bool was_running = ae->_running; ae->_running = false; ae->_buffer_size = 0; ae->_frame_rate = 0; ae->_jack = 0; - ae->Halted(); /* EMIT SIGNAL */ + if (was_running) { + ae->Halted(); /* EMIT SIGNAL */ + } } uint32_t diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 822fe2cb72..20115ff944 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -66,12 +66,12 @@ void AudioRegion::init () { _scale_amplitude = 1.0; - valid_transients = false; set_default_fades (); set_default_envelope (); listen_to_my_curves (); + listen_to_my_sources (); } /* constructor for use by derived types only */ @@ -122,6 +122,7 @@ AudioRegion::AudioRegion (const SourceList& srcs, nframes_t start, nframes_t len , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) { init (); + listen_to_my_sources (); } /** Create a new AudioRegion, that is part of an existing one */ @@ -172,9 +173,9 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, nframes_t } _scale_amplitude = other->_scale_amplitude; - valid_transients = false; assert(_type == DataType::AUDIO); + listen_to_my_sources (); } AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other) @@ -183,15 +184,14 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other) , _fade_out (new AutomationList(Parameter(FadeOutAutomation), 0.0, 2.0, 1.0)) , _envelope (new AutomationList(Parameter(EnvelopeAutomation), 0.0, 2.0, 1.0)) { + assert(_type == DataType::AUDIO); _scale_amplitude = other->_scale_amplitude; - valid_transients = false; _envelope = other->_envelope; set_default_fades (); listen_to_my_curves (); - - assert(_type == DataType::AUDIO); + listen_to_my_sources (); } AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, const XMLNode& node) @@ -206,13 +206,13 @@ AudioRegion::AudioRegion (boost::shared_ptr<AudioSource> src, const XMLNode& nod } init (); - valid_transients = false; if (set_state (node)) { throw failed_constructor(); } assert(_type == DataType::AUDIO); + listen_to_my_sources (); } AudioRegion::AudioRegion (SourceList& srcs, const XMLNode& node) @@ -228,6 +228,7 @@ AudioRegion::AudioRegion (SourceList& srcs, const XMLNode& node) } assert(_type == DataType::AUDIO); + listen_to_my_sources (); } AudioRegion::~AudioRegion () @@ -235,10 +236,11 @@ AudioRegion::~AudioRegion () } void -AudioRegion::invalidate_transients () +AudioRegion::listen_to_my_sources () { - valid_transients = false; - _transients.clear (); + for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) { + (*i)->AnalysisChanged.connect (mem_fun (*this, &AudioRegion::invalidate_transients)); + } } void @@ -1256,53 +1258,73 @@ 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) +AudioRegion::get_transients (AnalysisFeatureList& results, bool force_new) { - if (!playlist()) { + boost::shared_ptr<Playlist> pl = playlist(); + + if (!pl) { return -1; } - if (valid_transients && !force_new) { + if (_valid_transients && !force_new) { results = _transients; return 0; } - TransientDetector t (playlist()->session().frame_rate()); + SourceList::iterator s; + + for (s = _sources.begin() ; s != _sources.end(); ++s) { + if (!(*s)->has_been_analysed()) { + cerr << "For " << name() << " source " << (*s)->name() << " has not been analyzed\n"; + break; + } + } + + if (s == _sources.end()) { + /* all sources are analyzed, merge data from each one */ + + for (s = _sources.begin() ; s != _sources.end(); ++s) { + + /* find the set of transients within the bounds of this region */ + + AnalysisFeatureList::iterator low = lower_bound ((*s)->transients.begin(), + (*s)->transients.end(), + _start); + + AnalysisFeatureList::iterator high = upper_bound ((*s)->transients.begin(), + (*s)->transients.end(), + _start + _length); + + /* and add them */ + + results.insert (results.end(), low, high); + } + + TransientDetector::cleanup_transients (results, pl->session().frame_rate(), 3.0); + + /* translate all transients to current position */ + + for (AnalysisFeatureList::iterator x = results.begin(); x != results.end(); ++x) { + (*x) -= _start; + (*x) += _position; + } + + _transients = results; + _valid_transients = true; + + return 0; + } + + TransientDetector t (pl->session().frame_rate()); bool existing_results = !results.empty(); _transients.clear (); - valid_transients = false; + _valid_transients = false; for (uint32_t i = 0; i < n_channels(); ++i) { - vector<nframes64_t> these_results; + AnalysisFeatureList these_results; t.reset (); @@ -1312,7 +1334,7 @@ AudioRegion::get_transients (vector<nframes64_t>& results, bool force_new) /* translate all transients to give absolute position */ - for (vector<nframes64_t>::iterator i = these_results.begin(); i != these_results.end(); ++i) { + for (AnalysisFeatureList::iterator i = these_results.begin(); i != these_results.end(); ++i) { (*i) += _position; } @@ -1329,20 +1351,19 @@ AudioRegion::get_transients (vector<nframes64_t>& results, bool force_new) */ results.insert (results.end(), _transients.begin(), _transients.end()); - cleanup_transients (results); + TransientDetector::cleanup_transients (results, pl->session().frame_rate(), 3.0); } /* make sure ours are clean too */ - cleanup_transients (_transients); + TransientDetector::cleanup_transients (_transients, pl->session().frame_rate(), 3.0); } - valid_transients = true; + _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 80116988d5..cd71dd21e4 100644 --- a/libs/ardour/audiosource.cc +++ b/libs/ardour/audiosource.cc @@ -919,50 +919,3 @@ 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/enums.cc b/libs/ardour/enums.cc index 9f2ce4fab9..d64d3a2f99 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -82,6 +82,7 @@ setup_enum_writer () Location::Flags _Location_Flags; RouteGroup::Flag _RouteGroup_Flag; Region::Flag _Region_Flag; + Region::PositionLockStyle _Region_PositionLockStyle; Track::FreezeState _Track_FreezeState; AutomationList::InterpolationStyle _AutomationList_InterpolationStyle; @@ -364,6 +365,10 @@ setup_enum_writer () REGISTER_CLASS_ENUM (Region, DoNotSaveState); REGISTER_BITS (_Region_Flag); + REGISTER_CLASS_ENUM (Region, AudioTime); + REGISTER_CLASS_ENUM (Region, MusicTime); + REGISTER_BITS (_Region_PositionLockStyle); + REGISTER_CLASS_ENUM (Track, NoFreeze); REGISTER_CLASS_ENUM (Track, Frozen); REGISTER_CLASS_ENUM (Track, UnFrozen); diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 6bb21a419c..6aba688cae 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -43,6 +43,7 @@ #include <midi++/mmc.h> #include <ardour/ardour.h> +#include <ardour/analyser.h> #include <ardour/audio_library.h> #include <ardour/configuration.h> #include <ardour/profile.h> @@ -300,6 +301,7 @@ ARDOUR::init (bool use_vst, bool try_optimization) setup_hardware_optimization (try_optimization); SourceFactory::init (); + Analyser::init (); /* singleton - first object is "it" */ new PluginManager (); diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index 04649a56fe..da3a812123 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -37,6 +37,7 @@ #include <ardour/region.h> #include <ardour/region_factory.h> #include <ardour/playlist_factory.h> +#include <ardour/transient_detector.h> #include "i18n.h" @@ -1428,6 +1429,65 @@ Playlist::regions_touched (nframes_t start, nframes_t end) return rlist; } +nframes64_t +Playlist::find_next_transient (nframes64_t from, int dir) +{ + RegionLock rlock (this); + AnalysisFeatureList points; + AnalysisFeatureList these_points; + + for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + if (dir > 0) { + if ((*i)->last_frame() < from) { + continue; + } + } else { + if ((*i)->first_frame() > from) { + continue; + } + } + + (*i)->get_transients (these_points); + + /* add first frame, just, err, because */ + + these_points.push_back ((*i)->first_frame()); + + points.insert (points.end(), these_points.begin(), these_points.end()); + these_points.clear (); + } + + if (points.empty()) { + return -1; + } + + TransientDetector::cleanup_transients (points, _session.frame_rate(), 3.0); + bool reached = false; + + if (dir > 0) { + for (AnalysisFeatureList::iterator x = points.begin(); x != points.end(); ++x) { + if ((*x) >= from) { + reached = true; + } + + if (reached && (*x) > from) { + return *x; + } + } + } else { + for (AnalysisFeatureList::reverse_iterator x = points.rbegin(); x != points.rend(); ++x) { + if ((*x) <= from) { + reached = true; + } + + if (reached && (*x) < from) { + return *x; + } + } + } + + return -1; +} boost::shared_ptr<Region> Playlist::find_next_region (nframes_t frame, RegionPoint point, int dir) @@ -2259,3 +2319,18 @@ Playlist::region_is_shuffle_constrained (boost::shared_ptr<Region>) return false; } + +void +Playlist::update_after_tempo_map_change () +{ + RegionLock rlock (const_cast<Playlist*> (this)); + RegionList copy (regions); + + freeze (); + + for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) { + (*i)->update_position_after_tempo_map_change (); + } + + thaw (); +} diff --git a/libs/ardour/recent_sessions.cc b/libs/ardour/recent_sessions.cc index cfb8d5bf2e..ebc5499f89 100644 --- a/libs/ardour/recent_sessions.cc +++ b/libs/ardour/recent_sessions.cc @@ -75,14 +75,9 @@ ARDOUR::read_recent_sessions (RecentSessions& rs) break; } - if (!access(newpair.second.c_str(), R_OK)) { - rs.push_back (newpair); - } + rs.push_back (newpair); } - // This deletes any missing sessions - ARDOUR::write_recent_sessions (rs); - /* display sorting should be done in the GUI, otherwise the * natural order will be broken */ diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index 054e85cd2f..2193a69908 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -21,6 +21,7 @@ #include <cmath> #include <climits> #include <algorithm> +#include <sstream> #include <sigc++/bind.h> #include <sigc++/class_slot.h> @@ -28,11 +29,13 @@ #include <glibmm/thread.h> #include <pbd/xml++.h> #include <pbd/stacktrace.h> +#include <pbd/enumwriter.h> #include <ardour/region.h> #include <ardour/playlist.h> #include <ardour/session.h> #include <ardour/source.h> +#include <ardour/tempo.h> #include <ardour/region_factory.h> #include <ardour/filter.h> @@ -59,6 +62,8 @@ Region::Region (Session& s, nframes_t start, nframes_t length, const string& nam , _start(start) , _length(length) , _position(0) + , _last_position(0) + , _positional_lock_style(AudioTime) , _sync_position(_start) , _layer(layer) , _first_edit(EditChangesNothing) @@ -70,7 +75,6 @@ Region::Region (Session& s, nframes_t start, nframes_t length, const string& nam /* no sources at this point */ } - /** Basic Region constructor (single source) */ Region::Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length, const string& name, DataType type, layer_t layer, Region::Flag flags) : Automatable(src->session(), name) @@ -79,6 +83,8 @@ Region::Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length , _start(start) , _length(length) , _position(0) + , _last_position(0) + , _positional_lock_style(AudioTime) , _sync_position(_start) , _layer(layer) , _first_edit(EditChangesNothing) @@ -86,6 +92,8 @@ Region::Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length , _ancestral_start (start) , _ancestral_length (length) , _stretch (1.0) + , _shift (0.0) + , _valid_transients(false) , _read_data_count(0) , _pending_changed(Change (0)) , _last_layer_op(0) @@ -96,6 +104,7 @@ Region::Region (boost::shared_ptr<Source> src, nframes_t start, nframes_t length src->GoingAway.connect (bind (mem_fun (*this, &Region::source_deleted), src)); assert(_sources.size() > 0); + _positional_lock_style = AudioTime; } /** Basic Region constructor (many sources) */ @@ -106,6 +115,8 @@ Region::Region (const SourceList& srcs, nframes_t start, nframes_t length, const , _start(start) , _length(length) , _position(0) + , _last_position(0) + , _positional_lock_style(AudioTime) , _sync_position(_start) , _layer(layer) , _first_edit(EditChangesNothing) @@ -141,6 +152,8 @@ Region::Region (boost::shared_ptr<const Region> other, nframes_t offset, nframes , _start(other->_start + offset) , _length(length) , _position(0) + , _last_position(0) + , _positional_lock_style(other->_positional_lock_style) , _sync_position(_start) , _layer(layer) , _first_edit(EditChangesNothing) @@ -148,6 +161,8 @@ Region::Region (boost::shared_ptr<const Region> other, nframes_t offset, nframes , _ancestral_start (other->_ancestral_start + offset) , _ancestral_length (length) , _stretch (1.0) + , _shift (0.0) + , _valid_transients(false) , _read_data_count(0) , _pending_changed(Change (0)) , _last_layer_op(0) @@ -185,6 +200,8 @@ Region::Region (boost::shared_ptr<const Region> other) , _start(other->_start) , _length(other->_length) , _position(other->_position) + , _last_position(other->_last_position) + , _positional_lock_style(other->_positional_lock_style) , _sync_position(other->_sync_position) , _layer(other->_layer) , _first_edit(EditChangesID) @@ -192,6 +209,8 @@ Region::Region (boost::shared_ptr<const Region> other) , _ancestral_start (_start) , _ancestral_length (_length) , _stretch (1.0) + , _shift (0.0) + , _valid_transients(false) , _read_data_count(0) , _pending_changed(Change(0)) , _last_layer_op(other->_last_layer_op) @@ -229,6 +248,8 @@ Region::Region (const SourceList& srcs, const XMLNode& node) , _start(0) , _length(0) , _position(0) + , _last_position(0) + , _positional_lock_style(AudioTime) , _sync_position(_start) , _layer(0) , _first_edit(EditChangesNothing) @@ -267,6 +288,8 @@ Region::Region (boost::shared_ptr<Source> src, const XMLNode& node) , _start(0) , _length(0) , _position(0) + , _last_position(0) + , _positional_lock_style(AudioTime) , _sync_position(_start) , _layer(0) , _first_edit(EditChangesNothing) @@ -373,6 +396,7 @@ Region::set_length (nframes_t len, void *src) first_edit (); maybe_uncopy (); + invalidate_transients (); if (!_frozen) { recompute_at_end (); @@ -450,12 +474,49 @@ Region::special_set_position (nframes_t pos) } void +Region::set_position_lock_style (PositionLockStyle ps) +{ + boost::shared_ptr<Playlist> pl (playlist()); + + if (!pl) { + return; + } + + _positional_lock_style = ps; + + if (_positional_lock_style == MusicTime) { + pl->session().tempo_map().bbt_time (_position, _bbt_time); + } + +} + +void +Region::update_position_after_tempo_map_change () +{ + boost::shared_ptr<Playlist> pl (playlist()); + + if (!pl || _positional_lock_style != MusicTime) { + return; + } + + TempoMap& map (pl->session().tempo_map()); + nframes_t pos = map.frame_time (_bbt_time); + set_position_internal (pos, false); +} + +void Region::set_position (nframes_t pos, void *src) { if (!can_move()) { return; } + set_position_internal (pos, true); +} + +void +Region::set_position_internal (nframes_t pos, bool allow_bbt_recompute) +{ if (_position != pos) { _last_position = _position; _position = pos; @@ -470,6 +531,15 @@ Region::set_position (nframes_t pos, void *src) _last_length = _length; _length = max_frames - _position; } + + if (allow_bbt_recompute && _positional_lock_style == MusicTime) { + boost::shared_ptr<Playlist> pl (playlist()); + if (pl) { + pl->session().tempo_map().bbt_time (_position, _bbt_time); + } + } + + invalidate_transients (); } /* do this even if the position is the same. this helps out @@ -563,6 +633,7 @@ Region::set_start (nframes_t pos, void *src) _start = pos; _flags = Region::Flag (_flags & ~WholeFile); first_edit (); + invalidate_transients (); send_change (StartChanged); } @@ -974,9 +1045,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), "%Ld", _ancestral_start); + snprintf (buf, sizeof (buf), "%" PRIi64, _ancestral_start); node->add_property ("ancestral-start", buf); - snprintf (buf, sizeof (buf), "%Ld", _ancestral_length); + snprintf (buf, sizeof (buf), "%" PRIi64, _ancestral_length); node->add_property ("ancestral-length", buf); snprintf (buf, sizeof (buf), "%.12g", _stretch); node->add_property ("stretch", buf); @@ -1007,6 +1078,13 @@ Region::state (bool full_state) snprintf (buf, sizeof (buf), "%" PRIu32, _sync_position); node->add_property ("sync-position", buf); + if (_positional_lock_style != AudioTime) { + node->add_property ("positional-lock-style", enum_2_string (_positional_lock_style)); + stringstream str; + str << _bbt_time; + node->add_property ("bbt-position", str.str()); + } + return *node; } @@ -1095,6 +1173,27 @@ Region::set_live_state (const XMLNode& node, Change& what_changed, bool send) _sync_position = _start; } + if ((prop = node.property ("positional-lock-style")) != 0) { + _positional_lock_style = PositionLockStyle (string_2_enum (prop->value(), _positional_lock_style)); + + if (_positional_lock_style == MusicTime) { + if ((prop = node.property ("bbt-position")) == 0) { + /* missing BBT info, revert to audio time locking */ + _positional_lock_style = AudioTime; + } else { + if (sscanf (prop->value().c_str(), "%d|%d|%d", + &_bbt_time.bars, + &_bbt_time.beats, + &_bbt_time.ticks) != 3) { + _positional_lock_style = AudioTime; + } + } + } + + } else { + _positional_lock_style = AudioTime; + } + /* XXX FIRST EDIT !!! */ /* these 3 properties never change as a result of any editing */ @@ -1261,7 +1360,8 @@ Region::region_list_equivalent (boost::shared_ptr<const Region> other) const void Region::source_deleted (boost::shared_ptr<Source>) { - delete this; + _sources.clear (); + drop_references (); } vector<string> @@ -1397,3 +1497,10 @@ Region::apply (Filter& filter) } +void +Region::invalidate_transients () +{ + _valid_transients = false; + _transients.clear (); +} + diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index c6ace87b73..5a3144b828 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -80,6 +80,7 @@ #include <ardour/filename_extensions.h> #include <ardour/session_directory.h> #include <ardour/tape_file_matcher.h> +#include <ardour/analyser.h> #ifdef HAVE_LIBLO #include <ardour/osc.h> @@ -107,6 +108,7 @@ Session::mix_buffers_with_gain_t Session::mix_buffers_with_gain = 0; Session::mix_buffers_no_gain_t Session::mix_buffers_no_gain = 0; sigc::signal<int> Session::AskAboutPendingState; +sigc::signal<int,nframes_t,nframes_t> Session::AskAboutSampleRateMismatch; sigc::signal<void> Session::SendFeedback; sigc::signal<void> Session::SMPTEOffsetChanged; @@ -2771,6 +2773,14 @@ Session::add_source (boost::shared_ptr<Source> source) source->GoingAway.connect (sigc::bind (mem_fun (this, &Session::remove_source), boost::weak_ptr<Source> (source))); set_dirty(); } + + boost::shared_ptr<AudioFileSource> afs; + + if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(source)) != 0) { + if (Config->get_auto_analyse_audio()) { + Analyser::queue_source_for_analysis (source, false); + } + } } void @@ -3726,6 +3736,15 @@ void Session::tempo_map_changed (Change ignored) { clear_clicks (); + + for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) { + (*i)->update_after_tempo_map_change (); + } + + for (PlaylistList::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) { + (*i)->update_after_tempo_map_change (); + } + set_dirty (); } @@ -3737,7 +3756,7 @@ Session::ensure_buffers (ChanCount howmany) { if (current_block_size == 0) return; // too early? (is this ok?) - + // We need at least 2 MIDI scratch buffers to mix/merge if (howmany.n_midi() < 2) howmany.set_midi(2); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index d18b9cedd7..af4be07dc4 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -141,6 +141,9 @@ Session::first_stage_init (string fullpath, string snapshot_name) set_history_depth (Config->get_history_depth()); _current_frame_rate = _engine.frame_rate (); + _nominal_frame_rate = _current_frame_rate; + _base_frame_rate = _current_frame_rate; + _tempo_map = new TempoMap (_current_frame_rate); _tempo_map->StateChanged.connect (mem_fun (*this, &Session::tempo_map_changed)); @@ -236,9 +239,6 @@ Session::first_stage_init (string fullpath, string snapshot_name) waiting_for_sync_offset = false; } - _current_frame_rate = 48000; - _base_frame_rate = 48000; - last_smpte_when = 0; _smpte_offset = 0; _smpte_offset_negative = true; @@ -920,16 +920,16 @@ Session::state(bool full_state) // store libardour version, just in case char buf[16]; - snprintf(buf, sizeof(buf)-1, "%d.%d.%d", - libardour_major_version, libardour_minor_version, libardour_micro_version); + snprintf(buf, sizeof(buf), "%d.%d.%d", libardour3_major_version, libardour3_minor_version, libardour3_micro_version); node->add_property("version", string(buf)); /* store configuration settings */ if (full_state) { - /* store the name */ node->add_property ("name", _name); + snprintf (buf, sizeof (buf), "%" PRId32, _nominal_frame_rate); + node->add_property ("sample-rate", buf); if (session_dirs.size() > 1) { @@ -1148,7 +1148,6 @@ Session::set_state (const XMLNode& node) int ret = -1; _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave); - if (node.name() != X_("Session")){ fatal << _("programming error: Session: incorrect XML node sent to set_state()") << endmsg; @@ -1159,6 +1158,17 @@ Session::set_state (const XMLNode& node) _name = prop->value (); } + if ((prop = node.property (X_("sample-rate"))) != 0) { + + _nominal_frame_rate = atoi (prop->value()); + + if (_nominal_frame_rate != _current_frame_rate) { + if (AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_rate)) { + return -1; + } + } + } + setup_raid_path(_session_dir->root_path().to_string()); if ((prop = node.property (X_("id-counter"))) != 0) { @@ -2067,6 +2077,56 @@ Session::auto_save() save_state (_current_snapshot_name); } +static bool +state_file_filter (const string &str, void *arg) +{ + return (str.length() > strlen(statefile_suffix) && + str.find (statefile_suffix) == (str.length() - strlen (statefile_suffix))); +} + +struct string_cmp { + bool operator()(const string* a, const string* b) { + return *a < *b; + } +}; + +static string* +remove_end(string* state) +{ + string statename(*state); + + string::size_type start,end; + if ((start = statename.find_last_of ('/')) != string::npos) { + statename = statename.substr (start+1); + } + + if ((end = statename.rfind(".ardour")) == string::npos) { + end = statename.length(); + } + + return new string(statename.substr (0, end)); +} + +vector<string *> * +Session::possible_states (string path) +{ + PathScanner scanner; + vector<string*>* states = scanner (path, state_file_filter, 0, false, false); + + transform(states->begin(), states->end(), states->begin(), remove_end); + + string_cmp cmp; + sort (states->begin(), states->end(), cmp); + + return states; +} + +vector<string *> * +Session::possible_states () const +{ + return possible_states(_path); +} + RouteGroup * Session::add_edit_group (string name) { diff --git a/libs/ardour/source.cc b/libs/ardour/source.cc index a11e82f1e8..7941eb693b 100644 --- a/libs/ardour/source.cc +++ b/libs/ardour/source.cc @@ -27,19 +27,22 @@ #include <cmath> #include <iomanip> #include <algorithm> +#include <fstream> #include <glibmm/thread.h> +#include <glibmm/miscutils.h> +#include <glibmm/fileutils.h> #include <pbd/xml++.h> #include <pbd/pthread_utils.h> #include <ardour/source.h> #include <ardour/playlist.h> +#include <ardour/session.h> +#include <ardour/transient_detector.h> #include "i18n.h" -using std::min; -using std::max; - +using namespace std; using namespace ARDOUR; Source::Source (Session& s, const string& name, DataType type) @@ -49,6 +52,7 @@ Source::Source (Session& s, const string& name, DataType type) // not true.. is this supposed to be an assertion? //assert(_name.find("/") == string::npos); + _analysed = false; _timestamp = 0; _length = 0; _in_use = 0; @@ -60,6 +64,7 @@ Source::Source (Session& s, const XMLNode& node) { _timestamp = 0; _length = 0; + _analysed = false; _in_use = 0; if (set_state (node) || _type == DataType::NIL) { @@ -135,7 +140,7 @@ Source::add_playlist (boost::shared_ptr<Playlist> pl) { std::pair<PlaylistMap::iterator,bool> res; std::pair<boost::shared_ptr<Playlist>, uint32_t> newpair (pl, 1); - Glib::Mutex::Lock lm (playlist_lock); + Glib::Mutex::Lock lm (_playlist_lock); res = _playlists.insert (newpair); @@ -157,7 +162,7 @@ Source::remove_playlist (boost::weak_ptr<Playlist> wpl) } PlaylistMap::iterator x; - Glib::Mutex::Lock lm (playlist_lock); + Glib::Mutex::Lock lm (_playlist_lock); if ((x = _playlists.find (pl)) != _playlists.end()) { if (x->second > 1) { @@ -173,3 +178,90 @@ Source::used () const { return _playlists.size(); } + +bool +Source::has_been_analysed() const +{ + Glib::Mutex::Lock lm (_analysis_lock); + return _analysed; +} + +void +Source::set_been_analysed (bool yn) +{ + { + Glib::Mutex::Lock lm (_analysis_lock); + _analysed = yn; + } + + if (yn) { + AnalysisChanged(); // EMIT SIGNAL + } +} + +int +Source::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 +Source::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 += TransientDetector::operational_identifier(); + parts.push_back (s); + + return Glib::build_filename (parts); +} + +bool +Source::check_for_analysis_data_on_disk () +{ + /* looks to see if the analysis files for this source are on disk. + if so, mark us already analysed. + */ + + string path = get_transients_path (); + bool ok = true; + + if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) { + ok = false; + } + + // XXX add other tests here as appropriate + + set_been_analysed (ok); + return ok; +} diff --git a/libs/ardour/source_factory.cc b/libs/ardour/source_factory.cc index 02c35d2188..804d4a4d47 100644 --- a/libs/ardour/source_factory.cc +++ b/libs/ardour/source_factory.cc @@ -111,6 +111,7 @@ boost::shared_ptr<Source> SourceFactory::createSilent (Session& s, const XMLNode& node, nframes_t nframes, float sr) { boost::shared_ptr<Source> ret (new SilentFileSource (s, node, nframes, sr)); + // no analysis data - the file is non-existent SourceCreated (ret); return ret; } @@ -118,61 +119,64 @@ SourceFactory::createSilent (Session& s, const XMLNode& node, nframes_t nframes, boost::shared_ptr<Source> SourceFactory::create (Session& s, const XMLNode& node, bool defer_peaks) { - DataType type = DataType::AUDIO; - const XMLProperty* prop = node.property("type"); + DataType type = DataType::AUDIO; + const XMLProperty* prop = node.property("type"); - if (prop) { - type = DataType(prop->value()); - } + if (prop) { + type = DataType(prop->value()); + } - if (type == DataType::AUDIO) { + if (type == DataType::AUDIO) { #ifdef HAVE_COREAUDIO - try { - boost::shared_ptr<Source> ret (new CoreAudioSource (s, node)); - - if (setup_peakfile (ret, defer_peaks)) { - return boost::shared_ptr<Source>(); - } - - SourceCreated (ret); - return ret; - } - - - catch (failed_constructor& err) { - - /* this is allowed to throw */ - - boost::shared_ptr<Source> ret (new SndFileSource (s, node)); - - if (setup_peakfile (ret, defer_peaks)) { - return boost::shared_ptr<Source>(); - } - - SourceCreated (ret); - return ret; - } + try { + boost::shared_ptr<Source> ret (new CoreAudioSource (s, node)); + + if (setup_peakfile (ret, defer_peaks)) { + return boost::shared_ptr<Source>(); + } + + ret->check_for_analysis_data_on_disk (); + SourceCreated (ret); + return ret; + } + + + catch (failed_constructor& err) { + + /* this is allowed to throw */ + + boost::shared_ptr<Source> ret (new SndFileSource (s, node)); + + if (setup_peakfile (ret, defer_peaks)) { + return boost::shared_ptr<Source>(); + } + + ret->check_for_analysis_data_on_disk (); + SourceCreated (ret); + return ret; + } #else - /* this is allowed to throw */ - - boost::shared_ptr<Source> ret (new SndFileSource (s, node)); - - if (setup_peakfile (ret, defer_peaks)) { - return boost::shared_ptr<Source>(); - } - - SourceCreated (ret); - return ret; + /* this is allowed to throw */ + + boost::shared_ptr<Source> ret (new SndFileSource (s, node)); + + if (setup_peakfile (ret, defer_peaks)) { + return boost::shared_ptr<Source>(); + } + + ret->check_for_analysis_data_on_disk (); + SourceCreated (ret); + return ret; #endif - - } else if (type == DataType::MIDI) { - boost::shared_ptr<Source> ret (new SMFSource (s, node)); - - SourceCreated (ret); - return ret; - } + + } else if (type == DataType::MIDI) { + boost::shared_ptr<Source> ret (new SMFSource (s, node)); + ret->check_for_analysis_data_on_disk (); + SourceCreated (ret); + return ret; + } return boost::shared_ptr<Source>(); } @@ -190,6 +194,7 @@ SourceFactory::createReadable (DataType type, Session& s, string path, int chn, return boost::shared_ptr<Source>(); } + ret->check_for_analysis_data_on_disk (); if (announce) { SourceCreated (ret); } @@ -201,6 +206,7 @@ SourceFactory::createReadable (DataType type, Session& s, string path, int chn, if (setup_peakfile (ret, defer_peaks)) { return boost::shared_ptr<Source>(); } + ret->check_for_analysis_data_on_disk (); if (announce) { SourceCreated (ret); } @@ -213,6 +219,7 @@ SourceFactory::createReadable (DataType type, Session& s, string path, int chn, return boost::shared_ptr<Source>(); } + ret->check_for_analysis_data_on_disk (); if (announce) { SourceCreated (ret); } @@ -225,6 +232,7 @@ SourceFactory::createReadable (DataType type, Session& s, string path, int chn, // FIXME: flags? boost::shared_ptr<Source> ret (new SMFSource (s, path, SMFSource::Flag(0))); + ret->check_for_analysis_data_on_disk (); if (announce) { SourceCreated (ret); } @@ -252,6 +260,8 @@ SourceFactory::createWritable (DataType type, Session& s, std::string path, bool if (setup_peakfile (ret, defer_peaks)) { return boost::shared_ptr<Source>(); } + + // no analysis data - this is a new file if (announce) { SourceCreated (ret); @@ -261,6 +271,8 @@ SourceFactory::createWritable (DataType type, Session& s, std::string path, bool } else if (type == DataType::MIDI) { boost::shared_ptr<Source> ret (new SMFSource (s, path)); + + // no analysis data - this is a new file if (announce) { SourceCreated (ret); diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index b2865fc399..3170d588a1 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -246,29 +246,49 @@ TempoMap::~TempoMap () int TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when) { - if (when == section.start()) { + if (when == section.start() || !section.movable()) { return -1; } - if (!section.movable()) { - return 1; - } - Glib::RWLock::WriterLock lm (lock); MetricSectionSorter cmp; - BBT_Time corrected (when); - - if (dynamic_cast<MeterSection*>(§ion) != 0) { - if (corrected.beats > 1) { - corrected.beats = 1; - corrected.bars++; + + if (when.beats != 1) { + + /* position by audio frame, then recompute BBT timestamps from the audio ones */ + + nframes_t frame = frame_time (when); + // cerr << "nominal frame time = " << frame << endl; + + nframes_t prev_frame = round_to_type (frame, -1, Beat); + nframes_t next_frame = round_to_type (frame, 1, Beat); + + // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl; + + /* use the closest beat */ + + if ((frame - prev_frame) < (next_frame - frame)) { + frame = prev_frame; + } else { + frame = next_frame; } + + // cerr << "actual frame time = " << frame << endl; + section.set_frame (frame); + // cerr << "frame time = " << section.frame() << endl; + timestamp_metrics (false); + // cerr << "new BBT time = " << section.start() << endl; + metrics->sort (cmp); + + } else { + + /* positioned at bar start already, so just put it there */ + + section.set_start (when); + metrics->sort (cmp); + timestamp_metrics (true); } - corrected.ticks = 0; - section.set_start (corrected); - metrics->sort (cmp); - timestamp_metrics (true); return 0; } @@ -288,7 +308,6 @@ TempoMap::move_meter (MeterSection& meter, const BBT_Time& when) StateChanged (Change (0)); } } - void TempoMap::remove_tempo (const TempoSection& tempo) @@ -412,11 +431,12 @@ TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement) TempoSection *ts; if ((ts = dynamic_cast<TempoSection*>(*i)) != 0 && ts == &existing) { - - *((Tempo *) ts) = replacement; + + *((Tempo *) ts) = replacement; replaced = true; timestamp_metrics (true); + break; } } @@ -494,6 +514,21 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement) } void +TempoMap::change_initial_tempo (double beats_per_minute, double note_type) +{ + Tempo newtempo (beats_per_minute, note_type); + TempoSection* t; + + for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) { + if ((t = dynamic_cast<TempoSection*> (*i)) != 0) { + *((Tempo*) t) = newtempo; + StateChanged (Change (0)); + break; + } + } +} + +void TempoMap::change_existing_tempo_at (nframes_t where, double beats_per_minute, double note_type) { Tempo newtempo (beats_per_minute, note_type); @@ -582,6 +617,8 @@ TempoMap::timestamp_metrics (bool use_bbt) if (use_bbt) { + // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl; + nframes_t current = 0; nframes_t section_frames; BBT_Time start; @@ -611,42 +648,68 @@ TempoMap::timestamp_metrics (bool use_bbt) } else { + // cerr << "\n\n\n ######################\nTIMESTAMP via AUDIO ##############\n" << endl; + bool first = true; + MetricSection* prev = 0; for (i = metrics->begin(); i != metrics->end(); ++i) { BBT_Time bbt; - - bbt_time_with_metric ((*i)->frame(), bbt, Metric (*meter, *tempo)); + Metric metric (*meter, *tempo); + + if (prev) { + metric.set_start (prev->start()); + } else { + // metric will be at frames=0 bbt=1|1|0 by default + // which is correct for our purpose + } + + bbt_time_with_metric ((*i)->frame(), bbt, metric); // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => "; + if (first) { first = false; } else { - if (bbt.beats != 1 || bbt.ticks != 0) { + + if (bbt.ticks > Meter::ticks_per_beat/2) { + /* round up to next beat */ + bbt.beats += 1; + } + + bbt.ticks = 0; + + if (bbt.beats != 1) { + /* round up to next bar */ bbt.bars += 1; bbt.beats = 1; - bbt.ticks = 0; } } - - // cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl; - + + //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl; + (*i)->set_start (bbt); if ((t = dynamic_cast<TempoSection*>(*i)) != 0) { tempo = t; + // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl; } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) { meter = m; + // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl; } else { fatal << _("programming error: unhandled MetricSection type") << endmsg; /*NOTREACHED*/ } + + prev = (*i); } } // dump (cerr); + // cerr << "###############################################\n\n\n" << endl; + } TempoMap::Metric @@ -741,6 +804,8 @@ TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const Metric& me double xtra_beats = 0; double beats = 0; + // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl; + const double beats_per_bar = metric.meter().beats_per_bar(); const double frames_per_bar = metric.meter().frames_per_bar (metric.tempo(), _frame_rate); const double beat_frames = metric.tempo().frames_per_beat (_frame_rate, metric.meter()); @@ -748,11 +813,14 @@ TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const Metric& me /* now compute how far beyond that point we actually are. */ frame_diff = frame - metric.frame(); + + // cerr << "----\tdelta = " << frame_diff << endl; xtra_bars = (uint32_t) floor (frame_diff / frames_per_bar); frame_diff -= (uint32_t) floor (xtra_bars * frames_per_bar); xtra_beats = (double) frame_diff / beat_frames; + // cerr << "---\tmeaning " << xtra_bars << " xtra bars and " << xtra_beats << " xtra beats\n"; /* and set the returned value */ @@ -765,21 +833,21 @@ TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const Metric& me bbt.bars = metric.start().bars + xtra_bars; beats = (double) metric.start().beats + xtra_beats; - + bbt.bars += (uint32_t) floor(beats/ (beats_per_bar+1) ); beats = fmod(beats - 1, beats_per_bar )+ 1.0; bbt.ticks = (uint32_t)( round((beats - floor(beats)) *(double) Meter::ticks_per_beat)); bbt.beats = (uint32_t) floor(beats); - + + // cerr << "-----\t RETURN " << bbt << endl; } - 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 be "legal" BBT types, - that means that the beats and ticks should be inside a bar + that means that the beats and ticks should be inside a bar */ nframes_t frames = 0; @@ -814,7 +882,7 @@ TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) con nframes_t TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const { - /*this is used in timestamping the metrics by actually counting the beats */ + /* this is used in timestamping the metrics by actually counting the beats */ nframes_t frames = 0; uint32_t bar = start.bars; @@ -834,16 +902,26 @@ TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, beat = 1; ++bar; ++beats_counted; - } else { - ++beat; - ++beats_counted; + if (beat > beats_per_bar) { + /* this is a fractional beat at the end of a fractional bar - so it should only count for the fraction */ + so it should only count for the fraction + */ + beats_counted -= (ceil(beats_per_bar) - beats_per_bar); } + + } else { + ++beat; + ++beats_counted; } } + + // cerr << "Counted " << beats_counted << " from " << start << " to " << end + // << " bpb were " << beats_per_bar + // << " fpb was " << beat_frames + // << endl; frames = (nframes_t) floor (beats_counted * beat_frames); @@ -1052,7 +1130,6 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num) } nframes_t - TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) { Metric metric = metric_at (frame); @@ -1191,7 +1268,9 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const if (i == metrics->end()) { limit = upper; + // cerr << "== limit set to end of request @ " << limit << endl; } else { + // cerr << "== limit set to next metric @ " << (*i)->frame() << endl; limit = (*i)->frame(); } @@ -1224,6 +1303,10 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat++; } + // cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? " + // << (beat > ceil(beats_per_bar)) + // << endl; + if (beat > ceil(beats_per_bar) || i != metrics->end()) { /* we walked an entire bar. its @@ -1245,9 +1328,11 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const if (beat > ceil (beats_per_bar)) { /* next bar goes where the numbers suggest */ current -= beat_frames * (ceil(beats_per_bar)-beats_per_bar); + // cerr << "++ next bar from numbers\n"; } else { /* next bar goes where the next metric is */ current = limit; + // cerr << "++ next bar at next metric\n"; } bar++; beat = 1; @@ -1275,6 +1360,9 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const beat = 1; } + current = (*i)->frame (); + // cerr << "loop around with current @ " << current << endl; + beats_per_bar = meter->beats_per_bar (); frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate); beat_frames = tempo->frames_per_beat (_frame_rate, *meter); @@ -1419,3 +1507,32 @@ TempoMap::dump (std::ostream& o) const } } +int +TempoMap::n_tempos() const +{ + Glib::RWLock::ReaderLock lm (lock); + int cnt = 0; + + for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + if (dynamic_cast<const TempoSection*>(*i) != 0) { + cnt++; + } + } + + return cnt; +} + +int +TempoMap::n_meters() const +{ + Glib::RWLock::ReaderLock lm (lock); + int cnt = 0; + + for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) { + if (dynamic_cast<const MeterSection*>(*i) != 0) { + cnt++; + } + } + + return cnt; +} diff --git a/libs/ardour/transient_detector.cc b/libs/ardour/transient_detector.cc index b85700dd90..b92bf5fb2d 100644 --- a/libs/ardour/transient_detector.cc +++ b/libs/ardour/transient_detector.cc @@ -6,20 +6,36 @@ using namespace Vamp; using namespace ARDOUR; using namespace std; +string TransientDetector::_op_id; + TransientDetector::TransientDetector (float sr) : AudioAnalyser (sr, X_("libardourvampplugins:percussiononsets")) { + if (_op_id.empty()) { + _op_id = X_("libardourvampplugins:percussiononsets"); + + // XXX this should load the above-named plugin and get the current version + + _op_id += ":2"; + } } TransientDetector::~TransientDetector() { } +string +TransientDetector::operational_identifier() +{ + return _op_id; +} + int -TransientDetector::run (const std::string& path, Readable* src, uint32_t channel, vector<nframes64_t>& results) +TransientDetector::run (const std::string& path, Readable* src, uint32_t channel, AnalysisFeatureList& results) { current_results = &results; int ret = analyse (path, src, channel); + current_results = 0; return ret; } @@ -59,3 +75,42 @@ TransientDetector::set_sensitivity (float val) plugin->setParameter ("sensitivity", val); } } + +void +TransientDetector::cleanup_transients (AnalysisFeatureList& t, float sr, float gap_msecs) +{ + if (t.empty()) { + return; + } + + t.sort (); + + /* remove duplicates or other things that are too close */ + + AnalysisFeatureList::iterator i = t.begin(); + AnalysisFeatureList::iterator f, b; + const nframes64_t gap_frames = (nframes64_t) floor (gap_msecs * (sr / 1000.0)); + + while (i != t.end()) { + + // move front iterator to just past i, and back iterator the same place + + f = i; + ++f; + b = f; + + // move f until we find a new value that is far enough away + + while ((f != t.end()) && (((*f) - (*i)) < gap_frames)) { + ++f; + } + + i = f; + + // if f moved forward from b, we had duplicates/too-close points: get rid of them + + if (b != f) { + t.erase (b, f); + } + } +} diff --git a/libs/libgnomecanvasmm/SConscript b/libs/libgnomecanvasmm/SConscript index cc0aff0bd8..40967cc8d2 100644 --- a/libs/libgnomecanvasmm/SConscript +++ b/libs/libgnomecanvasmm/SConscript @@ -18,7 +18,8 @@ gnomecanvasmm.Merge([libraries['glibmm2'], libraries['libgnomecanvas2'], libraries['cairomm']]) -gnomecanvasmm.Append(CXXFLAGS = ["-DGLIBMM_PROPERTIES_ENABLED", "-DGLIBMM_EXCEPTIONS_ENABLED"]) +if gnomecanvasmm['IS_OSX']: + gnomecanvasmm.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048") libgnomecanvasmm = gnomecanvasmm.SharedLibrary('libgnomecanvasmm', gnomecanvasmm_files) Default(libgnomecanvasmm) diff --git a/libs/midi++2/SConscript b/libs/midi++2/SConscript index 769d888903..9d2ce5c95e 100644 --- a/libs/midi++2/SConscript +++ b/libs/midi++2/SConscript @@ -9,6 +9,9 @@ Import('env libraries install_prefix') midi2 = env.Copy() midi2.Merge([ libraries['sigc2'], libraries['xml'], libraries['glibmm2'], libraries['glib2'], libraries['pbd'], libraries['jack'] ]) +if midi2['IS_OSX']: + midi2.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048") + domain = 'midipp' midi2.Append(DOMAIN=domain,MAJOR=2,MINOR=1,MICRO=1) diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp index 406d4978c4..cbe179fb6b 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.cpp @@ -57,6 +57,8 @@ public: OutputList getOutputDescriptors() const; + void reset(); + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); FeatureSet getRemainingFeatures(); @@ -225,7 +227,8 @@ protected: vector<RingBuffer *> m_queue; float **m_buffers; float m_inputSampleRate; - RealTime m_timestamp; + RealTime m_timestamp; + bool m_unrun; OutputList m_outputs; void processBlock(FeatureSet& allFeatureSets, RealTime timestamp); @@ -253,6 +256,12 @@ PluginBufferingAdapter::getOutputDescriptors() const { return m_impl->getOutputDescriptors(); } + +void +PluginBufferingAdapter::reset() +{ + m_impl->reset(); +} PluginBufferingAdapter::FeatureSet PluginBufferingAdapter::process(const float *const *inputBuffers, @@ -277,7 +286,8 @@ PluginBufferingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : m_queue(0), m_buffers(0), m_inputSampleRate(inputSampleRate), - m_timestamp() + m_timestamp(RealTime::zeroTime), + m_unrun(true) { m_outputs = plugin->getOutputDescriptors(); } @@ -333,8 +343,8 @@ PluginBufferingAdapter::Impl::initialise(size_t channels, size_t stepSize, size_ } } - std::cerr << "PluginBufferingAdapter::initialise: stepSize " << m_inputStepSize << " -> " << m_stepSize - << ", blockSize " << m_inputBlockSize << " -> " << m_blockSize << std::endl; + // 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) { @@ -365,11 +375,27 @@ PluginBufferingAdapter::Impl::getOutputDescriptors() const return outs; } +void +PluginBufferingAdapter::Impl::reset() +{ + m_timestamp = RealTime::zeroTime; + m_unrun = true; + + for (size_t i = 0; i < m_queue.size(); ++i) { + m_queue[i]->reset(); + } +} + PluginBufferingAdapter::FeatureSet PluginBufferingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp) { FeatureSet allFeatureSets; + + if (m_unrun) { + m_timestamp = timestamp; + m_unrun = false; + } // queue the new input diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h index 96a958b9e5..24ca6f8db8 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginBufferingAdapter.h @@ -81,6 +81,8 @@ public: OutputList getOutputDescriptors() const; + void reset(); + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); FeatureSet getRemainingFeatures(); diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginChannelAdapter.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginChannelAdapter.cpp index 3ce51cedd2..fe676bcafd 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginChannelAdapter.cpp +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginChannelAdapter.cpp @@ -143,7 +143,7 @@ PluginChannelAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t m_pluginChannels = minch; - std::cerr << "PluginChannelAdapter::initialise: expanding " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; + // std::cerr << "PluginChannelAdapter::initialise: expanding " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; } else if (m_inputChannels > maxch) { @@ -155,18 +155,18 @@ PluginChannelAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t m_buffer = new float *[1]; m_buffer[0] = new float[blockSize]; - std::cerr << "PluginChannelAdapter::initialise: mixing " << m_inputChannels << " to mono for plugin" << std::endl; + // std::cerr << "PluginChannelAdapter::initialise: mixing " << m_inputChannels << " to mono for plugin" << std::endl; } else { - std::cerr << "PluginChannelAdapter::initialise: reducing " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; + // std::cerr << "PluginChannelAdapter::initialise: reducing " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; } m_pluginChannels = maxch; } else { - std::cerr << "PluginChannelAdapter::initialise: accepting given number of channels (" << m_inputChannels << ")" << std::endl; + // std::cerr << "PluginChannelAdapter::initialise: accepting given number of channels (" << m_inputChannels << ")" << std::endl; m_pluginChannels = m_inputChannels; } diff --git a/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp b/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp index e706414ae2..273925f96d 100644 --- a/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp +++ b/libs/vamp-sdk/vamp-sdk/hostext/PluginInputDomainAdapter.cpp @@ -8,6 +8,9 @@ Centre for Digital Music, Queen Mary, University of London. Copyright 2006-2007 Chris Cannam and QMUL. + This file is based in part on Don Cross's public domain FFT + implementation. + 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 @@ -38,28 +41,33 @@ #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. + * Be aware that FFTW is licensed under the GPL -- unlike this SDK, + * which is provided under a more liberal BSD license in order to + * permit use in closed source applications. The use of FFTW would + * mean that your code would need to be licensed under the GPL as + * well. Do not define this symbol unless you understand and accept + * the implications of this. + * + * Parties such as Linux distribution packagers who redistribute this + * SDK for use in other programs should _not_ define this symbol, as + * it would change the effective licensing terms under which the SDK + * was available to third party developers. + * + * The default is not to use FFTW, and to use the built-in FFT instead. * - * 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). + * Note: The FFTW code uses FFTW_MEASURE, and so 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 { diff --git a/svn_revision.h b/svn_revision.h index 14515fb63b..5000e5a2a4 100644 --- a/svn_revision.h +++ b/svn_revision.h @@ -1,4 +1,4 @@ #ifndef __ardour_svn_revision_h__ #define __ardour_svn_revision_h__ -static const char* ardour_svn_revision = "2918"; +static const char* ardour_svn_revision = "2985"; #endif |