comparison examples/10-Instruments/tank_wars/render.cpp @ 468:85cf9c0da052 prerelease

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