Merge branch 'master' into mixer-snapshots

This commit is contained in:
Nikolaus Gullotta 2019-07-22 09:37:33 -05:00
parent db09fe1eb8
commit ad46ad409a
75 changed files with 3042 additions and 3483 deletions

View File

@ -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);
}
}

View File

@ -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

View File

@ -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:

View File

@ -61,7 +61,7 @@
#include "selection_memento.h"
#include "tempo_curve.h"
#include "ptformat/ptfformat.h"
#include "ptformat/ptformat.h"
namespace Gtkmm2ext {
class Bindings;

View File

@ -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"

View File

@ -19,6 +19,9 @@
*/
#include <algorithm>
#include <sstream>
#include <gtkmm/menu.h>
#include "pbd/convert.h"
@ -29,8 +32,6 @@
#include "ardour/route.h"
#include "ardour/session.h"
#include <sstream>
#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> route = row[track_cols.route];
if (boost::dynamic_pointer_cast<Track> (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> route = row[track_cols.route];
if (!boost::dynamic_pointer_cast<Track> (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;
}
}

View File

@ -43,6 +43,8 @@
#include <gtkmm/treemodel.h>
#include <gtkmm/treeview.h>
#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 ();

View File

@ -1117,3 +1117,9 @@ GainMeter::route_active_changed ()
meter_configuration_changed (_meter->input_streams ());
}
}
void
GainMeter::redraw_metrics ()
{
GainMeterBase::redraw_metrics ();
}

View File

@ -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<Gtk::Style>&);
void redraw_metrics ();
private:

View File

@ -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<bool> 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<bool> 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 ();
}

View File

@ -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<PBD::IgnorableControllable> ignored;
bool _ignore_change;
Gtk::Adjustment adjustment;
LatencyBarController bc;
Gtk::HBox hbox1;

View File

@ -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 <ArdourMarker> ("ArdourMarkerList")

View File

@ -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

View File

@ -212,7 +212,7 @@ MidiStreamView::display_track (boost::shared_ptr<Track> 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

View File

@ -158,6 +158,8 @@ MidiTimeAxisView::set_route (boost::shared_ptr<Route> 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<Route> 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<Route> 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)
{

View File

@ -96,8 +96,6 @@ public:
boost::shared_ptr<MIDI::Name::MasterDeviceNames> get_device_names();
boost::shared_ptr<MIDI::Name::CustomDeviceMode> 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);

View File

@ -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<Route> 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<Route> 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<RouteUI*>(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<RouteUI*>(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<bool> (_suspend_menu_callbacks, true);
PBD::Unwinder<bool> 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();
}
}

View File

@ -114,6 +114,9 @@ public:
return _mixer_owned;
}
/* used for screenshots */
void hide_master_spacer (bool);
void hide_things ();
sigc::signal<void> WidthChanged;

View File

@ -30,6 +30,7 @@
#include <glibmm/threads.h>
#include <gtkmm/accelmap.h>
#include <gtkmm/offscreenwindow.h>
#include <gtkmm/stock.h>
#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> 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<Gdk::Pixbuf> pb = osw.get_pixbuf ();
pb->save (filename, "png");
/* unpack elements before destorying the Box & OffscreenWindow */
list<Gtk::Widget*> children = b.get_children();
for (list<Gtk::Widget*>::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;
}

View File

@ -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;

View File

@ -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 <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 <options> Set debug flags. Use \"-D list\" to see available options\n")
#endif
<< _(" -E, --save <file> Load the specified session, save it to <file> and then quit\n")
<< _(" -h, --help Print this message\n")
<< _(" -k, --keybindings <file> 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 <name> Use given template for new session\n")
<< _(" -U, --uuid <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);
}

View File

@ -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;

View File

@ -229,7 +229,7 @@ PatchChangeWidget::refill_banks ()
const int b = bank (_channel);
{
PBD::Unwinder<bool> (_ignore_spin_btn_signals, true);
PBD::Unwinder<bool> uw (_ignore_spin_btn_signals, true);
_bank_msb_spin.set_value (b >> 7);
_bank_lsb_spin.set_value (b & 127);
}

View File

