[iOS] How to integrate a JUCEApplication with my iOS app?

Hi! I am building an iOS app that uses tracktion_engine. Tracktion needs a JUCEApplication to run. My problems are:

  1. I can’t create the JUCEApplication with START_JUCE_APPLICATION (or START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE) because it creates a main function separate from my app’s main;

  2. Can’t call JUCEApplicationBase::main() directly because the main execution stops in MessageManager::getInstance()->runDispatchLoop();.

  3. Can’t run JUCEApplicationBase::main() in a background thread because tracktion_engine makes calls to the UI API that have to run in the main thread;

I don’t know what is the correct approach. Maybe it has something to do with the delegate, or running the MessageManager loop without a JUCEApplication.

Has anyone did this? Thanks

1 Like

I tried running the MessageManager loop in a separate thread, but then it keeps bumping into JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED and JUCE_ASSERT_MESSAGE_THREAD checks. It makes sense… I just don’t know how I could work.

Figured it out!

Don’t use START_JUCE_APPLICATION or START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE. Just call juce::MessageManager::getInstance(); before creating the tracktion_engine. This is my Objective-C++ wrapper (uses the PIMPL pattern):

@implementation MyEngineInterface {
    MyEngine* myEngine;
}
- (id)init {
    self = [super init];
    if (self)
    {
        juce::MessageManager::getInstance();
        juce::initialiseJuce_GUI();
        myEngine = new MyEngine(nil);

        if (!myEngine) self = nil;
    }
    return self;
}

MyEngine is a class with the tracktion_engine::Engine, tracktion_engine::Edit, etc.

BTW, I am building this as static lib (.a) and importing it in the main app (actually, a NativeScript app).

BTW2, the same code is being wrapped in WASM and used on a web page. Pretty cool, no?

6 Likes

That is awesome job!
Do you a sample repo that demonstrates this setup? I always found it hard to wrap my head around different ways iOS + Juce project can be configured. Running trackrion_engine on Web just sounds intriguing!
Would be awesome to see it on GitHub!

1 Like

Oh man that’s cool. I’m building an ReactNative app that uses a custom delegate & bridging headers but will be needing a good arrangement view inside my react component, I wonder if I should try this…

I don’t have a repo I could show this setup right now, sorry. But these simplified versions of the main files:

Builds/iOS/CasperakiEngineInterface.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CasperakiEngineInterface : NSObject {
}

- (id)init;
- (int)test;
- (void)play;
- (void)pause;
- (void)printState;
- (void)loadDrums;
- (void)loadVibe;

@end

Builds/iOS/CasperakiEngineInterface.mm

#import "CasperakiEngineInterface.h"
#include "../../Source/CasperakiEngine.h"

@implementation CasperakiEngineInterface {
    CasperakiEngine* casperakiEngine;
}
- (id)init {
    self = [super init];
    if (self)
    {
        juce::MessageManager::getInstance();
        juce::initialiseJuce_GUI();
        casperakiEngine = new CasperakiEngine(nil);
        casperakiEngine->initialise();

        if (!casperakiEngine) self = nil;
    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
    delete casperakiEngine;
}

- (int)test {
    NSLog(@"test");
    return 1;
}

- (void)play { casperakiEngine->play(); }

- (void)pause { casperakiEngine->pause(); }

- (void)printState {
    CasperakiEngine::ControlState state = casperakiEngine->getControlState();
    NSLog(@"state: %s\tpos: %f/%f", state.state.c_str(), state.position, state.length);
}

- (void)loadDrums {
    std::string drumsData;
    drumsData = "<some data>";
    casperakiEngine->addInstrument(drumsData);
}

- (void)loadVibe {
    std::string vibeData;
    vibeData = "<some data>";
    casperakiEngine->setSong(vibeData);
}

@end

Source/CasperakiEngine.h:

#pragma once

#include <JuceHeader.h>

class CasperakiEngine : private juce::ChangeListener, private juce::Timer {
public:
    struct ControlState {
        (...)
    };

    CasperakiEngine(const std::function<void(ControlState)>&);

    ~CasperakiEngine() {
        DBG("CasperakiEngine DESTROYED!!!!");
    };

    void initialise();

    void play();

    void pause();

    void stop();

    void addInstrument(std::string instrumentData);

    void setSong(std::string songData);

    ControlState getControlState();

private:

    void changeListenerCallback(juce::ChangeBroadcaster *source) override;

    std::unique_ptr<te::Engine> engine;
    std::unique_ptr<te::Edit> edit;
    te::TransportControl *transport;

    j::OwnedArray<Instrument> instruments {};
    ControlState lastControlState;
};

About tracktion_engine on the web, you can test it yourself from my fork:

It includes a working StepSequencerDemo. Here it is ready to test: StepSequencerDemo

1 Like

I am doing this in NativeScript, but React Native should be very similar. In my case, I don’t want to render any UI component with tracktion_engine.