| andrewm@10 | 1 /* | 
| andrewm@10 | 2  * render.cpp | 
| andrewm@10 | 3  * | 
| andrewm@10 | 4  *  Created on: Oct 24, 2014 | 
| andrewm@10 | 5  *      Author: parallels | 
| andrewm@10 | 6  */ | 
| andrewm@10 | 7 | 
| andrewm@10 | 8 | 
| andrewm@56 | 9 #include <BeagleRT.h> | 
| andrewm@56 | 10 #include <Utilities.h> | 
| andrewm@10 | 11 #include "game.h" | 
| andrewm@10 | 12 #include <rtdk.h> | 
| andrewm@10 | 13 #include <cmath> | 
| andrewm@10 | 14 #include <cstdlib> | 
| andrewm@10 | 15 #include <time.h> | 
| andrewm@10 | 16 | 
| andrewm@22 | 17 int gAudioFramesPerMatrixFrame = 2; // Ratio in audio to matrix sample rate | 
| andrewm@22 | 18 | 
| andrewm@10 | 19 int gInputTank1Angle = 0;		// Inputs for the cannon angles | 
| andrewm@10 | 20 int gInputTank2Angle = 1; | 
| andrewm@10 | 21 int gInputLauncher = 2;			// Input for launcher FSR | 
| andrewm@10 | 22 | 
| andrewm@10 | 23 int gOutputX = 0;				// Outputs for the scope | 
| andrewm@10 | 24 int gOutputY = 1; | 
| andrewm@10 | 25 int gOutputPlayer1LED = 2; | 
| andrewm@10 | 26 int gOutputPlayer2LED = 3; | 
| andrewm@10 | 27 | 
| andrewm@10 | 28 int gGameFramesPerSecond = 60;	// How often the physics are updated | 
| andrewm@10 | 29 int gGameFrameInterval;			// ...and in frames | 
| andrewm@10 | 30 int gSamplesUntilNextFrame;		// Counter until next update | 
| andrewm@10 | 31 int gSamplesSinceFinish = 0;	// How long since somebody won? | 
| andrewm@10 | 32 bool gGameShouldRestart = false;// Whether we need to reinitiliase the game | 
| andrewm@10 | 33 | 
| andrewm@10 | 34 // Counter for overall number of samples that have elapsed | 
| andrewm@10 | 35 unsigned int gSampleCounter = 0; | 
| andrewm@10 | 36 | 
| andrewm@10 | 37 // 1st-order filter and peak detector for launcher input | 
| andrewm@10 | 38 float gLauncherLastSample = 0; | 
| andrewm@10 | 39 float gLauncherFilterPole = 0.8; | 
| andrewm@10 | 40 float gLauncherPeakValue = 0; | 
| andrewm@10 | 41 float gLauncherPeakFilterPole = 0.999; | 
| andrewm@22 | 42 float gLauncherNoiseThreshold = 0.01; | 
| andrewm@22 | 43 float gLauncherMinimumPeak = 0.1; | 
| andrewm@10 | 44 bool gLauncherTriggered = false; | 
| andrewm@10 | 45 | 
| andrewm@10 | 46 // Screen update rate; affects buffer size. Actual contents of buffer | 
| andrewm@10 | 47 // may be smaller than this | 
| andrewm@10 | 48 int gScreenWidth = 512; | 
| andrewm@10 | 49 int gScreenHeight = 512; | 
| andrewm@10 | 50 int gScreenFramesPerSecond = 25; | 
| andrewm@10 | 51 | 
| andrewm@10 | 52 // Double buffer for rendering screen. Each is an interleaved buffer | 
| andrewm@10 | 53 // of XY data. | 
| andrewm@10 | 54 float *gScreenBuffer1, *gScreenBuffer2; | 
| andrewm@10 | 55 float *gScreenBufferWrite, *gScreenBufferRead; | 
| andrewm@10 | 56 int gScreenBufferMaxLength;				// What is the total buffer allocated? | 
| andrewm@10 | 57 int gScreenBufferReadLength;			// How long is the read buffer? | 
| andrewm@10 | 58 int gScreenBufferWriteLength;			// How long is the write (next) buffer? | 
| andrewm@10 | 59 int gScreenBufferReadPointer;			// Where are we in the read buffer now? | 
| andrewm@10 | 60 int gScreenBufferNextUpdateLocation;	// When should we render the next buffer? | 
| andrewm@10 | 61 bool gScreenNextBufferReady;			// Is the next buffer ready to go? | 
| andrewm@10 | 62 | 
| andrewm@10 | 63 // Auxiliary (low-priority) task for updating the screen | 
| andrewm@10 | 64 AuxiliaryTask gScreenUpdateTask; | 
| andrewm@10 | 65 | 
| andrewm@22 | 66 // Buffers for music and sound effects | 
| andrewm@22 | 67 extern float *gMusicBuffer; | 
| andrewm@22 | 68 extern int gMusicBufferLength; | 
| andrewm@22 | 69 extern float *gSoundBoomBuffer; | 
| andrewm@22 | 70 extern int gSoundBoomBufferLength; | 
| andrewm@22 | 71 | 
| andrewm@22 | 72 // Current state for sound and music | 
| andrewm@22 | 73 int gMusicBufferPointer = 0;	  // 0 means start of buffer... | 
| andrewm@22 | 74 int gSoundBoomBufferPointer = -1; // -1 means don't play... | 
| andrewm@22 | 75 float gSoundProjectileOscillatorPhase = 0; | 
| andrewm@22 | 76 float gSoundProjectileOscillatorGain = 0.2; | 
| andrewm@22 | 77 float gOscillatorPhaseScaler = 0; | 
| andrewm@22 | 78 | 
| andrewm@10 | 79 void screen_update(); | 
| andrewm@10 | 80 | 
| andrewm@56 | 81 // setup() is called once before the audio rendering starts. | 
| andrewm@10 | 82 // Use it to perform any initialisation and allocation which is dependent | 
| andrewm@10 | 83 // on the period size or sample rate. | 
| andrewm@10 | 84 // | 
| andrewm@10 | 85 // userData holds an opaque pointer to a data structure that was passed | 
| andrewm@10 | 86 // in from the call to initAudio(). | 
| andrewm@10 | 87 // | 
| andrewm@10 | 88 // Return true on success; returning false halts the program. | 
| andrewm@10 | 89 | 
| andrewm@56 | 90 bool setup(BeagleRTContext *context, void *userData) | 
| andrewm@10 | 91 { | 
| andrewm@10 | 92 	srandom(time(NULL)); | 
| andrewm@10 | 93 | 
| andrewm@10 | 94 	// Verify we are running with matrix enabled | 
| andrewm@56 | 95 	if(context->analogFrames == 0 || context->analogChannels < 4) { | 
| andrewm@14 | 96 		rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n"); | 
| andrewm@10 | 97 		return false; | 
| andrewm@10 | 98 	} | 
| andrewm@10 | 99 | 
| andrewm@22 | 100 	// Initialise audio variables | 
| andrewm@56 | 101 	gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames; | 
| andrewm@56 | 102 	gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate; | 
| andrewm@22 | 103 | 
| andrewm@10 | 104 	// Initialise the screen buffers | 
| andrewm@56 | 105 	gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond; | 
| andrewm@10 | 106 	gScreenBuffer1 = new float[gScreenBufferMaxLength]; | 
| andrewm@10 | 107 	gScreenBuffer2 = new float[gScreenBufferMaxLength]; | 
| andrewm@10 | 108 	if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) { | 
| andrewm@10 | 109 		rt_printf("Error initialising screen buffers\n"); | 
| andrewm@10 | 110 		return false; | 
| andrewm@10 | 111 	} | 
| andrewm@10 | 112 | 
| andrewm@10 | 113 	gScreenBufferRead = gScreenBuffer1; | 
| andrewm@10 | 114 	gScreenBufferWrite = gScreenBuffer2; | 
| andrewm@10 | 115 	gScreenBufferReadLength = gScreenBufferWriteLength = 0; | 
| andrewm@10 | 116 	gScreenBufferReadPointer = 0; | 
| andrewm@10 | 117 	gScreenBufferNextUpdateLocation = 0; | 
| andrewm@10 | 118 	gScreenNextBufferReady = false; | 
| andrewm@10 | 119 | 
| andrewm@10 | 120 	// Initialise the game | 
| andrewm@10 | 121 	setupGame(gScreenWidth, gScreenHeight); | 
| andrewm@56 | 122 	gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond; | 
| andrewm@10 | 123 	gSamplesUntilNextFrame = gGameFrameInterval; | 
| andrewm@10 | 124 | 
| andrewm@10 | 125 	// Initialise auxiliary tasks | 
| andrewm@56 | 126 	if((gScreenUpdateTask = BeagleRT_createAuxiliaryTask(&screen_update, 90, | 
| andrewm@56 | 127 														 "beaglert-screen-update")) == 0) | 
| andrewm@10 | 128 		return false; | 
| andrewm@10 | 129 | 
| andrewm@10 | 130 	return true; | 
| andrewm@10 | 131 } | 
| andrewm@10 | 132 | 
| andrewm@10 | 133 // Swap buffers on the screen | 
| andrewm@10 | 134 void swap_buffers() | 
| andrewm@10 | 135 { | 
| andrewm@10 | 136 	if(gScreenBufferRead == gScreenBuffer1) { | 
| andrewm@10 | 137 		gScreenBufferRead = gScreenBuffer2; | 
| andrewm@10 | 138 		gScreenBufferWrite = gScreenBuffer1; | 
| andrewm@10 | 139 	} | 
| andrewm@10 | 140 	else { | 
| andrewm@10 | 141 		gScreenBufferRead = gScreenBuffer1; | 
| andrewm@10 | 142 		gScreenBufferWrite = gScreenBuffer2; | 
| andrewm@10 | 143 	} | 
| andrewm@10 | 144 | 
| andrewm@10 | 145 	gScreenBufferReadLength = gScreenBufferWriteLength; | 
| andrewm@10 | 146 	gScreenBufferReadPointer = 0; | 
| andrewm@10 | 147 | 
| andrewm@10 | 148 	// Schedule next update for 3/4 of the way through the buffer | 
| andrewm@10 | 149 	gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75; | 
| andrewm@10 | 150 	gScreenNextBufferReady = false; | 
| andrewm@10 | 151 } | 
| andrewm@10 | 152 | 
| andrewm@10 | 153 // render() is called regularly at the highest priority by the audio engine. | 
| andrewm@10 | 154 // Input and output are given from the audio hardware and the other | 
| andrewm@10 | 155 // ADCs and DACs (if available). If only audio is available, numMatrixFrames | 
| andrewm@10 | 156 // will be 0. | 
| andrewm@10 | 157 | 
| andrewm@56 | 158 void render(BeagleRTContext *context, void *userData) | 
| andrewm@10 | 159 { | 
| andrewm@22 | 160 	int audioIndex = 0; | 
| andrewm@22 | 161 | 
| andrewm@56 | 162 	for(unsigned int n = 0; n < context->analogFrames; n++) { | 
| andrewm@22 | 163 		for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) { | 
| andrewm@22 | 164 			// Render music and sound | 
| andrewm@22 | 165 			float audioSample = 0; | 
| andrewm@22 | 166 | 
| andrewm@22 | 167 			// Music plays in a loop | 
| andrewm@22 | 168 			if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) { | 
| andrewm@22 | 169 				audioSample += gMusicBuffer[gMusicBufferPointer++]; | 
| andrewm@22 | 170 				if(gMusicBufferPointer >= gMusicBufferLength) | 
| andrewm@22 | 171 					gMusicBufferPointer = 0; | 
| andrewm@22 | 172 			} | 
| andrewm@22 | 173 | 
| andrewm@22 | 174 			// Sound effect plays until finished, then stops | 
| andrewm@22 | 175 			if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) { | 
| andrewm@22 | 176 				audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++]; | 
| andrewm@22 | 177 				if(gSoundBoomBufferPointer >= gSoundBoomBufferLength) | 
| andrewm@22 | 178 					gSoundBoomBufferPointer = -1; | 
| andrewm@22 | 179 			} | 
| andrewm@22 | 180 | 
| andrewm@22 | 181 			// Oscillator plays to indicate projectile height | 
| andrewm@22 | 182 			if(gameStatusProjectileInMotion()) { | 
| andrewm@22 | 183 				audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase); | 
| andrewm@22 | 184 | 
| andrewm@22 | 185 				gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(), | 
| andrewm@22 | 186 						1.0, 0, 300, 2000), 200, 6000); | 
| andrewm@22 | 187 				if(gSoundProjectileOscillatorPhase > 2.0 * M_PI) | 
| andrewm@22 | 188 					gSoundProjectileOscillatorPhase -= 2.0 * M_PI; | 
| andrewm@22 | 189 			} | 
| andrewm@22 | 190 | 
| andrewm@56 | 191 			context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample; | 
| andrewm@22 | 192 			audioIndex++; | 
| andrewm@22 | 193 		} | 
| andrewm@22 | 194 | 
| andrewm@10 | 195 		// First-order lowpass filter to remove noise on launch FSR | 
| andrewm@56 | 196 		float rawSample = analogReadFrame(context, n, gInputLauncher); | 
| andrewm@10 | 197 		float launchSample = gLauncherFilterPole * gLauncherLastSample + | 
| andrewm@10 | 198 							(1.0f - gLauncherFilterPole) * rawSample; | 
| andrewm@10 | 199 		gLauncherLastSample = launchSample; | 
| andrewm@10 | 200 | 
| andrewm@10 | 201 		// Peak-detect on launch signal | 
| andrewm@10 | 202 		if(launchSample >= gLauncherPeakValue) { | 
| andrewm@10 | 203 			gLauncherPeakValue = launchSample; | 
| andrewm@10 | 204 			gLauncherTriggered = false; | 
| andrewm@10 | 205 		} | 
| andrewm@10 | 206 		else { | 
| andrewm@10 | 207 			if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) { | 
| andrewm@10 | 208 				// Detected a peak; is it big enough overall? | 
| andrewm@10 | 209 				if(gLauncherPeakValue >= gLauncherMinimumPeak) { | 
| andrewm@10 | 210 					gLauncherTriggered = true; | 
| andrewm@10 | 211 					// Peak detected-- fire!! | 
| andrewm@10 | 212 					// Set both cannon strengths but only one will | 
| andrewm@10 | 213 					// fire depending on whose turn it is | 
| andrewm@10 | 214 					float strength = map(gLauncherPeakValue, | 
| andrewm@22 | 215 									     gLauncherMinimumPeak, 1.0, | 
| andrewm@10 | 216 										 0.5f, 10.0f); | 
| andrewm@10 | 217 					setTank1CannonStrength(strength); | 
| andrewm@10 | 218 					setTank2CannonStrength(strength); | 
| andrewm@10 | 219 					fireProjectile(); | 
| andrewm@10 | 220 				} | 
| andrewm@10 | 221 			} | 
| andrewm@10 | 222 | 
| andrewm@10 | 223 			gLauncherPeakValue *= gLauncherPeakFilterPole; | 
| andrewm@10 | 224 		} | 
| andrewm@10 | 225 | 
| andrewm@10 | 226 		if(--gSamplesUntilNextFrame <= 0) { | 
| andrewm@10 | 227 			// Update game physics and cannon angles | 
| andrewm@10 | 228 			gSamplesUntilNextFrame = gGameFrameInterval; | 
| andrewm@10 | 229 | 
| andrewm@56 | 230 			setTank1CannonAngle(map(analogReadFrame(context, n, gInputTank1Angle), | 
| andrewm@22 | 231 									0, 1.0, M_PI, 0)); | 
| andrewm@56 | 232 			setTank2CannonAngle(map(analogReadFrame(context, n, gInputTank2Angle), | 
| andrewm@22 | 233 									0, 1.0, M_PI, 0)); | 
| andrewm@10 | 234 			nextGameFrame(); | 
| andrewm@22 | 235 | 
| andrewm@22 | 236 			// Check for collision and start sound accordingly | 
| andrewm@22 | 237 			if(gameStatusCollisionOccurred()) { | 
| andrewm@22 | 238 				gSoundBoomBufferPointer = 0; | 
| andrewm@22 | 239 			} | 
| andrewm@10 | 240 		} | 
| andrewm@10 | 241 | 
| andrewm@10 | 242 		if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1 | 
| andrewm@10 | 243 			&& gScreenNextBufferReady) { | 
| andrewm@10 | 244 			// Got to the end; swap buffers | 
| andrewm@10 | 245 			swap_buffers(); | 
| andrewm@10 | 246 		} | 
| andrewm@10 | 247 | 
| andrewm@10 | 248 		// Push current screen buffer to the matrix output | 
| andrewm@10 | 249 		if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) { | 
| andrewm@10 | 250 			float x = gScreenBufferRead[gScreenBufferReadPointer++]; | 
| andrewm@10 | 251 			float y = gScreenBufferRead[gScreenBufferReadPointer++]; | 
| andrewm@10 | 252 | 
| andrewm@10 | 253 			// Rescale screen coordinates to matrix ranges; invert the Y | 
| andrewm@10 | 254 			// coordinate to go from normal screen coordinates to scope coordinates | 
| andrewm@56 | 255 			analogWriteFrameOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0)); | 
| andrewm@56 | 256 			analogWriteFrameOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0)); | 
| andrewm@10 | 257 		} | 
| andrewm@10 | 258 		else { | 
| andrewm@10 | 259 			// Still not ready! Write 0 until something happens | 
| andrewm@56 | 260 			analogWriteFrameOnce(context, n, gOutputX, 0); | 
| andrewm@56 | 261 			analogWriteFrameOnce(context, n, gOutputY, 0); | 
| andrewm@10 | 262 		} | 
| andrewm@10 | 263 | 
| andrewm@10 | 264 		if(gameStatusWinner() != 0) { | 
| andrewm@10 | 265 			// Blink one LED to show who won | 
| andrewm@10 | 266 			// Blink both LEDs when projectile is in motion | 
| andrewm@22 | 267 			float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0; | 
| andrewm@56 | 268 			analogWriteFrameOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0); | 
| andrewm@56 | 269 			analogWriteFrameOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0); | 
| andrewm@10 | 270 | 
| andrewm@10 | 271 			// After 5 seconds, restart the game | 
| andrewm@10 | 272 			gSamplesSinceFinish++; | 
| andrewm@10 | 273 			if(gSamplesSinceFinish > 22050*5) | 
| andrewm@10 | 274 				gGameShouldRestart = true; | 
| andrewm@10 | 275 		} | 
| andrewm@10 | 276 		else if(gameStatusProjectileInMotion()) { | 
| andrewm@10 | 277 			// Blink both LEDs when projectile is in motion | 
| andrewm@22 | 278 			float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0; | 
| andrewm@56 | 279 			analogWriteFrameOnce(context, n, gOutputPlayer1LED, val); | 
| andrewm@56 | 280 			analogWriteFrameOnce(context, n, gOutputPlayer2LED, val); | 
| andrewm@10 | 281 		} | 
| andrewm@10 | 282 		else if(gameStatusPlayer1Turn()) { | 
| andrewm@56 | 283 			analogWriteFrameOnce(context, n, gOutputPlayer1LED, 1.0); | 
| andrewm@56 | 284 			analogWriteFrameOnce(context, n, gOutputPlayer2LED, 0); | 
| andrewm@10 | 285 		} | 
| andrewm@10 | 286 		else { | 
| andrewm@56 | 287 			analogWriteFrameOnce(context, n, gOutputPlayer2LED, 1.0); | 
| andrewm@56 | 288 			analogWriteFrameOnce(context, n, gOutputPlayer1LED, 0); | 
| andrewm@10 | 289 		} | 
| andrewm@10 | 290 | 
| andrewm@10 | 291 		// Check if we have reached the point where we should next update | 
| andrewm@10 | 292 		if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation && | 
| andrewm@10 | 293 		   !gScreenNextBufferReady) { | 
| andrewm@10 | 294 			// Update the screen at lower priority than the audio thread | 
| andrewm@56 | 295 			BeagleRT_scheduleAuxiliaryTask(gScreenUpdateTask); | 
| andrewm@10 | 296 		} | 
| andrewm@10 | 297 | 
| andrewm@10 | 298 		gSampleCounter++; | 
| andrewm@10 | 299 	} | 
| andrewm@10 | 300 } | 
| andrewm@10 | 301 | 
| andrewm@10 | 302 void screen_update() | 
| andrewm@10 | 303 { | 
| andrewm@10 | 304 	// If we should restart, reinitialise the game | 
| andrewm@10 | 305 	if(gGameShouldRestart) { | 
| andrewm@10 | 306 		restartGame(); | 
| andrewm@10 | 307 		gGameShouldRestart = false; | 
| andrewm@10 | 308 		gSamplesSinceFinish = 0; | 
| andrewm@10 | 309 	} | 
| andrewm@10 | 310 | 
| andrewm@10 | 311 	// Render the game based on the current state | 
| andrewm@10 | 312 	gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength); | 
| andrewm@10 | 313 | 
| andrewm@10 | 314 	// Flag it as ready to go | 
| andrewm@10 | 315 	gScreenNextBufferReady = true; | 
| andrewm@10 | 316 } | 
| andrewm@10 | 317 | 
| andrewm@56 | 318 // cleanup() is called once at the end, after the audio has stopped. | 
| andrewm@56 | 319 // Release any resources that were allocated in setup(). | 
| andrewm@10 | 320 | 
| andrewm@56 | 321 void cleanup(BeagleRTContext *context, void *userData) | 
| andrewm@10 | 322 { | 
| andrewm@10 | 323 	// Clean up the game state | 
| andrewm@10 | 324 	cleanupGame(); | 
| andrewm@10 | 325 } |