Backend Audio Fundamentals

Hi… standard disclaimer I’m a beginner to software dev as a whole (~3 months). I am trying to understand juce fundamentally, the ‘core anatomy’ of juce’s audio structure and what the most minimal ‘audio system’ setup would be. Not accounting for gui. I want to use juce as the audio-only ‘sector’ for a standalone application that will use QML for gui. I’ve had my head buried (learning CMake, build process, cpp, ect… before I even reached this point) and my thoughts aren’t completely consolidated, but I will try and make this coherent. I will denote questions like this as I go.

In the attached repo, there is the most simple juce audio program. About this program :

- There is no gui.
- It plays noise.
- no JUCEApplicationBase is created.
- no JUCE_START_APPLICATION macro is used, but its own main().
- it uses C++20 modules, and builds/runs without any assertions.

After some study, it seems the most basic audio structure in a nutshell is :
1. AudioDeviceManager to find/open/start your hardware audio unit. Is AudioDeviceManager the link from your program to your systems audio driver that is running on your OS, outside the program?
2. AudioSourcePlayer that is input to the AudioDeviceManager.addAudioCallback(), added to the AudioDeviceManager internal juce_AudioDeviceManager.h - 511: Array<AudioIODeviceCallback*> callbacks;.
3. AudioSource contains the actual signals, it is input to the AudioSourcePlayer.setSource().

If you set this up and sleep your main thread so it doesn’t exit, the noise will play. But, juce assertions happen. There are two ‘sets’ :

  1. JUCE Assertion failure in juce_AsyncUpdater.cpp:78
    JUCE Assertion failure in juce_AsyncUpdater.cpp:78
    JUCE Assertion failure in juce_MidiDeviceListConnectionBroadcaster.cpp:48
    JUCE Assertion failure in juce_AsyncUpdater.cpp:78

These are triggered by the AudioDeviceManager, when you create a instance of it - so they are triggered somewhere in the chain of events starting from it’s constructor.
These assertions test if a MessageManager exists. So, one must exist before AudioDeviceManager is created. In AudioDeviceManager.ixx, you see ‘Messenger’ is created - and these assertions went away.

  1. *** Leaked objects detected: 1 instance(s) of class ALSADevice
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 1 instance(s) of class ALSAThread
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 1 instance(s) of class Thread
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 2 instance(s) of class WaitableEvent
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 1 instance(s) of class ALSAAudioIODeviceType
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 6 instance(s) of class StringArray
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104
    *** Leaked objects detected: 1 instance(s) of class MessageManager
    JUCE Assertion failure in juce_LeakedObjectDetector.h:104

These happen between the sleep timer on the main thread expiring, and on the programs way to exit. A ‘leak’ is a object you created on the heap and is allocated in memory, but your program has lost all track of it. It cannot use it nor know it exists, and will forever ‘float in memory’ uselessly until the program exits, right?

On a tip from jules here https://forum.juce.com/t/initialize-juce-gui-and-startup-event-loop/24028, I found the main() that is run from JUCE_START_APPLICATION macro. You can find it in juce_ApplicationBase.cpp - line 263.
In it you find this : MessageManager::getInstance()->runDispatchLoop();. This is the ‘main/ui event loop’ for out-of-the-box juce applications, that runs forever until you call MessageManager.stopDispatchLoop().

So, instead of sleeping the main thread, call Messenger->runDispatchLoop(). And, the leak assertions go away - but because I’m terminating in my IDE, and stopDispatchLoop is never called… Is the MessageManager responsible for managing the life of the objects listed above, created by the AudioDeviceManager? If you changed your audio playback device like you would in a DAW, those objects created by the AudioDeviceManager wouldn’t be destroyed?

Now we are at the state of the example repo. From here, you can run the program and play noise, no assertions along the way. You have to kill your program manually in your IDE.

But, if you want to use QML for your ‘main/ui thread’… obviously you can’t run two infinite loops in the same thread…so uhh…I’m just going to rattle off a bunch of questions if thats alright…

