hydrogen-music / hydrogen

The advanced drum machine for Linux, macOS, and Windows
http://www.hydrogen-music.org
GNU General Public License v2.0
1.04k stars 173 forks source link

Fix JACK synchronization (#1953) #1977

Closed theGreatWhiteShark closed 3 months ago

theGreatWhiteShark commented 4 months ago

The synchronization based on BBT (bar, beat, tick) information provided by the JACK server was broken again (in soo many ways). To fix this (hopefully once and for all), I wrote a large integration test using which two Hydrogen instances interact with each other via the JACK server.

Limitations

I'll do my best to provide a smooth JACK integration. But it turns out JACK does not even properly support tempo changes. Even though it encourage using them in the BBT synchronization.

Unfortunately, this has some really bad UX consequences for Hydrogen: clicking a position on the ruler is not guaranteed anymore to make Hydrogen relocate to this position.

The reason is as follows: Hydrogen calculates the frame for the desired position based on the current tempo and measure information provided by the JACK Timebase master - which will lead to wrong results in case of tempo and measure changes within the master. But that is all we can base our work on. Hydrogen sends this frame to the JACK server, which will broadcast it without any BBT info. We relocate to the desired location. But in the next processing cycle the Timebase master fills the BBT information corresponding to the frame and broadcasts it. The frame position corresponding to the BBT information we will relocate next might not correspond to the frame we specified (e.g. the master did already pass at least one tempo change).

We could disable the position ruler in the presence of an external Timebase master. But I think this is no good. When e.g. taking care of having the same tempo markers in both Hydrogen and the master, relocation would still work fine.

UX changes

Two allow for the integration test to work properly, a couple of new CLI options had to be introduced

Also, the CLI handling was unified between hydrogen and h2cli as well as simplified:

Preferences option JackBBTSyncMethod was dropped

Previously we had two different approaches to react on BBT (bar, beat, tick) information provided by the JACK server: 1) const measure and 2) identical bars.

In 1) we convert BBT into a tick position and relocate to this very point. If patterns in Hydrogen have a different length than the measure used by the Timebase master, bar: 2 will not necessarily result in Hydrogen relocating into pattern 2. But on the upside, transport is consistent. There are no sudden jumps and it does not matter whether the Timebase master relocates to BBT X or lets transport roll from an arbitrary prior point till that position. Hydrogen will be in the exact same position in both cases.

In 2) the bar information would be mapped to our columns. This means a mismatch between pattern length and measure in the master would either result to jumps during playback or inconsistent position when either rolling or relocating to position X. That is no good UX. That is why we suggested in the manual to ensure pattern lengths always match measures in the master. But when doing so this approach yields the same results as 1). So, I just drop it to make things more simple.

Internal changes

The test is not designed to be run in the AppVeyor pipeline (due to missing JACK support in there) but can be run locally on a Linux machine using build.sh i. This sets the additional cmake flag WANT_INTEGRATION_TESTS, due to which the test won't be compiled in our build pipelines.

Debug messages in JackAudioDriver, AudioEngine, and TransportPosition are now hard-coded and can be activated by setting the e.g. JACK_DEBUG macro to 1 at the top of JackAudioDriver.cpp. Also, all three use distinct colors, which makes audio engine-based problems much faster and easier to debug.

The DiskWriterDriver is no longer using EVENT_PROGRESS to indicate success or failure (but only to indicate progress). This had several downsides. Our events are designed to be one-way messages between core and GUI. There is no way the core knows whether export was completed or failed. To complicate things, events are poped from the EventQueue. So, reading them at several places is off the table. Instead, a bool member of the DiskWriterDriver class is now used to indicated success/failure.

Log messages on level Locks do now include the thread ID.

TODO

Fixes #1953