Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jack-midi support #19246

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open

jack-midi support #19246

wants to merge 54 commits into from

Conversation

lyrra
Copy link
Contributor

@lyrra lyrra commented Sep 1, 2023

  • I signed the CLA

Purpose

For linux users, the ability to at runtime switch between alsa and jack audio+midi driver.

Changes

  • Make both jack and alsa(linuxdriver) more similar to osx-driver
  • Make jack and alsa drivers "subdrivers" of linuxdriver (AudioDriverState).

class design changes

Before:

AudioModule
│
├── LinuxAudioDriver
│   │
│   Alsa
│   └──> device: "default"
│
├── OSXAudioDriver
│   ...
│
└── WasapiAudioDriver
    ...

MidiModule
│
├── LinuxAudioDriver
│   │
│   ├────> AlsaMidiOutPort
│   └────> AlsaMidiInPort
│   
:

After:


AudioModule
│
|         MidiModule
│             |
├── AudioMidiManager (was LinuxAudioDriver)
│        |      │
│        |      ├─── JackDriverState
│        |      │    │
│        |      │    (jack-code)
│        |      │    └──> jack client open
│        |      └──> AlsaDriverState
│        |                      │
│        |              (alsa-code)
│        |                      └──> device: "default"
│        │
│        + LinuxMidiOutPort
│        │
│        ├────> JackMidiOutPort
│        ├────> AlsaMidiOutPort
│        + LinuxMidiInPort
|          :
│
├── OSXAudioDriver
│   ...
│
└── WasapiAudioDriver
    ...

MidiModule
│
├── AudioMidiManager (was LinuxAudioDriver)
│   │
│   + LinuxMidiOutPort
│   │
│   ├────> JackMidiOutPort
│   ├────> AlsaMidiOutPort
│   + LinuxMidiInPort
:

Audio Tests

  • start with jack, play and hear audio OK
  • start with alsa, play and hear audio OK
  • start with jack, switch to alsa, then play OK
  • start with alsa, switch to jack, then play OK
  • switch from jack to alsa during play OK
  • switch from alsa to jack during play OK

Midi tests

  • start fluidsynth in verbose mode, musescore and qjackctl (to midi-connect them), verify midi-events are logged by fluidsynth.

Optional cleanups / redesign

Remove LinuxAudioDriver and keep only AlsaAudioDriver and JackAudioDriver as two independent implementations. To switch between them, you need to add another class, something like IAudioDriverProvider. This is what you need to add to IoC (and remove the drivers from IoC). Access the driver through this class, like:

class IAudioDriverProvider ...
{
...

        std::shared_ptr<IAudioDriver> driver() const;
  
        void swithDriver(const std::string& name); 
        async::Chanel<std::string /*name*/> dirverChanged() const;
}


class SomeClass
    Inject<IAudioDriverProvider > audioDriverProvider;


...

     audioDriverProvider()->driver();

This greatly simplifies everything, each class becomes simple and does one thing.
Moreover, such a system is easily scalable; we can easily add other drivers, cross-platform or platform-specific.

Not in scope for this PR

  • MIDI-channel-per-instrument

Known cleanups & fixes before merge

  • send foreign commits upstream:
    Misc. needed for jack #22373
  • cleanup: avoid global in app
  • If possible do mutual dependency Injection between module Audio and Midi.
    Testing confirmed, can't inject modules.
    Injection is used, but not at modul level.
  • de-pollute any interfaces that got sprinkled with data members
  • ensure no public data members begins with m_ (those should be private)
  • remove sample-rate dropdown OR make it work,
    No 'requires restart' added
  • automatic connection to audio ports upon start
    Not in scope, brings in too much arbitrary heuristics code from Mu3.
  • Possible to switch between jack/alsa driver (currently it crashes)
  • Possible to switch bufferSize in jack without crash (possible in alsa)
  • Possible to disable jack-transport via configuration checkbox

