The previous design had a race condition. When WaveViewThreads::stop_threads() was called, it would
first acquire the mutex, then set _quit, then call condition.broadcast(). But worker threads would
check _quit without holding the mutex. It was therefore for a thread to be delayed in its
own lock acquisition by the ::stop_threads() caller, then end up back in cond.wait() AFTER
the cond.broadcast() was done. Such a thread would sleep forever and never wake up.
This new design removes WaveViewDrawRequestQueue, which was a clean encapsulation of the
queueing aspects of WaveViewThreads, but unfortunately made correct mutex acquisition
and condition signalling/waiting needlessly complex. THe mutex, condition variable
and actual queue were moved into WaveViewThreads, and all worker threads execute a method
of the class which gives the appropriate code easy access to the mutex and condition var,
which must always be used together.
```
gtk2_ardour/ardour_ui.cc:2060: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
/usr/include/architecture/i386/math.h:343: note: candidate 1: double pow(double, double)
/usr/include/c++/4.2.1/cmath:357: note: candidate 2: float std::pow(float, float)
```
This replaces a Mutex and adds additional read-locks.
This is needed to address some threading issues with rt-threads
calling auto_loop_location() while the GUI changes locations.
Since locations are C-pointers this is still not entirely safe!
Locations::remove() may delete a location while a pointer
to it is being used in another thread.
Previously the freewheel export thread directly called
Session::butler_transport_work(). The butler thread
may concurrently call the same function. This can lead
double free or memory corruption (see below)
Now export thread summons the butler and does nothing
until it completed its work.
```
Export Thread:
3 XMLNode::~XMLNode
4 ARDOUR::AutomationList::snapshot_history
5 ARDOUR::AutomationList::start_write_pass
6 ARDOUR::Automatable::non_realtime_locate
7 ARDOUR::Route::non_realtime_locate
8 ARDOUR::Session::non_realtime_locate
9 ARDOUR::Session::butler_transport_work
10 ARDOUR::Session::process_export_fw
Butler thread:
7 XMLNode::~XMLNode
8 ARDOUR::AutomationList::snapshot_history
9 ARDOUR::AutomationList::start_write_pass
10 ARDOUR::Automatable::non_realtime_locate
11 ARDOUR::Route::non_realtime_locate
12 ARDOUR::Session::non_realtime_locate
13 ARDOUR::Session::butler_transport_work
14 ARDOUR::Butler::thread_work
15 ARDOUR::Butler::_thread_work
```
Previously x-axis threshold was in samples. This is
useless unless zoomed in to the max. So in most cases
the first-move was always in x-direction, making constrained
y-axis drags near impossible.
Furthermore the threshold for copy-drags was increased
as per #8686
Region positions were updated in the GUI, before the playlist
was catching up.
The butler thread reads a region using the region's new position,
but the playlist's old range.
Thread 1 (GUI)
```
#22 ARDOUR::Playlist::notify_layering_changed()
#26 ARDOUR::AudioPlaylist::region_changed
#27 ARDOUR::Playlist::region_changed_proxy
#35 ARDOUR::Region::send_change
#36 ARDOUR::Region::set_position
#37 RegionRippleDrag::remove_unselected_from_views
#38 RegionRippleDrag::finished
```
LayeringChanged() also triggers DiskIOProcessor::playlist_modified
which schedules a pending-override and summons the butler.
Note that when moving only a few regions the butler starts after all
updates have been completed.
Butler thread:
```
#4 ARDOUR::AudioRegion::read_at
#5 ARDOUR::AudioPlaylist::read
#6 ARDOUR::DiskReader::audio_read
#7 ARDOUR::DiskReader::overwrite_existing_audio
#8 ARDOUR::DiskReader::overwrite_existing_buffers
#9 ARDOUR::Track::overwrite_existing_buffers
#10 ARDOUR::Session::non_realtime_overwrite
```
Region read fails:
```
libs/ardour/audioregion.cc:503 assert (position >= _position);
(gdb) p position
$1 = 1312000
(gdb) p _position
$2 = {<PBD::PropertyTemplate<long>> = {_have_old = true,
_current = 1336000, _old = 1312000} }
```
It is not needed in the relative case and causes issues when taking
sessions between platforms.
On windows, the default path would be "\export". When that is used
on linux the resulting fullpath would end up as
/your/session/path/\export
which is then not found on the file system.
This change is consistent with how relative paths are normally written and
does cure the default path when moved across platforms.
It does not solve the larger issue of mixed directory separators.
A relative path of "export\myMixV1" will still fail when moving between
platforms.
Previously the following were treated as equal
"MIDI_foo" == "MIDI bar"
So std::map<> PortManager::_ports and PortEngineSharedImpl:_ports
could only have either. This resulted in missing ports
and missing calls to existing ports in cycle_start().
This mainly affected MIDI tracks with imported files, since there
is "MIDI Clock in", and imported files result in tracks
"MIDI_file-name".
This issue was introduced in 6.6-200-g60ff3ef764
We do need to know that a reversal is intended before we do a locate, but we should
not enter Reversing before the declick is finished. So instead, we add a new
member, _reverse_after_declick to keep track, and enter Reversing just before
we start the locate