Getting Started Table of ContentsShort 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 SetupYou 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 partObviously 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 clientFirst thing to do on code level, is to create a JACK client for your app: #include <jack/jack.h>
...
jack_status_t 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.
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.
Publish client iconTo 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 Register callback functionsNext 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>
{
// process incoming audio data
// (in this example of one audio input port, thus mono)
{
// application specific part
doSomethingWithThisReceivedAudioData(buffer, nframes);
}
// generate and output audio data
// (in this example of two audio output ports, thus "stereo")
{
// application specific part
generateSomeAudioData(buffer_left, buffer_right, nframes);
}
}
{
// handle incoming MIDI data
// (in this example of only one MIDI input port)
{
void* buffer = jack_port_get_buffer(midi_in_port, nframes);
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
}
}
}
// 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_clear_buffer(buffer);
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.
{
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.
{
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*/, ^{
jack_client_close(client);
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 {
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.
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.
Create audio & MIDI portsNow 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 clientFinally we just have to activate the client and it will be an active part of the JACK system. 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 exitWhen 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: jack_client_close(client);
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 StartThe 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. 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 // 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. 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.
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: 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. Generated by |
|
|