@lyrra lyrra force-pushed the jack branch 3 times, most recently from ce43ca7 to b218652 Compare September 1, 2023 09:42
@lyrra lyrra changed the title jack: cleanup, remove usage of static variables jack/alsa: cleanup, remove usage of static variables Sep 1, 2023
@lyrra lyrra force-pushed the jack branch 6 times, most recently from 7638762 to 22128db Compare September 2, 2023 11:18
@lyrra lyrra changed the title jack/alsa: cleanup, remove usage of static variables jack/alsa: switch runtime support Sep 2, 2023
@lyrra lyrra force-pushed the jack branch 2 times, most recently from 999e2a8 to 7a2a941 Compare September 2, 2023 12:29
@lyrra lyrra marked this pull request as ready for review September 2, 2023 13:20
@lyrra lyrra force-pushed the jack branch 7 times, most recently from 3e582e1 to 24705bc Compare September 8, 2023 04:57
@cbjeukendrup cbjeukendrup linked an issue Sep 8, 2023 that may be closed by this pull request
@lyrra lyrra mentioned this pull request Sep 11, 2023
@lyrra lyrra force-pushed the jack branch 4 times, most recently from 31f02f4 to 3331ccb Compare October 26, 2024 12:46
@lyrra
Copy link
Contributor Author

lyrra commented Oct 26, 2024

@toto-polo there's some changes for mac, not sure if the jack api shows up now. If it shows up, it should be the only available audio api (hardcoded to use jack only).

@toto-polo
Copy link

@toto-polo there's some changes for mac, not sure if the jack api shows up now. If it shows up, it should be the only available audio api (hardcoded to use jack only).

Thank you for this new version! Unfortunately, I don't see any changes compared to the previous versions. I still have a dropdown menu with all my audio devices, but Jack does not appear.

Capture d’écran 2024-10-28 à 11 07 36 Capture d’écran 2024-10-28 à 11 08 49

I kept the same testing methodology: launching it as is the first time, then manually signing each subcomponent, and finally signing the entire app. Nothing works.

I noticed in the build the reference to MacOSX14.5.sdk used by Xcode 15.4. I am on macOS 13. Could this have an influence?

Could another macOS user test it as well?

@toto-polo
Copy link

@lyrra You did it ! It works ! Audio and playback sync !

  • I did not have to do any re-sign thing to any component,
  • I saw musescore appearing on Jack Graph on start without doing anything,
  • In the audio device list there is only JACK, like you said earlier,

Awesome !

@toto-polo
Copy link

What about tempo sync with Jack ? Ardour and Musescore cannot share their tempo. Is it possible to have a Controller and a Remote ?

@cfirwin3
Copy link

cfirwin3 commented Nov 1, 2024

What about tempo sync with Jack ? Ardour and Musescore cannot share their tempo. Is it possible to have a Controller and a Remote ?

@toto-polo
In my opinion, that sort of feature is beyond the scope of this project. Mere synchronization allows one to set identical meter and tempo changes in each synchronized application so that all features of a score work properly in time.
Be aware, that this is fairly easy to do just from a standpoint of midi export. You can export a midi track from M4, import it into any DAW that does meter and tempo mapping from .mid files (Ardour does this) and then everything will be aligned. I have demonstrated on my YouTube videos how there is a static delay with the M4 play engine and this would still need to be addressed with an offset measure and tempo change... but that's a small price to pay.

For features like what you are asking about, you would need to have Muse Group take on a DAW project that will be synchronized with M4 as a sister application (like Dorico/Cubase). At the end of the day, meter and tempo changes are not live/dynamic attributes of a DAW or Notation program. Those elements are independently programmed into their respective projects. The passage of TIME is what is being synchronized through ReWire or Jack Transport-like connections that use timecode and midi clock.

Comment on lines +62 to +63
${CMAKE_CURRENT_LIST_DIR}/internal/platform/lin/audiodeviceslistener.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/platform/lin/audiodeviceslistener.h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these two lines shouldn't be here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's used by m_devicesListener in AudioMidiManager

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not ideal, since the OSXAudioDriver has its own, more efficient, way of detecting changes in the devices list. Perhaps a way can be found to reuse that?

