andrewm@10: /* andrewm@10: * render.cpp andrewm@10: * andrewm@10: * Created on: Oct 24, 2014 andrewm@10: * Author: parallels andrewm@10: */ andrewm@10: andrewm@10: giuliomoro@301: #include andrewm@10: #include "game.h" andrewm@10: #include andrewm@10: #include andrewm@10: #include andrewm@10: #include andrewm@10: andrewm@22: int gAudioFramesPerMatrixFrame = 2; // Ratio in audio to matrix sample rate andrewm@22: andrewm@10: int gInputTank1Angle = 0; // Inputs for the cannon angles andrewm@10: int gInputTank2Angle = 1; andrewm@10: int gInputLauncher = 2; // Input for launcher FSR andrewm@10: andrewm@10: int gOutputX = 0; // Outputs for the scope andrewm@10: int gOutputY = 1; andrewm@10: int gOutputPlayer1LED = 2; andrewm@10: int gOutputPlayer2LED = 3; andrewm@10: andrewm@10: int gGameFramesPerSecond = 60; // How often the physics are updated andrewm@10: int gGameFrameInterval; // ...and in frames andrewm@10: int gSamplesUntilNextFrame; // Counter until next update andrewm@10: int gSamplesSinceFinish = 0; // How long since somebody won? andrewm@10: bool gGameShouldRestart = false;// Whether we need to reinitiliase the game andrewm@10: andrewm@10: // Counter for overall number of samples that have elapsed andrewm@10: unsigned int gSampleCounter = 0; andrewm@10: andrewm@10: // 1st-order filter and peak detector for launcher input andrewm@10: float gLauncherLastSample = 0; andrewm@10: float gLauncherFilterPole = 0.8; andrewm@10: float gLauncherPeakValue = 0; andrewm@10: float gLauncherPeakFilterPole = 0.999; andrewm@22: float gLauncherNoiseThreshold = 0.01; andrewm@22: float gLauncherMinimumPeak = 0.1; andrewm@10: bool gLauncherTriggered = false; andrewm@10: andrewm@10: // Screen update rate; affects buffer size. Actual contents of buffer andrewm@10: // may be smaller than this andrewm@10: int gScreenWidth = 512; andrewm@10: int gScreenHeight = 512; andrewm@10: int gScreenFramesPerSecond = 25; andrewm@10: andrewm@10: // Double buffer for rendering screen. Each is an interleaved buffer andrewm@10: // of XY data. andrewm@10: float *gScreenBuffer1, *gScreenBuffer2; andrewm@10: float *gScreenBufferWrite, *gScreenBufferRead; andrewm@10: int gScreenBufferMaxLength; // What is the total buffer allocated? andrewm@10: int gScreenBufferReadLength; // How long is the read buffer? andrewm@10: int gScreenBufferWriteLength; // How long is the write (next) buffer? andrewm@10: int gScreenBufferReadPointer; // Where are we in the read buffer now? andrewm@10: int gScreenBufferNextUpdateLocation; // When should we render the next buffer? andrewm@10: bool gScreenNextBufferReady; // Is the next buffer ready to go? andrewm@10: andrewm@10: // Auxiliary (low-priority) task for updating the screen andrewm@10: AuxiliaryTask gScreenUpdateTask; andrewm@10: andrewm@22: // Buffers for music and sound effects andrewm@22: extern float *gMusicBuffer; andrewm@22: extern int gMusicBufferLength; andrewm@22: extern float *gSoundBoomBuffer; andrewm@22: extern int gSoundBoomBufferLength; andrewm@268: extern float *gSoundHitBuffer; andrewm@268: extern int gSoundHitBufferLength; andrewm@22: andrewm@22: // Current state for sound and music andrewm@22: int gMusicBufferPointer = 0; // 0 means start of buffer... andrewm@22: int gSoundBoomBufferPointer = -1; // -1 means don't play... andrewm@268: int gSoundHitBufferPointer = -1; andrewm@22: float gSoundProjectileOscillatorPhase = 0; andrewm@22: float gSoundProjectileOscillatorGain = 0.2; andrewm@22: float gOscillatorPhaseScaler = 0; andrewm@22: andrewm@10: void screen_update(); andrewm@10: andrewm@56: // setup() is called once before the audio rendering starts. andrewm@10: // Use it to perform any initialisation and allocation which is dependent andrewm@10: // on the period size or sample rate. andrewm@10: // andrewm@10: // userData holds an opaque pointer to a data structure that was passed andrewm@10: // in from the call to initAudio(). andrewm@10: // andrewm@10: // Return true on success; returning false halts the program. andrewm@10: giuliomoro@301: bool setup(BelaContext *context, void *userData) andrewm@10: { andrewm@10: srandom(time(NULL)); andrewm@10: andrewm@10: // Verify we are running with matrix enabled andrewm@56: if(context->analogFrames == 0 || context->analogChannels < 4) { andrewm@14: rt_printf("Error: this example needs the matrix enabled with at least 4 channels\n"); andrewm@10: return false; andrewm@10: } andrewm@10: andrewm@22: // Initialise audio variables andrewm@56: gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames; andrewm@56: gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate; andrewm@22: andrewm@10: // Initialise the screen buffers andrewm@56: gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond; andrewm@10: gScreenBuffer1 = new float[gScreenBufferMaxLength]; andrewm@10: gScreenBuffer2 = new float[gScreenBufferMaxLength]; andrewm@10: if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) { andrewm@10: rt_printf("Error initialising screen buffers\n"); andrewm@10: return false; andrewm@10: } andrewm@10: andrewm@10: gScreenBufferRead = gScreenBuffer1; andrewm@10: gScreenBufferWrite = gScreenBuffer2; andrewm@10: gScreenBufferReadLength = gScreenBufferWriteLength = 0; andrewm@10: gScreenBufferReadPointer = 0; andrewm@10: gScreenBufferNextUpdateLocation = 0; andrewm@10: gScreenNextBufferReady = false; andrewm@10: andrewm@10: // Initialise the game andrewm@10: setupGame(gScreenWidth, gScreenHeight); andrewm@56: gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond; andrewm@10: gSamplesUntilNextFrame = gGameFrameInterval; andrewm@10: andrewm@10: // Initialise auxiliary tasks giuliomoro@301: if((gScreenUpdateTask = Bela_createAuxiliaryTask(&screen_update, 90, andrewm@303: "bela-screen-update")) == 0) andrewm@10: return false; andrewm@10: andrewm@10: return true; andrewm@10: } andrewm@10: andrewm@10: // Swap buffers on the screen andrewm@10: void swap_buffers() andrewm@10: { andrewm@10: if(gScreenBufferRead == gScreenBuffer1) { andrewm@10: gScreenBufferRead = gScreenBuffer2; andrewm@10: gScreenBufferWrite = gScreenBuffer1; andrewm@10: } andrewm@10: else { andrewm@10: gScreenBufferRead = gScreenBuffer1; andrewm@10: gScreenBufferWrite = gScreenBuffer2; andrewm@10: } andrewm@10: andrewm@10: gScreenBufferReadLength = gScreenBufferWriteLength; andrewm@10: gScreenBufferReadPointer = 0; andrewm@10: andrewm@10: // Schedule next update for 3/4 of the way through the buffer andrewm@10: gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75; andrewm@10: gScreenNextBufferReady = false; andrewm@10: } andrewm@10: andrewm@10: // render() is called regularly at the highest priority by the audio engine. andrewm@10: // Input and output are given from the audio hardware and the other andrewm@10: // ADCs and DACs (if available). If only audio is available, numMatrixFrames andrewm@10: // will be 0. andrewm@10: giuliomoro@301: void render(BelaContext *context, void *userData) andrewm@10: { andrewm@22: int audioIndex = 0; andrewm@22: andrewm@56: for(unsigned int n = 0; n < context->analogFrames; n++) { andrewm@22: for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) { andrewm@22: // Render music and sound andrewm@22: float audioSample = 0; andrewm@22: andrewm@22: // Music plays in a loop andrewm@22: if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) { andrewm@22: audioSample += gMusicBuffer[gMusicBufferPointer++]; andrewm@22: if(gMusicBufferPointer >= gMusicBufferLength) andrewm@22: gMusicBufferPointer = 0; andrewm@22: } andrewm@22: andrewm@22: // Sound effect plays until finished, then stops andrewm@22: if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) { andrewm@22: audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++]; andrewm@22: if(gSoundBoomBufferPointer >= gSoundBoomBufferLength) andrewm@22: gSoundBoomBufferPointer = -1; andrewm@22: } andrewm@22: andrewm@268: if(gSoundHitBuffer != 0 && gSoundHitBufferPointer >= 0) { andrewm@268: audioSample += gSoundHitBuffer[gSoundHitBufferPointer++]; andrewm@268: if(gSoundHitBufferPointer >= gSoundHitBufferLength) andrewm@268: gSoundHitBufferPointer = -1; andrewm@268: } andrewm@268: andrewm@22: // Oscillator plays to indicate projectile height andrewm@22: if(gameStatusProjectileInMotion()) { andrewm@22: audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase); andrewm@22: andrewm@22: gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(), andrewm@22: 1.0, 0, 300, 2000), 200, 6000); andrewm@22: if(gSoundProjectileOscillatorPhase > 2.0 * M_PI) andrewm@22: gSoundProjectileOscillatorPhase -= 2.0 * M_PI; andrewm@22: } andrewm@22: andrewm@56: context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample; andrewm@22: audioIndex++; andrewm@22: } andrewm@22: andrewm@10: // First-order lowpass filter to remove noise on launch FSR andrewm@311: float rawSample = analogRead(context, n, gInputLauncher); andrewm@10: float launchSample = gLauncherFilterPole * gLauncherLastSample + andrewm@10: (1.0f - gLauncherFilterPole) * rawSample; andrewm@10: gLauncherLastSample = launchSample; andrewm@10: andrewm@10: // Peak-detect on launch signal andrewm@10: if(launchSample >= gLauncherPeakValue) { andrewm@10: gLauncherPeakValue = launchSample; andrewm@10: gLauncherTriggered = false; andrewm@10: } andrewm@10: else { andrewm@10: if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) { andrewm@10: // Detected a peak; is it big enough overall? andrewm@10: if(gLauncherPeakValue >= gLauncherMinimumPeak) { andrewm@10: gLauncherTriggered = true; andrewm@10: // Peak detected-- fire!! andrewm@10: // Set both cannon strengths but only one will andrewm@10: // fire depending on whose turn it is andrewm@10: float strength = map(gLauncherPeakValue, andrewm@22: gLauncherMinimumPeak, 1.0, andrewm@10: 0.5f, 10.0f); andrewm@10: setTank1CannonStrength(strength); andrewm@10: setTank2CannonStrength(strength); andrewm@10: fireProjectile(); andrewm@10: } andrewm@10: } andrewm@10: andrewm@10: gLauncherPeakValue *= gLauncherPeakFilterPole; andrewm@10: } andrewm@10: andrewm@10: if(--gSamplesUntilNextFrame <= 0) { andrewm@10: // Update game physics and cannon angles andrewm@10: gSamplesUntilNextFrame = gGameFrameInterval; andrewm@10: andrewm@311: setTank1CannonAngle(map(analogRead(context, n, gInputTank1Angle), andrewm@22: 0, 1.0, M_PI, 0)); andrewm@311: setTank2CannonAngle(map(analogRead(context, n, gInputTank2Angle), andrewm@22: 0, 1.0, M_PI, 0)); andrewm@10: nextGameFrame(); andrewm@22: andrewm@22: // Check for collision and start sound accordingly andrewm@22: if(gameStatusCollisionOccurred()) { andrewm@22: gSoundBoomBufferPointer = 0; andrewm@22: } andrewm@268: andrewm@268: if(gameStatusTankHitOccurred()) { andrewm@268: gSoundHitBufferPointer = 0; andrewm@268: } andrewm@10: } andrewm@10: andrewm@10: if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1 andrewm@10: && gScreenNextBufferReady) { andrewm@10: // Got to the end; swap buffers andrewm@10: swap_buffers(); andrewm@10: } andrewm@10: andrewm@10: // Push current screen buffer to the matrix output andrewm@10: if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) { andrewm@10: float x = gScreenBufferRead[gScreenBufferReadPointer++]; andrewm@10: float y = gScreenBufferRead[gScreenBufferReadPointer++]; andrewm@10: andrewm@10: // Rescale screen coordinates to matrix ranges; invert the Y andrewm@10: // coordinate to go from normal screen coordinates to scope coordinates andrewm@311: analogWriteOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0)); andrewm@311: analogWriteOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0)); andrewm@10: } andrewm@10: else { andrewm@10: // Still not ready! Write 0 until something happens andrewm@311: analogWriteOnce(context, n, gOutputX, 0); andrewm@311: analogWriteOnce(context, n, gOutputY, 0); andrewm@10: } andrewm@10: andrewm@10: if(gameStatusWinner() != 0) { andrewm@10: // Blink one LED to show who won andrewm@10: // Blink both LEDs when projectile is in motion andrewm@22: float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0; andrewm@311: analogWriteOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0); andrewm@311: analogWriteOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0); andrewm@10: andrewm@10: // After 5 seconds, restart the game andrewm@10: gSamplesSinceFinish++; andrewm@10: if(gSamplesSinceFinish > 22050*5) andrewm@10: gGameShouldRestart = true; andrewm@10: } andrewm@10: else if(gameStatusProjectileInMotion()) { andrewm@10: // Blink both LEDs when projectile is in motion andrewm@22: float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0; andrewm@311: analogWriteOnce(context, n, gOutputPlayer1LED, val); andrewm@311: analogWriteOnce(context, n, gOutputPlayer2LED, val); andrewm@10: } andrewm@10: else if(gameStatusPlayer1Turn()) { andrewm@311: analogWriteOnce(context, n, gOutputPlayer1LED, 1.0); andrewm@311: analogWriteOnce(context, n, gOutputPlayer2LED, 0); andrewm@10: } andrewm@10: else { andrewm@311: analogWriteOnce(context, n, gOutputPlayer2LED, 1.0); andrewm@311: analogWriteOnce(context, n, gOutputPlayer1LED, 0); andrewm@10: } andrewm@10: andrewm@10: // Check if we have reached the point where we should next update andrewm@10: if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation && andrewm@10: !gScreenNextBufferReady) { andrewm@10: // Update the screen at lower priority than the audio thread giuliomoro@301: Bela_scheduleAuxiliaryTask(gScreenUpdateTask); andrewm@10: } andrewm@10: andrewm@10: gSampleCounter++; andrewm@10: } andrewm@10: } andrewm@10: andrewm@10: void screen_update() andrewm@10: { andrewm@10: // If we should restart, reinitialise the game andrewm@10: if(gGameShouldRestart) { andrewm@10: restartGame(); andrewm@10: gGameShouldRestart = false; andrewm@10: gSamplesSinceFinish = 0; andrewm@10: } andrewm@10: andrewm@10: // Render the game based on the current state andrewm@10: gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength); andrewm@10: andrewm@10: // Flag it as ready to go andrewm@10: gScreenNextBufferReady = true; andrewm@10: } andrewm@10: andrewm@56: // cleanup() is called once at the end, after the audio has stopped. andrewm@56: // Release any resources that were allocated in setup(). andrewm@10: giuliomoro@301: void cleanup(BelaContext *context, void *userData) andrewm@10: { andrewm@10: // Clean up the game state andrewm@10: cleanupGame(); andrewm@10: }