Implementing Korg WIST in an iOS Music App February 13, 2012
This is for the lawyers: WIST is a trademark and software of KORG INC. All other trademarks are property of their respective owners. I am not an alien invader.
This post details my experiences implementing WIST for my amazing ipad drum machine, which will hopefully be out soon (one more feature...) I am going to refer to WIST as wist from here on out, apologies in advance.
What is wist?
From a user's perspective, wist is a way to get two iOS music apps playing in sync.
From a developer's perspective, wist is an iOS library you can use get your app playing in sync with other apps and brag about on your features list. It is BSD (3-clause) licensed and available for download here: http://code.google.com/p/korg-wist-sdk/
Architecturally, wist is 3 things:
- An abstraction over GameCenter for making BlueTooth connections and negotiating the latency between them
- A User Interface
- A source and destination for start and stop events
Thankfully, you don't have to worry about numbers 1 and 2, Korg has taken care of things for you. For number 3, You just tell the library when you are going to start and stop if you are the master and implement the callbacks for when you are the slave.
How to add WIST to your iOS Music App
- Add the code to your project
- Instantiate the wist object
- Delegate and Control
- Establish a connection to another device
1. Adding the code to your project
Get the code ( http://code.google.com/p/korg-wist-sdk/ ), extract it and copy the files KorgWirelessSyncStart.h and KorgWirelessSyncStart.m to your project (Add Files...)
If you are using ARC, you will get a bunch of errors. =/ To rememdy, go to "Build Phases" and where it says "Compile Sources (37 Items)" find the KorgWirelessSyncStart.m in the list and double click the area to the right, to add a compiler flag. Use the flags "-fno-objc-arc -w", without the quotes. This should compile just fine.
2. Instantiating the wist object
You're going to need to get a handle to the wist object whenever you start or stop your sequencer, and it will need to have your sequencer (or a proxy) as a delegate. import the header file and alloc/init a new KorgWirelessSyncStart object, and keep a reference to it handy. Add your class as its delegate. For that to work, you'll need to implement the KorgWirelessSyncStartDelegate protocol...
3. Delegate and Control
The trickiest thing about wist is getting your sequencer to start at the right time. You don't start it as soon as you get the callback nor as soon as you tell wist to notify the slave. It is a two step process: first, notify wist of your intentions and then go ahead and follow through.
As a slave, the wist object calls:
- (void)wistStartCommandReceived:(uint64_t)hostTime withTempo:(float)tempo
tempo is the bpm as a float, which is pretty straightforward -- set this as the current tempo for your sequencer. hostTime is the time your sequencer should start. That time is in the future! If you start immediately you will be way too early; kind of like starting to play the song when the drummer starts counting off instead of when the drummer starts playing.
In your non-audio code, you can get the current time using:
And from in your audio callback, you can get the current time using:
UInt64 nowTime = inTimeStamp->mHostTime;
So, you get the wistStartCommandReceived and then you start playing once the current time is the same as the requested hostTime. Since I am using an extremely small audio buffer (256 samples,) I cheated and just check to see if the current time is greater than the hostTime and if so, start playing immediately. I was planning on calculating the latency and having a sample-precise start time, but it was extremely tight sync with just this simple technique so I didn't bother.
As a slave, the other method that wist calls is to let you know that a stop command was received. It is pretty straightforward; you can go ahead and just stop immediately whenever you get that.
Things are slightly more tricky as a master.
When a user goes to hit the play button, you check (wist_.isConnected && wist_.isMaster) to see if you are connected and master, then you grab the current time as above (mach_absolute_time()) and call sendStartCommand with the current time. You also delay your own sequencer until the time returned by estimatedLocalHostTime, which will be a time in the future.
So, just to be clear, you always delay your sequencer start until the time wist tells you.
All that's left now is establishing the connection, and everything should "just work".
4. Establish a connection to another device
call searchPeer and wist will create a new view that mimics an alert box, blocking out the rest of the UI while trying to negotiate a connection. When the connection is established or the user hits cancel, the UI goes away. Everything is handled for you. When the connection drops, a dialogue will tell the user that happened, automatically.
5. Buy Stochastik!
That's all I have for now. I hope you liked this overview and tutorial. My app ( http://stochastik.xitive.com ) should be in the app store soon. In the meantime, you can follow me at @aaronblohowiak or @xitiveinc on Twitter.
I have some contract availability starting in may for iOS, RoR or Node.js work.