@@ -211,6 +211,8 @@ void ConsoleApp::applyCommandLineOptions(const CmdOptions& options, IApplication
notationConfiguration()->setTemplateModeEnabled(options.notation.templateModeEnabled);
notationConfiguration()->setTestModeEnabled(options.notation.testModeEnabled);

audioConfiguration()->setAudioDelayCompensate(options.audio.audioDelayCompensate.value_or(1024)); // FIX: equal to buffer-size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be moved to GuiApp, otherwise it doesn't have any effect, because ConsoleApp is only used when running in CLI mode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to move this as a gui preferences option in jack section

Comment on lines +293 to +300
elseif (OS_IS_WIN AND (NOT (MINGW)))
set(MODULE_LINK ${MODULE_LINK} winmm mmdevapi mfplat)
elseif (OS_IS_LIN)
find_package(ALSA REQUIRED)
set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${ALSA_INCLUDE_DIRS} )
set(MODULE_LINK ${MODULE_LINK} ${ALSA_LIBRARIES} pthread )
else ()
if (OS_IS_MAC)
find_library(AudioToolbox NAMES AudioToolbox)
find_library(CoreAudio NAMES CoreAudio)
set(MODULE_LINK ${MODULE_LINK} ${AudioToolbox} ${CoreAudio})
elseif (OS_IS_WIN)
set(MODULE_LINK ${MODULE_LINK} winmm mmdevapi mfplat)
elseif (OS_IS_LIN)
find_package(ALSA REQUIRED)
set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${ALSA_INCLUDE_DIRS} )
set(MODULE_LINK ${MODULE_LINK} ${ALSA_LIBRARIES} pthread )
endif()
set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${ALSA_INCLUDE_DIRS})
set(MODULE_LINK ${MODULE_LINK} ${ALSA_LIBRARIES} pthread)
elseif (MINGW)
set(MODULE_LINK ${MODULE_LINK} winmm mmdevapi mfplat)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The elseif (OS_IS_WIN AND (NOT (MINGW))) and elseif (MINGW) cases can be combined

Comment on lines 284 to +286
find_package(Jack REQUIRED)
set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${JACK_INCLUDE_DIRS} )
set(MODULE_LINK ${MODULE_LINK} ${JACK_LIBRARIES} pthread )
set(MODULE_INCLUDE_PRIVATE ${MODULE_INCLUDE_PRIVATE} ${JACK_INCLUDE_DIRS})
set(MODULE_LINK ${MODULE_LINK} ${JACK_LDFLAGS} pthread)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Often, a find_package also generates an imported target, probably called something like Jack::Jack. If that is the case, it would be enough to list(APPEND MODULE_LINK Jack::Jack), and that would set up include directories automatically. Might be worth trying.

