diff --git a/gtk2_ardour/actions.cc b/gtk2_ardour/actions.cc index dea2c1c213..1d10ffaf54 100644 --- a/gtk2_ardour/actions.cc +++ b/gtk2_ardour/actions.cc @@ -90,7 +90,7 @@ ActionManager::load_menus (const string& menus_file) if (!loaded) { cerr << string_compose (_("%1 will not work without a valid menu definition file"), PROGRAM_NAME) << endl; error << string_compose (_("%1 will not work without a valid menu definition file"), PROGRAM_NAME) << endmsg; - exit(1); + exit (EXIT_FAILURE); } } diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index 96a7ab8cf5..9966e61c17 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -19,7 +19,7 @@ export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour: export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:. export ARDOUR_EXPORT_FORMATS_PATH=$TOP/export:. export ARDOUR_THEMES_PATH=$TOP/gtk2_ardour/themes:. -export ARDOUR_BACKEND_PATH=$libs/backends/jack:$libs/backends/wavesaudio:$libs/backends/dummy:$libs/backends/alsa:$libs/backends/coreaudio:$libs/backends/portaudio:$libs/backends/asio +export ARDOUR_BACKEND_PATH=$libs/backends/jack:$libs/backends/dummy:$libs/backends/alsa:$libs/backends/coreaudio:$libs/backends/portaudio export ARDOUR_TEST_PATH=$TOP/libs/ardour/test/data export PBD_TEST_PATH=$TOP/libs/pbd/test export EVORAL_TEST_PATH=$TOP/libs/evoral/test/testdata diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index a33c13dd50..41f5e23464 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -353,7 +353,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) MessageDialog msg (string_compose (_("Your configuration files were copied. You can now restart %1."), PROGRAM_NAME), true); msg.run (); /* configuration was modified, exit immediately */ - _exit (0); + _exit (EXIT_SUCCESS); } @@ -742,7 +742,7 @@ ARDOUR_UI::post_engine () halt_connection.disconnect (); AudioEngine::instance()->stop (); - exit (0); + exit (EXIT_SUCCESS); } @@ -819,7 +819,7 @@ ARDOUR_UI::post_engine () halt_connection.disconnect (); AudioEngine::instance()->stop (); - exit (0); + exit (EXIT_SUCCESS); } /* this being a GUI and all, we want peakfiles */ @@ -1264,7 +1264,7 @@ ARDOUR_UI::starting () c.signal_toggled().connect (sigc::hide_return (sigc::bind (sigc::ptr_fun (toggle_file_existence), path))); if (d.run () != RESPONSE_OK) { - _exit (0); + _exit (EXIT_SUCCESS); } } #endif @@ -1881,7 +1881,7 @@ ARDOUR_UI::open_recent_session () recent_session_dialog.hide(); return; } else { - exit (1); + exit (EXIT_FAILURE); } } @@ -3245,7 +3245,7 @@ ARDOUR_UI::load_from_application_api (const std::string& path) ARDOUR_COMMAND_LINE::session_name = ""; if (get_session_parameters (true, false)) { - exit (1); + exit (EXIT_FAILURE); } } } @@ -3288,15 +3288,33 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri template_name = load_template; } - session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name); session_path = ARDOUR_COMMAND_LINE::session_name; if (!session_path.empty()) { + if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_EXISTS)) { + + session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name); + if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_IS_REGULAR)) { /* session/snapshot file, change path to be dir */ session_path = Glib::path_get_dirname (session_path); } + } else { + + /* session (file or folder) does not exist ... did the + * user give us a path or just a name? + */ + + if (session_path.find (G_DIR_SEPARATOR) == string::npos) { + /* user gave session name with no path info, use + default session folder. + */ + session_name = ARDOUR_COMMAND_LINE::session_name; + session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name); + } else { + session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name); + } } } @@ -3328,11 +3346,8 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri session_dialog.clear_given (); } - if (should_be_new || session_name.empty()) { - /* need the dialog to get info from user */ - - cerr << "run dialog\n"; - + if (session_name.empty()) { + /* need the dialog to get the name (at least) from the user */ switch (session_dialog.run()) { case RESPONSE_ACCEPT: break; @@ -3486,12 +3501,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri if (ret == -2) { /* not connected to the AudioEngine, so quit to avoid an infinite loop */ - exit (1); - } - - if (!ARDOUR_COMMAND_LINE::immediate_save.empty()) { - _session->save_state (ARDOUR_COMMAND_LINE::immediate_save, false); - exit (1); + exit (EXIT_FAILURE); } /* clear this to avoid endless attempts to load the @@ -3521,7 +3531,7 @@ ARDOUR_UI::close_session() ARDOUR_COMMAND_LINE::session_name = ""; if (get_session_parameters (true, false)) { - exit (1); + exit (EXIT_FAILURE); } } @@ -3585,7 +3595,7 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, switch (response) { case RESPONSE_CANCEL: - exit (1); + exit (EXIT_FAILURE); default: break; } @@ -5580,7 +5590,7 @@ ARDOUR_UI::audioengine_became_silent () case Gtk::RESPONSE_NO: /* save and quit */ save_state_canfail (""); - exit (0); + exit (EXIT_SUCCESS); break; case Gtk::RESPONSE_CANCEL: diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index b7c3d7e2c5..a96738b932 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -61,7 +61,7 @@ #include "selection_memento.h" #include "tempo_curve.h" -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" namespace Gtkmm2ext { class Bindings; diff --git a/gtk2_ardour/editor_pt_import.cc b/gtk2_ardour/editor_pt_import.cc index 12b6484b78..d81a03f3cf 100644 --- a/gtk2_ardour/editor_pt_import.cc +++ b/gtk2_ardour/editor_pt_import.cc @@ -44,7 +44,7 @@ #include "ardour/session.h" #include "pbd/memento_command.h" -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include "ardour_ui.h" #include "cursor_context.h" diff --git a/gtk2_ardour/export_channel_selector.cc b/gtk2_ardour/export_channel_selector.cc index a17cfbaf3a..14053f9eb0 100644 --- a/gtk2_ardour/export_channel_selector.cc +++ b/gtk2_ardour/export_channel_selector.cc @@ -19,6 +19,9 @@ */ #include +#include + +#include #include "pbd/convert.h" @@ -29,8 +32,6 @@ #include "ardour/route.h" #include "ardour/session.h" -#include - #include "export_channel_selector.h" #include "route_sorter.h" @@ -547,18 +548,37 @@ RegionExportChannelSelector::handle_selection () TrackExportChannelSelector::TrackExportChannelSelector (ARDOUR::Session * session, ProfileManagerPtr manager) : ExportChannelSelector(session, manager) , track_output_button(_("Apply track/bus processing")) - , select_tracks_button (_("Select all tracks")) - , select_busses_button (_("Select all busses")) - , select_none_button (_("Deselect all")) { pack_start(main_layout); + // Populate Selection Menu + { + using namespace Gtk::Menu_Helpers; + + select_menu.set_text (_("Selection Actions")); + select_menu.disable_scrolling (); + + select_menu.AddMenuElem (MenuElem (_("Select tracks"), sigc::mem_fun (*this, &TrackExportChannelSelector::select_tracks))); + select_menu.AddMenuElem (MenuElem (_("Select busses"), sigc::mem_fun (*this, &TrackExportChannelSelector::select_busses))); + select_menu.AddMenuElem (MenuElem (_("Deselect all"), sigc::mem_fun (*this, &TrackExportChannelSelector::select_none))); + select_menu.AddMenuElem (SeparatorElem ()); + + exclude_hidden = new Gtk::CheckMenuItem (_("Exclude Hidden")); + exclude_hidden->set_active (false); + exclude_hidden->show(); + select_menu.AddMenuElem (*exclude_hidden); + + exclude_muted = new Gtk::CheckMenuItem (_("Exclude Muted")); + exclude_muted->set_active (true); + exclude_muted->show(); + select_menu.AddMenuElem (*exclude_muted); + } + // Options - options_box.pack_start(track_output_button); - options_box.pack_start (select_tracks_button); - options_box.pack_start (select_busses_button); - options_box.pack_start (select_none_button); - main_layout.pack_start(options_box, false, false); + options_box.set_spacing (8); + options_box.pack_start (track_output_button, false, false); + options_box.pack_start (select_menu, false, false); + main_layout.pack_start (options_box, false, false); // Track scroller track_scroller.add (track_view); @@ -589,10 +609,6 @@ TrackExportChannelSelector::TrackExportChannelSelector (ARDOUR::Session * sessio column->pack_start (*text_renderer, false); column->add_attribute (text_renderer->property_text(), track_cols.label); - select_tracks_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_tracks)); - select_busses_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_busses)); - select_none_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_none)); - track_output_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::track_outputs_selected)); fill_list(); @@ -600,6 +616,12 @@ TrackExportChannelSelector::TrackExportChannelSelector (ARDOUR::Session * sessio show_all_children (); } +TrackExportChannelSelector::~TrackExportChannelSelector () +{ + delete exclude_hidden; + delete exclude_muted; +} + void TrackExportChannelSelector::sync_with_manager () { @@ -610,11 +632,19 @@ TrackExportChannelSelector::sync_with_manager () void TrackExportChannelSelector::select_tracks () { + bool excl_hidden = exclude_hidden->get_active (); + bool excl_muted = exclude_muted->get_active (); + for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) { Gtk::TreeModel::Row row = *it; boost::shared_ptr route = row[track_cols.route]; if (boost::dynamic_pointer_cast (route)) { - // it's a track + if (excl_muted && route->muted ()) { + continue; + } + if (excl_hidden && route->is_hidden ()) { + continue; + } row[track_cols.selected] = true; } } @@ -624,11 +654,19 @@ TrackExportChannelSelector::select_tracks () void TrackExportChannelSelector::select_busses () { + bool excl_hidden = exclude_hidden->get_active (); + bool excl_muted = exclude_muted->get_active (); + for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) { Gtk::TreeModel::Row row = *it; boost::shared_ptr route = row[track_cols.route]; if (!boost::dynamic_pointer_cast (route)) { - // it's not a track, must be a bus + if (excl_muted && route->muted ()) { + continue; + } + if (excl_hidden && route->is_hidden ()) { + continue; + } row[track_cols.selected] = true; } } diff --git a/gtk2_ardour/export_channel_selector.h b/gtk2_ardour/export_channel_selector.h index 1be8ec3547..d9cab3caf4 100644 --- a/gtk2_ardour/export_channel_selector.h +++ b/gtk2_ardour/export_channel_selector.h @@ -43,6 +43,8 @@ #include #include +#include "widgets/ardour_dropdown.h" + namespace ARDOUR { class Session; class ExportChannelConfiguration; @@ -243,6 +245,7 @@ class TrackExportChannelSelector : public ExportChannelSelector { public: TrackExportChannelSelector (ARDOUR::Session * session, ProfileManagerPtr manager); + ~TrackExportChannelSelector (); virtual void sync_with_manager (); @@ -274,11 +277,11 @@ class TrackExportChannelSelector : public ExportChannelSelector Gtk::ScrolledWindow track_scroller; - Gtk::HBox options_box; - Gtk::CheckButton track_output_button; - Gtk::Button select_tracks_button; - Gtk::Button select_busses_button; - Gtk::Button select_none_button; + Gtk::HBox options_box; + Gtk::CheckButton track_output_button; + ArdourWidgets::ArdourDropdown select_menu; + Gtk::CheckMenuItem* exclude_hidden; + Gtk::CheckMenuItem* exclude_muted; void select_tracks (); void select_busses (); void select_none (); diff --git a/gtk2_ardour/gain_meter.cc b/gtk2_ardour/gain_meter.cc index 35cb66407b..1b6d32f904 100644 --- a/gtk2_ardour/gain_meter.cc +++ b/gtk2_ardour/gain_meter.cc @@ -1117,3 +1117,9 @@ GainMeter::route_active_changed () meter_configuration_changed (_meter->input_streams ()); } } + +void +GainMeter::redraw_metrics () +{ + GainMeterBase::redraw_metrics (); +} diff --git a/gtk2_ardour/gain_meter.h b/gtk2_ardour/gain_meter.h index bbc8465d57..f1bfc8e57a 100644 --- a/gtk2_ardour/gain_meter.h +++ b/gtk2_ardour/gain_meter.h @@ -225,6 +225,7 @@ class GainMeter : public GainMeterBase, public Gtk::VBox gint meter_ticks1_expose (GdkEventExpose *); gint meter_ticks2_expose (GdkEventExpose *); void on_style_changed (const Glib::RefPtr&); + void redraw_metrics (); private: diff --git a/gtk2_ardour/latency_gui.cc b/gtk2_ardour/latency_gui.cc index d5ed0c2a32..a323b4fbc2 100644 --- a/gtk2_ardour/latency_gui.cc +++ b/gtk2_ardour/latency_gui.cc @@ -24,6 +24,8 @@ #include "pbd/convert.h" #include "pbd/error.h" +#include "pbd/unwind.h" + #include "ardour/latent.h" #include "gtkmm2ext/utils.h" @@ -64,15 +66,14 @@ LatencyBarController::get_label (double&) } LatencyGUI::LatencyGUI (Latent& l, samplepos_t sr, samplepos_t psz) - : _latent (l), - initial_value (_latent.effective_latency ()), - sample_rate (sr), - period_size (psz), - ignored (new PBD::IgnorableControllable()), - /* max 1 second, step by samples, page by msecs */ - adjustment (initial_value, 0.0, sample_rate, 1.0, sample_rate / 1000.0f), - bc (adjustment, this), - reset_button (_("Reset")) + : _latent (l) + , sample_rate (sr) + , period_size (psz) + , ignored (new PBD::IgnorableControllable()) + , _ignore_change (false) + , adjustment (0, 0.0, sample_rate, 1.0, sample_rate / 1000.0f) /* max 1 second, step by samples, page by msecs */ + , bc (adjustment, this) + , reset_button (_("Reset")) { Widget* w; @@ -103,6 +104,12 @@ LatencyGUI::LatencyGUI (Latent& l, samplepos_t sr, samplepos_t psz) plus_button.signal_clicked().connect (sigc::bind (sigc::mem_fun (*this, &LatencyGUI::change_latency_from_button), 1)); reset_button.signal_clicked().connect (sigc::mem_fun (*this, &LatencyGUI::reset)); + /* Limit value to adjustment range (max = sample_rate). + * Otherwise if the signal_latency() is larger than the adjustment's max, + * LatencyGUI::finish() would set the adjustment's max value as custom-latency. + */ + adjustment.set_value (std::min (sample_rate, _latent.signal_latency ())); + adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &LatencyGUI::finish)); bc.set_size_request (-1, 25); @@ -116,28 +123,26 @@ LatencyGUI::LatencyGUI (Latent& l, samplepos_t sr, samplepos_t psz) void LatencyGUI::finish () { - samplepos_t new_value = (samplepos_t) adjustment.get_value(); - if (new_value != initial_value) { - _latent.set_user_latency (new_value); + if (_ignore_change) { + return; } + samplepos_t new_value = (samplepos_t) adjustment.get_value(); + _latent.set_user_latency (new_value); } void LatencyGUI::reset () { _latent.unset_user_latency (); - initial_value = std::min (sample_rate, _latent.signal_latency ()); - adjustment.set_value (initial_value); + PBD::Unwinder uw (_ignore_change, true); + adjustment.set_value (std::min (sample_rate, _latent.signal_latency ())); } void LatencyGUI::refresh () { - /* limit to adjustment range, otherwise LatencyGUI::finish() would - * set the adjustment's value as custom-latency - */ - initial_value = std::min (sample_rate, _latent.effective_latency ()); - adjustment.set_value (initial_value); + PBD::Unwinder uw (_ignore_change, true); + adjustment.set_value (std::min (sample_rate, _latent.effective_latency ())); } void @@ -175,5 +180,3 @@ LatencyDialog::LatencyDialog (const std::string& title, Latent& l, samplepos_t s show_all (); run (); } - - diff --git a/gtk2_ardour/latency_gui.h b/gtk2_ardour/latency_gui.h index 943944571e..25e469abfc 100644 --- a/gtk2_ardour/latency_gui.h +++ b/gtk2_ardour/latency_gui.h @@ -60,17 +60,19 @@ public: LatencyGUI (ARDOUR::Latent&, samplepos_t sample_rate, samplepos_t period_size); ~LatencyGUI() { } - void finish (); - void reset (); void refresh (); private: + void reset (); + void finish (); + ARDOUR::Latent& _latent; - samplepos_t initial_value; samplepos_t sample_rate; samplepos_t period_size; + boost::shared_ptr ignored; + bool _ignore_change; Gtk::Adjustment adjustment; LatencyBarController bc; Gtk::HBox hbox1; diff --git a/gtk2_ardour/luainstance.cc b/gtk2_ardour/luainstance.cc index 0756d803be..f5d768788f 100644 --- a/gtk2_ardour/luainstance.cc +++ b/gtk2_ardour/luainstance.cc @@ -45,6 +45,7 @@ #include "luainstance.h" #include "luasignal.h" #include "marker.h" +#include "mixer_ui.h" #include "region_view.h" #include "processor_box.h" #include "time_axis_view.h" @@ -379,6 +380,10 @@ namespace LuaMixer { }; +static void mixer_screenshot (const std::string& fn) { + Mixer_UI::instance()->screenshot (fn); +} + //////////////////////////////////////////////////////////////////////////////// static PBD::ScopedConnectionList _luaexecs; @@ -747,6 +752,8 @@ LuaInstance::register_classes (lua_State* L) .addFunction ("http_get", &http_get_unlogged) + .addFunction ("mixer_screenshot", &mixer_screenshot) + .addFunction ("processor_selection", &LuaMixer::processor_selection) .beginStdCPtrList ("ArdourMarkerList") diff --git a/gtk2_ardour/main.cc b/gtk2_ardour/main.cc index 2635dc9c50..ea75744f51 100644 --- a/gtk2_ardour/main.cc +++ b/gtk2_ardour/main.cc @@ -149,7 +149,7 @@ This could be due to misconfiguration or to an error inside %2.\n\ Click OK to exit %1."), PROGRAM_NAME, AudioEngine::instance()->current_backend_name())); msg.run (); - _exit (0); + _exit (EXIT_SUCCESS); } else { @@ -355,7 +355,7 @@ int main (int argc, char *argv[]) if (parse_opts (argc, argv)) { command_line_parse_error (&argc, &argv); - exit (1); + exit (EXIT_FAILURE); } cout << PROGRAM_NAME @@ -369,7 +369,7 @@ int main (int argc, char *argv[]) << endl; if (just_version) { - exit (0); + exit (EXIT_SUCCESS); } if (no_splash) { @@ -390,11 +390,7 @@ int main (int argc, char *argv[]) "Run %1 from a commandline for more information."), PROGRAM_NAME), false, Gtk::MESSAGE_ERROR , Gtk::BUTTONS_OK, true); msg.run (); - exit (1); - } - - if (curvetest_file) { - return curvetest (curvetest_file); + exit (EXIT_FAILURE); } #ifndef PLATFORM_WINDOWS @@ -407,14 +403,14 @@ int main (int argc, char *argv[]) if (UIConfiguration::instance().pre_gui_init ()) { error << _("Could not complete pre-GUI initialization") << endmsg; - exit (1); + exit (EXIT_FAILURE); } try { ui = new ARDOUR_UI (&argc, &argv, localedir.c_str()); } catch (failed_constructor& err) { error << string_compose (_("could not create %1 GUI"), PROGRAM_NAME) << endmsg; - exit (1); + exit (EXIT_FAILURE); } #ifndef NDEBUG diff --git a/gtk2_ardour/midi_streamview.cc b/gtk2_ardour/midi_streamview.cc index 52ac8ff3e7..1495a2898d 100644 --- a/gtk2_ardour/midi_streamview.cc +++ b/gtk2_ardour/midi_streamview.cc @@ -212,7 +212,7 @@ MidiStreamView::display_track (boost::shared_ptr tr) draw_note_lines(); - NoteRangeChanged(); + NoteRangeChanged(); /* EMIT SIGNAL*/ } void @@ -424,7 +424,7 @@ MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region apply_note_range_to_regions (); } - NoteRangeChanged(); + NoteRangeChanged(); /* EMIT SIGNAL*/ } void diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 72fff56ef6..0eb6a73001 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -158,6 +158,8 @@ MidiTimeAxisView::set_route (boost::shared_ptr rt) subplugin_menu.set_name ("ArdourContextMenu"); + _note_range_changed_connection.disconnect(); + if (!gui_property ("note-range-min").empty ()) { midi_view()->apply_note_range (atoi (gui_property ("note-range-min").c_str()), atoi (gui_property ("note-range-max").c_str()), @@ -202,13 +204,6 @@ MidiTimeAxisView::set_route (boost::shared_ptr rt) _piano_roll_header->ToggleNoteSelection.connect ( sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection)); - /* Update StreamView during scroomer drags.*/ - - _range_scroomer->DragStarting.connect ( - sigc::mem_fun (*this, &MidiTimeAxisView::start_scroomer_update)); - _range_scroomer->DragFinishing.connect ( - sigc::mem_fun (*this, &MidiTimeAxisView::stop_scroomer_update)); - /* Put the scroomer and the keyboard in a VBox with a padding label so that they can be reduced in height for stacked-view tracks. @@ -231,8 +226,13 @@ MidiTimeAxisView::set_route (boost::shared_ptr rt) time_axis_hbox.pack_end(*v, false, false, 0); midi_scroomer_size_group->add_widget (*v); - midi_view()->NoteRangeChanged.connect ( - sigc::mem_fun(*this, &MidiTimeAxisView::update_range)); + /* callback from StreamView scroomer drags, as well as + * automatic changes of note-range (e.g. at rec-stop). + * This callback is used to save the note-range-min/max + * GUI Object property + */ + _note_range_changed_connection = midi_view()->NoteRangeChanged.connect ( + sigc::mem_fun (*this, &MidiTimeAxisView::note_range_changed)); /* ask for notifications of any new RegionViews */ _view->RegionViewAdded.connect ( @@ -383,19 +383,6 @@ MidiTimeAxisView::setup_midnam_patches () } } -void -MidiTimeAxisView::start_scroomer_update () -{ - _note_range_changed_connection.disconnect(); - _note_range_changed_connection = midi_view()->NoteRangeChanged.connect ( - sigc::mem_fun (*this, &MidiTimeAxisView::note_range_changed)); -} -void -MidiTimeAxisView::stop_scroomer_update () -{ - _note_range_changed_connection.disconnect(); -} - void MidiTimeAxisView::update_patch_selector () { @@ -1147,11 +1134,6 @@ MidiTimeAxisView::set_note_range (MidiStreamView::VisibleNoteRange range, bool a } } -void -MidiTimeAxisView::update_range() -{ -} - void MidiTimeAxisView::show_all_automation (bool apply_to_selection) { diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h index 68c2c796b1..4ca76e37dd 100644 --- a/gtk2_ardour/midi_time_axis.h +++ b/gtk2_ardour/midi_time_axis.h @@ -96,8 +96,6 @@ public: boost::shared_ptr get_device_names(); boost::shared_ptr get_device_mode(); - void update_range(); - Gtk::CheckMenuItem* automation_child_menu_item (Evoral::Parameter); StepEditor* step_editor() { return _step_editor; } @@ -121,8 +119,6 @@ private: void setup_midnam_patches (); void update_patch_selector (); - void start_scroomer_update (); - void stop_scroomer_update (); sigc::connection _note_range_changed_connection; void model_changed(const std::string& model); diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc index 35801cbd0f..dc86a27222 100644 --- a/gtk2_ardour/mixer_strip.cc +++ b/gtk2_ardour/mixer_strip.cc @@ -106,6 +106,7 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, bool in_mixer) , _comment_button (_("Comments")) , trim_control (ArdourKnob::default_elements, ArdourKnob::Flags (ArdourKnob::Detent | ArdourKnob::ArcToZero)) , _visibility (X_("mixer-element-visibility")) + , _suspend_menu_callbacks (false) , control_slave_ui (sess) { init (); @@ -138,6 +139,7 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, boost::shared_ptr rt , _comment_button (_("Comments")) , trim_control (ArdourKnob::default_elements, ArdourKnob::Flags (ArdourKnob::Detent | ArdourKnob::ArcToZero)) , _visibility (X_("mixer-element-visibility")) + , _suspend_menu_callbacks (false) , control_slave_ui (sess) { init (); @@ -605,11 +607,7 @@ MixerStrip::set_route (boost::shared_ptr rt) control_slave_ui.set_sensitive(true); } - if (_mixer_owned && route()->is_master() ) { - spacer.show(); - } else { - spacer.hide(); - } + hide_master_spacer (false); if (is_track()) { monitor_input_button->show (); @@ -934,10 +932,15 @@ MixerStrip::output_press (GdkEventButton *ev) citems.pop_back (); } - if (!ARDOUR::Profile->get_mixbus()) { - citems.push_back (SeparatorElem()); + citems.push_back (SeparatorElem()); + if (!ARDOUR::Profile->get_mixbus()) { + bool need_separator = false; for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { + if (!_route->output()->can_add_port (*i)) { + continue; + } + need_separator = true; citems.push_back ( MenuElem ( string_compose (_("Add %1 port"), (*i).to_i18n_string()), @@ -945,9 +948,11 @@ MixerStrip::output_press (GdkEventButton *ev) ) ); } + if (need_separator) { + citems.push_back (SeparatorElem()); + } } - citems.push_back (SeparatorElem()); citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast(this)), &RouteUI::edit_output_configuration))); Gtkmm2ext::anchored_menu_popup(&output_menu, &output_button, "", @@ -1040,7 +1045,13 @@ MixerStrip::input_press (GdkEventButton *ev) } citems.push_back (SeparatorElem()); + + bool need_separator = false; for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { + if (!_route->input()->can_add_port (*i)) { + continue; + } + need_separator = true; citems.push_back ( MenuElem ( string_compose (_("Add %1 port"), (*i).to_i18n_string()), @@ -1048,8 +1059,10 @@ MixerStrip::input_press (GdkEventButton *ev) ) ); } + if (need_separator) { + citems.push_back (SeparatorElem()); + } - citems.push_back (SeparatorElem()); citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast(this)), &RouteUI::edit_input_configuration))); Gtkmm2ext::anchored_menu_popup(&input_menu, &input_button, "", @@ -2524,7 +2537,7 @@ MixerStrip::popup_level_meter_menu (GdkEventButton* ev) RadioMenuItem::Group group; - PBD::Unwinder (_suspend_menu_callbacks, true); + PBD::Unwinder uw (_suspend_menu_callbacks, true); add_level_meter_item_point (items, group, _("Input"), MeterInput); add_level_meter_item_point (items, group, _("Pre Fader"), MeterPreFader); add_level_meter_item_point (items, group, _("Post Fader"), MeterPostFader); @@ -2658,3 +2671,13 @@ MixerStrip::set_marked_for_display (bool yn) { return RouteUI::mark_hidden (!yn); } + +void +MixerStrip::hide_master_spacer (bool yn) +{ + if (_mixer_owned && route()->is_master() && !yn) { + spacer.show(); + } else { + spacer.hide(); + } +} diff --git a/gtk2_ardour/mixer_strip.h b/gtk2_ardour/mixer_strip.h index 1510d38242..7164d15e63 100644 --- a/gtk2_ardour/mixer_strip.h +++ b/gtk2_ardour/mixer_strip.h @@ -114,6 +114,9 @@ public: return _mixer_owned; } + /* used for screenshots */ + void hide_master_spacer (bool); + void hide_things (); sigc::signal WidthChanged; diff --git a/gtk2_ardour/mixer_ui.cc b/gtk2_ardour/mixer_ui.cc index 52b5708216..71f6bca825 100644 --- a/gtk2_ardour/mixer_ui.cc +++ b/gtk2_ardour/mixer_ui.cc @@ -30,6 +30,7 @@ #include #include +#include #include #include "pbd/convert.h" @@ -160,15 +161,14 @@ Mixer_UI::Mixer_UI () #endif _group_tabs = new MixerGroupTabs (this); - VBox* b = manage (new VBox); - b->set_spacing (0); - b->set_border_width (0); - b->pack_start (*_group_tabs, PACK_SHRINK); - b->pack_start (strip_packer); - b->show_all (); - b->signal_scroll_event().connect (sigc::mem_fun (*this, &Mixer_UI::on_scroll_event), false); + strip_group_box.set_spacing (0); + strip_group_box.set_border_width (0); + strip_group_box.pack_start (*_group_tabs, PACK_SHRINK); + strip_group_box.pack_start (strip_packer); + strip_group_box.show_all (); + strip_group_box.signal_scroll_event().connect (sigc::mem_fun (*this, &Mixer_UI::on_scroll_event), false); - scroller.add (*b); + scroller.add (strip_group_box); scroller.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC); setup_track_display (); @@ -3502,3 +3502,83 @@ Mixer_UI::vca_unassign (boost::shared_ptr vca) } } } + +bool +Mixer_UI::screenshot (std::string const& filename) +{ + if (!_session) { + return false; + } + + int height = strip_packer.get_height(); + bool with_vca = vca_vpacker.is_visible (); + MixerStrip* master = strip_by_route (_session->master_out ()); + + Gtk::OffscreenWindow osw; + Gtk::HBox b; + osw.add (b); + b.show (); + + /* unpack widgets, add to OffscreenWindow */ + + strip_group_box.remove (strip_packer); + b.pack_start (strip_packer, false, false); + /* hide extra elements inside strip_packer */ + add_button.hide (); + scroller_base.hide (); +#ifdef MIXBUS + mb_shadow.hide(); +#endif + + if (with_vca) { + /* work around Gtk::ScrolledWindow */ + Gtk::Viewport* viewport = (Gtk::Viewport*) vca_scroller.get_child(); + viewport->remove (); // << vca_hpacker + b.pack_start (vca_hpacker, false, false); + /* hide some growing widgets */ + add_vca_button.hide (); + vca_scroller_base.hide(); + } + + if (master) { + out_packer.remove (*master); + b.pack_start (*master, false, false); + master->hide_master_spacer (true); + } + + /* prepare the OffscreenWindow for rendering */ + osw.set_size_request (-1, height); + osw.show (); + osw.queue_resize (); + osw.queue_draw (); + osw.get_window()->process_updates (true); + + /* create screenshot */ + Glib::RefPtr pb = osw.get_pixbuf (); + pb->save (filename, "png"); + + /* unpack elements before destorying the Box & OffscreenWindow */ + list children = b.get_children(); + for (list::iterator child = children.begin(); child != children.end(); ++child) { + b.remove (**child); + } + osw.remove (); + + /* now re-pack the widgets into the main mixer window */ + add_button.show (); + scroller_base.show (); +#ifdef MIXBUS + mb_shadow.show(); +#endif + strip_group_box.pack_start (strip_packer); + if (with_vca) { + add_vca_button.show (); + vca_scroller_base.show(); + vca_scroller.add (vca_hpacker); + } + if (master) { + master->hide_master_spacer (false); + out_packer.pack_start (*master, false, false); + } + return true; +} diff --git a/gtk2_ardour/mixer_ui.h b/gtk2_ardour/mixer_ui.h index 9ea2dbe59a..cda2d4e1bd 100644 --- a/gtk2_ardour/mixer_ui.h +++ b/gtk2_ardour/mixer_ui.h @@ -147,6 +147,8 @@ public: void showhide_mixbusses (bool on); #endif + bool screenshot (std::string const&); + protected: void set_axis_targets_for_operation (); ARDOUR::AutomationControlSet selected_gaincontrols (); @@ -175,6 +177,7 @@ private: ArdourWidgets::VPane rhs_pane1; ArdourWidgets::VPane rhs_pane2; ArdourWidgets::HPane inner_pane; + Gtk::VBox strip_group_box; Gtk::HBox strip_packer; Gtk::ScrolledWindow vca_scroller; Gtk::HBox vca_hpacker; diff --git a/gtk2_ardour/opts.cc b/gtk2_ardour/opts.cc index d0657abe8b..3860434263 100644 --- a/gtk2_ardour/opts.cc +++ b/gtk2_ardour/opts.cc @@ -37,20 +37,17 @@ using namespace std; string ARDOUR_COMMAND_LINE::session_name = ""; string ARDOUR_COMMAND_LINE::backend_client_name = "ardour"; -string ARDOUR_COMMAND_LINE::backend_session_uuid; bool ARDOUR_COMMAND_LINE::show_key_actions = false; bool ARDOUR_COMMAND_LINE::show_actions = false; bool ARDOUR_COMMAND_LINE::no_splash = false; bool ARDOUR_COMMAND_LINE::just_version = false; bool ARDOUR_COMMAND_LINE::use_vst = true; bool ARDOUR_COMMAND_LINE::new_session = false; -char* ARDOUR_COMMAND_LINE::curvetest_file = 0; bool ARDOUR_COMMAND_LINE::try_hw_optimization = true; bool ARDOUR_COMMAND_LINE::no_connect_ports = false; string ARDOUR_COMMAND_LINE::keybindings_path = ""; /* empty means use builtin default */ std::string ARDOUR_COMMAND_LINE::menus_file = "ardour.menus"; bool ARDOUR_COMMAND_LINE::finder_invoked_ardour = false; -string ARDOUR_COMMAND_LINE::immediate_save; string ARDOUR_COMMAND_LINE::load_template; bool ARDOUR_COMMAND_LINE::check_announcements = true; @@ -72,14 +69,10 @@ print_help (const char *execname) << _(" -b, --bindings Display all current key bindings\n") << _(" -B, --bypass-plugins Bypass all plugins in an existing session\n") << _(" -c, --name Use a specific backend client name, default is ardour\n") -#ifndef NDEBUG - << _(" -C, --curvetest filename Curve algorithm debugger\n") -#endif << _(" -d, --disable-plugins Disable all plugins (safe mode)\n") #ifndef NDEBUG << _(" -D, --debug Set debug flags. Use \"-D list\" to see available options\n") #endif - << _(" -E, --save Load the specified session, save it to and then quit\n") << _(" -h, --help Print this message\n") << _(" -k, --keybindings Name of key bindings to load\n") << _(" -m, --menus file Use \"file\" to define menus\n") @@ -89,7 +82,6 @@ print_help (const char *execname) << _(" -P, --no-connect-ports Do not connect any ports at startup\n") << _(" -S, --sync Draw the GUI synchronously\n") << _(" -T, --template Use given template for new session\n") - << _(" -U, --uuid Set (jack) backend UUID\n") << _(" -v, --version Print version and exit\n") #ifdef WINDOWS_VST_SUPPORT << _(" -V, --novst Disable WindowsVST support\n") @@ -130,9 +122,6 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) { "new", 1, 0, 'N' }, { "no-hw-optimizations", 0, 0, 'O' }, { "sync", 0, 0, 'S' }, - { "curvetest", 1, 0, 'C' }, - { "save", 1, 0, 'E' }, - { "uuid", 1, 0, 'U' }, { "template", 1, 0, 'T' }, { "no-connect-ports", 0, 0, 'P' }, { 0, 0, 0, 0 } @@ -158,7 +147,7 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) case 'h': print_help (execname); - exit (0); + exit (EXIT_SUCCESS); break; case 'H': #ifndef NDEBUG @@ -187,7 +176,7 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) case 'D': if (PBD::parse_debug_options (optarg)) { - exit (0); + exit (EXIT_SUCCESS); } break; @@ -234,22 +223,10 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) backend_client_name = optarg; break; - case 'C': - curvetest_file = optarg; - break; - case 'k': keybindings_path = optarg; break; - case 'E': - immediate_save = optarg; - break; - - case 'U': - backend_session_uuid = optarg; - break; - default: return print_help(execname); } diff --git a/gtk2_ardour/opts.h b/gtk2_ardour/opts.h index b95d931cc7..ee24572bc1 100644 --- a/gtk2_ardour/opts.h +++ b/gtk2_ardour/opts.h @@ -30,17 +30,14 @@ extern bool show_actions; extern bool no_splash; extern bool just_version; extern std::string backend_client_name; -extern std::string backend_session_uuid; extern bool use_vst; extern bool new_session; -extern char* curvetest_file; extern bool try_hw_optimization; extern bool no_connect_ports; extern bool use_gtk_theme; extern std::string keybindings_path; extern std::string menus_file; extern bool finder_invoked_ardour; -extern std::string immediate_save; extern std::string load_template; extern bool check_announcements; diff --git a/gtk2_ardour/patch_change_widget.cc b/gtk2_ardour/patch_change_widget.cc index f9c88d8068..de20c30bc5 100644 --- a/gtk2_ardour/patch_change_widget.cc +++ b/gtk2_ardour/patch_change_widget.cc @@ -229,7 +229,7 @@ PatchChangeWidget::refill_banks () const int b = bank (_channel); { - PBD::Unwinder (_ignore_spin_btn_signals, true); + PBD::Unwinder uw (_ignore_spin_btn_signals, true); _bank_msb_spin.set_value (b >> 7); _bank_lsb_spin.set_value (b & 127); } diff --git a/gtk2_ardour/plugin_eq_gui.cc b/gtk2_ardour/plugin_eq_gui.cc index 19ee2c9f1a..5b842d62a4 100644 --- a/gtk2_ardour/plugin_eq_gui.cc +++ b/gtk2_ardour/plugin_eq_gui.cc @@ -407,9 +407,15 @@ PluginEqGui::run_impulse_analysis () } samplepos_t sample_pos = 0; - samplecnt_t latency = _plugin->signal_latency (); + samplecnt_t latency = _plugin_insert->effective_latency (); samplecnt_t samples_remain = _buffer_size + latency; + /* Note: https://discourse.ardour.org/t/plugins-ladspa-questions/101292/15 + * Capture the complete response from the beginning, and more than "latency" samples, + * Then unwrap the phase-response corresponding to reported latency, leaving the + * magnitude unchanged. + */ + _impulse_fft->reset (); while (samples_remain > 0) { diff --git a/gtk2_ardour/port_matrix.cc b/gtk2_ardour/port_matrix.cc index 23e9acaf62..b5e7cb008d 100644 --- a/gtk2_ardour/port_matrix.cc +++ b/gtk2_ardour/port_matrix.cc @@ -446,7 +446,7 @@ PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t) if (can_add_channels (bc[dim].bundle)) { /* Start off with options for the `natural' port type */ for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { - if (should_show (*i)) { + if (should_show (*i) && can_add_channel_proxy (w, *i)) { snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str()); sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i))); } @@ -454,7 +454,7 @@ PortMatrix::popup_menu (BundleChannel column, BundleChannel row, uint32_t t) /* Now add other ones */ for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { - if (!should_show (*i)) { + if (!should_show (*i) && can_add_channel_proxy (w, *i)) { snprintf (buf, sizeof (buf), _("Add %s %s"), (*i).to_i18n_string(), channel_noun().c_str()); sub.push_back (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy), w, *i))); } @@ -790,6 +790,17 @@ PortMatrix::remove_all_channels (boost::weak_ptr w) } } +bool +PortMatrix::can_add_channel_proxy (boost::weak_ptr w, DataType t) const +{ + boost::shared_ptr b = w.lock (); + if (!b) { + return false; + } + boost::shared_ptr io = io_from_bundle (b); + return io->can_add_port (t); +} + void PortMatrix::add_channel_proxy (boost::weak_ptr w, DataType t) { diff --git a/gtk2_ardour/port_matrix.h b/gtk2_ardour/port_matrix.h index 09c334b5ef..5fc0bd221f 100644 --- a/gtk2_ardour/port_matrix.h +++ b/gtk2_ardour/port_matrix.h @@ -182,6 +182,7 @@ private: void routes_changed (); void reconnect_to_routes (); void select_arrangement (); + bool can_add_channel_proxy (boost::weak_ptr, ARDOUR::DataType) const; void add_channel_proxy (boost::weak_ptr, ARDOUR::DataType); void remove_channel_proxy (boost::weak_ptr, uint32_t); void rename_channel_proxy (boost::weak_ptr, uint32_t); diff --git a/gtk2_ardour/pt_import_selector.cc b/gtk2_ardour/pt_import_selector.cc index 3446383f2f..9756fc967f 100644 --- a/gtk2_ardour/pt_import_selector.cc +++ b/gtk2_ardour/pt_import_selector.cc @@ -27,7 +27,7 @@ #include "pbd/i18n.h" #include "pbd/file_utils.h" -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include "ardour/session_handle.h" @@ -111,24 +111,35 @@ void PTImportSelector::update_ptf() { if (ptimport_ptf_chooser.get_filename ().size () > 0) { + int err = 0; std::string path = ptimport_ptf_chooser.get_filename (); bool ok = Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_REGULAR | Glib::FILE_TEST_IS_SYMLINK) && !Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR); if (ok) { - if (_ptf->load (path, _session_rate) == -1) { + err = _ptf->load (path, _session_rate); + if (err == -1) { + ptimport_info_text.get_buffer ()->set_text ("Cannot decrypt PT session\n"); + ptimport_import_button.set_sensitive(false); + } else if (err == -2) { ptimport_info_text.get_buffer ()->set_text ("Cannot detect PT session\n"); ptimport_import_button.set_sensitive(false); + } else if (err == -3) { + ptimport_info_text.get_buffer ()->set_text ("Incompatible PT version\n"); + ptimport_import_button.set_sensitive(false); + } else if (err == -4) { + ptimport_info_text.get_buffer ()->set_text ("Cannot parse PT session\n"); + ptimport_import_button.set_sensitive(false); } else { std::string ptinfo = string_compose (_("PT Session [ VALID ]\n\nSession Info:\n\n\nPT v%1 Session @ %2Hz\n\n%3 audio files\n%4 audio regions\n%5 active audio regions\n%6 midi regions\n%7 active midi regions\n\n"), - (int)_ptf->version, - _ptf->sessionrate, - _ptf->audiofiles.size (), - _ptf->regions.size (), - _ptf->tracks.size (), - _ptf->midiregions.size (), - _ptf->miditracks.size () + (int)_ptf->version (), + _ptf->sessionrate (), + _ptf->audiofiles ().size (), + _ptf->regions ().size (), + _ptf->tracks ().size (), + _ptf->midiregions ().size (), + _ptf->miditracks ().size () ); - if (_session_rate != _ptf->sessionrate) { + if (_session_rate != _ptf->sessionrate ()) { ptinfo = string_compose (_("%1WARNING:\n\nSample rate mismatch,\nwill be resampling\n"), ptinfo); } ptimport_info_text.get_buffer ()->set_text (ptinfo); diff --git a/gtk2_ardour/pt_import_selector.h b/gtk2_ardour/pt_import_selector.h index 278a046619..54a9aac96f 100644 --- a/gtk2_ardour/pt_import_selector.h +++ b/gtk2_ardour/pt_import_selector.h @@ -27,7 +27,7 @@ #include #include -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include "ardour_dialog.h" #include "ardour/session.h" diff --git a/gtk2_ardour/route_params_ui.cc b/gtk2_ardour/route_params_ui.cc index 95a945d27c..f4c2b11e99 100644 --- a/gtk2_ardour/route_params_ui.cc +++ b/gtk2_ardour/route_params_ui.cc @@ -60,7 +60,6 @@ using namespace Gtkmm2ext; RouteParams_UI::RouteParams_UI () : ArdourWindow (_("Tracks and Busses")) - , latency_apply_button (Stock::APPLY) , track_menu(0) { insert_box = 0; @@ -114,7 +113,6 @@ RouteParams_UI::RouteParams_UI () update_title(); latency_packer.set_spacing (18); - latency_button_box.pack_start (latency_apply_button); delay_label.set_alignment (0, 0.5); // changeable area @@ -286,7 +284,6 @@ RouteParams_UI::cleanup_latency_frame () if (latency_widget) { latency_frame.remove (); latency_packer.remove (*latency_widget); - latency_packer.remove (latency_button_box); latency_packer.remove (delay_label); latency_connections.drop_connections (); latency_click_connection.disconnect (); @@ -302,15 +299,11 @@ RouteParams_UI::setup_latency_frame () { latency_widget = new LatencyGUI (*(_route->output()), _session->sample_rate(), AudioEngine::instance()->samples_per_cycle()); - char buf[128]; - snprintf (buf, sizeof (buf), _("Latency: %" PRId64 " samples"), _route->signal_latency()); - delay_label.set_text (buf); + refresh_latency (); latency_packer.pack_start (*latency_widget, false, false); - latency_packer.pack_start (latency_button_box, false, false); - latency_packer.pack_start (delay_label); + latency_packer.pack_start (delay_label, false, false); - latency_click_connection = latency_apply_button.signal_clicked().connect (sigc::mem_fun (*latency_widget, &LatencyGUI::finish)); _route->signal_latency_updated.connect (latency_connections, invalidator (*this), boost::bind (&RouteParams_UI::refresh_latency, this), gui_context()); latency_frame.add (latency_packer); diff --git a/gtk2_ardour/route_params_ui.h b/gtk2_ardour/route_params_ui.h index 1bd850daf9..9cf7f99e8d 100644 --- a/gtk2_ardour/route_params_ui.h +++ b/gtk2_ardour/route_params_ui.h @@ -92,8 +92,6 @@ private: Gtk::Frame latency_frame; Gtk::VBox latency_packer; - Gtk::HButtonBox latency_button_box; - Gtk::Button latency_apply_button; LatencyGUI* latency_widget; Gtk::Label delay_label; diff --git a/gtk2_ardour/session_dialog.cc b/gtk2_ardour/session_dialog.cc index b08eda4840..2569219067 100644 --- a/gtk2_ardour/session_dialog.cc +++ b/gtk2_ardour/session_dialog.cc @@ -104,19 +104,19 @@ SessionDialog::SessionDialog (bool require_new, const std::string& session_name, info_frame.set_border_width (12); get_vbox()->pack_start (info_frame, false, false); + if (!template_name.empty()) { + load_template_override = template_name; + } + setup_new_session_page (); - if (!new_only) { + if (!require_new) { setup_initial_choice_box (); get_vbox()->pack_start (ic_vbox, true, true); } else { get_vbox()->pack_start (session_new_vbox, true, true); } - if (!template_name.empty()) { - load_template_override = template_name; - } - get_vbox()->show_all (); /* fill data models and show/hide accordingly */ @@ -139,19 +139,6 @@ SessionDialog::SessionDialog (bool require_new, const std::string& session_name, recent_label.hide (); } } - - /* possibly get out of here immediately if everything is ready to go. - We still need to set up the whole dialog because of the way - ARDOUR_UI::get_session_parameters() might skip it on a first - pass then require it for a second pass (e.g. when there - is an error with session loading and we have to ask the user - what to do next). - */ - - if (!session_name.empty() && !require_new) { - response (RESPONSE_OK); - return; - } } SessionDialog::SessionDialog () @@ -183,8 +170,6 @@ SessionDialog::SessionDialog () } - - SessionDialog::~SessionDialog() { } @@ -285,8 +270,36 @@ std::string SessionDialog::session_template_name () { if (!load_template_override.empty()) { - string the_path (ARDOUR::user_template_directory()); - return Glib::build_filename (the_path, load_template_override + ARDOUR::template_suffix); + /* compare to SessionDialog::populate_session_templates */ + + /* compare by name (path may or may not be UTF-8) */ + vector templates; + find_session_templates (templates, false); + for (vector::iterator x = templates.begin(); x != templates.end(); ++x) { + if ((*x).name == load_template_override) { + return (*x).path; + } + } + + /* look up script by name */ + LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit)); + LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction)); + for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) { + if ((*s)->subtype & LuaScriptInfo::SessionSetup) { + scripts.push_back (*s); + } + } + std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter()); + for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) { + if ((*s)->name == load_template_override) { + return "urn:ardour:" + (*s)->path; + } + } + + /* this will produce a more or less meaninful error later: + * "ERROR: Could not open session template [abs-path to user-config dir]" + */ + return Glib::build_filename (ARDOUR::user_template_directory (), load_template_override); } if (template_chooser.get_selection()->count_selected_rows() > 0) { @@ -305,8 +318,11 @@ SessionDialog::session_template_name () std::string SessionDialog::session_name (bool& should_be_new) { - if (!_provided_session_name.empty() && !new_only) { - should_be_new = false; + if (!_provided_session_name.empty()) { + /* user gave name on cmdline/invocation. Did they also specify + that it must be a new session? + */ + should_be_new = new_only; return _provided_session_name; } @@ -338,7 +354,7 @@ SessionDialog::session_name (bool& should_be_new) std::string SessionDialog::session_folder () { - if (!_provided_session_path.empty() && !new_only) { + if (!_provided_session_path.empty()) { return _provided_session_path; } @@ -564,14 +580,6 @@ SessionDialog::open_button_pressed (GdkEventButton* ev) return true; } -struct LuaScriptListSorter -{ - bool operator() (LuaScriptInfoPtr const a, LuaScriptInfoPtr const b) const { - return ARDOUR::cmp_nocase_utf8 (a->name, b->name) < 0; - } -}; - - void SessionDialog::populate_session_templates () { @@ -592,8 +600,7 @@ SessionDialog::populate_session_templates () } } - LuaScriptListSorter cmp; - std::sort (scripts.begin(), scripts.end(), cmp); + std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter()); for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) { TreeModel::Row row = *(template_model->append ()); @@ -690,7 +697,7 @@ SessionDialog::setup_new_session_page () HBox* template_hbox = manage (new HBox); //if the "template override" is provided, don't give the user any template selections (?) - if ( load_template_override.empty() ) { + if (load_template_override.empty()) { template_hbox->set_spacing (8); Gtk::ScrolledWindow *template_scroller = manage (new Gtk::ScrolledWindow()); diff --git a/gtk2_ardour/ui_config_vars.h b/gtk2_ardour/ui_config_vars.h index 0789a1a7d0..87564d2f5b 100644 --- a/gtk2_ardour/ui_config_vars.h +++ b/gtk2_ardour/ui_config_vars.h @@ -107,7 +107,7 @@ UI_CONFIG_VARIABLE (bool, open_gui_after_adding_plugin, "open-gui-after-adding-p UI_CONFIG_VARIABLE (bool, show_inline_display_by_default, "show-inline-display-by-default", true) UI_CONFIG_VARIABLE (bool, prefer_inline_over_gui, "prefer-inline-over-gui", true) UI_CONFIG_VARIABLE (uint32_t, max_inline_controls, "max-inline-controls", 32) /* per processor */ -UI_CONFIG_VARIABLE (uint32_t, action_table_columns, "action-table-columns", 0) +UI_CONFIG_VARIABLE (uint32_t, action_table_columns, "action-table-columns", 3) UI_CONFIG_VARIABLE (bool, use_wm_visibility, "use-wm-visibility", true) UI_CONFIG_VARIABLE (std::string, stripable_color_palette, "stripable-color-palette", "#AA3939:#FFAAAA:#D46A6A:#801515:#550000:#AA8E39:#FFEAAA:#D4BA6A:#806515:#554000:#343477:#8080B3:#565695:#1A1A59:#09093B:#2D882D:#88CC88:#55AA55:#116611:#004400") /* Gtk::ColorSelection::palette_to_string */ UI_CONFIG_VARIABLE (bool, use_note_bars_for_velocity, "use-note-bars-for-velocity", true) diff --git a/headless/hardbg b/headless/hardbg index ffab6221b0..816c8be185 100755 --- a/headless/hardbg +++ b/headless/hardbg @@ -1,5 +1,12 @@ #!/bin/sh -. `dirname "$0"`/../build/headless/hardev_common_waf.sh +TOP=`dirname "$0"`/.. +. $TOP/build/gtk2_ardour/ardev_common_waf.sh LD_LIBRARY_PATH=$LD_LIBRARY_PATH export ARDOUR_INSIDE_GDB=1 -exec gdb --args $TOP/$EXECUTABLE $@ +if test -n "`which gdb`"; then + exec gdb --args $TOP/build/headless/hardour-$ARDOURVERSION "$@" +fi +if test -n "`which lldb`"; then + exec lldb -- $TOP/build/headless/hardour-$ARDOURVERSION "$@" +fi +echo "neither gdb nor lldb was found." diff --git a/headless/hardev b/headless/hardev index 75c29e54de..b22f9c2552 100755 --- a/headless/hardev +++ b/headless/hardev @@ -1,4 +1,5 @@ #!/bin/sh -. `dirname "$0"`/../build/headless/hardev_common_waf.sh +TOP=`dirname "$0"`/.. +. $TOP/build/gtk2_ardour/ardev_common_waf.sh export UBUNTU_MENUPROXY="" -exec $TOP/$EXECUTABLE "$@" +exec $TOP/build/headless/hardour-$ARDOURVERSION "$@" diff --git a/headless/hardev_common.sh.in b/headless/hardev_common.sh.in deleted file mode 100644 index 3af6c76efe..0000000000 --- a/headless/hardev_common.sh.in +++ /dev/null @@ -1,39 +0,0 @@ -TOP=`dirname "$0"`/.. - -#export G_DEBUG=fatal_criticals - -libs=$TOP/@LIBS@ - -# -# when running ardev, the various parts of Ardour have not been consolidated into the locations that they -# would normally end up after an install. We therefore need to set up environment variables so that we -# can find all the components. -# - -export ARDOUR_PATH=$TOP/gtk2_ardour/icons:$TOP/gtk2_ardour/pixmaps:$TOP/build/gtk2_ardour:$TOP/gtk2_ardour:. -export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote -export ARDOUR_PANNER_PATH=$libs/panners -export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:. -export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:. -export ARDOUR_MCP_PATH=$TOP/mcp:. -export ARDOUR_EXPORT_FORMATS_PATH=$TOP/export:. -export ARDOUR_BACKEND_PATH=$libs/backends/jack - -# -# even though we set the above variables, ardour requires that these -# two also be set. the above settings will override them. -# - -export ARDOUR_CONFIG_PATH=$TOP:$TOP/gtk2_ardour:$TOP/build:$TOP/build/gtk2_ardour -export ARDOUR_DLL_PATH=$libs - -export GTK_PATH=~/.ardour3:$libs/clearlooks-newer -export VAMP_PATH=$libs/vamp-plugins${VAMP_PATH:+:$VAMP_PATH} - -export LD_LIBRARY_PATH=$libs/qm-dsp:$libs/vamp-sdk:$libs/surfaces:$libs/surfaces/control_protocol:$libs/ardour:$libs/midi++2:$libs/pbd:$libs/rubberband:$libs/soundtouch:$libs/gtkmm2ext:$libs/gnomecanvas:$libs/libsndfile:$libs/appleutility:$libs/taglib:$libs/evoral:$libs/evoral/src/libsmf:$libs/audiographer:$libs/temporal:$libs/libltc:$libs/canvas:$libs/ardouralsautil${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} - -# DYLD_LIBRARY_PATH is for darwin. -export DYLD_FALLBACK_LIBRARY_PATH=$LD_LIBRARY_PATH - -ARDOURVERSION=@VERSION@ -EXECUTABLE=@EXECUTABLE@ diff --git a/headless/harval b/headless/harval index fa3430e684..14331e88eb 100755 --- a/headless/harval +++ b/headless/harval @@ -1,8 +1,12 @@ #!/bin/sh -. `dirname "$0"`/../build/headless/hardev_common_waf.sh -LD_LIBRARY_PATH=$LD_LIBRARY_PATH +VALGRIND_OPTIONS="$VALGRIND_OPTIONS --num-callers=50" +VALGRIND_OPTIONS="$VALGRIND_OPTIONS --error-limit=no" +#VALGRIND_OPTIONS="$VALGRIND_OPTIONS --leak-check=full --leak-resolution=high" +#VALGRIND_OPTIONS="$VALGRIND_OPTIONS --show-leak-kinds=all -v" +#VALGRIND_OPTIONS="$VALGRIND_OPTIONS --log-file=/tmp/hardour-%p.log" TOP=`dirname "$0"`/.. +. $TOP/build/gtk2_ardour/ardev_common_waf.sh if uname | grep -q arwin; then OBJSUPP="--suppressions=${TOP}/tools/objc.supp" fi @@ -12,4 +16,4 @@ exec valgrind --tool=memcheck \ --track-origins=yes \ --suppressions=${TOP}/tools/valgrind.supp \ $OBJSUPP \ - $TOP/$EXECUTABLE --novst "$@" + $TOP/build/headless/hardour-$ARDOURVERSION --novst "$@" diff --git a/headless/load_session.cc b/headless/load_session.cc index 712e32c168..b02650340a 100644 --- a/headless/load_session.cc +++ b/headless/load_session.cc @@ -1,34 +1,43 @@ -#include #include #include +#include -#include "pbd/failed_constructor.h" -#include "pbd/error.h" +#ifndef PLATFORM_WINDOWS +#include +#endif + +#include + +#include "pbd/convert.h" +#include "pbd/crossthread.h" #include "pbd/debug.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" #include "ardour/ardour.h" #include "ardour/audioengine.h" +#include "ardour/revision.h" #include "ardour/session.h" +#include "control_protocol/control_protocol.h" + #include "misc.h" using namespace std; using namespace ARDOUR; using namespace PBD; -#ifdef PLATFORM_WINDOWS -#include -#define sleep(X) Sleep((X) * 1000) -#endif - static const char* localedir = LOCALEDIR; -TestReceiver test_receiver; +static string backend_client_name; +static string backend_name = "JACK"; +static CrossThreadChannel xthread (true); +static TestReceiver test_receiver; /** @param dir Session directory. * @param state Session state file, without .ardour suffix. */ -Session * +static Session* load_session (string dir, string state) { SessionEvent::create_per_thread_pool ("test", 512); @@ -40,14 +49,14 @@ load_session (string dir, string state) AudioEngine* engine = AudioEngine::create (); - if (!engine->set_default_backend ()) { - std::cerr << "Cannot create Audio/MIDI engine\n"; - ::exit (1); + if (!engine->set_backend (backend_name, backend_client_name, "")) { + std::cerr << "Cannot set Audio/MIDI engine backend\n"; + exit (EXIT_FAILURE); } if (engine->start () != 0) { std::cerr << "Cannot start Audio/MIDI engine\n"; - ::exit (1); + exit (EXIT_FAILURE); } Session* session = new Session (*engine, dir, state); @@ -55,15 +64,52 @@ load_session (string dir, string state) return session; } -string session_name = ""; -string backend_client_name = "ardour"; -string backend_session_uuid; -bool just_version = false; -bool use_vst = true; -bool try_hw_optimization = true; -bool no_connect_ports = false; +static void +access_action (const std::string& action_group, const std::string& action_item) +{ + if (action_group == "Common" && action_item == "Quit") { + xthread.deliver ('x'); + } +} -void +static void +engine_halted (const char* reason) +{ + cerr << "The audio backend has been shutdown"; + if (reason && strlen (reason) > 0) { + cerr << ": " << reason; + } else { + cerr << "."; + } + cerr << endl; + xthread.deliver ('x'); +} + +#ifndef PLATFORM_WINDOWS +static void +wearedone (int) +{ + cerr << "caught signal - terminating." << endl; + xthread.deliver ('x'); +} +#endif + +static void +print_version () +{ + cout + << PROGRAM_NAME + << VERSIONSTRING + << " (built using " + << ARDOUR::revision +#ifdef __GNUC__ + << " and GCC version " << __VERSION__ +#endif + << ')' + << endl; +} + +static void print_help () { cout << "Usage: hardour [OPTIONS]... DIR SNAPSHOT_NAME\n\n" @@ -79,129 +125,146 @@ print_help () #ifdef WINDOWS_VST_SUPPORT << " -V, --novst Do not use VST support\n" #endif - ; + ; } -int main (int argc, char* argv[]) +int +main (int argc, char* argv[]) { - const char *optstring = "vhBdD:c:VOU:P"; + const char* optstring = "vhBdD:c:VOU:P"; + /* clang-format off */ const struct option longopts[] = { - { "version", 0, 0, 'v' }, - { "help", 0, 0, 'h' }, - { "bypass-plugins", 1, 0, 'B' }, - { "disable-plugins", 1, 0, 'd' }, - { "debug", 1, 0, 'D' }, - { "name", 1, 0, 'c' }, - { "novst", 0, 0, 'V' }, - { "no-hw-optimizations", 0, 0, 'O' }, - { "uuid", 1, 0, 'U' }, - { "no-connect-ports", 0, 0, 'P' }, + { "version", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { "bypass-plugins", no_argument, 0, 'B' }, + { "disable-plugins", no_argument, 0, 'd' }, + { "debug", required_argument, 0, 'D' }, + { "name", required_argument, 0, 'c' }, + { "novst", no_argument, 0, 'V' }, + { "no-hw-optimizations", no_argument, 0, 'O' }, + { "no-connect-ports", no_argument, 0, 'P' }, { 0, 0, 0, 0 } }; + /* clang-format on */ - int option_index = 0; - int c = 0; + bool use_vst = true; + bool try_hw_optimization = true; - while (1) { - c = getopt_long (argc, argv, optstring, longopts, &option_index); - - if (c == -1) { - break; - } + backend_client_name = PBD::downcase (std::string (PROGRAM_NAME)); + int c; + while ((c = getopt_long (argc, argv, optstring, longopts, (int*)0)) != EOF) { switch (c) { - case 0: - break; + case 0: + break; - case 'v': - just_version = true; - break; + case 'v': + print_version (); + exit (EXIT_SUCCESS); + break; - case 'h': - print_help (); - exit (0); - break; + case 'h': + print_help (); + exit (EXIT_SUCCESS); + break; - case 'c': - backend_client_name = optarg; - break; + case 'c': + backend_client_name = optarg; + break; - case 'B': - ARDOUR::Session::set_bypass_all_loaded_plugins (true); - break; + case 'B': + ARDOUR::Session::set_bypass_all_loaded_plugins (true); + break; - case 'd': - ARDOUR::Session::set_disable_all_loaded_plugins (true); - break; + case 'd': + ARDOUR::Session::set_disable_all_loaded_plugins (true); + break; - case 'D': - if (PBD::parse_debug_options (optarg)) { - ::exit (1); - } - break; + case 'D': + if (PBD::parse_debug_options (optarg)) { + exit (EXIT_SUCCESS); + } + break; - case 'O': - try_hw_optimization = false; - break; + case 'O': + try_hw_optimization = false; + break; - case 'P': - no_connect_ports = true; - break; + case 'P': + ARDOUR::Port::set_connecting_blocked (true); + break; - case 'V': + case 'V': #ifdef WINDOWS_VST_SUPPORT - use_vst = false; + use_vst = false; #endif /* WINDOWS_VST_SUPPORT */ - break; + break; - case 'U': - backend_session_uuid = optarg; - break; - - default: - print_help (); - ::exit (1); + default: + print_help (); + exit (EXIT_FAILURE); } } if (argc < 3) { print_help (); - ::exit (1); + exit (EXIT_FAILURE); } - if (!ARDOUR::init (false, true, localedir)) { - cerr << "Ardour failed to initialize\n" << endl; - ::exit (1); + if (!ARDOUR::init (use_vst, try_hw_optimization, localedir)) { + cerr << "Ardour failed to initialize\n" + << endl; + exit (EXIT_FAILURE); } Session* s = 0; try { - s = load_session (argv[optind], argv[optind+1]); + s = load_session (argv[optind], argv[optind + 1]); } catch (failed_constructor& e) { - cerr << "failed_constructor: " << e.what() << "\n"; + cerr << "failed_constructor: " << e.what () << "\n"; exit (EXIT_FAILURE); } catch (AudioEngine::PortRegistrationFailure& e) { - cerr << "PortRegistrationFailure: " << e.what() << "\n"; + cerr << "PortRegistrationFailure: " << e.what () << "\n"; exit (EXIT_FAILURE); } catch (exception& e) { - cerr << "exception: " << e.what() << "\n"; + cerr << "exception: " << e.what () << "\n"; exit (EXIT_FAILURE); } catch (...) { cerr << "unknown exception.\n"; exit (EXIT_FAILURE); } + /* allow signal propagation, callback/thread-pool setup, etc + * similar to to GUI "first idle" + */ + Glib::usleep (1000000); // 1 sec + + if (!s) { + cerr << "failed_to load session\n"; + exit (EXIT_FAILURE); + } + + PBD::ScopedConnectionList con; + BasicUI::AccessAction.connect_same_thread (con, boost::bind (&access_action, _1, _2)); + AudioEngine::instance ()->Halted.connect_same_thread (con, boost::bind (&engine_halted, _1)); + +#ifndef PLATFORM_WINDOWS + signal (SIGINT, wearedone); + signal (SIGTERM, wearedone); +#endif + s->request_transport_speed (1.0); - sleep (-1); + char msg; + do { + } while (0 == xthread.receive (msg, true)); - AudioEngine::instance()->remove_session (); + AudioEngine::instance ()->remove_session (); delete s; - AudioEngine::instance()->stop (); + AudioEngine::instance ()->stop (); AudioEngine::destroy (); - return 0; } diff --git a/headless/wscript b/headless/wscript index 80296a10de..0aea4751c2 100644 --- a/headless/wscript +++ b/headless/wscript @@ -74,26 +74,3 @@ def build(bld): if bld.is_defined('NEED_INTL'): obj.linkflags = ' -lintl' - - # Wrappers - - wrapper_subst_dict = { - 'INSTALL_PREFIX' : bld.env['PREFIX'], - 'LIBDIR' : os.path.normpath(bld.env['LIBDIR']), - 'DATADIR' : os.path.normpath(bld.env['DATADIR']), - 'CONFDIR' : os.path.normpath(bld.env['CONFDIR']), - 'LIBS' : 'build/libs', - 'VERSION' : bld.env['VERSION'], - 'EXECUTABLE' : 'build/headless/hardour-' + str(bld.env['VERSION']) - } - - def set_subst_dict(obj, dict): - for i in dict: - setattr(obj, i, dict[i]) - - obj = bld(features = 'subst') - obj.source = 'hardev_common.sh.in' - obj.target = 'hardev_common_waf.sh' - obj.chmod = Utils.O755 - obj.dict = wrapper_subst_dict - set_subst_dict(obj, wrapper_subst_dict) diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index 4f8d7a94ba..79aab2ef62 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -68,7 +68,6 @@ class LIBARDOUR_API AudioEngine : public PortManager, public SessionHandlePtr int discover_backends(); std::vector available_backends() const; std::string current_backend_name () const; - boost::shared_ptr set_default_backend (); boost::shared_ptr set_backend (const std::string&, const std::string& arg1, const std::string& arg2); boost::shared_ptr current_backend() const { return _backend; } bool setup_required () const; diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h index 27238e5897..25153a19f1 100644 --- a/libs/ardour/ardour/graph.h +++ b/libs/ardour/ardour/graph.h @@ -1,46 +1,43 @@ /* - Copyright (C) 2010 Paul Davis - Copyright (C) 2017 Robin Gareus - Author: Torben Hohn - - 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. -*/ - + * Copyright (C) 2010 Paul Davis + * Copyright (C) 2017-2019 Robin Gareus + * incl. some work from Torben Hohn + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ #ifndef __ardour_graph_h__ #define __ardour_graph_h__ #include #include -#include #include +#include #include -#include - +#include "pbd/mpmc_queue.h" #include "pbd/semutils.h" -#include "ardour/libardour_visibility.h" -#include "ardour/types.h" #include "ardour/audio_backend.h" +#include "ardour/libardour_visibility.h" #include "ardour/session_handle.h" +#include "ardour/types.h" namespace ARDOUR { - class GraphNode; class Graph; @@ -50,27 +47,27 @@ class GraphEdges; typedef boost::shared_ptr node_ptr_t; -typedef std::list< node_ptr_t > node_list_t; -typedef std::set< node_ptr_t > node_set_t; +typedef std::list node_list_t; +typedef std::set node_set_t; class LIBARDOUR_API Graph : public SessionHandleRef { public: - Graph (Session & session); + Graph (Session& session); - void trigger (GraphNode * n); - void rechain (boost::shared_ptr, GraphEdges const &); + void trigger (GraphNode* n); + void rechain (boost::shared_ptr, GraphEdges const&); void dump (int chain); - void dec_ref(); + void reached_terminal_node (); - void helper_thread(); + void helper_thread (); int process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool& need_butler); - int routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending ); + int routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending); - void process_one_route (Route * route); + void process_one_route (Route* route); void clear_other_chain (); @@ -80,36 +77,40 @@ protected: virtual void session_going_away (); private: - volatile bool _threads_active; - void reset_thread_list (); void drop_threads (); - void restart_cycle(); - bool run_one(); - void main_thread(); - void prep(); + void run_one (); + void main_thread (); + void prep (); node_list_t _nodes_rt[2]; - node_list_t _init_trigger_list[2]; - std::vector _trigger_queue; - pthread_mutex_t _trigger_mutex; + PBD::MPMCQueue _trigger_queue; ///< nodes that can be processed + volatile guint _trigger_queue_size; ///< number of entries in trigger-queue + /** Start worker threads */ PBD::Semaphore _execution_sem; + /** The number of processing threads that are asleep */ + volatile guint _idle_thread_cnt; + /** Signalled to start a run of the graph for a process callback */ PBD::Semaphore _callback_start_sem; PBD::Semaphore _callback_done_sem; - /** The number of processing threads that are asleep */ - volatile gint _execution_tokens; /** The number of unprocessed nodes that do not feed any other node; updated during processing */ - volatile gint _finished_refcount; - /** The initial number of nodes that do not feed any other node (for each chain) */ - volatile gint _init_finished_refcount[2]; + volatile guint _terminal_refcnt; - bool _graph_empty; + /** The initial number of nodes that do not feed any other node (for each chain) */ + guint _n_terminal_nodes[2]; + bool _graph_empty; + + /* number of background worker threads >= 0 */ + volatile guint _n_workers; + + /* flag to terminate background threads */ + volatile gint _terminate; /* chain swapping */ Glib::Threads::Mutex _swap_mutex; @@ -132,7 +133,7 @@ private: /* engine / thread connection */ PBD::ScopedConnectionList engine_connections; - void engine_stopped (); + void engine_stopped (); }; } // namespace diff --git a/libs/ardour/ardour/graphnode.h b/libs/ardour/ardour/graphnode.h index aa04d9b20a..f30844532b 100644 --- a/libs/ardour/ardour/graphnode.h +++ b/libs/ardour/ardour/graphnode.h @@ -17,7 +17,6 @@ */ - #ifndef __ardour_graphnode_h__ #define __ardour_graphnode_h__ @@ -29,40 +28,48 @@ namespace ARDOUR { - class Graph; class GraphNode; typedef boost::shared_ptr node_ptr_t; -typedef std::set< node_ptr_t > node_set_t; -typedef std::list< node_ptr_t > node_list_t; +typedef std::set node_set_t; +typedef std::list node_list_t; -/** A node on our processing graph, ie a Route */ -class LIBARDOUR_API GraphNode +class LIBARDOUR_API GraphActivision { - public: - GraphNode( boost::shared_ptr Graph ); - virtual ~GraphNode(); - - void prep( int chain ); - void dec_ref(); - void finish( int chain ); - - virtual void process(); - - private: +protected: friend class Graph; - /** Nodes that we directly feed */ - node_set_t _activation_set[2]; - - boost::shared_ptr _graph; - - gint _refcount; + node_set_t _activation_set[2]; /** The number of nodes that we directly feed us (one count for each chain) */ gint _init_refcount[2]; }; +/** A node on our processing graph, ie a Route */ +class LIBARDOUR_API GraphNode : public GraphActivision +{ +public: + GraphNode (boost::shared_ptr Graph); + virtual ~GraphNode (); + + void prep (int chain); + void trigger (); + + void + run (int chain) + { + process (); + finish (chain); + } + +private: + void finish (int chain); + void process (); + + boost::shared_ptr _graph; + + gint _refcount; +}; } #endif diff --git a/libs/ardour/ardour/io.h b/libs/ardour/ardour/io.h index 3f4fdc5797..d9fc412e27 100644 --- a/libs/ardour/ardour/io.h +++ b/libs/ardour/ardour/io.h @@ -104,6 +104,8 @@ public: boost::shared_ptr bundle () { return _bundle; } + bool can_add_port (DataType) const; + int add_port (std::string connection, void *src, DataType type = DataType::NIL); int remove_port (boost::shared_ptr, void *src); int connect (boost::shared_ptr our_port, std::string other_port, void *src); diff --git a/libs/ardour/ardour/luascripting.h b/libs/ardour/ardour/luascripting.h index 7570ff1bc8..2d03495d32 100644 --- a/libs/ardour/ardour/luascripting.h +++ b/libs/ardour/ardour/luascripting.h @@ -123,6 +123,10 @@ public: static std::string get_factory_bytecode (const std::string&, const std::string& ffn = "factory", const std::string& fp = "f"); static std::string user_script_dir (); + struct LIBARDOUR_API Sorter { + bool operator() (LuaScriptInfoPtr const a, LuaScriptInfoPtr const b) const; + }; + private: static LuaScripting* _instance; // singleton LuaScripting (); diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 6b0c70b44c..f49c46a6b9 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -853,16 +853,6 @@ AudioEngine::drop_backend () } } -boost::shared_ptr -AudioEngine::set_default_backend () -{ - if (_backends.empty()) { - return boost::shared_ptr(); - } - - return set_backend (_backends.begin()->first, "", ""); -} - boost::shared_ptr AudioEngine::set_backend (const std::string& name, const std::string& arg1, const std::string& arg2) { diff --git a/libs/ardour/filesystem_paths.cc b/libs/ardour/filesystem_paths.cc index ee5fc97ad7..5ecef5863e 100644 --- a/libs/ardour/filesystem_paths.cc +++ b/libs/ardour/filesystem_paths.cc @@ -98,7 +98,7 @@ user_config_directory (int version) #endif if (home_dir.empty ()) { error << "Unable to determine home directory" << endmsg; - exit (1); + exit (EXIT_FAILURE); } p = home_dir; @@ -119,7 +119,7 @@ user_config_directory (int version) if (g_mkdir_with_parents (p.c_str(), 0755)) { error << string_compose (_("Cannot create Configuration directory %1 - cannot run"), p) << endmsg; - exit (1); + exit (EXIT_FAILURE); } } else if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) { fatal << string_compose (_("Configuration directory %1 already exists and is not a directory/folder - cannot run"), @@ -156,7 +156,7 @@ user_cache_directory (std::string cachename) #endif if (home_dir.empty ()) { error << "Unable to determine home directory" << endmsg; - exit (1); + exit (EXIT_FAILURE); } p = home_dir; @@ -187,7 +187,7 @@ user_cache_directory (std::string cachename) if (g_mkdir_with_parents (p.c_str(), 0755)) { error << string_compose (_("Cannot create cache directory %1 - cannot run"), p) << endmsg; - exit (1); + exit (EXIT_FAILURE); } } else if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) { fatal << string_compose (_("Cache directory %1 already exists and is not a directory/folder - cannot run"), @@ -209,7 +209,7 @@ ardour_dll_directory () std::string s = Glib::getenv("ARDOUR_DLL_PATH"); if (s.empty()) { std::cerr << _("ARDOUR_DLL_PATH not set in environment - exiting\n"); - ::exit (1); + ::exit (EXIT_FAILURE); } return s; #endif diff --git a/libs/ardour/graph.cc b/libs/ardour/graph.cc index 0389b2ef1a..1d6218917e 100644 --- a/libs/ardour/graph.cc +++ b/libs/ardour/graph.cc @@ -1,36 +1,37 @@ /* - Copyright (C) 2010 Paul Davis - Author: Torben Hohn + * Copyright (C) 2010 Paul Davis + * Copyright (C) 2017-2019 Robin Gareus + * incl. some work from Torben Hohn + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ -#include #include +#include #include "pbd/compose.h" #include "pbd/debug_rt_alloc.h" #include "pbd/pthread_utils.h" +#include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/graph.h" -#include "ardour/types.h" -#include "ardour/session.h" -#include "ardour/route.h" #include "ardour/process_thread.h" -#include "ardour/audioengine.h" +#include "ardour/route.h" +#include "ardour/session.h" +#include "ardour/types.h" #include "pbd/i18n.h" @@ -43,44 +44,43 @@ static Graph* graph = 0; extern "C" { -int alloc_allowed () +int +alloc_allowed () { return !graph->in_process_thread (); } - } #endif -Graph::Graph (Session & session) +#define g_atomic_uint_get(x) static_cast (g_atomic_int_get (x)) + +Graph::Graph (Session& session) : SessionHandleRef (session) - , _threads_active (false) , _execution_sem ("graph_execution", 0) , _callback_start_sem ("graph_start", 0) , _callback_done_sem ("graph_done", 0) + , _graph_empty (true) + , _current_chain (0) + , _pending_chain (0) + , _setup_chain (1) { - pthread_mutex_init( &_trigger_mutex, NULL); + g_atomic_int_set (&_terminal_refcnt, 0); + g_atomic_int_set (&_terminate, 0); + g_atomic_int_set (&_n_workers, 0); + g_atomic_int_set (&_idle_thread_cnt, 0); + g_atomic_int_set (&_trigger_queue_size, 0); - /* XXX: rather hacky `fix' to stop _trigger_queue.push_back() allocating - * memory in the RT thread. - */ - _trigger_queue.reserve (8192); + /* pre-allocate memory */ + _trigger_queue.reserve (1024); - _execution_tokens = 0; - - _current_chain = 0; - _pending_chain = 0; - _setup_chain = 1; - _graph_empty = true; - - - ARDOUR::AudioEngine::instance()->Running.connect_same_thread (engine_connections, boost::bind (&Graph::reset_thread_list, this)); - ARDOUR::AudioEngine::instance()->Stopped.connect_same_thread (engine_connections, boost::bind (&Graph::engine_stopped, this)); - ARDOUR::AudioEngine::instance()->Halted.connect_same_thread (engine_connections, boost::bind (&Graph::engine_stopped, this)); + ARDOUR::AudioEngine::instance ()->Running.connect_same_thread (engine_connections, boost::bind (&Graph::reset_thread_list, this)); + ARDOUR::AudioEngine::instance ()->Stopped.connect_same_thread (engine_connections, boost::bind (&Graph::engine_stopped, this)); + ARDOUR::AudioEngine::instance ()->Halted.connect_same_thread (engine_connections, boost::bind (&Graph::engine_stopped, this)); reset_thread_list (); #ifdef DEBUG_RT_ALLOC - graph = this; + graph = this; pbd_alloc_allowed = &::alloc_allowed; #endif } @@ -89,9 +89,9 @@ void Graph::engine_stopped () { #ifndef NDEBUG - cerr << "Graph::engine_stopped. n_thread: " << AudioEngine::instance()->process_thread_count() << endl; + cerr << "Graph::engine_stopped. n_thread: " << AudioEngine::instance ()->process_thread_count () << endl; #endif - if (AudioEngine::instance()->process_thread_count() != 0) { + if (AudioEngine::instance ()->process_thread_count () != 0) { drop_threads (); } } @@ -101,73 +101,84 @@ void Graph::reset_thread_list () { uint32_t num_threads = how_many_dsp_threads (); + guint n_workers = g_atomic_uint_get (&_n_workers); /* For now, we shouldn't be using the graph code if we only have 1 DSP thread */ assert (num_threads > 1); + assert (AudioEngine::instance ()->process_thread_count () == n_workers); /* don't bother doing anything here if we already have the right * number of threads. */ - if (AudioEngine::instance()->process_thread_count() == num_threads) { + if (AudioEngine::instance ()->process_thread_count () == num_threads) { return; } - Glib::Threads::Mutex::Lock lm (_session.engine().process_lock()); + Glib::Threads::Mutex::Lock lm (_session.engine ().process_lock ()); - if (AudioEngine::instance()->process_thread_count() != 0) { + if (n_workers > 0) { drop_threads (); } - _threads_active = true; + /* Allow threads to run */ + g_atomic_int_set (&_terminate, 0); - if (AudioEngine::instance()->create_process_thread (boost::bind (&Graph::main_thread, this)) != 0) { + if (AudioEngine::instance ()->create_process_thread (boost::bind (&Graph::main_thread, this)) != 0) { throw failed_constructor (); } for (uint32_t i = 1; i < num_threads; ++i) { - if (AudioEngine::instance()->create_process_thread (boost::bind (&Graph::helper_thread, this))) { + if (AudioEngine::instance ()->create_process_thread (boost::bind (&Graph::helper_thread, this))) { throw failed_constructor (); } } + + while (g_atomic_uint_get (&_n_workers) + 1 != num_threads) { + sched_yield (); + } } void -Graph::session_going_away() +Graph::session_going_away () { drop_threads (); // now drop all references on the nodes. - _nodes_rt[0].clear(); - _nodes_rt[1].clear(); - _init_trigger_list[0].clear(); - _init_trigger_list[1].clear(); - _trigger_queue.clear(); + _nodes_rt[0].clear (); + _nodes_rt[1].clear (); + _init_trigger_list[0].clear (); + _init_trigger_list[1].clear (); + g_atomic_int_set (&_trigger_queue_size, 0); + _trigger_queue.clear (); } void Graph::drop_threads () { Glib::Threads::Mutex::Lock ls (_swap_mutex); - _threads_active = false; - uint32_t thread_count = AudioEngine::instance()->process_thread_count (); + /* Flag threads to terminate */ + g_atomic_int_set (&_terminate, 1); - for (unsigned int i=0; i < thread_count; i++) { - pthread_mutex_lock (&_trigger_mutex); + /* Wake-up sleeping threads */ + guint tc = g_atomic_uint_get (&_idle_thread_cnt); + assert (tc == g_atomic_uint_get (&_n_workers)); + for (guint i = 0; i < tc; ++i) { _execution_sem.signal (); - pthread_mutex_unlock (&_trigger_mutex); } - pthread_mutex_lock (&_trigger_mutex); + /* and the main thread */ _callback_start_sem.signal (); - pthread_mutex_unlock (&_trigger_mutex); - AudioEngine::instance()->join_process_threads (); + /* join process threads */ + AudioEngine::instance ()->join_process_threads (); + + g_atomic_int_set (&_n_workers, 0); + g_atomic_int_set (&_idle_thread_cnt, 0); /* signal main process thread if it's waiting for an already terminated thread */ _callback_done_sem.signal (); - _execution_tokens = 0; /* reset semaphores. * This is somewhat ugly, yet if a thread is killed (e.g jackd terminates @@ -177,7 +188,7 @@ Graph::drop_threads () int d1 = _execution_sem.reset (); int d2 = _callback_start_sem.reset (); int d3 = _callback_done_sem.reset (); - cerr << "Graph::drop_threads() sema-counts: " << d1 << ", " << d2<< ", " << d3 << endl; + cerr << "Graph::drop_threads() sema-counts: " << d1 << ", " << d2 << ", " << d3 << endl; #else _execution_sem.reset (); _callback_start_sem.reset (); @@ -185,6 +196,7 @@ Graph::drop_threads () #endif } +/* special case route removal -- called from Session::remove_routes */ void Graph::clear_other_chain () { @@ -192,9 +204,8 @@ Graph::clear_other_chain () while (1) { if (_setup_chain != _pending_chain) { - - for (node_list_t::iterator ni=_nodes_rt[_setup_chain].begin(); ni!=_nodes_rt[_setup_chain].end(); ni++) { - (*ni)->_activation_set[_setup_chain].clear(); + for (node_list_t::iterator ni = _nodes_rt[_setup_chain].begin (); ni != _nodes_rt[_setup_chain].end (); ++ni) { + (*ni)->_activation_set[_setup_chain].clear (); } _nodes_rt[_setup_chain].clear (); @@ -209,98 +220,107 @@ Graph::clear_other_chain () } void -Graph::prep() +Graph::prep () { - node_list_t::iterator i; - int chain; - - if (_swap_mutex.trylock()) { - // we got the swap mutex. - if (_current_chain != _pending_chain) - { - // printf ("chain swap ! %d -> %d\n", _current_chain, _pending_chain); - _setup_chain = _current_chain; + if (_swap_mutex.trylock ()) { + /* swap mutex acquired */ + if (_current_chain != _pending_chain) { + /* use new chain */ + _setup_chain = _current_chain; _current_chain = _pending_chain; + /* ensure that all nodes can be queued */ + _trigger_queue.reserve (_nodes_rt[_current_chain].size ()); + assert (g_atomic_uint_get (&_trigger_queue_size) == 0); _cleanup_cond.signal (); } _swap_mutex.unlock (); } - chain = _current_chain; - _graph_empty = true; - for (i=_nodes_rt[chain].begin(); i!=_nodes_rt[chain].end(); i++) { - (*i)->prep( chain); + + int chain = _current_chain; + + node_list_t::iterator i; + for (i = _nodes_rt[chain].begin (); i != _nodes_rt[chain].end (); ++i) { + (*i)->prep (chain); _graph_empty = false; } - _finished_refcount = _init_finished_refcount[chain]; + + assert (_graph_empty != (_n_terminal_nodes[chain] > 0)); + + g_atomic_int_set (&_terminal_refcnt, _n_terminal_nodes[chain]); /* Trigger the initial nodes for processing, which are the ones at the `input' end */ - pthread_mutex_lock (&_trigger_mutex); - for (i=_init_trigger_list[chain].begin(); i!=_init_trigger_list[chain].end(); i++) { - /* don't use ::trigger here, as we have already locked the mutex */ + for (i = _init_trigger_list[chain].begin (); i != _init_trigger_list[chain].end (); i++) { + g_atomic_int_inc (&_trigger_queue_size); _trigger_queue.push_back (i->get ()); } - pthread_mutex_unlock (&_trigger_mutex); } void Graph::trigger (GraphNode* n) { - pthread_mutex_lock (&_trigger_mutex); + g_atomic_int_inc (&_trigger_queue_size); _trigger_queue.push_back (n); - pthread_mutex_unlock (&_trigger_mutex); } /** Called when a node at the `output' end of the chain (ie one that has no-one to feed) * is finished. */ void -Graph::dec_ref() +Graph::reached_terminal_node () { - if (g_atomic_int_dec_and_test (const_cast (&_finished_refcount))) { + if (g_atomic_int_dec_and_test (&_terminal_refcnt)) { + again: /* We have run all the nodes that are at the `output' end of * the graph, so there is nothing more to do this time around. */ + assert (g_atomic_uint_get (&_trigger_queue_size) == 0); - restart_cycle (); + /* Notify caller */ + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 cycle done.\n", pthread_name ())); + + _callback_done_sem.signal (); + + /* Ensure that all background threads are idle. + * When freewheeling there may be an immediate restart: + * If there are more threads than CPU cores, some worker- + * threads may only be "on the way" to become idle. + */ + guint n_workers = g_atomic_uint_get (&_n_workers); + while (g_atomic_uint_get (&_idle_thread_cnt) != n_workers) { + sched_yield (); + } + + /* Block until the a process callback */ + _callback_start_sem.wait (); + + if (g_atomic_int_get (&_terminate)) { + return; + } + + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 prepare new cycle.\n", pthread_name ())); + + /* Prepare next cycle: + * - Reset terminal reference count + * - queue initial nodes + */ + prep (); + + if (_graph_empty && !g_atomic_int_get (&_terminate)) { + goto again; + } + /* .. continue in worker-thread */ } } -void -Graph::restart_cycle() -{ - // we are through. wakeup our caller. - DEBUG_TRACE(DEBUG::ProcessThreads, string_compose ("%1 cycle done.\n", pthread_name())); - -again: - _callback_done_sem.signal (); - - /* Block until the a process callback triggers us */ - _callback_start_sem.wait(); - - if (!_threads_active) { - return; - } - - DEBUG_TRACE(DEBUG::ProcessThreads, string_compose ("%1 prepare new cycle.\n", pthread_name())); - prep (); - - if (_graph_empty && _threads_active) { - goto again; - } - - // returning will restart the cycle. - // starting with waking up the others. -} - /** Rechain our stuff using a list of routes (which can be in any order) and * a directed graph of their interconnections, which is guaranteed to be * acyclic. */ void -Graph::rechain (boost::shared_ptr routelist, GraphEdges const & edges) +Graph::rechain (boost::shared_ptr routelist, GraphEdges const& edges) { Glib::Threads::Mutex::Lock ls (_swap_mutex); @@ -310,26 +330,25 @@ Graph::rechain (boost::shared_ptr routelist, GraphEdges const & edges /* This will become the number of nodes that do not feed any other node; * once we have processed this number of those nodes, we have finished. */ - _init_finished_refcount[chain] = 0; + _n_terminal_nodes[chain] = 0; /* This will become a list of nodes that are not fed by another node, ie * those at the `input' end. */ - _init_trigger_list[chain].clear(); + _init_trigger_list[chain].clear (); - _nodes_rt[chain].clear(); + _nodes_rt[chain].clear (); /* Clear things out, and make _nodes_rt[chain] a copy of routelist */ - for (RouteList::iterator ri=routelist->begin(); ri!=routelist->end(); ri++) { + for (RouteList::iterator ri = routelist->begin (); ri != routelist->end (); ri++) { (*ri)->_init_refcount[chain] = 0; - (*ri)->_activation_set[chain].clear(); + (*ri)->_activation_set[chain].clear (); _nodes_rt[chain].push_back (*ri); } // now add refs for the connections. - for (node_list_t::iterator ni = _nodes_rt[chain].begin(); ni != _nodes_rt[chain].end(); ni++) { - + for (node_list_t::iterator ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) { boost::shared_ptr r = boost::dynamic_pointer_cast (*ni); /* The routes that are directly fed by r */ @@ -339,7 +358,7 @@ Graph::rechain (boost::shared_ptr routelist, GraphEdges const & edges bool const has_output = !fed_from_r.empty (); /* Set up r's activation set */ - for (set::iterator i = fed_from_r.begin(); i != fed_from_r.end(); ++i) { + for (set::iterator i = fed_from_r.begin (); i != fed_from_r.end (); ++i) { r->_activation_set[chain].insert (*i); } @@ -347,7 +366,7 @@ Graph::rechain (boost::shared_ptr routelist, GraphEdges const & edges bool const has_input = !edges.has_none_to (r); /* Increment the refcount of any route that we directly feed */ - for (node_set_t::iterator ai = r->_activation_set[chain].begin(); ai != r->_activation_set[chain].end(); ai++) { + for (node_set_t::iterator ai = r->_activation_set[chain].begin (); ai != r->_activation_set[chain].end (); ai++) { (*ai)->_init_refcount[chain] += 1; } @@ -360,128 +379,147 @@ Graph::rechain (boost::shared_ptr routelist, GraphEdges const & edges /* no output, so this is one of the nodes that we can count off to decide * if we've finished */ - _init_finished_refcount[chain] += 1; + _n_terminal_nodes[chain] += 1; } } _pending_chain = chain; - dump(chain); + dump (chain); } -/** Called by both the main thread and all helpers. - * @return true to quit, false to carry on. - */ -bool -Graph::run_one() +/** Called by both the main thread and all helpers. */ +void +Graph::run_one () { - GraphNode* to_run; + GraphNode* to_run = NULL; - pthread_mutex_lock (&_trigger_mutex); - if (_trigger_queue.size()) { - to_run = _trigger_queue.back(); - _trigger_queue.pop_back(); - } else { - to_run = 0; + if (g_atomic_int_get (&_terminate)) { + return; } - /* the number of threads that are asleep */ - int et = _execution_tokens; - /* the number of nodes that need to be run */ - int ts = _trigger_queue.size(); + if (_trigger_queue.pop_front (to_run)) { + /* Wake up idle threads, but at most as many as there's + * work in the trigger queue that can be processed by + * other threads. + * This thread as not yet decreased _trigger_queue_size. + */ + guint idle_cnt = g_atomic_uint_get (&_idle_thread_cnt); + guint work_avail = g_atomic_uint_get (&_trigger_queue_size); + guint wakeup = std::min (idle_cnt + 1, work_avail); - /* hence how many threads to wake up */ - int wakeup = min (et, ts); - /* update the number of threads that will still be sleeping */ - _execution_tokens -= wakeup; - - DEBUG_TRACE(DEBUG::ProcessThreads, string_compose ("%1 signals %2\n", pthread_name(), wakeup)); - - for (int i = 0; i < wakeup; i++) { - _execution_sem.signal (); + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 signals %2 threads\n", pthread_name (), wakeup)); + for (guint i = 1; i < wakeup; ++i) { + _execution_sem.signal (); + } } - while (to_run == 0) { - _execution_tokens += 1; - pthread_mutex_unlock (&_trigger_mutex); - DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 goes to sleep\n", pthread_name())); + while (!to_run) { + /* Wait for work, fall asleep */ + g_atomic_int_inc (&_idle_thread_cnt); + assert (g_atomic_uint_get (&_idle_thread_cnt) <= _n_workers); + + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 goes to sleep\n", pthread_name ())); _execution_sem.wait (); - if (!_threads_active) { - return true; - } - DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 is awake\n", pthread_name())); - pthread_mutex_lock (&_trigger_mutex); - if (_trigger_queue.size()) { - to_run = _trigger_queue.back(); - _trigger_queue.pop_back(); + + if (g_atomic_int_get (&_terminate)) { + return; } + + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 is awake\n", pthread_name ())); + + g_atomic_int_dec_and_test (&_idle_thread_cnt); + + /* Try to find some work to do */ + _trigger_queue.pop_front (to_run); } - pthread_mutex_unlock (&_trigger_mutex); - to_run->process(); - to_run->finish (_current_chain); + /* Process the graph-node */ + g_atomic_int_dec_and_test (&_trigger_queue_size); + to_run->run (_current_chain); - DEBUG_TRACE(DEBUG::ProcessThreads, string_compose ("%1 has finished run_one()\n", pthread_name())); - - return !_threads_active; + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 has finished run_one()\n", pthread_name ())); } void -Graph::helper_thread() +Graph::helper_thread () { + g_atomic_int_inc (&_n_workers); + guint id = g_atomic_uint_get (&_n_workers); + + /* This is needed for ARDOUR::Session requests called from rt-processors + * in particular Lua scripts may do cross-thread calls */ + if (!SessionEvent::has_per_thread_pool ()) { + char name[64]; + snprintf (name, 64, "RT-%u-%p", id, (void*)DEBUG_THREAD_SELF); + pthread_set_name (name); + SessionEvent::create_per_thread_pool (name, 64); + PBD::notify_event_loops_about_thread_creation (pthread_self (), name, 64); + } + suspend_rt_malloc_checks (); ProcessThread* pt = new ProcessThread (); resume_rt_malloc_checks (); - pt->get_buffers(); + pt->get_buffers (); - while(1) { - if (run_one()) { - break; - } + while (!g_atomic_int_get (&_terminate)) { + run_one (); } - pt->drop_buffers(); + pt->drop_buffers (); delete pt; } /** Here's the main graph thread */ void -Graph::main_thread() +Graph::main_thread () { + /* first time setup */ + suspend_rt_malloc_checks (); ProcessThread* pt = new ProcessThread (); + + /* This is needed for ARDOUR::Session requests called from rt-processors + * in particular Lua scripts may do cross-thread calls */ + if (!SessionEvent::has_per_thread_pool ()) { + char name[64]; + snprintf (name, 64, "RT-main-%p", (void*)DEBUG_THREAD_SELF); + pthread_set_name (name); + SessionEvent::create_per_thread_pool (name, 64); + PBD::notify_event_loops_about_thread_creation (pthread_self (), name, 64); + } resume_rt_malloc_checks (); - pt->get_buffers(); + pt->get_buffers (); + /* Wait for initial process callback */ again: _callback_start_sem.wait (); - DEBUG_TRACE(DEBUG::ProcessThreads, "main thread is awake\n"); + DEBUG_TRACE (DEBUG::ProcessThreads, "main thread is awake\n"); - if (!_threads_active) { - pt->drop_buffers(); + if (g_atomic_int_get (&_terminate)) { + pt->drop_buffers (); delete (pt); return; } + /* Bootstrap the trigger-list + * (later this is done by Graph_reached_terminal_node) */ prep (); - if (_graph_empty && _threads_active) { + if (_graph_empty && !g_atomic_int_get (&_terminate)) { _callback_done_sem.signal (); - DEBUG_TRACE(DEBUG::ProcessThreads, "main thread sees graph done, goes back to sleep\n"); + DEBUG_TRACE (DEBUG::ProcessThreads, "main thread sees graph done, goes back to sleep\n"); goto again; } - /* This loop will run forever */ - while (1) { - DEBUG_TRACE(DEBUG::ProcessThreads, string_compose ("main thread (%1) runs one graph node\n", pthread_name ())); - if (run_one()) { - break; - } + /* After setup, the main-thread just becomes a normal worker */ + while (!g_atomic_int_get (&_terminate)) { + run_one (); } - pt->drop_buffers(); + pt->drop_buffers (); delete (pt); } @@ -490,25 +528,25 @@ Graph::dump (int chain) { #ifndef NDEBUG node_list_t::iterator ni; - node_set_t::iterator ai; + node_set_t::iterator ai; chain = _pending_chain; DEBUG_TRACE (DEBUG::Graph, "--------------------------------------------Graph dump:\n"); - for (ni=_nodes_rt[chain].begin(); ni!=_nodes_rt[chain].end(); ni++) { - boost::shared_ptr rp = boost::dynamic_pointer_cast( *ni); - DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", rp->name().c_str(), (*ni)->_init_refcount[chain])); - for (ai=(*ni)->_activation_set[chain].begin(); ai!=(*ni)->_activation_set[chain].end(); ai++) { - DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", boost::dynamic_pointer_cast(*ai)->name().c_str())); + for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) { + boost::shared_ptr rp = boost::dynamic_pointer_cast (*ni); + DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", rp->name ().c_str (), (*ni)->_init_refcount[chain])); + for (ai = (*ni)->_activation_set[chain].begin (); ai != (*ni)->_activation_set[chain].end (); ai++) { + DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", boost::dynamic_pointer_cast (*ai)->name ().c_str ())); } } DEBUG_TRACE (DEBUG::Graph, "------------- trigger list:\n"); - for (ni=_init_trigger_list[chain].begin(); ni!=_init_trigger_list[chain].end(); ni++) { - DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", boost::dynamic_pointer_cast(*ni)->name().c_str(), (*ni)->_init_refcount[chain])); + for (ni = _init_trigger_list[chain].begin (); ni != _init_trigger_list[chain].end (); ni++) { + DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", boost::dynamic_pointer_cast (*ni)->name ().c_str (), (*ni)->_init_refcount[chain])); } - DEBUG_TRACE (DEBUG::Graph, string_compose ("final activation refcount: %1\n", _init_finished_refcount[chain])); + DEBUG_TRACE (DEBUG::Graph, string_compose ("final activation refcount: %1\n", _n_terminal_nodes[chain])); #endif } @@ -517,17 +555,19 @@ Graph::process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t { DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("graph execution from %1 to %2 = %3\n", start_sample, end_sample, nframes)); - if (!_threads_active) return 0; + if (g_atomic_int_get (&_terminate)) { + return 0; + } - _process_nframes = nframes; + _process_nframes = nframes; _process_start_sample = start_sample; - _process_end_sample = end_sample; + _process_end_sample = end_sample; - _process_noroll = false; - _process_retval = 0; + _process_noroll = false; + _process_retval = 0; _process_need_butler = false; - DEBUG_TRACE(DEBUG::ProcessThreads, "wake graph for non-silent process\n"); + DEBUG_TRACE (DEBUG::ProcessThreads, "wake graph for non-silent process\n"); _callback_start_sem.signal (); _callback_done_sem.wait (); DEBUG_TRACE (DEBUG::ProcessThreads, "graph execution complete\n"); @@ -542,18 +582,20 @@ Graph::routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t { DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("no-roll graph execution from %1 to %2 = %3\n", start_sample, end_sample, nframes)); - if (!_threads_active) return 0; + if (g_atomic_int_get (&_terminate)) { + return 0; + } - _process_nframes = nframes; - _process_start_sample = start_sample; - _process_end_sample = end_sample; + _process_nframes = nframes; + _process_start_sample = start_sample; + _process_end_sample = end_sample; _process_non_rt_pending = non_rt_pending; - _process_noroll = true; - _process_retval = 0; + _process_noroll = true; + _process_retval = 0; _process_need_butler = false; - DEBUG_TRACE(DEBUG::ProcessThreads, "wake graph for no-roll process\n"); + DEBUG_TRACE (DEBUG::ProcessThreads, "wake graph for no-roll process\n"); _callback_start_sem.signal (); _callback_done_sem.wait (); DEBUG_TRACE (DEBUG::ProcessThreads, "graph execution complete\n"); @@ -564,11 +606,11 @@ void Graph::process_one_route (Route* route) { bool need_butler = false; - int retval; + int retval; assert (route); - DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 runs route %2\n", pthread_name(), route->name())); + DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 runs route %2\n", pthread_name (), route->name ())); if (_process_noroll) { retval = route->no_roll (_process_nframes, _process_start_sample, _process_end_sample, _process_non_rt_pending); @@ -588,5 +630,5 @@ Graph::process_one_route (Route* route) bool Graph::in_process_thread () const { - return AudioEngine::instance()->in_process_thread (); + return AudioEngine::instance ()->in_process_thread (); } diff --git a/libs/ardour/graphnode.cc b/libs/ardour/graphnode.cc index 47cabfbae0..b42d57bf9c 100644 --- a/libs/ardour/graphnode.cc +++ b/libs/ardour/graphnode.cc @@ -25,11 +25,11 @@ using namespace ARDOUR; GraphNode::GraphNode (boost::shared_ptr graph) - : _graph(graph) + : _graph (graph) { } -GraphNode::~GraphNode() +GraphNode::~GraphNode () { } @@ -37,19 +37,20 @@ void GraphNode::prep (int chain) { /* This is the number of nodes that directly feed us */ - _refcount = _init_refcount[chain]; + g_atomic_int_set (&_refcount, _init_refcount[chain]); } -/** Called by another node to tell us that one of the nodes that feed us - * has been processed. - */ +/** Called by an upstream node, when it has completed processing */ void -GraphNode::dec_ref() +GraphNode::trigger () { + /* check if we can run */ if (g_atomic_int_dec_and_test (&_refcount)) { - /* All the nodes that feed us are done, so we can queue this node - * for processing. - */ +#if 0 // TODO optimize: remove prep() + /* reset reference count for next cycle */ + g_atomic_int_set (&_refcount, _init_refcount[chain]); +#endif + /* All nodes that feed this node have completed, so this node be processed now. */ _graph->trigger (this); } } @@ -58,23 +59,23 @@ void GraphNode::finish (int chain) { node_set_t::iterator i; - bool feeds_somebody = false; + bool feeds = false; - /* Tell the nodes that we feed that we've finished */ - for (i=_activation_set[chain].begin(); i!=_activation_set[chain].end(); i++) { - (*i)->dec_ref(); - feeds_somebody = true; + /* Notify downstream nodes that depend on this node */ + for (i = _activation_set[chain].begin (); i != _activation_set[chain].end (); ++i) { + (*i)->trigger (); + feeds = true; } - if (!feeds_somebody) { - /* This node does not feed anybody, so decrement the graph's finished count */ - _graph->dec_ref(); + if (!feeds) { + /* This node is a terminal node that does not feed another note, + * so notify the graph to decrement the the finished count */ + _graph->reached_terminal_node (); } } - void -GraphNode::process() +GraphNode::process () { - _graph->process_one_route (dynamic_cast(this)); + _graph->process_one_route (dynamic_cast (this)); } diff --git a/libs/ardour/import_pt.cc b/libs/ardour/import_pt.cc index d668326a7e..bcd6c00b8e 100644 --- a/libs/ardour/import_pt.cc +++ b/libs/ardour/import_pt.cc @@ -48,7 +48,7 @@ #include "ardour/session.h" #include "pbd/memento_command.h" -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include "pbd/i18n.h" @@ -201,7 +201,7 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) vector ptfwavpair; vector ptfregpair; - vector::iterator w; + vector::const_iterator w; SourceList just_one_src; SourceList imported; @@ -213,18 +213,18 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK); - for (w = ptf.audiofiles.begin (); w != ptf.audiofiles.end () && !status.cancel; ++w) { + for (w = ptf.audiofiles ().begin (); w != ptf.audiofiles ().end () && !status.cancel; ++w) { ptflookup_t p; ok = false; /* Try audio file */ - fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path), "Audio Files"); + fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path ()), "Audio Files"); fullpath = Glib::build_filename (fullpath, w->filename); if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS)) { just_one_src.clear(); ok = import_sndfile_as_region (fullpath, SrcBest, pos, just_one_src, status); } else { /* Try fade file */ - fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path), "Fade Files"); + fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path ()), "Fade Files"); fullpath = Glib::build_filename (fullpath, w->filename); if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS)) { just_one_src.clear(); @@ -237,7 +237,7 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) * it won't be resampled, so we can only do this * when sample rates are matching */ - if (sample_rate () == ptf.sessionrate) { + if (sample_rate () == ptf.sessionrate ()) { /* Insert reference to missing source */ samplecnt_t sourcelen = w->length; XMLNode srcxml (X_("Source")); @@ -276,8 +276,8 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) lx.acquire(); save_state(""); - for (vector::iterator a = ptf.regions.begin (); - a != ptf.regions.end (); ++a) { + for (vector::const_iterator a = ptf.regions ().begin (); + a != ptf.regions ().end (); ++a) { for (vector::iterator p = ptfwavpair.begin (); p != ptfwavpair.end (); ++p) { if ((p->index1 == a->wave.index) && (strcmp (a->wave.filename.c_str (), "") != 0)) { @@ -310,7 +310,7 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) } } - for (vector::iterator a = ptf.tracks.begin (); a != ptf.tracks.end (); ++a) { + for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { for (vector::iterator p = ptfregpair.begin (); p != ptfregpair.end (); ++p) { @@ -384,7 +384,7 @@ Session::import_pt (PTFFormat& ptf, ImportStatus& status) trymidi: status.paths.clear(); - status.paths.push_back(ptf.path); + status.paths.push_back(ptf.path ()); status.current = 1; status.total = 1; status.freeze = false; @@ -396,7 +396,7 @@ trymidi: vector uniquetr; - for (vector::iterator a = ptf.miditracks.begin (); a != ptf.miditracks.end (); ++a) { + for (vector::const_iterator a = ptf.miditracks ().begin (); a != ptf.miditracks ().end (); ++a) { bool found = false; for (vector::iterator b = uniquetr.begin (); b != uniquetr.end (); ++b) { if (b->trname == a->name) { @@ -429,7 +429,7 @@ trymidi: } /* MIDI - Add midi regions one-by-one to corresponding midi tracks */ - for (vector::iterator a = ptf.miditracks.begin (); a != ptf.miditracks.end (); ++a) { + for (vector::const_iterator a = ptf.miditracks ().begin (); a != ptf.miditracks ().end (); ++a) { boost::shared_ptr midi_track = midi_tracks[a->index]; assert (midi_track); @@ -453,7 +453,7 @@ trymidi: MidiModel::NoteDiffCommand *midicmd; midicmd = mm->new_note_diff_command ("Import ProTools MIDI"); - for (vector::iterator j = a->reg.midi.begin (); j != a->reg.midi.end (); ++j) { + for (vector::const_iterator j = a->reg.midi.begin (); j != a->reg.midi.end (); ++j) { //printf(" : MIDI : pos=%f len=%f\n", (float)j->pos / 960000., (float)j->length / 960000.); Temporal::Beats start = (Temporal::Beats)(j->pos / 960000.); Temporal::Beats len = (Temporal::Beats)(j->length / 960000.); diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index c6624b2e4a..52bbd59b5f 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -200,6 +200,19 @@ IO::connect (boost::shared_ptr our_port, string other_port, void* src) return 0; } +bool +IO::can_add_port (DataType type) const +{ + switch (type) { + case DataType::NIL: + return false; + case DataType::AUDIO: + return true; + case DataType::MIDI: + return _ports.count ().n_midi() < 1; + } +} + int IO::remove_port (boost::shared_ptr port, void* src) { @@ -269,6 +282,10 @@ IO::add_port (string destination, void* src, DataType type) type = _default_type; } + if (!can_add_port (type)) { + return -1; + } + ChanCount before = _ports.count (); ChanCount after = before; after.set (type, after.get (type) + 1); diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index b8b336c933..fde6d594e9 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -1066,6 +1066,7 @@ LuaBindings::common (lua_State* L) .addFunction ("set_name", &Route::set_name) .addFunction ("comment", &Route::comment) .addFunction ("active", &Route::active) + .addFunction ("data_type", &Route::data_type) .addFunction ("set_active", &Route::set_active) .addFunction ("nth_plugin", &Route::nth_plugin) .addFunction ("nth_processor", &Route::nth_processor) @@ -1170,6 +1171,10 @@ LuaBindings::common (lua_State* L) .addFunction ("bounce", &Track::bounce) .addFunction ("bounce_range", &Track::bounce_range) .addFunction ("playlist", &Track::playlist) + .addFunction ("use_playlist", &Track::use_playlist) + .addFunction ("use_copy_playlist", &Track::use_copy_playlist) + .addFunction ("use_new_playlist", &Track::use_new_playlist) + .addFunction ("find_and_use_playlist", &Track::find_and_use_playlist) .endClass () .deriveWSPtrClass ("AudioTrack") @@ -2251,6 +2256,8 @@ LuaBindings::common (lua_State* L) .addFunction ("request_stop", &Session::request_stop) .addFunction ("request_play_loop", &Session::request_play_loop) .addFunction ("get_play_loop", &Session::get_play_loop) + .addFunction ("get_xrun_count", &Session::get_xrun_count) + .addFunction ("reset_xrun_count", &Session::reset_xrun_count) .addFunction ("last_transport_start", &Session::last_transport_start) .addFunction ("goto_start", &Session::goto_start) .addFunction ("goto_end", &Session::goto_end) diff --git a/libs/ardour/luaproc.cc b/libs/ardour/luaproc.cc index 8cb5f66c1c..04d83862ce 100644 --- a/libs/ardour/luaproc.cc +++ b/libs/ardour/luaproc.cc @@ -624,13 +624,7 @@ LuaProc::connect_and_run (BufferSet& bufs, Plugin::connect_and_run (bufs, start, end, speed, in, out, nframes, offset); // This is needed for ARDOUR::Session requests :( - if (! SessionEvent::has_per_thread_pool ()) { - char name[64]; - snprintf (name, 64, "Proc-%p", this); - pthread_set_name (name); - SessionEvent::create_per_thread_pool (name, 64); - PBD::notify_event_loops_about_thread_creation (pthread_self(), name, 64); - } + assert (SessionEvent::has_per_thread_pool ()); uint32_t const n = parameter_count (); for (uint32_t i = 0; i < n; ++i) { diff --git a/libs/ardour/luascripting.cc b/libs/ardour/luascripting.cc index e748fb656e..2d56a608ea 100644 --- a/libs/ardour/luascripting.cc +++ b/libs/ardour/luascripting.cc @@ -28,6 +28,7 @@ #include "ardour/luascripting.h" #include "ardour/lua_script_params.h" #include "ardour/search_paths.h" +#include "ardour/utils.h" #include "lua/luastate.h" #include "LuaBridge/LuaBridge.h" @@ -104,11 +105,10 @@ LuaScripting::refresh (bool run_scan) } } -struct ScriptSorter { - bool operator () (LuaScriptInfoPtr a, LuaScriptInfoPtr b) { - return a->name < b->name; - } -}; +bool +LuaScripting::Sorter::operator() (LuaScriptInfoPtr const a, LuaScriptInfoPtr const b) const { + return ARDOUR::cmp_nocase_utf8 (a->name, b->name) < 0; +} LuaScriptInfoPtr LuaScripting::script_info (const std::string &script) { @@ -166,13 +166,13 @@ LuaScripting::scan () } } - std::sort (_sl_dsp->begin(), _sl_dsp->end(), ScriptSorter()); - std::sort (_sl_session->begin(), _sl_session->end(), ScriptSorter()); - std::sort (_sl_hook->begin(), _sl_hook->end(), ScriptSorter()); - std::sort (_sl_action->begin(), _sl_action->end(), ScriptSorter()); - std::sort (_sl_snippet->begin(), _sl_snippet->end(), ScriptSorter()); - std::sort (_sl_setup->begin(), _sl_setup->end(), ScriptSorter()); - std::sort (_sl_tracks->begin(), _sl_tracks->end(), ScriptSorter()); + std::sort (_sl_dsp->begin(), _sl_dsp->end(), Sorter()); + std::sort (_sl_session->begin(), _sl_session->end(), Sorter()); + std::sort (_sl_hook->begin(), _sl_hook->end(), Sorter()); + std::sort (_sl_action->begin(), _sl_action->end(), Sorter()); + std::sort (_sl_snippet->begin(), _sl_snippet->end(), Sorter()); + std::sort (_sl_setup->begin(), _sl_setup->end(), Sorter()); + std::sort (_sl_tracks->begin(), _sl_tracks->end(), Sorter()); scripts_changed (); /* EMIT SIGNAL */ } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index e3e907be84..4114f0acbd 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -218,7 +218,11 @@ Route::init () /* set default meter type */ if (is_master()) { +#ifdef MIXBUS + set_meter_type (MeterK14); +#else set_meter_type (Config->get_meter_type_master ()); +#endif } else if (dynamic_cast(this)) { set_meter_type (Config->get_meter_type_track ()); } else { diff --git a/libs/ardour/template_utils.cc b/libs/ardour/template_utils.cc index a62f034f86..9d1739c99b 100644 --- a/libs/ardour/template_utils.cc +++ b/libs/ardour/template_utils.cc @@ -91,8 +91,6 @@ find_session_templates (vector& template_names, bool read_xml) return; } - cerr << "Found " << templates.size() << " along " << template_search_path().to_string() << endl; - for (vector::iterator i = templates.begin(); i != templates.end(); ++i) { string file = session_template_dir_to_file (*i); diff --git a/libs/ardour/wscript b/libs/ardour/wscript index efc070af05..790ff53f0e 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -311,7 +311,7 @@ def configure(conf): autowaf.check_pkg(conf, 'sratom-0', uselib_store='SRATOM', atleast_version='0.2.0', mandatory=True) autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV', - atleast_version='0.24.2', mandatory=False) + atleast_version='0.24.2', mandatory=True) autowaf.check_pkg(conf, 'suil-0', uselib_store='SUIL', atleast_version='0.6.0', mandatory=False) conf.define ('LV2_SUPPORT', 1) diff --git a/libs/fst/scanner.cc b/libs/fst/scanner.cc index 9bf714ff69..405bf56201 100644 --- a/libs/fst/scanner.cc +++ b/libs/fst/scanner.cc @@ -76,7 +76,7 @@ class DummyReceiver : public Receiver { std::cerr << prefix << str << std::endl; if (chn == Transmitter::Fatal) { - ::exit (1); + ::exit (EXIT_FAILURE); } } }; diff --git a/libs/gtkmm2ext/gtk_ui.cc b/libs/gtkmm2ext/gtk_ui.cc index 444e7471e9..2f5d0ac6b4 100644 --- a/libs/gtkmm2ext/gtk_ui.cc +++ b/libs/gtkmm2ext/gtk_ui.cc @@ -594,7 +594,7 @@ UI::process_error_message (Transmitter::Channel chn, const char *str) default: /* no choice but to use text/console output here */ cerr << "programmer error in UI::check_error_messages (channel = " << chn << ")\n"; - ::exit (1); + ::exit (EXIT_FAILURE); } errors->text().get_buffer()->begin_user_action(); diff --git a/libs/pbd/openuri.cc b/libs/pbd/openuri.cc index 1843043ea5..7d096e7153 100644 --- a/libs/pbd/openuri.cc +++ b/libs/pbd/openuri.cc @@ -77,7 +77,7 @@ PBD::open_uri (const char* uri) #else if (::vfork () == 0) { ::execlp ("xdg-open", "xdg-open", s.c_str(), (char*)NULL); - exit (0); + exit (EXIT_SUCCESS); } #endif diff --git a/libs/pbd/pbd/mpmc_queue.h b/libs/pbd/pbd/mpmc_queue.h new file mode 100644 index 0000000000..eda78473b8 --- /dev/null +++ b/libs/pbd/pbd/mpmc_queue.h @@ -0,0 +1,147 @@ +/* + * (C) 2017, 2019 Robin Gareus + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _pbd_mpc_queue_h_ +#define _pbd_mpc_queue_h_ + +#include +#include +#include + +namespace PBD { + +/** Lock free multiple producer, multiple consumer queue + * + * inspired by http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue + * Kudos to Dmitry Vyukov + */ +template +class /*LIBPBD_API*/ MPMCQueue +{ +public: + MPMCQueue (size_t buffer_size = 8) + : _buffer (0) + , _buffer_mask (0) + { + reserve (buffer_size); + } + + ~MPMCQueue () + { + delete[] _buffer; + } + + static size_t + power_of_two_size (size_t sz) + { + int32_t power_of_two; + for (power_of_two = 1; 1U << power_of_two < sz; ++power_of_two) ; + return 1U << power_of_two; + } + + void + reserve (size_t buffer_size) + { + buffer_size = power_of_two_size (buffer_size); + assert ((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0)); + if (_buffer_mask >= buffer_size - 1) { + return; + } + delete[] _buffer; + _buffer = new cell_t[buffer_size]; + _buffer_mask = buffer_size - 1; + clear (); + } + + void + clear () + { + for (size_t i = 0; i <= _buffer_mask; ++i) { + g_atomic_int_set (&_buffer[i]._sequence, i); + } + g_atomic_int_set (&_enqueue_pos, 0); + g_atomic_int_set (&_dequeue_pos, 0); + } + + bool + push_back (T const& data) + { + cell_t* cell; + guint pos = g_atomic_int_get (&_enqueue_pos); + for (;;) { + cell = &_buffer[pos & _buffer_mask]; + guint seq = g_atomic_int_get (&cell->_sequence); + intptr_t dif = (intptr_t)seq - (intptr_t)pos; + if (dif == 0) { + if (g_atomic_int_compare_and_exchange (&_enqueue_pos, pos, pos + 1)) { + break; + } + } else if (dif < 0) { + assert (0); + return false; + } else { + pos = g_atomic_int_get (&_enqueue_pos); + } + } + + cell->_data = data; + g_atomic_int_set (&cell->_sequence, pos + 1); + return true; + } + + bool + pop_front (T& data) + { + cell_t* cell; + guint pos = g_atomic_int_get (&_dequeue_pos); + for (;;) { + cell = &_buffer[pos & _buffer_mask]; + guint seq = g_atomic_int_get (&cell->_sequence); + intptr_t dif = (intptr_t)seq - (intptr_t) (pos + 1); + if (dif == 0) { + if (g_atomic_int_compare_and_exchange (&_dequeue_pos, pos, pos + 1)) { + break; + } + } else if (dif < 0) { + return false; + } else { + pos = g_atomic_int_get (&_dequeue_pos); + } + } + + data = cell->_data; + g_atomic_int_set (&cell->_sequence, pos + _buffer_mask + 1); + return true; + } + +private: + struct cell_t { + volatile guint _sequence; + T _data; + }; + + cell_t* _buffer; + size_t _buffer_mask; + + volatile guint _enqueue_pos; + volatile guint _dequeue_pos; +}; + +} /* end namespace */ + +#endif diff --git a/libs/pbd/system_exec.cc b/libs/pbd/system_exec.cc index 0a17cce560..97f1056872 100644 --- a/libs/pbd/system_exec.cc +++ b/libs/pbd/system_exec.cc @@ -962,7 +962,7 @@ SystemExec::start (StdErrMode stderr_mode, const char *vfork_exec_wrapper) char buf = 0; (void) ::write (pok[1], &buf, 1); close_fd (pok[1]); - exit (-1); + exit (EXIT_FAILURE); return -1; } diff --git a/libs/pbd/transmitter.cc b/libs/pbd/transmitter.cc index fccb667b48..0e9ae79530 100644 --- a/libs/pbd/transmitter.cc +++ b/libs/pbd/transmitter.cc @@ -91,7 +91,7 @@ Transmitter::deliver () sigemptyset (&mask); sigsuspend (&mask); /*NOTREACHED*/ - exit (1); + exit (EXIT_FAILURE); /* JE - From what I can tell, the above code suspends * program execution until (any) signal occurs. Not * sure at the moment what this achieves, unless it diff --git a/libs/ptformat/MSVCptformat/ptformat.vcproj b/libs/ptformat/MSVCptformat/ptformat.vcproj index f8b440ba61..65e20a1eba 100644 --- a/libs/ptformat/MSVCptformat/ptformat.vcproj +++ b/libs/ptformat/MSVCptformat/ptformat.vcproj @@ -262,7 +262,7 @@ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" > @@ -272,7 +272,7 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > -#include -#include -#include -#include - -#include - -#include "ptformat/ptfformat.h" - -#if 0 -#define verbose_printf(...) printf(__VA_ARGS__) -#else -#define verbose_printf(...) -#endif - -using namespace std; - -static bool wavidx_compare(PTFFormat::wav_t& w1, PTFFormat::wav_t& w2) { - return w1.index < w2.index; -} - -static bool wavname_compare(PTFFormat::wav_t& w1, PTFFormat::wav_t& w2) { - return (strcasecmp(w1.filename.c_str(), w2.filename.c_str()) < 0); -} - -static bool regidx_compare(PTFFormat::region_t& r1, PTFFormat::region_t& r2) { - return r1.index < r2.index; -} - -static bool regname_compare(PTFFormat::region_t& r1, PTFFormat::region_t& r2) { - return (strcasecmp(r1.name.c_str(), r2.name.c_str()) < 0); -} - -static void -hexdump(uint8_t *data, int len) -{ - int i,j,end,step=16; - - for (i = 0; i < len; i += step) { - printf("0x%02X: ", i); - end = i + step; - if (end > len) end = len; - for (j = i; j < end; j++) { - printf("0x%02X ", data[j]); - } - for (j = i; j < end; j++) { - if (data[j] < 128 && data[j] > 32) - printf("%c", data[j]); - else - printf("."); - } - printf("\n"); - } -} - -PTFFormat::PTFFormat() : version(0), product(NULL), ptfunxored(NULL) { -} - -PTFFormat::~PTFFormat() { - cleanup(); -} - -uint16_t -PTFFormat::u_endian_read2(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint16_t)(buf[0]) << 8) | (uint16_t)(buf[1]); - } else { - return ((uint16_t)(buf[1]) << 8) | (uint16_t)(buf[0]); - } -} - -uint32_t -PTFFormat::u_endian_read3(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint32_t)(buf[0]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[2]); - } else { - return ((uint32_t)(buf[2]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[0]); - } -} - -uint32_t -PTFFormat::u_endian_read4(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint32_t)(buf[0]) << 24) | - ((uint32_t)(buf[1]) << 16) | - ((uint32_t)(buf[2]) << 8) | - (uint32_t)(buf[3]); - } else { - return ((uint32_t)(buf[3]) << 24) | - ((uint32_t)(buf[2]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[0]); - } -} - -uint64_t -PTFFormat::u_endian_read5(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint64_t)(buf[0]) << 32) | - ((uint64_t)(buf[1]) << 24) | - ((uint64_t)(buf[2]) << 16) | - ((uint64_t)(buf[3]) << 8) | - (uint64_t)(buf[4]); - } else { - return ((uint64_t)(buf[4]) << 32) | - ((uint64_t)(buf[3]) << 24) | - ((uint64_t)(buf[2]) << 16) | - ((uint64_t)(buf[1]) << 8) | - (uint64_t)(buf[0]); - } -} - -void -PTFFormat::cleanup(void) { - if (ptfunxored) { - free(ptfunxored); - ptfunxored = NULL; - } - audiofiles.clear(); - regions.clear(); - midiregions.clear(); - compounds.clear(); - tracks.clear(); - miditracks.clear(); - version = 0; - product = NULL; -} - -int64_t -PTFFormat::foundat(unsigned char *haystack, uint64_t n, const char *needle) { - int64_t found = 0; - uint64_t i, j, needle_n; - needle_n = strlen(needle); - - for (i = 0; i < n; i++) { - found = i; - for (j = 0; j < needle_n; j++) { - if (haystack[i+j] != needle[j]) { - found = -1; - break; - } - } - if (found > 0) - return found; - } - return -1; -} - -bool -PTFFormat::jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { - uint64_t i; - uint64_t k = *currpos; - while (k + needlelen < maxoffset) { - bool foundall = true; - for (i = 0; i < needlelen; i++) { - if (buf[k+i] != needle[i]) { - foundall = false; - break; - } - } - if (foundall) { - *currpos = k; - return true; - } - k++; - } - return false; -} - -bool -PTFFormat::jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { - uint64_t i; - uint64_t k = *currpos; - while (k > 0 && k + needlelen < maxoffset) { - bool foundall = true; - for (i = 0; i < needlelen; i++) { - if (buf[k+i] != needle[i]) { - foundall = false; - break; - } - } - if (foundall) { - *currpos = k; - return true; - } - k--; - } - return false; -} - -bool -PTFFormat::foundin(std::string haystack, std::string needle) { - size_t found = haystack.find(needle); - if (found != std::string::npos) { - return true; - } else { - return false; - } -} - -/* Return values: 0 success - -1 could not decrypt pt session -*/ -int -PTFFormat::unxor(std::string path) { - FILE *fp; - unsigned char xxor[256]; - unsigned char ct; - uint64_t i; - uint8_t xor_type; - uint8_t xor_value; - uint8_t xor_delta; - uint16_t xor_len; - - if (! (fp = g_fopen(path.c_str(), "rb"))) { - return -1; - } - - fseek(fp, 0, SEEK_END); - len = ftell(fp); - if (len < 0x14) { - fclose(fp); - return -1; - } - - if (! (ptfunxored = (unsigned char*) malloc(len * sizeof(unsigned char)))) { - /* Silently fail -- out of memory*/ - fclose(fp); - ptfunxored = 0; - return -1; - } - - /* The first 20 bytes are always unencrypted */ - fseek(fp, 0x00, SEEK_SET); - i = fread(ptfunxored, 1, 0x14, fp); - if (i < 0x14) { - fclose(fp); - return -1; - } - - xor_type = ptfunxored[0x12]; - xor_value = ptfunxored[0x13]; - xor_len = 256; - - // xor_type 0x01 = ProTools 5, 6, 7, 8 and 9 - // xor_type 0x05 = ProTools 10, 11, 12 - switch(xor_type) { - case 0x01: - xor_delta = gen_xor_delta(xor_value, 53, false); - break; - case 0x05: - xor_delta = gen_xor_delta(xor_value, 11, true); - break; - default: - fclose(fp); - return -1; - } - - /* Generate the xor_key */ - for (i=0; i < xor_len; i++) - xxor[i] = (i * xor_delta) & 0xff; - - /* hexdump(xxor, xor_len); */ - - /* Read file and decrypt rest of file */ - i = 0x14; - fseek(fp, i, SEEK_SET); - while (fread(&ct, 1, 1, fp) != 0) { - uint8_t xor_index = (xor_type == 0x01) ? i & 0xff : (i >> 12) & 0xff; - ptfunxored[i++] = ct ^ xxor[xor_index]; - } - fclose(fp); - return 0; -} - -/* Return values: 0 success - -1 could not parse pt session -*/ -int -PTFFormat::load(std::string ptf, int64_t targetsr) { - cleanup(); - path = ptf; - - if (unxor(path)) - return -1; - - if (parse_version()) - return -1; - - if (version < 5 || version > 12) - return -1; - - targetrate = targetsr; - - if (parse()) - return -1; - - return 0; -} - -bool -PTFFormat::parse_version() { - uint32_t seg_len,str_len; - uint8_t *data = ptfunxored + 0x14; - uintptr_t data_end = ((uintptr_t)ptfunxored) + 0x100; - uint8_t seg_type; - bool success = false; - - while( ((uintptr_t)data < data_end) && (success == false) ) { - - if (data[0] != 0x5a) { - success = false; - break; - } - - seg_type = data[1]; - /* Skip segment header */ - data += 3; - if (data[0] == 0 && data[1] == 0) { - /* BE */ - is_bigendian = true; - } else { - /* LE */ - is_bigendian = false; - } - seg_len = u_endian_read4(&data[0], is_bigendian); - - /* Skip seg_len */ - data += 4; - if (!(seg_type == 0x04 || seg_type == 0x03) || data[0] != 0x03) { - /* Go to next segment */ - data += seg_len; - continue; - } - /* Skip 0x03 0x00 0x00 */ - data += 3; - seg_len -= 3; - str_len = (*(uint8_t *)data); - if (! (product = (uint8_t *)malloc((str_len+1) * sizeof(uint8_t)))) { - success = false; - break; - } - - /* Skip str_len */ - data += 4; - seg_len -= 4; - - memcpy(product, data, str_len); - product[str_len] = 0; - data += str_len; - seg_len -= str_len; - - /* Skip 0x03 0x00 0x00 0x00 */ - data += 4; - seg_len -= 4; - - version = data[0]; - if (version == 0) { - version = data[3]; - } - data += seg_len; - success = true; - } - - /* If the above does not work, try other heuristics */ - if ((uintptr_t)data >= data_end - seg_len) { - version = ptfunxored[0x40]; - if (version == 0) { - version = ptfunxored[0x3d]; - } - if (version == 0) { - version = ptfunxored[0x3a] + 2; - } - if (version != 0) { - success = true; - } - } - return (!success); -} - -uint8_t -PTFFormat::gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative) { - uint16_t i; - for (i = 0; i < 256; i++) { - if (((i * mul) & 0xff) == xor_value) { - return (negative) ? i * (-1) : i; - } - } - // Should not occur - return 0; -} - -int -PTFFormat::parse(void) { - if (version == 5) { - parse5header(); - setrates(); - if (sessionrate < 44100 || sessionrate > 192000) - return -1; - parseaudio5(); - parserest5(); - parsemidi(); - } else if (version == 7) { - parse7header(); - setrates(); - if (sessionrate < 44100 || sessionrate > 192000) - return -1; - parseaudio(); - parserest89(); - parsemidi(); - } else if (version == 8) { - parse8header(); - setrates(); - if (sessionrate < 44100 || sessionrate > 192000) - return -1; - parseaudio(); - parserest89(); - parsemidi(); - } else if (version == 9) { - parse9header(); - setrates(); - if (sessionrate < 44100 || sessionrate > 192000) - return -1; - parseaudio(); - parserest89(); - parsemidi(); - } else if (version == 10 || version == 11 || version == 12) { - parse10header(); - setrates(); - if (sessionrate < 44100 || sessionrate > 192000) - return -1; - parseaudio(); - parserest12(); - parsemidi12(); - } else { - // Should not occur - return -1; - } - return 0; -} - -void -PTFFormat::setrates(void) { - ratefactor = 1.f; - if (sessionrate != 0) { - ratefactor = (float)targetrate / sessionrate; - } -} - -void -PTFFormat::parse5header(void) { - uint32_t k; - - // Find session sample rate - k = 0x100; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x00\x02", 3)) { - jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2); - k--; - } - - sessionrate = u_endian_read3(&ptfunxored[k+12], is_bigendian); -} - -void -PTFFormat::parse7header(void) { - uint32_t k; - - // Find session sample rate - k = 0x100; - - jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x00\x05", 3); - - sessionrate = u_endian_read3(&ptfunxored[k+12], is_bigendian); -} - -void -PTFFormat::parse8header(void) { - uint32_t k; - - // Find session sample rate - k = 0; - - jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x05", 2); - - sessionrate = u_endian_read3(&ptfunxored[k+11], is_bigendian); -} - -void -PTFFormat::parse9header(void) { - uint32_t k; - - // Find session sample rate - k = 0x100; - - jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x06", 2); - - sessionrate = u_endian_read3(&ptfunxored[k+11], is_bigendian); -} - -void -PTFFormat::parse10header(void) { - uint32_t k; - - // Find session sample rate - k = 0x100; - - jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x09", 2); - - sessionrate = u_endian_read3(&ptfunxored[k+11], is_bigendian); -} - -void -PTFFormat::parserest5(void) { - uint32_t i, j, k; - uint64_t regionspertrack, lengthofname, numberofregions; - uint64_t startbytes, lengthbytes, offsetbytes, somethingbytes, skipbytes; - uint16_t tracknumber = 0; - uint16_t findex; - uint16_t rindex; - unsigned char tag1[3]; - unsigned char tag2[3]; - unsigned char tag3[3]; - - if (is_bigendian) { - tag1[0] = tag2[0] = tag3[0] = '\x5a'; - tag1[1] = tag2[1] = tag3[1] = '\x00'; - tag1[2] = '\x01'; - tag2[2] = '\x02'; - tag3[2] = '\x03'; - } else { - tag1[0] = tag2[0] = tag3[0] = '\x5a'; - tag1[1] = '\x01'; - tag2[1] = '\x02'; - tag3[1] = '\x03'; - tag1[2] = tag2[2] = tag3[2] = '\x00'; - } - - // Find Source->Region info - k = upto; - for (i = 0; i < 2; i++) { - jumpto(&k, ptfunxored, len, tag3, 3); - k++; - } - jumpto(&k, ptfunxored, len, tag2, 3); - - numberofregions = u_endian_read4(&ptfunxored[k-13], is_bigendian); - - i = k; - while (numberofregions > 0 && i < len) { - jumpto(&i, ptfunxored, len, tag2, 3); - - uint32_t lengthofname = u_endian_read4(&ptfunxored[i+9], is_bigendian); - - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[i+13+j]; - } - name[j] = '\0'; - j += i+13; - //uint8_t disabled = ptfunxored[j]; - - if (is_bigendian) { - offsetbytes = (ptfunxored[j+4] & 0xf0) >> 4; - lengthbytes = (ptfunxored[j+3] & 0xf0) >> 4; - startbytes = (ptfunxored[j+2] & 0xf0) >> 4; - somethingbytes = (ptfunxored[j+2] & 0xf); - skipbytes = ptfunxored[j+1]; - } else { - offsetbytes = (ptfunxored[j+1] & 0xf0) >> 4; //3 - lengthbytes = (ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (ptfunxored[j+3] & 0xf0) >> 4; //1 - somethingbytes = (ptfunxored[j+3] & 0xf); - skipbytes = ptfunxored[j+4]; - } - findex = u_endian_read4(&ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes], is_bigendian); - - uint32_t sampleoffset = 0; - switch (offsetbytes) { - case 4: - sampleoffset = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - sampleoffset = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - sampleoffset = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - sampleoffset = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=offsetbytes; - uint32_t length = 0; - switch (lengthbytes) { - case 4: - length = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - length = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - length = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - length = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=lengthbytes; - uint32_t start = 0; - switch (startbytes) { - case 4: - start = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - start = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - start = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - start = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=startbytes; - - std::string filename = string(name); - wav_t f = { - "", - (uint16_t)findex, - (int64_t)(start*ratefactor), - (int64_t)(length*ratefactor), - }; - - vector::iterator begin = actualwavs.begin(); - vector::iterator finish = actualwavs.end(); - vector::iterator found; - if ((found = std::find(begin, finish, f)) != finish) { - f.filename = (*found).filename; - } - std::vector m; - region_t r = { - name, - rindex, - (int64_t)(start*ratefactor), - (int64_t)(sampleoffset*ratefactor), - (int64_t)(length*ratefactor), - f, - m - }; - regions.push_back(r); - rindex++; - i = j + 1; - numberofregions--; - } - - // Find Region->Track info - /* - k = 0; - for (i = 0; i < 4; i++) { - jumpto(&k, ptfunxored, len, tag3, 3); - k++; - } - */ - k = j = i; - while (j < len) { - if (jumpto(&j, ptfunxored, j+13+3, tag1, 3)) { - j++; - if (jumpto(&j, ptfunxored, j+13+3, tag1, 3)) { - j++; - if (jumpto(&j, ptfunxored, j+13+3, tag1, 3)) { - if ((j == k+26) && (ptfunxored[j-13] == '\x5a') && (ptfunxored[j-26] == '\x5a')) { - k = j; - break; - } - } - } - } - k++; - j = k; - } - - if (ptfunxored[k+13] == '\x5a') { - k++; - } - - // Start parsing track info - rindex = 0; - while (k < len) { - if ( (ptfunxored[k ] == 0xff) && - (ptfunxored[k+1] == 0xff)) { - break; - } - jumpto(&k, ptfunxored, len, tag1, 3); - - lengthofname = u_endian_read4(&ptfunxored[k+9], is_bigendian); - if (ptfunxored[k+13] == 0x5a) { - k++; - break; - } - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[k+13+j]; - } - name[j] = '\0'; - regionspertrack = u_endian_read4(&ptfunxored[k+13+j], is_bigendian); - for (i = 0; i < regionspertrack; i++) { - jumpto(&k, ptfunxored, len, tag3, 3); - j = k + 15; - if (is_bigendian) { - offsetbytes = (ptfunxored[j+1] & 0xf0) >> 4; - //somethingbytes = (ptfunxored[j+2] & 0xf); - lengthbytes = (ptfunxored[j+3] & 0xf0) >> 4; - startbytes = (ptfunxored[j+4] & 0xf0) >> 4; - } else { - offsetbytes = (ptfunxored[j+4] & 0xf0) >> 4; - //somethingbytes = (ptfunxored[j+3] & 0xf); - lengthbytes = (ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (ptfunxored[j+1] & 0xf0) >> 4; - } - rindex = u_endian_read4(&ptfunxored[k+11], is_bigendian); - uint32_t start = 0; - switch (startbytes) { - case 4: - start = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - start = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - start = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - start = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=startbytes; - uint32_t length = 0; - switch (lengthbytes) { - case 4: - length = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - length = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - length = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - length = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=lengthbytes; - uint32_t sampleoffset = 0; - switch (offsetbytes) { - case 4: - sampleoffset = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - sampleoffset = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - sampleoffset = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - sampleoffset = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=offsetbytes; - - track_t tr; - tr.name = name; - - region_t r; - r.index = rindex; - - vector::iterator begin = regions.begin(); - vector::iterator finish = regions.end(); - vector::iterator found; - if ((found = std::find(begin, finish, r)) != finish) { - tr.reg = (*found); - } - - tr.reg.startpos = (int64_t)(start*ratefactor); - tr.reg.sampleoffset = (int64_t)(sampleoffset*ratefactor); - tr.reg.length = (int64_t)(length*ratefactor); - vector::iterator ti; - vector::iterator bt = tracks.begin(); - vector::iterator et = tracks.end(); - if ((ti = std::find(bt, et, tr)) != et) { - tracknumber = (*ti).index; - } else { - ++tracknumber; - } - track_t t = { - name, - (uint16_t)tracknumber, - uint8_t(0), - tr.reg - }; - //if (tr.reg.length > 0) { - tracks.push_back(t); - //} - k++; - } - k++; - } -} - -void -PTFFormat::resort(std::vector& ws) { - int j = 0; - std::sort(ws.begin(), ws.end()); - for (std::vector::iterator i = ws.begin(); i != ws.end(); ++i) { - (*i).index = j; - j++; - } -} - -void -PTFFormat::resort(std::vector& rs) { - int j = 0; - //std::sort(rs.begin(), rs.end()); - for (std::vector::iterator i = rs.begin(); i != rs.end(); ++i) { - (*i).index = j; - j++; - } -} - -void -PTFFormat::filter(std::vector& rs) { - for (std::vector::iterator i = rs.begin(); i != rs.end(); ++i) { - if (i->length == 0) - rs.erase(i); - } -} - -void -PTFFormat::parseaudio5(void) { - uint32_t i,k,l; - uint64_t lengthofname, wavnumber; - uint32_t numberofwavs; - unsigned char tag6_LE[3], tag5_BE[3]; - unsigned char tag2_LE[3], tag2_BE[3]; - - // Find start of wav file list - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff", 4)) - return; - numberofwavs = u_endian_read4(&ptfunxored[k-18], is_bigendian); - - // Find actual wav names - char wavname[256]; - i = k; - jumpto(&i, ptfunxored, len, (const unsigned char *)"Files", 5); - - wavnumber = 0; - i+=16; - char ext[5]; - while (i < len && numberofwavs > 0) { - i++; - if ( ((ptfunxored[i ] == 0x5a) && - (ptfunxored[i+1] == 0x00) && - (ptfunxored[i+2] == 0x05)) || - ((ptfunxored[i ] == 0x5a) && - (ptfunxored[i+1] == 0x06))) { - break; - } - lengthofname = u_endian_read4(&ptfunxored[i-3], is_bigendian); - i++; - l = 0; - while (l < lengthofname) { - wavname[l] = ptfunxored[i+l]; - l++; - } - i+=lengthofname; - ext[0] = ptfunxored[i++]; - ext[1] = ptfunxored[i++]; - ext[2] = ptfunxored[i++]; - ext[3] = ptfunxored[i++]; - ext[4] = '\0'; - - wavname[l] = 0; - if (foundin(wavname, ".L") || foundin(wavname, ".R")) { - extension = string(""); - } else if (foundin(wavname, ".wav") || foundin(ext, "WAVE")) { - extension = string(".wav"); - } else if (foundin(wavname, ".aif") || foundin(ext, "AIFF")) { - extension = string(".aif"); - } else { - extension = string(""); - } - - std::string wave = string(wavname); - - if (foundin(wave, string(".grp"))) { - continue; - } - if (foundin(wave, string("Fade Files"))) { - i += 7; - continue; - } - - wav_t f = { wave, (uint16_t)(wavnumber++), 0, 0 }; - - actualwavs.push_back(f); - audiofiles.push_back(f); - //printf("done\n"); - numberofwavs--; - i += 7; - } - - i -= 7; - - tag5_BE[0] = tag6_LE[0] = tag2_BE[0] = tag2_LE[0] = '\x5a'; - tag5_BE[1] = '\x00'; - tag6_LE[1] = '\x06'; - tag2_BE[1] = '\x00'; - tag2_LE[1] = '\x02'; - tag5_BE[2] = '\x05'; - tag6_LE[2] = '\x00'; - tag2_BE[2] = '\x02'; - tag2_LE[2] = '\x00'; - - // Loop through all the sources - for (vector::iterator w = audiofiles.begin(); w != audiofiles.end(); ++w) { - // Jump to start of source metadata for this source - if (is_bigendian) { - if (!jumpto(&i, ptfunxored, len, tag5_BE, 3)) - return; - if (!jumpto(&i, ptfunxored, len, tag2_BE, 3)) - return; - w->length = u_endian_read4(&ptfunxored[i+19], true); - } else { - if (!jumpto(&i, ptfunxored, len, tag6_LE, 3)) - return; - if (!jumpto(&i, ptfunxored, len, tag2_LE, 3)) - return; - w->length = u_endian_read4(&ptfunxored[i+15], false); - } - } - upto = i; -} - -struct mchunk { - mchunk (uint64_t zt, uint64_t ml, std::vector const& c) - : zero (zt) - , maxlen (ml) - , chunk (c) - {} - uint64_t zero; - uint64_t maxlen; - std::vector chunk; -}; - -void -PTFFormat::parsemidi(void) { - uint32_t i, k; - uint64_t tr, n_midi_events, zero_ticks; - uint64_t midi_pos, midi_len, max_pos, region_pos; - uint8_t midi_velocity, midi_note; - uint16_t ridx; - uint16_t nmiditracks, regionnumber = 0; - uint32_t nregions, mr; - - std::vector midichunks; - midi_ev_t m; - - // Find MdNLB - k = 0; - - // Parse all midi chunks, not 1:1 mapping to regions yet - while (k + 35 < len) { - max_pos = 0; - std::vector midi; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"MdNLB", 5)) { - break; - } - k += 11; - n_midi_events = ptfunxored[k] | ptfunxored[k+1] << 8 | - ptfunxored[k+2] << 16 | ptfunxored[k+3] << 24; - - k += 4; - zero_ticks = u_endian_read5(&ptfunxored[k], is_bigendian); - for (i = 0; i < n_midi_events && k < len; i++, k += 35) { - midi_pos = u_endian_read5(&ptfunxored[k], is_bigendian); - midi_pos -= zero_ticks; - midi_note = ptfunxored[k+8]; - midi_len = u_endian_read5(&ptfunxored[k+9], is_bigendian); - midi_velocity = ptfunxored[k+17]; - - if (midi_pos + midi_len > max_pos) { - max_pos = midi_pos + midi_len; - } - - m.pos = midi_pos; - m.length = midi_len; - m.note = midi_note; - m.velocity = midi_velocity; -#if 1 -// stop gap measure to prevent crashes in ardour, -// remove when decryption is fully solved for .ptx - if ((m.velocity & 0x80) || (m.note & 0x80) || - (m.pos & 0xff00000000LL) || (m.length & 0xff00000000LL)) { - continue; - } -#endif - midi.push_back(m); - } - midichunks.push_back(mchunk (zero_ticks, max_pos, midi)); - } - - // Map midi chunks to regions - while (k < len) { - char midiregionname[256]; - uint8_t namelen; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"MdTEL", 5)) { - break; - } - - k += 41; - - nregions = u_endian_read2(&ptfunxored[k], is_bigendian); - - for (mr = 0; mr < nregions; mr++) { - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x0c", 2)) { - break; - } - - k += 9; - - namelen = ptfunxored[k]; - for (i = 0; i < namelen; i++) { - midiregionname[i] = ptfunxored[k+4+i]; - } - midiregionname[namelen] = '\0'; - k += 4 + namelen; - - k += 5; - /* - region_pos = (uint64_t)ptfunxored[k] | - (uint64_t)ptfunxored[k+1] << 8 | - (uint64_t)ptfunxored[k+2] << 16 | - (uint64_t)ptfunxored[k+3] << 24 | - (uint64_t)ptfunxored[k+4] << 32; - */ - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xfe\xff\xff\xff", 4)) { - break; - } - - k += 40; - - ridx = ptfunxored[k]; - ridx |= ptfunxored[k+1] << 8; - - struct mchunk mc = *(midichunks.begin()+ridx); - - wav_t w = { std::string(""), 0, 0, 0 }; - region_t r = { - midiregionname, - regionnumber++, - //(int64_t)mc.zero, - (int64_t)0xe8d4a51000ULL, - (int64_t)(0), - //(int64_t)(max_pos*sessionrate*60/(960000*120)), - (int64_t)mc.maxlen, - w, - mc.chunk, - }; - midiregions.push_back(r); - } - } - - // Put midi regions on midi tracks - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) { - return; - } - - nmiditracks = u_endian_read2(&ptfunxored[k-4], is_bigendian); - - for (tr = 0; tr < nmiditracks; tr++) { - char miditrackname[256]; - uint8_t namelen; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) { - return; - } - - namelen = ptfunxored[k+9]; - for (i = 0; i < namelen; i++) { - miditrackname[i] = ptfunxored[k+13+i]; - } - miditrackname[namelen] = '\0'; - k += 13 + namelen; - nregions = u_endian_read2(&ptfunxored[k], is_bigendian); - - for (i = 0; (i < nregions) && (k < len); i++) { - k += 24; - - ridx = u_endian_read2(&ptfunxored[k], is_bigendian); - - k += 5; - - region_pos = u_endian_read5(&ptfunxored[k], is_bigendian); - - k += 20; - - track_t mtr; - mtr.name = string(miditrackname); - mtr.index = tr; - mtr.playlist = 0; - // Find the midi region with index 'ridx' - std::vector::iterator begin = midiregions.begin(); - std::vector::iterator finish = midiregions.end(); - std::vector::iterator mregion; - wav_t w = { std::string(""), 0, 0, 0 }; - std::vector m; - region_t r = { std::string(""), ridx, 0, 0, 0, w, m}; - if ((mregion = std::find(begin, finish, r)) != finish) { - mtr.reg = *mregion; - mtr.reg.startpos = labs(region_pos - mtr.reg.startpos); - miditracks.push_back(mtr); - } - } - } -} - -void -PTFFormat::parsemidi12(void) { - uint32_t i, k; - uint64_t tr, n_midi_events, zero_ticks; - uint64_t midi_pos, midi_len, max_pos, region_pos; - uint8_t midi_velocity, midi_note; - uint16_t ridx; - uint16_t nmiditracks, regionnumber = 0; - uint32_t nregions, mr; - - std::vector midichunks; - midi_ev_t m; - - k = 0; - - // Parse all midi chunks, not 1:1 mapping to regions yet - while (k + 35 < len) { - max_pos = 0; - std::vector midi; - - // Find MdNLB - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"MdNLB", 5)) { - break; - } - - k += 11; - n_midi_events = u_endian_read4(&ptfunxored[k], is_bigendian); - - k += 4; - zero_ticks = u_endian_read5(&ptfunxored[k], is_bigendian); - for (i = 0; i < n_midi_events && k < len; i++, k += 35) { - midi_pos = u_endian_read5(&ptfunxored[k], is_bigendian); - midi_pos -= zero_ticks; - midi_note = ptfunxored[k+8]; - midi_len = u_endian_read5(&ptfunxored[k+9], is_bigendian); - midi_velocity = ptfunxored[k+17]; - - if (midi_pos + midi_len > max_pos) { - max_pos = midi_pos + midi_len; - } - - m.pos = midi_pos; - m.length = midi_len; - m.note = midi_note; - m.velocity = midi_velocity; -#if 1 -// stop gap measure to prevent crashes in ardour, -// remove when decryption is fully solved for .ptx - if ((m.velocity & 0x80) || (m.note & 0x80) || - (m.pos & 0xff00000000LL) || (m.length & 0xff00000000LL)) { - continue; - } -#endif - midi.push_back(m); - } - midichunks.push_back(mchunk (zero_ticks, max_pos, midi)); - } - - // Map midi chunks to regions - while (k < len) { - char midiregionname[256]; - uint8_t namelen; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"MdTEL", 5)) { - break; - } - - k += 41; - - nregions = u_endian_read2(&ptfunxored[k], is_bigendian); - - for (mr = 0; mr < nregions; mr++) { - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x01", 2)) { - break; - } - k += 18; - - namelen = ptfunxored[k]; - for (i = 0; i < namelen; i++) { - midiregionname[i] = ptfunxored[k+4+i]; - } - midiregionname[namelen] = '\0'; - k += 4 + namelen; - - k += 5; - /* - region_pos = (uint64_t)ptfunxored[k] | - (uint64_t)ptfunxored[k+1] << 8 | - (uint64_t)ptfunxored[k+2] << 16 | - (uint64_t)ptfunxored[k+3] << 24 | - (uint64_t)ptfunxored[k+4] << 32; - */ - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xfe\xff\x00\x00", 4)) { - break; - } - - k += 37; - - ridx = u_endian_read2(&ptfunxored[k], is_bigendian); - - k += 3; - struct mchunk mc = *(midichunks.begin()+ridx); - - wav_t w = { std::string(""), 0, 0, 0 }; - region_t r = { - midiregionname, - regionnumber++, - //(int64_t)mc.zero, - (int64_t)0xe8d4a51000ULL, - (int64_t)(0), - //(int64_t)(max_pos*sessionrate*60/(960000*120)), - (int64_t)mc.maxlen, - w, - mc.chunk, - }; - midiregions.push_back(r); - } - } - - // Put midi regions on midi tracks - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) { - return; - } - - nmiditracks = u_endian_read2(&ptfunxored[k-4], is_bigendian); - - for (tr = 0; tr < nmiditracks; tr++) { - char miditrackname[256]; - uint8_t namelen; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) { - return; - } - - namelen = ptfunxored[k+9]; - for (i = 0; i < namelen; i++) { - miditrackname[i] = ptfunxored[k+13+i]; - } - miditrackname[namelen] = '\0'; - k += 13 + namelen; - nregions = u_endian_read2(&ptfunxored[k], is_bigendian); - - k += 13; - - for (i = 0; (i < nregions) && (k < len); i++) { - while (k < len) { - if ( (ptfunxored[k] == 0x5a) && - (ptfunxored[k+1] & 0x08)) { - break; - } - k++; - } - k += 11; - - ridx = u_endian_read2(&ptfunxored[k], is_bigendian); - - k += 5; - - region_pos = u_endian_read5(&ptfunxored[k], is_bigendian); - - track_t mtr; - mtr.name = string(miditrackname); - mtr.index = tr; - mtr.playlist = 0; - // Find the midi region with index 'ridx' - std::vector::iterator begin = midiregions.begin(); - std::vector::iterator finish = midiregions.end(); - std::vector::iterator mregion; - wav_t w = { std::string(""), 0, 0, 0 }; - std::vector m; - region_t r = { std::string(""), ridx, 0, 0, 0, w, m}; - if ((mregion = std::find(begin, finish, r)) != finish) { - mtr.reg = *mregion; - mtr.reg.startpos = labs(region_pos - mtr.reg.startpos); - miditracks.push_back(mtr); - } - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff\xff\xff\xff\xff", 8)) { - return; - } - } - } -} - -void -PTFFormat::parseaudio(void) { - uint32_t i,j,k,l; - std::string wave; - - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"Audio Files", 11)) - return; - - // Find end of wav file list - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff", 4)) - return; - - // Find number of wave files - uint16_t numberofwavs; - j = k; - if (!jumpback(&j, ptfunxored, len, (const unsigned char *)"\x5a\x01", 2)) - return; - - numberofwavs = u_endian_read4(&ptfunxored[j-4], is_bigendian); - //printf("%d wavs\n", numberofwavs); - - // Find actual wav names - char wavname[256]; - j = k - 2; - for (i = 0; i < numberofwavs; i++) { - while (j > 0) { - if ( ((ptfunxored[j ] == 'W') || (ptfunxored[j ] == 'A') || ptfunxored[j ] == '\0') && - ((ptfunxored[j-1] == 'A') || (ptfunxored[j-1] == 'I') || ptfunxored[j-1] == '\0') && - ((ptfunxored[j-2] == 'V') || (ptfunxored[j-2] == 'F') || ptfunxored[j-2] == '\0')) { - break; - } - j--; - } - j -= 4; - l = 0; - while (ptfunxored[j] != '\0') { - wavname[l] = ptfunxored[j]; - l++; - j--; - } - wavname[l] = '\0'; - - // Must be at least "vaw.z\0" - if (l < 6) { - i--; - continue; - } - - // and skip "zWAVE" or "zAIFF" - if ( ( (wavname[1] == 'W') && - (wavname[2] == 'A') && - (wavname[3] == 'V') && - (wavname[4] == 'E')) || - ( (wavname[1] == 'A') && - (wavname[2] == 'I') && - (wavname[3] == 'F') && - (wavname[4] == 'F'))) { - wave = string(&wavname[5]); - } else { - wave = string(wavname); - } - //uint8_t playlist = ptfunxored[j-8]; - - std::reverse(wave.begin(), wave.end()); - wav_t f = { wave, (uint16_t)(numberofwavs - i - 1), 0, 0 }; - - if (foundin(wave, string("Audio Files")) || - foundin(wave, string("Fade Files"))) { - i--; - continue; - } - - actualwavs.push_back(f); - audiofiles.push_back(f); - - //printf(" %d:%s \n", numberofwavs - i - 1, wave.c_str()); - } - std::reverse(audiofiles.begin(), audiofiles.end()); - std::reverse(actualwavs.begin(), actualwavs.end()); - //resort(audiofiles); - //resort(actualwavs); - - // Jump to end of wav file list - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff", 4)) - return; - - // Loop through all the sources - for (vector::iterator w = audiofiles.begin(); w != audiofiles.end(); ++w) { - // Jump to start of source metadata for this source - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x07", 2)) - return; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) - return; - k++; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) - return; - - w->length = u_endian_read4(&ptfunxored[k-25], false); - } -} - -void -PTFFormat::parserest89(void) { - uint32_t i,j,k; - uint8_t startbytes = 0; - uint8_t lengthbytes = 0; - uint8_t offsetbytes = 0; - uint8_t somethingbytes = 0; - uint8_t skipbytes = 0; - - // Find Regions - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"Snap", 4)) { - return; - } - - uint16_t rindex = 0; - uint32_t findex = 0; - for (i = k; i < len-70; i++) { - if ( (ptfunxored[i ] == 0x5a) && - (ptfunxored[i+1] == 0x0a)) { - break; - } - if ( (ptfunxored[i ] == 0x5a) && - (ptfunxored[i+1] == 0x0c)) { - - uint8_t lengthofname = ptfunxored[i+9]; - - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[i+13+j]; - } - name[j] = '\0'; - j += i+13; - //uint8_t disabled = ptfunxored[j]; - - offsetbytes = (ptfunxored[j+1] & 0xf0) >> 4; - lengthbytes = (ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (ptfunxored[j+3] & 0xf0) >> 4; - somethingbytes = (ptfunxored[j+3] & 0xf); - skipbytes = ptfunxored[j+4]; - findex = ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +40]; - /*rindex = ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +24]; - */ - uint32_t sampleoffset = 0; - switch (offsetbytes) { - case 4: - sampleoffset = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - sampleoffset = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - sampleoffset = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - sampleoffset = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=offsetbytes; - uint32_t length = 0; - switch (lengthbytes) { - case 4: - length = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - length = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - length = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - length = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=lengthbytes; - uint32_t start = 0; - switch (startbytes) { - case 4: - start = u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - start = u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - start = (uint32_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - start = (uint32_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=startbytes; - /* - uint32_t something = 0; - switch (somethingbytes) { - case 4: - something |= (uint32_t)(ptfunxored[j+8] << 24); - case 3: - something |= (uint32_t)(ptfunxored[j+7] << 16); - case 2: - something |= (uint32_t)(ptfunxored[j+6] << 8); - case 1: - something |= (uint32_t)(ptfunxored[j+5]); - default: - break; - } - j+=somethingbytes; - */ - std::string filename = string(name); - wav_t f = { - filename, - (uint16_t)findex, - (int64_t)(start*ratefactor), - (int64_t)(length*ratefactor), - }; - - //printf("something=%d\n", something); - - vector::iterator begin = actualwavs.begin(); - vector::iterator finish = actualwavs.end(); - vector::iterator found; - // Add file to list only if it is an actual wav - if ((found = std::find(begin, finish, f)) != finish) { - f.filename = (*found).filename; - // Also add plain wav as region - std::vector m; - region_t r = { - name, - rindex, - (int64_t)(start*ratefactor), - (int64_t)(sampleoffset*ratefactor), - (int64_t)(length*ratefactor), - f, - m - }; - regions.push_back(r); - // Region only - } else { - if (foundin(filename, string(".grp"))) { - continue; - } - std::vector m; - region_t r = { - name, - rindex, - (int64_t)(start*ratefactor), - (int64_t)(sampleoffset*ratefactor), - (int64_t)(length*ratefactor), - f, - m - }; - regions.push_back(r); - } - rindex++; - } - } - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) { - return; - } - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) { - return; - } - k++; - - // Tracks - uint32_t offset; - uint32_t tracknumber = 0; - uint32_t regionspertrack = 0; - for (;k < len; k++) { - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] == 0x04)) { - break; - } - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] == 0x02)) { - - uint8_t lengthofname = 0; - lengthofname = ptfunxored[k+9]; - if (lengthofname == 0x5a) { - continue; - } - track_t tr; - - regionspertrack = (uint8_t)(ptfunxored[k+13+lengthofname]); - - //printf("regions/track=%d\n", regionspertrack); - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[j+k+13]; - } - name[j] = '\0'; - tr.name = string(name); - tr.index = tracknumber++; - - for (j = k; regionspertrack > 0 && j < len; j++) { - jumpto(&j, ptfunxored, len, (const unsigned char *)"\x5a\x07", 2); - tr.reg.index = (uint16_t)(ptfunxored[j+11] & 0xff) - | (uint16_t)((ptfunxored[j+12] << 8) & 0xff00); - vector::iterator begin = regions.begin(); - vector::iterator finish = regions.end(); - vector::iterator found; - if ((found = std::find(begin, finish, tr.reg)) != finish) { - tr.reg = (*found); - } - i = j+16; - offset = u_endian_read4(&ptfunxored[i], is_bigendian); - tr.reg.startpos = (int64_t)(offset*ratefactor); - if (tr.reg.length > 0) { - tracks.push_back(tr); - } - regionspertrack--; - } - } - } -} - -void -PTFFormat::parserest12(void) { - uint32_t i,j,k,l,m,n; - uint8_t startbytes = 0; - uint8_t lengthbytes = 0; - uint8_t offsetbytes = 0; - uint8_t somethingbytes = 0; - uint8_t skipbytes = 0; - uint32_t maxregions = 0; - uint32_t findex = 0; - uint32_t findex2 = 0; - uint32_t findex3 = 0; - uint16_t rindex = 0; - vector groups; - uint16_t groupcount, compoundcount, groupmax; - uint16_t gindex, gindex2; - - m = 0; - n = 0; - vector groupmap; - // Find region group total - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"Custom 1\0\0\x5a", 11)) - goto nocustom; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff", 4)) - return; - - if (!jumpback(&k, ptfunxored, len, (const unsigned char *)"\x5a", 1)) - return; - - jumpto(&k, ptfunxored, k+0x2000, (const unsigned char *)"\x5a\x03", 2); - k++; - - groupcount = 0; - for (i = k; i < len; i++) { - if (!jumpto(&i, ptfunxored, len, (const unsigned char *)"\x5a\x03", 2)) - break; - groupcount++; - } - verbose_printf("groupcount=%d\n", groupcount); - - // Find start of group names -> group indexes - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"Custom 1\0\0\x5a", 11)) - return; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff", 4)) - return; - - if (!jumpback(&k, ptfunxored, len, (const unsigned char *)"\x5a", 1)) - return; - k++; - - // Skip total number of groups - for (i = 0; i < groupcount; i++) { - while (k < len) { - if ( (ptfunxored[k ] == 0x5a) && - ((ptfunxored[k+1] == 0x03) || (ptfunxored[k+1] == 0x0a))) { - break; - } - k++; - } - k++; - } - - while (k < len) { - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] & 0x02)) { - break; - } - k++; - } - k++; - - while (k < len) { - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] & 0x02)) { - break; - } - k++; - } - k++; - - verbose_printf("start of groups k=0x%x\n", k); - // Loop over all groups and associate the compound index/name - for (i = 0; i < groupcount; i++) { - while (k < len) { - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] & 0x02)) { - break; - } - k++; - } - if (k > len) - break; - gindex = u_endian_read2(&ptfunxored[k+9], is_bigendian); - gindex2 = u_endian_read2(&ptfunxored[k+3], is_bigendian); - - uint8_t lengthofname = ptfunxored[k+13]; - char name[256] = {0}; - for (l = 0; l < lengthofname; l++) { - name[l] = ptfunxored[k+17+l]; - } - name[l] = '\0'; - - if (strlen(name) == 0) { - i--; - k++; - continue; - } - compound_t c = { - (uint16_t)i, // curr_index - gindex, // unknown1 - 0, // level - 0, // ontopof_index - gindex2, // next_index - string(name) - }; - groupmap.push_back(c); - k++; - } - - // Sort lookup table by group index - //std::sort(glookup.begin(), glookup.end(), regidx_compare); - - // print compounds as flattened tree - j = 0; - for (std::vector::iterator i = groupmap.begin(); i != groupmap.end(); ++i) { - verbose_printf("g(%u) uk(%u) ni(%u) %s\n", i->curr_index, i->unknown1, i->next_index, i->name.c_str()); - j++; - } - -nocustom: - // Find region groups - k = 0; - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"Snap", 4)) - return; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\x5a\x06", 2)) - return; - k++; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)) - return; - k++; - - if (!jumpto(&k, ptfunxored, len, (const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)) - return; - k++; - - // Hack to find actual start of region group information - while (k < len) { - if ((ptfunxored[k+13] == 0x5a) && (ptfunxored[k+14] & 0xf)) { - k += 13; - continue; - } else { - if ((ptfunxored[k+9] == 0x5a) && (ptfunxored[k+10] & 0xf)) { - k += 9; - continue; - } - } - if ((ptfunxored[k] == 0x5a) && (ptfunxored[k+1] & 0xf)) - break; - k++; - } - verbose_printf("hack region groups k=0x%x\n", k); - - compoundcount = 0; - j = k; - groupmax = groupcount == 0 ? 0 : u_endian_read2(&ptfunxored[j+3], is_bigendian); - groupcount = 0; - for (i = k; (groupcount < groupmax) && (i < len-70); i++) { - if ( (ptfunxored[i ] == 0x5a) && - (ptfunxored[i+1] == 0x03)) { - break; - } - if ( (ptfunxored[i ] == 0x5a) && - ((ptfunxored[i+1] == 0x01) || (ptfunxored[i+1] == 0x02))) { - - //findex = ptfunxored[i-48] | ptfunxored[i-47] << 8; - //rindex = ptfunxored[i+3] | ptfunxored[i+4] << 8; - - uint8_t lengthofname = ptfunxored[i+9]; - if (ptfunxored[i+13] == 0x5a) { - continue; - } - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[i+13+j]; - } - name[j] = '\0'; - j += i+13; - - offsetbytes = (ptfunxored[j+1] & 0xf0) >> 4; - lengthbytes = (ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (ptfunxored[j+3] & 0xf0) >> 4; - somethingbytes = (ptfunxored[j+3] & 0xf); - skipbytes = ptfunxored[j+4]; - uint16_t regionsingroup = ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +12] - | ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +13] << 8; - - findex = ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +37] - | ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +38] << 8; - - uint64_t sampleoffset = 0; - switch (offsetbytes) { - case 5: - sampleoffset = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - sampleoffset = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - sampleoffset = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - sampleoffset = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - sampleoffset = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=offsetbytes; - uint64_t length = 0; - switch (lengthbytes) { - case 5: - length = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - length = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - length = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - length = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - length = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=lengthbytes; - uint64_t start = 0; - switch (startbytes) { - case 5: - start = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - start = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - start = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - start = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - start = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=startbytes; - - if (offsetbytes == 5) - sampleoffset -= 1000000000000ULL; - if (startbytes == 5) - start -= 1000000000000ULL; - - std::string filename = string(name); - wav_t f = { - filename, - (uint16_t)findex, - (int64_t)(start*ratefactor), - (int64_t)(length*ratefactor), - }; - - if (strlen(name) == 0) { - continue; - } - if (length == 0) { - continue; - } - //if (foundin(filename, string(".grp")) && !regionsingroup && !findex) { - // // Empty region group - // verbose_printf(" EMPTY: %s\n", name); - // continue; - if (regionsingroup) { - // Active region grouping - // Iterate parsing all the regions in the group - verbose_printf("\nGROUP\t%d %s\n", groupcount, name); - m = j; - n = j+16; - - for (l = 0; l < regionsingroup; l++) { - if (!jumpto(&n, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) { - return; - } - n++; - } - n--; - //printf("n=0x%x\n", n+112); - //findex = ptfunxored[n+112] | (ptfunxored[n+113] << 8); - findex = u_endian_read2(&ptfunxored[i-11], is_bigendian); - findex2 = u_endian_read2(&ptfunxored[n+108], is_bigendian); - //findex2= rindex; //XXX - // Find wav with correct findex - vector::iterator wave = actualwavs.end(); - for (vector::iterator aw = actualwavs.begin(); - aw != actualwavs.end(); ++aw) { - if (aw->index == findex) { - wave = aw; - } - } - if (wave == actualwavs.end()) - return; - - if (!jumpto(&n, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) - return; - n += 37; - //rindex = ptfunxored[n] | (ptfunxored[n+1] << 8); - for (l = 0; l < regionsingroup; l++) { - if (!jumpto(&m, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) - return; - - m += 37; - rindex = u_endian_read2(&ptfunxored[m], is_bigendian); - - m += 12; - sampleoffset = 0; - switch (offsetbytes) { - case 5: - sampleoffset = u_endian_read5(&ptfunxored[m], false); - break; - case 4: - sampleoffset = (uint64_t)u_endian_read4(&ptfunxored[m], false); - break; - case 3: - sampleoffset = (uint64_t)u_endian_read3(&ptfunxored[m], false); - break; - case 2: - sampleoffset = (uint64_t)u_endian_read2(&ptfunxored[m], false); - break; - case 1: - sampleoffset = (uint64_t)(ptfunxored[m]); - break; - default: - break; - } - m+=offsetbytes+3; - start = 0; - switch (offsetbytes) { - case 5: - start = u_endian_read5(&ptfunxored[m], false); - break; - case 4: - start = (uint64_t)u_endian_read4(&ptfunxored[m], false); - break; - case 3: - start = (uint64_t)u_endian_read3(&ptfunxored[m], false); - break; - case 2: - start = (uint64_t)u_endian_read2(&ptfunxored[m], false); - break; - case 1: - start = (uint64_t)(ptfunxored[m]); - break; - default: - break; - } - m+=offsetbytes+3; - length = 0; - switch (lengthbytes) { - case 5: - length = u_endian_read5(&ptfunxored[m], false); - break; - case 4: - length = (uint64_t)u_endian_read4(&ptfunxored[m], false); - break; - case 3: - length = (uint64_t)u_endian_read3(&ptfunxored[m], false); - break; - case 2: - length = (uint64_t)u_endian_read2(&ptfunxored[m], false); - break; - case 1: - length = (uint64_t)(ptfunxored[m]); - break; - default: - break; - } - m+=8; - findex3 = ptfunxored[m] | (ptfunxored[m+1] << 8); - sampleoffset -= 1000000000000ULL; - start -= 1000000000000ULL; - - /* - // Find wav with correct findex - vector::iterator wave = actualwavs.end(); - for (vector::iterator aw = actualwavs.begin(); - aw != actualwavs.end(); ++aw) { - if (aw->index == (glookup.begin()+findex2)->startpos) { - wave = aw; - } - } - if (wave == actualwavs.end()) - return; - // findex is the true source - std::vector md; - region_t r = { - name, - (uint16_t)rindex, - (int64_t)findex, //(start*ratefactor), - (int64_t)findex2, //(sampleoffset*ratefactor), - (int64_t)findex3, //(length*ratefactor), - *wave, - md - }; - groups.push_back(r); - */ - vector::iterator g = groupmap.begin()+findex2; - if (g >= groupmap.end()) - continue; - compound_t c; - c.name = string(g->name); - c.curr_index = compoundcount; - c.level = findex; - c.ontopof_index = findex3; - c.next_index = g->next_index; - c.unknown1 = g->unknown1; - compounds.push_back(c); - verbose_printf("COMPOUND\tc(%d) %s (%d %d) -> c(%u) %s\n", c.curr_index, c.name.c_str(), c.level, c.ontopof_index, c.next_index, name); - compoundcount++; - } - groupcount++; - } - } - } - j = 0; - - // Start pure regions - k = m != 0 ? m : k - 1; - if (!jumpto(&k, ptfunxored, k+64, (const unsigned char *)"\x5a\x05", 2)) - jumpto(&k, ptfunxored, k+0x400, (const unsigned char *)"\x5a\x02", 2); - - verbose_printf("pure regions k=0x%x\n", k); - - maxregions = u_endian_read4(&ptfunxored[k-4], is_bigendian); - - verbose_printf("maxregions=%u\n", maxregions); - rindex = 0; - for (i = k; rindex < maxregions && i < len; i++) { - if ( (ptfunxored[i ] == 0xff) && - (ptfunxored[i+1] == 0x5a) && - (ptfunxored[i+2] == 0x01)) { - break; - } - //if ( (ptfunxored[i ] == 0x5a) && - // (ptfunxored[i+1] == 0x03)) { - // break; - //} - if ( (ptfunxored[i ] == 0x5a) && - ((ptfunxored[i+1] == 0x01) || (ptfunxored[i+1] == 0x02))) { - - //findex = ptfunxored[i-48] | ptfunxored[i-47] << 8; - //rindex = ptfunxored[i+3] | ptfunxored[i+4] << 8; - - uint8_t lengthofname = ptfunxored[i+9]; - if (ptfunxored[i+13] == 0x5a) { - continue; - } - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[i+13+j]; - } - name[j] = '\0'; - j += i+13; - - offsetbytes = (ptfunxored[j+1] & 0xf0) >> 4; - lengthbytes = (ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (ptfunxored[j+3] & 0xf0) >> 4; - somethingbytes = (ptfunxored[j+3] & 0xf); - skipbytes = ptfunxored[j+4]; - findex = ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +37] - | ptfunxored[j+5 - +startbytes - +lengthbytes - +offsetbytes - +somethingbytes - +skipbytes - +38] << 8; - - uint64_t sampleoffset = 0; - switch (offsetbytes) { - case 5: - sampleoffset = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - sampleoffset = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - sampleoffset = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - sampleoffset = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - sampleoffset = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=offsetbytes; - uint64_t length = 0; - switch (lengthbytes) { - case 5: - length = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - length = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - length = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - length = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - length = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=lengthbytes; - uint64_t start = 0; - switch (startbytes) { - case 5: - start = u_endian_read5(&ptfunxored[j+5], false); - break; - case 4: - start = (uint64_t)u_endian_read4(&ptfunxored[j+5], false); - break; - case 3: - start = (uint64_t)u_endian_read3(&ptfunxored[j+5], false); - break; - case 2: - start = (uint64_t)u_endian_read2(&ptfunxored[j+5], false); - break; - case 1: - start = (uint64_t)(ptfunxored[j+5]); - break; - default: - break; - } - j+=startbytes; - - if (offsetbytes == 5) - sampleoffset -= 1000000000000ULL; - if (startbytes == 5) - start -= 1000000000000ULL; - - std::string filename = string(name); - wav_t f = { - filename, - (uint16_t)findex, - (int64_t)(start*ratefactor), - (int64_t)(length*ratefactor), - }; - - if (strlen(name) == 0) { - continue; - } - if (length == 0) { - continue; - } - // Regular region mapping to a source - uint32_t n = j; - if (!jumpto(&n, ptfunxored, len, (const unsigned char *)"\x5a\x01", 2)) - return; - //printf("XXX=%d\n", ptfunxored[n+12] | ptfunxored[n+13]<<8); - - // Find wav with correct findex - vector::iterator wave = actualwavs.end(); - for (vector::iterator aw = actualwavs.begin(); - aw != actualwavs.end(); ++aw) { - if (aw->index == findex) { - wave = aw; - } - } - if (wave == actualwavs.end()) { - verbose_printf("missing source with findex\n"); - continue; - } - //verbose_printf("\n+r(%d) w(%d) REGION: %s st(%lx)x%u of(%lx)x%u ln(%lx)x%u\n", rindex, findex, name, start, startbytes, sampleoffset, offsetbytes, length, lengthbytes); - verbose_printf("REGION\tg(NA)\tr(%d)\tw(%d) %s(%s)\n", rindex, findex, name, wave->filename.c_str()); - std::vector md; - region_t r = { - name, - rindex, - (int64_t)(start*ratefactor), - (int64_t)(sampleoffset*ratefactor), - (int64_t)(length*ratefactor), - *wave, - md - }; - regions.push_back(r); - rindex++; - } - } - - // print compounds - vector rootnodes; - bool found = false; - - j = 0; - for (vector::iterator cmp = compounds.begin(); - cmp != compounds.end(); ++cmp) { - found = false; - for (vector::iterator tmp = compounds.begin(); - tmp != compounds.end(); ++tmp) { - if (tmp == cmp) - continue; - if (tmp->ontopof_index == cmp->curr_index) - found = true; - } - // Collect a vector of all the root nodes (no others point to) - if (!found) - rootnodes.push_back(cmp->curr_index); - } - - for (vector::iterator rt = rootnodes.begin(); - rt != rootnodes.end(); ++rt) { - vector::iterator cmp = compounds.begin()+(*rt); - // Now we are at a root node, follow to leaf - if (cmp >= compounds.end()) - continue; - - verbose_printf("----\n"); - - for (; cmp < compounds.end() && cmp->curr_index != cmp->next_index; - cmp = compounds.begin()+cmp->next_index) { - - // Find region - vector::iterator r = regions.end(); - for (vector::iterator rs = regions.begin(); - rs != regions.end(); rs++) { - if (rs->index == cmp->unknown1 + cmp->level) { - r = rs; - } - } - if (r == regions.end()) - continue; - verbose_printf("\t->cidx(%u) pl(%u)+ridx(%u) cflags(0x%x) ?(%u) grp(%s) reg(%s)\n", cmp->curr_index, cmp->level, cmp->unknown1, cmp->ontopof_index, cmp->next_index, cmp->name.c_str(), r->name.c_str()); - } - // Find region - vector::iterator r = regions.end(); - for (vector::iterator rs = regions.begin(); - rs != regions.end(); rs++) { - if (rs->index == cmp->unknown1 + cmp->level) { - r = rs; - } - } - if (r == regions.end()) - continue; - verbose_printf("\tLEAF->cidx(%u) pl(%u)+ridx(%u) cflags(0x%x) ?(%u) grp(%s) reg(%s)\n", cmp->curr_index, cmp->level, cmp->unknown1, cmp->ontopof_index, cmp->next_index, cmp->name.c_str(), r->name.c_str()); - } - - // Start grouped regions - - // Print region groups mapped to sources - for (vector::iterator a = groups.begin(); a != groups.end(); ++a) { - // Find wav with findex - vector::iterator wav = audiofiles.end(); - for (vector::iterator ws = audiofiles.begin(); - ws != audiofiles.end(); ws++) { - if (ws->index == a->startpos) { - wav = ws; - } - } - if (wav == audiofiles.end()) - continue; - - // Find wav with findex2 - vector::iterator wav2 = audiofiles.end(); - for (vector::iterator ws = audiofiles.begin(); - ws != audiofiles.end(); ws++) { - if (ws->index == a->sampleoffset) { - wav2 = ws; - } - } - if (wav2 == audiofiles.end()) - continue; - - verbose_printf("Group: %s -> %s OR %s\n", a->name.c_str(), wav->filename.c_str(), wav2->filename.c_str()); - } - - //filter(regions); - //resort(regions); - - // Tracks - uint32_t offset; - uint32_t tracknumber = 0; - uint32_t regionspertrack = 0; - - // Total tracks - j = k; - if (!jumpto(&j, ptfunxored, len, (const unsigned char *)"\x5a\x03\x00", 3)) - return; - //maxtracks = u_endian_read4(&ptfunxored[j-4]); - - // Jump to start of region -> track mappings - if (jumpto(&k, ptfunxored, k + regions.size() * 0x400, (const unsigned char *)"\x5a\x08", 2)) { - if (!jumpback(&k, ptfunxored, len, (const unsigned char *)"\x5a\x02", 2)) - return; - } else if (jumpto(&k, ptfunxored, k + regions.size() * 0x400, (const unsigned char *)"\x5a\x0a", 2)) { - if (!jumpback(&k, ptfunxored, len, (const unsigned char *)"\x5a\x01", 2)) - return; - } else { - return; - } - verbose_printf("tracks k=0x%x\n", k); - - for (;k < len; k++) { - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] & 0x04)) { - break; - } - if ( (ptfunxored[k ] == 0x5a) && - (ptfunxored[k+1] & 0x02)) { - - uint8_t lengthofname = 0; - lengthofname = ptfunxored[k+9]; - if (lengthofname == 0x5a) { - continue; - } - track_t tr; - - regionspertrack = (uint8_t)(ptfunxored[k+13+lengthofname]); - - //printf("regions/track=%d\n", regionspertrack); - char name[256] = {0}; - for (j = 0; j < lengthofname; j++) { - name[j] = ptfunxored[j+k+13]; - } - name[j] = '\0'; - tr.name = string(name); - tr.index = tracknumber++; - - for (j = k+18+lengthofname; regionspertrack > 0 && j < len; j++) { - jumpto(&j, ptfunxored, len, (const unsigned char *)"\x5a", 1); - bool isgroup = ptfunxored[j+27] > 0; - if (isgroup) { - tr.reg.name = string(""); - tr.reg.length = 0; - //tr.reg.index = 0xffff; - verbose_printf("TRACK: t(%d) g(%d) G(%s) -> T(%s)\n", - tracknumber, tr.reg.index, tr.reg.name.c_str(), tr.name.c_str()); - } else { - tr.reg.index = ((uint16_t)(ptfunxored[j+11]) & 0xff) - | (((uint16_t)(ptfunxored[j+12]) << 8) & 0xff00); - vector::iterator begin = regions.begin(); - vector::iterator finish = regions.end(); - vector::iterator found; - if ((found = std::find(begin, finish, tr.reg)) != finish) { - tr.reg = *found; - } - verbose_printf("TRACK: t(%d) r(%d) R(%s) -> T(%s)\n", - tracknumber, tr.reg.index, tr.reg.name.c_str(), tr.name.c_str()); - } - i = j+16; - offset = u_endian_read4(&ptfunxored[i], is_bigendian); - tr.reg.startpos = (int64_t)(offset*ratefactor); - if (tr.reg.length > 0) { - tracks.push_back(tr); - } - regionspertrack--; - - jumpto(&j, ptfunxored, len, (const unsigned char *)"\xff\xff\xff\xff\xff\xff\xff\xff", 8); - j += 12; - } - } - } -} diff --git a/libs/ptformat/ptformat.cc b/libs/ptformat/ptformat.cc new file mode 100644 index 0000000000..33717d9cf3 --- /dev/null +++ b/libs/ptformat/ptformat.cc @@ -0,0 +1,1327 @@ +/* + * libptformat - a library to read ProTools sessions + * + * Copyright (C) 2015-2019 Damien Zammit + * Copyright (C) 2015-2019 Robin Gareus + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_GLIB +# include +# define ptf_open g_fopen +#else +# define ptf_open fopen +#endif + +#include "ptformat/ptformat.h" + +#define BITCODE "0010111100101011" +#define ZMARK '\x5a' +#define ZERO_TICKS 0xe8d4a51000ULL +#define MAX_CONTENT_TYPE 0x3000 +#define MAX_CHANNELS_PER_TRACK 8 + +#if 0 +#define DEBUG +#endif + +#ifdef DEBUG +#define verbose_printf(...) printf("XXX PTFORMAT XXX: " __VA_ARGS__) +#else +#define verbose_printf(...) +#endif + +using namespace std; + +static void +hexdump(uint8_t *data, int length, int level) +{ + int i,j,k,end,step=16; + + for (i = 0; i < length; i += step) { + end = i + step; + if (end > length) end = length; + for (k = 0; k < level; k++) + printf(" "); + for (j = i; j < end; j++) { + printf("%02X ", data[j]); + } + for (j = i; j < end; j++) { + if (data[j] < 128 && data[j] > 32) + printf("%c", data[j]); + else + printf("."); + } + printf("\n"); + } +} + +PTFFormat::PTFFormat() + : _ptfunxored(0) + , _len(0) + , _sessionrate(0) + , _version(0) + , _product(NULL) + , _targetrate (0) + , _ratefactor (1.0) + , is_bigendian(false) +{ +} + +PTFFormat::~PTFFormat() { + cleanup(); +} + +const std::string +PTFFormat::get_content_description(uint16_t ctype) { + switch(ctype) { + case 0x0030: + return std::string("INFO product and version"); + case 0x1001: + return std::string("WAV samplerate, size"); + case 0x1003: + return std::string("WAV metadata"); + case 0x1004: + return std::string("WAV list full"); + case 0x1007: + return std::string("region name, number"); + case 0x1008: + return std::string("AUDIO region name, number (v5)"); + case 0x100b: + return std::string("AUDIO region list (v5)"); + case 0x100f: + return std::string("AUDIO region->track entry"); + case 0x1011: + return std::string("AUDIO region->track map entries"); + case 0x1012: + return std::string("AUDIO region->track full map"); + case 0x1014: + return std::string("AUDIO track name, number"); + case 0x1015: + return std::string("AUDIO tracks"); + case 0x1017: + return std::string("PLUGIN entry"); + case 0x1018: + return std::string("PLUGIN full list"); + case 0x1021: + return std::string("I/O channel entry"); + case 0x1022: + return std::string("I/O channel list"); + case 0x1028: + return std::string("INFO sample rate"); + case 0x103a: + return std::string("WAV names"); + case 0x104f: + return std::string("AUDIO region->track subentry (v8)"); + case 0x1050: + return std::string("AUDIO region->track entry (v8)"); + case 0x1052: + return std::string("AUDIO region->track map entries (v8)"); + case 0x1054: + return std::string("AUDIO region->track full map (v8)"); + case 0x1056: + return std::string("MIDI region->track entry"); + case 0x1057: + return std::string("MIDI region->track map entries"); + case 0x1058: + return std::string("MIDI region->track full map"); + case 0x2000: + return std::string("MIDI events block"); + case 0x2001: + return std::string("MIDI region name, number (v5)"); + case 0x2002: + return std::string("MIDI regions map (v5)"); + case 0x2067: + return std::string("INFO path of session"); + case 0x2511: + return std::string("Snaps block"); + case 0x2519: + return std::string("MIDI track full list"); + case 0x251a: + return std::string("MIDI track name, number"); + case 0x2523: + return std::string("COMPOUND region element"); + case 0x2602: + return std::string("I/O route"); + case 0x2603: + return std::string("I/O routing table"); + case 0x2628: + return std::string("COMPOUND region group"); + case 0x2629: + return std::string("AUDIO region name, number (v10)"); + case 0x262a: + return std::string("AUDIO region list (v10)"); + case 0x262c: + return std::string("COMPOUND region full map"); + case 0x2633: + return std::string("MIDI regions name, number (v10)"); + case 0x2634: + return std::string("MIDI regions map (v10)"); + case 0x271a: + return std::string("MARKER list"); + default: + return std::string("UNKNOWN content type"); + } +} + +static uint16_t +u_endian_read2(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint16_t)(buf[0]) << 8) | (uint16_t)(buf[1]); + } else { + return ((uint16_t)(buf[1]) << 8) | (uint16_t)(buf[0]); + } +} + +static uint32_t +u_endian_read3(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint32_t)(buf[0]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[2]); + } else { + return ((uint32_t)(buf[2]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[0]); + } +} + +static uint32_t +u_endian_read4(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint32_t)(buf[0]) << 24) | + ((uint32_t)(buf[1]) << 16) | + ((uint32_t)(buf[2]) << 8) | + (uint32_t)(buf[3]); + } else { + return ((uint32_t)(buf[3]) << 24) | + ((uint32_t)(buf[2]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[0]); + } +} + +static uint64_t +u_endian_read5(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint64_t)(buf[0]) << 32) | + ((uint64_t)(buf[1]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[3]) << 8) | + (uint64_t)(buf[4]); + } else { + return ((uint64_t)(buf[4]) << 32) | + ((uint64_t)(buf[3]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[1]) << 8) | + (uint64_t)(buf[0]); + } +} + +static uint64_t +u_endian_read8(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint64_t)(buf[0]) << 56) | + ((uint64_t)(buf[1]) << 48) | + ((uint64_t)(buf[2]) << 40) | + ((uint64_t)(buf[3]) << 32) | + ((uint64_t)(buf[4]) << 24) | + ((uint64_t)(buf[5]) << 16) | + ((uint64_t)(buf[6]) << 8) | + (uint64_t)(buf[7]); + } else { + return ((uint64_t)(buf[7]) << 56) | + ((uint64_t)(buf[6]) << 48) | + ((uint64_t)(buf[5]) << 40) | + ((uint64_t)(buf[4]) << 32) | + ((uint64_t)(buf[3]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[1]) << 8) | + (uint64_t)(buf[0]); + } +} + +void +PTFFormat::cleanup(void) { + _len = 0; + _sessionrate = 0; + _version = 0; + free(_ptfunxored); + _ptfunxored = NULL; + free (_product); + _product = NULL; + _audiofiles.clear(); + _regions.clear(); + _midiregions.clear(); + _tracks.clear(); + _miditracks.clear(); + free_all_blocks(); +} + +int64_t +PTFFormat::foundat(unsigned char *haystack, uint64_t n, const char *needle) { + int64_t found = 0; + uint64_t i, j, needle_n; + needle_n = strlen(needle); + + for (i = 0; i < n; i++) { + found = i; + for (j = 0; j < needle_n; j++) { + if (haystack[i+j] != needle[j]) { + found = -1; + break; + } + } + if (found > 0) + return found; + } + return -1; +} + +bool +PTFFormat::jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { + uint64_t i; + uint64_t k = *currpos; + while (k + needlelen < maxoffset) { + bool foundall = true; + for (i = 0; i < needlelen; i++) { + if (buf[k+i] != needle[i]) { + foundall = false; + break; + } + } + if (foundall) { + *currpos = k; + return true; + } + k++; + } + return false; +} + +bool +PTFFormat::jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { + uint64_t i; + uint64_t k = *currpos; + while (k > 0 && k + needlelen < maxoffset) { + bool foundall = true; + for (i = 0; i < needlelen; i++) { + if (buf[k+i] != needle[i]) { + foundall = false; + break; + } + } + if (foundall) { + *currpos = k; + return true; + } + k--; + } + return false; +} + +bool +PTFFormat::foundin(std::string const& haystack, std::string const& needle) { + size_t found = haystack.find(needle); + if (found != std::string::npos) { + return true; + } else { + return false; + } +} + +/* Return values: 0 success + -1 error decrypting pt session +*/ +int +PTFFormat::unxor(std::string const& path) { + FILE *fp; + unsigned char xxor[256]; + unsigned char ct; + uint64_t i; + uint8_t xor_type; + uint8_t xor_value; + uint8_t xor_delta; + uint16_t xor_len; + + if (! (fp = ptf_open(path.c_str(), "rb"))) { + return -1; + } + + fseek(fp, 0, SEEK_END); + _len = ftell(fp); + if (_len < 0x14) { + fclose(fp); + return -1; + } + + if (! (_ptfunxored = (unsigned char*) malloc(_len * sizeof(unsigned char)))) { + /* Silently fail -- out of memory*/ + fclose(fp); + _ptfunxored = 0; + return -1; + } + + /* The first 20 bytes are always unencrypted */ + fseek(fp, 0x00, SEEK_SET); + i = fread(_ptfunxored, 1, 0x14, fp); + if (i < 0x14) { + fclose(fp); + return -1; + } + + xor_type = _ptfunxored[0x12]; + xor_value = _ptfunxored[0x13]; + xor_len = 256; + + // xor_type 0x01 = ProTools 5, 6, 7, 8 and 9 + // xor_type 0x05 = ProTools 10, 11, 12 + switch(xor_type) { + case 0x01: + xor_delta = gen_xor_delta(xor_value, 53, false); + break; + case 0x05: + xor_delta = gen_xor_delta(xor_value, 11, true); + break; + default: + fclose(fp); + return -1; + } + + /* Generate the xor_key */ + for (i=0; i < xor_len; i++) + xxor[i] = (i * xor_delta) & 0xff; + + /* hexdump(xxor, xor_len); */ + + /* Read file and decrypt rest of file */ + i = 0x14; + fseek(fp, i, SEEK_SET); + while (fread(&ct, 1, 1, fp) != 0) { + uint8_t xor_index = (xor_type == 0x01) ? i & 0xff : (i >> 12) & 0xff; + _ptfunxored[i++] = ct ^ xxor[xor_index]; + } + fclose(fp); + return 0; +} + +/* Return values: 0 success + -1 error decrypting pt session + -2 error detecting pt session + -3 incompatible pt version + -4 error parsing pt session +*/ +int +PTFFormat::load(std::string const& ptf, int64_t targetsr) { + cleanup(); + _path = ptf; + + if (unxor(_path)) + return -1; + + if (parse_version()) + return -2; + + if (_version < 5 || _version > 12) + return -3; + + _targetrate = targetsr; + + int err = 0; + if ((err = parse())) { + printf ("PARSE FAILED %d\n", err); + return -4; + } + + return 0; +} + +bool +PTFFormat::parse_version() { + uint32_t seg_len,str_len; + uint8_t *data = _ptfunxored + 0x14; + uintptr_t data_end = ((uintptr_t)_ptfunxored) + 0x100; + uint8_t seg_type; + bool success = false; + + if (_ptfunxored[0] != '\x03' && foundat(_ptfunxored, 0x100, BITCODE) != 1) { + return false; + } + + while( ((uintptr_t)data < data_end) && (success == false) ) { + + if (data[0] != 0x5a) { + success = false; + break; + } + + seg_type = data[1]; + /* Skip segment header */ + data += 3; + if (data[0] == 0 && data[1] == 0) { + /* BE */ + is_bigendian = true; + } else { + /* LE */ + is_bigendian = false; + } + seg_len = u_endian_read4(&data[0], is_bigendian); + + /* Skip seg_len */ + data += 4; + if (!(seg_type == 0x04 || seg_type == 0x03) || data[0] != 0x03) { + /* Go to next segment */ + data += seg_len; + continue; + } + /* Skip 0x03 0x00 0x00 */ + data += 3; + seg_len -= 3; + str_len = (*(uint8_t *)data); + if (! (_product = (uint8_t *)malloc((str_len+1) * sizeof(uint8_t)))) { + success = false; + break; + } + + /* Skip str_len */ + data += 4; + seg_len -= 4; + + memcpy(_product, data, str_len); + _product[str_len] = 0; + data += str_len; + seg_len -= str_len; + + /* Skip 0x03 0x00 0x00 0x00 */ + data += 4; + seg_len -= 4; + + _version = data[0]; + if (_version == 0) { + _version = data[3]; + } + data += seg_len; + success = true; + } + + /* If the above does not work, try other heuristics */ + if ((uintptr_t)data >= data_end - seg_len) { + _version = _ptfunxored[0x40]; + if (_version == 0) { + _version = _ptfunxored[0x3d]; + } + if (_version == 0) { + _version = _ptfunxored[0x3a] + 2; + } + if (_version != 0) { + success = true; + } + } + return (!success); +} + +uint8_t +PTFFormat::gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative) { + uint16_t i; + for (i = 0; i < 256; i++) { + if (((i * mul) & 0xff) == xor_value) { + return (negative) ? i * (-1) : i; + } + } + // Should not occur + return 0; +} + +void +PTFFormat::setrates(void) { + _ratefactor = 1.f; + if (_sessionrate != 0) { + _ratefactor = (float)_targetrate / _sessionrate; + } +} + +bool +PTFFormat::parse_block_at(uint32_t pos, struct block_t *block, struct block_t *parent, int level) { + struct block_t b; + int childjump = 0; + uint32_t i; + uint32_t max = _len; + + if (_ptfunxored[pos] != ZMARK) + return false; + + if (parent) + max = parent->block_size + parent->offset; + + b.zmark = ZMARK; + b.block_type = u_endian_read2(&_ptfunxored[pos+1], is_bigendian); + b.block_size = u_endian_read4(&_ptfunxored[pos+3], is_bigendian); + b.content_type = u_endian_read2(&_ptfunxored[pos+7], is_bigendian); + b.offset = pos + 7; + + if (b.block_size + b.offset > max) + return false; + if (b.block_type & 0xff00) + return false; + + block->zmark = b.zmark; + block->block_type = b.block_type; + block->block_size = b.block_size; + block->content_type = b.content_type; + block->offset = b.offset; + block->child.clear(); + + for (i = 1; (i < block->block_size) && (pos + i + childjump < max); i += childjump ? childjump : 1) { + int p = pos + i; + struct block_t bchild; + childjump = 0; + if (parse_block_at(p, &bchild, block, level+1)) { + block->child.push_back(bchild); + childjump = bchild.block_size + 7; + } + } + return true; +} + +void +PTFFormat::dump_block(struct block_t& b, int level) +{ + int i; + + for (i = 0; i < level; i++) { + printf(" "); + } + printf("%s(0x%04x)\n", get_content_description(b.content_type).c_str(), b.content_type); + hexdump(&_ptfunxored[b.offset], b.block_size, level); + + for (vector::iterator c = b.child.begin(); + c != b.child.end(); ++c) { + dump_block(*c, level + 1); + } +} + +void +PTFFormat::free_block(struct block_t& b) +{ + for (vector::iterator c = b.child.begin(); + c != b.child.end(); ++c) { + free_block(*c); + } + + b.child.clear(); +} + +void +PTFFormat::free_all_blocks(void) +{ + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + free_block(*b); + } + + blocks.clear(); +} + +void +PTFFormat::dump(void) { + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + dump_block(*b, 0); + } +} + +void +PTFFormat::parseblocks(void) { + uint32_t i = 20; + + while (i < _len) { + struct block_t b; + if (parse_block_at(i, &b, NULL, 0)) { + blocks.push_back(b); + } + i += b.block_size ? b.block_size + 7 : 1; + } +} + +int +PTFFormat::parse(void) { + parseblocks(); +#ifdef DEBUG + dump(); +#endif + if (!parseheader()) + return -1; + setrates(); + if (_sessionrate < 44100 || _sessionrate > 192000) + return -2; + if (!parseaudio()) + return -3; + if (!parserest()) + return -4; + if (!parsemidi()) + return -5; + return 0; +} + +bool +PTFFormat::parseheader(void) { + bool found = false; + + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1028) { + _sessionrate = u_endian_read4(&_ptfunxored[b->offset+4], is_bigendian); + found = true; + } + } + return found; +} + +std::string +PTFFormat::parsestring (uint32_t pos) { + uint32_t length = u_endian_read4(&_ptfunxored[pos], is_bigendian); + pos += 4; + return std::string((const char *)&_ptfunxored[pos], length); +} + +bool +PTFFormat::parseaudio(void) { + bool found = false; + uint32_t nwavs, i, n; + uint32_t pos = 0; + std::string wavtype; + std::string wavname; + + // Parse wav names + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1004) { + + nwavs = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x103a) { + found = true; + //nstrings = u_endian_read4(&_ptfunxored[c->offset+1], is_bigendian); + pos = c->offset + 11; + // Found wav list + for (i = n = 0; (pos < c->offset + c->block_size) && (n < nwavs); i++) { + wavname = parsestring(pos); + pos += wavname.size() + 4; + wavtype = std::string((const char*)&_ptfunxored[pos], 4); + pos += 9; + if (foundin(wavname, std::string(".grp"))) + continue; + + if (foundin(wavname, std::string("Audio Files"))) { + continue; + } + if (foundin(wavname, std::string("Fade Files"))) { + continue; + } + if (_version < 10) { + if (!(foundin(wavtype, std::string("WAVE")) || + foundin(wavtype, std::string("EVAW")) || + foundin(wavtype, std::string("AIFF")) || + foundin(wavtype, std::string("FFIA"))) ) { + continue; + } + } else { + if (wavtype.size() != 0) { + if (!(foundin(wavtype, std::string("WAVE")) || + foundin(wavtype, std::string("EVAW")) || + foundin(wavtype, std::string("AIFF")) || + foundin(wavtype, std::string("FFIA"))) ) { + continue; + } + } else if (!(foundin(wavname, std::string(".wav")) || + foundin(wavname, std::string(".aif"))) ) { + continue; + } + } + wav_t f (n); + f.filename = wavname; + n++; + _audiofiles.push_back(f); + } + } + } + } + } + + // Add wav length information + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1004) { + + vector::iterator wav = _audiofiles.begin(); + + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1003) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1001) { + (*wav).length = u_endian_read8(&_ptfunxored[d->offset+8], is_bigendian); + wav++; + } + } + } + } + } + } + + return found; +} + + +void +PTFFormat::parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length) { + uint8_t offsetbytes, lengthbytes, startbytes; + + if (is_bigendian) { + offsetbytes = (_ptfunxored[j+4] & 0xf0) >> 4; + lengthbytes = (_ptfunxored[j+3] & 0xf0) >> 4; + startbytes = (_ptfunxored[j+2] & 0xf0) >> 4; + //somethingbytes = (_ptfunxored[j+2] & 0xf); + //skipbytes = _ptfunxored[j+1]; + } else { + offsetbytes = (_ptfunxored[j+1] & 0xf0) >> 4; //3 + lengthbytes = (_ptfunxored[j+2] & 0xf0) >> 4; + startbytes = (_ptfunxored[j+3] & 0xf0) >> 4; //1 + //somethingbytes = (_ptfunxored[j+3] & 0xf); + //skipbytes = _ptfunxored[j+4]; + } + + switch (offsetbytes) { + case 5: + offset = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + offset = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + offset = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + offset = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + offset = (uint64_t)(_ptfunxored[j+5]); + break; + default: + offset = 0; + break; + } + j+=offsetbytes; + switch (lengthbytes) { + case 5: + length = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + length = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + length = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + length = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + length = (uint64_t)(_ptfunxored[j+5]); + break; + default: + length = 0; + break; + } + j+=lengthbytes; + switch (startbytes) { + case 5: + start = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + start = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + start = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + start = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + start = (uint64_t)(_ptfunxored[j+5]); + break; + default: + start = 0; + break; + } +} + +void +PTFFormat::parse_region_info(uint32_t j, block_t& blk, region_t& r) { + uint64_t findex, start, sampleoffset, length; + + parse_three_point(j, start, sampleoffset, length); + + findex = u_endian_read4(&_ptfunxored[blk.offset + blk.block_size], is_bigendian); + wav_t f (findex); + f.posabsolute = start * _ratefactor; + f.length = length * _ratefactor; + + wav_t found; + if (find_wav(findex, found)) { + f.filename = found.filename; + } + + std::vector m; + r.startpos = (int64_t)(start*_ratefactor); + r.sampleoffset = (int64_t)(sampleoffset*_ratefactor); + r.length = (int64_t)(length*_ratefactor); + r.wave = f; + r.midi = m; +} + +bool +PTFFormat::parserest(void) { + uint32_t i, j, count; + uint64_t start; + uint16_t rindex, rawindex, tindex, mindex; + uint32_t nch; + uint16_t ch_map[MAX_CHANNELS_PER_TRACK]; + bool found = false; + std::string regionname, trackname, midiregionname; + rindex = 0; + + // Parse sources->regions + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x100b || b->content_type == 0x262a) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1008 || c->content_type == 0x2629) { + vector::iterator d = c->child.begin(); + region_t r; + + found = true; + j = c->offset + 11; + regionname = parsestring(j); + j += regionname.size() + 4; + + r.name = regionname; + r.index = rindex; + parse_region_info(j, *d, r); + + _regions.push_back(r); + rindex++; + } + } + found = true; + } + } + + // Parse tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1015) { + //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1014) { + j = c->offset + 2; + trackname = parsestring(j); + j += trackname.size() + 5; + nch = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4; + for (i = 0; i < nch; i++) { + ch_map[i] = u_endian_read2(&_ptfunxored[j], is_bigendian); + + track_t ti; + if (!find_track(ch_map[i], ti)) { + // Add a dummy region for now + region_t r (65535); + track_t t (ch_map[i]); + t.name = trackname; + t.reg = r; + _tracks.push_back(t); + } + //verbose_printf("%s : %d(%d)\n", reg, nch, ch_map[0]); + j += 2; + } + } + } + } + } + + // Reparse from scratch to exclude audio tracks from all tracks to get midi tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x2519) { + tindex = 0; + mindex = 0; + //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x251a) { + j = c->offset + 4; + trackname = parsestring(j); + j += trackname.size() + 4 + 18; + //tindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + + // Add a dummy region for now + region_t r (65535); + track_t t (mindex); + t.name = trackname; + t.reg = r; + + track_t ti; + // If the current track is not an audio track, insert as midi track + if (!(find_track(tindex, ti) && foundin(trackname, ti.name))) { + _miditracks.push_back(t); + mindex++; + } + tindex++; + } + } + } + } + + // Parse regions->tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + tindex = 0; + if (b->content_type == 0x1012) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1011) { + regionname = parsestring(c->offset + 2); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x100f) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x100e) { + // Region->track + track_t ti; + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + if (!find_track(count, ti)) + continue; + if (!find_region(rawindex, ti.reg)) + continue; + if (ti.reg.index != 65535) { + _tracks.push_back(ti); + } + } + } + } + } + found = true; + count++; + } + } + } else if (b->content_type == 0x1054) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1052) { + regionname = parsestring(c->offset + 2); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1050) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x104f) { + // Region->track + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4 + 1; + start = u_endian_read4(&_ptfunxored[j], is_bigendian); + tindex = count; + track_t ti; + if (!find_track(tindex, ti)) { + verbose_printf("dropped track %d\n", tindex); + continue; + } + if (!find_region(rawindex, ti.reg)) { + verbose_printf("dropped region %d\n", rawindex); + continue; + } + ti.reg.startpos = start; + if (ti.reg.index != 65535) { + _tracks.push_back(ti); + } + } + } + } + } + found = true; + count++; + } + } + } + } + for (std::vector::iterator tr = _tracks.begin(); + tr != _tracks.end(); /* noop */) { + if ((*tr).reg.index == 65535) { + tr = _tracks.erase(tr); + } else { + tr++; + } + } + return found; +} + +struct mchunk { + mchunk (uint64_t zt, uint64_t ml, std::vector const& c) + : zero (zt) + , maxlen (ml) + , chunk (c) + {} + uint64_t zero; + uint64_t maxlen; + std::vector chunk; +}; + +bool +PTFFormat::parsemidi(void) { + uint32_t i, j, k, n, rindex, tindex, mindex, count, rawindex; + uint64_t n_midi_events, zero_ticks, start, offset, length, start2, stop2; + uint64_t midi_pos, midi_len, max_pos, region_pos; + uint8_t midi_velocity, midi_note; + uint16_t regionnumber = 0; + std::string midiregionname; + + std::vector midichunks; + midi_ev_t m; + + std::string regionname, trackname; + rindex = 0; + + // Parse MIDI events + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x2000) { + + k = b->offset; + + // Parse all midi chunks, not 1:1 mapping to regions yet + while (k + 35 < b->block_size + b->offset) { + max_pos = 0; + std::vector midi; + + if (!jumpto(&k, _ptfunxored, _len, (const unsigned char *)"MdNLB", 5)) { + break; + } + k += 11; + n_midi_events = u_endian_read4(&_ptfunxored[k], is_bigendian); + + k += 4; + zero_ticks = u_endian_read5(&_ptfunxored[k], is_bigendian); + for (i = 0; i < n_midi_events && k < _len; i++, k += 35) { + midi_pos = u_endian_read5(&_ptfunxored[k], is_bigendian); + midi_pos -= zero_ticks; + midi_note = _ptfunxored[k+8]; + midi_len = u_endian_read5(&_ptfunxored[k+9], is_bigendian); + midi_velocity = _ptfunxored[k+17]; + + if (midi_pos + midi_len > max_pos) { + max_pos = midi_pos + midi_len; + } + + m.pos = midi_pos; + m.length = midi_len; + m.note = midi_note; + m.velocity = midi_velocity; + midi.push_back(m); + } + midichunks.push_back(mchunk (zero_ticks, max_pos, midi)); + } + + // Put chunks onto regions + } else if ((b->content_type == 0x2002) || (b->content_type == 0x2634)) { + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if ((c->content_type == 0x2001) || (c->content_type == 0x2633)) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if ((d->content_type == 0x1007) || (d->content_type == 0x2628)) { + j = d->offset + 2; + midiregionname = parsestring(j); + j += 4 + midiregionname.size(); + parse_three_point(j, region_pos, zero_ticks, midi_len); + j = d->offset + d->block_size; + rindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + struct mchunk mc = *(midichunks.begin()+rindex); + + region_t r (regionnumber++); + r.name = midiregionname; + r.startpos = (int64_t)0xe8d4a51000ULL; + r.sampleoffset = 0; + r.length = mc.maxlen; + r.midi = mc.chunk; + + _midiregions.push_back(r); + //verbose_printf("MIDI %s : r(%d) (%llu, %llu, %llu)\n", str, rindex, zero_ticks, region_pos, midi_len); + //dump_block(*d, 1); + } + } + } + } + } + } + + // COMPOUND MIDI regions + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x262c) { + mindex = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x262b) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x2628) { + count = 0; + j = d->offset + 2; + regionname = parsestring(j); + j += 4 + regionname.size(); + parse_three_point(j, start, offset, length); + j = d->offset + d->block_size + 2; + n = u_endian_read2(&_ptfunxored[j], is_bigendian); + + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x2523) { + // FIXME Compound MIDI region + j = e->offset + 39; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 12; + start2 = u_endian_read5(&_ptfunxored[j], is_bigendian); + int64_t signedval = (int64_t)start2; + signedval -= ZERO_TICKS; + if (signedval < 0) { + signedval = -signedval; + } + start2 = signedval; + j += 8; + stop2 = u_endian_read5(&_ptfunxored[j], is_bigendian); + signedval = (int64_t)stop2; + signedval -= ZERO_TICKS; + if (signedval < 0) { + signedval = -signedval; + } + stop2 = signedval; + j += 16; + //nn = u_endian_read4(&_ptfunxored[j], is_bigendian); + //verbose_printf("COMPOUND %s : c(%d) r(%d) ?(%d) ?(%d) (%llu %llu)(%llu %llu %llu)\n", str, mindex, rawindex, n, nn, start2, stop2, start, offset, length); + count++; + } + } + if (!count) { + // Plain MIDI region + struct mchunk mc = *(midichunks.begin()+n); + + region_t r (n); + r.name = midiregionname; + r.startpos = (int64_t)0xe8d4a51000ULL; + r.length = mc.maxlen; + r.midi = mc.chunk; + _midiregions.push_back(r); + verbose_printf("%s : MIDI region mr(%d) ?(%d) (%llu %llu %llu)\n", str, mindex, n, start, offset, length); + mindex++; + } + } + } + } + } + } + } + + // Put midi regions onto midi tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1058) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1057) { + regionname = parsestring(c->offset + 2); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1056) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x104f) { + // MIDI region->MIDI track + track_t ti; + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4 + 1; + start = u_endian_read5(&_ptfunxored[j], is_bigendian); + tindex = count; + if (!find_miditrack(tindex, ti)) { + verbose_printf("dropped midi t(%d) r(%d)\n", tindex, rawindex); + continue; + } + if (!find_midiregion(rawindex, ti.reg)) { + verbose_printf("dropped midiregion\n"); + continue; + } + //verbose_printf("MIDI : %s : t(%d) r(%d) %llu(%llu)\n", ti.name.c_str(), tindex, rawindex, start, ti.reg.startpos); + int64_t signedstart = (int64_t)(start - ZERO_TICKS); + if (signedstart < 0) + signedstart = -signedstart; + ti.reg.startpos = (uint64_t)signedstart; + if (ti.reg.index != 65535) { + _miditracks.push_back(ti); + } + } + } + } + } + count++; + } + } + } + } + for (std::vector::iterator tr = _miditracks.begin(); + tr != _miditracks.end(); /* noop */) { + if ((*tr).reg.index == 65535) { + tr = _miditracks.erase(tr); + } else { + tr++; + } + } + return true; +} diff --git a/libs/ptformat/ptformat/ptfformat.h b/libs/ptformat/ptformat/ptfformat.h deleted file mode 100644 index 72dc64c715..0000000000 --- a/libs/ptformat/ptformat/ptfformat.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - * libptformat - a library to read ProTools sessions - * - * Copyright (C) 2015 Damien Zammit - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -#ifndef PTFFORMAT_H -#define PTFFORMAT_H - -#include -#include -#include -#include -#include -#include "ptformat/visibility.h" - -class LIBPTFORMAT_API PTFFormat { -public: - PTFFormat(); - ~PTFFormat(); - - /* Return values: 0 success - -1 could not parse pt session - */ - int load(std::string path, int64_t targetsr); - - /* Return values: 0 success - -1 could not decrypt pt session - */ - int unxor(std::string path); - - struct wav_t { - std::string filename; - uint16_t index; - - int64_t posabsolute; - int64_t length; - - bool operator <(const struct wav_t& other) const { - return (strcasecmp(this->filename.c_str(), - other.filename.c_str()) < 0); - } - - bool operator ==(const struct wav_t& other) const { - return (this->filename == other.filename || - this->index == other.index); - } - - }; - - struct midi_ev_t { - uint64_t pos; - uint64_t length; - uint8_t note; - uint8_t velocity; - }; - - typedef struct region { - std::string name; - uint16_t index; - int64_t startpos; - int64_t sampleoffset; - int64_t length; - wav_t wave; - std::vector midi; - - bool operator ==(const struct region& other) { - return (this->index == other.index); - } - - bool operator <(const struct region& other) const { - return (strcasecmp(this->name.c_str(), - other.name.c_str()) < 0); - } - } region_t; - - typedef struct compound { - uint16_t curr_index; - uint16_t unknown1; - uint16_t level; - uint16_t ontopof_index; - uint16_t next_index; - std::string name; - } compound_t; - - typedef struct track { - std::string name; - uint16_t index; - uint8_t playlist; - region_t reg; - - bool operator ==(const struct track& other) { - return (this->name == other.name); - } - } track_t; - - std::vector audiofiles; - std::vector regions; - std::vector midiregions; - std::vector compounds; - std::vector tracks; - std::vector miditracks; - - static bool regionexistsin(std::vector reg, uint16_t index) { - std::vector::iterator begin = reg.begin(); - std::vector::iterator finish = reg.end(); - std::vector::iterator found; - - wav_t w = { std::string(""), 0, 0, 0 }; - std::vector m; - region_t r = { std::string(""), index, 0, 0, 0, w, m}; - - if ((found = std::find(begin, finish, r)) != finish) { - return true; - } - return false; - } - - static bool wavexistsin(std::vector wv, uint16_t index) { - std::vector::iterator begin = wv.begin(); - std::vector::iterator finish = wv.end(); - std::vector::iterator found; - - wav_t w = { std::string(""), index, 0, 0 }; - - if ((found = std::find(begin, finish, w)) != finish) { - return true; - } - return false; - } - - int64_t sessionrate; - int64_t targetrate; - uint8_t version; - uint8_t *product; - std::string path; - - unsigned char c0; - unsigned char c1; - unsigned char *ptfunxored; - uint64_t len; - bool is_bigendian; - -private: - bool jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); - bool jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); - bool foundin(std::string haystack, std::string needle); - int64_t foundat(unsigned char *haystack, uint64_t n, const char *needle); - uint16_t u_endian_read2(unsigned char *buf, bool); - uint32_t u_endian_read3(unsigned char *buf, bool); - uint32_t u_endian_read4(unsigned char *buf, bool); - uint64_t u_endian_read5(unsigned char *buf, bool); - - int parse(void); - bool parse_version(); - uint8_t gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative); - void setrates(void); - void cleanup(void); - void parse5header(void); - void parse7header(void); - void parse8header(void); - void parse9header(void); - void parse10header(void); - void parserest5(void); - void parserest89(void); - void parserest12(void); - void parseaudio5(void); - void parseaudio(void); - void parsemidi(void); - void parsemidi12(void); - void resort(std::vector& ws); - void resort(std::vector& rs); - void filter(std::vector& rs); - std::vector actualwavs; - float ratefactor; - std::string extension; - uint32_t upto; -}; - - -#endif diff --git a/libs/ptformat/ptformat/ptformat.h b/libs/ptformat/ptformat/ptformat.h new file mode 100644 index 0000000000..3426d7e05b --- /dev/null +++ b/libs/ptformat/ptformat/ptformat.h @@ -0,0 +1,277 @@ +/* + * libptformat - a library to read ProTools sessions + * + * Copyright (C) 2015-2019 Damien Zammit + * Copyright (C) 2015-2019 Robin Gareus + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef PTFFORMAT_H +#define PTFFORMAT_H + +#include +#include +#include +#include +#include +#include "ptformat/visibility.h" + +class LIBPTFORMAT_API PTFFormat { +public: + PTFFormat(); + ~PTFFormat(); + + /* Return values: 0 success + -1 error decrypting pt session + -2 error detecting pt session + -3 incompatible pt version + -4 error parsing pt session + */ + int load(std::string const& path, int64_t targetsr); + + /* Return values: 0 success + -1 error decrypting pt session + */ + int unxor(std::string const& path); + + struct wav_t { + std::string filename; + uint16_t index; + + int64_t posabsolute; + int64_t length; + + bool operator <(const struct wav_t& other) const { + return (strcasecmp(this->filename.c_str(), + other.filename.c_str()) < 0); + } + + bool operator ==(const struct wav_t& other) const { + return (this->filename == other.filename || + this->index == other.index); + } + + wav_t (uint16_t idx = 0) : index (idx), posabsolute (0), length (0) {} + }; + + struct midi_ev_t { + uint64_t pos; + uint64_t length; + uint8_t note; + uint8_t velocity; + midi_ev_t () : pos (0), length (0), note (0), velocity (0) {} + }; + + struct region_t { + std::string name; + uint16_t index; + int64_t startpos; + int64_t sampleoffset; + int64_t length; + wav_t wave; + std::vector midi; + + bool operator ==(const region_t& other) const { + return (this->index == other.index); + } + + bool operator <(const region_t& other) const { + return (strcasecmp(this->name.c_str(), + other.name.c_str()) < 0); + } + region_t (uint16_t idx = 0) : index (idx), startpos (0), sampleoffset (0), length (0) {} + }; + + struct track_t { + std::string name; + uint16_t index; + uint8_t playlist; + region_t reg; + + bool operator <(const track_t& other) const { + return (this->index < other.index); + } + + bool operator ==(const track_t& other) const { + return (this->index == other.index); + } + track_t (uint16_t idx = 0) : index (idx), playlist (0) {} + }; + + bool find_track(uint16_t index, track_t& tt) const { + std::vector::const_iterator begin = _tracks.begin(); + std::vector::const_iterator finish = _tracks.end(); + std::vector::const_iterator found; + + track_t t (index); + + if ((found = std::find(begin, finish, t)) != finish) { + tt = *found; + return true; + } + return false; + } + + bool find_region(uint16_t index, region_t& rr) const { + std::vector::const_iterator begin = _regions.begin(); + std::vector::const_iterator finish = _regions.end(); + std::vector::const_iterator found; + + region_t r; + r.index = index; + + if ((found = std::find(begin, finish, r)) != finish) { + rr = *found; + return true; + } + return false; + } + + bool find_miditrack(uint16_t index, track_t& tt) const { + std::vector::const_iterator begin = _miditracks.begin(); + std::vector::const_iterator finish = _miditracks.end(); + std::vector::const_iterator found; + + track_t t (index); + + if ((found = std::find(begin, finish, t)) != finish) { + tt = *found; + return true; + } + return false; + } + + bool find_midiregion(uint16_t index, region_t& rr) const { + std::vector::const_iterator begin = _midiregions.begin(); + std::vector::const_iterator finish = _midiregions.end(); + std::vector::const_iterator found; + + region_t r (index); + + if ((found = std::find(begin, finish, r)) != finish) { + rr = *found; + return true; + } + return false; + } + + bool find_wav(uint16_t index, wav_t& ww) const { + std::vector::const_iterator begin = _audiofiles.begin(); + std::vector::const_iterator finish = _audiofiles.end(); + std::vector::const_iterator found; + + wav_t w (index); + + if ((found = std::find(begin, finish, w)) != finish) { + ww = *found; + return true; + } + return false; + } + + static bool regionexistsin(std::vector const& reg, uint16_t index) { + std::vector::const_iterator begin = reg.begin(); + std::vector::const_iterator finish = reg.end(); + + region_t r (index); + + if (std::find(begin, finish, r) != finish) { + return true; + } + return false; + } + + static bool wavexistsin (std::vector const& wv, uint16_t index) { + std::vector::const_iterator begin = wv.begin(); + std::vector::const_iterator finish = wv.end(); + + wav_t w (index); + + if (std::find(begin, finish, w) != finish) { + return true; + } + return false; + } + + uint8_t version () const { return _version; } + int64_t sessionrate () const { return _sessionrate ; } + const std::string& path () { return _path; } + + const std::vector& audiofiles () const { return _audiofiles ; } + const std::vector& regions () const { return _regions ; } + const std::vector& midiregions () const { return _midiregions ; } + const std::vector& tracks () const { return _tracks ; } + const std::vector& miditracks () const { return _miditracks ; } + + const unsigned char* unxored_data () const { return _ptfunxored; } + uint64_t unxored_size () const { return _len; } + +private: + + std::vector _audiofiles; + std::vector _regions; + std::vector _midiregions; + std::vector _tracks; + std::vector _miditracks; + + std::string _path; + + unsigned char* _ptfunxored; + uint64_t _len; + int64_t _sessionrate; + uint8_t _version; + uint8_t* _product; + int64_t _targetrate; + float _ratefactor; + bool is_bigendian; + + struct block_t { + uint8_t zmark; // 'Z' + uint16_t block_type; // type of block + uint32_t block_size; // size of block + uint16_t content_type; // type of content + uint32_t offset; // offset in file + std::vector child; // vector of child blocks + }; + std::vector blocks; + + bool jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); + bool jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); + bool foundin(std::string const& haystack, std::string const& needle); + int64_t foundat(unsigned char *haystack, uint64_t n, const char *needle); + + std::string parsestring(uint32_t pos); + const std::string get_content_description(uint16_t ctype); + int parse(void); + void parseblocks(void); + bool parseheader(void); + bool parserest(void); + bool parseaudio(void); + bool parsemidi(void); + void dump(void); + bool parse_block_at(uint32_t pos, struct block_t *b, struct block_t *parent, int level); + void dump_block(struct block_t& b, int level); + bool parse_version(); + void parse_region_info(uint32_t j, block_t& blk, region_t& r); + void parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length); + uint8_t gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative); + void setrates(void); + void cleanup(void); + void free_block(struct block_t& b); + void free_all_blocks(void); +}; + +#endif diff --git a/libs/ptformat/wscript b/libs/ptformat/wscript index b638e92558..0b26ade2fb 100644 --- a/libs/ptformat/wscript +++ b/libs/ptformat/wscript @@ -30,10 +30,10 @@ def configure(conf): def build(bld): # Library if bld.is_defined ('INTERNAL_SHARED_LIBS'): - obj = bld.shlib (features = 'cxx cxxshlib', source = [ 'ptfformat.cc' ]) + obj = bld.shlib (features = 'cxx cxxshlib', source = [ 'ptformat.cc' ]) obj.defines = [ 'LIBPTFORMAT_DLL_EXPORTS=1' ] else: - obj = bld.stdlib (source = [ 'ptfformat.cc' ]) + obj = bld.stdlib (source = [ 'ptformat.cc' ]) obj.cxxflags = [ bld.env['compiler_flags_dict']['pic'] ] obj.cflags = [ bld.env['compiler_flags_dict']['pic'] ] diff --git a/scripts/_dump_playlists.lua b/scripts/_dump_playlists.lua index a6bc23661a..656a5ead97 100644 --- a/scripts/_dump_playlists.lua +++ b/scripts/_dump_playlists.lua @@ -21,7 +21,11 @@ function factory () return function () for r in Session:get_tracks():iter() do print ("*", r:name()) for p in Session:playlists():playlists_for_track (r:to_track()):iter() do - print (" -", p:name(), p:n_regions()) + if (p == r:to_track():playlist()) then + print (" >-", p:name(), p:n_regions()) + else + print (" -", p:name(), p:n_regions()) + end end end end end diff --git a/scripts/_sort_tracks_by_name.lua b/scripts/_sort_tracks_by_name.lua new file mode 100644 index 0000000000..36dfdc970c --- /dev/null +++ b/scripts/_sort_tracks_by_name.lua @@ -0,0 +1,36 @@ +ardour { + ["type"] = "EditorAction", + name = "Track Sort", + author = "Ardour Lua Taskforce", + description = [[Sort tracks alphabetically by name]] +} + +function factory () return function () + + -- sort compare function + -- a,b here are http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route + -- return true if route "a" should be ordered before route "b" + function tsort (a, b) + return a:name() < b:name() + end + + -- create a sortable list of tracks + local tracklist = {} + for t in Session:get_tracks():iter() do + table.insert(tracklist, t) + end + + -- sort the list using the compare function + table.sort(tracklist, tsort) + + -- traverse the sorted list and assign "presentation-order" to each track + local pos = 1; + for _, t in ipairs(tracklist) do + t:set_presentation_order(pos) + pos = pos + 1 + end + + -- drop all track references + tracklist = nil + collectgarbage () +end end diff --git a/session_utils/copy-mixer.cc b/session_utils/copy-mixer.cc index 88ed8a0f20..660b8a59e0 100644 --- a/session_utils/copy-mixer.cc +++ b/session_utils/copy-mixer.cc @@ -290,7 +290,7 @@ int main (int argc, char* argv[]) break; case 'h': - usage (0); + usage (EXIT_SUCCESS); break; case 'l': @@ -304,7 +304,7 @@ int main (int argc, char* argv[]) case 'V': printf ("ardour-utils version %s\n\n", VERSIONSTRING); printf ("Copyright (C) GPL 2016 Robin Gareus \n"); - exit (0); + exit (EXIT_SUCCESS); break; case 'v': @@ -330,19 +330,19 @@ int main (int argc, char* argv[]) if (!ends_with (src, statefile_suffix)) { fprintf (stderr, "source is not a .ardour session file.\n"); - exit (1); + exit (EXIT_FAILURE); } if (!ends_with (dst, statefile_suffix)) { fprintf (stderr, "target is not a .ardour session file.\n"); - exit (1); + exit (EXIT_FAILURE); } if (!Glib::file_test (src, Glib::FILE_TEST_IS_REGULAR)) { fprintf (stderr, "source is not a regular file.\n"); - exit (1); + exit (EXIT_FAILURE); } if (!Glib::file_test (dst, Glib::FILE_TEST_IS_REGULAR)) { fprintf (stderr, "target is not a regular file.\n"); - exit (1); + exit (EXIT_FAILURE); } std::string src_path = Glib::path_get_dirname (src); diff --git a/session_utils/export.cc b/session_utils/export.cc index 4531595abf..2f63d14ac5 100644 --- a/session_utils/export.cc +++ b/session_utils/export.cc @@ -287,11 +287,11 @@ int main (int argc, char* argv[]) case 'V': printf ("ardour-utils version %s\n\n", VERSIONSTRING); printf ("Copyright (C) GPL 2015,2017 Robin Gareus \n"); - exit (0); + exit (EXIT_SUCCESS); break; case 'h': - usage (0); + usage (EXIT_SUCCESS); break; default: diff --git a/session_utils/fix_bbtppq.cc b/session_utils/fix_bbtppq.cc index 952202856e..d8a04a120c 100644 --- a/session_utils/fix_bbtppq.cc +++ b/session_utils/fix_bbtppq.cc @@ -416,18 +416,18 @@ int main (int argc, char* argv[]) case 'o': outfile = optarg; if (outfile.empty()) { - usage (0); + usage (EXIT_SUCCESS); } break; case 'V': printf ("ardour-utils version %s\n\n", VERSIONSTRING); printf ("Copyright (C) GPL 2015 Robin Gareus \n"); - exit (0); + exit (EXIT_SUCCESS); break; case 'h': - usage (0); + usage (EXIT_SUCCESS); break; default: diff --git a/session_utils/new_empty_session.cc b/session_utils/new_empty_session.cc index 6dc0151d0b..b44c32fc20 100644 --- a/session_utils/new_empty_session.cc +++ b/session_utils/new_empty_session.cc @@ -70,11 +70,11 @@ int main (int argc, char* argv[]) case 'V': printf ("ardour-utils version %s\n\n", VERSIONSTRING); printf ("Copyright (C) GPL 2017 Robin Gareus \n"); - exit (0); + exit (EXIT_SUCCESS); break; case 'h': - usage (0); + usage (EXIT_SUCCESS); break; default: diff --git a/session_utils/new_session.cc b/session_utils/new_session.cc new file mode 100644 index 0000000000..2c03bc1850 --- /dev/null +++ b/session_utils/new_session.cc @@ -0,0 +1,242 @@ +#include +#include +#include + +#include + +#include "ardour/audioengine.h" +#include "ardour/filename_extensions.h" +#include "ardour/template_utils.h" + +#include "common.h" + +using namespace std; +using namespace ARDOUR; +using namespace SessionUtils; + +static void +usage (int status) +{ + // help2man compatible format (standard GNU help-text) + printf (UTILNAME " - create a new session from the commandline.\n\n"); + printf ("Usage: " UTILNAME " [ OPTIONS ] [session-name]\n\n"); + printf ("Options:\n\ + -L, --list-templates List available templates and exit\n\ + -h, --help Display this help and exit\n\ + -m, --master-channels Master-bus channel count (default 2)\n\ + -s, --samplerate Samplerate to use (default 48000)\n\ + -t, --template