@ -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) {

View File

@ -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<Bundle> w)
}
}
bool
PortMatrix::can_add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t) const
{
boost::shared_ptr<Bundle> b = w.lock ();
if (!b) {
return false;
}
boost::shared_ptr<IO> io = io_from_bundle (b);
return io->can_add_port (t);
}
void
PortMatrix::add_channel_proxy (boost::weak_ptr<Bundle> w, DataType t)
{

View File

@ -182,6 +182,7 @@ private:
void routes_changed ();
void reconnect_to_routes ();
void select_arrangement ();
bool can_add_channel_proxy (boost::weak_ptr<ARDOUR::Bundle>, ARDOUR::DataType) const;
void add_channel_proxy (boost::weak_ptr<ARDOUR::Bundle>, ARDOUR::DataType);
void remove_channel_proxy (boost::weak_ptr<ARDOUR::Bundle>, uint32_t);
void rename_channel_proxy (boost::weak_ptr<ARDOUR::Bundle>, uint32_t);

View File

@ -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);

View File

@ -27,7 +27,7 @@
#include <sstream>
#include <vector>
#include "ptformat/ptfformat.h"
#include "ptformat/ptformat.h"
#include "ardour_dialog.h"
#include "ardour/session.h"

View File

@ -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);

View File

@ -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;

View File

@ -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<TemplateInfo> templates;
find_session_templates (templates, false);
for (vector<TemplateInfo>::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());

View File

@ -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)

View File

@ -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."

View File

@ -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 "$@"

View File

@ -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@

View File

@ -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 "$@"

View File

@ -1,34 +1,43 @@
#include <iostream>
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include "pbd/failed_constructor.h"
#include "pbd/error.h"
#ifndef PLATFORM_WINDOWS
#include <signal.h>
#endif
#include <glibmm.h>
#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 <windows.h>
#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;
}

View File

@ -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)

View File

@ -68,7 +68,6 @@ class LIBARDOUR_API AudioEngine : public PortManager, public SessionHandlePtr
int discover_backends();
std::vector<const AudioBackendInfo*> available_backends() const;
std::string current_backend_name () const;
boost::shared_ptr<AudioBackend> set_default_backend ();
boost::shared_ptr<AudioBackend> set_backend (const std::string&, const std::string& arg1, const std::string& arg2);
boost::shared_ptr<AudioBackend> current_backend() const { return _backend; }
bool setup_required () const;

View File

