1 // for finding memory leaks in debug mode with Visual Studio
2 #if defined _DEBUG && defined _MSC_VER
3 #include <crtdbg.h>
4 #endif
5
6 #include <stdint.h>
7 #include <stdbool.h>
8 #include <math.h> // modf()
9 #ifndef _WIN32
10 #include <unistd.h> // usleep()
11 #endif
12 #include "../ft2_header.h"
13 #include "../ft2_events.h"
14 #include "../ft2_config.h"
15 #include "../ft2_audio.h"
16 #include "../ft2_gui.h"
17 #include "../ft2_midi.h"
18 #include "../ft2_bmp.h"
19 #include "../ft2_mouse.h"
20 #include "../ft2_video.h"
21 #include "../ft2_tables.h"
22 #include "../ft2_structs.h"
23 #include "ft2_scopes.h"
24 #include "ft2_scopedraw.h"
25
26 static volatile bool scopesUpdatingFlag, scopesDisplayingFlag;
27 static uint32_t scopeTimeLen, scopeTimeLenFrac;
28 static uint64_t timeNext64, timeNext64Frac;
29 static volatile scope_t scope[MAX_CHANNELS];
30 static SDL_Thread *scopeThread;
31
32 lastChInstr_t lastChInstr[MAX_CHANNELS]; // global
33
getSamplePosition(uint8_t ch)34 int32_t getSamplePosition(uint8_t ch)
35 {
36 if (ch >= song.numChannels)
37 return -1;
38
39 volatile scope_t *sc = &scope[ch];
40
41 // cache some stuff
42 volatile bool active = sc->active;
43 volatile int32_t position = sc->position;
44 volatile int32_t sampleEnd = sc->sampleEnd;
45
46 if (!active || sampleEnd == 0)
47 return -1;
48
49 if (position >= 0 && position < sampleEnd)
50 return position;
51
52 return -1; // not active or overflown
53 }
54
stopAllScopes(void)55 void stopAllScopes(void)
56 {
57 // wait for scopes to finish updating
58 while (scopesUpdatingFlag);
59
60 volatile scope_t *sc = scope;
61 for (int32_t i = 0; i < MAX_CHANNELS; i++, sc++)
62 sc->active = false;
63
64 // wait for scope displaying to be done (safety)
65 while (scopesDisplayingFlag);
66 }
67
68 // toggle mute
setChannel(int32_t chNr,bool on)69 static void setChannel(int32_t chNr, bool on)
70 {
71 channel_t *ch = &channel[chNr];
72
73 ch->channelOff = !on;
74 if (ch->channelOff)
75 {
76 ch->efx = 0;
77 ch->efxData = 0;
78 ch->realVol = 0;
79 ch->outVol = 0;
80 ch->oldVol = 0;
81 ch->fFinalVol = 0.0f;
82 ch->outPan = 128;
83 ch->oldPan = 128;
84 ch->finalPan = 128;
85 ch->status = IS_Vol;
86
87 ch->envSustainActive = false; // non-FT2 bug fix for stuck piano keys
88 }
89
90 scope[chNr].wasCleared = false;
91 }
92
drawScopeNumber(uint16_t scopeXOffs,uint16_t scopeYOffs,uint8_t chNr,bool outline)93 static void drawScopeNumber(uint16_t scopeXOffs, uint16_t scopeYOffs, uint8_t chNr, bool outline)
94 {
95 scopeXOffs++;
96 scopeYOffs++;
97 chNr++;
98
99 if (outline)
100 {
101 if (chNr < 10) // one digit?
102 {
103 charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + chNr);
104 }
105 else
106 {
107 charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);
108 charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);
109 }
110 }
111 else
112 {
113 if (chNr < 10) // one digit?
114 {
115 charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + chNr);
116 }
117 else
118 {
119 charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);
120 charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);
121 }
122 }
123 }
124
redrawScope(int32_t ch)125 static void redrawScope(int32_t ch)
126 {
127 int32_t i;
128
129 int32_t chansPerRow = (uint32_t)song.numChannels >> 1;
130 int32_t chanLookup = chansPerRow - 1;
131 const uint16_t *scopeLens = scopeLenTab[chanLookup];
132
133 // get x,y,len for scope according to channel (we must do it this way since 'len' can differ!)
134
135 uint16_t x = 2;
136 uint16_t y = 94;
137
138 uint16_t scopeLen = 0; // prevent compiler warning
139 for (i = 0; i < song.numChannels; i++)
140 {
141 scopeLen = scopeLens[i];
142
143 if (i == chansPerRow) // did we reach end of row?
144 {
145 // yes, go one row down
146 x = 2;
147 y += 39;
148 }
149
150 if (i == ch)
151 break;
152
153 // adjust position to next channel
154 x += scopeLen + 3;
155 }
156
157 drawFramework(x, y, scopeLen + 2, 38, FRAMEWORK_TYPE2);
158
159 // draw mute graphics if channel is muted
160 if (!editor.chnMode[i])
161 {
162 const uint16_t muteGfxLen = scopeMuteBMP_Widths[chanLookup];
163 const uint16_t muteGfxX = x + ((scopeLen - muteGfxLen) >> 1);
164
165 blitFastClipX(muteGfxX, y + 6, bmp.scopeMute+scopeMuteBMP_Offs[chanLookup], 162, scopeMuteBMP_Heights[chanLookup], muteGfxLen);
166
167 if (config.ptnChnNumbers)
168 drawScopeNumber(x + 1, y + 1, (uint8_t)i, true);
169 }
170
171 scope[ch].wasCleared = false;
172 }
173
refreshScopes(void)174 void refreshScopes(void)
175 {
176 for (int32_t i = 0; i < MAX_CHANNELS; i++)
177 scope[i].wasCleared = false;
178 }
179
channelMode(int32_t chn)180 static void channelMode(int32_t chn)
181 {
182 int32_t i;
183
184 assert(chn < song.numChannels);
185
186 bool m = mouse.leftButtonPressed && !mouse.rightButtonPressed;
187 bool m2 = mouse.rightButtonPressed && mouse.leftButtonPressed;
188
189 if (m2)
190 {
191 bool test = false;
192 for (i = 0; i < song.numChannels; i++)
193 {
194 if (i != chn && !editor.chnMode[i])
195 test = true;
196 }
197
198 if (test)
199 {
200 for (i = 0; i < song.numChannels; i++)
201 editor.chnMode[i] = true;
202 }
203 else
204 {
205 for (i = 0; i < song.numChannels; i++)
206 editor.chnMode[i] = (i == chn);
207 }
208 }
209 else if (m)
210 {
211 editor.chnMode[chn] ^= 1;
212 }
213 else
214 {
215 if (editor.chnMode[chn])
216 {
217 config.multiRecChn[chn] ^= 1;
218 }
219 else
220 {
221 config.multiRecChn[chn] = true;
222 editor.chnMode[chn] = true;
223 m = true;
224 }
225 }
226
227 for (i = 0; i < song.numChannels; i++)
228 setChannel(i, editor.chnMode[i]);
229
230 if (m2)
231 {
232 for (i = 0; i < song.numChannels; i++)
233 redrawScope(i);
234 }
235 else
236 {
237 redrawScope(chn);
238 }
239 }
240
testScopesMouseDown(void)241 bool testScopesMouseDown(void)
242 {
243 int32_t i;
244
245 if (!ui.scopesShown)
246 return false;
247
248 if (mouse.y >= 95 && mouse.y <= 169 && mouse.x >= 3 && mouse.x <= 288)
249 {
250 if (mouse.y > 130 && mouse.y < 134)
251 return true;
252
253 int32_t chansPerRow = (uint32_t)song.numChannels >> 1;
254 const uint16_t *scopeLens = scopeLenTab[chansPerRow-1];
255
256 // find out if we clicked inside a scope
257 uint16_t x = 3;
258 for (i = 0; i < chansPerRow; i++)
259 {
260 if (mouse.x >= x && mouse.x < x+scopeLens[i])
261 break;
262
263 x += scopeLens[i]+3;
264 }
265
266 if (i == chansPerRow)
267 return true; // scope framework was clicked instead
268
269 int32_t chanToToggle = i;
270 if (mouse.y >= 134) // second row of scopes?
271 chanToToggle += chansPerRow; // yes, increase lookup offset
272
273 channelMode(chanToToggle);
274 return true;
275 }
276
277 return false;
278 }
279
scopeTrigger(int32_t ch,const sample_t * s,int32_t playOffset)280 static void scopeTrigger(int32_t ch, const sample_t *s, int32_t playOffset)
281 {
282 volatile scope_t tempState;
283 volatile scope_t *sc = &scope[ch];
284
285 int32_t length = s->length;
286 int32_t loopStart = s->loopStart;
287 int32_t loopLength = s->loopLength;
288 int32_t loopEnd = s->loopStart + s->loopLength;
289 uint8_t loopType = GET_LOOPTYPE(s->flags);
290 bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
291
292 if (s->dataPtr == NULL || length < 1)
293 {
294 sc->active = false; // shut down scope (illegal parameters)
295 return;
296 }
297
298 tempState = *sc; // get copy of current scope state
299
300 if (loopLength < 1) // disable loop if loopLength is below 1
301 loopType = 0;
302
303 if (sample16Bit)
304 tempState.base16 = (const int16_t *)s->dataPtr;
305 else
306 tempState.base8 = s->dataPtr;
307
308 tempState.sample16Bit = sample16Bit;
309 tempState.loopType = loopType;
310 tempState.direction = 1; // forwards
311 tempState.sampleEnd = (loopType == LOOP_OFF) ? length : loopEnd;
312 tempState.loopStart = loopStart;
313 tempState.loopLength = loopLength;
314 tempState.position = playOffset;
315 tempState.positionFrac = 0;
316
317 // if position overflows (f.ex. through 9xx command), shut down scopes
318 if (tempState.position >= tempState.sampleEnd)
319 {
320 sc->active = false;
321 return;
322 }
323
324 tempState.active = true;
325
326 /* Update live scope now.
327 ** In theory it -can- be written to in the middle of a cached read,
328 ** then the read thread writes its own non-updated cached copy back and
329 ** the trigger never happens. So far I have never seen it happen,
330 ** so it's probably very rare. Yes, this is not good coding...
331 */
332
333 *sc = tempState; // set new scope state
334 }
335
updateScopes(void)336 static void updateScopes(void)
337 {
338 scopesUpdatingFlag = true;
339
340 volatile scope_t *sc = scope;
341 for (int32_t i = 0; i < song.numChannels; i++, sc++)
342 {
343 volatile scope_t s = *sc; // get copy of current scope state
344 if (!s.active)
345 continue; // scope is not active
346
347 // scope position update
348
349 s.positionFrac += s.delta;
350 const uint32_t wholeSamples = (uint32_t)(s.positionFrac >> SCOPE_FRAC_BITS);
351 s.positionFrac &= SCOPE_FRAC_MASK;
352
353 if (s.direction == 1)
354 s.position += wholeSamples; // forwards
355 else
356 s.position -= wholeSamples; // backwards
357
358 // handle loop wrapping or sample end
359 if (s.direction == -1 && s.position < s.loopStart) // sampling backwards (definitely pingpong loop)
360 {
361 s.direction = 1; // change direction to forwards
362
363 if (s.loopLength >= 2)
364 s.position = s.loopStart + ((s.loopStart - s.position - 1) % s.loopLength);
365 else
366 s.position = s.loopStart;
367
368 assert(s.position >= s.loopStart && s.position < s.sampleEnd);
369 }
370 else if (s.position >= s.sampleEnd)
371 {
372 uint32_t loopOverflowVal;
373
374 if (s.loopLength >= 2)
375 loopOverflowVal = (s.position - s.sampleEnd) % s.loopLength;
376 else
377 loopOverflowVal = 0;
378
379 if (s.loopType == LOOP_DISABLED)
380 {
381 s.active = false;
382 }
383 else if (s.loopType == LOOP_FORWARD)
384 {
385 s.position = s.loopStart + loopOverflowVal;
386 assert(s.position >= s.loopStart && s.position < s.sampleEnd);
387 }
388 else // pingpong loop
389 {
390 s.direction = -1; // change direction to backwards
391 s.position = (s.sampleEnd - 1) - loopOverflowVal;
392 assert(s.position >= s.loopStart && s.position < s.sampleEnd);
393 }
394 }
395 assert(s.position >= 0);
396
397 *sc = s; // set new scope state
398 }
399 scopesUpdatingFlag = false;
400 }
401
drawScopes(void)402 void drawScopes(void)
403 {
404 scopesDisplayingFlag = true;
405 int32_t chansPerRow = (uint32_t)song.numChannels >> 1;
406
407 const uint16_t *scopeLens = scopeLenTab[chansPerRow-1];
408 uint16_t scopeXOffs = 3;
409 uint16_t scopeYOffs = 95;
410 int16_t scopeLineY = 112;
411
412 for (int32_t i = 0; i < song.numChannels; i++)
413 {
414 // if we reached the last scope on the row, go to first scope on the next row
415 if (i == chansPerRow)
416 {
417 scopeXOffs = 3;
418 scopeYOffs = 134;
419 scopeLineY = 151;
420 }
421
422 const uint16_t scopeDrawLen = scopeLens[i];
423 if (!editor.chnMode[i]) // scope muted (mute graphics blit()'ed elsewhere)
424 {
425 scopeXOffs += scopeDrawLen+3; // align x to next scope
426 continue;
427 }
428
429 const scope_t s = scope[i]; // cache scope to lower thread race condition issues
430 if (s.active && s.volume > 0 && !audio.locked)
431 {
432 // scope is active
433 scope[i].wasCleared = false;
434
435 // clear scope background
436 clearRect(scopeXOffs, scopeYOffs, scopeDrawLen, SCOPE_HEIGHT);
437
438 // draw scope
439 bool linedScopesFlag = !!(config.specialFlags & LINED_SCOPES);
440 scopeDrawRoutineTable[(linedScopesFlag * 6) + (s.sample16Bit * 3) + s.loopType](&s, scopeXOffs, scopeLineY, scopeDrawLen);
441 }
442 else
443 {
444 // scope is inactive
445 volatile scope_t *sc = &scope[i];
446 if (!sc->wasCleared)
447 {
448 // clear scope background
449 clearRect(scopeXOffs, scopeYOffs, scopeDrawLen, SCOPE_HEIGHT);
450
451 // draw empty line
452 hLine(scopeXOffs, scopeLineY, scopeDrawLen, PAL_PATTEXT);
453
454 sc->wasCleared = true;
455 }
456 }
457
458 // draw channel numbering (if enabled)
459 if (config.ptnChnNumbers)
460 drawScopeNumber(scopeXOffs, scopeYOffs, (uint8_t)i, false);
461
462 // draw rec. symbol (if enabled)
463 if (config.multiRecChn[i])
464 blit(scopeXOffs + 1, scopeYOffs + 31, bmp.scopeRec, 13, 4);
465
466 scopeXOffs += scopeDrawLen+3; // align x to next scope
467 }
468
469 scopesDisplayingFlag = false;
470 }
471
drawScopeFramework(void)472 void drawScopeFramework(void)
473 {
474 drawFramework(0, 92, 291, 81, FRAMEWORK_TYPE1);
475 for (int32_t i = 0; i < song.numChannels; i++)
476 redrawScope(i);
477 }
478
handleScopesFromChQueue(chSyncData_t * chSyncData,uint8_t * scopeUpdateStatus)479 void handleScopesFromChQueue(chSyncData_t *chSyncData, uint8_t *scopeUpdateStatus)
480 {
481 volatile scope_t *sc = scope;
482 syncedChannel_t *ch = chSyncData->channels;
483 for (int32_t i = 0; i < song.numChannels; i++, sc++, ch++)
484 {
485 const uint8_t status = scopeUpdateStatus[i];
486
487 if (status & IS_Vol)
488 sc->volume = ch->scopeVolume;
489
490 if (status & IS_Period)
491 sc->delta = ch->scopeDelta;
492
493 if (status & IS_Trigger)
494 {
495 if (instr[ch->instrNum] != NULL)
496 {
497 scopeTrigger(i, &instr[ch->instrNum]->smp[ch->smpNum], ch->smpStartPos);
498
499 // set some stuff used by Smp. Ed. for sampling position line
500
501 if (ch->instrNum == 130 || (ch->instrNum == editor.curInstr && ch->smpNum == editor.curSmp))
502 editor.curSmpChannel = (uint8_t)i;
503
504 lastChInstr[i].instrNum = ch->instrNum;
505 lastChInstr[i].smpNum = ch->smpNum;
506 }
507 else
508 {
509 // empty instrument, shut down scope
510 scope[i].active = false;
511 lastChInstr[i].instrNum = 255;
512 lastChInstr[i].smpNum = 255;
513 }
514 }
515 }
516 }
517
scopeThreadFunc(void * ptr)518 static int32_t SDLCALL scopeThreadFunc(void *ptr)
519 {
520 // this is needed for scope stability (confirmed)
521 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
522
523 // set next frame time
524 timeNext64 = SDL_GetPerformanceCounter() + scopeTimeLen;
525 timeNext64Frac = scopeTimeLenFrac;
526
527 while (editor.programRunning)
528 {
529 editor.scopeThreadBusy = true;
530 updateScopes();
531 editor.scopeThreadBusy = false;
532
533 uint64_t time64 = SDL_GetPerformanceCounter();
534 if (time64 < timeNext64)
535 {
536 time64 = timeNext64 - time64;
537 if (time64 > INT32_MAX)
538 time64 = INT32_MAX;
539
540 const int32_t diff32 = (int32_t)time64;
541
542 // convert and round to microseconds
543 const int32_t time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
544
545 // delay until we have reached the next frame
546 if (time32 > 0)
547 usleep(time32);
548 }
549
550 // update next tick time
551 timeNext64 += scopeTimeLen;
552 timeNext64Frac += scopeTimeLenFrac;
553 if (timeNext64Frac > UINT32_MAX)
554 {
555 timeNext64Frac &= UINT32_MAX;
556 timeNext64++;
557 }
558 }
559
560 (void)ptr;
561 return true;
562 }
563
initScopes(void)564 bool initScopes(void)
565 {
566 double dInt;
567
568 // calculate scope time for performance counters and split into int/frac
569 double dFrac = modf(editor.dPerfFreq / SCOPE_HZ, &dInt);
570
571 // integer part
572 scopeTimeLen = (int32_t)dInt;
573
574 // fractional part (scaled to 0..2^32-1)
575 dFrac *= UINT32_MAX+1.0;
576 scopeTimeLenFrac = (uint32_t)dFrac;
577
578 scopeThread = SDL_CreateThread(scopeThreadFunc, NULL, NULL);
579 if (scopeThread == NULL)
580 {
581 showErrorMsgBox("Couldn't create channel scope thread!");
582 return false;
583 }
584
585 SDL_DetachThread(scopeThread);
586 return true;
587 }
588