1. *What exactly do the MessageManager and AudioDeviceManager communicate? What is there relationship?. I know it's possible to play audio without the MessageManager.*
2. *Is the MessageManager like the bridge between your GUI and the 'audio sector'?.*
3. *In a normal juce application, does the AudioDeviceManager run on the same thread as MessageManager?*
4. *What exactly runs on this 'dedicated high priority audio thread' I hear about - just the processBlock()s of AudioSources?*
5. *Could you run MessageManager on it's own thread, and QML event loop on its own thread?*
6. *Could you communicate to your 'audio sector' (AudioDeviceManager, ect..) from QML, through the 'Messenger Thread'?*
7. *Is the MessageManager even critial for audio-only juce subsystem? Those assertions only pertain to a normal juce application setup?*
8. *If I use the MessageManager, I could re-implement whats needed in QML- essentially integrate it? Which would solve double event loop problem*

I appreciate any input you have - whether to the questions or any thoughts in general, any related info is good…

Most Minimal Backend Audio Setup

too busy to help now, in mean time we suggest & encourage you use GTK+ GUI SDK in addition to JUCE, learn & become familiar with both to broaden your development scope & help improve by reporting bugs etc to teams worldwide involved developing GTK & JUCE etc

https://www.gtk.org/

thanks, taking a look

I am not sure how the suggestion to look into GTK would be helpful here for the original poster’s problem? The original poster already mentioned the intention is to use Qt/Qml for the GUI.

lol your right but I did say ‘any related info is good’ so thats enough to make it valid I suppose x). Surprised I haven’t heard of GTK actually, but QML is too pretty to give up, and I know people have gotten it to work. I noticed he was the one in the forum link from 7 years ago asking about using your own event loop, looks like he’s been down this road so your welcome to the party :slight_smile:

When you get assert failures, the first thing to check is, what actually failed. Look the AsyncUpdater:

Since you didn’t spin up a juce application, this message manager is missing.

There is a simple object that manages all initialisations for you, called ScopedJuceInitialiser__GUI

Just create one in yout QApplication or where it fits the QML (it’s been a while that I used QML). It just needs to exist somewhere so it is deleted at the end of the program. At the start of your int main() is a good place too.

If you are lucky, the existence of the initialiser might solve your leaks too, because without some automatic cleanup will also not happen.
But they could still be genuine leaks too which need separate addressing.

Why that requirement? C++ modules are new and unproven technology and you will likely encounter all kinds of extraneous issues that few people will be able to help you with. (I for example didn’t completely understand what was going on in your source code repo you linked to.)

I didn’t want to start messing around with Qt/Qml or C++ modules, but here’s a simple example I wrote that uses Juce for the audio and the Choc library for the event loop/GUI :

IMHO, literally every one of these questions would best be answered by doing the JUCE tutorials, which educate the new JUCE developer on its architecture and the reasoning behind design decisions, both as an application and a plugin framework. You can write a JUCE application, but JUCE plugins are often the primary product, so it’s valuable for newcomers to understand the JUCEApplication class in addition to plugin heuristics.

https://juce.com/learn/tutorials/


1. What exactly do the MessageManager and AudioDeviceManager communicate? What is their relationship? I know it’s possible to play audio without the MessageManager.

The MessageManager primarily manages JUCE’s GUI thread and message dispatching, while AudioDeviceManager handles audio device selection and callbacks. They don’t communicate directly but operate independently on separate threads, ensuring real-time performance and avoiding conflicts.

See the notes at:
JUCE/modules/juce_audio_processors/processors/Juce_AudioProcessor.h


2. Is the MessageManager like the bridge between your GUI and the ‘audio sector’?

Instead of thinking in terms of “sectors,” consider the core component of each module as its own thread. The GUI thread, managed by MessageManager, and the high-priority audio thread are distinct and should remain separate to avoid performance issues. When interaction is needed, JUCE provides mechanisms like AsyncUpdater to safely communicate between these threads without compromising real-time audio.


3. In a normal JUCE application, does the AudioDeviceManager run on the same thread as MessageManager?

No, AudioDeviceManager coordinates with AudioIODevice to manage audio processing on the audio thread, separate from the GUI thread, ensuring device management doesn’t interfere with GUI performance.


4. What exactly runs on this ‘dedicated high-priority audio thread’ I hear about - just the processBlock()s of AudioSources?

Yes, the high-priority audio thread runs processBlock(), handling the audio resources of your application or plugin as efficiently as possible. In plugin contexts, it also manages other essential real-time tasks like AudioProcessorcallbacks.

https://docs.juce.com/master/tutorial_playing_sound_files.html


5. Could you run MessageManager on its own thread, and QML event loop on its own thread?

