annotate projects/tank_wars/render.cpp @ 45:579c86316008 newapi

Major API overhaul. Moved to a single data structure for handling render functions. Functionally, generally similar except for scheduling within PRU loop function, which now uses interrupts from the PRU rather than polling. This requires an updated kernel.
author andrewm
date Thu, 28 May 2015 14:35:55 -0400
parents fbfeb5895efd
children 3c3a1357657d
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
andrewm@10 9 #include "../../include/RTAudio.h"
andrewm@10 10 #include "../../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@10 81 // initialise_render() 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@22 90 bool initialise_render(int numAnalogChannels, int numDigitalChannels, int numAudioChannels,
andrewm@22 91 int numAnalogFramesPerPeriod,
andrewm@14 92 int numAudioFramesPerPeriod,
andrewm@22 93 float analogSampleRate, float audioSampleRate,
andrewm@14 94 void *userData)
andrewm@10 95 {
andrewm@10 96 srandom(time(NULL));
andrewm@10 97
andrewm@10 98 // Verify we are running with matrix enabled
andrewm@22 99 if(numAnalogFramesPerPeriod == 0 || numAnalogChannels < 4) {
andrewm@14 100 rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n");
andrewm@10 101 return false;
andrewm@10 102 }
andrewm@10 103
andrewm@22 104 // Initialise audio variables
andrewm@22 105 gAudioFramesPerMatrixFrame = numAudioFramesPerPeriod / numAnalogFramesPerPeriod;
andrewm@22 106 gOscillatorPhaseScaler = 2.0 * M_PI / audioSampleRate;
andrewm@22 107
andrewm@10 108 // Initialise the screen buffers
andrewm@22 109 gScreenBufferMaxLength = 2 * analogSampleRate / gScreenFramesPerSecond;
andrewm@10 110 gScreenBuffer1 = new float[gScreenBufferMaxLength];
andrewm@10 111 gScreenBuffer2 = new float[gScreenBufferMaxLength];
andrewm@10 112 if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
andrewm@10 113 rt_printf("Error initialising screen buffers\n");
andrewm@10 114 return false;
andrewm@10 115 }
andrewm@10 116
andrewm@10 117 gScreenBufferRead = gScreenBuffer1;
andrewm@10 118 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 119 gScreenBufferReadLength = gScreenBufferWriteLength = 0;
andrewm@10 120 gScreenBufferReadPointer = 0;
andrewm@10 121 gScreenBufferNextUpdateLocation = 0;
andrewm@10 122 gScreenNextBufferReady = false;
andrewm@10 123
andrewm@10 124 // Initialise the game
andrewm@10 125 setupGame(gScreenWidth, gScreenHeight);
andrewm@22 126 gGameFrameInterval = analogSampleRate / gGameFramesPerSecond;
andrewm@10 127 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 128
andrewm@10 129 // Initialise auxiliary tasks
andrewm@10 130 if((gScreenUpdateTask = createAuxiliaryTaskLoop(&screen_update, 90,
andrewm@10 131 "beaglert-screen-update")) == 0)
andrewm@10 132 return false;
andrewm@10 133
andrewm@10 134 return true;
andrewm@10 135 }
andrewm@10 136
andrewm@10 137 // Swap buffers on the screen
andrewm@10 138 void swap_buffers()
andrewm@10 139 {
andrewm@10 140 if(gScreenBufferRead == gScreenBuffer1) {
andrewm@10 141 gScreenBufferRead = gScreenBuffer2;
andrewm@10 142 gScreenBufferWrite = gScreenBuffer1;
andrewm@10 143 }
andrewm@10 144 else {
andrewm@10 145 gScreenBufferRead = gScreenBuffer1;
andrewm@10 146 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 147 }
andrewm@10 148
andrewm@10 149 gScreenBufferReadLength = gScreenBufferWriteLength;
andrewm@10 150 gScreenBufferReadPointer = 0;
andrewm@10 151
andrewm@10 152 // Schedule next update for 3/4 of the way through the buffer
andrewm@10 153 gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
andrewm@10 154 gScreenNextBufferReady = false;
andrewm@10 155 }
andrewm@10 156
andrewm@10 157 // render() is called regularly at the highest priority by the audio engine.
andrewm@10 158 // Input and output are given from the audio hardware and the other
andrewm@10 159 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@10 160 // will be 0.
andrewm@10 161
andrewm@22 162 void render(int numAnalogFrames, int numDigitalFrames, int numAudioFrames, float *audioIn, float *audioOut,
andrewm@22 163 float *analogIn, float *analogOut, uint32_t *digital)
andrewm@10 164 {
andrewm@22 165 int audioIndex = 0;
andrewm@22 166
andrewm@22 167 for(int n = 0; n < numAnalogFrames; n++) {
andrewm@22 168 for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) {
andrewm@22 169 // Render music and sound
andrewm@22 170 float audioSample = 0;
andrewm@22 171
andrewm@22 172 // Music plays in a loop
andrewm@22 173 if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) {
andrewm@22 174 audioSample += gMusicBuffer[gMusicBufferPointer++];
andrewm@22 175 if(gMusicBufferPointer >= gMusicBufferLength)
andrewm@22 176 gMusicBufferPointer = 0;
andrewm@22 177 }
andrewm@22 178
andrewm@22 179 // Sound effect plays until finished, then stops
andrewm@22 180 if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) {
andrewm@22 181 audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++];
andrewm@22 182 if(gSoundBoomBufferPointer >= gSoundBoomBufferLength)
andrewm@22 183 gSoundBoomBufferPointer = -1;
andrewm@22 184 }
andrewm@22 185
andrewm@22 186 // Oscillator plays to indicate projectile height
andrewm@22 187 if(gameStatusProjectileInMotion()) {
andrewm@22 188 audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase);
andrewm@22 189
andrewm@22 190 gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(),
andrewm@22 191 1.0, 0, 300, 2000), 200, 6000);
andrewm@22 192 if(gSoundProjectileOscillatorPhase > 2.0 * M_PI)
andrewm@22 193 gSoundProjectileOscillatorPhase -= 2.0 * M_PI;
andrewm@22 194 }
andrewm@22 195
andrewm@22 196 audioOut[2*audioIndex] = audioOut[2*audioIndex + 1] = audioSample;
andrewm@22 197 audioIndex++;
andrewm@22 198 }
andrewm@22 199
andrewm@10 200 // First-order lowpass filter to remove noise on launch FSR
andrewm@22 201 float rawSample = AnalogRead(gInputLauncher, n);
andrewm@10 202 float launchSample = gLauncherFilterPole * gLauncherLastSample +
andrewm@10 203 (1.0f - gLauncherFilterPole) * rawSample;
andrewm@10 204 gLauncherLastSample = launchSample;
andrewm@10 205
andrewm@10 206 // Peak-detect on launch signal
andrewm@10 207 if(launchSample >= gLauncherPeakValue) {
andrewm@10 208 gLauncherPeakValue = launchSample;
andrewm@10 209 gLauncherTriggered = false;
andrewm@10 210 }
andrewm@10 211 else {
andrewm@10 212 if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
andrewm@10 213 // Detected a peak; is it big enough overall?
andrewm@10 214 if(gLauncherPeakValue >= gLauncherMinimumPeak) {
andrewm@10 215 gLauncherTriggered = true;
andrewm@10 216 // Peak detected-- fire!!
andrewm@10 217 // Set both cannon strengths but only one will
andrewm@10 218 // fire depending on whose turn it is
andrewm@10 219 float strength = map(gLauncherPeakValue,
andrewm@22 220 gLauncherMinimumPeak, 1.0,
andrewm@10 221 0.5f, 10.0f);
andrewm@10 222 setTank1CannonStrength(strength);
andrewm@10 223 setTank2CannonStrength(strength);
andrewm@10 224 fireProjectile();
andrewm@10 225 }
andrewm@10 226 }
andrewm@10 227
andrewm@10 228 gLauncherPeakValue *= gLauncherPeakFilterPole;
andrewm@10 229 }
andrewm@10 230
andrewm@10 231 if(--gSamplesUntilNextFrame <= 0) {
andrewm@10 232 // Update game physics and cannon angles
andrewm@10 233 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 234
andrewm@22 235 setTank1CannonAngle(map(AnalogRead(gInputTank1Angle, n),
andrewm@22 236 0, 1.0, M_PI, 0));
andrewm@22 237 setTank2CannonAngle(map(AnalogRead(gInputTank2Angle, n),
andrewm@22 238 0, 1.0, M_PI, 0));
andrewm@10 239 nextGameFrame();
andrewm@22 240
andrewm@22 241 // Check for collision and start sound accordingly
andrewm@22 242 if(gameStatusCollisionOccurred()) {
andrewm@22 243 gSoundBoomBufferPointer = 0;
andrewm@22 244 }
andrewm@10 245 }
andrewm@10 246
andrewm@10 247 if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
andrewm@10 248 && gScreenNextBufferReady) {
andrewm@10 249 // Got to the end; swap buffers
andrewm@10 250 swap_buffers();
andrewm@10 251 }
andrewm@10 252
andrewm@10 253 // Push current screen buffer to the matrix output
andrewm@10 254 if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
andrewm@10 255 float x = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 256 float y = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 257
andrewm@10 258 // Rescale screen coordinates to matrix ranges; invert the Y
andrewm@10 259 // coordinate to go from normal screen coordinates to scope coordinates
andrewm@22 260 AnalogWriteFrame(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
andrewm@22 261 AnalogWriteFrame(gOutputY, n, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
andrewm@10 262 }
andrewm@10 263 else {
andrewm@10 264 // Still not ready! Write 0 until something happens
andrewm@22 265 AnalogWriteFrame(gOutputX, n, 0);
andrewm@22 266 AnalogWriteFrame(gOutputY, n, 0);
andrewm@10 267 }
andrewm@10 268
andrewm@10 269 if(gameStatusWinner() != 0) {
andrewm@10 270 // Blink one LED to show who won
andrewm@10 271 // Blink both LEDs when projectile is in motion
andrewm@22 272 float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0;
andrewm@22 273 AnalogWriteFrame(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0);
andrewm@22 274 AnalogWriteFrame(gOutputPlayer2LED, n, gameStatusWinner() == 2 ? val : 0);
andrewm@10 275
andrewm@10 276 // After 5 seconds, restart the game
andrewm@10 277 gSamplesSinceFinish++;
andrewm@10 278 if(gSamplesSinceFinish > 22050*5)
andrewm@10 279 gGameShouldRestart = true;
andrewm@10 280 }
andrewm@10 281 else if(gameStatusProjectileInMotion()) {
andrewm@10 282 // Blink both LEDs when projectile is in motion
andrewm@22 283 float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0;
andrewm@22 284 AnalogWriteFrame(gOutputPlayer1LED, n, val);
andrewm@22 285 AnalogWriteFrame(gOutputPlayer2LED, n, val);
andrewm@10 286 }
andrewm@10 287 else if(gameStatusPlayer1Turn()) {
andrewm@22 288 AnalogWriteFrame(gOutputPlayer1LED, n, 1.0);
andrewm@22 289 AnalogWriteFrame(gOutputPlayer2LED, n, 0);
andrewm@10 290 }
andrewm@10 291 else {
andrewm@22 292 AnalogWriteFrame(gOutputPlayer2LED, n, 1.0);
andrewm@22 293 AnalogWriteFrame(gOutputPlayer1LED, n, 0);
andrewm@10 294 }
andrewm@10 295
andrewm@10 296 // Check if we have reached the point where we should next update
andrewm@10 297 if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
andrewm@10 298 !gScreenNextBufferReady) {
andrewm@10 299 // Update the screen at lower priority than the audio thread
andrewm@10 300 scheduleAuxiliaryTask(gScreenUpdateTask);
andrewm@10 301 }
andrewm@10 302
andrewm@10 303 gSampleCounter++;
andrewm@10 304 }
andrewm@10 305 }
andrewm@10 306
andrewm@10 307 void screen_update()
andrewm@10 308 {
andrewm@10 309 // If we should restart, reinitialise the game
andrewm@10 310 if(gGameShouldRestart) {
andrewm@10 311 restartGame();
andrewm@10 312 gGameShouldRestart = false;
andrewm@10 313 gSamplesSinceFinish = 0;
andrewm@10 314 }
andrewm@10 315
andrewm@10 316 // Render the game based on the current state
andrewm@10 317 gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
andrewm@10 318
andrewm@10 319 // Flag it as ready to go
andrewm@10 320 gScreenNextBufferReady = true;
andrewm@10 321 }
andrewm@10 322
andrewm@10 323 // cleanup_render() is called once at the end, after the audio has stopped.
andrewm@10 324 // Release any resources that were allocated in initialise_render().
andrewm@10 325
andrewm@10 326 void cleanup_render()
andrewm@10 327 {
andrewm@10 328 // Clean up the game state
andrewm@10 329 cleanupGame();
andrewm@10 330 }