view projects/tank_wars/render.cpp @ 151:e9c9404e3d1f ClockSync

Pff partially working. No PID. When setting the audio clock on the bbb to 44098 the master and slave clock keep diverging instead of converging ...
author Giulio Moro <giuliomoro@yahoo.it>
date Tue, 22 Sep 2015 04:10:07 +0100
parents 3c3a1357657d
children 8d80eda512cd
line wrap: on
line source
/*
 * render.cpp
 *
 *  Created on: Oct 24, 2014
 *      Author: parallels
 */


#include <BeagleRT.h>
#include <Utilities.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;

// Current state for sound and music
int gMusicBufferPointer = 0;	  // 0 means start of buffer...
int gSoundBoomBufferPointer = -1; // -1 means don't play...
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(BeagleRTContext *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 = BeagleRT_createAuxiliaryTask(&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(BeagleRTContext *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;
			}

			// 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 = analogReadFrame(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(analogReadFrame(context, n, gInputTank1Angle),
									0, 1.0, M_PI, 0));
			setTank2CannonAngle(map(analogReadFrame(context, n, gInputTank2Angle),
									0, 1.0, M_PI, 0));
			nextGameFrame();

			// Check for collision and start sound accordingly
			if(gameStatusCollisionOccurred()) {
				gSoundBoomBufferPointer = 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
			analogWriteFrameOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
			analogWriteFrameOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
		}
		else {
			// Still not ready! Write 0 until something happens
			analogWriteFrameOnce(context, n, gOutputX, 0);
			analogWriteFrameOnce(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;
			analogWriteFrameOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0);
			analogWriteFrameOnce(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;
			analogWriteFrameOnce(context, n, gOutputPlayer1LED, val);
			analogWriteFrameOnce(context, n, gOutputPlayer2LED, val);
		}
		else if(gameStatusPlayer1Turn()) {
			analogWriteFrameOnce(context, n, gOutputPlayer1LED, 1.0);
			analogWriteFrameOnce(context, n, gOutputPlayer2LED, 0);
		}
		else {
			analogWriteFrameOnce(context, n, gOutputPlayer2LED, 1.0);
			analogWriteFrameOnce(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
			BeagleRT_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(BeagleRTContext *context, void *userData)
{
	// Clean up the game state
	cleanupGame();
}