Mercurial > hg > beaglert
diff examples/10-Instruments/tank_wars/render.cpp @ 464:8fcfbfb32aa0 prerelease
Examples reorder with subdirectories. Added header to each project. Moved Doxygen to bottom of render.cpp.
author | Robert Jack <robert.h.jack@gmail.com> |
---|---|
date | Mon, 20 Jun 2016 16:20:38 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/10-Instruments/tank_wars/render.cpp Mon Jun 20 16:20:38 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(); +}