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