The McLaren Synth Kit presents audio synthesis as the construction of a graph of audio operators (voices) with the parameters of each type of operator determined by an associated model. Voices define the code that is run in the audio thread and models hold the values that are read from the audio thread. This separation allows the audio thread code to be ensured that maniplations on the models do not block. With models safely out of the audio loop, UI interactions with model properties is straightforward and can use Cocoa idioms for manipulating property values.
At least that was the intent. The McLaren Synth Kit was released without associated GUI controls, but we believed it would be straightforward to define controls for the various models.
But we had never fully defined a Cocoa-based application that rested on the Synth Kit. We finally put it all together in our newly released application called “Synth80”.
Synth80 is a two-oscillator multi-timbral polyphonic synthesizer. This application pulls together the following features.
- Cocoa bindings for linking Controllers to McLaren Synth Kit Models
- Document-based application for saving and restoring presets
- Undo/Redo facility
- Single Document Interface document-based application for the singleton Synthesizer window
This article summarizes some of the things we learned about using Cocoa this way while developing Synth80.
Focus on Data Structures
We found that we were able to focus on modeling data more than building GUI elements. The models for our synthesizer elements (envelopes, oscillators, filters, etc) were designed first, with controls added later.
Cocoa Bindings allowed us to make a clear separation between the models and the controllerss, with each having almost no knowledge of the implementation of the other. We were even able to add a new binding type (a “sample” value) with an associated Sample View. This worked out rather nicely.
Document Model
This was our first time implementing a Cocoa document-based app. We got a lot of good information from “Cocoa Programming Developer’s Handbook” by David Chisnall. (Note: picked up a well-used copy at Amazon.)
The Document-model is actually another case of focusing on data structures and less on writing code. Once we defined our “Synth” model, and wrote the NSCoding
protocol methods for saving and restoring objects, there was little else to do. The Cocoa document functions like saving or opening “just worked.”
The “Undo” function is closely related to the document model. Using a technique from the book above, we were able to implement “Undo” using KVO (“Key Value Observing”) in a manner that is entirely external to the models. This complete separation of the “Undo” aspect of the application from the rest of the code is a great simplification.
Once the document model and the “Undo” function was implemented, niceties like the indication that a document needed saving (in the Window Title) just happened automatically, in a way that is entirely consistent with what I’m used to on a Mac.
Single Window Application
The normal way that a document-based application works is that a new window is opened for each opened document. For the Synth application, where there is only one audio output, it didn’t make sense to have multiple open windows. We wanted a single window to represent the currently opened document.
This is a little bit like the way Garage Band works: it is a singleton window.
We got some good pointers from the article linked below.
That showed us how to make a singleton sharedWindowController
. It took some trial-and-error to get everything connected correctly, but it was essentially straightforward.
Our application does not use Interface Builder, and hence, does not load NIB files. To signal to AppKit that we were defining our window programmatically se needed to override isWindowLoaded
. I felt that we were a little outside the norm not using NIB files, but I’m happy with our GSTable-based layout and the technique we used to define a layout in code.
Conclusion
The result of the Synth80 project is less of an “Application” and more of an “Architecture.” It is an architecture for creating a single document interface document-based Cocoa application, with models that control realtime properties of elements in an audio loop. Cocoa Bindings let us focus on our data models, without thinking about Bindings or Views at all during the audio development phase. The addition of Controls, Views, Windows, Document save/restore and Undo were all added on-top of the basic McLaren Synth Kit elements without requiring the Synth Kit elements to have any knowledge of these higher-level aspects of the program. This separation of concerns is the strength of the Cocoa application model.