Mercurial > hg > beaglert
view 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 |
line wrap: on
line source
/* * render.cpp * * Created on: Oct 24, 2014 * Author: parallels */ #include "../../include/RTAudio.h" #include "../../include/Utilities.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; // Current state for sound and music int gMusicBufferPointer = 0; // 0 means start of buffer... int gSoundBoomBufferPointer = -1; // -1 means don't play... float gSoundProjectileOscillatorPhase = 0; float gSoundProjectileOscillatorGain = 0.2; float gOscillatorPhaseScaler = 0; void screen_update(); // initialise_render() 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 initialise_render(int numAnalogChannels, int numDigitalChannels, int numAudioChannels, int numAnalogFramesPerPeriod, int numAudioFramesPerPeriod, float analogSampleRate, float audioSampleRate, void *userData) { srandom(time(NULL)); // Verify we are running with matrix enabled if(numAnalogFramesPerPeriod == 0 || numAnalogChannels < 4) { rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n"); return false; } // Initialise audio variables gAudioFramesPerMatrixFrame = numAudioFramesPerPeriod / numAnalogFramesPerPeriod; gOscillatorPhaseScaler = 2.0 * M_PI / audioSampleRate; // Initialise the screen buffers gScreenBufferMaxLength = 2 * 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 = analogSampleRate / gGameFramesPerSecond; gSamplesUntilNextFrame = gGameFrameInterval; // Initialise auxiliary tasks if((gScreenUpdateTask = createAuxiliaryTaskLoop(&screen_update, 90, "beaglert-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(int numAnalogFrames, int numDigitalFrames, int numAudioFrames, float *audioIn, float *audioOut, float *analogIn, float *analogOut, uint32_t *digital) { int audioIndex = 0; for(int n = 0; n < numAnalogFrames; 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; } // 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; } audioOut[2*audioIndex] = audioOut[2*audioIndex + 1] = audioSample; audioIndex++; } // First-order lowpass filter to remove noise on launch FSR float rawSample = AnalogRead(gInputLauncher, n); 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(gInputTank1Angle, n), 0, 1.0, M_PI, 0)); setTank2CannonAngle(map(AnalogRead(gInputTank2Angle, n), 0, 1.0, M_PI, 0)); nextGameFrame(); // Check for collision and start sound accordingly if(gameStatusCollisionOccurred()) { gSoundBoomBufferPointer = 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 AnalogWriteFrame(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0)); AnalogWriteFrame(gOutputY, n, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0)); } else { // Still not ready! Write 0 until something happens AnalogWriteFrame(gOutputX, n, 0); AnalogWriteFrame(gOutputY, n, 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; AnalogWriteFrame(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0); AnalogWriteFrame(gOutputPlayer2LED, n, 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; AnalogWriteFrame(gOutputPlayer1LED, n, val); AnalogWriteFrame(gOutputPlayer2LED, n, val); } else if(gameStatusPlayer1Turn()) { AnalogWriteFrame(gOutputPlayer1LED, n, 1.0); AnalogWriteFrame(gOutputPlayer2LED, n, 0); } else { AnalogWriteFrame(gOutputPlayer2LED, n, 1.0); AnalogWriteFrame(gOutputPlayer1LED, n, 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 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_render() is called once at the end, after the audio has stopped. // Release any resources that were allocated in initialise_render(). void cleanup_render() { // Clean up the game state cleanupGame(); }