In my last post, I covered the last in a series of code generation examples for generating various different types of wrappers around components of the Arduino Audio library. Earlier in the series, we generated messages corresponding to each function on a component of the Audio library. Then we implemented Smalltalk-style message dipatch. In the last post, we wrote a C++ layer on top  and a little C++ example app for testing.

Now that we've looked at the generated C++ code and the Swift code that generates it, I'll show you the tool where these code generators live. It's called Clockwork and while it's still quite experimental, it has more capabilities than what was revealed in the previous posts. Clockwork can parse APIs in several languages (currently C++, Swift, and Python) and generate code in several languages (currently C++, Swift, Python, and Kotlin). It is modular, so new parsers and code generators can be added over time. In addition to creating Smalltalk-like message dispatching interfaces on top of procedural languages so that we can experiment with Effective Programming techniques, it can also provide cross-languge remote procedure calls. For instance, I have used it for both Kotlin clients talking to Swift servers over the network and a Kotlin app talking over USB serial to a CircuitPython program running on a microprocessor.

To run Clockwork out of its project directory, first create a config. An example of how to do this for the previous Arduino Audio examples is the following:

swift run ClockworkCommandLine new Audio.config --cpp-messages ~/Documents/Arduino/ClockworkDemo --cpp-module ~/Documents/Arduino/ClockworkDemo --cpp-universe ~/Documents/Arduino/ClockworkDemo ~/Documents/Arduino/libraries/Audio_-_Adafruit_Fork/control_sgtl5000.h

This will create a config file specifying how to parse the SGTL5000 audio codec controller header file and use the API it finds there to generate the code covered in the previous posts. In order to use this config, run it like this:

swift run ClockworkCommandLine run Audio.config

You should see some output like this:

Building for debugging...
Build complete! (0.18s)
Generating AudioControlSGTL5000Messages.h...
Generating AudioControlSGTL5000Module.h...
Generating AudioControlSGTL5000Module.cpp...
Generating AudioControlSGTL5000Universe.h...
Generating AudioControlSGTL5000Universe.cpp...

Easy!

We can't do cross-language remote procedure calls to C++ quite yet as this requires some additional code. Specifically, we need to write some code to serialize and deserialize our C++ message objects. In Swift and Kotlin, this is handled for us automatically, but in C++ we'll have to write a code generator for the serialization/deserialization code, so this will take a bit more work. Cross-language remote procedure calls, or more accurately remote message dispatch, is one of the cool things we get out of all of this work we did to reify procedure calls into messages. Since messages are data structures and since the API contains only value types and no pointers, the messages can be serialized in a fairly straightforward manner. Before we get into that, though, let's use our code generator tool to generate enough code for an example that makes actual sound.

Following the example above, we can also do code generation for the output_i2s.h and synth_tonesweep.h header files, putting all of the generated code in the ClockworkDemo directory. Then we can enhance our sample program from a previous post to use the newly generated code:

#include <Arduino.h>
#include "Audio.h"
#include "AudioControlSGTL5000Module.h"
#include "AudioControlSGTL5000Universe.h"
#include "AudioSynthToneSweepModule.h"
#include "AudioSynthToneSweepUniverse.h"
#include "AudioOutputI2SModule.h"
#include "AudioOutputI2SUniverse.h"

AudioControlSGTL5000 codec;
AudioControlSGTL5000Module codecModule(&codec);
AudioControlSGTL5000Universe codecUniverse(&codecModule);

AudioSynthToneSweep tonesweep;
AudioSynthToneSweepModule tonesweepModule(&tonesweep);
AudioSynthToneSweepUniverse tonesweepUniverse(&tonesweepModule);

AudioOutputI2S i2s;
AudioOutputI2SModule i2sModule(&i2s);
AudioOutputI2SUniverse i2sUniverse(&i2sModule);

AudioConnection c1(tonesweep, 0, i2s, 0);
AudioConnection c1(tonesweep, 1, i2s, 1);

void setup()
{
  codecUniverse.enable();
  codecUniverse.volume(0.5, 0.5);    

  i2sUniverse.begin();
}

void loop()
{
  if(!tonesweepUniverse.isPlaying())
  {
    tonesweepUniverse.play(0.8, 256, 512, 10);  
  }
}

There we go, a complete demo of our code generation system as it currently stands! Right now it's not too exciting, we've just replaced the original C++ tonesweep demo with a more complicated version. This is just for testing, though. Now that we have all of this nice code for parsing the API and then generating code around it, we can start to work on the syntax for a little language to replace C++ for Arduino Audio programs. This will be the subject of my next post.

Code Generation for Arduino Audio - Running the Code Generation Tool