robert@493
|
1 /*
|
robert@493
|
2 * render.cpp
|
robert@493
|
3 *
|
robert@493
|
4 * Created on: Oct 24, 2014
|
robert@493
|
5 * Author: parallels
|
robert@493
|
6 */
|
robert@493
|
7
|
robert@493
|
8
|
robert@493
|
9 #include <Bela.h>
|
robert@493
|
10 #include "game.h"
|
robert@493
|
11 #include <rtdk.h>
|
robert@493
|
12 #include <cmath>
|
robert@493
|
13 #include <cstdlib>
|
robert@493
|
14 #include <time.h>
|
robert@493
|
15
|
robert@493
|
16 int gAudioFramesPerMatrixFrame = 2; // Ratio in audio to matrix sample rate
|
robert@493
|
17
|
robert@493
|
18 int gInputTank1Angle = 0; // Inputs for the cannon angles
|
robert@493
|
19 int gInputTank2Angle = 1;
|
robert@493
|
20 int gInputLauncher = 2; // Input for launcher FSR
|
robert@493
|
21
|
robert@493
|
22 int gOutputX = 0; // Outputs for the scope
|
robert@493
|
23 int gOutputY = 1;
|
robert@493
|
24 int gOutputPlayer1LED = 2;
|
robert@493
|
25 int gOutputPlayer2LED = 3;
|
robert@493
|
26
|
robert@493
|
27 int gGameFramesPerSecond = 60; // How often the physics are updated
|
robert@493
|
28 int gGameFrameInterval; // ...and in frames
|
robert@493
|
29 int gSamplesUntilNextFrame; // Counter until next update
|
robert@493
|
30 int gSamplesSinceFinish = 0; // How long since somebody won?
|
robert@493
|
31 bool gGameShouldRestart = false;// Whether we need to reinitiliase the game
|
robert@493
|
32
|
robert@493
|
33 // Counter for overall number of samples that have elapsed
|
robert@493
|
34 unsigned int gSampleCounter = 0;
|
robert@493
|
35
|
robert@493
|
36 // 1st-order filter and peak detector for launcher input
|
robert@493
|
37 float gLauncherLastSample = 0;
|
robert@493
|
38 float gLauncherFilterPole = 0.8;
|
robert@493
|
39 float gLauncherPeakValue = 0;
|
robert@493
|
40 float gLauncherPeakFilterPole = 0.999;
|
robert@493
|
41 float gLauncherNoiseThreshold = 0.01;
|
robert@493
|
42 float gLauncherMinimumPeak = 0.1;
|
robert@493
|
43 bool gLauncherTriggered = false;
|
robert@493
|
44
|
robert@493
|
45 // Screen update rate; affects buffer size. Actual contents of buffer
|
robert@493
|
46 // may be smaller than this
|
robert@493
|
47 int gScreenWidth = 512;
|
robert@493
|
48 int gScreenHeight = 512;
|
robert@493
|
49 int gScreenFramesPerSecond = 25;
|
robert@493
|
50
|
robert@493
|
51 // Double buffer for rendering screen. Each is an interleaved buffer
|
robert@493
|
52 // of XY data.
|
robert@493
|
53 float *gScreenBuffer1, *gScreenBuffer2;
|
robert@493
|
54 float *gScreenBufferWrite, *gScreenBufferRead;
|
robert@493
|
55 int gScreenBufferMaxLength; // What is the total buffer allocated?
|
robert@493
|
56 int gScreenBufferReadLength; // How long is the read buffer?
|
robert@493
|
57 int gScreenBufferWriteLength; // How long is the write (next) buffer?
|
robert@493
|
58 int gScreenBufferReadPointer; // Where are we in the read buffer now?
|
robert@493
|
59 int gScreenBufferNextUpdateLocation; // When should we render the next buffer?
|
robert@493
|
60 bool gScreenNextBufferReady; // Is the next buffer ready to go?
|
robert@493
|
61
|
robert@493
|
62 // Auxiliary (low-priority) task for updating the screen
|
robert@493
|
63 AuxiliaryTask gScreenUpdateTask;
|
robert@493
|
64
|
robert@493
|
65 // Buffers for music and sound effects
|
robert@493
|
66 extern float *gMusicBuffer;
|
robert@493
|
67 extern int gMusicBufferLength;
|
robert@493
|
68 extern float *gSoundBoomBuffer;
|
robert@493
|
69 extern int gSoundBoomBufferLength;
|
robert@493
|
70 extern float *gSoundHitBuffer;
|
robert@493
|
71 extern int gSoundHitBufferLength;
|
robert@493
|
72
|
robert@493
|
73 // Current state for sound and music
|
robert@493
|
74 int gMusicBufferPointer = 0; // 0 means start of buffer...
|
robert@493
|
75 int gSoundBoomBufferPointer = -1; // -1 means don't play...
|
robert@493
|
76 int gSoundHitBufferPointer = -1;
|
robert@493
|
77 float gSoundProjectileOscillatorPhase = 0;
|
robert@493
|
78 float gSoundProjectileOscillatorGain = 0.2;
|
robert@493
|
79 float gOscillatorPhaseScaler = 0;
|
robert@493
|
80
|
robert@493
|
81 void screen_update();
|
robert@493
|
82
|
robert@493
|
83 // setup() is called once before the audio rendering starts.
|
robert@493
|
84 // Use it to perform any initialisation and allocation which is dependent
|
robert@493
|
85 // on the period size or sample rate.
|
robert@493
|
86 //
|
robert@493
|
87 // userData holds an opaque pointer to a data structure that was passed
|
robert@493
|
88 // in from the call to initAudio().
|
robert@493
|
89 //
|
robert@493
|
90 // Return true on success; returning false halts the program.
|
robert@493
|
91
|
robert@493
|
92 bool setup(BelaContext *context, void *userData)
|
robert@493
|
93 {
|
robert@493
|
94 srandom(time(NULL));
|
robert@493
|
95
|
chris@543
|
96 // Verify we are running with analog channels enabled
|
chris@543
|
97 if(context->analogFrames == 0 || context->analogOutChannels < 4) {
|
chris@543
|
98 rt_printf("Error: this example needs least 4 analog output channels\n");
|
robert@493
|
99 return false;
|
robert@493
|
100 }
|
robert@493
|
101
|
robert@493
|
102 // Initialise audio variables
|
robert@493
|
103 gAudioFramesPerMatrixFrame = context->audioFrames / context->analogFrames;
|
robert@493
|
104 gOscillatorPhaseScaler = 2.0 * M_PI / context->audioSampleRate;
|
robert@493
|
105
|
robert@493
|
106 // Initialise the screen buffers
|
robert@493
|
107 gScreenBufferMaxLength = 2 * context->analogSampleRate / gScreenFramesPerSecond;
|
robert@493
|
108 gScreenBuffer1 = new float[gScreenBufferMaxLength];
|
robert@493
|
109 gScreenBuffer2 = new float[gScreenBufferMaxLength];
|
robert@493
|
110 if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
|
robert@493
|
111 rt_printf("Error initialising screen buffers\n");
|
robert@493
|
112 return false;
|
robert@493
|
113 }
|
robert@493
|
114
|
robert@493
|
115 gScreenBufferRead = gScreenBuffer1;
|
robert@493
|
116 gScreenBufferWrite = gScreenBuffer2;
|
robert@493
|
117 gScreenBufferReadLength = gScreenBufferWriteLength = 0;
|
robert@493
|
118 gScreenBufferReadPointer = 0;
|
robert@493
|
119 gScreenBufferNextUpdateLocation = 0;
|
robert@493
|
120 gScreenNextBufferReady = false;
|
robert@493
|
121
|
robert@493
|
122 // Initialise the game
|
robert@493
|
123 setupGame(gScreenWidth, gScreenHeight);
|
robert@493
|
124 gGameFrameInterval = context->analogSampleRate / gGameFramesPerSecond;
|
robert@493
|
125 gSamplesUntilNextFrame = gGameFrameInterval;
|
robert@493
|
126
|
robert@493
|
127 // Initialise auxiliary tasks
|
robert@493
|
128 if((gScreenUpdateTask = Bela_createAuxiliaryTask(&screen_update, 90,
|
robert@493
|
129 "bela-screen-update")) == 0)
|
robert@493
|
130 return false;
|
robert@493
|
131
|
robert@493
|
132 return true;
|
robert@493
|
133 }
|
robert@493
|
134
|
robert@493
|
135 // Swap buffers on the screen
|
robert@493
|
136 void swap_buffers()
|
robert@493
|
137 {
|
robert@493
|
138 if(gScreenBufferRead == gScreenBuffer1) {
|
robert@493
|
139 gScreenBufferRead = gScreenBuffer2;
|
robert@493
|
140 gScreenBufferWrite = gScreenBuffer1;
|
robert@493
|
141 }
|
robert@493
|
142 else {
|
robert@493
|
143 gScreenBufferRead = gScreenBuffer1;
|
robert@493
|
144 gScreenBufferWrite = gScreenBuffer2;
|
robert@493
|
145 }
|
robert@493
|
146
|
robert@493
|
147 gScreenBufferReadLength = gScreenBufferWriteLength;
|
robert@493
|
148 gScreenBufferReadPointer = 0;
|
robert@493
|
149
|
robert@493
|
150 // Schedule next update for 3/4 of the way through the buffer
|
robert@493
|
151 gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
|
robert@493
|
152 gScreenNextBufferReady = false;
|
robert@493
|
153 }
|
robert@493
|
154
|
robert@493
|
155 // render() is called regularly at the highest priority by the audio engine.
|
robert@493
|
156 // Input and output are given from the audio hardware and the other
|
robert@493
|
157 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
|
robert@493
|
158 // will be 0.
|
robert@493
|
159
|
robert@493
|
160 void render(BelaContext *context, void *userData)
|
robert@493
|
161 {
|
robert@493
|
162 int audioIndex = 0;
|
robert@493
|
163
|
robert@493
|
164 for(unsigned int n = 0; n < context->analogFrames; n++) {
|
robert@493
|
165 for(int k = 0; k < gAudioFramesPerMatrixFrame; k++) {
|
robert@493
|
166 // Render music and sound
|
robert@493
|
167 float audioSample = 0;
|
robert@493
|
168
|
robert@493
|
169 // Music plays in a loop
|
robert@493
|
170 if(gMusicBuffer != 0 && gMusicBufferPointer >= 0) {
|
robert@493
|
171 audioSample += gMusicBuffer[gMusicBufferPointer++];
|
robert@493
|
172 if(gMusicBufferPointer >= gMusicBufferLength)
|
robert@493
|
173 gMusicBufferPointer = 0;
|
robert@493
|
174 }
|
robert@493
|
175
|
robert@493
|
176 // Sound effect plays until finished, then stops
|
robert@493
|
177 if(gSoundBoomBuffer != 0 && gSoundBoomBufferPointer >= 0) {
|
robert@493
|
178 audioSample += gSoundBoomBuffer[gSoundBoomBufferPointer++];
|
robert@493
|
179 if(gSoundBoomBufferPointer >= gSoundBoomBufferLength)
|
robert@493
|
180 gSoundBoomBufferPointer = -1;
|
robert@493
|
181 }
|
robert@493
|
182
|
robert@493
|
183 if(gSoundHitBuffer != 0 && gSoundHitBufferPointer >= 0) {
|
robert@493
|
184 audioSample += gSoundHitBuffer[gSoundHitBufferPointer++];
|
robert@493
|
185 if(gSoundHitBufferPointer >= gSoundHitBufferLength)
|
robert@493
|
186 gSoundHitBufferPointer = -1;
|
robert@493
|
187 }
|
robert@493
|
188
|
robert@493
|
189 // Oscillator plays to indicate projectile height
|
robert@493
|
190 if(gameStatusProjectileInMotion()) {
|
robert@493
|
191 audioSample += gSoundProjectileOscillatorGain * sinf(gSoundProjectileOscillatorPhase);
|
robert@493
|
192
|
robert@493
|
193 gSoundProjectileOscillatorPhase += gOscillatorPhaseScaler * constrain(map(gameStatusProjectileHeight(),
|
robert@493
|
194 1.0, 0, 300, 2000), 200, 6000);
|
robert@493
|
195 if(gSoundProjectileOscillatorPhase > 2.0 * M_PI)
|
robert@493
|
196 gSoundProjectileOscillatorPhase -= 2.0 * M_PI;
|
robert@493
|
197 }
|
robert@493
|
198
|
robert@493
|
199 context->audioOut[2*audioIndex] = context->audioOut[2*audioIndex + 1] = audioSample;
|
robert@493
|
200 audioIndex++;
|
robert@493
|
201 }
|
robert@493
|
202
|
robert@493
|
203 // First-order lowpass filter to remove noise on launch FSR
|
robert@493
|
204 float rawSample = analogRead(context, n, gInputLauncher);
|
robert@493
|
205 float launchSample = gLauncherFilterPole * gLauncherLastSample +
|
robert@493
|
206 (1.0f - gLauncherFilterPole) * rawSample;
|
robert@493
|
207 gLauncherLastSample = launchSample;
|
robert@493
|
208
|
robert@493
|
209 // Peak-detect on launch signal
|
robert@493
|
210 if(launchSample >= gLauncherPeakValue) {
|
robert@493
|
211 gLauncherPeakValue = launchSample;
|
robert@493
|
212 gLauncherTriggered = false;
|
robert@493
|
213 }
|
robert@493
|
214 else {
|
robert@493
|
215 if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
|
robert@493
|
216 // Detected a peak; is it big enough overall?
|
robert@493
|
217 if(gLauncherPeakValue >= gLauncherMinimumPeak) {
|
robert@493
|
218 gLauncherTriggered = true;
|
robert@493
|
219 // Peak detected-- fire!!
|
robert@493
|
220 // Set both cannon strengths but only one will
|
robert@493
|
221 // fire depending on whose turn it is
|
robert@493
|
222 float strength = map(gLauncherPeakValue,
|
robert@493
|
223 gLauncherMinimumPeak, 1.0,
|
robert@493
|
224 0.5f, 10.0f);
|
robert@493
|
225 setTank1CannonStrength(strength);
|
robert@493
|
226 setTank2CannonStrength(strength);
|
robert@493
|
227 fireProjectile();
|
robert@493
|
228 }
|
robert@493
|
229 }
|
robert@493
|
230
|
robert@493
|
231 gLauncherPeakValue *= gLauncherPeakFilterPole;
|
robert@493
|
232 }
|
robert@493
|
233
|
robert@493
|
234 if(--gSamplesUntilNextFrame <= 0) {
|
robert@493
|
235 // Update game physics and cannon angles
|
robert@493
|
236 gSamplesUntilNextFrame = gGameFrameInterval;
|
robert@493
|
237
|
robert@493
|
238 setTank1CannonAngle(map(analogRead(context, n, gInputTank1Angle),
|
robert@493
|
239 0, 1.0, M_PI, 0));
|
robert@493
|
240 setTank2CannonAngle(map(analogRead(context, n, gInputTank2Angle),
|
robert@493
|
241 0, 1.0, M_PI, 0));
|
robert@493
|
242 nextGameFrame();
|
robert@493
|
243
|
robert@493
|
244 // Check for collision and start sound accordingly
|
robert@493
|
245 if(gameStatusCollisionOccurred()) {
|
robert@493
|
246 gSoundBoomBufferPointer = 0;
|
robert@493
|
247 }
|
robert@493
|
248
|
robert@493
|
249 if(gameStatusTankHitOccurred()) {
|
robert@493
|
250 gSoundHitBufferPointer = 0;
|
robert@493
|
251 }
|
robert@493
|
252 }
|
robert@493
|
253
|
robert@493
|
254 if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
|
robert@493
|
255 && gScreenNextBufferReady) {
|
robert@493
|
256 // Got to the end; swap buffers
|
robert@493
|
257 swap_buffers();
|
robert@493
|
258 }
|
robert@493
|
259
|
robert@493
|
260 // Push current screen buffer to the matrix output
|
robert@493
|
261 if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
|
robert@493
|
262 float x = gScreenBufferRead[gScreenBufferReadPointer++];
|
robert@493
|
263 float y = gScreenBufferRead[gScreenBufferReadPointer++];
|
robert@493
|
264
|
robert@493
|
265 // Rescale screen coordinates to matrix ranges; invert the Y
|
robert@493
|
266 // coordinate to go from normal screen coordinates to scope coordinates
|
robert@493
|
267 analogWriteOnce(context, n, gOutputX, constrain(map(x, 0, gScreenWidth, 0, 1.0), 0, 1.0));
|
robert@493
|
268 analogWriteOnce(context, n, gOutputY, constrain(map(y, 0, gScreenHeight, 1.0, 0), 0, 1.0));
|
robert@493
|
269 }
|
robert@493
|
270 else {
|
robert@493
|
271 // Still not ready! Write 0 until something happens
|
robert@493
|
272 analogWriteOnce(context, n, gOutputX, 0);
|
robert@493
|
273 analogWriteOnce(context, n, gOutputY, 0);
|
robert@493
|
274 }
|
robert@493
|
275
|
robert@493
|
276 if(gameStatusWinner() != 0) {
|
robert@493
|
277 // Blink one LED to show who won
|
robert@493
|
278 // Blink both LEDs when projectile is in motion
|
robert@493
|
279 float val = (gSampleCounter % 4000 > 2000) ? 1.0 : 0;
|
robert@493
|
280 analogWriteOnce(context, n, gOutputPlayer1LED, gameStatusWinner() == 1 ? val : 0);
|
robert@493
|
281 analogWriteOnce(context, n, gOutputPlayer2LED, gameStatusWinner() == 2 ? val : 0);
|
robert@493
|
282
|
robert@493
|
283 // After 5 seconds, restart the game
|
robert@493
|
284 gSamplesSinceFinish++;
|
robert@493
|
285 if(gSamplesSinceFinish > 22050*5)
|
robert@493
|
286 gGameShouldRestart = true;
|
robert@493
|
287 }
|
robert@493
|
288 else if(gameStatusProjectileInMotion()) {
|
robert@493
|
289 // Blink both LEDs when projectile is in motion
|
robert@493
|
290 float val = (gSampleCounter % 2000 > 1000) ? 1.0 : 0;
|
robert@493
|
291 analogWriteOnce(context, n, gOutputPlayer1LED, val);
|
robert@493
|
292 analogWriteOnce(context, n, gOutputPlayer2LED, val);
|
robert@493
|
293 }
|
robert@493
|
294 else if(gameStatusPlayer1Turn()) {
|
robert@493
|
295 analogWriteOnce(context, n, gOutputPlayer1LED, 1.0);
|
robert@493
|
296 analogWriteOnce(context, n, gOutputPlayer2LED, 0);
|
robert@493
|
297 }
|
robert@493
|
298 else {
|
robert@493
|
299 analogWriteOnce(context, n, gOutputPlayer2LED, 1.0);
|
robert@493
|
300 analogWriteOnce(context, n, gOutputPlayer1LED, 0);
|
robert@493
|
301 }
|
robert@493
|
302
|
robert@493
|
303 // Check if we have reached the point where we should next update
|
robert@493
|
304 if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
|
robert@493
|
305 !gScreenNextBufferReady) {
|
robert@493
|
306 // Update the screen at lower priority than the audio thread
|
robert@493
|
307 Bela_scheduleAuxiliaryTask(gScreenUpdateTask);
|
robert@493
|
308 }
|
robert@493
|
309
|
robert@493
|
310 gSampleCounter++;
|
robert@493
|
311 }
|
robert@493
|
312 }
|
robert@493
|
313
|
robert@493
|
314 void screen_update()
|
robert@493
|
315 {
|
robert@493
|
316 // If we should restart, reinitialise the game
|
robert@493
|
317 if(gGameShouldRestart) {
|
robert@493
|
318 restartGame();
|
robert@493
|
319 gGameShouldRestart = false;
|
robert@493
|
320 gSamplesSinceFinish = 0;
|
robert@493
|
321 }
|
robert@493
|
322
|
robert@493
|
323 // Render the game based on the current state
|
robert@493
|
324 gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
|
robert@493
|
325
|
robert@493
|
326 // Flag it as ready to go
|
robert@493
|
327 gScreenNextBufferReady = true;
|
robert@493
|
328 }
|
robert@493
|
329
|
robert@493
|
330 // cleanup() is called once at the end, after the audio has stopped.
|
robert@493
|
331 // Release any resources that were allocated in setup().
|
robert@493
|
332
|
robert@493
|
333 void cleanup(BelaContext *context, void *userData)
|
robert@493
|
334 {
|
robert@493
|
335 // Clean up the game state
|
robert@493
|
336 cleanupGame();
|
robert@493
|
337 }
|