Mercurial > hg > beaglert
view examples/10-Instruments/tank-wars/render.cpp @ 543:8f8809c77dda prerelease
updated basics, digital, instruments, extras examples
author | chnrx <chris.heinrichs@gmail.com> |
---|---|
date | Fri, 24 Jun 2016 13:19:52 +0100 |
parents | a23d74e2f6cb |
children |
line wrap: on
line source
/* * 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 analog channels enabled if(context->analogFrames == 0 || context->analogOutChannels < 4) { rt_printf("Error: this example needs least 4 analog output 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(); }