Comment on lines +117 to +142
#if defined(JACK_AUDIO)
if (deviceId == "jack") {
bool transportEnable = playbackConfiguration()->jackTransportEnable();
m_current_audioDriverState = std::make_unique<JackDriverState>(this, transportEnable);
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
} else if (deviceId == "alsa") {
m_current_audioDriverState = std::make_unique<AlsaDriverState>();
#endif
//#ifdef Q_OS_MACOS
// } else if (deviceId == "osx") {
// m_current_audioDriverState = std::make_unique<OSXAudioDriverState>();
//#endif
#else
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (deviceId == "alsa") {
m_current_audioDriverState = std::make_unique<AlsaDriverState>();
#endif
//#ifdef Q_OS_MACOS
// m_current_audioDriverState = std::make_unique<OSXAudioDriverState>();
//#endif
#endif
} else {
LOGE() << "Unknown device name: " << deviceId;
return false;
}
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if defined(JACK_AUDIO)
if (deviceId == "jack") {
bool transportEnable = playbackConfiguration()->jackTransportEnable();
m_current_audioDriverState = std::make_unique<JackDriverState>(this, transportEnable);
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
} else if (deviceId == "alsa") {
m_current_audioDriverState = std::make_unique<AlsaDriverState>();
#endif
//#ifdef Q_OS_MACOS
// } else if (deviceId == "osx") {
// m_current_audioDriverState = std::make_unique<OSXAudioDriverState>();
//#endif
#else
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (deviceId == "alsa") {
m_current_audioDriverState = std::make_unique<AlsaDriverState>();
#endif
//#ifdef Q_OS_MACOS
// m_current_audioDriverState = std::make_unique<OSXAudioDriverState>();
//#endif
#endif
} else {
LOGE() << "Unknown device name: " << deviceId;
return false;
}
return true;
#if defined(JACK_AUDIO)
if (deviceId == "jack") {
bool transportEnable = playbackConfiguration()->jackTransportEnable();
m_current_audioDriverState = std::make_unique<JackDriverState>(this, transportEnable);
return true;
}
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (deviceId == "alsa") {
m_current_audioDriverState = std::make_unique<AlsaDriverState>();
return true;
}
#endif
//#ifdef Q_OS_MACOS
// if (deviceId == "osx") {
// m_current_audioDriverState = std::make_unique<OSXAudioDriverState>();
// return true;
// }
//#endif
LOGE() << "Unknown device name: " << deviceId;
return false;


bool AudioMidiManager::open(const Spec& spec, Spec* activeSpec)
{
// re-initialize devide
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// re-initialize devide
// re-initialize device

?

@@ -82,6 +90,7 @@ if (OS_IS_MAC)
elseif (OS_IS_WIN)
set(MODULE_LINK ${MODULE_LINK} winmm)
elseif (OS_IS_LIN)
# FIX: why does jack sources compile without a find_library here?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is because the include dirs of some other package are just /usr/local/include and Jack is also installed to that location?

@toto-polo
Copy link

In my opinion, that sort of feature is beyond the scope of this project. Mere synchronization allows one to set identical meter and tempo changes in each synchronized application so that all features of a score work properly in time. Be aware, that this is fairly easy to do just from a standpoint of midi export. You can export a midi track from M4, import it into any DAW that does meter and tempo mapping from .mid files (Ardour does this) and then everything will be aligned. I have demonstrated on my YouTube videos how there is a static delay with the M4 play engine and this would still need to be addressed with an offset measure and tempo change... but that's a small price to pay.

For features like what you are asking about, you would need to have Muse Group take on a DAW project that will be synchronized with M4 as a sister application (like Dorico/Cubase). At the end of the day, meter and tempo changes are not live/dynamic attributes of a DAW or Notation program. Those elements are independently programmed into their respective projects. The passage of TIME is what is being synchronized through ReWire or Jack Transport-like connections that use timecode and midi clock.

Thank you for your feedback!

In reality, the JACK Transport engine already knows how to handle tempo and can easily share it between different applications. For example, it is easy to modify the tempo in Ardour and see this tempo change in Hydrogen or even in modules like jack_midi_clock.

I think the question is rather about managing the priority of the application that will have control over tempo management. A priori, Ardour is designed to be the Time Master. Perhaps it would be wise to make Musescore the Time Master, as tempo management on a score is slightly more rigid than in a traditional DAW.

The only current limitations seem to be primarily in the management of "tempo ramps," such as accelerandos and ritardandos.

https://github.com/x42/jack_midi_clock
https://manpages.org/jack_midi_clock

A Timebase Master tempo utility example:
https://manpages.org/klick

A 15-year-old thread:
https://jack-devel.jackaudio.narkive.com/CQBTDfH0/jack-transport-improvements

@cfirwin3
Copy link

cfirwin3 commented Nov 3, 2024

In reality, the JACK Transport engine already knows how to handle tempo and can easily share it between different applications. For example, it is easy to modify the tempo in Ardour and see this tempo change in Hydrogen or even in modules like jack_midi_clock.

I think the question is rather about managing the priority of the application that will have control over tempo management. A priori, Ardour is designed to be the Time Master. Perhaps it would be wise to make Musescore the Time Master, as tempo management on a score is slightly more rigid than in a traditional DAW.

The only current limitations seem to be primarily in the management of "tempo ramps," such as accelerandos and ritardandos.

Meter? Specifically, meter changes?
The controls and score format congruence issues are deeper than merely how fast the click/beat is and what controls it.

@toto-polo
Copy link

Meter? Specifically, meter changes?
The controls and score format congruence issues are deeper than merely how fast the click/beat is and what controls it.

I haven't tested it myself, but it's clearly stated in the official documentation with the functions :

float jack_position_t::beat_type
Time signature "denominator"

float jack_position_t::beats_per_bar
Time signature "numerator"

https://jackaudio.org/api/group__TransportControl.html

@cfirwin3
Copy link

cfirwin3 commented Nov 3, 2024

Meter? Specifically, meter changes?
The controls and score format congruence issues are deeper than merely how fast the click/beat is and what controls it.

I haven't tested it myself, but it's clearly stated in the official documentation with the functions :

float jack_position_t::beat_type Time signature "denominator"

float jack_position_t::beats_per_bar Time signature "numerator"

https://jackaudio.org/api/group__TransportControl.html

In both Ardour and M4, meter is an element that is placed by the composer. Jack would have no ability to dynamically change the time signature and division of beats.

@toto-polo
Copy link

The controls and score format congruence issues are deeper than merely how fast the click/beat is and what controls it.

It is actually quite practical for communicating with applications external to JACK ;) For now, it requires using Ardour to generate a Midi Master Clock.

Tempo sync Ardour

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MuseScore not supporting JACK audio anymore (Linux)