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