annotate examples/tank_wars/render.cpp @ 311:493a07f6ec09 prerelease

Renamed BelaContext->audioSampleCount to BelaContext->audioFramesElapsed for consistency of terminology
author andrewm
date Fri, 27 May 2016 18:37:51 +0100
parents 421a69d42943
children 9dc5a0ccad25
rev   line source
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
giuliomoro@301 9 #include <Bela.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@268 71 extern float *gSoundHitBuffer;
andrewm@268 72 extern int gSoundHitBufferLength;
andrewm@22 73
andrewm@22 74 // Current state for sound and music
andrewm@22 75 int gMusicBufferPointer = 0; // 0 means start of buffer...
andrewm@22 76 int gSoundBoomBufferPointer = -1; // -1 means don't play...
andrewm@268 77 int gSoundHitBufferPointer = -1;
andrewm@22 78 float gSoundProjectileOscillatorPhase = 0;
andrewm@22 79 float gSoundProjectileOscillatorGain = 0.2;
andrewm@22 80 float gOscillatorPhaseScaler = 0;
andrewm@22 81
andrewm@10 82 void screen_update();
andrewm@10 83
andrewm@56 84 // setup() is called once before the audio rendering starts.
andrewm@10 85 // Use it to perform any initialisation and allocation which is dependent
andrewm@10 86 // on the period size or sample rate.
andrewm@10 87 //
andrewm@10 88 // userData holds an opaque pointer to a data structure that was passed
andrewm@10 89 // in from the call to initAudio().
andrewm@10 90 //
andrewm@10 91 // Return true on success; returning false halts the program.
andrewm@10 92
giuliomoro@301 93 bool setup(BelaContext *context, void *userData)
andrewm@10 94 {
andrewm@10 95 srandom(time(NULL));
andrewm@10 96
andrewm@10 97 // Verify we are running with matrix enabled
andrewm@56 98 if(context->analogFrames == 0 || context->analogChannels < 4) {
andrewm@14 99 rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n");
andrewm@10 100 return false;
andrewm@10 101 }
andrewm@10 102
andrewm@22 103 // Initialise audio variables
andrewm@56 104 gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames;
andrewm@56 105 gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate;
andrewm@22 106
andrewm@10 107 // Initialise the screen buffers
andrewm@56 108 gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond;
andrewm@10 109 gScreenBuffer1 = new float[gScreenBufferMaxLength];
andrewm@10 110 gScreenBuffer2 = new float[gScreenBufferMaxLength];
andrewm@10 111 if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
andrewm@10 112 rt_printf("Error initialising screen buffers\n");
andrewm@10 113 return false;
andrewm@10 114 }
andrewm@10 115
andrewm@10 116 gScreenBufferRead = gScreenBuffer1;
andrewm@10 117 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 118 gScreenBufferReadLength = gScreenBufferWriteLength = 0;
andrewm@10 119 gScreenBufferReadPointer = 0;
andrewm@10 120 gScreenBufferNextUpdateLocation = 0;
andrewm@10 121 gScreenNextBufferReady = false;
andrewm@10 122
andrewm@10 123 // Initialise the game
andrewm@10 124 setupGame(gScreenWidth, gScreenHeight);
andrewm@56 125 gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond;
andrewm@10 126 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 127
andrewm@10 128 // Initialise auxiliary tasks
giuliomoro@301 129 if((gScreenUpdateTask = Bela_createAuxiliaryTask(&screen_update, 90,
andrewm@303 130 "bela-screen-update")) == 0)
andrewm@10 131 return false;
andrewm@10 132
andrewm@10 133 return true;
andrewm@10 134 }
andrewm@10 135
andrewm@10 136 // Swap buffers on the screen
andrewm@10 137 void swap_buffers()
andrewm@10 138 {
andrewm@10 139 if(gScreenBufferRead == gScreenBuffer1) {
andrewm@10 140 gScreenBufferRead = gScreenBuffer2;
andrewm@10 141 gScreenBufferWrite = gScreenBuffer1;
andrewm@10 142 }
andrewm@10 143 else {
andrewm@10 144 gScreenBufferRead = gScreenBuffer1;
andrewm@10 145 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 146 }
andrewm@10 147
andrewm@10 148 gScreenBufferReadLength = gScreenBufferWriteLength;
andrewm@10 149 gScreenBufferReadPointer = 0;
andrewm@10 150
andrewm@10 151 // Schedule next update for 3/4 of the way through the buffer
andrewm@10 152 gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
andrewm@10 153 gScreenNextBufferReady = false;
andrewm@10 154 }
andrewm@10 155
andrewm@10 156 // render() is called regularly at the highest priority by the audio engine.
andrewm@10 157 // Input and output are given from the audio hardware and the other
andrewm@10 158 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@10 159 // will be 0.
andrewm@10 160
giuliomoro@301 161 void render(BelaContext *context, void *userData)
andrewm@10 162 {
andrewm@22 163 int audioIndex = 0;
andrewm@22 164
andrewm@56 165 for(unsigned int n = 0; n < context->analogFrames; n++) {
andrewm@22 166 for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) {
andrewm@22 167 // Render music and sound
andrewm@22 168 float audioSample = 0;
andrewm@22 169
andrewm@22 170 // Music plays in a loop
andrewm@22 171 if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) {
andrewm@22 172 audioSample += gMusicBuffer[gMusicBufferPointer++];
andrewm@22 173 if(gMusicBufferPointer >= gMusicBufferLength)
andrewm@22 174 gMusicBufferPointer = 0;
andrewm@22 175 }
andrewm@22 176
andrewm@22 177 // Sound effect plays until finished, then stops
andrewm@22 178 if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) {
andrewm@22 179 audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++];
andrewm@22 180 if(gSoundBoomBufferPointer >= gSoundBoomBufferLength)
andrewm@22 181 gSoundBoomBufferPointer = -1;
andrewm@22 182 }
andrewm@22 183
andrewm@268 184 if(gSoundHitBuffer != 0 && gSoundHitBufferPointer >= 0) {
andrewm@268 185 audioSample += gSoundHitBuffer[gSoundHitBufferPointer++];
andrewm@268 186 if(gSoundHitBufferPointer >= gSoundHitBufferLength)
andrewm@268 187 gSoundHitBufferPointer = -1;
andrewm@268 188 }
andrewm@268 189
andrewm@22 190 // Oscillator plays to indicate projectile height
andrewm@22 191 if(gameStatusProjectileInMotion()) {
andrewm@22 192 audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase);
andrewm@22 193
andrewm@22 194 gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(),
andrewm@22 195 1.0, 0, 300, 2000), 200, 6000);
andrewm@22 196 if(gSoundProjectileOscillatorPhase > 2.0 * M_PI)
andrewm@22 197 gSoundProjectileOscillatorPhase -= 2.0 * M_PI;
andrewm@22 198 }
andrewm@22 199
andrewm@56 200 context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample;
andrewm@22 201 audioIndex++;
andrewm@22 202 }
andrewm@22 203
andrewm@10 204 // First-order lowpass filter to remove noise on launch FSR
andrewm@311 205 float rawSample = analogRead(context, n, gInputLauncher);
andrewm@10 206 float launchSample = gLauncherFilterPole * gLauncherLastSample +
andrewm@10 207 (1.0f - gLauncherFilterPole) * rawSample;
andrewm@10 208 gLauncherLastSample = launchSample;
andrewm@10 209
andrewm@10 210 // Peak-detect on launch signal
andrewm@10 211 if(launchSample >= gLauncherPeakValue) {
andrewm@10 212 gLauncherPeakValue = launchSample;
andrewm@10 213 gLauncherTriggered = false;
andrewm@10 214 }
andrewm@10 215 else {
andrewm@10 216 if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
andrewm@10 217 // Detected a peak; is it big enough overall?
andrewm@10 218 if(gLauncherPeakValue >= gLauncherMinimumPeak) {
andrewm@10 219 gLauncherTriggered = true;
andrewm@10 220 // Peak detected-- fire!!
andrewm@10 221 // Set both cannon strengths but only one will
andrewm@10 222 // fire depending on whose turn it is
andrewm@10 223 float strength = map(gLauncherPeakValue,
andrewm@22 224 gLauncherMinimumPeak, 1.0,
andrewm@10 225 0.5f, 10.0f);
andrewm@10 226 setTank1CannonStrength(strength);
andrewm@10 227 setTank2CannonStrength(strength);
andrewm@10 228 fireProjectile();
andrewm@10 229 }
andrewm@10 230 }
andrewm@10 231
andrewm@10 232 gLauncherPeakValue *= gLauncherPeakFilterPole;
andrewm@10 233 }
andrewm@10 234
andrewm@10 235 if(--gSamplesUntilNextFrame <= 0) {
andrewm@10 236 // Update game physics and cannon angles
andrewm@10 237 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 238
andrewm@311 239 setTank1CannonAngle(map(analogRead(context, n, gInputTank1Angle),
andrewm@22 240 0, 1.0, M_PI, 0));
andrewm@311 241 setTank2CannonAngle(map(analogRead(context, n, gInputTank2Angle),
andrewm@22 242 0, 1.0, M_PI, 0));
andrewm@10 243 nextGameFrame();
andrewm@22 244
andrewm@22 245 // Check for collision and start sound accordingly
andrewm@22 246 if(gameStatusCollisionOccurred()) {
andrewm@22 247 gSoundBoomBufferPointer = 0;
andrewm@22 248 }
andrewm@268 249
andrewm@268 250 if(gameStatusTankHitOccurred()) {
andrewm@268 251 gSoundHitBufferPointer = 0;
andrewm@268 252 }
andrewm@10 253 }
andrewm@10 254
andrewm@10 255 if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
andrewm@10 256 && gScreenNextBufferReady) {
andrewm@10 257 // Got to the end; swap buffers
andrewm@10 258 swap_buffers();
andrewm@10 259 }
andrewm@10 260
andrewm@10 261 // Push current screen buffer to the matrix output
andrewm@10 262 if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
andrewm@10 263 float x = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 264 float y = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 265
andrewm@10 266 // Rescale screen coordinates to matrix ranges; invert the Y
andrewm@10 267 // coordinate to go from normal screen coordinates to scope coordinates
andrewm@311 268 analogWriteOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
andrewm@311 269 analogWriteOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
andrewm@10 270 }
andrewm@10 271 else {
andrewm@10 272 // Still not ready! Write 0 until something happens
andrewm@311 273 analogWriteOnce(context, n, gOutputX, 0);
andrewm@311 274 analogWriteOnce(context, n, gOutputY, 0);
andrewm@10 275 }
andrewm@10 276
andrewm@10 277 if(gameStatusWinner() != 0) {
andrewm@10 278 // Blink one LED to show who won
andrewm@10 279 // Blink both LEDs when projectile is in motion
andrewm@22 280 float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0;
andrewm@311 281 analogWriteOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0);
andrewm@311 282 analogWriteOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0);
andrewm@10 283
andrewm@10 284 // After 5 seconds, restart the game
andrewm@10 285 gSamplesSinceFinish++;
andrewm@10 286 if(gSamplesSinceFinish > 22050*5)
andrewm@10 287 gGameShouldRestart = true;
andrewm@10 288 }
andrewm@10 289 else if(gameStatusProjectileInMotion()) {
andrewm@10 290 // Blink both LEDs when projectile is in motion
andrewm@22 291 float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0;
andrewm@311 292 analogWriteOnce(context, n, gOutputPlayer1LED, val);
andrewm@311 293 analogWriteOnce(context, n, gOutputPlayer2LED, val);
andrewm@10 294 }
andrewm@10 295 else if(gameStatusPlayer1Turn()) {
andrewm@311 296 analogWriteOnce(context, n, gOutputPlayer1LED, 1.0);
andrewm@311 297 analogWriteOnce(context, n, gOutputPlayer2LED, 0);
andrewm@10 298 }
andrewm@10 299 else {
andrewm@311 300 analogWriteOnce(context, n, gOutputPlayer2LED, 1.0);
andrewm@311 301 analogWriteOnce(context, n, gOutputPlayer1LED, 0);
andrewm@10 302 }
andrewm@10 303
andrewm@10 304 // Check if we have reached the point where we should next update
andrewm@10 305 if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
andrewm@10 306 !gScreenNextBufferReady) {
andrewm@10 307 // Update the screen at lower priority than the audio thread
giuliomoro@301 308 Bela_scheduleAuxiliaryTask(gScreenUpdateTask);
andrewm@10 309 }
andrewm@10 310
andrewm@10 311 gSampleCounter++;
andrewm@10 312 }
andrewm@10 313 }
andrewm@10 314
andrewm@10 315 void screen_update()
andrewm@10 316 {
andrewm@10 317 // If we should restart, reinitialise the game
andrewm@10 318 if(gGameShouldRestart) {
andrewm@10 319 restartGame();
andrewm@10 320 gGameShouldRestart = false;
andrewm@10 321 gSamplesSinceFinish = 0;
andrewm@10 322 }
andrewm@10 323
andrewm@10 324 // Render the game based on the current state
andrewm@10 325 gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
andrewm@10 326
andrewm@10 327 // Flag it as ready to go
andrewm@10 328 gScreenNextBufferReady = true;
andrewm@10 329 }
andrewm@10 330
andrewm@56 331 // cleanup() is called once at the end, after the audio has stopped.
andrewm@56 332 // Release any resources that were allocated in setup().
andrewm@10 333
giuliomoro@301 334 void cleanup(BelaContext *context, void *userData)
andrewm@10 335 {
andrewm@10 336 // Clean up the game state
andrewm@10 337 cleanupGame();
andrewm@10 338 }