view projects/tank_wars/render.cpp @ 12:a6beeba3a648

Initial support for higher matrix sample rates by reducing the number of channels. Input not tested yet, and not all examples updated to new format.
author andrewm
date Thu, 22 Jan 2015 19:00:22 +0000
parents 49f22e1246b2
children 06f93bef7dd2
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>

float gFrequency;
float gPhase;
float gInverseSampleRate;
int gNumChannels;

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 * MATRIX_MAX;
float gLauncherMinimumPeak = 0.1 * MATRIX_MAX;
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;

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 numChannels, int numMatrixFramesPerPeriod,
					   int numAudioFramesPerPeriod, float matrixSampleRate,
					   float audioSampleRate, void *userData)
{
	srandom(time(NULL));

	// Verify we are running with matrix enabled
	if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) {
		rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n");
		return false;
	}

	// Initialise the screen buffers
	gScreenBufferMaxLength = 2 * matrixSampleRate / 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 = matrixSampleRate / 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 numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
			uint16_t *matrixIn, uint16_t *matrixOut)
{
	for(int n = 0; n < numMatrixFrames; n++) {
		// 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, MATRIX_MAX,
										 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, MATRIX_MAX, M_PI, 0));
			setTank2CannonAngle(map(analogRead(gInputTank2Angle, n),
									0, MATRIX_MAX, M_PI, 0));
			nextGameFrame();
		}

		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
			analogWrite(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, MATRIX_MAX), 0, MATRIX_MAX));
			analogWrite(gOutputY, n, constrain(map(y, 0, gScreenHeight, MATRIX_MAX, 0), 0, MATRIX_MAX));
		}
		else {
			// Still not ready! Write 0 until something happens
			analogWrite(gOutputX, n, 0);
			analogWrite(gOutputY, n, 0);
		}

		if(gameStatusWinner() != 0) {
			// Blink one LED to show who won
			// Blink both LEDs when projectile is in motion
			uint16_t val = (gSampleCounter % 4000 > 2000) ? MATRIX_MAX : 0;
			analogWrite(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0);
			analogWrite(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
			uint16_t val = (gSampleCounter % 2000 > 1000) ? MATRIX_MAX : 0;
			analogWrite(gOutputPlayer1LED, n, val);
			analogWrite(gOutputPlayer2LED, n, val);
		}
		else if(gameStatusPlayer1Turn()) {
			analogWrite(gOutputPlayer1LED, n, MATRIX_MAX);
			analogWrite(gOutputPlayer2LED, n, 0);
		}
		else {
			analogWrite(gOutputPlayer2LED, n, MATRIX_MAX);
			analogWrite(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();
}