Direct modification of MessageManager for custom event processing isn’t typical in JUCE. For QML integration, it’s better to create a bridge that aligns JUCE and QML event loops without altering MessageManager.


6. Could you communicate with your ‘audio sector’ (AudioDeviceManager, etc.) from QML through the ‘Messenger Thread’?

In an audio-only context, MessageManager isn’t critical as it primarily manages the GUI thread, not the audio subsystem. JUCE’s audio engine can operate without MessageManager if there’s no GUI.


7. Is the MessageManager even critical for the audio-only JUCE subsystem? Does this apply only to normal JUCE application setups?

Correct, MessageManager isn’t essential for audio-only contexts since it primarily handles GUI threads. The audio subsystem in JUCE can operate independently.


8. If I use the MessageManager, could I re-implement what’s needed in QML and essentially integrate it to solve the double event loop problem?

Integrating QML with JUCE’s threading and messaging system typically requires a separate thread or synchronization method rather than direct integration via MessageManager. Generally, QML’s event loop operates independently with defined synchronization points for necessary communication.

First of all, the MessageManager is not a thread (with the exception of linux). Instead it hooks into the OS messaging system, e.g. on windows it receives and sends the WM_XXX messages.
QT does the same, so there is no synchronisation necessary. The message queue is the synchronisation.

In addition there is the audio thread, which is the audio driver abstracted away behind an juce::AudioIODevice. In a plugin it is the host calling processBlock of the juce::AudioProcessor. The audio thread calls ONLY the processBlock or in an app the AudioIODeviceCallback. From there it can call all kinds of things, it could be an AudioSourcePlayer, an AudioProcessorPlayer or a bespoke AudioIODeviceCallback.

Partially ‘true’, except it runs on the “main thread”, as all applications have at least one thread.

just the kind of help I was looking for, thanks everyone! Alot to chew on, let me go through in order here…hours later ok this is going to take some time to digest, so I’m just posting this to say thanks, and I will follow up when the dust has settled a bit.

1 Like

Okay! I had a think about things. In order of the replys…

When you get assert failures, the first thing to check is, what actually failed. Look the AsyncUpdater:

I did find this when I was debugging, which led me to the MessageManager for the first time. At first glance, it just forwarded to’MessageManager::getInstance();', which I just directly called in my first project.

After taking another look after you brought it up I see it’s doing more. Especially the call to shutdownJuce_GUI(), which is in the ScopedJuceInitialiser_GUI destructor. I learned when main() reaches the end of execuction, objects created in main() (that are created with ‘automatic storage duration’) will have there destructor called before the program completely terminates.

So ScopedJuceInitialiser_GUI in your main would call shutdownJUCE_GUI when main() ends. As you said ‘If you are lucky, the existence of the initialiser might solve your leaks too’. Perhaps shutdownJUCE_GUI() deallocates juce objects (like Audio* related objects, the ones that were leaking) gracefully? Will expand on shutdownJuce_GUI() in the codebase later.

Meanwhile I mapped out initialiseJuce_GUI() for the record/any curious lurkers:

Why that requirement? C++ modules are new and unproven technology and you will likely encounter all kinds of extraneous issues that few people will be able to help you with. (I for example didn’t completely understand what was going on in your source code repo you linked to.)

