From e90e31d68247148b828763f82994831d8b12871f Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 29 Apr 2024 21:00:27 +0200 Subject: [PATCH 01/13] Add a Tracks/Waveform icon --- libs/widgets/ardour_icon.cc | 36 ++++++++++++++++++++++++++++++ libs/widgets/widgets/ardour_icon.h | 1 + 2 files changed, 37 insertions(+) diff --git a/libs/widgets/ardour_icon.cc b/libs/widgets/ardour_icon.cc index 1e51999293..441cbedd0e 100644 --- a/libs/widgets/ardour_icon.cc +++ b/libs/widgets/ardour_icon.cc @@ -1509,6 +1509,39 @@ icon_meters (cairo_t* cr, const int width, const int height, const uint32_t fg_c VECTORICONSTROKE (lw, fg_color); } +static void +icon_waveform (cairo_t* cr, const int width, const int height, const uint32_t fg_color) +{ + const double x = width * .5; + const double y = height * .5; + const double wh = std::min (x, y); + + const double lw = DEFAULT_LINE_WIDTH; + const double lc = fmod (lw * .5, 1.0); + + const int m = floor (1.6 * wh - lw); + const double x0 = rint (x + 1 - 0.5 * m); + + // for i=0,60 do print (string.format ("%.2f, ", (math.random(1,100)/100) * (math.random(1,100)/100))) end + static const float wave[] = { + 0.12, 0.40, 0.28, 0.21, 0.25, 0.57, 0.57, 0.41, 0.33, 0.63, + 0.11, 0.89, 0.13, 0.29, 0.18, 0.24, 0.10, 0.05, 0.24, 0.15, + 0.01, 0.39, 0.93, 0.27, 0.28, 0.07, 0.15, 0.12, 0.10, 0.13, + 0.08, 0.03, 0.04, 0.59, 0.64, 0.49, 0.01, 0.04, 0.01, 0.39, + 0.44, 0.01, 0.21, 0.12, 0.06, 0.07, 0.01, 0.11, 0.07, 0.33, + 0.38, 0.24, 0.16, 0.64, 0.17, 0.05, 0.24, 0.07, 0.04, 0.35, + }; + + static const int p = sizeof(wave)/sizeof (float); + + for (int i = 0; i < m; ++i) { + double dy = (wh * .8) * wave[i % p] * sqrt(sin (M_PI * i / m)); + cairo_move_to (cr, x0 + i - lc, y - dy); + cairo_line_to (cr, x0 + i - lc, y + dy); + } + VECTORICONSTROKE (lw, fg_color); +} + /*****************************************************************************/ bool @@ -1667,6 +1700,9 @@ ArdourWidgets::ArdourIcon::render (cairo_t* cr case Meters: icon_meters (cr, width, height, fg_color); break; + case TrackWaveform: + icon_waveform (cr, width, height, fg_color); + break; case NoIcon: rv = false; break; diff --git a/libs/widgets/widgets/ardour_icon.h b/libs/widgets/widgets/ardour_icon.h index 4651ec7a77..7a04c4593c 100644 --- a/libs/widgets/widgets/ardour_icon.h +++ b/libs/widgets/widgets/ardour_icon.h @@ -76,6 +76,7 @@ namespace ArdourWidgets { namespace ArdourIcon { Lock, Mixer, Meters, + TrackWaveform, NoIcon //< Last }; From 2af2df351655096832d7aac1f12cf8c16230161c Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 29 Apr 2024 21:46:06 +0200 Subject: [PATCH 02/13] Raise the butler's I/O priority This likely won't make much difference on modern systems, since it requires a kernel based I/O scheduler. which is disabled (set to "none" for NVMe and SSDs). --- libs/ardour/butler.cc | 11 +++++++++++ libs/ardour/wscript | 12 +++++++++++- wscript | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index b1e05bb344..0c16425022 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -24,6 +24,10 @@ #include #include +#ifdef HAVE_IOPRIO +#include +#endif + #ifndef PLATFORM_WINDOWS #include #endif @@ -177,6 +181,13 @@ Butler::thread_work () bool disk_work_outstanding = false; RouteList::iterator i; +#ifdef HAVE_IOPRIO + // ioprio_set (IOPRIO_WHO_PROCESS, 0 /*calling thread*/, IOPRIO_PRIO_VALUE (IOPRIO_CLASS_RT, 4)) + if (0 != syscall (SYS_ioprio_set, 1, 0, (1 << 13) | 4)) { + warning << _("Cannot set I/O Priority for disk read/write thread") << endmsg; + } +#endif + while (true) { DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 butler main loop, disk work outstanding ? %2 @ %3\n", DEBUG_THREAD_SELF, disk_work_outstanding, g_get_monotonic_time ())); diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 9f87f689cb..aa75d4a256 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -345,9 +345,19 @@ def configure(conf): conf.check(header_name='sys/vfs.h', define_name='HAVE_SYS_VFS_H',mandatory=False) conf.check(header_name='sys/statvfs.h', define_name='HAVE_SYS_STATVFS_H',mandatory=False) - conf.check(header_name='unistd.h', define_name='HAVE_UNISTD',mandatory=False) + have_sys_ioprio = conf.check_cc( + msg="Checking for 'ioprio_set' syscall support", + features = 'c', + mandatory = False, + execute = False, + fragment = "#include \nint main () { syscall(SYS_ioprio_set, 1, 0, 8192); return 0; }") + + if have_sys_ioprio: + conf.define('HAVE_IOPRIO', 1) + conf.env['HAVE_IOPRIO'] = True + conf.write_config_header('libardour-config.h', remove=False) # Boost headers diff --git a/wscript b/wscript index f46c3de520..a11924f964 100644 --- a/wscript +++ b/wscript @@ -1571,6 +1571,7 @@ const char* const ardour_config_info = "\\n\\ write_config_text('Futex Semaphore', conf.is_defined('USE_FUTEX_SEMAPHORE')) write_config_text('Freedesktop files', opts.freedesktop) write_config_text('G_ENABLE_DEBUG', opts.gdebug or conf.env['DEBUG']) + write_config_text('I/O Priorty Set', conf.is_defined('HAVE_IOPRIO')) write_config_text('Libjack linking', conf.env['libjack_link']) write_config_text('Libjack metadata', conf.is_defined ('HAVE_JACK_METADATA')) write_config_text('Lua Binding Doc', conf.is_defined('LUABINDINGDOC')) From 170b9150381a13ccee28517aff19c61db8a8386a Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 30 Apr 2024 00:01:32 +0200 Subject: [PATCH 03/13] Prepare for parallel Disk I/O --- libs/ardour/ardour/io_tasklist.h | 47 ++++++++++++++++++++++++++++ libs/ardour/ardour/session.h | 3 ++ libs/ardour/butler.cc | 36 +++++++++++++--------- libs/ardour/io_tasklist.cc | 53 ++++++++++++++++++++++++++++++++ libs/ardour/session.cc | 4 +++ libs/ardour/session_transport.cc | 38 +++++++++++++++-------- libs/ardour/wscript | 1 + 7 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 libs/ardour/ardour/io_tasklist.h create mode 100644 libs/ardour/io_tasklist.cc diff --git a/libs/ardour/ardour/io_tasklist.h b/libs/ardour/ardour/io_tasklist.h new file mode 100644 index 0000000000..daac29fdb0 --- /dev/null +++ b/libs/ardour/ardour/io_tasklist.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _ardour_io_tasklist_h_ +#define _ardour_io_tasklist_h_ + +#include +#include + +#include "ardour/libardour_visibility.h" + +namespace ARDOUR +{ + +class LIBARDOUR_API IOTaskList +{ +public: + IOTaskList (); + ~IOTaskList (); + + /** process tasks in list in parallel, wait for them to complete */ + void process (); + void push_back (boost::function fn); + +private: + std::vector> _tasks; + + size_t _n_threads; +}; + +} // namespace ARDOUR +#endif diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 09ef6f25d9..bfddd4c6a6 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -149,6 +149,7 @@ struct GraphChain; class IO; class IOPlug; class IOProcessor; +class IOTaskList; class ImportStatus; class MidiClockTicker; class MidiControlUI; @@ -334,6 +335,7 @@ public: } std::shared_ptr rt_tasklist () { return _rt_tasklist; } + std::shared_ptr io_tasklist () { return _io_tasklist; } RouteList get_routelist (bool mixer_order = false, PresentationInfo::Flag fl = PresentationInfo::MixerRoutes) const; @@ -2344,6 +2346,7 @@ private: std::shared_ptr _ltc_output_port; std::shared_ptr _rt_tasklist; + std::shared_ptr _io_tasklist; /* Scene Changing */ SceneChanger* _scene_changer; diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 0c16425022..8c76c9b084 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -44,6 +44,7 @@ #include "ardour/disk_io.h" #include "ardour/disk_reader.h" #include "ardour/io.h" +#include "ardour/io_tasklist.h" #include "ardour/session.h" #include "ardour/track.h" @@ -264,6 +265,8 @@ Butler::thread_work () DEBUG_TRACE (DEBUG::Butler, string_compose ("butler starts refill loop, twr = %1\n", transport_work_requested ())); + std::shared_ptr tl = _session.io_tasklist (); + for (i = rl_with_auditioner.begin (); !transport_work_requested () && should_run && i != rl_with_auditioner.end (); ++i) { std::shared_ptr tr = std::dynamic_pointer_cast (*i); @@ -278,24 +281,27 @@ Butler::thread_work () // DEBUG_TRACE (DEBUG::Butler, string_compose ("butler skips inactive track %1\n", tr->name())); continue; } - // DEBUG_TRACE (DEBUG::Butler, string_compose ("butler refills %1, playback load = %2\n", tr->name(), tr->playback_buffer_load())); - switch (tr->do_refill ()) { - case 0: - //DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill done %1\n", tr->name())); - break; - case 1: - DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill unfinished %1\n", tr->name ())); - disk_work_outstanding = true; - break; - - default: - error << string_compose (_("Butler read ahead failure on dstream %1"), (*i)->name ()) << endmsg; - std::cerr << string_compose (_("Butler read ahead failure on dstream %1"), (*i)->name ()) << std::endl; - break; - } + tl->push_back ([tr, &disk_work_outstanding]() { + switch (tr->do_refill ()) { + case 0: + //DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill done %1\n", tr->name())); + break; + case 1: + DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill unfinished %1\n", tr->name ())); + disk_work_outstanding = true; + break; + default: + error << string_compose (_("Butler read ahead failure on dstream %1"), tr->name ()) << endmsg; + std::cerr << string_compose (_("Butler read ahead failure on dstream %1"), tr->name ()) << std::endl; + break; + } + }); } + tl->process (); + tl.reset (); + if (i != rl_with_auditioner.begin () && i != rl_with_auditioner.end ()) { /* we didn't get to all the streams */ disk_work_outstanding = true; diff --git a/libs/ardour/io_tasklist.cc b/libs/ardour/io_tasklist.cc new file mode 100644 index 0000000000..8af4a7f4e2 --- /dev/null +++ b/libs/ardour/io_tasklist.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2024 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "pbd/pthread_utils.h" + +#include "ardour/io_tasklist.h" + +using namespace ARDOUR; + +IOTaskList::IOTaskList () + : _n_threads (0) +{ +} + +IOTaskList::~IOTaskList () +{ +} + +void +IOTaskList::push_back (boost::function fn) +{ + _tasks.push_back (fn); +} + +void +IOTaskList::process () +{ + assert (strcmp (pthread_name (), "butler") == 0); + //std::cout << "IOTaskList::process " << pthread_name () << " " << _tasks.size () << "\n"; + if (_n_threads > 1 && _tasks.size () > 2) { + // TODO + } else { + for (auto const& fn : _tasks) { + fn (); + } + } + _tasks.clear (); +} diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index fa071ba7f3..af1438cbcf 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -85,6 +85,7 @@ #include "ardour/gain_control.h" #include "ardour/graph.h" #include "ardour/io_plug.h" +#include "ardour/io_tasklist.h" #include "ardour/luabindings.h" #include "ardour/lv2_plugin.h" #include "ardour/midiport_manager.h" @@ -589,6 +590,7 @@ Session::immediately_post_engine () _process_graph.reset (new Graph (*this)); _rt_tasklist.reset (new RTTaskList (_process_graph)); + _io_tasklist.reset (new IOTaskList ()); /* every time we reconnect, recompute worst case output latencies */ @@ -720,6 +722,8 @@ Session::destroy () _io_graph_chain[0].reset (); _io_graph_chain[1].reset (); + _io_tasklist.reset (); + _butler->drop_references (); delete _butler; _butler = 0; diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index aeece88079..09e957983e 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -54,6 +54,7 @@ #include "ardour/click.h" #include "ardour/debug.h" #include "ardour/disk_reader.h" +#include "ardour/io_tasklist.h" #include "ardour/location.h" #include "ardour/playlist.h" #include "ardour/profile.h" @@ -1283,12 +1284,14 @@ Session::non_realtime_locate () tf = _transport_sample; start = get_microseconds (); + std::shared_ptr tl = io_tasklist (); for (auto const& i : *rl) { ++nt; - i->non_realtime_locate (tf); - if (sc != _seek_counter.load ()) { - goto restart; - } + tl->push_back ([this, i, tf, sc]() { if (sc == _seek_counter.load ()) { i->non_realtime_locate (tf); }}); + } + tl->process (); + if (sc != _seek_counter.load ()) { + goto restart; } microseconds_t end = get_microseconds (); @@ -1528,15 +1531,26 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished, bool will_ DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n")); - for (auto const& i : *r) { - DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", i->name())); - i->non_realtime_locate (_transport_sample); + std::shared_ptr tl = io_tasklist (); - if (on_entry != _butler->should_do_transport_work.load()) { - finished = false; - /* we will be back */ - return; - } + std::atomic fini (finished); + for (auto const& i : *r) { + tl->push_back ([this, i, on_entry, &fini]() { + if (!fini.load ()) { + return; + } + DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", i->name())); + i->non_realtime_locate (_transport_sample); + if (on_entry != _butler->should_do_transport_work.load()) { + fini = false; + } + }); + } + tl->process (); + if (!fini.load ()) { + finished = false; + /* we will be back */ + return; } VCAList v = _vca_manager->vcas (); diff --git a/libs/ardour/wscript b/libs/ardour/wscript index aa75d4a256..777a0325f1 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -110,6 +110,7 @@ libardour_sources = [ 'io.cc', 'io_plug.cc', 'io_processor.cc', + 'io_tasklist.cc', 'kmeterdsp.cc', 'ladspa_plugin.cc', 'latent.cc', From ee87b068e8847331a48a5e481fd973abde2660f3 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 30 Apr 2024 01:54:33 +0200 Subject: [PATCH 04/13] Move RegionFx ThreadBuffers to Butler thread --- libs/ardour/audioregion.cc | 5 ----- libs/ardour/butler.cc | 7 ++++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index f172143547..6944e05bca 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -2430,9 +2430,6 @@ AudioRegion::apply_region_fx (BufferSet& bufs, samplepos_t start_sample, samplep } } - ARDOUR::ProcessThread* pt = new ProcessThread (); // TODO -> move to butler ? - pt->get_buffers (); - samplecnt_t latency_offset = 0; for (auto const& rfx : _plugins) { @@ -2470,6 +2467,4 @@ AudioRegion::apply_region_fx (BufferSet& bufs, samplepos_t start_sample, samplep } _fx_pos = end_sample; _fx_latent_read = false; - pt->drop_buffers (); - delete pt; } diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 8c76c9b084..039d1aef00 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -172,7 +172,12 @@ Butler::_thread_work (void* arg) { SessionEvent::create_per_thread_pool ("butler events", 4096); pthread_set_name (X_("butler")); - return ((Butler*)arg)->thread_work (); + ARDOUR::ProcessThread* pt = new ProcessThread (); + pt->get_buffers (); + void* rv = ((Butler*)arg)->thread_work (); + pt->drop_buffers (); + delete pt; + return rv; } void* From 4b0da72bc27ea5b34f82013755d3f752b99f74df Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 30 Apr 2024 01:55:12 +0200 Subject: [PATCH 05/13] Delegate all DiskReader I/O to the IOTaskList (amend 170b9150) --- libs/ardour/session_transport.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 09e957983e..2f5ac62651 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -1152,14 +1152,17 @@ Session::butler_transport_work (bool have_process_lock) if (!have_process_lock) { lx.acquire (); } + std::shared_ptr tl = io_tasklist (); for (auto const& i : *r) { std::shared_ptr tr = std::dynamic_pointer_cast (i); if (tr) { tr->adjust_playback_buffering (); /* and refill those buffers ... */ } - i->non_realtime_locate (_transport_sample); + tl->push_back ([this, i]() { i->non_realtime_locate (_transport_sample); }); } + tl->process (); + VCAList v = _vca_manager->vcas (); for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { (*i)->non_realtime_locate (_transport_sample); From 5b9e4fff634e1d5052cf78375f1387bfceca4cbd Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 30 Apr 2024 03:46:39 +0200 Subject: [PATCH 06/13] Parallelize Disk I/O and RegionFx processing --- libs/ardour/ardour/debug.h | 1 + libs/ardour/ardour/disk_reader.h | 6 +- libs/ardour/ardour/io_tasklist.h | 18 ++- libs/ardour/ardour/rc_configuration_vars.h | 1 + libs/ardour/ardour/utils.h | 1 + libs/ardour/butler.cc | 5 + libs/ardour/debug.cc | 1 + libs/ardour/disk_reader.cc | 6 +- libs/ardour/globals.cc | 9 +- libs/ardour/io_tasklist.cc | 126 ++++++++++++++++++++- libs/ardour/session.cc | 5 +- libs/ardour/session_state.cc | 1 - libs/ardour/utils.cc | 19 ++++ libs/pbd/pbd/pthread_utils.h | 3 +- libs/pbd/pthread_utils.cc | 7 ++ 15 files changed, 189 insertions(+), 20 deletions(-) diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index db63e17756..18aaa74ae7 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -58,6 +58,7 @@ namespace PBD { LIBARDOUR_API extern DebugBits FaderPort; LIBARDOUR_API extern DebugBits GenericMidi; LIBARDOUR_API extern DebugBits Graph; + LIBARDOUR_API extern DebugBits IOTaskList; LIBARDOUR_API extern DebugBits LTC; LIBARDOUR_API extern DebugBits LV2; LIBARDOUR_API extern DebugBits LV2Automate; diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index ed240c744a..63797e8685 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -229,9 +229,9 @@ private: int channel, bool reversed); - static Sample* _sum_buffer; - static Sample* _mixdown_buffer; - static gain_t* _gain_buffer; + static thread_local Sample* _sum_buffer; + static thread_local Sample* _mixdown_buffer; + static thread_local gain_t* _gain_buffer; int refill (Sample* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level, bool reversed); int refill_audio (Sample* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level, bool reversed); diff --git a/libs/ardour/ardour/io_tasklist.h b/libs/ardour/ardour/io_tasklist.h index daac29fdb0..7758b6c340 100644 --- a/libs/ardour/ardour/io_tasklist.h +++ b/libs/ardour/ardour/io_tasklist.h @@ -19,8 +19,12 @@ #ifndef _ardour_io_tasklist_h_ #define _ardour_io_tasklist_h_ +#include #include #include +#include + +#include "pbd/semutils.h" #include "ardour/libardour_visibility.h" @@ -30,7 +34,7 @@ namespace ARDOUR class LIBARDOUR_API IOTaskList { public: - IOTaskList (); + IOTaskList (uint32_t); ~IOTaskList (); /** process tasks in list in parallel, wait for them to complete */ @@ -38,9 +42,19 @@ public: void push_back (boost::function fn); private: + static void* _worker_thread (void*); + + void io_thread (); + std::vector> _tasks; - size_t _n_threads; + uint32_t _n_threads; + std::atomic _n_workers; + std::vector _workers; + std::atomic _terminate; + PBD::Semaphore _exec_sem; + PBD::Semaphore _idle_sem; + Glib::Threads::Mutex _tasks_mutex; }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h index d6f35e1807..9b3d4f6809 100644 --- a/libs/ardour/ardour/rc_configuration_vars.h +++ b/libs/ardour/ardour/rc_configuration_vars.h @@ -221,6 +221,7 @@ CONFIG_VARIABLE (std::string, sample_lib_path, "sample-lib-path", "") /* custom CONFIG_VARIABLE (bool, allow_special_bus_removal, "allow-special-bus-removal", false) CONFIG_VARIABLE (int32_t, processor_usage, "processor-usage", -1) CONFIG_VARIABLE (int32_t, cpu_dma_latency, "cpu-dma-latency", -1) /* >=0 to enable */ +CONFIG_VARIABLE (int32_t, io_thread_count, "io-thread-count", -2) CONFIG_VARIABLE (gain_t, max_gain, "max-gain", 2.0) /* +6.0dB */ CONFIG_VARIABLE (uint32_t, max_recent_sessions, "max-recent-sessions", 10) CONFIG_VARIABLE (uint32_t, max_recent_templates, "max-recent-templates", 10) diff --git a/libs/ardour/ardour/utils.h b/libs/ardour/ardour/utils.h index 0030ddead8..22a99c758f 100644 --- a/libs/ardour/ardour/utils.h +++ b/libs/ardour/ardour/utils.h @@ -109,6 +109,7 @@ LIBARDOUR_API const char* native_header_format_extension (ARDOUR::HeaderFormat, LIBARDOUR_API bool matching_unsuffixed_filename_exists_in (const std::string& dir, const std::string& name); LIBARDOUR_API uint32_t how_many_dsp_threads (); +LIBARDOUR_API uint32_t how_many_io_threads (); LIBARDOUR_API std::string compute_sha1_of_file (std::string path); diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 039d1aef00..6c954e63b4 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -172,9 +172,14 @@ Butler::_thread_work (void* arg) { SessionEvent::create_per_thread_pool ("butler events", 4096); pthread_set_name (X_("butler")); + /* get thread buffers for RegionFx */ ARDOUR::ProcessThread* pt = new ProcessThread (); pt->get_buffers (); + DiskReader::allocate_working_buffers (); + void* rv = ((Butler*)arg)->thread_work (); + + DiskReader::free_working_buffers (); pt->drop_buffers (); delete pt; return rv; diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index e061c2840d..99a8dbce20 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -53,6 +53,7 @@ PBD::DebugBits PBD::DEBUG::FaderPort = PBD::new_debug_bit ("faderport"); PBD::DebugBits PBD::DEBUG::FaderPort8 = PBD::new_debug_bit ("faderport8"); PBD::DebugBits PBD::DEBUG::GenericMidi = PBD::new_debug_bit ("genericmidi"); PBD::DebugBits PBD::DEBUG::Graph = PBD::new_debug_bit ("graph"); +PBD::DebugBits PBD::DEBUG::IOTaskList = PBD::new_debug_bit ("iotasklist"); PBD::DebugBits PBD::DEBUG::LTC = PBD::new_debug_bit ("ltc"); PBD::DebugBits PBD::DEBUG::LV2 = PBD::new_debug_bit ("lv2"); PBD::DebugBits PBD::DEBUG::LV2Automate = PBD::new_debug_bit ("lv2automate"); diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index d672bf3da4..bf040cf8b3 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -49,9 +49,9 @@ using namespace std; ARDOUR::samplecnt_t DiskReader::_chunk_samples = default_chunk_samples (); PBD::Signal0 DiskReader::Underrun; -Sample* DiskReader::_sum_buffer = 0; -Sample* DiskReader::_mixdown_buffer = 0; -gain_t* DiskReader::_gain_buffer = 0; +thread_local Sample* DiskReader::_sum_buffer = 0; +thread_local Sample* DiskReader::_mixdown_buffer = 0; +thread_local gain_t* DiskReader::_gain_buffer = 0; std::atomic DiskReader::_no_disk_output (0); DiskReader::Declicker DiskReader::loop_declick_in; DiskReader::Declicker DiskReader::loop_declick_out; diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 49afbe5cf6..3b8c4889aa 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -734,11 +734,14 @@ ARDOUR::init (bool try_optimization, const char* localedir, bool with_gui) * each cycle). Session Export uses one, and the GUI requires * buffers (for plugin-analysis, auditioner updates) but not * concurrently. - * Last but not least, the butler needs one for RegionFX. * - * In theory (hw + 4) should be sufficient, let's add one for luck. + * Last but not least, the butler needs one for RegionFX for + * each I/O thread (up to hardware_concurrency) and one for itself + * (butler's main thread). + * + * In theory (2 * hw + 4) should be sufficient, let's add one for luck. */ - BufferManager::init (hardware_concurrency () + 5); + BufferManager::init (hardware_concurrency () * 2 + 5); PannerManager::instance ().discover_panners (); diff --git a/libs/ardour/io_tasklist.cc b/libs/ardour/io_tasklist.cc index 8af4a7f4e2..ae86e9a640 100644 --- a/libs/ardour/io_tasklist.cc +++ b/libs/ardour/io_tasklist.cc @@ -16,19 +16,72 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifdef HAVE_IOPRIO +#include +#endif + +#include "pbd/compose.h" +#include "pbd/cpus.h" +#include "pbd/debug.h" +#include "pbd/failed_constructor.h" #include "pbd/pthread_utils.h" +#include "temporal/tempo.h" + +#include "ardour/debug.h" +#include "ardour/disk_reader.h" #include "ardour/io_tasklist.h" +#include "ardour/process_thread.h" +#include "ardour/session_event.h" using namespace ARDOUR; -IOTaskList::IOTaskList () - : _n_threads (0) +IOTaskList::IOTaskList (uint32_t n_threads) + : _n_threads (n_threads) + , _terminate (false) + , _exec_sem ("io thread exec", 0) + , _idle_sem ("io thread idle", 0) { + assert (n_threads <= hardware_concurrency ()); + + if (n_threads < 2) { + return; + } + + pthread_attr_t attr; + struct sched_param parm; + parm.sched_priority = pbd_absolute_rt_priority (SCHED_RR, pbd_pthread_priority (THREAD_IO)); + + pthread_attr_init (&attr); +#ifdef PLATFORM_WINDOWS + pthread_attr_setschedpolicy (&attr, SCHED_OTHER); +#else + pthread_attr_setschedpolicy (&attr, SCHED_RR); +#endif + pthread_attr_setschedparam (&attr, &parm); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED); + + DEBUG_TRACE (PBD::DEBUG::IOTaskList, string_compose ("IOTaskList starting %1 threads with priority = %2\n", _n_threads, parm.sched_priority)); + + _workers.resize (_n_threads); + for (uint32_t i = 0; i < _n_threads; ++i) { + if (pthread_create (&_workers[i], &attr, &_worker_thread, this)) { + throw failed_constructor (); + } + } + pthread_attr_destroy (&attr); } IOTaskList::~IOTaskList () { + _terminate.store (true); + for (size_t i = 0; i < _workers.size (); ++i) { + _exec_sem.signal (); + } + for (auto const& t : _workers) { + pthread_join (t, NULL); + } } void @@ -41,13 +94,78 @@ void IOTaskList::process () { assert (strcmp (pthread_name (), "butler") == 0); - //std::cout << "IOTaskList::process " << pthread_name () << " " << _tasks.size () << "\n"; if (_n_threads > 1 && _tasks.size () > 2) { - // TODO + uint32_t wakeup = std::min (_n_threads, _tasks.size ()); + DEBUG_TRACE (PBD::DEBUG::IOTaskList, string_compose ("IOTaskList process wakeup %1 thread for %2 tasks.\n", wakeup, _tasks.size ())) + for (uint32_t i = 0; i < wakeup; ++i) { + _exec_sem.signal (); + } + for (uint32_t i = 0; i < wakeup; ++i) { + _idle_sem.wait (); + } } else { + DEBUG_TRACE (PBD::DEBUG::IOTaskList, string_compose ("IOTaskList process %1 task(s) in main thread.\n", _tasks.size ())) for (auto const& fn : _tasks) { fn (); } } _tasks.clear (); } + +void* +IOTaskList::_worker_thread (void* me) +{ + IOTaskList* self = static_cast (me); + + uint32_t id = self->_n_workers.fetch_add (1); + char name[64]; + snprintf (name, 64, "IO-%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); + + DiskReader::allocate_working_buffers (); + ARDOUR::ProcessThread* pt = new ProcessThread (); + pt->get_buffers (); + +#ifdef HAVE_IOPRIO + /* compare to Butler::_thread_work */ + // ioprio_set (IOPRIO_WHO_PROCESS, 0 /*calling thread*/, IOPRIO_PRIO_VALUE (IOPRIO_CLASS_RT, 4)) + syscall (SYS_ioprio_set, 1, 0, (1 << 13) | 4); +#endif + + self->io_thread (); + + DiskReader::free_working_buffers (); + pt->drop_buffers (); + delete pt; + return 0; +} + +void +IOTaskList::io_thread () +{ + while (1) { + _exec_sem.wait (); + if (_terminate.load ()) { + break; + } + + Temporal::TempoMap::fetch (); + + while (1) { + boost::function fn; + Glib::Threads::Mutex::Lock lm (_tasks_mutex); + if (_tasks.empty ()) { + break; + } + fn = _tasks.back (); + _tasks.pop_back (); + lm.release (); + + fn (); + } + _idle_sem.signal (); + } +} diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index af1438cbcf..8dab2704e3 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -590,7 +590,8 @@ Session::immediately_post_engine () _process_graph.reset (new Graph (*this)); _rt_tasklist.reset (new RTTaskList (_process_graph)); - _io_tasklist.reset (new IOTaskList ()); + + _io_tasklist.reset (new IOTaskList (how_many_io_threads ())); /* every time we reconnect, recompute worst case output latencies */ @@ -775,8 +776,6 @@ Session::destroy () _bundles.flush (); _io_plugins.flush (); - DiskReader::free_working_buffers(); - /* tell everyone who is still standing that we're about to die */ drop_references (); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index a74ec00194..9f0bcd9ddf 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -272,7 +272,6 @@ Session::post_engine_init () _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this, true)); _engine.MidiSelectionPortsChanged.connect_same_thread (*this, boost::bind (&Session::rewire_midi_selection_ports, this)); - DiskReader::allocate_working_buffers(); refresh_disk_space (); /* we're finally ready to call set_state() ... all objects have diff --git a/libs/ardour/utils.cc b/libs/ardour/utils.cc index 9e06652fc4..25b31f4984 100644 --- a/libs/ardour/utils.cc +++ b/libs/ardour/utils.cc @@ -696,6 +696,25 @@ ARDOUR::how_many_dsp_threads () return num_threads; } +uint32_t +ARDOUR::how_many_io_threads () +{ + int num_cpu = hardware_concurrency(); + int pu = Config->get_io_thread_count (); + uint32_t num_threads = max (num_cpu - 2, 2); + if (pu < 0) { + if (-pu < num_cpu) { + num_threads = num_cpu + pu; + } + } else if (pu == 0) { + num_threads = num_cpu; + + } else { + num_threads = min (num_cpu, pu); + } + return num_threads; +} + double ARDOUR::gain_to_slider_position_with_max (double g, double max_gain) { diff --git a/libs/pbd/pbd/pthread_utils.h b/libs/pbd/pbd/pthread_utils.h index ed6628123b..e1b18f2603 100644 --- a/libs/pbd/pbd/pthread_utils.h +++ b/libs/pbd/pbd/pthread_utils.h @@ -68,7 +68,8 @@ LIBPBD_API void pthread_set_name (const char* name); enum PBDThreadClass { THREAD_MAIN, // main audio I/O thread THREAD_MIDI, // MIDI I/O threads - THREAD_PROC // realtime worker + THREAD_PROC, // realtime worker + THREAD_IO // non-realtime I/O }; LIBPBD_API int pbd_pthread_priority (PBDThreadClass); diff --git a/libs/pbd/pthread_utils.cc b/libs/pbd/pthread_utils.cc index 2387bd947f..052d035535 100644 --- a/libs/pbd/pthread_utils.cc +++ b/libs/pbd/pthread_utils.cc @@ -280,6 +280,11 @@ pbd_pthread_priority (PBDThreadClass which) default: case THREAD_PROC: return -2; + case THREAD_IO: + /* https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/src/sched.c + * -> THREAD_PRIORITY_HIGHEST + */ + return -13; } #else int base = -20; @@ -299,6 +304,8 @@ pbd_pthread_priority (PBDThreadClass which) default: case THREAD_PROC: return base - 2; + case THREAD_IO: + return base - 10; } #endif } From 2ccda116c7409a707c76dd4d5b77c720c7ff6a1d Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 30 Apr 2024 03:54:57 +0200 Subject: [PATCH 07/13] Add preference for I/O thread count --- gtk2_ardour/rc_option_editor.cc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc index 0385778c5c..ffaa8e3497 100644 --- a/gtk2_ardour/rc_option_editor.cc +++ b/gtk2_ardour/rc_option_editor.cc @@ -4906,6 +4906,27 @@ These settings will only take effect after %1 is restarted.\n\ add_option (_("Performance"), new BufferingOptions (_rc_config)); + if (hwcpus > 1) { + ComboOption* procs = new ComboOption ( + "io-thread-count", + _("Disk I/O threads"), + sigc::mem_fun (*_rc_config, &RCConfiguration::get_io_thread_count), + sigc::mem_fun (*_rc_config, &RCConfiguration::set_io_thread_count) + ); + + procs->add (-2, _("all but two processor")); + procs->add (-1, _("all but one processor")); + procs->add (0, _("all available processors")); + + for (uint32_t i = 1; i <= hwcpus; ++i) { + procs->add (i, string_compose (P_("%1 processor", "%1 processors", i), i)); + } + + procs->set_note (string_compose (_("This setting will only take effect when %1 is restarted."), PROGRAM_NAME)); + + add_option (_("Performance"), procs); + } + /* Image cache size */ add_option (_("Performance"), new OptionEditorHeading (_("Memory Usage"))); From b9da1a5bd5a58550c6f6151d763547ec8dd65583 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 1 May 2024 03:51:42 +0200 Subject: [PATCH 08/13] Expose HW concurrency to LV2 plugins --- libs/ardour/lv2_plugin.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index de50c3f8e9..489604ed4d 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -610,6 +610,7 @@ LV2Plugin::init(const void* c_plugin, samplecnt_t rate) static const int32_t _max_block_length = 8192; // max possible (with all engines and during export) static const int32_t rt_policy = PBD_SCHED_FIFO; static const int32_t rt_priority = pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority () - 1); + static const int32_t hw_concurrency = how_many_dsp_threads (); /* Consider updating max-block-size whenever the buffersize changes. * It requires re-instantiating the plugin (which is a non-realtime operation), * so it should be done lightly and only for plugins that require it. @@ -632,6 +633,8 @@ LV2Plugin::init(const void* c_plugin, samplecnt_t rate) sizeof(int32_t), atom_Int, &rt_policy }, { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#schedPriority"), sizeof(int32_t), atom_Int, &rt_priority }, + { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://ardour.org/lv2/threads/#concurrency"), + sizeof(int32_t), atom_Int, &hw_concurrency }, { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#backgroundColor"), sizeof(int32_t), atom_Int, &_ui_background_color }, { LV2_OPTIONS_INSTANCE, 0, _uri_map.uri_to_id("http://lv2plug.in/ns/extensions/ui#foregroundColor"), From f35555183934b0b990a9517a506a1f5363c76c4d Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 1 May 2024 14:54:31 +0200 Subject: [PATCH 09/13] Don't nag Trax users after export --- gtk2_ardour/export_dialog.cc | 2 +- gtk2_ardour/simple_export_dialog.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk2_ardour/export_dialog.cc b/gtk2_ardour/export_dialog.cc index fc1d26f7ea..ef466f4305 100644 --- a/gtk2_ardour/export_dialog.cc +++ b/gtk2_ardour/export_dialog.cc @@ -477,7 +477,7 @@ ExportDialog::show_progress () if (!status->aborted()) { hide(); - if (!ARDOUR::Profile->get_mixbus()) { + if (!ARDOUR::Profile->get_mixbus () && !ARDOUR::Profile->get_livetrax ()) { NagScreen* ns = NagScreen::maybe_nag (_("export")); if (ns) { ns->nag (); diff --git a/gtk2_ardour/simple_export_dialog.cc b/gtk2_ardour/simple_export_dialog.cc index 98b417eec6..8318843751 100644 --- a/gtk2_ardour/simple_export_dialog.cc +++ b/gtk2_ardour/simple_export_dialog.cc @@ -345,7 +345,7 @@ SimpleExportDialog::start_export () if (_post_export_combo.get_active_row_number () == 0) { PBD::open_folder (folder ()); } - if (!ARDOUR::Profile->get_mixbus ()) { + if (!ARDOUR::Profile->get_mixbus () && !ARDOUR::Profile->get_livetrax ()) { NagScreen* ns = NagScreen::maybe_nag (_("Export")); if (ns) { ns->nag (); From 99e2ac28e1a07c37dfaf057fb3068ff02f7e682d Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 1 May 2024 22:30:04 +0200 Subject: [PATCH 10/13] Update debug message to include regionfx and offset --- libs/ardour/audioregion.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 6944e05bca..3f5aaa82b3 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -669,9 +669,6 @@ AudioRegion::read_at (Sample* buf, uint32_t fx_latency = _fx_latency; lm.release (); - DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 read at %3 - %4 to_read: %5 with fx: %6\n", - name(), chan_n, internal_offset, internal_offset + to_read, to_read, have_fx)); - ChanCount cc (DataType::AUDIO, n_channels ()); _readcache.ensure_buffers (cc, to_read + _fx_latency); @@ -698,6 +695,9 @@ AudioRegion::read_at (Sample* buf, n_read = max (0, min (to_read, lsamples - offset)); } + DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 read: %3 - %4 (%5) to_read: %6 offset: %7 with fx: %8 fx_latency: %9\n", + name(), chan_n, readat, readat + n_read, n_read, to_read, internal_offset, have_fx, fx_latency)); + _readcache.ensure_buffers (cc, n_proc); if (n_read < n_proc) { From 631ee17e341bc39a91a1afc2e927720438f2c0ec Mon Sep 17 00:00:00 2001 From: John Emmas Date: Thu, 2 May 2024 10:22:44 +0100 Subject: [PATCH 11/13] Modify class ARDOUR::DiskReader because it now includes members declared using 'thread_local' On Windows, variables defined as having thread storage can have a different address in different threads and as such, they aren't allowed to be imported or exported from a DLL. --- libs/ardour/ardour/disk_reader.h | 88 ++++++++++++++++---------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index 63797e8685..29d5c2164f 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -38,72 +38,72 @@ class MidiPlaylist; template class MidiRingBuffer; -class LIBARDOUR_API DiskReader : public DiskIOProcessor +class /*LIBARDOUR_API*/ DiskReader : public DiskIOProcessor { public: - DiskReader (Session&, Track&, std::string const& name, Temporal::TimeDomainProvider const &, DiskIOProcessor::Flag f = DiskIOProcessor::Flag (0)); - ~DiskReader (); + LIBARDOUR_API DiskReader (Session&, Track&, std::string const& name, Temporal::TimeDomainProvider const &, DiskIOProcessor::Flag f = DiskIOProcessor::Flag (0)); + LIBARDOUR_API ~DiskReader (); - bool set_name (std::string const& str); + LIBARDOUR_API bool set_name (std::string const& str); - std::string display_name () const; + LIBARDOUR_API std::string display_name () const; - static samplecnt_t chunk_samples () + LIBARDOUR_API static samplecnt_t chunk_samples () { return _chunk_samples; } - static void set_chunk_samples (samplecnt_t n) + LIBARDOUR_API static void set_chunk_samples (samplecnt_t n) { _chunk_samples = n; } - static samplecnt_t default_chunk_samples (); + LIBARDOUR_API static samplecnt_t default_chunk_samples (); - void run (BufferSet& /*bufs*/, samplepos_t /*start_sample*/, samplepos_t /*end_sample*/, double speed, pframes_t /*nframes*/, bool /*result_required*/); - void realtime_handle_transport_stopped (); - void realtime_locate (bool); - bool overwrite_existing_buffers (); - void set_pending_overwrite (OverwriteReason); - void set_loop (Location*); + LIBARDOUR_API void run (BufferSet& /*bufs*/, samplepos_t /*start_sample*/, samplepos_t /*end_sample*/, double speed, pframes_t /*nframes*/, bool /*result_required*/); + LIBARDOUR_API void realtime_handle_transport_stopped (); + LIBARDOUR_API void realtime_locate (bool); + LIBARDOUR_API bool overwrite_existing_buffers (); + LIBARDOUR_API void set_pending_overwrite (OverwriteReason); + LIBARDOUR_API void set_loop (Location*); - int set_state (const XMLNode&, int version); + LIBARDOUR_API int set_state (const XMLNode&, int version); PBD::Signal0 AlignmentStyleChanged; - float buffer_load () const; + LIBARDOUR_API float buffer_load () const; - void move_processor_automation (std::weak_ptr, std::list const&); + LIBARDOUR_API void move_processor_automation (std::weak_ptr, std::list const&); /* called by the Butler in a non-realtime context as part of its normal * buffer refill loop (not due to transport-mechanism requests like * locate) */ - int do_refill (); + LIBARDOUR_API int do_refill (); /** For contexts outside the normal butler refill loop (allocates temporary working buffers) */ int do_refill_with_alloc (bool partial_fill, bool reverse); - bool pending_overwrite () const; + LIBARDOUR_API bool pending_overwrite () const; /* Working buffers for do_refill (butler thread) */ - static void allocate_working_buffers (); - static void free_working_buffers (); + LIBARDOUR_API static void allocate_working_buffers (); + LIBARDOUR_API static void free_working_buffers (); - void adjust_buffering (); + LIBARDOUR_API void adjust_buffering (); - bool can_internal_playback_seek (sampleoffset_t distance); - void internal_playback_seek (sampleoffset_t distance); - int seek (samplepos_t sample, bool complete_refill = false); + LIBARDOUR_API bool can_internal_playback_seek (sampleoffset_t distance); + LIBARDOUR_API void internal_playback_seek (sampleoffset_t distance); + LIBARDOUR_API int seek (samplepos_t sample, bool complete_refill = false); - static PBD::Signal0 Underrun; + LIBARDOUR_API static PBD::Signal0 Underrun; - void playlist_modified (); - void reset_tracker (); + LIBARDOUR_API void playlist_modified (); + LIBARDOUR_API void reset_tracker (); - bool declick_in_progress () const; + LIBARDOUR_API bool declick_in_progress () const; - void set_need_midi_catchup (bool); + LIBARDOUR_API void set_need_midi_catchup (bool); /* inc/dec variants MUST be called as part of the process call tree, before any * disk readers are invoked. We use it when the session needs the @@ -112,21 +112,21 @@ public: * don't want any actual disk output yet because we are still not * synced. */ - static void inc_no_disk_output (); - static void dec_no_disk_output (); - static bool no_disk_output () + LIBARDOUR_API static void inc_no_disk_output (); + LIBARDOUR_API static void dec_no_disk_output (); + LIBARDOUR_API static bool no_disk_output () { return _no_disk_output.load (); } - static void reset_loop_declick (Location*, samplecnt_t sample_rate); - static void alloc_loop_declick (samplecnt_t sample_rate); + LIBARDOUR_API static void reset_loop_declick (Location*, samplecnt_t sample_rate); + LIBARDOUR_API static void alloc_loop_declick (samplecnt_t sample_rate); protected: friend class Track; friend class MidiTrack; struct ReaderChannelInfo : public DiskIOProcessor::ChannelInfo { - ReaderChannelInfo (samplecnt_t buffer_size, samplecnt_t preloop_size) + LIBARDOUR_API ReaderChannelInfo (samplecnt_t buffer_size, samplecnt_t preloop_size) : DiskIOProcessor::ChannelInfo (buffer_size) , pre_loop_buffer (0) , pre_loop_buffer_size (0) @@ -135,27 +135,27 @@ protected: resize (buffer_size); } - ~ReaderChannelInfo () + LIBARDOUR_API ~ReaderChannelInfo () { delete[] pre_loop_buffer; } - void resize (samplecnt_t); - void resize_preloop (samplecnt_t); + LIBARDOUR_API void resize (samplecnt_t); + LIBARDOUR_API void resize_preloop (samplecnt_t); Sample* pre_loop_buffer; samplecnt_t pre_loop_buffer_size; bool initialized; }; - XMLNode& state () const; + LIBARDOUR_API XMLNode& state () const; - void resolve_tracker (Evoral::EventSink& buffer, samplepos_t time); + LIBARDOUR_API void resolve_tracker (Evoral::EventSink& buffer, samplepos_t time); - int use_playlist (DataType, std::shared_ptr); - void playlist_ranges_moved (std::list const&, bool); + LIBARDOUR_API int use_playlist (DataType, std::shared_ptr); + LIBARDOUR_API void playlist_ranges_moved (std::list const&, bool); - int add_channel_to (std::shared_ptr, uint32_t how_many); + LIBARDOUR_API int add_channel_to (std::shared_ptr, uint32_t how_many); class DeclickAmp { From a88d430609aa34343b8ce1481145fa2ab19f6ffb Mon Sep 17 00:00:00 2001 From: Ben Loftis Date: Thu, 2 May 2024 12:42:23 -0500 Subject: [PATCH 12/13] null check for a missing session (for LT) --- gtk2_ardour/editor_sections.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gtk2_ardour/editor_sections.cc b/gtk2_ardour/editor_sections.cc index 1fafdcf1bd..d8709e0430 100644 --- a/gtk2_ardour/editor_sections.cc +++ b/gtk2_ardour/editor_sections.cc @@ -228,6 +228,10 @@ EditorSections::scroll_row_timeout () void EditorSections::update_time_selection () { + if (!_session) { + return; + } + _view.get_selection ()->unselect_all (); Selection& selection (PublicEditor::instance ().get_selection ()); @@ -256,6 +260,10 @@ EditorSections::update_time_selection () void EditorSections::selection_changed () { + if (!_session) { + return; + } + TreeView::Selection::ListHandle_Path rows = _view.get_selection ()->get_selected_rows (); if (rows.empty ()) { return; From 27e2348b47cc10f8a03be6fbc4a82d9f743d83cd Mon Sep 17 00:00:00 2001 From: Ben Loftis Date: Thu, 2 May 2024 12:43:12 -0500 Subject: [PATCH 13/13] when capturing a midi pgm change, display the pgm num (for LT) --- libs/ardour/midi_scene_changer.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/ardour/midi_scene_changer.cc b/libs/ardour/midi_scene_changer.cc index 14d0ea6328..d791c1275a 100644 --- a/libs/ardour/midi_scene_changer.cc +++ b/libs/ardour/midi_scene_changer.cc @@ -339,6 +339,7 @@ MIDISceneChanger::program_change_input (MIDI::Parser& parser, MIDI::byte program } loc->set_scene_change (std::shared_ptr (msc)); + loc->set_name( string_compose(_("scene [%1]"), 1+bank*128+(program & 0x7f))); /* this will generate a "changed" signal to be emitted by locations, and we will call ::gather() to update our list of MIDI events.