changeset 497:37f10f61433a prerelease

merge
author Giulio Moro <giuliomoro@yahoo.it>
date Tue, 21 Jun 2016 20:01:26 +0100
parents eb237b131ec7 (current diff) 5779ed0562ac (diff)
children c932df224fd3
files examples/10-Instruments/oscillator_bank/audio_routines.S examples/10-Instruments/oscillator_bank/main.cpp examples/10-Instruments/oscillator_bank/render.cpp examples/10-Instruments/tank_wars/game.cpp examples/10-Instruments/tank_wars/game.h examples/10-Instruments/tank_wars/main.cpp examples/10-Instruments/tank_wars/render.cpp examples/10-Instruments/tank_wars/vector_graphics.cpp examples/10-Instruments/tank_wars/vector_graphics.h
diffstat 19 files changed, 1509 insertions(+), 1500 deletions(-) [+]
line wrap: on
line diff
--- a/examples/05-Communication/OSC/render.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ b/examples/05-Communication/OSC/render.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -31,40 +31,40 @@
 
 // this example is designed to be run alongside resources/osc/osc.js
 
-// parse messages recieved by OSC Server
+// parse messages received by OSC Server
 // msg is Message class of oscpkt: http://gruntthepeon.free.fr/oscpkt/
 void parseMessage(oscpkt::Message msg){
     
-    rt_printf("recieved message to: %s\n", msg.addressPattern().c_str());
+    rt_printf("received message to: %s\n", msg.addressPattern().c_str());
     
     int intArg;
     float floatArg;
     if (msg.match("/osc-test").popInt32(intArg).popFloat(floatArg).isOkNoMoreArgs()){
-        rt_printf("recieved int %i and float %f\n", intArg, floatArg);
+        rt_printf("received int %i and float %f\n", intArg, floatArg);
     }
     
 }
 
 bool setup(BelaContext *context, void *userData)
 {
-    // setup the OSC server to recieve on port 7562
+    // setup the OSC server to receive on port 7562
     oscServer.setup(7562);
     // setup the OSC client to send on port 7563
     oscClient.setup(7563);
     
     // the following code sends an OSC message to address /osc-setup
     // then waits 1 second for a reply on /osc-setup-reply
-    bool handshakeRecieved = false;
+    bool handshakeReceived = false;
     oscClient.sendMessageNow(oscClient.newMessage.to("/osc-setup").end());
-    oscServer.recieveMessageNow(1000);
+    oscServer.receiveMessageNow(1000);
     while (oscServer.messageWaiting()){
         if (oscServer.popMessage().match("/osc-setup-reply")){
-            handshakeRecieved = true;
+            handshakeReceived = true;
         }
     }
     
-    if (handshakeRecieved){
-        rt_printf("handshake recieved!\n");
+    if (handshakeReceived){
+        rt_printf("handshake received!\n");
     } else {
         rt_printf("timeout!\n");
     }
@@ -74,10 +74,10 @@
 
 void render(BelaContext *context, void *userData)
 {
-    // recieve OSC messages, parse them, and send back an acknowledgment
+    // receive OSC messages, parse them, and send back an acknowledgment
     while (oscServer.messageWaiting()){
         parseMessage(oscServer.popMessage());
-        oscClient.queueMessage(oscClient.newMessage.to("/osc-acknowledge").add(5).add(4.2f).add(std::string("OSC message recieved")).end());
+        oscClient.queueMessage(oscClient.newMessage.to("/osc-acknowledge").add(5).add(4.2f).add(std::string("OSC message received")).end());
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/oscillator-bank/audio_routines.S	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,161 @@
+@
+@ audio_routines.S
+@
+@ NEON-based functions for time-critical audio processing
+@
+@ Andrew McPherson 2014
+@ Queen Mary University of London
+@
+
+	.syntax unified
+	.arch armv7-a
+	.fpu neon
+
+@ 	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
+@							  int activePartialNum, int lookupTableSize,
+@							  float *phases, float *frequencies, float *amplitudes,
+@							  float *freqDerivatives, float *ampDerivatives,
+@							  float *lookupTable);
+
+@ Registers:
+@    r0: numAudioFrames        How many frames to render
+@    r1: audioOut              Buffer for audio output samples [stereo]
+@    r2: activePartialNum      How many active partials to render
+@    r3: lookupTableSize       Size of lookup table
+@    ---- other arguments start on the stack and are moved: -----
+@    r4: phases                Phase of each oscillator (pointer)
+@    r5: frequencies           Normalised frequency of each oscillator (pointer)
+@    r6: amplitudes            Normalised amplitude of each oscillator (pointer)
+@    r7: freqDerivatives       Derivative of frequency for each oscillator (pointer)
+@    r8: ampDerivatives        Derivative of amplitude for each oscillator (pointer)
+@    r9: lookupTable           Lookup table containing one oscillation
+@
+@ Alignment requirements:
+@    audioOut: 8-byte boundary
+@    phases: 16-byte boundary
+@    frequencies: 16-byte boundary
+@    amplitudes: 16-byte boundary
+@    freqDerivatives: 16-byte bounary
+@    ampDerivatives: 16-byte boundary
+@    lookupTable: 4-byte boundary (TODO: check this)
+
+	.align	2
+	.global	oscillator_bank_neon
+	.thumb
+	.thumb_func
+	.type	oscillator_bank_neon, %function
+oscillator_bank_neon:
+
+
+dSample		.dn		D6.F32
+qPhases		.qn		Q8.F32
+dPhases_0	.dn		D16.F32
+dPhases_1	.dn		D17.F32
+qFreqs		.qn		Q9.F32
+dFreqs_0	.dn		D18.F32
+dFreqs_1	.dn		D19.F32
+qAmps		.qn		Q10.F32
+dAmps_0		.dn		D20.F32
+dAmps_1		.dn		D21.F32
+qFreqDs		.qn		Q11.F32
+dFreqDs_0	.dn		D22.F32
+dFreqDs_1	.dn		D23.F32
+qAmpDs		.qn		Q12.F32
+dAmpDs_0	.dn		D24.F32
+dAmpDs_1	.dn		D25.F32
+
+qBaseInts	.qn		Q13.U32		@ Base indexes: unsigned ints x4
+dBaseInts_0	.dn		D26.U32
+dBaseInts_1	.dn		D27.U32
+qFractions  .qn     Q14.F32		@ Fraction indexes: floats x4
+qTableBase	.qn		Q15.U32		@ Base of lookup table
+
+	cmp r0, #0					@ Check for trivial case 1: zero frames
+	it eq
+	bxeq lr						@ Return if that's the case (otherwise might have odd behaviour)
+	cmp r2, #4					@ Check for trivial case 2: zero oscillators
+	it lt
+	bxlt lr						@ Return if that's the case
+
+	push {r4-r11}				@ Now arguments start 32 bytes above SP
+    add r11, sp, #32			@ Pointer to 32 bytes into the stack
+    ldm r11, {r4-r9}			@ Load 6 arguments into registers
+
+	vdup qTableBase, r9			@ Move lookup table base index into 4 ints
+
+	@ Outer loop: iterate over the number of oscillators, choosing 4 at a
+	@ time to work with.
+oscbank_oscillator_loop:
+	vld1 {dPhases_0, dPhases_1}, [r4]		@ no increment; will store at end of sample loop
+	vld1 {dFreqs_0, dFreqs_1}, [r5]
+	vld1 {dAmps_0, dAmps_1}, [r6]
+	vld1 {dFreqDs_0, dFreqDs_1}, [r7]!		@ increment; won't update at end of sample loop
+	vld1 {dAmpDs_0, dAmpDs_1}, [r8]!
+
+	push {r0-r1,r4-r8}
+	@ --- inner loop: iterate over the number of samples ---
+oscbank_sample_loop:
+	vcvt qBaseInts, qPhases		     		@ Take floor(phases)
+	vmov q2.f32, #1.0						@ Load 1.0 into every slot of q2
+	vshl q0.U32, qBaseInts, #2				@ Shift the indexes left 2 (*4 for float addressing)
+	vcvt qFractions, qBaseInts				@ int back to float
+	vadd q0.U32, q0.U32, qTableBase			@ Find memory addresses
+
+	vmov r4, r5, d0							@ Move two indexes to ARM registers
+	vmov r6, r7, d1							@ Move two more indexes to ARM registers
+	vsub qFractions, qPhases, qFractions	@ fraction = phase - floor(phase)
+
+	vldr.64	d0, [r4]						@ Load two consecutive floats at each location
+	vldr.64 d1, [r5]						@ These hold the previous and following samples in the table
+	vldr.64	d2, [r6]						@ TODO: check whether these work at 4-byte alignment
+	vldr.64 d3, [r7]
+
+	@ Format at this point:
+	@ Osc0(before) Osc0(after) Osc1(before) Osc1(after) Osc2(before) Osc2(after) Osc3(before) Osc3(after)
+	@ We want:
+	@ Osc0(before) Osc1(before) Osc2(before) Osc3(before) Osc0(after) Osc1(after) Osc2(after) Osc3(after)
+
+	vuzp.32 q0, q1							@ Now q0 contains before, q1 contains after
+	vsub q2.f32, q2.f32, qFractions			@ q2 = 1.0 - fraction
+	vmul q1.f32, q1.f32, qFractions			@ q1 = fraction * after
+	vmul q0.f32, q0.f32, q2.f32				@ q0 = (1.0 - fraction) * before
+
+	vadd qPhases, qPhases, qFreqs			@ Update phases
+	vadd qFreqs, qFreqs, qFreqDs			@ Update frequencies
+
+	vadd q0.f32, q0.f32, q1.f32				@ Add two interpolated components to get the final sample
+	vdup q2.u32, r3							@ Put lookup table size into each element of q2
+	vcvt qBaseInts, qPhases					@ Take floor of new phases
+	vmul q0.f32, q0.f32, qAmps				@ Multiply samples by current amplitude
+
+	vld1 dSample, [r1]						@ Load the current stereo samples
+	vpadd d2.f32, d0.f32, d1.f32			@ Pairwise accumulate q0 (output sample) into d2
+
+	vand q2, q2, qBaseInts					@ Logical AND of new phase int leaves 1 bit set only if phase >= table size
+	vpadd d3.f32, d2.f32, d2.f32			@ Pairwise accumulate d2 into d0 --> d0[0] and d0[1] both hold total of 4 oscillators
+	vadd qAmps, qAmps, qAmpDs				@ Update amplitudes
+	vcvt q0.f32, q2.u32						@ Convert int back to float after AND operation
+
+	vadd  dSample, dSample, d3.f32			@ Add oscillator outputs to each channel
+
+	subs r0, r0, #1							@ numFrames--
+	vsub qPhases, qPhases, q0.f32			@ Keep phases in table range
+	vst1 dSample, [r1]!						@ Store back in buffer and increment by 8
+
+	it gt
+	bgt oscbank_sample_loop					@ Loop if numFrames > 0
+
+	@ --- end inner loop ---
+	pop {r0-r1,r4-r8}						@ Restore registers: restores audioOut and numFrames, among others
+
+	vst1 {dPhases_0, dPhases_1}, [r4]!		@ Store phases back to array
+	vst1 {dFreqs_0, dFreqs_1}, [r5]!		@ Store frequencies back to array
+	vst1 {dAmps_0, dAmps_1}, [r6]!			@ Store amplitudes back to array
+											@ No need to update r7, r8
+
+	subs r2, r2, #4							@ numPartials -= 4
+	it  gt
+	bgt oscbank_oscillator_loop	@ Loop if numPartials > 0
+
+    pop {r4-r11}
+	bx lr
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/oscillator-bank/main.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,116 @@
+/*
+ * main.cpp
+ *
+ *  Created on: Oct 24, 2014
+ *      Author: parallels
+ */
+
+#include <iostream>
+#include <cstdlib>
+#include <libgen.h>
+#include <signal.h>
+#include <getopt.h>
+#include <Bela.h>
+
+using namespace std;
+
+int gNumOscillators = 32;
+int gWavetableLength = 1024;
+
+// Handle Ctrl-C by requesting that the audio rendering stop
+void interrupt_handler(int var)
+{
+	gShouldStop = true;
+}
+
+// Print usage information
+void usage(const char * processName)
+{
+	cerr << "Usage: " << processName << " [options]" << endl;
+
+	Bela_usage();
+
+	cerr << "   --num-oscillators [-n] oscs: Set the number of oscillators to use (default: 32)\n";
+	cerr << "   --wavetable [-w] length:     Set the wavetable length in samples (default: 1024)\n";
+	cerr << "   --help [-h]:                 Print this menu\n";
+}
+
+int main(int argc, char *argv[])
+{
+	BelaInitSettings settings;	// Standard audio settings
+
+	struct option customOptions[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"num-oscillators", 1, NULL, 'n'},
+		{"wavetable", 1, NULL, 'w'},
+		{NULL, 0, NULL, 0}
+	};
+
+	// Set default settings
+	Bela_defaultSettings(&settings);
+
+	// Parse command-line arguments
+	while (1) {
+		int c;
+		if ((c = Bela_getopt_long(argc, argv, "hn:w:", customOptions, &settings)) < 0)
+				break;
+		switch (c) {
+		case 'h':
+				usage(basename(argv[0]));
+				exit(0);
+		case 'n':
+				gNumOscillators = atoi(optarg);
+				if(gNumOscillators <= 0) {
+					usage(basename(argv[0]));
+					exit(0);
+				}
+				break;
+		case 'w':
+				gWavetableLength = atoi(optarg);
+				if(gWavetableLength < 4)
+					gWavetableLength = 4;
+				if(gWavetableLength > 16384)
+					gWavetableLength = 16384;
+				break;
+		case '?':
+		default:
+				usage(basename(argv[0]));
+				exit(1);
+		}
+	}
+
+	// Initialise the PRU audio device
+	if(Bela_initAudio(&settings, 0) != 0) {
+		cout << "Error: unable to initialise audio" << endl;
+		return -1;
+	}
+
+	if(settings.verbose) {
+		cout << "--> Using " << gNumOscillators << " oscillators and wavetable of " << gWavetableLength << " samples\n";
+	}
+
+	// Start the audio device running
+	if(Bela_startAudio()) {
+		cout << "Error: unable to start real-time audio" << endl;
+		return -1;
+	}
+
+	// Set up interrupt handler to catch Control-C and SIGTERM
+	signal(SIGINT, interrupt_handler);
+	signal(SIGTERM, interrupt_handler);
+
+	// Run until told to stop
+	while(!gShouldStop) {
+		usleep(100000);
+	}
+
+	// Stop the audio device
+	Bela_stopAudio();
+
+	// Clean up any resources allocated for audio
+	Bela_cleanupAudio();
+
+	// All done!
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/oscillator-bank/render.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,247 @@
+/*
+ ____  _____ _        _    
+| __ )| ____| |      / \   
+|  _ \|  _| | |     / _ \  
+| |_) | |___| |___ / ___ \ 
+|____/|_____|_____/_/   \_\
+
+The platform for ultra-low latency audio and sensor processing
+
+http://bela.io
+
+A project of the Augmented Instruments Laboratory within the
+Centre for Digital Music at Queen Mary University of London.
+http://www.eecs.qmul.ac.uk/~andrewm
+
+(c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
+  Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
+  Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
+
+The Bela software is distributed under the GNU Lesser General Public License
+(LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
+*/
+
+
+#include <Bela.h>
+#include <rtdk.h>
+#include <cstdlib>
+#include <cmath>
+#include <cstring>
+#include <time.h>
+
+const float kMinimumFrequency = 20.0f;
+const float kMaximumFrequency = 8000.0f;
+
+float *gWavetable;		// Buffer holding the precalculated sine lookup table
+float *gPhases;			// Buffer holding the phase of each oscillator
+float *gFrequencies;	// Buffer holding the frequencies of each oscillator
+float *gAmplitudes;		// Buffer holding the amplitudes of each oscillator
+float *gDFrequencies;	// Buffer holding the derivatives of frequency
+float *gDAmplitudes;	// Buffer holding the derivatives of amplitude
+
+float gAudioSampleRate;
+int gSampleCount;		// Sample counter for indicating when to update frequencies
+float gNewMinFrequency;
+float gNewMaxFrequency;
+
+// Task for handling the update of the frequencies using the matrix
+AuxiliaryTask gFrequencyUpdateTask;
+
+// These settings are carried over from main.cpp
+// Setting global variables is an alternative approach
+// to passing a structure to userData in setup()
+
+extern int gNumOscillators;
+extern int gWavetableLength;
+
+void recalculate_frequencies();
+
+extern "C" {
+	// Function prototype for ARM assembly implementation of oscillator bank
+	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
+							  int activePartialNum, int lookupTableSize,
+							  float *phases, float *frequencies, float *amplitudes,
+							  float *freqDerivatives, float *ampDerivatives,
+							  float *lookupTable);
+}
+
+// setup() is called once before the audio rendering starts.
+// Use it to perform any initialisation and allocation which is dependent
+// on the period size or sample rate.
+//
+// userData holds an opaque pointer to a data structure that was passed
+// in from the call to initAudio().
+//
+// Return true on success; returning false halts the program.
+bool setup(BelaContext *context, void *userData)
+{
+	srandom(time(NULL));
+
+	if(context->audioChannels != 2) {
+		rt_printf("Error: this example needs stereo audio enabled\n");
+		return false;
+	}
+
+	// Initialise the sine wavetable
+	if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) {
+		rt_printf("Error allocating wavetable\n");
+		return false;
+	}
+	for(int n = 0; n < gWavetableLength + 1; n++)
+		gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength);
+
+	// Allocate the other buffers
+	if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) {
+		rt_printf("Error allocating phase buffer\n");
+		return false;
+	}
+	if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) {
+		rt_printf("Error allocating frequency buffer\n");
+		return false;
+	}
+	if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) {
+		rt_printf("Error allocating amplitude buffer\n");
+		return false;
+	}
+	if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) {
+		rt_printf("Error allocating frequency derivative buffer\n");
+		return false;
+	}
+	if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) {
+		rt_printf("Error allocating amplitude derivative buffer\n");
+		return false;
+	}
+
+	// Initialise buffer contents
+
+	float freq = kMinimumFrequency;
+	float increment = (kMaximumFrequency - kMinimumFrequency) / (float)gNumOscillators;
+
+	for(int n = 0; n < gNumOscillators; n++) {
+		gPhases[n] = 0.0;
+
+		if(context->analogFrames == 0) {
+			// Random frequencies when used without matrix
+			gFrequencies[n] = kMinimumFrequency + (kMaximumFrequency - kMinimumFrequency) * ((float)random() / (float)RAND_MAX);
+		}
+		else {
+			// Constant spread of frequencies when used with matrix
+			gFrequencies[n] = freq;
+			freq += increment;
+		}
+
+		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
+		gFrequencies[n] *= (float)gWavetableLength / context->audioSampleRate;
+		gAmplitudes[n] = ((float)random() / (float)RAND_MAX) / (float)gNumOscillators;
+		gDFrequencies[n] = gDAmplitudes[n] = 0.0;
+	}
+
+	increment = 0;
+	freq = 440.0;
+
+	for(int n = 0; n < gNumOscillators; n++) {
+		// Update the frequencies to a regular spread, plus a small amount of randomness
+		// to avoid weird phase effects
+		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
+		float newFreq = freq * randScale;
+
+		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
+		gFrequencies[n] = newFreq * (float)gWavetableLength / context->audioSampleRate;
+
+		freq += increment;
+	}
+
+	// Initialise auxiliary tasks
+	if((gFrequencyUpdateTask = Bela_createAuxiliaryTask(&recalculate_frequencies, 85, "bela-update-frequencies")) == 0)
+		return false;
+
+	//for(int n = 0; n < gNumOscillators; n++)
+	//	rt_printf("%f\n", gFrequencies[n]);
+
+	gAudioSampleRate = context->audioSampleRate;
+	gSampleCount = 0;
+
+	return true;
+}
+
+// render() is called regularly at the highest priority by the audio engine.
+// Input and output are given from the audio hardware and the other
+// ADCs and DACs (if available). If only audio is available, numMatrixFrames
+// will be 0.
+
+void render(BelaContext *context, void *userData)
+{
+	// Initialise buffer to 0
+	memset(context->audioOut, 0, 2 * context->audioFrames * sizeof(float));
+
+	// Render audio frames
+	oscillator_bank_neon(context->audioFrames, context->audioOut,
+			gNumOscillators, gWavetableLength,
+			gPhases, gFrequencies, gAmplitudes,
+			gDFrequencies, gDAmplitudes,
+			gWavetable);
+
+	if(context->analogFrames != 0 && (gSampleCount += context->audioFrames) >= 128) {
+		gSampleCount = 0;
+		gNewMinFrequency = map(context->analogIn[0], 0, 1.0, 1000.0f, 8000.0f);
+		gNewMaxFrequency = map(context->analogIn[1], 0, 1.0, 1000.0f, 8000.0f);
+
+		// Make sure max >= min
+		if(gNewMaxFrequency < gNewMinFrequency) {
+			float temp = gNewMaxFrequency;
+			gNewMaxFrequency = gNewMinFrequency;
+			gNewMinFrequency = temp;
+		}
+
+		// Request that the lower-priority task run at next opportunity
+		//Bela_scheduleAuxiliaryTask(gFrequencyUpdateTask);
+	}
+}
+
+// This is a lower-priority call to update the frequencies which will happen
+// periodically when the matrix is enabled. By placing it at a lower priority,
+// it has minimal effect on the audio performance but it will take longer to
+// complete if the system is under heavy audio load.
+
+void recalculate_frequencies()
+{
+	float freq = gNewMinFrequency;
+	float increment = (gNewMaxFrequency - gNewMinFrequency) / (float)gNumOscillators;
+
+	for(int n = 0; n < gNumOscillators; n++) {
+		// Update the frequencies to a regular spread, plus a small amount of randomness
+		// to avoid weird phase effects
+		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
+		float newFreq = freq * randScale;
+
+		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
+		gFrequencies[n] = newFreq * (float)gWavetableLength / gAudioSampleRate;
+
+		freq += increment;
+	}
+}
+
+
+// cleanup() is called once at the end, after the audio has stopped.
+// Release any resources that were allocated in setup().
+
+void cleanup(BelaContext *context, void *userData)
+{
+	free(gWavetable);
+	free(gPhases);
+	free(gFrequencies);
+	free(gAmplitudes);
+	free(gDFrequencies);
+	free(gDAmplitudes);
+}
+
+/**
+\example 10-oscillator-bank
+
+Oscillator Bank
+----------------------
+
+These files demonstrate an oscillator bank implemented in assembly code 
+that is used as part of the d-box project.
+*/
+
--- a/examples/10-Instruments/oscillator_bank/audio_routines.S	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-@
-@ audio_routines.S
-@
-@ NEON-based functions for time-critical audio processing
-@
-@ Andrew McPherson 2014
-@ Queen Mary University of London
-@
-
-	.syntax unified
-	.arch armv7-a
-	.fpu neon
-
-@ 	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
-@							  int activePartialNum, int lookupTableSize,
-@							  float *phases, float *frequencies, float *amplitudes,
-@							  float *freqDerivatives, float *ampDerivatives,
-@							  float *lookupTable);
-
-@ Registers:
-@    r0: numAudioFrames        How many frames to render
-@    r1: audioOut              Buffer for audio output samples [stereo]
-@    r2: activePartialNum      How many active partials to render
-@    r3: lookupTableSize       Size of lookup table
-@    ---- other arguments start on the stack and are moved: -----
-@    r4: phases                Phase of each oscillator (pointer)
-@    r5: frequencies           Normalised frequency of each oscillator (pointer)
-@    r6: amplitudes            Normalised amplitude of each oscillator (pointer)
-@    r7: freqDerivatives       Derivative of frequency for each oscillator (pointer)
-@    r8: ampDerivatives        Derivative of amplitude for each oscillator (pointer)
-@    r9: lookupTable           Lookup table containing one oscillation
-@
-@ Alignment requirements:
-@    audioOut: 8-byte boundary
-@    phases: 16-byte boundary
-@    frequencies: 16-byte boundary
-@    amplitudes: 16-byte boundary
-@    freqDerivatives: 16-byte bounary
-@    ampDerivatives: 16-byte boundary
-@    lookupTable: 4-byte boundary (TODO: check this)
-
-	.align	2
-	.global	oscillator_bank_neon
-	.thumb
-	.thumb_func
-	.type	oscillator_bank_neon, %function
-oscillator_bank_neon:
-
-
-dSample		.dn		D6.F32
-qPhases		.qn		Q8.F32
-dPhases_0	.dn		D16.F32
-dPhases_1	.dn		D17.F32
-qFreqs		.qn		Q9.F32
-dFreqs_0	.dn		D18.F32
-dFreqs_1	.dn		D19.F32
-qAmps		.qn		Q10.F32
-dAmps_0		.dn		D20.F32
-dAmps_1		.dn		D21.F32
-qFreqDs		.qn		Q11.F32
-dFreqDs_0	.dn		D22.F32
-dFreqDs_1	.dn		D23.F32
-qAmpDs		.qn		Q12.F32
-dAmpDs_0	.dn		D24.F32
-dAmpDs_1	.dn		D25.F32
-
-qBaseInts	.qn		Q13.U32		@ Base indexes: unsigned ints x4
-dBaseInts_0	.dn		D26.U32
-dBaseInts_1	.dn		D27.U32
-qFractions  .qn     Q14.F32		@ Fraction indexes: floats x4
-qTableBase	.qn		Q15.U32		@ Base of lookup table
-
-	cmp r0, #0					@ Check for trivial case 1: zero frames
-	it eq
-	bxeq lr						@ Return if that's the case (otherwise might have odd behaviour)
-	cmp r2, #4					@ Check for trivial case 2: zero oscillators
-	it lt
-	bxlt lr						@ Return if that's the case
-
-	push {r4-r11}				@ Now arguments start 32 bytes above SP
-    add r11, sp, #32			@ Pointer to 32 bytes into the stack
-    ldm r11, {r4-r9}			@ Load 6 arguments into registers
-
-	vdup qTableBase, r9			@ Move lookup table base index into 4 ints
-
-	@ Outer loop: iterate over the number of oscillators, choosing 4 at a
-	@ time to work with.
-oscbank_oscillator_loop:
-	vld1 {dPhases_0, dPhases_1}, [r4]		@ no increment; will store at end of sample loop
-	vld1 {dFreqs_0, dFreqs_1}, [r5]
-	vld1 {dAmps_0, dAmps_1}, [r6]
-	vld1 {dFreqDs_0, dFreqDs_1}, [r7]!		@ increment; won't update at end of sample loop
-	vld1 {dAmpDs_0, dAmpDs_1}, [r8]!
-
-	push {r0-r1,r4-r8}
-	@ --- inner loop: iterate over the number of samples ---
-oscbank_sample_loop:
-	vcvt qBaseInts, qPhases		     		@ Take floor(phases)
-	vmov q2.f32, #1.0						@ Load 1.0 into every slot of q2
-	vshl q0.U32, qBaseInts, #2				@ Shift the indexes left 2 (*4 for float addressing)
-	vcvt qFractions, qBaseInts				@ int back to float
-	vadd q0.U32, q0.U32, qTableBase			@ Find memory addresses
-
-	vmov r4, r5, d0							@ Move two indexes to ARM registers
-	vmov r6, r7, d1							@ Move two more indexes to ARM registers
-	vsub qFractions, qPhases, qFractions	@ fraction = phase - floor(phase)
-
-	vldr.64	d0, [r4]						@ Load two consecutive floats at each location
-	vldr.64 d1, [r5]						@ These hold the previous and following samples in the table
-	vldr.64	d2, [r6]						@ TODO: check whether these work at 4-byte alignment
-	vldr.64 d3, [r7]
-
-	@ Format at this point:
-	@ Osc0(before) Osc0(after) Osc1(before) Osc1(after) Osc2(before) Osc2(after) Osc3(before) Osc3(after)
-	@ We want:
-	@ Osc0(before) Osc1(before) Osc2(before) Osc3(before) Osc0(after) Osc1(after) Osc2(after) Osc3(after)
-
-	vuzp.32 q0, q1							@ Now q0 contains before, q1 contains after
-	vsub q2.f32, q2.f32, qFractions			@ q2 = 1.0 - fraction
-	vmul q1.f32, q1.f32, qFractions			@ q1 = fraction * after
-	vmul q0.f32, q0.f32, q2.f32				@ q0 = (1.0 - fraction) * before
-
-	vadd qPhases, qPhases, qFreqs			@ Update phases
-	vadd qFreqs, qFreqs, qFreqDs			@ Update frequencies
-
-	vadd q0.f32, q0.f32, q1.f32				@ Add two interpolated components to get the final sample
-	vdup q2.u32, r3							@ Put lookup table size into each element of q2
-	vcvt qBaseInts, qPhases					@ Take floor of new phases
-	vmul q0.f32, q0.f32, qAmps				@ Multiply samples by current amplitude
-
-	vld1 dSample, [r1]						@ Load the current stereo samples
-	vpadd d2.f32, d0.f32, d1.f32			@ Pairwise accumulate q0 (output sample) into d2
-
-	vand q2, q2, qBaseInts					@ Logical AND of new phase int leaves 1 bit set only if phase >= table size
-	vpadd d3.f32, d2.f32, d2.f32			@ Pairwise accumulate d2 into d0 --> d0[0] and d0[1] both hold total of 4 oscillators
-	vadd qAmps, qAmps, qAmpDs				@ Update amplitudes
-	vcvt q0.f32, q2.u32						@ Convert int back to float after AND operation
-
-	vadd  dSample, dSample, d3.f32			@ Add oscillator outputs to each channel
-
-	subs r0, r0, #1							@ numFrames--
-	vsub qPhases, qPhases, q0.f32			@ Keep phases in table range
-	vst1 dSample, [r1]!						@ Store back in buffer and increment by 8
-
-	it gt
-	bgt oscbank_sample_loop					@ Loop if numFrames > 0
-
-	@ --- end inner loop ---
-	pop {r0-r1,r4-r8}						@ Restore registers: restores audioOut and numFrames, among others
-
-	vst1 {dPhases_0, dPhases_1}, [r4]!		@ Store phases back to array
-	vst1 {dFreqs_0, dFreqs_1}, [r5]!		@ Store frequencies back to array
-	vst1 {dAmps_0, dAmps_1}, [r6]!			@ Store amplitudes back to array
-											@ No need to update r7, r8
-
-	subs r2, r2, #4							@ numPartials -= 4
-	it  gt
-	bgt oscbank_oscillator_loop	@ Loop if numPartials > 0
-
-    pop {r4-r11}
-	bx lr
--- a/examples/10-Instruments/oscillator_bank/main.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * main.cpp
- *
- *  Created on: Oct 24, 2014
- *      Author: parallels
- */
-
-#include <iostream>
-#include <cstdlib>
-#include <libgen.h>
-#include <signal.h>
-#include <getopt.h>
-#include <Bela.h>
-
-using namespace std;
-
-int gNumOscillators = 32;
-int gWavetableLength = 1024;
-
-// Handle Ctrl-C by requesting that the audio rendering stop
-void interrupt_handler(int var)
-{
-	gShouldStop = true;
-}
-
-// Print usage information
-void usage(const char * processName)
-{
-	cerr << "Usage: " << processName << " [options]" << endl;
-
-	Bela_usage();
-
-	cerr << "   --num-oscillators [-n] oscs: Set the number of oscillators to use (default: 32)\n";
-	cerr << "   --wavetable [-w] length:     Set the wavetable length in samples (default: 1024)\n";
-	cerr << "   --help [-h]:                 Print this menu\n";
-}
-
-int main(int argc, char *argv[])
-{
-	BelaInitSettings settings;	// Standard audio settings
-
-	struct option customOptions[] =
-	{
-		{"help", 0, NULL, 'h'},
-		{"num-oscillators", 1, NULL, 'n'},
-		{"wavetable", 1, NULL, 'w'},
-		{NULL, 0, NULL, 0}
-	};
-
-	// Set default settings
-	Bela_defaultSettings(&settings);
-
-	// Parse command-line arguments
-	while (1) {
-		int c;
-		if ((c = Bela_getopt_long(argc, argv, "hn:w:", customOptions, &settings)) < 0)
-				break;
-		switch (c) {
-		case 'h':
-				usage(basename(argv[0]));
-				exit(0);
-		case 'n':
-				gNumOscillators = atoi(optarg);
-				if(gNumOscillators <= 0) {
-					usage(basename(argv[0]));
-					exit(0);
-				}
-				break;
-		case 'w':
-				gWavetableLength = atoi(optarg);
-				if(gWavetableLength < 4)
-					gWavetableLength = 4;
-				if(gWavetableLength > 16384)
-					gWavetableLength = 16384;
-				break;
-		case '?':
-		default:
-				usage(basename(argv[0]));
-				exit(1);
-		}
-	}
-
-	// Initialise the PRU audio device
-	if(Bela_initAudio(&settings, 0) != 0) {
-		cout << "Error: unable to initialise audio" << endl;
-		return -1;
-	}
-
-	if(settings.verbose) {
-		cout << "--> Using " << gNumOscillators << " oscillators and wavetable of " << gWavetableLength << " samples\n";
-	}
-
-	// Start the audio device running
-	if(Bela_startAudio()) {
-		cout << "Error: unable to start real-time audio" << endl;
-		return -1;
-	}
-
-	// Set up interrupt handler to catch Control-C and SIGTERM
-	signal(SIGINT, interrupt_handler);
-	signal(SIGTERM, interrupt_handler);
-
-	// Run until told to stop
-	while(!gShouldStop) {
-		usleep(100000);
-	}
-
-	// Stop the audio device
-	Bela_stopAudio();
-
-	// Clean up any resources allocated for audio
-	Bela_cleanupAudio();
-
-	// All done!
-	return 0;
-}
--- a/examples/10-Instruments/oscillator_bank/render.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-/*
- ____  _____ _        _    
-| __ )| ____| |      / \   
-|  _ \|  _| | |     / _ \  
-| |_) | |___| |___ / ___ \ 
-|____/|_____|_____/_/   \_\.io
-
- */
-
-/*
- * render.cpp
- *
- *  Created on: Oct 24, 2014
- *      Author: parallels
- */
-
-/**
-\example 4_oscillator_bank
-
-Oscillator Bank
-----------------------
-
-These files demonstrate an oscillator bank implemented in assembly code 
-that is used as part of the d-box project.
-*/
-
-#include <Bela.h>
-#include <rtdk.h>
-#include <cstdlib>
-#include <cmath>
-#include <cstring>
-#include <time.h>
-
-const float kMinimumFrequency = 20.0f;
-const float kMaximumFrequency = 8000.0f;
-
-float *gWavetable;		// Buffer holding the precalculated sine lookup table
-float *gPhases;			// Buffer holding the phase of each oscillator
-float *gFrequencies;	// Buffer holding the frequencies of each oscillator
-float *gAmplitudes;		// Buffer holding the amplitudes of each oscillator
-float *gDFrequencies;	// Buffer holding the derivatives of frequency
-float *gDAmplitudes;	// Buffer holding the derivatives of amplitude
-
-float gAudioSampleRate;
-int gSampleCount;		// Sample counter for indicating when to update frequencies
-float gNewMinFrequency;
-float gNewMaxFrequency;
-
-// Task for handling the update of the frequencies using the matrix
-AuxiliaryTask gFrequencyUpdateTask;
-
-// These settings are carried over from main.cpp
-// Setting global variables is an alternative approach
-// to passing a structure to userData in setup()
-
-extern int gNumOscillators;
-extern int gWavetableLength;
-
-void recalculate_frequencies();
-
-extern "C" {
-	// Function prototype for ARM assembly implementation of oscillator bank
-	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
-							  int activePartialNum, int lookupTableSize,
-							  float *phases, float *frequencies, float *amplitudes,
-							  float *freqDerivatives, float *ampDerivatives,
-							  float *lookupTable);
-}
-
-// setup() is called once before the audio rendering starts.
-// Use it to perform any initialisation and allocation which is dependent
-// on the period size or sample rate.
-//
-// userData holds an opaque pointer to a data structure that was passed
-// in from the call to initAudio().
-//
-// Return true on success; returning false halts the program.
-bool setup(BelaContext *context, void *userData)
-{
-	srandom(time(NULL));
-
-	if(context->audioChannels != 2) {
-		rt_printf("Error: this example needs stereo audio enabled\n");
-		return false;
-	}
-
-	// Initialise the sine wavetable
-	if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) {
-		rt_printf("Error allocating wavetable\n");
-		return false;
-	}
-	for(int n = 0; n < gWavetableLength + 1; n++)
-		gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength);
-
-	// Allocate the other buffers
-	if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) {
-		rt_printf("Error allocating phase buffer\n");
-		return false;
-	}
-	if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) {
-		rt_printf("Error allocating frequency buffer\n");
-		return false;
-	}
-	if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) {
-		rt_printf("Error allocating amplitude buffer\n");
-		return false;
-	}
-	if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) {
-		rt_printf("Error allocating frequency derivative buffer\n");
-		return false;
-	}
-	if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) {
-		rt_printf("Error allocating amplitude derivative buffer\n");
-		return false;
-	}
-
-	// Initialise buffer contents
-
-	float freq = kMinimumFrequency;
-	float increment = (kMaximumFrequency - kMinimumFrequency) / (float)gNumOscillators;
-
-	for(int n = 0; n < gNumOscillators; n++) {
-		gPhases[n] = 0.0;
-
-		if(context->analogFrames == 0) {
-			// Random frequencies when used without matrix
-			gFrequencies[n] = kMinimumFrequency + (kMaximumFrequency - kMinimumFrequency) * ((float)random() / (float)RAND_MAX);
-		}
-		else {
-			// Constant spread of frequencies when used with matrix
-			gFrequencies[n] = freq;
-			freq += increment;
-		}
-
-		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
-		gFrequencies[n] *= (float)gWavetableLength / context->audioSampleRate;
-		gAmplitudes[n] = ((float)random() / (float)RAND_MAX) / (float)gNumOscillators;
-		gDFrequencies[n] = gDAmplitudes[n] = 0.0;
-	}
-
-	increment = 0;
-	freq = 440.0;
-
-	for(int n = 0; n < gNumOscillators; n++) {
-		// Update the frequencies to a regular spread, plus a small amount of randomness
-		// to avoid weird phase effects
-		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
-		float newFreq = freq * randScale;
-
-		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
-		gFrequencies[n] = newFreq * (float)gWavetableLength / context->audioSampleRate;
-
-		freq += increment;
-	}
-
-	// Initialise auxiliary tasks
-	if((gFrequencyUpdateTask = Bela_createAuxiliaryTask(&recalculate_frequencies, 85, "bela-update-frequencies")) == 0)
-		return false;
-
-	//for(int n = 0; n < gNumOscillators; n++)
-	//	rt_printf("%f\n", gFrequencies[n]);
-
-	gAudioSampleRate = context->audioSampleRate;
-	gSampleCount = 0;
-
-	return true;
-}
-
-// render() is called regularly at the highest priority by the audio engine.
-// Input and output are given from the audio hardware and the other
-// ADCs and DACs (if available). If only audio is available, numMatrixFrames
-// will be 0.
-
-void render(BelaContext *context, void *userData)
-{
-	// Initialise buffer to 0
-	memset(context->audioOut, 0, 2 * context->audioFrames * sizeof(float));
-
-	// Render audio frames
-	oscillator_bank_neon(context->audioFrames, context->audioOut,
-			gNumOscillators, gWavetableLength,
-			gPhases, gFrequencies, gAmplitudes,
-			gDFrequencies, gDAmplitudes,
-			gWavetable);
-
-	if(context->analogFrames != 0 && (gSampleCount += context->audioFrames) >= 128) {
-		gSampleCount = 0;
-		gNewMinFrequency = map(context->analogIn[0], 0, 1.0, 1000.0f, 8000.0f);
-		gNewMaxFrequency = map(context->analogIn[1], 0, 1.0, 1000.0f, 8000.0f);
-
-		// Make sure max >= min
-		if(gNewMaxFrequency < gNewMinFrequency) {
-			float temp = gNewMaxFrequency;
-			gNewMaxFrequency = gNewMinFrequency;
-			gNewMinFrequency = temp;
-		}
-
-		// Request that the lower-priority task run at next opportunity
-		//Bela_scheduleAuxiliaryTask(gFrequencyUpdateTask);
-	}
-}
-
-// This is a lower-priority call to update the frequencies which will happen
-// periodically when the matrix is enabled. By placing it at a lower priority,
-// it has minimal effect on the audio performance but it will take longer to
-// complete if the system is under heavy audio load.
-
-void recalculate_frequencies()
-{
-	float freq = gNewMinFrequency;
-	float increment = (gNewMaxFrequency - gNewMinFrequency) / (float)gNumOscillators;
-
-	for(int n = 0; n < gNumOscillators; n++) {
-		// Update the frequencies to a regular spread, plus a small amount of randomness
-		// to avoid weird phase effects
-		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
-		float newFreq = freq * randScale;
-
-		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
-		gFrequencies[n] = newFreq * (float)gWavetableLength / gAudioSampleRate;
-
-		freq += increment;
-	}
-}
-
-
-// cleanup() is called once at the end, after the audio has stopped.
-// Release any resources that were allocated in setup().
-
-void cleanup(BelaContext *context, void *userData)
-{
-	free(gWavetable);
-	free(gPhases);
-	free(gFrequencies);
-	free(gAmplitudes);
-	free(gDFrequencies);
-	free(gDAmplitudes);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/game.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,339 @@
+/*
+ * game.cpp
+ *
+ *  Created on: Nov 10, 2014
+ *      Author: parallels
+ */
+
+#include <cmath>
+#include <cstdlib>
+#include "vector_graphics.h"
+#include <Utilities.h>
+
+// Virtual screen size
+int screenWidth, screenHeight;
+
+// Basic information on the terrain and the tanks
+float *groundLevel;  // Y coordinate of the ground for each X coordinate
+float tank1X, tank1Y, tank2X, tank2Y; // Positions of the two tanks
+float tankRadius = 20;  // Radius of the tanks
+float cannonLength = 40;   // How long the cannon on each tank extends
+float gravity = 0.05;      // Strength of gravity
+
+// Current state of the game
+int playerHasWon = 0; // 1 if player 1 wins, 2 if player 2 wins, 0 if game in progress
+bool player1Turn = true;  // true if it's player 1's turn; false otherwise
+float tank1CannonAngle = M_PI/2;
+float tank2CannonAngle = M_PI/2; // Direction the tank cannons are pointing
+float tank1CannonStrength = 3;
+float tank2CannonStrength = 3; // Strength of intended projectile launch
+
+// Location of the projectile
+bool projectileInMotion = false;
+float projectilePositionX, projectilePositionY;
+float projectileVelocityX, projectileVelocityY;
+
+// Infor needed for sound rendering
+bool collisionJustOccurred = false;
+bool tankHitJustOccurred = false;
+
+// Useful utility function for generating random floating-point values
+float randomFloat(float low, float hi)
+{
+	float r = (float)random() / (float)RAND_MAX;
+	return map(r, 0, 1, low, hi);
+}
+
+// Restart the game, without reallocating memory
+void restartGame()
+{
+	float player1Height = screenHeight * 3/4; // randomFloat(screenHeight/2, screenHeight-5);
+	float player2Height = screenHeight - 5; // randomFloat(screenHeight/2, screenHeight-5);
+	for(int i = 0; i < screenWidth * 0.2; i++) {
+		groundLevel[i] = player1Height;
+	}
+	for(int i = screenWidth * 0.2; i < screenWidth * 0.8; i++) {
+		groundLevel[i] = player1Height + (player2Height - player1Height) * (i - screenWidth*0.2)/(screenWidth*0.6);
+	}
+	for(int i = screenWidth * 0.8; i < screenWidth; i++) {
+		groundLevel[i] = player2Height;
+	}
+
+	// Set the location of the two tanks so they rest on the ground at opposite sides
+	tank1X = screenWidth * 0.1;
+	tank1Y = player1Height;
+	tank2X = screenWidth * 0.9;
+	tank2Y = player2Height;
+
+	playerHasWon = 0;
+	projectileInMotion = false;
+}
+
+// Initialise the game
+void setupGame(int width, int height)
+{
+	// Set the screen size
+	screenWidth = width;
+	screenHeight = height;
+
+	// Initialize the ground level
+	groundLevel = new float[screenWidth];
+
+	restartGame();
+}
+
+// Advance the turn to the next player
+void nextPlayersTurn() {
+	player1Turn = !player1Turn;
+}
+
+
+// Move forward one frame on the game physics
+void nextGameFrame()
+{
+	if(!projectileInMotion)
+		return;
+
+	// Update position of projectile
+	projectilePositionX += projectileVelocityX;
+	projectilePositionY += projectileVelocityY;
+	projectileVelocityY += gravity;
+
+	// Check collision with tanks first: a collision with tank 1 means player 2 wins and vice-versa
+	if((tank1X - projectilePositionX)*(tank1X - projectilePositionX) +
+		(tank1Y - projectilePositionY)*(tank1Y - projectilePositionY)
+		<= tankRadius * tankRadius)
+	{
+		projectileInMotion = false;
+		collisionJustOccurred = false;
+		tankHitJustOccurred = true;
+		playerHasWon = 2;
+	}
+	else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) +
+		(tank2Y - projectilePositionY)*(tank2Y - projectilePositionY)
+		<= tankRadius * tankRadius)
+	{
+		projectileInMotion = false;
+		collisionJustOccurred = false;
+		tankHitJustOccurred = true;
+		playerHasWon = 1;
+	}
+	else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) {
+		// Check collision whether projectile has exited the screen to the left or right
+		projectileInMotion = false;
+		collisionJustOccurred = true;
+		nextPlayersTurn();
+	}
+	else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) {
+		// Check for projectile collision with ground
+		projectileInMotion = false;
+		collisionJustOccurred = true;
+		nextPlayersTurn();
+	}
+}
+
+// Updates for game state
+void setTank1CannonAngle(float angle)
+{
+	tank1CannonAngle = angle;
+}
+
+void setTank2CannonAngle(float angle)
+{
+	tank2CannonAngle = angle;
+}
+
+void setTank1CannonStrength(float strength)
+{
+	tank1CannonStrength = strength;
+}
+
+void setTank2CannonStrength(float strength)
+{
+	tank2CannonStrength = strength;
+}
+
+// FIRE!
+void fireProjectile()
+{
+	// Can't fire while projectile is already moving, or if someone has won
+	if(projectileInMotion)
+		return;
+	if(playerHasWon != 0)
+		return;
+
+    if(player1Turn) {
+		projectilePositionX = tank1X + cannonLength * cosf(tank1CannonAngle);
+		projectilePositionY = tank1Y - cannonLength * sinf(tank1CannonAngle);
+		projectileVelocityX = tank1CannonStrength * cosf(tank1CannonAngle);
+		projectileVelocityY = -tank1CannonStrength * sinf(tank1CannonAngle);
+    }
+    else {
+		projectilePositionX = tank2X + cannonLength * cosf(tank2CannonAngle);
+		projectilePositionY = tank2Y - cannonLength * sinf(tank2CannonAngle);
+		projectileVelocityX = tank2CannonStrength * cosf(tank2CannonAngle);
+		projectileVelocityY = -tank2CannonStrength * sinf(tank2CannonAngle);
+    }
+
+    // GO!
+    projectileInMotion = true;
+}
+
+// Game state queries
+bool gameStatusPlayer1Turn()
+{
+	return player1Turn;
+}
+
+bool gameStatusProjectileInMotion()
+{
+	return projectileInMotion;
+}
+
+int gameStatusWinner()
+{
+	return playerHasWon;
+}
+
+bool gameStatusCollisionOccurred()
+{
+	if(collisionJustOccurred) {
+		collisionJustOccurred = false;
+		return true;
+	}
+	return false;
+}
+
+bool gameStatusTankHitOccurred()
+{
+	if(tankHitJustOccurred) {
+		tankHitJustOccurred = false;
+		return true;
+	}
+	return false;
+}
+
+
+float gameStatusProjectileHeight()
+{
+	return projectilePositionY / (float)screenHeight;
+}
+
+// Clean up any allocated memory for the game
+void cleanupGame()
+{
+	delete groundLevel;
+}
+
+// Drawing routines. Arguments are (interleaved) buffer to render
+// into, the available size, and the target for how many samples
+// to use (actual usage might vary slightly). Regardless of
+// lengthTarget, never use more than bufferSize samples.
+
+int drawGround(float *buffer, int bufferSize, int framesTarget)
+{
+	int length;
+
+	// Calculate total length of ground line, to arrive at a speed calculation
+	float totalLineLength = 0.4f*screenWidth
+							+ sqrtf(0.36f*screenWidth*screenWidth
+									+ (tank2Y-tank1Y)*(tank2Y-tank1Y));
+
+	// Speed is calculated in pixels per frame
+	float speed = totalLineLength / (float)framesTarget;
+
+	// Draw three lines: platforms for tanks and the connecting line.
+	// Eventually, render a more complex ground from the array.
+	length = renderLine(0, tank1Y, screenWidth * 0.2, tank1Y,
+						speed, buffer, bufferSize);
+	length += renderLine(screenWidth * 0.2, tank1Y, screenWidth * 0.8, tank2Y,
+						speed, &buffer[length], bufferSize - length);
+	length += renderLine(screenWidth * 0.8, tank2Y, screenWidth, tank2Y,
+						speed, &buffer[length], bufferSize - length);
+
+	return length;
+}
+
+int drawTanks(float *buffer, int bufferSize, int framesTarget)
+{
+	int length = 0;
+
+	// Calculate total length of tank lines, to arrive at a speed calculation
+	float totalLineLength = 2.0*M_PI*tankRadius + 2.0*(cannonLength - tankRadius);
+
+	// Speed is calculated in pixels per frame
+	float speed = totalLineLength / (float)framesTarget;
+
+	if(playerHasWon != 2) {
+		// Tank 1 body = semicircle + line
+		length += renderArc(tank1X, tank1Y, tankRadius, M_PI, 2.0 * M_PI,
+							speed, buffer, bufferSize);
+		length += renderLine(tank1X + tankRadius, tank1Y,
+							 tank1X - tankRadius, tank1Y,
+							speed, &buffer[length], bufferSize - length);
+		// Tank 1 cannon (line depending on angle)
+		length += renderLine(tank1X + tankRadius * cosf(tank1CannonAngle),
+			 tank1Y - tankRadius * sinf(tank1CannonAngle),
+			 tank1X + cannonLength * cosf(tank1CannonAngle),
+			 tank1Y - cannonLength * sinf(tank1CannonAngle),
+			 speed, &buffer[length], bufferSize - length);
+	}
+
+	if(playerHasWon != 1) {
+		// Same idea for tank 2
+		length += renderArc(tank2X, tank2Y, tankRadius, M_PI, 2.0 * M_PI,
+							speed, &buffer[length], bufferSize - length);
+		length += renderLine(tank2X + tankRadius, tank2Y,
+							 tank2X - tankRadius, tank2Y,
+							 speed, &buffer[length], bufferSize - length);
+		length += renderLine(tank2X + tankRadius * cosf(tank2CannonAngle),
+			 tank2Y - tankRadius * sinf(tank2CannonAngle),
+			 tank2X + cannonLength * cosf(tank2CannonAngle),
+			 tank2Y - cannonLength * sinf(tank2CannonAngle),
+			 speed, &buffer[length], bufferSize - length);
+	}
+
+	return length;
+}
+
+int drawProjectile(float *buffer, int bufferSize, int framesTarget)
+{
+	if(!projectileInMotion)
+		return 0;
+
+	// Draw a point for a specified number of frames (each containing X and Y)
+	// Return the number of items used in the buffer, which will be twice
+	// the number of frames unless the buffer is full
+
+	if(bufferSize/2 < framesTarget) {
+		renderPoint(projectilePositionX, projectilePositionY, buffer, bufferSize/2);
+		return bufferSize;
+	}
+	else {
+		renderPoint(projectilePositionX, projectilePositionY, buffer, framesTarget);
+		return framesTarget*2;
+	}
+}
+
+// Main drawing routine entry point
+int drawGame(float *buffer, int bufferSize)
+{
+	int length;
+
+	// Based on buffer size, come up with speeds for each of the elements
+	// 50% of time to ground; 30% to the tanks and 20% to the projectile
+	// Give a margin of 25% beyond so we don't run out of buffer space
+	// if things take longer to draw than we guess they will
+	const float amountToUse = 0.375; // 0.75/2 because two samples per frame
+	const float groundFraction = 0.5 * amountToUse;
+	const float tankFraction = 0.3 * amountToUse;
+	const float projectileFraction = 0.2 * amountToUse;
+
+	length = drawGround(buffer, bufferSize, bufferSize * groundFraction);
+	length += drawTanks(&buffer[length], bufferSize - length,
+						bufferSize * tankFraction);
+	length += drawProjectile(&buffer[length], bufferSize - length,
+						bufferSize * projectileFraction);
+
+	return length;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/game.h	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,39 @@
+/*
+ * game.h
+ *
+ *  Created on: Nov 10, 2014
+ *      Author: parallels
+ */
+
+#ifndef GAME_H_
+#define GAME_H_
+
+// Initialisation
+void setupGame(int width, int height);
+void restartGame();
+
+// Update physics
+void nextGameFrame();
+
+// State updaters
+void setTank1CannonAngle(float angle);
+void setTank2CannonAngle(float angle);
+void setTank1CannonStrength(float strength);
+void setTank2CannonStrength(float strength);
+void fireProjectile();
+
+// State queries
+bool gameStatusPlayer1Turn();
+bool gameStatusProjectileInMotion();
+int gameStatusWinner();
+bool gameStatusCollisionOccurred();
+bool gameStatusTankHitOccurred();
+float gameStatusProjectileHeight();
+
+// Render screen; returns length of buffer used
+int drawGame(float *buffer, int bufferSize);
+
+// Cleanup and memory release
+void cleanupGame();
+
+#endif /* GAME_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/main.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,169 @@
+/*
+ * main.cpp
+ *
+ *  Created on: Oct 24, 2014
+ *      Author: parallels
+ */
+
+#include <iostream>
+#include <cstdlib>
+#include <libgen.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sndfile.h>
+#include <Bela.h>
+
+extern int gScreenFramesPerSecond;
+
+float *gMusicBuffer = 0;
+int gMusicBufferLength = 0;
+float *gSoundBoomBuffer = 0;
+int gSoundBoomBufferLength = 0;
+float *gSoundHitBuffer = 0;
+int gSoundHitBufferLength = 0;
+
+
+using namespace std;
+
+// Load a sound sample from file
+int loadSoundFile(const string& path, float **buffer, int *bufferLength)
+{
+	SNDFILE *sndfile ;
+	SF_INFO sfinfo ;
+
+	if (!(sndfile = sf_open (path.c_str(), SFM_READ, &sfinfo))) {
+		cout << "Couldn't open file " << path << endl;
+		return 1;
+	}
+
+	int numChan = sfinfo.channels;
+	if(numChan != 1)
+	{
+		cout << "Error: " << path << " is not a mono file" << endl;
+		return 1;
+	}
+
+	*bufferLength = sfinfo.frames * numChan;
+	*buffer = new float[*bufferLength];
+	if(*buffer == 0){
+		cout << "Could not allocate buffer" << endl;
+		return 1;
+	}
+
+	int subformat = sfinfo.format & SF_FORMAT_SUBMASK;
+	int readcount = sf_read_float(sndfile, *buffer, *bufferLength);
+
+	// Pad with zeros in case we couldn't read whole file
+	for(int k = readcount; k < *bufferLength; k++)
+		(*buffer)[k] = 0;
+
+	sf_close(sndfile);
+	return 0;
+}
+
+// Handle Ctrl-C by requesting that the audio rendering stop
+void interrupt_handler(int var)
+{
+	gShouldStop = true;
+}
+
+// Print usage information
+void usage(const char * processName)
+{
+	cerr << "Usage: " << processName << " [options]" << endl;
+
+	Bela_usage();
+
+	cerr << "   --fps [-f] value:           Set target frames per second\n";
+	cerr << "   --help [-h]:                Print this menu\n";
+}
+
+int main(int argc, char *argv[])
+{
+	BelaInitSettings settings;	// Standard audio settings
+	string musicFileName = "music.wav";
+	string soundBoomFileName = "boom.wav";
+	string soundHitFileName = "hit.wav";
+	
+	struct option customOptions[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"fps", 1, NULL, 'f'},
+		{NULL, 0, NULL, 0}
+	};
+
+	// Set default settings
+	Bela_defaultSettings(&settings);
+
+	// Parse command-line arguments
+	while (1) {
+		int c;
+		if ((c = Bela_getopt_long(argc, argv, "hf:", customOptions, &settings)) < 0)
+				break;
+		switch (c) {
+		case 'f':
+				gScreenFramesPerSecond = atoi(optarg);
+				if(gScreenFramesPerSecond < 1)
+					gScreenFramesPerSecond = 1;
+				if(gScreenFramesPerSecond > 100)
+					gScreenFramesPerSecond = 100;
+				break;
+		case 'h':
+				usage(basename(argv[0]));
+				exit(0);
+		case '?':
+		default:
+				usage(basename(argv[0]));
+				exit(1);
+		}
+	}
+
+	// Load the sound files
+	if(loadSoundFile(musicFileName, &gMusicBuffer, &gMusicBufferLength) != 0) {
+		cout << "Warning: unable to load sound file " << musicFileName << endl;
+	}
+	if(loadSoundFile(soundBoomFileName, &gSoundBoomBuffer, &gSoundBoomBufferLength) != 0) {
+		cout << "Warning: unable to load sound file " << soundBoomFileName << endl;
+	}
+	if(loadSoundFile(soundHitFileName, &gSoundHitBuffer, &gSoundHitBufferLength) != 0) {
+		cout << "Warning: unable to load sound file " << soundHitFileName << endl;
+	}
+	
+	// Initialise the PRU audio device
+	if(Bela_initAudio(&settings, 0) != 0) {
+		cout << "Error: unable to initialise audio" << endl;
+		return -1;
+	}
+
+	// Start the audio device running
+	if(Bela_startAudio()) {
+		cout << "Error: unable to start real-time audio" << endl;
+		return -1;
+	}
+
+	// Set up interrupt handler to catch Control-C and SIGTERM
+	signal(SIGINT, interrupt_handler);
+	signal(SIGTERM, interrupt_handler);
+
+	// Run until told to stop
+	while(!gShouldStop) {
+		usleep(100000);
+	}
+
+	// Stop the audio device
+	Bela_stopAudio();
+
+	// Clean up any resources allocated for audio
+	Bela_cleanupAudio();
+
+	// Release sound files
+	if(gMusicBuffer != 0)
+		free(gMusicBuffer);
+	if(gSoundBoomBuffer != 0)
+		free(gSoundBoomBuffer);
+	if(gSoundHitBuffer != 0)
+		free(gSoundHitBuffer);
+	
+	// All done!
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/render.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,337 @@
+/*
+ * render.cpp
+ *
+ *  Created on: Oct 24, 2014
+ *      Author: parallels
+ */
+
+
+#include <Bela.h>
+#include "game.h"
+#include <rtdk.h>
+#include <cmath>
+#include <cstdlib>
+#include <time.h>
+
+int gAudioFramesPerMatrixFrame = 2; // Ratio in audio to matrix sample rate
+
+int gInputTank1Angle = 0;		// Inputs for the cannon angles
+int gInputTank2Angle = 1;
+int gInputLauncher = 2;			// Input for launcher FSR
+
+int gOutputX = 0;				// Outputs for the scope
+int gOutputY = 1;
+int gOutputPlayer1LED = 2;
+int gOutputPlayer2LED = 3;
+
+int gGameFramesPerSecond = 60;	// How often the physics are updated
+int gGameFrameInterval;			// ...and in frames
+int gSamplesUntilNextFrame;		// Counter until next update
+int gSamplesSinceFinish = 0;	// How long since somebody won?
+bool gGameShouldRestart = false;// Whether we need to reinitiliase the game
+
+// Counter for overall number of samples that have elapsed
+unsigned int gSampleCounter = 0;
+
+// 1st-order filter and peak detector for launcher input
+float gLauncherLastSample = 0;
+float gLauncherFilterPole = 0.8;
+float gLauncherPeakValue = 0;
+float gLauncherPeakFilterPole = 0.999;
+float gLauncherNoiseThreshold = 0.01;
+float gLauncherMinimumPeak = 0.1;
+bool gLauncherTriggered = false;
+
+// Screen update rate; affects buffer size. Actual contents of buffer
+// may be smaller than this
+int gScreenWidth = 512;
+int gScreenHeight = 512;
+int gScreenFramesPerSecond = 25;
+
+// Double buffer for rendering screen. Each is an interleaved buffer
+// of XY data.
+float *gScreenBuffer1, *gScreenBuffer2;
+float *gScreenBufferWrite, *gScreenBufferRead;
+int gScreenBufferMaxLength;				// What is the total buffer allocated?
+int gScreenBufferReadLength;			// How long is the read buffer?
+int gScreenBufferWriteLength;			// How long is the write (next) buffer?
+int gScreenBufferReadPointer;			// Where are we in the read buffer now?
+int gScreenBufferNextUpdateLocation;	// When should we render the next buffer?
+bool gScreenNextBufferReady;			// Is the next buffer ready to go?
+
+// Auxiliary (low-priority) task for updating the screen
+AuxiliaryTask gScreenUpdateTask;
+
+// Buffers for music and sound effects
+extern float *gMusicBuffer;
+extern int gMusicBufferLength;
+extern float *gSoundBoomBuffer;
+extern int gSoundBoomBufferLength;
+extern float *gSoundHitBuffer;
+extern int gSoundHitBufferLength;
+
+// Current state for sound and music
+int gMusicBufferPointer = 0;	  // 0 means start of buffer...
+int gSoundBoomBufferPointer = -1; // -1 means don't play...
+int gSoundHitBufferPointer = -1;
+float gSoundProjectileOscillatorPhase = 0;
+float gSoundProjectileOscillatorGain = 0.2;
+float gOscillatorPhaseScaler = 0;
+
+void screen_update();
+
+// setup() is called once before the audio rendering starts.
+// Use it to perform any initialisation and allocation which is dependent
+// on the period size or sample rate.
+//
+// userData holds an opaque pointer to a data structure that was passed
+// in from the call to initAudio().
+//
+// Return true on success; returning false halts the program.
+
+bool setup(BelaContext *context, void *userData)
+{
+	srandom(time(NULL));
+
+	// Verify we are running with matrix enabled
+	if(context->analogFrames == 0 || context->analogChannels < 4) {
+		rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n");
+		return false;
+	}
+
+	// Initialise audio variables
+	gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames;
+	gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate;
+
+	// Initialise the screen buffers
+	gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond;
+	gScreenBuffer1 = new float[gScreenBufferMaxLength];
+	gScreenBuffer2 = new float[gScreenBufferMaxLength];
+	if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
+		rt_printf("Error initialising screen buffers\n");
+		return false;
+	}
+
+	gScreenBufferRead = gScreenBuffer1;
+	gScreenBufferWrite = gScreenBuffer2;
+	gScreenBufferReadLength = gScreenBufferWriteLength = 0;
+	gScreenBufferReadPointer = 0;
+	gScreenBufferNextUpdateLocation = 0;
+	gScreenNextBufferReady = false;
+
+	// Initialise the game
+	setupGame(gScreenWidth, gScreenHeight);
+	gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond;
+	gSamplesUntilNextFrame = gGameFrameInterval;
+
+	// Initialise auxiliary tasks
+	if((gScreenUpdateTask = Bela_createAuxiliaryTask(&screen_update, 90,
+														 "bela-screen-update")) == 0)
+		return false;
+
+	return true;
+}
+
+// Swap buffers on the screen
+void swap_buffers()
+{
+	if(gScreenBufferRead == gScreenBuffer1) {
+		gScreenBufferRead = gScreenBuffer2;
+		gScreenBufferWrite = gScreenBuffer1;
+	}
+	else {
+		gScreenBufferRead = gScreenBuffer1;
+		gScreenBufferWrite = gScreenBuffer2;
+	}
+
+	gScreenBufferReadLength = gScreenBufferWriteLength;
+	gScreenBufferReadPointer = 0;
+
+	// Schedule next update for 3/4 of the way through the buffer
+	gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
+	gScreenNextBufferReady = false;
+}
+
+// render() is called regularly at the highest priority by the audio engine.
+// Input and output are given from the audio hardware and the other
+// ADCs and DACs (if available). If only audio is available, numMatrixFrames
+// will be 0.
+
+void render(BelaContext *context, void *userData)
+{
+	int audioIndex = 0;
+
+	for(unsigned int n = 0; n < context->analogFrames; n++) {
+		for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) {
+			// Render music and sound
+			float audioSample = 0;
+
+			// Music plays in a loop
+			if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) {
+				audioSample += gMusicBuffer[gMusicBufferPointer++];
+				if(gMusicBufferPointer >= gMusicBufferLength)
+					gMusicBufferPointer = 0;
+			}
+
+			// Sound effect plays until finished, then stops
+			if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) {
+				audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++];
+				if(gSoundBoomBufferPointer >= gSoundBoomBufferLength)
+					gSoundBoomBufferPointer = -1;
+			}
+
+			if(gSoundHitBuffer != 0 && gSoundHitBufferPointer >= 0) {
+				audioSample += gSoundHitBuffer[gSoundHitBufferPointer++];
+				if(gSoundHitBufferPointer >= gSoundHitBufferLength)
+					gSoundHitBufferPointer = -1;
+			}
+
+			// Oscillator plays to indicate projectile height
+			if(gameStatusProjectileInMotion()) {
+				audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase);
+
+				gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(),
+						1.0, 0, 300, 2000), 200, 6000);
+				if(gSoundProjectileOscillatorPhase > 2.0 * M_PI)
+					gSoundProjectileOscillatorPhase -= 2.0 * M_PI;
+			}
+
+			context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample;
+			audioIndex++;
+		}
+
+		// First-order lowpass filter to remove noise on launch FSR
+		float rawSample = analogRead(context, n, gInputLauncher);
+		float launchSample = gLauncherFilterPole * gLauncherLastSample +
+							(1.0f - gLauncherFilterPole) * rawSample;
+		gLauncherLastSample = launchSample;
+
+		// Peak-detect on launch signal
+		if(launchSample >= gLauncherPeakValue) {
+			gLauncherPeakValue = launchSample;
+			gLauncherTriggered = false;
+		}
+		else {
+			if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
+				// Detected a peak; is it big enough overall?
+				if(gLauncherPeakValue >= gLauncherMinimumPeak) {
+					gLauncherTriggered = true;
+					// Peak detected-- fire!!
+					// Set both cannon strengths but only one will
+					// fire depending on whose turn it is
+					float strength = map(gLauncherPeakValue,
+									     gLauncherMinimumPeak, 1.0,
+										 0.5f, 10.0f);
+					setTank1CannonStrength(strength);
+					setTank2CannonStrength(strength);
+					fireProjectile();
+				}
+			}
+
+			gLauncherPeakValue *= gLauncherPeakFilterPole;
+		}
+
+		if(--gSamplesUntilNextFrame <= 0) {
+			// Update game physics and cannon angles
+			gSamplesUntilNextFrame = gGameFrameInterval;
+
+			setTank1CannonAngle(map(analogRead(context, n, gInputTank1Angle),
+									0, 1.0, M_PI, 0));
+			setTank2CannonAngle(map(analogRead(context, n, gInputTank2Angle),
+									0, 1.0, M_PI, 0));
+			nextGameFrame();
+
+			// Check for collision and start sound accordingly
+			if(gameStatusCollisionOccurred()) {
+				gSoundBoomBufferPointer = 0;
+			}
+			
+			if(gameStatusTankHitOccurred()) {
+				gSoundHitBufferPointer = 0;
+			}
+		}
+
+		if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
+			&& gScreenNextBufferReady) {
+			// Got to the end; swap buffers
+			swap_buffers();
+		}
+
+		// Push current screen buffer to the matrix output
+		if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
+			float x = gScreenBufferRead[gScreenBufferReadPointer++];
+			float y = gScreenBufferRead[gScreenBufferReadPointer++];
+
+			// Rescale screen coordinates to matrix ranges; invert the Y
+			// coordinate to go from normal screen coordinates to scope coordinates
+			analogWriteOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
+			analogWriteOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
+		}
+		else {
+			// Still not ready! Write 0 until something happens
+			analogWriteOnce(context, n, gOutputX, 0);
+			analogWriteOnce(context, n, gOutputY, 0);
+		}
+
+		if(gameStatusWinner() != 0) {
+			// Blink one LED to show who won
+			// Blink both LEDs when projectile is in motion
+			float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0;
+			analogWriteOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0);
+			analogWriteOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0);
+
+			// After 5 seconds, restart the game
+			gSamplesSinceFinish++;
+			if(gSamplesSinceFinish > 22050*5)
+				gGameShouldRestart = true;
+		}
+		else if(gameStatusProjectileInMotion()) {
+			// Blink both LEDs when projectile is in motion
+			float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0;
+			analogWriteOnce(context, n, gOutputPlayer1LED, val);
+			analogWriteOnce(context, n, gOutputPlayer2LED, val);
+		}
+		else if(gameStatusPlayer1Turn()) {
+			analogWriteOnce(context, n, gOutputPlayer1LED, 1.0);
+			analogWriteOnce(context, n, gOutputPlayer2LED, 0);
+		}
+		else {
+			analogWriteOnce(context, n, gOutputPlayer2LED, 1.0);
+			analogWriteOnce(context, n, gOutputPlayer1LED, 0);
+		}
+
+		// Check if we have reached the point where we should next update
+		if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
+		   !gScreenNextBufferReady) {
+			// Update the screen at lower priority than the audio thread
+			Bela_scheduleAuxiliaryTask(gScreenUpdateTask);
+		}
+
+		gSampleCounter++;
+	}
+}
+
+void screen_update()
+{
+	// If we should restart, reinitialise the game
+	if(gGameShouldRestart) {
+		restartGame();
+		gGameShouldRestart = false;
+		gSamplesSinceFinish = 0;
+	}
+
+	// Render the game based on the current state
+	gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
+
+	// Flag it as ready to go
+	gScreenNextBufferReady = true;
+}
+
+// cleanup() is called once at the end, after the audio has stopped.
+// Release any resources that were allocated in setup().
+
+void cleanup(BelaContext *context, void *userData)
+{
+	// Clean up the game state
+	cleanupGame();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/vector_graphics.cpp	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,72 @@
+/*
+ * vector_graphics.cpp
+ *
+ *  Created on: Nov 10, 2014
+ *      Author: parallels
+ */
+
+#include <cmath>
+
+// Draw a line between two points at a specified rate in
+// pixels per buffer sample. Indicate maximum available space.
+// Returns space used
+int renderLine(float x1, float y1, float x2, float y2, float speed,
+			   float *buffer, int maxLength) {
+	// Figure out length of line and therefore how many samples
+	// are needed to represent it based on the speed (rounded to nearest int)
+	float totalLineLength = sqrtf((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
+	int samplesNeeded = floorf(totalLineLength / speed + 0.5);
+
+	// Now render into the buffer
+	int length = 0;
+	float scaleFactor = 1.0f / samplesNeeded;
+	for(int n = 0; n < samplesNeeded; n++) {
+		if(length >= maxLength - 1)
+			return length;
+		// X coordinate
+		*buffer++ = x1 + (float)n * scaleFactor * (x2 - x1);
+		// Y coordinate
+		*buffer++ = y1 + (float)n * scaleFactor * (y2 - y1);
+		length += 2;
+	}
+
+	return length;
+}
+
+// Draw an arc around a centre point at a specified rate of pixels
+// per buffer sample. Indicate maximum available space.
+// Returns space used
+int renderArc(float x, float y, float radius, float thetaMin, float thetaMax,
+			  float speed, float *buffer, int maxLength) {
+	// Figure out circumference of arc and therefore how many samples
+	// are needed to represent it based on the speed (rounded to nearest int)
+	float circumference = (thetaMax - thetaMin) * radius;
+	int samplesNeeded = floorf(circumference / speed + 0.5);
+
+	// Now render into the buffer
+	int length = 0;
+	float scaleFactor = 1.0f / samplesNeeded;
+	for(int n = 0; n < samplesNeeded; n++) {
+		if(length >= maxLength - 1)
+			return length;
+		// Get current angle
+		float theta = thetaMin + (float)n * scaleFactor * (thetaMax - thetaMin);
+
+		// Convert polar to cartesian coordinates
+		*buffer++ = x + radius * cosf(theta);
+		*buffer++ = y + radius * sinf(theta);
+
+		length += 2;
+	}
+
+	return length;
+}
+
+// Draw a single point for a specified number of frames
+void renderPoint(float x, float y, float *buffer, float length) {
+	while(length > 0) {
+		*buffer++ = x;
+		*buffer++ = y;
+		length--;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/vector_graphics.h	Tue Jun 21 20:01:26 2016 +0100
@@ -0,0 +1,18 @@
+/*
+ * vector_graphics.h
+ *
+ *  Created on: Nov 10, 2014
+ *      Author: parallels
+ */
+
+#ifndef VECTOR_GRAPHICS_H_
+#define VECTOR_GRAPHICS_H_
+
+int renderLine(float x1, float y1, float x2, float y2, float speed,
+			   float *buffer, int maxLength);
+int renderArc(float x, float y, float radius, float thetaMin, float thetaMax,
+			  float speed, float *buffer, int maxLength);
+void renderPoint(float x, float y, float *buffer, float length);
+
+
+#endif /* VECTOR_GRAPHICS_H_ */
--- a/examples/10-Instruments/tank_wars/game.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,339 +0,0 @@
-/*
- * game.cpp
- *
- *  Created on: Nov 10, 2014
- *      Author: parallels
- */
-
-#include <cmath>
-#include <cstdlib>
-#include "vector_graphics.h"
-#include <Utilities.h>
-
-// Virtual screen size
-int screenWidth, screenHeight;
-
-// Basic information on the terrain and the tanks
-float *groundLevel;  // Y coordinate of the ground for each X coordinate
-float tank1X, tank1Y, tank2X, tank2Y; // Positions of the two tanks
-float tankRadius = 20;  // Radius of the tanks
-float cannonLength = 40;   // How long the cannon on each tank extends
-float gravity = 0.05;      // Strength of gravity
-
-// Current state of the game
-int playerHasWon = 0; // 1 if player 1 wins, 2 if player 2 wins, 0 if game in progress
-bool player1Turn = true;  // true if it's player 1's turn; false otherwise
-float tank1CannonAngle = M_PI/2;
-float tank2CannonAngle = M_PI/2; // Direction the tank cannons are pointing
-float tank1CannonStrength = 3;
-float tank2CannonStrength = 3; // Strength of intended projectile launch
-
-// Location of the projectile
-bool projectileInMotion = false;
-float projectilePositionX, projectilePositionY;
-float projectileVelocityX, projectileVelocityY;
-
-// Infor needed for sound rendering
-bool collisionJustOccurred = false;
-bool tankHitJustOccurred = false;
-
-// Useful utility function for generating random floating-point values
-float randomFloat(float low, float hi)
-{
-	float r = (float)random() / (float)RAND_MAX;
-	return map(r, 0, 1, low, hi);
-}
-
-// Restart the game, without reallocating memory
-void restartGame()
-{
-	float player1Height = screenHeight * 3/4; // randomFloat(screenHeight/2, screenHeight-5);
-	float player2Height = screenHeight - 5; // randomFloat(screenHeight/2, screenHeight-5);
-	for(int i = 0; i < screenWidth * 0.2; i++) {
-		groundLevel[i] = player1Height;
-	}
-	for(int i = screenWidth * 0.2; i < screenWidth * 0.8; i++) {
-		groundLevel[i] = player1Height + (player2Height - player1Height) * (i - screenWidth*0.2)/(screenWidth*0.6);
-	}
-	for(int i = screenWidth * 0.8; i < screenWidth; i++) {
-		groundLevel[i] = player2Height;
-	}
-
-	// Set the location of the two tanks so they rest on the ground at opposite sides
-	tank1X = screenWidth * 0.1;
-	tank1Y = player1Height;
-	tank2X = screenWidth * 0.9;
-	tank2Y = player2Height;
-
-	playerHasWon = 0;
-	projectileInMotion = false;
-}
-
-// Initialise the game
-void setupGame(int width, int height)
-{
-	// Set the screen size
-	screenWidth = width;
-	screenHeight = height;
-
-	// Initialize the ground level
-	groundLevel = new float[screenWidth];
-
-	restartGame();
-}
-
-// Advance the turn to the next player
-void nextPlayersTurn() {
-	player1Turn = !player1Turn;
-}
-
-
-// Move forward one frame on the game physics
-void nextGameFrame()
-{
-	if(!projectileInMotion)
-		return;
-
-	// Update position of projectile
-	projectilePositionX += projectileVelocityX;
-	projectilePositionY += projectileVelocityY;
-	projectileVelocityY += gravity;
-
-	// Check collision with tanks first: a collision with tank 1 means player 2 wins and vice-versa
-	if((tank1X - projectilePositionX)*(tank1X - projectilePositionX) +
-		(tank1Y - projectilePositionY)*(tank1Y - projectilePositionY)
-		<= tankRadius * tankRadius)
-	{
-		projectileInMotion = false;
-		collisionJustOccurred = false;
-		tankHitJustOccurred = true;
-		playerHasWon = 2;
-	}
-	else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) +
-		(tank2Y - projectilePositionY)*(tank2Y - projectilePositionY)
-		<= tankRadius * tankRadius)
-	{
-		projectileInMotion = false;
-		collisionJustOccurred = false;
-		tankHitJustOccurred = true;
-		playerHasWon = 1;
-	}
-	else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) {
-		// Check collision whether projectile has exited the screen to the left or right
-		projectileInMotion = false;
-		collisionJustOccurred = true;
-		nextPlayersTurn();
-	}
-	else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) {
-		// Check for projectile collision with ground
-		projectileInMotion = false;
-		collisionJustOccurred = true;
-		nextPlayersTurn();
-	}
-}
-
-// Updates for game state
-void setTank1CannonAngle(float angle)
-{
-	tank1CannonAngle = angle;
-}
-
-void setTank2CannonAngle(float angle)
-{
-	tank2CannonAngle = angle;
-}
-
-void setTank1CannonStrength(float strength)
-{
-	tank1CannonStrength = strength;
-}
-
-void setTank2CannonStrength(float strength)
-{
-	tank2CannonStrength = strength;
-}
-
-// FIRE!
-void fireProjectile()
-{
-	// Can't fire while projectile is already moving, or if someone has won
-	if(projectileInMotion)
-		return;
-	if(playerHasWon != 0)
-		return;
-
-    if(player1Turn) {
-		projectilePositionX = tank1X + cannonLength * cosf(tank1CannonAngle);
-		projectilePositionY = tank1Y - cannonLength * sinf(tank1CannonAngle);
-		projectileVelocityX = tank1CannonStrength * cosf(tank1CannonAngle);
-		projectileVelocityY = -tank1CannonStrength * sinf(tank1CannonAngle);
-    }
-    else {
-		projectilePositionX = tank2X + cannonLength * cosf(tank2CannonAngle);
-		projectilePositionY = tank2Y - cannonLength * sinf(tank2CannonAngle);
-		projectileVelocityX = tank2CannonStrength * cosf(tank2CannonAngle);
-		projectileVelocityY = -tank2CannonStrength * sinf(tank2CannonAngle);
-    }
-
-    // GO!
-    projectileInMotion = true;
-}
-
-// Game state queries
-bool gameStatusPlayer1Turn()
-{
-	return player1Turn;
-}
-
-bool gameStatusProjectileInMotion()
-{
-	return projectileInMotion;
-}
-
-int gameStatusWinner()
-{
-	return playerHasWon;
-}
-
-bool gameStatusCollisionOccurred()
-{
-	if(collisionJustOccurred) {
-		collisionJustOccurred = false;
-		return true;
-	}
-	return false;
-}
-
-bool gameStatusTankHitOccurred()
-{
-	if(tankHitJustOccurred) {
-		tankHitJustOccurred = false;
-		return true;
-	}
-	return false;
-}
-
-
-float gameStatusProjectileHeight()
-{
-	return projectilePositionY / (float)screenHeight;
-}
-
-// Clean up any allocated memory for the game
-void cleanupGame()
-{
-	delete groundLevel;
-}
-
-// Drawing routines. Arguments are (interleaved) buffer to render
-// into, the available size, and the target for how many samples
-// to use (actual usage might vary slightly). Regardless of
-// lengthTarget, never use more than bufferSize samples.
-
-int drawGround(float *buffer, int bufferSize, int framesTarget)
-{
-	int length;
-
-	// Calculate total length of ground line, to arrive at a speed calculation
-	float totalLineLength = 0.4f*screenWidth
-							+ sqrtf(0.36f*screenWidth*screenWidth
-									+ (tank2Y-tank1Y)*(tank2Y-tank1Y));
-
-	// Speed is calculated in pixels per frame
-	float speed = totalLineLength / (float)framesTarget;
-
-	// Draw three lines: platforms for tanks and the connecting line.
-	// Eventually, render a more complex ground from the array.
-	length = renderLine(0, tank1Y, screenWidth * 0.2, tank1Y,
-						speed, buffer, bufferSize);
-	length += renderLine(screenWidth * 0.2, tank1Y, screenWidth * 0.8, tank2Y,
-						speed, &buffer[length], bufferSize - length);
-	length += renderLine(screenWidth * 0.8, tank2Y, screenWidth, tank2Y,
-						speed, &buffer[length], bufferSize - length);
-
-	return length;
-}
-
-int drawTanks(float *buffer, int bufferSize, int framesTarget)
-{
-	int length = 0;
-
-	// Calculate total length of tank lines, to arrive at a speed calculation
-	float totalLineLength = 2.0*M_PI*tankRadius + 2.0*(cannonLength - tankRadius);
-
-	// Speed is calculated in pixels per frame
-	float speed = totalLineLength / (float)framesTarget;
-
-	if(playerHasWon != 2) {
-		// Tank 1 body = semicircle + line
-		length += renderArc(tank1X, tank1Y, tankRadius, M_PI, 2.0 * M_PI,
-							speed, buffer, bufferSize);
-		length += renderLine(tank1X + tankRadius, tank1Y,
-							 tank1X - tankRadius, tank1Y,
-							speed, &buffer[length], bufferSize - length);
-		// Tank 1 cannon (line depending on angle)
-		length += renderLine(tank1X + tankRadius * cosf(tank1CannonAngle),
-			 tank1Y - tankRadius * sinf(tank1CannonAngle),
-			 tank1X + cannonLength * cosf(tank1CannonAngle),
-			 tank1Y - cannonLength * sinf(tank1CannonAngle),
-			 speed, &buffer[length], bufferSize - length);
-	}
-
-	if(playerHasWon != 1) {
-		// Same idea for tank 2
-		length += renderArc(tank2X, tank2Y, tankRadius, M_PI, 2.0 * M_PI,
-							speed, &buffer[length], bufferSize - length);
-		length += renderLine(tank2X + tankRadius, tank2Y,
-							 tank2X - tankRadius, tank2Y,
-							 speed, &buffer[length], bufferSize - length);
-		length += renderLine(tank2X + tankRadius * cosf(tank2CannonAngle),
-			 tank2Y - tankRadius * sinf(tank2CannonAngle),
-			 tank2X + cannonLength * cosf(tank2CannonAngle),
-			 tank2Y - cannonLength * sinf(tank2CannonAngle),
-			 speed, &buffer[length], bufferSize - length);
-	}
-
-	return length;
-}
-
-int drawProjectile(float *buffer, int bufferSize, int framesTarget)
-{
-	if(!projectileInMotion)
-		return 0;
-
-	// Draw a point for a specified number of frames (each containing X and Y)
-	// Return the number of items used in the buffer, which will be twice
-	// the number of frames unless the buffer is full
-
-	if(bufferSize/2 < framesTarget) {
-		renderPoint(projectilePositionX, projectilePositionY, buffer, bufferSize/2);
-		return bufferSize;
-	}
-	else {
-		renderPoint(projectilePositionX, projectilePositionY, buffer, framesTarget);
-		return framesTarget*2;
-	}
-}
-
-// Main drawing routine entry point
-int drawGame(float *buffer, int bufferSize)
-{
-	int length;
-
-	// Based on buffer size, come up with speeds for each of the elements
-	// 50% of time to ground; 30% to the tanks and 20% to the projectile
-	// Give a margin of 25% beyond so we don't run out of buffer space
-	// if things take longer to draw than we guess they will
-	const float amountToUse = 0.375; // 0.75/2 because two samples per frame
-	const float groundFraction = 0.5 * amountToUse;
-	const float tankFraction = 0.3 * amountToUse;
-	const float projectileFraction = 0.2 * amountToUse;
-
-	length = drawGround(buffer, bufferSize, bufferSize * groundFraction);
-	length += drawTanks(&buffer[length], bufferSize - length,
-						bufferSize * tankFraction);
-	length += drawProjectile(&buffer[length], bufferSize - length,
-						bufferSize * projectileFraction);
-
-	return length;
-}
--- a/examples/10-Instruments/tank_wars/game.h	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-/*
- * game.h
- *
- *  Created on: Nov 10, 2014
- *      Author: parallels
- */
-
-#ifndef GAME_H_
-#define GAME_H_
-
-// Initialisation
-void setupGame(int width, int height);
-void restartGame();
-
-// Update physics
-void nextGameFrame();
-
-// State updaters
-void setTank1CannonAngle(float angle);
-void setTank2CannonAngle(float angle);
-void setTank1CannonStrength(float strength);
-void setTank2CannonStrength(float strength);
-void fireProjectile();
-
-// State queries
-bool gameStatusPlayer1Turn();
-bool gameStatusProjectileInMotion();
-int gameStatusWinner();
-bool gameStatusCollisionOccurred();
-bool gameStatusTankHitOccurred();
-float gameStatusProjectileHeight();
-
-// Render screen; returns length of buffer used
-int drawGame(float *buffer, int bufferSize);
-
-// Cleanup and memory release
-void cleanupGame();
-
-#endif /* GAME_H_ */
--- a/examples/10-Instruments/tank_wars/main.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/*
- * main.cpp
- *
- *  Created on: Oct 24, 2014
- *      Author: parallels
- */
-
-#include <iostream>
-#include <cstdlib>
-#include <libgen.h>
-#include <signal.h>
-#include <getopt.h>
-#include <sndfile.h>
-#include <Bela.h>
-
-extern int gScreenFramesPerSecond;
-
-float *gMusicBuffer = 0;
-int gMusicBufferLength = 0;
-float *gSoundBoomBuffer = 0;
-int gSoundBoomBufferLength = 0;
-float *gSoundHitBuffer = 0;
-int gSoundHitBufferLength = 0;
-
-
-using namespace std;
-
-// Load a sound sample from file
-int loadSoundFile(const string& path, float **buffer, int *bufferLength)
-{
-	SNDFILE *sndfile ;
-	SF_INFO sfinfo ;
-
-	if (!(sndfile = sf_open (path.c_str(), SFM_READ, &sfinfo))) {
-		cout << "Couldn't open file " << path << endl;
-		return 1;
-	}
-
-	int numChan = sfinfo.channels;
-	if(numChan != 1)
-	{
-		cout << "Error: " << path << " is not a mono file" << endl;
-		return 1;
-	}
-
-	*bufferLength = sfinfo.frames * numChan;
-	*buffer = new float[*bufferLength];
-	if(*buffer == 0){
-		cout << "Could not allocate buffer" << endl;
-		return 1;
-	}
-
-	int subformat = sfinfo.format & SF_FORMAT_SUBMASK;
-	int readcount = sf_read_float(sndfile, *buffer, *bufferLength);
-
-	// Pad with zeros in case we couldn't read whole file
-	for(int k = readcount; k < *bufferLength; k++)
-		(*buffer)[k] = 0;
-
-	sf_close(sndfile);
-	return 0;
-}
-
-// Handle Ctrl-C by requesting that the audio rendering stop
-void interrupt_handler(int var)
-{
-	gShouldStop = true;
-}
-
-// Print usage information
-void usage(const char * processName)
-{
-	cerr << "Usage: " << processName << " [options]" << endl;
-
-	Bela_usage();
-
-	cerr << "   --fps [-f] value:           Set target frames per second\n";
-	cerr << "   --help [-h]:                Print this menu\n";
-}
-
-int main(int argc, char *argv[])
-{
-	BelaInitSettings settings;	// Standard audio settings
-	string musicFileName = "music.wav";
-	string soundBoomFileName = "boom.wav";
-	string soundHitFileName = "hit.wav";
-	
-	struct option customOptions[] =
-	{
-		{"help", 0, NULL, 'h'},
-		{"fps", 1, NULL, 'f'},
-		{NULL, 0, NULL, 0}
-	};
-
-	// Set default settings
-	Bela_defaultSettings(&settings);
-
-	// Parse command-line arguments
-	while (1) {
-		int c;
-		if ((c = Bela_getopt_long(argc, argv, "hf:", customOptions, &settings)) < 0)
-				break;
-		switch (c) {
-		case 'f':
-				gScreenFramesPerSecond = atoi(optarg);
-				if(gScreenFramesPerSecond < 1)
-					gScreenFramesPerSecond = 1;
-				if(gScreenFramesPerSecond > 100)
-					gScreenFramesPerSecond = 100;
-				break;
-		case 'h':
-				usage(basename(argv[0]));
-				exit(0);
-		case '?':
-		default:
-				usage(basename(argv[0]));
-				exit(1);
-		}
-	}
-
-	// Load the sound files
-	if(loadSoundFile(musicFileName, &gMusicBuffer, &gMusicBufferLength) != 0) {
-		cout << "Warning: unable to load sound file " << musicFileName << endl;
-	}
-	if(loadSoundFile(soundBoomFileName, &gSoundBoomBuffer, &gSoundBoomBufferLength) != 0) {
-		cout << "Warning: unable to load sound file " << soundBoomFileName << endl;
-	}
-	if(loadSoundFile(soundHitFileName, &gSoundHitBuffer, &gSoundHitBufferLength) != 0) {
-		cout << "Warning: unable to load sound file " << soundHitFileName << endl;
-	}
-	
-	// Initialise the PRU audio device
-	if(Bela_initAudio(&settings, 0) != 0) {
-		cout << "Error: unable to initialise audio" << endl;
-		return -1;
-	}
-
-	// Start the audio device running
-	if(Bela_startAudio()) {
-		cout << "Error: unable to start real-time audio" << endl;
-		return -1;
-	}
-
-	// Set up interrupt handler to catch Control-C and SIGTERM
-	signal(SIGINT, interrupt_handler);
-	signal(SIGTERM, interrupt_handler);
-
-	// Run until told to stop
-	while(!gShouldStop) {
-		usleep(100000);
-	}
-
-	// Stop the audio device
-	Bela_stopAudio();
-
-	// Clean up any resources allocated for audio
-	Bela_cleanupAudio();
-
-	// Release sound files
-	if(gMusicBuffer != 0)
-		free(gMusicBuffer);
-	if(gSoundBoomBuffer != 0)
-		free(gSoundBoomBuffer);
-	if(gSoundHitBuffer != 0)
-		free(gSoundHitBuffer);
-	
-	// All done!
-	return 0;
-}
--- a/examples/10-Instruments/tank_wars/render.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,337 +0,0 @@
-/*
- * render.cpp
- *
- *  Created on: Oct 24, 2014
- *      Author: parallels
- */
-
-
-#include <Bela.h>
-#include "game.h"
-#include <rtdk.h>
-#include <cmath>
-#include <cstdlib>
-#include <time.h>
-
-int gAudioFramesPerMatrixFrame = 2; // Ratio in audio to matrix sample rate
-
-int gInputTank1Angle = 0;		// Inputs for the cannon angles
-int gInputTank2Angle = 1;
-int gInputLauncher = 2;			// Input for launcher FSR
-
-int gOutputX = 0;				// Outputs for the scope
-int gOutputY = 1;
-int gOutputPlayer1LED = 2;
-int gOutputPlayer2LED = 3;
-
-int gGameFramesPerSecond = 60;	// How often the physics are updated
-int gGameFrameInterval;			// ...and in frames
-int gSamplesUntilNextFrame;		// Counter until next update
-int gSamplesSinceFinish = 0;	// How long since somebody won?
-bool gGameShouldRestart = false;// Whether we need to reinitiliase the game
-
-// Counter for overall number of samples that have elapsed
-unsigned int gSampleCounter = 0;
-
-// 1st-order filter and peak detector for launcher input
-float gLauncherLastSample = 0;
-float gLauncherFilterPole = 0.8;
-float gLauncherPeakValue = 0;
-float gLauncherPeakFilterPole = 0.999;
-float gLauncherNoiseThreshold = 0.01;
-float gLauncherMinimumPeak = 0.1;
-bool gLauncherTriggered = false;
-
-// Screen update rate; affects buffer size. Actual contents of buffer
-// may be smaller than this
-int gScreenWidth = 512;
-int gScreenHeight = 512;
-int gScreenFramesPerSecond = 25;
-
-// Double buffer for rendering screen. Each is an interleaved buffer
-// of XY data.
-float *gScreenBuffer1, *gScreenBuffer2;
-float *gScreenBufferWrite, *gScreenBufferRead;
-int gScreenBufferMaxLength;				// What is the total buffer allocated?
-int gScreenBufferReadLength;			// How long is the read buffer?
-int gScreenBufferWriteLength;			// How long is the write (next) buffer?
-int gScreenBufferReadPointer;			// Where are we in the read buffer now?
-int gScreenBufferNextUpdateLocation;	// When should we render the next buffer?
-bool gScreenNextBufferReady;			// Is the next buffer ready to go?
-
-// Auxiliary (low-priority) task for updating the screen
-AuxiliaryTask gScreenUpdateTask;
-
-// Buffers for music and sound effects
-extern float *gMusicBuffer;
-extern int gMusicBufferLength;
-extern float *gSoundBoomBuffer;
-extern int gSoundBoomBufferLength;
-extern float *gSoundHitBuffer;
-extern int gSoundHitBufferLength;
-
-// Current state for sound and music
-int gMusicBufferPointer = 0;	  // 0 means start of buffer...
-int gSoundBoomBufferPointer = -1; // -1 means don't play...
-int gSoundHitBufferPointer = -1;
-float gSoundProjectileOscillatorPhase = 0;
-float gSoundProjectileOscillatorGain = 0.2;
-float gOscillatorPhaseScaler = 0;
-
-void screen_update();
-
-// setup() is called once before the audio rendering starts.
-// Use it to perform any initialisation and allocation which is dependent
-// on the period size or sample rate.
-//
-// userData holds an opaque pointer to a data structure that was passed
-// in from the call to initAudio().
-//
-// Return true on success; returning false halts the program.
-
-bool setup(BelaContext *context, void *userData)
-{
-	srandom(time(NULL));
-
-	// Verify we are running with matrix enabled
-	if(context->analogFrames == 0 || context->analogChannels < 4) {
-		rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n");
-		return false;
-	}
-
-	// Initialise audio variables
-	gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames;
-	gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate;
-
-	// Initialise the screen buffers
-	gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond;
-	gScreenBuffer1 = new float[gScreenBufferMaxLength];
-	gScreenBuffer2 = new float[gScreenBufferMaxLength];
-	if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
-		rt_printf("Error initialising screen buffers\n");
-		return false;
-	}
-
-	gScreenBufferRead = gScreenBuffer1;
-	gScreenBufferWrite = gScreenBuffer2;
-	gScreenBufferReadLength = gScreenBufferWriteLength = 0;
-	gScreenBufferReadPointer = 0;
-	gScreenBufferNextUpdateLocation = 0;
-	gScreenNextBufferReady = false;
-
-	// Initialise the game
-	setupGame(gScreenWidth, gScreenHeight);
-	gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond;
-	gSamplesUntilNextFrame = gGameFrameInterval;
-
-	// Initialise auxiliary tasks
-	if((gScreenUpdateTask = Bela_createAuxiliaryTask(&screen_update, 90,
-														 "bela-screen-update")) == 0)
-		return false;
-
-	return true;
-}
-
-// Swap buffers on the screen
-void swap_buffers()
-{
-	if(gScreenBufferRead == gScreenBuffer1) {
-		gScreenBufferRead = gScreenBuffer2;
-		gScreenBufferWrite = gScreenBuffer1;
-	}
-	else {
-		gScreenBufferRead = gScreenBuffer1;
-		gScreenBufferWrite = gScreenBuffer2;
-	}
-
-	gScreenBufferReadLength = gScreenBufferWriteLength;
-	gScreenBufferReadPointer = 0;
-
-	// Schedule next update for 3/4 of the way through the buffer
-	gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
-	gScreenNextBufferReady = false;
-}
-
-// render() is called regularly at the highest priority by the audio engine.
-// Input and output are given from the audio hardware and the other
-// ADCs and DACs (if available). If only audio is available, numMatrixFrames
-// will be 0.
-
-void render(BelaContext *context, void *userData)
-{
-	int audioIndex = 0;
-
-	for(unsigned int n = 0; n < context->analogFrames; n++) {
-		for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) {
-			// Render music and sound
-			float audioSample = 0;
-
-			// Music plays in a loop
-			if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) {
-				audioSample += gMusicBuffer[gMusicBufferPointer++];
-				if(gMusicBufferPointer >= gMusicBufferLength)
-					gMusicBufferPointer = 0;
-			}
-
-			// Sound effect plays until finished, then stops
-			if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) {
-				audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++];
-				if(gSoundBoomBufferPointer >= gSoundBoomBufferLength)
-					gSoundBoomBufferPointer = -1;
-			}
-
-			if(gSoundHitBuffer != 0 && gSoundHitBufferPointer >= 0) {
-				audioSample += gSoundHitBuffer[gSoundHitBufferPointer++];
-				if(gSoundHitBufferPointer >= gSoundHitBufferLength)
-					gSoundHitBufferPointer = -1;
-			}
-
-			// Oscillator plays to indicate projectile height
-			if(gameStatusProjectileInMotion()) {
-				audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase);
-
-				gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(),
-						1.0, 0, 300, 2000), 200, 6000);
-				if(gSoundProjectileOscillatorPhase > 2.0 * M_PI)
-					gSoundProjectileOscillatorPhase -= 2.0 * M_PI;
-			}
-
-			context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample;
-			audioIndex++;
-		}
-
-		// First-order lowpass filter to remove noise on launch FSR
-		float rawSample = analogRead(context, n, gInputLauncher);
-		float launchSample = gLauncherFilterPole * gLauncherLastSample +
-							(1.0f - gLauncherFilterPole) * rawSample;
-		gLauncherLastSample = launchSample;
-
-		// Peak-detect on launch signal
-		if(launchSample >= gLauncherPeakValue) {
-			gLauncherPeakValue = launchSample;
-			gLauncherTriggered = false;
-		}
-		else {
-			if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
-				// Detected a peak; is it big enough overall?
-				if(gLauncherPeakValue >= gLauncherMinimumPeak) {
-					gLauncherTriggered = true;
-					// Peak detected-- fire!!
-					// Set both cannon strengths but only one will
-					// fire depending on whose turn it is
-					float strength = map(gLauncherPeakValue,
-									     gLauncherMinimumPeak, 1.0,
-										 0.5f, 10.0f);
-					setTank1CannonStrength(strength);
-					setTank2CannonStrength(strength);
-					fireProjectile();
-				}
-			}
-
-			gLauncherPeakValue *= gLauncherPeakFilterPole;
-		}
-
-		if(--gSamplesUntilNextFrame <= 0) {
-			// Update game physics and cannon angles
-			gSamplesUntilNextFrame = gGameFrameInterval;
-
-			setTank1CannonAngle(map(analogRead(context, n, gInputTank1Angle),
-									0, 1.0, M_PI, 0));
-			setTank2CannonAngle(map(analogRead(context, n, gInputTank2Angle),
-									0, 1.0, M_PI, 0));
-			nextGameFrame();
-
-			// Check for collision and start sound accordingly
-			if(gameStatusCollisionOccurred()) {
-				gSoundBoomBufferPointer = 0;
-			}
-			
-			if(gameStatusTankHitOccurred()) {
-				gSoundHitBufferPointer = 0;
-			}
-		}
-
-		if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
-			&& gScreenNextBufferReady) {
-			// Got to the end; swap buffers
-			swap_buffers();
-		}
-
-		// Push current screen buffer to the matrix output
-		if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
-			float x = gScreenBufferRead[gScreenBufferReadPointer++];
-			float y = gScreenBufferRead[gScreenBufferReadPointer++];
-
-			// Rescale screen coordinates to matrix ranges; invert the Y
-			// coordinate to go from normal screen coordinates to scope coordinates
-			analogWriteOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
-			analogWriteOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
-		}
-		else {
-			// Still not ready! Write 0 until something happens
-			analogWriteOnce(context, n, gOutputX, 0);
-			analogWriteOnce(context, n, gOutputY, 0);
-		}
-
-		if(gameStatusWinner() != 0) {
-			// Blink one LED to show who won
-			// Blink both LEDs when projectile is in motion
-			float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0;
-			analogWriteOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0);
-			analogWriteOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0);
-
-			// After 5 seconds, restart the game
-			gSamplesSinceFinish++;
-			if(gSamplesSinceFinish > 22050*5)
-				gGameShouldRestart = true;
-		}
-		else if(gameStatusProjectileInMotion()) {
-			// Blink both LEDs when projectile is in motion
-			float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0;
-			analogWriteOnce(context, n, gOutputPlayer1LED, val);
-			analogWriteOnce(context, n, gOutputPlayer2LED, val);
-		}
-		else if(gameStatusPlayer1Turn()) {
-			analogWriteOnce(context, n, gOutputPlayer1LED, 1.0);
-			analogWriteOnce(context, n, gOutputPlayer2LED, 0);
-		}
-		else {
-			analogWriteOnce(context, n, gOutputPlayer2LED, 1.0);
-			analogWriteOnce(context, n, gOutputPlayer1LED, 0);
-		}
-
-		// Check if we have reached the point where we should next update
-		if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
-		   !gScreenNextBufferReady) {
-			// Update the screen at lower priority than the audio thread
-			Bela_scheduleAuxiliaryTask(gScreenUpdateTask);
-		}
-
-		gSampleCounter++;
-	}
-}
-
-void screen_update()
-{
-	// If we should restart, reinitialise the game
-	if(gGameShouldRestart) {
-		restartGame();
-		gGameShouldRestart = false;
-		gSamplesSinceFinish = 0;
-	}
-
-	// Render the game based on the current state
-	gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
-
-	// Flag it as ready to go
-	gScreenNextBufferReady = true;
-}
-
-// cleanup() is called once at the end, after the audio has stopped.
-// Release any resources that were allocated in setup().
-
-void cleanup(BelaContext *context, void *userData)
-{
-	// Clean up the game state
-	cleanupGame();
-}
--- a/examples/10-Instruments/tank_wars/vector_graphics.cpp	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- * vector_graphics.cpp
- *
- *  Created on: Nov 10, 2014
- *      Author: parallels
- */
-
-#include <cmath>
-
-// Draw a line between two points at a specified rate in
-// pixels per buffer sample. Indicate maximum available space.
-// Returns space used
-int renderLine(float x1, float y1, float x2, float y2, float speed,
-			   float *buffer, int maxLength) {
-	// Figure out length of line and therefore how many samples
-	// are needed to represent it based on the speed (rounded to nearest int)
-	float totalLineLength = sqrtf((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
-	int samplesNeeded = floorf(totalLineLength / speed + 0.5);
-
-	// Now render into the buffer
-	int length = 0;
-	float scaleFactor = 1.0f / samplesNeeded;
-	for(int n = 0; n < samplesNeeded; n++) {
-		if(length >= maxLength - 1)
-			return length;
-		// X coordinate
-		*buffer++ = x1 + (float)n * scaleFactor * (x2 - x1);
-		// Y coordinate
-		*buffer++ = y1 + (float)n * scaleFactor * (y2 - y1);
-		length += 2;
-	}
-
-	return length;
-}
-
-// Draw an arc around a centre point at a specified rate of pixels
-// per buffer sample. Indicate maximum available space.
-// Returns space used
-int renderArc(float x, float y, float radius, float thetaMin, float thetaMax,
-			  float speed, float *buffer, int maxLength) {
-	// Figure out circumference of arc and therefore how many samples
-	// are needed to represent it based on the speed (rounded to nearest int)
-	float circumference = (thetaMax - thetaMin) * radius;
-	int samplesNeeded = floorf(circumference / speed + 0.5);
-
-	// Now render into the buffer
-	int length = 0;
-	float scaleFactor = 1.0f / samplesNeeded;
-	for(int n = 0; n < samplesNeeded; n++) {
-		if(length >= maxLength - 1)
-			return length;
-		// Get current angle
-		float theta = thetaMin + (float)n * scaleFactor * (thetaMax - thetaMin);
-
-		// Convert polar to cartesian coordinates
-		*buffer++ = x + radius * cosf(theta);
-		*buffer++ = y + radius * sinf(theta);
-
-		length += 2;
-	}
-
-	return length;
-}
-
-// Draw a single point for a specified number of frames
-void renderPoint(float x, float y, float *buffer, float length) {
-	while(length > 0) {
-		*buffer++ = x;
-		*buffer++ = y;
-		length--;
-	}
-}
--- a/examples/10-Instruments/tank_wars/vector_graphics.h	Tue Jun 21 20:00:12 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-/*
- * vector_graphics.h
- *
- *  Created on: Nov 10, 2014
- *      Author: parallels
- */
-
-#ifndef VECTOR_GRAPHICS_H_
-#define VECTOR_GRAPHICS_H_
-
-int renderLine(float x1, float y1, float x2, float y2, float speed,
-			   float *buffer, int maxLength);
-int renderArc(float x, float y, float radius, float thetaMin, float thetaMax,
-			  float speed, float *buffer, int maxLength);
-void renderPoint(float x, float y, float *buffer, float length);
-
-
-#endif /* VECTOR_GRAPHICS_H_ */