@ -1,46 +1,43 @@
/*
Copyright (C) 2010 Paul Davis
Copyright (C) 2017 Robin Gareus <robin@gareus.org>
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 <robin@gareus.org>
* 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 <list>
#include <set>
#include <vector>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <glib.h>
#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<GraphNode> 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_ptr_t> node_list_t;
typedef std::set<node_ptr_t> 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<RouteList>, GraphEdges const &);
void trigger (GraphNode* n);
void rechain (boost::shared_ptr<RouteList>, 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<GraphNode *> _trigger_queue;
pthread_mutex_t _trigger_mutex;
PBD::MPMCQueue<GraphNode*> _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

View File

@ -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<GraphNode> 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_ptr_t> node_set_t;
typedef std::list<node_ptr_t> 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> 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> _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> 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> _graph;
gint _refcount;
};
}
#endif

View File

@ -104,6 +104,8 @@ public:
boost::shared_ptr<Bundle> 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<Port>, void *src);
int connect (boost::shared_ptr<Port> our_port, std::string other_port, void *src);

View File

@ -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 ();

View File

@ -853,16 +853,6 @@ AudioEngine::drop_backend ()
}
}
boost::shared_ptr<AudioBackend>
AudioEngine::set_default_backend ()
{
if (_backends.empty()) {
return boost::shared_ptr<AudioBackend>();
}
return set_backend (_backends.begin()->first, "", "");
}
boost::shared_ptr<AudioBackend>
AudioEngine::set_backend (const std::string& name, const std::string& arg1, const std::string& arg2)
{

View File

@ -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

View File

@ -1,36 +1,37 @@
/*
Copyright (C) 2010 Paul Davis
Author: Torben Hohn
* Copyright (C) 2010 Paul Davis
* Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
* 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 <stdio.h>
#include <cmath>
#include <stdio.h>
#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<guint> (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<gint*> (&_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> routelist, GraphEdges const & edges)
Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const& edges)
{
Glib::Threads::Mutex::Lock ls (_swap_mutex);
@ -310,26 +330,25 @@ Graph::rechain (boost::shared_ptr<RouteList> 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<Route> r = boost::dynamic_pointer_cast<Route> (*ni);
/* The routes that are directly fed by r */
@ -339,7 +358,7 @@ Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const & edges
bool const has_output = !fed_from_r.empty ();
/* Set up r's activation set */
for (set<GraphVertex>::iterator i = fed_from_r.begin(); i != fed_from_r.end(); ++i) {
for (set<GraphVertex>::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> 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> 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<Route> rp = boost::dynamic_pointer_cast<Route>( *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<Route>(*ai)->name().c_str()));
for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
boost::shared_ptr<Route> rp = boost::dynamic_pointer_cast<Route> (*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<Route> (*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<Route>(*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<Route> (*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 ();
}

View File

@ -25,11 +25,11 @@
using namespace ARDOUR;
GraphNode::GraphNode (boost::shared_ptr<Graph> 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<Route *>(this));
_graph->process_one_route (dynamic_cast<Route*> (this));
}

View File

@ -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<ptflookup_t> ptfwavpair;
vector<ptflookup_t> ptfregpair;
vector<PTFFormat::wav_t>::iterator w;
vector<PTFFormat::wav_t>::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<PTFFormat::region_t>::iterator a = ptf.regions.begin ();
a != ptf.regions.end (); ++a) {
for (vector<PTFFormat::region_t>::const_iterator a = ptf.regions ().begin ();
a != ptf.regions ().end (); ++a) {
for (vector<ptflookup_t>::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<PTFFormat::track_t>::iterator a = ptf.tracks.begin (); a != ptf.tracks.end (); ++a) {
for (vector<PTFFormat::track_t>::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) {
for (vector<ptflookup_t>::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<midipair> uniquetr;
for (vector<PTFFormat::track_t>::iterator a = ptf.miditracks.begin (); a != ptf.miditracks.end (); ++a) {
for (vector<PTFFormat::track_t>::const_iterator a = ptf.miditracks ().begin (); a != ptf.miditracks ().end (); ++a) {
bool found = false;
for (vector<midipair>::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<PTFFormat::track_t>::iterator a = ptf.miditracks.begin (); a != ptf.miditracks.end (); ++a) {
for (vector<PTFFormat::track_t>::const_iterator a = ptf.miditracks ().begin (); a != ptf.miditracks ().end (); ++a) {
boost::shared_ptr<MidiTrack> 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<PTFFormat::midi_ev_t>::iterator j = a->reg.midi.begin (); j != a->reg.midi.end (); ++j) {
for (vector<PTFFormat::midi_ev_t>::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.);

View File

@ -200,6 +200,19 @@ IO::connect (boost::shared_ptr<Port> 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> 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);

View File

@ -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, Track> ("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)

View File

@ -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) {

View File

@ -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 */
}

View File

@ -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<Track*>(this)) {
set_meter_type (Config->get_meter_type_track ());
} else {

View File

@ -91,8 +91,6 @@ find_session_templates (vector<TemplateInfo>& template_names, bool read_xml)
return;
}
cerr << "Found " << templates.size() << " along " << template_search_path().to_string() << endl;
for (vector<string>::iterator i = templates.begin(); i != templates.end(); ++i) {
string file = session_template_dir_to_file (*i);

View File

@ -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)

View File

@ -76,7 +76,7 @@ class DummyReceiver : public Receiver {
std::cerr << prefix << str << std::endl;
if (chn == Transmitter::Fatal) {
::exit (1);
::exit (EXIT_FAILURE);
}
}
};

View File

@ -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();

View File

@ -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

147
libs/pbd/pbd/mpmc_queue.h Normal file
View File

@ -0,0 +1,147 @@
/*
* (C) 2017, 2019 Robin Gareus <robin@gareus.org>
*
* 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 <cassert>
#include <glib.h>
#include <stdint.h>
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 <typename T>
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

View File

@ -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;
}

View File

@ -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

View File

@ -262,7 +262,7 @@
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath="..\ptfformat.cc"
RelativePath="..\ptformat.cc"
>
</File>
</Filter>
@ -272,7 +272,7 @@
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath="..\ptformat\ptfformat.h"
RelativePath="..\ptformat\ptformat.h"
>
</File>
<File

File diff suppressed because it is too large Load Diff

1327
libs/ptformat/ptformat.cc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stdint.h>
#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_ev_t> 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<wav_t> audiofiles;
std::vector<region_t> regions;
std::vector<region_t> midiregions;
std::vector<compound_t> compounds;
std::vector<track_t> tracks;
std::vector<track_t> miditracks;
static bool regionexistsin(std::vector<region_t> reg, uint16_t index) {
std::vector<region_t>::iterator begin = reg.begin();
std::vector<region_t>::iterator finish = reg.end();
std::vector<region_t>::iterator found;
wav_t w = { std::string(""), 0, 0, 0 };
std::vector<midi_ev_t> 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<wav_t> wv, uint16_t index) {
std::vector<wav_t>::iterator begin = wv.begin();
std::vector<wav_t>::iterator finish = wv.end();
std::vector<wav_t>::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<wav_t>& ws);
void resort(std::vector<region_t>& rs);
void filter(std::vector<region_t>& rs);
std::vector<wav_t> actualwavs;
float ratefactor;
std::string extension;
uint32_t upto;
};
#endif

View File

@ -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 <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stdint.h>
#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_ev_t> 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<track_t>::const_iterator begin = _tracks.begin();
std::vector<track_t>::const_iterator finish = _tracks.end();
std::vector<track_t>::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<region_t>::const_iterator begin = _regions.begin();
std::vector<region_t>::const_iterator finish = _regions.end();
std::vector<region_t>::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<track_t>::const_iterator begin = _miditracks.begin();
std::vector<track_t>::const_iterator finish = _miditracks.end();
std::vector<track_t>::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<region_t>::const_iterator begin = _midiregions.begin();
std::vector<region_t>::const_iterator finish = _midiregions.end();
std::vector<region_t>::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<wav_t>::const_iterator begin = _audiofiles.begin();
std::vector<wav_t>::const_iterator finish = _audiofiles.end();
std::vector<wav_t>::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<region_t> const& reg, uint16_t index) {
std::vector<region_t>::const_iterator begin = reg.begin();
std::vector<region_t>::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<wav_t> const& wv, uint16_t index) {
std::vector<wav_t>::const_iterator begin = wv.begin();
std::vector<wav_t>::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<wav_t>& audiofiles () const { return _audiofiles ; }
const std::vector<region_t>& regions () const { return _regions ; }
const std::vector<region_t>& midiregions () const { return _midiregions ; }
const std::vector<track_t>& tracks () const { return _tracks ; }
const std::vector<track_t>& miditracks () const { return _miditracks ; }
const unsigned char* unxored_data () const { return _ptfunxored; }
uint64_t unxored_size () const { return _len; }
private:
std::vector<wav_t> _audiofiles;
std::vector<region_t> _regions;
std::vector<region_t> _midiregions;
std::vector<track_t> _tracks;
std::vector<track_t> _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<block_t> child; // vector of child blocks
};
std::vector<block_t> 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

View File

@ -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'] ]

View File

@ -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

View File

@ -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

View File

@ -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 <robin@gareus.org>\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);

View File

@ -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 <robin@gareus.org>\n");
exit (0);
exit (EXIT_SUCCESS);
break;
case 'h':
usage (0);
usage (EXIT_SUCCESS);
break;
default:

View File

@ -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 <robin@gareus.org>\n");
exit (0);
exit (EXIT_SUCCESS);
break;
case 'h':
usage (0);
usage (EXIT_SUCCESS);
break;
default:

View File

@ -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 <robin@gareus.org>\n");
exit (0);
exit (EXIT_SUCCESS);
break;
case 'h':
usage (0);
usage (EXIT_SUCCESS);
break;
default:

View File

@ -0,0 +1,242 @@
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <glibmm.h>
#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-dir> [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 <chn> Master-bus channel count (default 2)\n\
-s, --samplerate <rate> Samplerate to use (default 48000)\n\
-t, --template <template> Use given template for new session\n\
-V, --version Print version information and exit\n\
\n");
printf ("\n\
This tool creates a new Ardour session, optionally based on a\n\
session-template.\n\
\n\
If the session-name is unspecified, the sesion-dir-name is used.\n\
If specified, the tool expects a session-name without .ardour\n\
file-name extension.\n\
\n\
If no template is specified, an empty session with a stereo master\n\
bus is created. The -m option allows to specify the master-bus channel\n\
count. If zero is used as channel count, no master-bus is created.\n\
\n\
Note: this tool can only use static session templates.\n\
Interactive Lua init-scripts or dynamic templates are not supported.\n\
\n");
printf ("\n\
Examples:\n\
" UTILNAME " -s 44100 -m 4 /tmp/NewSession\n\
\n");
printf ("Report bugs to <http://tracker.ardour.org/>\n"
"Website: <http://ardour.org/>\n");
::exit (status);
}
static void
list_templates ()
{
vector<TemplateInfo> templates;
find_session_templates (templates, false);
cout << "---- List of Session Templates ----\n";
for (vector<TemplateInfo>::iterator x = templates.begin (); x != templates.end (); ++x) {
cout << "[TPL] " << (*x).name << "\n";
}
cout << "----\n";
}
static std::string
template_path_from_name (std::string const& name)
{
vector<TemplateInfo> templates;
find_session_templates (templates, false);
for (vector<TemplateInfo>::iterator x = templates.begin (); x != templates.end (); ++x) {
if ((*x).name == name) {
return (*x).path;
}
}
return "";
}
static Session*
create_new_session (string const& dir, string const& state, float sample_rate, int master_bus_chn, string const& template_path)
{
AudioEngine* engine = AudioEngine::create ();
if (!engine->set_backend ("None (Dummy)", "Unit-Test", "")) {
cerr << "Cannot create Audio/MIDI engine\n";
::exit (EXIT_FAILURE);
}
engine->set_input_channels (256);
engine->set_output_channels (256);
if (engine->set_sample_rate (sample_rate)) {
cerr << "Cannot set session's samplerate.\n";
return 0;
}
if (engine->start () != 0) {
cerr << "Cannot start Audio/MIDI engine\n";
return 0;
}
string s = Glib::build_filename (dir, state + statefile_suffix);
if (Glib::file_test (dir, Glib::FILE_TEST_EXISTS)) {
cerr << "Session folder already exists '" << dir << "'\n";
}
if (Glib::file_test (s, Glib::FILE_TEST_EXISTS)) {
cerr << "Session file exists '" << s << "'\n";
return 0;
}
BusProfile bus_profile;
BusProfile* bus_profile_ptr = NULL;
if (master_bus_chn > 0) {
bus_profile_ptr = &bus_profile;
bus_profile.master_out_channels = master_bus_chn;
}
if (!template_path.empty ()) {
bus_profile_ptr = NULL;
}
Session* session = new Session (*engine, dir, state, bus_profile_ptr, template_path);
engine->set_session (session);
return session;
}
int
main (int argc, char* argv[])
{
int sample_rate = 48000;
int master_bus_chn = 2;
string template_path;
const char* optstring = "Lm:hs:t:V";
/* clang-format off */
const struct option longopts[] = {
{ "list-templates", no_argument, 0, 'L' },
{ "help", no_argument, 0, 'h' },
{ "master-channels", no_argument, 0, 'm' },
{ "samplerate", required_argument, 0, 's' },
{ "template", required_argument, 0, 't' },
{ "version", no_argument, 0, 'V' },
};
/* clang-format on */
int c = 0;
while (EOF != (c = getopt_long (argc, argv,
optstring, longopts, (int*)0))) {
switch (c) {
case 'L':
list_templates ();
exit (EXIT_SUCCESS);
break;
case 'm': {
const int mc = atoi (optarg);
if (mc >= 0 && mc < 128) {
master_bus_chn = mc;
} else {
cerr << "Invalid master bus channel count\n";
}
} break;
case 's': {
const int sr = atoi (optarg);
if (sr >= 8000 && sr <= 192000) {
sample_rate = sr;
} else {
cerr << "Invalid Samplerate\n";
}
} break;
case 't':
template_path = template_path_from_name (optarg);
if (template_path.empty ()) {
cerr << "Invalid (non-existent) template:" << optarg << "\n";
::exit (EXIT_FAILURE);
}
break;
case 'V':
printf ("ardour-utils version %s\n\n", VERSIONSTRING);
printf ("Copyright (C) GPL 2019 Robin Gareus <robin@gareus.org>\n");
exit (EXIT_SUCCESS);
break;
case 'h':
usage (EXIT_SUCCESS);
break;
default:
usage (EXIT_FAILURE);
break;
}
}
string snapshot_name;
if (optind + 2 == argc) {
snapshot_name = argv[optind + 1];
} else if (optind + 1 == argc) {
snapshot_name = Glib::path_get_basename (argv[optind]);
} else {
usage (EXIT_FAILURE);
}
if (snapshot_name.empty ()) {
cerr << "Error: Invalid empty session/snapshot name.\n";
::exit (EXIT_FAILURE);
}
/* all systems go */
SessionUtils::init ();
Session* s = 0;
try {
s = create_new_session (argv[optind], snapshot_name, sample_rate, master_bus_chn, template_path);
} catch (ARDOUR::SessionException& e) {
cerr << "Error: " << e.what () << "\n";
} catch (...) {
cerr << "Error: unknown exception.\n";
}
/* save is implicit when creating a new session */
if (s) {
cout << "Created session in '" << s->path () << "'" << endl;
}
SessionUtils::unload_session (s);
SessionUtils::cleanup ();
return 0;
}

View File

@ -52,7 +52,7 @@ foreach (json_decode ($json, true) as $b) {
if (count ($doc) == 0) {
fwrite (STDERR, "Failed to read luadoc.json\n");
exit (1);
exit (EXIT_FAILURE);
}
################################################################################