Crudebyte Logo
JACKiOS  1.9.10.3
Getting Started

Short explanation how to add JACK support to iOS Apps.

The following sections on this page describe the key parts in brief form, about how to add support for JACK to an iOS application. It is directed to developers who have never actively been using JACK before. It helps you getting your JACK app working in a reasonable short time. It does not cover all possibilities of the JACK API in full extent. For more advanced purposes, please browse the more detailed sections of this JACK API documentation.

Xcode Project Setup

You should first download the latest version of the JACK iOS SDK and extract it to some arbitrary place on your Mac. Then drag jack.framework into your iOS app's Xcode project and make sure the audio background mode is enabled in your app's Info.plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>My App Name</string>
...
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>

This ensures that your app will continue running in the background after the user pressed the home button of the device.

The coding part

Obviously you need to write some code to glue your app to the JACK system. However it does not involve as much code as you think. Simply follow the steps and code examples below.

Create JACK client

First thing to do on code level, is to create a JACK client for your app:

#include <jack/jack.h>
...
jack_status_t status;
jack_client_t* client = jack_client_open("My App Name", JackNullOption, &status);
if (!client) {
if (status & JackVersionError)
fprintf(stderr, "App not compatible with running JACK version!\n");
else
fprintf(stderr, "JACK (server) app seems not to be running!\n");
return -1;
}

The call above usually just might fail because of the 2 reasons shown.

  1. The official JACK (server) app must be running, since it not only provides the control GUI of the JACK system, but also the JACK server. We recommend you, to simply call jack_client_open() on app start, and in case it succeeds, initialize your app for using JACK. If the call fails, initialize your app to use your usual audio/MIDI system like the CoreAudio/CoreMIDI API. This way end users don't have to configure your app explicitly. Keep in mind the end user might start quite a bunch of JACK client apps!
  2. Please also check for the JackVersionError flag on iOS, and i.e. prompt the user a message box in this case. Reason for the JackVersionError flag is the fact, that you have to statically link the JACK client library into your app binary on iOS (Apple does not allow DLLs for App Store Apps at this point). So the JACK library you linked now, might become incompatible to a future version of JACK. We took precautions that this case should not happen in any time soon. It might happen one day though, however it will most probably just require that you download the latest version of the JACK SDK and rebuild your app.

Usually one only creates one JACK client per app. However you are free to create even more, individual JACK clients for one app. For example for rather complex DAW apps with several integrated soft sequencers and effects, you might expose parts of your app as separate JACK clients.

You may also use jack_server_installed() to check whether JACK is installed on the device at all, and jack_server_running() to check whether the main JACK (server) app is currently running on the device.

Note
In the rare case you want your own CoreAudio code rendering/capturing code to remain active for some reason, while using JACK at the same time in the same app, you should pass JackNoBackground to jack_client_open(). Reason: the JACK client framework automatically runs a fake CoreAudio mechanism to actually enable your app to remain active in the background, which would in that very special case cause a conflict.

Publish client icon

To show an appropriate icon for your app on the "Client Map" screen of JACK (server) app, use the following Objective C code:

#include <jack/custom.h>
...
NSString* iconFile = [[NSBundle mainBundle] pathForResource:@"Icon-Metro136" ofType:@"png"];
NSFileHandle* fileHandle = [NSFileHandle fileHandleForReadingAtPath:iconFile];
NSData* data = [fileHandle readDataToEndOfFile];
const void* rawData = [data bytes];
const size_t size = [data length];
jack_custom_publish_data(client, "icon.png", rawData, size);
[fileHandle closeFile];

Adjust "Icon-Metro136" to the actual name of your icon image file (PNG format expected at this point). And make sure that icon file is added to your Xcode project. Currently icons on the "Client Map" of the JACK (server) app are shown with a resolution of 136x136px on Retina displays. If the icon you provide has a different resolution, it will be scaled to the appropriate resolution. You may replace the Objective C code above of course by any other means. The relevant part is only the jack_custom_publish_data() call, which actually transmits the PNG icon as raw data buffer to the JACK server. The key name "icon.png" is mandatory on this call. If you use another name, it will not appear on the JACK GUI.

Register callback functions

Next you should register your callback functions which JACK should call for various purposes. At least you need to call jack_set_process_callback() to register a function written by you, that will handle audio and/or MIDI processing. The following example demonstrates the common part of processing audio & MIDI with JACK:

#include <jack/jack.h>
#include <jack/midiport.h>
static void process_audio (jack_nframes_t nframes)
{
// process incoming audio data
// (in this example of one audio input port, thus mono)
{
float* buffer = (float*) jack_port_get_buffer(audio_in_port, nframes);
// application specific part
doSomethingWithThisReceivedAudioData(buffer, nframes);
}
// generate and output audio data
// (in this example of two audio output ports, thus "stereo")
{
float* buffer_left = (float*) jack_port_get_buffer(audio_out_port_left, nframes);
float* buffer_right = (float*) jack_port_get_buffer(audio_out_port_right, nframes);
// application specific part
generateSomeAudioData(buffer_left, buffer_right, nframes);
}
}
static void process_midi (jack_nframes_t nframes)
{
// handle incoming MIDI data
// (in this example of only one MIDI input port)
{
void* buffer = jack_port_get_buffer(midi_in_port, nframes);
const int event_count = jack_midi_get_event_count(buffer);
for (int i = 0; i < event_count; ++i) {
jack_midi_event_get(&ev, buffer, i);
if (ev.buffer) {
// the application specific part, process this standard MIDI event
doSomethingWithThisMIDIEvent((char*)ev.buffer, ev.size);
}
}
}
// send data to output port(s)
// (in this example, only one MIDI output port)
{
int data_size;
// the application specific part, assuming this function
// returns a buffer with standard MIDI data, and sets
// "data_size" to the size of the buffer in bytes
char* data = generateSomeMIDIOutputData(&data_size);
// copy the app generated MIDI data to the output port
void* buffer = jack_port_get_buffer(midi_out_port, nframes);
jack_midi_data_t* midi_data_buffer = jack_midi_event_reserve(buffer, 0/*time*/, data_size);
memcpy(midi_data_buffer, data, data_size);
}
}
// The main callback for every JACK client. It is called once per period by
// the JACK server (in a separate thread within the client's process).
//
// Argument "arg" is the data you might have passed to jack_set_process_callback(),
// it can be anything you want.
static int process_callback (jack_nframes_t nframes, void *arg)
{
process_midi(nframes);
process_audio(nframes);
return 0;
}
// Callback for being executed when the JACK (server) app is closing this
// JACK client for some reason. We definitely recommend you to implement
// such a callback for your app.
static void shut_down (jack_status_t code, const char* reason, void* arg)
{
if (code & JackClientKilled) {
// If this flag is set, then your client was closed by the user by
// pressing "X" button of the client icon in the JACK control app.
// In this case you should simply quit your app, since the user
// was already asked in the JACK control app whether he really wants
// close this app.
exit(0);
} else {
// Don't forget to free your JACK client when the JACK shutdown
// callback is triggered, because JACK does *not* do it for you!
dispatch_async(dispatch_get_main_queue() /*GUI Thread*/, ^{
client = NULL;
});
// ... otherwise if this block is reached, your client might got
// closed because the JACK control app (and with it the server)
// was closed or i.e. when JACK apps created too much CPU load,
// causing the server to sort out some of the clients, to prevent
// the device turning unresponsive. Either quit the app, or show a
// message dialog to the user, asking him if the app can be closed
// (i.e. preventing some unsaved data to get lost) or automatically
// switch your app from JACK mode to i.e. CoreAudio/CoreMIDI mode.
if (code & JackClientZombie) {
// Client was closed because of too much CPU load
} else {
// Possibly (still unknown) reason
}
}
}

Each audio port in JACK is a mono audio channel in 32 bit float format. The argument nframes of the process callback is an integer type variable, which reflects the amount of frames (or "sample points") to be processed in the current period. So assuming your app would just want to write "silence" to the output ports, it could look like this:

static void generateSomeAudioData(float* buffer_left, float* buffer_right, jack_nframes_t nframes)
{
for (int i = 0; i < nframes; ++i) {
buffer_left[i] = 0.f;
buffer_right[i] = 0.f;
}
}

Which would neither be efficient nor useful for anything, however this is just about giving you an intuitive, compact idea about how to write your audio code for it.

MIDI events in JACK are simply raw MIDI data buffers along with a time stamp, see jack_midi_event for more details about it.

Note
The code in the supplied "process" function must be suitable for real-time execution. That means that it cannot call functions that might block for a long time. This includes any function that directly or indirectly allocate or deallocate memory like malloc(), free(), printf(), as well as common blocking synchronization functions as pthread_mutex_lock(), sleep(), wait(), poll(), select(), pthread_join(), pthread_cond_wait(), as well as any Objective-C method, throwing of C++ Exceptions, etc. The JACK server carefully monitors the timing of each client, and removes a client automatically from the audio execution graph in case it misses to return from the process callback within the expected time frame of the currently selected latency setting of JACK, to prevent one single JACK client application to tear down the whole JACK system.

Then actually register the callback functions you just implemented with:

jack_set_process_callback(client, process_callback, NULL/*user data*/);
jack_on_info_shutdown(client, shut_down, NULL/*user data*/);

There are various other callback functions which you might register. For example for reacting when essential system parameters have been changed like sample rate, latency (buffer size) and much more. A list of the most important JACK callbacks can be found at Setting Client Callbacks.

Note
you must register all your callbacks before calling jack_activate() - more on this in upcoming section Activate client.

Create audio & MIDI ports

Now we actually have to create the amount of audio in/out & MIDI in/out ports for the application. Following our ongoing example it would be:

// create the audio in/out ports
jack_port_t* audio_in_port =
jack_port_t* audio_out_port_left =
jack_port_t* audio_out_port_right =
// create MIDI in/out ports
jack_port_t* midi_out_port =
jack_port_t* midi_in_port =

The names above for the ports are arbitrarily chosen. You can create any amount and specific type of ports you like. You can even create and destroy ports later on, at any time.

Activate client

Finally we just have to activate the client and it will be an active part of the JACK system.

if (jack_activate(client)) {
fprintf (stderr, "cannot activate client\n");
goto error;
}

If this call succeeds, your callback functions will now periodically be called to let your app process audio & MIDI data. Please note, that after this call, no more callback functions can be registered. If you really need to register additional callbacks after this point for some reason, you might do so by suspending your JACK client for a moment with jack_deactivate(), add your callback functions, and reactivate your JACK client with jack_activate().

Close client on exit

When your application exits, or when you want to switch from JACK mode to i.e a native CoreAudio mode in your app at some point, then close your JACK client with:

This will automatically destroy all the ports, data, connections and everything created and established previously for the client.

Please note, if the JACK server closes your client, your JACK client will not be freed. You have to call jack_client_close() on your own to free the resources allocated for your client by dispatching it in your JACK shutdown callback as already shown above. You would dispatch it in the shutdown callback, since it is not valid to call jack_client_close() in the context (thread) of the shutdown callback. We recommend to also ensure freeing your client's resources in your app delegate's termination handler:

- (void)applicationWillTerminate:(UIApplication *)application
{
if (client) jack_client_close(client);
}

Freeing your client resources is so important, because it would otherwise leak system resources, which would even remain leaked after your app terminated.

Remote App Start

app_launch_dialog_shot.png

The JACK control app provides a very important feature from user aspect: the ability to launch JACK client apps directly from the JACK (server) app and fast switching the screen between the various JACK apps, without forcing the user to do such things with home button and iOS desktop.

To support these important features, you need to add an URL schema entry to your app's Info.plist file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>My App Name</string>
...
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.foocompany.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>foocompanymyapp</string>
</array>
</dict>
</array>
</dict>
</plist>

It is important here that you choose an unique URL schema, which is not already been used by another iOS app. So preferably make the URL schema name i.e. a combination of your company's name and your app's name, which was i.e. foocompanymyapp in the example above.

We also recommend you to call jack_app_register() each time your app launches. For example in the following method of your app delegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
...
// ensure this app is known by JACK
return YES;
}

This call checks whether your app is already been "known" to the local JACK installation. If not, it will inform the JACK installation about the existence of your app, so that your app appears in the list of installed JACK apps and accordingly to allow the user to start your app directly within the JACK control app.

You can check the launchOptions argument of that app delegate method shown above, for finding out whether your app was launched regularly from the iOS desktop or by JACK instead:

// was this app launched from the iOS desktop or by some other app?
NSURL* url = [launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
NSString* src = [launchOptions valueForKey:UIApplicationLaunchOptionsSourceApplicationKey];
NSLog(@"App launched by app '%@' with URL '%@'", src, [url absoluteString]);

You may also provide a button in your app to switch back to the JACK control app. You can download the JACK icon from our website and you may include it in your app, i.e. as skin for such a button.

jack_button_example_shot.png

When that button is pressed by the user, simply call jack_gui_switch_to_client() with the following string argument:

// bring the JACK control app into foreground on the screen
jack_gui_switch_to_client(client, "jack");

which will immediately bring the JACK (server) app into foreground on the screen. You might even use the same function with your own JACK client name instead to bring your own app into foreground, i.e. to inform the user about some very important occurrence in your app while the user is currently watching at another app. However we discourage to use jack_gui_switch_to_client() to bring your own app "unasked" into foreground. It might make sense under very, very rare and really important circumstances. But in doubt, don't use the function for that purpose. Because otherwise it could end up your app being very annoying to the user, causing more damage to the user experience instead of improving it.

Note
You need to build your app with JACK iOS SDK 1.9.10.2 or higher for the remote app start and screen switching features.

That's it!

You should now be able to compile your app with JACK support in Xcode.

Note that the main JACK (server) app is not dictating. You can also connect and disconnect between any foreign apps from your own app, which is pretty simple, for example:

jack_connect(client, "MIDI Wrench:Virtual Keyboard", "CMP Grand Piano:Midi In";
jack_disconnect(client, "CMP Grand Piano:LEFT Out", "system:playback_1");
jack_disconnect(client, "CMP Grand Piano:RIGHT Out", "system:playback_2");

So it's just CLIENT_NAME:PORT_NAME as argument. Same functions for Audio and MIDI connections. So you could also implement something like the "Jack Clients" screen in your own app. You can even adjust JACK system wide parameters like latency (buffer size) which effect the entire JACK system.

Anything unclear? Don't hesitate to ask us directly or on our dedicated web forum.

Back to overview ...

DE • EN
Copyright © MMXIII Crudebyte. All rights reserved.

twitter