I just heard it was the new way forward. That new codebases should adopt it, and old ones should consider migrating (its not going well https://arewemodulesyet.org/). Not a requirement here, cpp modules/qml is only a side quest, this is about juce architecture!

I don’t know how familiar you are with modules and I don’t want to go on a long tangent, but I will say this for anyone interested : One benefit of cpp modules is build time gains.

If you declare a class in the public fragment (like you would in a header) and your implementations in the private fragment and build your project. Then change a implementation and build again - only that module gets re-compiled, not everything that imports it.

Modules spit out a preliminary ‘interface file’ alongside the usual object file, into your build output tree (on clang its .pcm I think). They contain declarations of symbols that were marked with export (or inside a parent export{} braces) from the public fragment, that is - everything you can import elsewhere.

During compilation, anything that imports a module will find the interface file, and create ‘Stubs’ of the symbols in its object file. During linking, stubs are replaced with implementations from that modules object file. So if you change a module class method after initial build - things that import it never knew, and doesnt trigger recompilation. It only sees the public fragment via the interface file during compilation, and updates during linking. I like the design more then header/source personally. C++ trying to be more pythonic lol. Anyways!

here’s a simple example I wrote that uses Juce for the audio and the Choc library for the event loop/GUI :

the MyAudioCallback is very interesting, it seems you bypassed the need for (or reimplemented) AudioSourcePlayer/AudioSource. Looks like your combining them, alongside AsyncUpdater, which I have been wondering about. Its a bit over my head at first glance, but I’m going to mark this and come back to it, I think im missing some fundamentals at the moment. And I see your using ScopedJuceInitialiser_GUI, not calling MessageManager::getInstance() directly, as dan also hinted to. Thanks!

literally every one of these questions would best be answered by doing the JUCE tutorials

hehe, I have gone through a bunch of them (they got me this far), but many are left. Maybe I jumped the gun on this post a bit. But if I didn’t, we wouldn’t have this nice post from you :stuck_out_tongue: will definetly be finishing those up tho.

The MessageManager primarily manages JUCE’s GUI thread and message dispatching

Noted! thanks. When you look at the main() from JUCEApplicationBase, it creates the juce app (and thus the interface) and the MessageManager there. So just getting it locked the ‘GUI Thread’ must be the thread main() runs on, acoording to normal juce convention.

AudioDeviceManager handles audio device selection and callbacks

thanks for confirming, I had dug into AudioDeviceManager a bit from tangets off tutorials, and came to some understanding of that.

Just for the record and if anyone wants to double check me - when you call AudioDeviceManager.initialise(). Basically it will find a subclass of AudioIODevice that matches your system, on my linux box its class ALSAAudioIODevice final : public AudioIODevice. cross platform juce in action!

It will create a instance of that class, and assign it to this internal pointer in AudioDeviceManager, 510: std::unique_ptr<AudioIODevice> currentAudioDevice;.

Then it calls currentAudioDevice->open(), and then currentAudioDevice->start(). You see in currentAudioDevice->start (callbackHandler.get());, it passes the AudioDeviceManagers nested 546:std::unique_ptr<CallbackHandler> callbackHandler; into the function.

The CallBackHandler was first created, when you created AudioDeviceManager - thats all it’s constructor does :

AudioDeviceManager::AudioDeviceManager()
{
    callbackHandler.reset (new CallbackHandler (*this));
}

What is confusing is how AudioDeviceManager holds a CallBackHandler, and the CallBackHandler holds owner, which is the AudioDeviceManager LOL. Typical confusing C++ design patterns.

And when you call currentAudioDevice->start(), It passes in the CallBackHandler. Inside, is passes itself (the AudioIODevice subclass) into a method of the CallBackHandler. See what I mean? its almost like a ‘recursive double loop’, hard to grasp. A class method takes in a input, then calls a method of the input and plugs itself into that. /psyduck.gif.

Ultimately, AudioDeviceManager.initialise() chain of events seems (?) to end at calling callbacks.audioDeviceAboutToStart(). callbacks is a array of AudioIODeviceCallBack objects, nested in the AudioDeviceManager. You added to the array with AudioDeviceManager.addAudioCallback(). Note AudioSourcePlayer inherits from AudioIODeviceCallback, it is inside the callbacks.

I tried to map it out here. Probably missed a bunch of stuff, you may need to blow up the image on desktop :

With all that aside, the goal of the AudioDeviceManager (or at least one of the primary ones), is to fetch your samples, and forward them to the audio driver on your OS, right?

Typically AudioSource holds the samples. with AudioSourcePlayer.setSource(AudioSource), and then AudioDeviceManager.addAudioCallback(AudioSourcePlayer), I see the path from your samples to the AudioDeviceManager.

But what method is called on AudioDeviceManager for it to return samples (or ‘trigger the callbacks’), and what calls it? I tried digging in the codebase and maybe found the answer to ‘what method’ : AudioDeviceManager.audioDeviceIOCallbackInt(). I have a diagram to explain…

So I see the path in the code for your AudioDeviceManager to return the samples - leaving

Question 1 : What calls AudioDeviceManager.audioDeviceIOCallbackInt and how does it call it?.

man this stuff is super complicated… You will never grasp it the first time. For now I am going to leave the pieces on the ground and walk away like a coward…

Instead of thinking in terms of “sectors,” consider the core component of each module as its own thread.

Ok, got it. That makes some sense because MessageManager is in juce_events, and AudioDeviceManager is in juce_audio_devices. Since we know juce modules are kind of self contained (right?) You shouldn’t think there is a strict dependancy between them. As you said the audio subsystem can work without the messaging system.

When interaction is needed, JUCE provides mechanisms like AsyncUpdater to safely communicate between these threads without compromising real-time audio.

Ok good to have a high level point of AsyncUpdater - it handles communication between threads. It was a mystery. I will probably learn more in one of the juce tutorials.

Yes, the high-priority audio thread runs processBlock(), handling the audio resources of your application or plugin as efficiently as possible. In plugin contexts, it also manages other essential real-time tasks like AudioProcessorcallbacks.

Copy! hadn’t got to that tutorial yet my bad.

Direct modification of MessageManager for custom event processing isn’t typical in JUCE. For QML integration, it’s better to create a bridge that aligns JUCE and QML event loops without altering MessageManager.

and

Integrating QML with JUCE’s threading and messaging system typically requires a separate thread or synchronization method rather than direct integration via MessageManager.

Roger that… try to be as ‘hands off’ when screwing with juce, and let it do it’s thing. Try to make clean, seperate threads that link to juce as it is, instead of trying to weave crazy integrations into the existing juce classes. Actually I tried a project where I subclassed juce::Thread and created MessageManager in it, and called runDispatchLoop() inside the juce::Thread.run(). No idea what I was doing, but it become clear I totally broke juce::Thread, because it’s designed to check threadShouldExit() in a while loop, and all the other thread methods work with it. So yea, i see what your saying…

Okay! checkpoint. I think I got a grasp of everything above to some degree. Just the question about what calls AudioDeviceManager.audioDeviceIOCallbackInt(), Not important right now…

Just to clarify two things before a conclusion :

First of all, the MessageManager is not a thread

Right, it is a object. A thread and a object are totally different concepts. And even if a object was designed to have ‘stuff’ that runs on a different thread, just creating that object doesn’t mean the ‘stuff’ will start running on that thread it was instanced on, or any other. That’s how juce::Thread works right. What is inside juce::Thread.run(), is what gets executed on a different thread. and you have to call start() to trigger run(). I think I saw the AudioSampleBuffer (Advanced) tutorial goes over juce::Thread. I’ll get to it…

except it runs on the “main thread”

So I confirmed this by looking at the usual juce main(), here again :

int JUCEApplicationBase::main()
{
    ScopedJuceInitialiser_GUI libraryInitialiser;
    jassert (createInstance != nullptr);

    const std::unique_ptr<JUCEApplicationBase> app (createInstance());
    jassert (app != nullptr);

    if (! app->initialiseApp())
        return app->shutdownApp();

    JUCE_TRY
    {
        // loop until a quit message is received..
        MessageManager::getInstance()->runDispatchLoop();
    }
    JUCE_CATCH_EXCEPTION

    return app->shutdownApp();
}

and when you expand runDispatchLoop :

```cpp
void MessageManager::runDispatchLoop()
{
    jassert (isThisTheMessageThread()); // must only be called by the message thread

    while (quitMessageReceived.get() == 0)
    {
        JUCE_TRY
        {
            if (! detail::dispatchNextMessageOnSystemQueue (false))
                Thread::sleep (1);
        }
        JUCE_CATCH_EXCEPTION
    }
}

// must only be called by the message thead ← note this. and, in the MessageManager constructor :

MessageManager::MessageManager() noexcept
  : messageThreadId (Thread::getCurrentThreadId())

sets messageThreadId to the current thread (we instanced it in main via ScopedJuceInitialiser_GUI libraryInitialiser) and :

bool MessageManager::isThisTheMessageThread() const noexcept
{
    const std::lock_guard<std::mutex> lock { messageThreadIdMutex };

    return Thread::getCurrentThreadId() == messageThreadId;
}

The point is, and what I just want to have on the record for any other beginners that find this in the future : The GUI Thread is the Message Thread is the Thread main() runs on, in a normal juce application. They are all the same.

Finally,

JUCE’s audio engine can operate without MessageManager if there’s no GUI.

Understood, but given you want a gui, even if it is a different framework (qml), that means you should have a MessageManager. Ok got it. And as you said, if your using a different gui framework, make a seperate thread instead of trying to integrate. Right, we went over this, moving on…

Question 2 : How does MessageManager.runDispatchLoop() fit into the picture, with a application that uses a different gui framework? And, is runDispatchLoop() critial for the MessageManager to function?

I’m still a bit vague on what the messaging system is actually sending, I know it work with the GUI tho. The confusion comes, because runDispatchLoop is a blocking loop. It is the native juce event loop. Usually, it runs in main(). But we can’t run it in main(). Or else it would block the QML event loop. And, it’s best to not mix them in one. This is why I did that project to runDispatchLoop() in a juce::Thread.run(), to leave main() open for QML. But this can’t be the right way. If we don’t need runDispatchLoop for MessageManager to function, problem solved. Maybe the answer is found traversing runDispatchLoop code, maybe in tutorials. I noticed in Xenokios project, he never called runDispatchLoop(). Is having two event loops in one application even a thing people do?

In summary, there is really only two outstanding questions. I did a bit of a brain dump, of course you can correct anything If I’m mistaken. From here, I’m going to put a pin in this train of thought and finish all the tutorials. I’m sure they will fill in the blanks somewhat.

But first I will now go explore this place they call the ‘out-side’.

I don’t, because I am using the message loop from the Choc library with choc::messageloop::run(); The Juce MessageManager “magically” incorporates itself into that. Works that way at least on Windows, I can’t easily test at the moment if it actually would work the same on Mac and Linux, but I would expect so.

By the way, I am not sure if this is completely correct :

JUCE’s audio engine can operate without MessageManager if there’s no GUI.

The Juce audio classes depend on being able to do async operations in the GUI/main thread and those won’t work if the Juce MessageManager hasn’t been initialised. For example, the Mac CoreAudio and Windows WASAPI and ASIO implementations in Juce use AsyncUpdater and Timer, which need a message loop running.

However, you don’t have to actually run the Juce MessageManager message loop if you already have a message loop running, like I did with Choc in my example. I’d expect it to work in a Qt app something like this :

int main(int argc, char *argv[])
{
    juce::ScopedJuceInitialiser_GUI guiInit;
    // Init the Juce audio stuff
    // ...
    QApplication a(argc, argv);
    Window w;
    w.show();
    auto result = a.exec();
    // shutdown the Juce audio stuff if needed
    return result;
}

I can’t unfortunately test if this would actually work since I don’t have Qt set up for development and I don’t want to do it just to test this. (Qt is way too bloated and complicated to set up for my taste.)

By the way, another thing you will want to watch out is using Juce classes as global variables, some of them won’t work correctly used that way because of C++ initialization/deinitialization ordering issues.

ah okay, so choc solved that problem… and as you said maybe qt does too, but not confirmed.

By the way, another thing you will want to watch out is using Juce classes as global variables, some of them won’t work correctly used that way because of C++ initialization/deinitialization ordering issues.

Thanks, noted.

I asked a LLM 'Is it common to run two event loops in one application?" and it said this

" Some frameworks offer a way to “pump” or “poll” their event loops manually, allowing you to integrate two loops more smoothly. For instance, Qt allows a custom event loop to integrate with its main loop by periodically calling processEvents() ."

If in timestamp below, they mention a polling thread for audio to gui communication, so maybe this how you would do (given you did have to use runDispatchLoop, or whatever is it you have to do to forward from audio thread). You would have to set it up to run in a std or pthread tho, as juce::Thread wasn’t designed for that.

Anyways getting a bit ahead of myself. but good to look ahead a bit to know it’s possible and add belief in your purpose. It’s gonna take at least 6 months of study before I should go much further here. Might remake my first project to update some of the things I learned here, and try a CMake build (juce and qml have there own exe targets, so that will probably be tricky) but after that its pencils down and back to the basics. Thanks again everyone

This is also true of the old header/cpp file idiom. If only the cpp file changes and not the header, then other TUs that only include the header don’t get recompiled.

1 Like

I just wanted to point you to a short thread:

The QT events are processed in the QEventDispatcher which is started with the QCoreApplication::exec() and ended with QCoreApplication::quit().

You cannot have two event loops, because the whole purpose is to avoid concurrency issues e.g. when manipulating windows etc. I understand you don’t manage windows from juce, but that’s what it is.

oh really? I started learning with modules from the start so not versed in header/source… this guy makes it seem like only a modules thing : https://www.youtube.com/watch?v=VcQXn4mNttI&t=1s&ab_channel=LearnQtGuide

Oh! so we can take runDispatchLoop out of the equation. Yea that makes it alot easier. Nice find, thanks