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 <stdio.h>
7 #include <stdint.h>
8 #include <stdbool.h>
9 #include <string.h>
10 #include <math.h> // modf()
11 #ifdef _WIN32
12 #define WIN32_MEAN_AND_LEAN
13 #include <windows.h>
14 #include <SDL2/SDL_syswm.h>
15 #endif
16 #include <stdint.h>
17 #include <stdbool.h>
18 #ifndef _WIN32
19 #include <unistd.h> // usleep()
20 #endif
21 #include <ctype.h> // tolower()
22 #include "pt2_header.h"
23 #include "pt2_keyboard.h"
24 #include "pt2_mouse.h"
25 #include "pt2_audio.h"
26 #include "pt2_helpers.h"
27 #include "pt2_textout.h"
28 #include "pt2_tables.h"
29 #include "pt2_module_loader.h"
30 #include "pt2_module_saver.h"
31 #include "pt2_sample_loader.h"
32 #include "pt2_sample_saver.h"
33 #include "pt2_pattern_viewer.h"
34 #include "pt2_sampler.h"
35 #include "pt2_diskop.h"
36 #include "pt2_visuals.h"
37 #include "pt2_scopes.h"
38 #include "pt2_edit.h"
39 #include "pt2_pat2smp.h"
40 #include "pt2_mod2wav.h"
41 #include "pt2_config.h"
42 #include "pt2_bmp.h"
43 #include "pt2_sampling.h"
44 #include "pt2_chordmaker.h"
45 
46 typedef struct sprite_t
47 {
48 	bool visible;
49 	int8_t pixelType;
50 	int16_t newX, newY, x, y, w, h;
51 	uint32_t colorKey, *refreshBuffer;
52 	const void *data;
53 } sprite_t;
54 
55 static uint32_t vuMetersBg[4 * (10 * 48)];
56 static uint64_t timeNext64, timeNext64Frac;
57 
58 sprite_t sprites[SPRITE_NUM]; // globalized
59 
60 static const uint16_t cursorPosTable[24] =
61 {
62 	 30,  54,  62,  70,  78,  86,
63 	102, 126, 134, 142, 150, 158,
64 	174, 198, 206, 214, 222, 230,
65 	246, 270, 278, 286, 294, 302
66 };
67 
68 void updateSongInfo1(void);
69 void updateSongInfo2(void);
70 void updateSampler(void);
71 void updatePatternData(void);
72 void updateMOD2WAVDialog(void);
73 
blit32(int32_t x,int32_t y,int32_t w,int32_t h,const uint32_t * src)74 void blit32(int32_t x, int32_t y, int32_t w, int32_t h, const uint32_t *src)
75 {
76 	const uint32_t *srcPtr = src;
77 	uint32_t *dstPtr = &video.frameBuffer[(y * SCREEN_W) + x];
78 
79 	for (int32_t yy = 0; yy < h; yy++)
80 	{
81 		memcpy(dstPtr, srcPtr, w * sizeof (int32_t));
82 
83 		srcPtr += w;
84 		dstPtr += SCREEN_W;
85 	}
86 }
87 
putPixel(int32_t x,int32_t y,const uint32_t pixelColor)88 void putPixel(int32_t x, int32_t y, const uint32_t pixelColor)
89 {
90 	video.frameBuffer[(y * SCREEN_W) + x] = pixelColor;
91 }
92 
hLine(int32_t x,int32_t y,int32_t w,const uint32_t pixelColor)93 void hLine(int32_t x, int32_t y, int32_t w, const uint32_t pixelColor)
94 {
95 	uint32_t *dstPtr = &video.frameBuffer[(y * SCREEN_W) + x];
96 	for (int32_t xx = 0; xx < w; xx++)
97 		dstPtr[xx] = pixelColor;
98 }
99 
vLine(int32_t x,int32_t y,int32_t h,const uint32_t pixelColor)100 void vLine(int32_t x, int32_t y, int32_t h, const uint32_t pixelColor)
101 {
102 	uint32_t *dstPtr = &video.frameBuffer[(y * SCREEN_W) + x];
103 	for (int32_t yy = 0; yy < h; yy++)
104 	{
105 		*dstPtr = pixelColor;
106 		dstPtr += SCREEN_W;
107 	}
108 }
109 
drawFramework1(int32_t x,int32_t y,int32_t w,int32_t h)110 void drawFramework1(int32_t x, int32_t y, int32_t w, int32_t h)
111 {
112 	hLine(x, y, w-1, video.palette[PAL_BORDER]);
113 	vLine(x, y+1, h-2, video.palette[PAL_BORDER]);
114 	hLine(x+1, y+h-1, w-1, video.palette[PAL_GENBKG2]);
115 	vLine(x+w-1, y+1, h-2, video.palette[PAL_GENBKG2]);
116 
117 	putPixel(x, y+h-1, video.palette[PAL_GENBKG]);
118 	putPixel(x+w-1, y, video.palette[PAL_GENBKG]);
119 
120 	fillRect(x+1, y+1, w-2, h-2, video.palette[PAL_GENBKG]);
121 }
122 
drawFramework2(int32_t x,int32_t y,int32_t w,int32_t h)123 void drawFramework2(int32_t x, int32_t y, int32_t w, int32_t h)
124 {
125 	hLine(x, y, w-1, video.palette[PAL_GENBKG2]);
126 	vLine(x, y+1, h-2, video.palette[PAL_GENBKG2]);
127 	hLine(x+1, y+h-1, w-1, video.palette[PAL_BORDER]);
128 	vLine(x+w-1, y+1, h-2, video.palette[PAL_BORDER]);
129 
130 	putPixel(x, y+h-1, video.palette[PAL_GENBKG]);
131 	putPixel(x+w-1, y, video.palette[PAL_GENBKG]);
132 
133 	fillRect(x+1, y+1, w-2, h-2, video.palette[PAL_BACKGRD]);
134 }
135 
fillRect(int32_t x,int32_t y,int32_t w,int32_t h,const uint32_t pixelColor)136 void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, const uint32_t pixelColor)
137 {
138 	uint32_t *dstPtr = &video.frameBuffer[(y * SCREEN_W) + x];
139 
140 	for (int32_t yy = 0; yy < h; yy++)
141 	{
142 		for (int32_t xx = 0; xx < w; xx++)
143 			dstPtr[xx] = pixelColor;
144 
145 		dstPtr += SCREEN_W;
146 	}
147 }
148 
drawButton(int32_t x,int32_t y,int32_t w,const char * text)149 void drawButton(int32_t x, int32_t y, int32_t w, const char *text)
150 {
151 	if (w < 2)
152 		return;
153 
154 	const int32_t textW = (int32_t)strlen(text) * (FONT_CHAR_W-1);
155 	const int32_t textX = x + (((w - 2) - textW) >> 1);
156 
157 	drawFramework1(x, y, w, 11);
158 	textOut2(textX, y+3, text);
159 }
160 
drawUpButton(int32_t x,int32_t y)161 void drawUpButton(int32_t x, int32_t y)
162 {
163 	drawFramework1(x, y, 11, 11);
164 	textOut2(x+1, y+2, ARROW_UP_STR);
165 }
166 
drawDownButton(int32_t x,int32_t y)167 void drawDownButton(int32_t x, int32_t y)
168 {
169 	drawFramework1(x, y, 11, 11);
170 	textOut2(x+1, y+2, ARROW_DOWN_STR);
171 }
172 
statusAllRight(void)173 void statusAllRight(void)
174 {
175 	setStatusMessage("ALL RIGHT", DO_CARRY);
176 }
177 
statusOutOfMemory(void)178 void statusOutOfMemory(void)
179 {
180 	displayErrorMsg("OUT OF MEMORY !!!");
181 }
182 
statusSampleIsEmpty(void)183 void statusSampleIsEmpty(void)
184 {
185 	displayErrorMsg("SAMPLE IS EMPTY");
186 }
187 
changeStatusText(const char * text)188 void changeStatusText(const char *text)
189 {
190 	fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
191 	textOut(88, 127, text, video.palette[PAL_GENTXT]);
192 }
193 
statusNotSampleZero(void)194 void statusNotSampleZero(void)
195 {
196 	/* This rather confusing error message actually means that
197 	** you can't load a sample to sample slot #0 (which isn't a
198 	** real sample slot).
199 	*/
200 	displayErrorMsg("NOT SAMPLE 0 !");
201 }
202 
setupPerfFreq(void)203 void setupPerfFreq(void)
204 {
205 	uint64_t perfFreq64;
206 	double dInt, dFrac;
207 
208 	perfFreq64 = SDL_GetPerformanceFrequency(); assert(perfFreq64 != 0);
209 	editor.dPerfFreq = (double)perfFreq64;
210 	editor.dPerfFreqMulMicro = 1000000.0 / editor.dPerfFreq;
211 
212 	// calculate vblank time for performance counters and split into int/frac
213 	dFrac = modf(editor.dPerfFreq / VBLANK_HZ, &dInt);
214 
215 	// integer part
216 	editor.vblankTimeLen = (int32_t)dInt;
217 
218 	// fractional part (scaled to 0..2^32-1)
219 	dFrac *= UINT32_MAX+1.0;
220 	editor.vblankTimeLenFrac = (uint32_t)dFrac;
221 }
222 
setupWaitVBL(void)223 void setupWaitVBL(void)
224 {
225 	// set next frame time
226 	timeNext64 = SDL_GetPerformanceCounter() + editor.vblankTimeLen;
227 	timeNext64Frac = editor.vblankTimeLenFrac;
228 }
229 
waitVBL(void)230 void waitVBL(void)
231 {
232 	// this routine almost never delays if we have 60Hz vsync, but it's still needed in some occasions
233 
234 	uint64_t time64 = SDL_GetPerformanceCounter();
235 	if (time64 < timeNext64)
236 	{
237 		time64 = timeNext64 - time64;
238 		if (time64 > UINT32_MAX)
239 			time64 = UINT32_MAX;
240 
241 		const uint32_t diff32 = (uint32_t)time64;
242 
243 		// convert to microseconds and round to integer
244 		const int32_t time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
245 
246 		// delay until we have reached the next frame
247 		if (time32 > 0)
248 			usleep(time32);
249 	}
250 
251 	// update next tick time
252 	timeNext64 += editor.vblankTimeLen;
253 	timeNext64Frac += editor.vblankTimeLenFrac;
254 	if (timeNext64Frac > 0xFFFFFFFF)
255 	{
256 		timeNext64Frac &= 0xFFFFFFFF;
257 		timeNext64++;
258 	}
259 }
260 
renderFrame(void)261 void renderFrame(void)
262 {
263 	updateMOD2WAVDialog(); // must be first to avoid flickering issues
264 
265 	updateSongInfo1(); // top left side of screen, when "disk op"/"pos ed" is hidden
266 	updateSongInfo2(); // two middle rows of screen, always visible
267 	updateEditOp();
268 	updatePatternData();
269 	updateDiskOp();
270 	updateSampler();
271 	updatePosEd();
272 	updateVisualizer();
273 	handleLastGUIObjectDown();
274 	drawSamplerLine();
275 	writeSampleMonitorWaveform();
276 }
277 
resetAllScreens(void)278 void resetAllScreens(void) // prepare GUI for "really quit?" dialog
279 {
280 	editor.mixFlag = false;
281 	editor.swapChannelFlag = false;
282 	ui.clearScreenShown = false;
283 
284 	ui.changingChordNote = false;
285 	ui.changingSmpResample = false;
286 	ui.changingSamplingNote = false;
287 	ui.changingDrumPadNote = false;
288 
289 	ui.pat2SmpDialogShown = false;
290 	ui.disablePosEd = false;
291 	ui.disableVisualizer = false;
292 
293 	if (ui.samplerScreenShown)
294 	{
295 		if (ui.samplingBoxShown)
296 		{
297 			ui.samplingBoxShown = false;
298 			removeSamplingBox();
299 		}
300 
301 		ui.samplerVolBoxShown = false;
302 		ui.samplerFiltersBoxShown = false;
303 		displaySample(); // removes volume/filter box
304 	}
305 
306 	if (ui.editTextFlag)
307 		exitGetTextLine(EDIT_TEXT_NO_UPDATE);
308 }
309 
removeAskDialog(void)310 void removeAskDialog(void)
311 {
312 	if (!ui.askScreenShown && !editor.isWAVRendering)
313 		displayMainScreen();
314 
315 	ui.disablePosEd = false;
316 	ui.disableVisualizer = false;
317 }
318 
renderAskDialog(void)319 void renderAskDialog(void)
320 {
321 	ui.disablePosEd = true;
322 	ui.disableVisualizer = true;
323 
324 	const uint32_t *srcPtr = ui.pat2SmpDialogShown ? pat2SmpDialogBMP : yesNoDialogBMP;
325 	blit32(160, 51, 104, 39, srcPtr);
326 }
327 
renderBigAskDialog(void)328 void renderBigAskDialog(void)
329 {
330 	ui.disablePosEd = true;
331 	ui.disableVisualizer = true;
332 
333 	blit32(120, 44, 200, 55, bigYesNoDialogBMP);
334 }
335 
showDownsampleAskDialog(void)336 void showDownsampleAskDialog(void)
337 {
338 	ui.askScreenShown = true;
339 	ui.askScreenType = ASK_LOAD_DOWNSAMPLE;
340 	pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
341 	setStatusMessage("PLEASE SELECT", NO_CARRY);
342 	renderBigAskDialog();
343 
344 	textOutTight(133, 49, "THE SAMPLE'S FREQUENCY IS", video.palette[PAL_BACKGRD]);
345 	textOutTight(154, 57, "HIGH (ABOVE 22KHZ).", video.palette[PAL_BACKGRD]);
346 	textOutTight(133, 65, "DO YOU WANT TO DOWNSAMPLE", video.palette[PAL_BACKGRD]);
347 	textOutTight(156, 73, "BEFORE LOADING IT?", video.palette[PAL_BACKGRD]);
348 }
349 
fillFromVuMetersBgBuffer(void)350 static void fillFromVuMetersBgBuffer(void)
351 {
352 	const uint32_t *srcPtr;
353 	uint32_t *dstPtr;
354 
355 	if (ui.samplerScreenShown || editor.isWAVRendering || editor.isSMPRendering)
356 		return;
357 
358 	srcPtr = vuMetersBg;
359 	dstPtr = &video.frameBuffer[(187 * SCREEN_W) + 55];
360 
361 	for (uint32_t i = 0; i < AMIGA_VOICES; i++)
362 	{
363 		for (uint32_t y = 0; y < 48; y++)
364 		{
365 			for (uint32_t x = 0; x < 10; x++)
366 				dstPtr[x] = srcPtr[x];
367 
368 			srcPtr += 10;
369 			dstPtr -= SCREEN_W;
370 		}
371 
372 		dstPtr += (SCREEN_W * 48) + 72;
373 	}
374 }
375 
fillToVuMetersBgBuffer(void)376 void fillToVuMetersBgBuffer(void)
377 {
378 	const uint32_t *srcPtr;
379 	uint32_t *dstPtr;
380 
381 	if (ui.samplerScreenShown || editor.isWAVRendering || editor.isSMPRendering)
382 		return;
383 
384 	srcPtr = &video.frameBuffer[(187 * SCREEN_W) + 55];
385 	dstPtr = vuMetersBg;
386 
387 	for (uint32_t i = 0; i < AMIGA_VOICES; i++)
388 	{
389 		for (uint32_t y = 0; y < 48; y++)
390 		{
391 			for (uint32_t x = 0; x < 10; x++)
392 				dstPtr[x] = srcPtr[x];
393 
394 			srcPtr -= SCREEN_W;
395 			dstPtr += 10;
396 		}
397 
398 		srcPtr += (SCREEN_W * 48) + 72;
399 	}
400 }
401 
renderVuMeters(void)402 void renderVuMeters(void)
403 {
404 	const uint32_t *srcPtr;
405 	uint32_t h, *dstPtr;
406 
407 	if (ui.samplerScreenShown || editor.isWAVRendering || editor.isSMPRendering)
408 		return;
409 
410 	fillToVuMetersBgBuffer();
411 
412 	dstPtr = &video.frameBuffer[(187 * SCREEN_W) + 55];
413 	for (uint32_t i = 0; i < AMIGA_VOICES; i++)
414 	{
415 		if (config.realVuMeters)
416 			h = editor.realVuMeterVolumes[i];
417 		else
418 			h = editor.vuMeterVolumes[i];
419 
420 		if (h > 48)
421 			h = 48;
422 
423 		srcPtr = vuMeterBMP;
424 		for (uint32_t y = 0; y < h; y++)
425 		{
426 			for (uint32_t x = 0; x < 10; x++)
427 				dstPtr[x] = srcPtr[x];
428 
429 			srcPtr += 10;
430 			dstPtr -= SCREEN_W;
431 		}
432 
433 		dstPtr += (SCREEN_W * h) + 72;
434 	}
435 }
436 
updateSongInfo1(void)437 void updateSongInfo1(void) // left side of screen, when Disk Op. is hidden
438 {
439 	moduleSample_t *currSample;
440 
441 	if (ui.diskOpScreenShown)
442 		return;
443 
444 	currSample = &song->samples[editor.currSample];
445 
446 	if (ui.updateSongPos)
447 	{
448 		ui.updateSongPos = false;
449 		printThreeDecimalsBg(72, 3, *editor.currPosDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
450 	}
451 
452 	if (ui.updateSongPattern)
453 	{
454 		ui.updateSongPattern = false;
455 		printTwoDecimalsBg(80, 14, *editor.currPatternDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
456 	}
457 
458 	if (ui.updateSongLength)
459 	{
460 		ui.updateSongLength = false;
461 		if (!editor.isWAVRendering)
462 			printThreeDecimalsBg(72, 25, *editor.currLengthDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
463 	}
464 
465 	if (ui.updateCurrSampleFineTune)
466 	{
467 		ui.updateCurrSampleFineTune = false;
468 
469 		if (!editor.isWAVRendering)
470 			textOutBg(80, 36, ftuneStrTab[currSample->fineTune & 0xF], video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
471 	}
472 
473 	if (ui.updateCurrSampleNum)
474 	{
475 		ui.updateCurrSampleNum = false;
476 		if (!editor.isWAVRendering)
477 		{
478 			printTwoHexBg(80, 47,
479 				editor.sampleZero ? 0 : ((*editor.currSampleDisp) + 1), video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
480 		}
481 	}
482 
483 	if (ui.updateCurrSampleVolume)
484 	{
485 		ui.updateCurrSampleVolume = false;
486 		if (!editor.isWAVRendering)
487 			printTwoHexBg(80, 58, *currSample->volumeDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
488 	}
489 
490 	if (ui.updateCurrSampleLength)
491 	{
492 		ui.updateCurrSampleLength = false;
493 		if (!editor.isWAVRendering)
494 			printFourHexBg(64, 69, *currSample->lengthDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
495 	}
496 
497 	if (ui.updateCurrSampleRepeat)
498 	{
499 		ui.updateCurrSampleRepeat = false;
500 		printFourHexBg(64, 80, *currSample->loopStartDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
501 	}
502 
503 	if (ui.updateCurrSampleReplen)
504 	{
505 		ui.updateCurrSampleReplen = false;
506 		printFourHexBg(64, 91, *currSample->loopLengthDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
507 	}
508 }
509 
updateSongInfo2(void)510 void updateSongInfo2(void) // two middle rows of screen, always present
511 {
512 	char tempChar;
513 	int32_t x, i;
514 	moduleSample_t *currSample;
515 
516 	if (ui.updateStatusText)
517 	{
518 		ui.updateStatusText = false;
519 
520 		// clear background
521 		fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
522 
523 		// render status text
524 		if (!editor.errorMsgActive && editor.blockMarkFlag && !ui.askScreenShown
525 			&& !ui.clearScreenShown && !editor.swapChannelFlag)
526 		{
527 			textOut(88, 127, "MARK BLOCK", video.palette[PAL_GENTXT]);
528 			charOut(192, 127, '-', video.palette[PAL_GENTXT]);
529 
530 			editor.blockToPos = song->currRow;
531 			if (editor.blockFromPos >= editor.blockToPos)
532 			{
533 				printTwoDecimals(176, 127, editor.blockToPos, video.palette[PAL_GENTXT]);
534 				printTwoDecimals(200, 127, editor.blockFromPos, video.palette[PAL_GENTXT]);
535 			}
536 			else
537 			{
538 				printTwoDecimals(176, 127, editor.blockFromPos, video.palette[PAL_GENTXT]);
539 				printTwoDecimals(200, 127, editor.blockToPos, video.palette[PAL_GENTXT]);
540 			}
541 		}
542 		else
543 		{
544 			textOut(88, 127, ui.statusMessage, video.palette[PAL_GENTXT]);
545 		}
546 	}
547 
548 	if (ui.updateSongBPM)
549 	{
550 		ui.updateSongBPM = false;
551 		if (!ui.samplerScreenShown)
552 			printThreeDecimalsBg(32, 123, song->currBPM, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
553 	}
554 
555 	if (ui.updateCurrPattText)
556 	{
557 		ui.updateCurrPattText = false;
558 		if (!ui.samplerScreenShown)
559 			printTwoDecimalsBg(8, 127, *editor.currEditPatternDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
560 	}
561 
562 	if (ui.updateTrackerFlags)
563 	{
564 		ui.updateTrackerFlags = false;
565 
566 		charOutBg(1, 113, ' ', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
567 		charOutBg(8, 113, ' ', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
568 
569 		if (editor.autoInsFlag)
570 		{
571 			charOut(0, 113, 'I', video.palette[PAL_GENTXT]);
572 
573 			// in Amiga PT, "auto insert" 9 means 0
574 			if (editor.autoInsSlot == 9)
575 				charOut(8, 113, '0', video.palette[PAL_GENTXT]);
576 			else
577 				charOut(8, 113, '1' + editor.autoInsSlot, video.palette[PAL_GENTXT]);
578 		}
579 
580 		charOutBg(1, 102, ' ', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
581 		if (editor.metroFlag)
582 			charOut(0, 102, 'M', video.palette[PAL_GENTXT]);
583 
584 		charOutBg(16, 102, ' ', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
585 		if (editor.multiFlag)
586 			charOut(16, 102, 'M', video.palette[PAL_GENTXT]);
587 
588 		charOutBg(24, 102, '0' + editor.editMoveAdd, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
589 
590 		charOutBg(311, 128, ' ', video.palette[PAL_GENBKG], video.palette[PAL_GENBKG]);
591 		if (editor.pNoteFlag == 1)
592 		{
593 			putPixel(314, 129, video.palette[PAL_GENTXT]);
594 			putPixel(315, 129, video.palette[PAL_GENTXT]);
595 		}
596 		else if (editor.pNoteFlag == 2)
597 		{
598 			putPixel(314, 128, video.palette[PAL_GENTXT]);
599 			putPixel(315, 128, video.palette[PAL_GENTXT]);
600 			putPixel(314, 130, video.palette[PAL_GENTXT]);
601 			putPixel(315, 130, video.palette[PAL_GENTXT]);
602 		}
603 	}
604 
605 	// playback timer
606 
607 	const uint32_t ms1024 = editor.musicTime64 >> 32; // milliseconds (scaled from 1000 to 1024)
608 
609 	uint32_t seconds = ms1024 >> 10;
610 	if (seconds <= 5999) // below 100 minutes (99:59 is max for the UI)
611 	{
612 		const uint32_t MI_TimeM = seconds / 60;
613 		const uint32_t MI_TimeS = seconds - (MI_TimeM * 60);
614 
615 		// xx:xx
616 		printTwoDecimalsBg(272, 102, MI_TimeM, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
617 		printTwoDecimalsBg(296, 102, MI_TimeS, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
618 	}
619 	else
620 	{
621 		// 99:59
622 		printTwoDecimalsBg(272, 102, 99, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
623 		printTwoDecimalsBg(296, 102, 59, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
624 	}
625 
626 	if (ui.updateSongName)
627 	{
628 		ui.updateSongName = false;
629 		for (x = 0; x < 20; x++)
630 		{
631 			tempChar = song->header.name[x];
632 			if (tempChar == '\0')
633 				tempChar = '_';
634 
635 			charOutBg(104 + (x * FONT_CHAR_W), 102, tempChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
636 		}
637 	}
638 
639 	if (ui.updateCurrSampleName)
640 	{
641 		ui.updateCurrSampleName = false;
642 		currSample = &song->samples[editor.currSample];
643 
644 		for (x = 0; x < 22; x++)
645 		{
646 			tempChar = currSample->text[x];
647 			if (tempChar == '\0')
648 				tempChar = '_';
649 
650 			charOutBg(104 + (x * FONT_CHAR_W), 113, tempChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
651 		}
652 	}
653 
654 	if (ui.updateSongSize)
655 	{
656 		ui.updateSongSize = false;
657 
658 		// clear background
659 		fillRect(264, 123, 6*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
660 
661 		// calculate module length
662 
663 		uint32_t totalSampleDataSize = 0;
664 		for (i = 0; i < MOD_SAMPLES; i++)
665 			totalSampleDataSize += song->samples[i].length;
666 
667 		uint32_t totalPatterns = 0;
668 		for (i = 0; i < MOD_ORDERS; i++)
669 		{
670 			if (song->header.order[i] > totalPatterns)
671 				totalPatterns = song->header.order[i];
672 		}
673 
674 		uint32_t moduleSize = 2108 + (totalPatterns * 1024) + totalSampleDataSize;
675 		if (moduleSize > 999999)
676 		{
677 			charOut(304, 123, 'K', video.palette[PAL_GENTXT]);
678 			printFourDecimals(272, 123, moduleSize / 1000, video.palette[PAL_GENTXT]);
679 		}
680 		else
681 		{
682 			printSixDecimals(264, 123, moduleSize, video.palette[PAL_GENTXT]);
683 		}
684 	}
685 
686 	if (ui.updateSongTiming)
687 	{
688 		ui.updateSongTiming = false;
689 		textOutBg(288, 130, (editor.timingMode == TEMPO_MODE_CIA) ? "CIA" : "VBL", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
690 	}
691 }
692 
updateCursorPos(void)693 void updateCursorPos(void)
694 {
695 	if (!ui.samplerScreenShown)
696 		setSpritePos(SPRITE_PATTERN_CURSOR, cursorPosTable[cursor.pos], 188);
697 }
698 
updateSampler(void)699 void updateSampler(void)
700 {
701 	int32_t tmpSampleOffset;
702 	moduleSample_t *s;
703 
704 	if (!ui.samplerScreenShown || ui.samplingBoxShown)
705 		return;
706 
707 	assert(editor.currSample >= 0 && editor.currSample <= 30);
708 	s = &song->samples[editor.currSample];
709 
710 	// update 9xx offset
711 	if (mouse.y >= 138 && mouse.y <= 201 && mouse.x >= 3 && mouse.x <= 316)
712 	{
713 		if (!ui.samplerVolBoxShown && !ui.samplerFiltersBoxShown && s->length > 0)
714 		{
715 			tmpSampleOffset = (scr2SmpPos(mouse.x-3) + (1 << 7)) >> 8; // rounded
716 			tmpSampleOffset = 0x900 + CLAMP(tmpSampleOffset, 0x00, 0xFF);
717 
718 			if (tmpSampleOffset != ui.lastSampleOffset)
719 			{
720 				ui.lastSampleOffset = (uint16_t)tmpSampleOffset;
721 				ui.update9xxPos = true;
722 			}
723 		}
724 	}
725 
726 	// display 9xx offset
727 	if (ui.update9xxPos)
728 	{
729 		ui.update9xxPos = false;
730 		printThreeHexBg(288, 247, ui.lastSampleOffset, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
731 	}
732 
733 	if (ui.updateResampleNote)
734 	{
735 		ui.updateResampleNote = false;
736 
737 		// show resample note
738 		if (ui.changingSmpResample)
739 		{
740 			textOutBg(288, 236, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
741 		}
742 		else
743 		{
744 			assert(editor.resampleNote < 36);
745 			textOutBg(288, 236,
746 				config.accidental ? noteNames2[2+editor.resampleNote] : noteNames1[2+editor.resampleNote],
747 				video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
748 		}
749 	}
750 
751 	if (ui.samplerVolBoxShown)
752 	{
753 		if (ui.updateVolFromText)
754 		{
755 			ui.updateVolFromText = false;
756 			printThreeDecimalsBg(176, 157, *editor.vol1Disp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
757 		}
758 
759 		if (ui.updateVolToText)
760 		{
761 			ui.updateVolToText = false;
762 			printThreeDecimalsBg(176, 168, *editor.vol2Disp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
763 		}
764 	}
765 	else if (ui.samplerFiltersBoxShown)
766 	{
767 		if (ui.updateLPText)
768 		{
769 			ui.updateLPText = false;
770 			printFourDecimalsBg(168, 157, *editor.lpCutOffDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
771 		}
772 
773 		if (ui.updateHPText)
774 		{
775 			ui.updateHPText = false;
776 			printFourDecimalsBg(168, 168, *editor.hpCutOffDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
777 		}
778 
779 		if (ui.updateNormFlag)
780 		{
781 			ui.updateNormFlag = false;
782 
783 			if (editor.normalizeFiltersFlag)
784 				textOutBg(208, 179, "YES", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
785 			else
786 				textOutBg(208, 179, "NO ", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
787 		}
788 	}
789 }
790 
showVolFromSlider(void)791 void showVolFromSlider(void)
792 {
793 	uint32_t *dstPtr, pixel, bgPixel, sliderStart, sliderEnd;
794 
795 	sliderStart = ((editor.vol1 * 3) + 5) / 10;
796 	sliderEnd  = sliderStart + 4;
797 	pixel = video.palette[PAL_QADSCP];
798 	bgPixel = video.palette[PAL_BACKGRD];
799 	dstPtr = &video.frameBuffer[(158 * SCREEN_W) + 105];
800 
801 	for (uint32_t y = 0; y < 3; y++)
802 	{
803 		for (uint32_t x = 0; x < 65; x++)
804 		{
805 			if (x >= sliderStart && x <= sliderEnd)
806 				dstPtr[x] = pixel;
807 			else
808 				dstPtr[x] = bgPixel;
809 		}
810 
811 		dstPtr += SCREEN_W;
812 	}
813 }
814 
showVolToSlider(void)815 void showVolToSlider(void)
816 {
817 	uint32_t *dstPtr, pixel, bgPixel, sliderStart, sliderEnd;
818 
819 	sliderStart = ((editor.vol2 * 3) + 5) / 10;
820 	sliderEnd = sliderStart + 4;
821 	pixel = video.palette[PAL_QADSCP];
822 	bgPixel = video.palette[PAL_BACKGRD];
823 	dstPtr = &video.frameBuffer[(169 * SCREEN_W) + 105];
824 
825 	for (uint32_t y = 0; y < 3; y++)
826 	{
827 		for (uint32_t x = 0; x < 65; x++)
828 		{
829 			if (x >= sliderStart && x <= sliderEnd)
830 				dstPtr[x] = pixel;
831 			else
832 				dstPtr[x] = bgPixel;
833 		}
834 
835 		dstPtr += SCREEN_W;
836 	}
837 }
838 
renderSamplerVolBox(void)839 void renderSamplerVolBox(void)
840 {
841 	blit32(72, 154, 136, 33, samplerVolumeBMP);
842 
843 	ui.updateVolFromText = true;
844 	ui.updateVolToText = true;
845 	showVolFromSlider();
846 	showVolToSlider();
847 
848 	// hide loop sprites
849 	hideSprite(SPRITE_LOOP_PIN_LEFT);
850 	hideSprite(SPRITE_LOOP_PIN_RIGHT);
851 }
852 
removeSamplerVolBox(void)853 void removeSamplerVolBox(void)
854 {
855 	displaySample();
856 }
857 
renderSamplerFiltersBox(void)858 void renderSamplerFiltersBox(void)
859 {
860 	blit32(65, 154, 186, 33, samplerFiltersBMP);
861 
862 	textOut(200, 157, "HZ", video.palette[PAL_GENTXT]);
863 	textOut(200, 168, "HZ", video.palette[PAL_GENTXT]);
864 
865 	ui.updateLPText = true;
866 	ui.updateHPText = true;
867 	ui.updateNormFlag = true;
868 
869 	// hide loop sprites
870 	hideSprite(SPRITE_LOOP_PIN_LEFT);
871 	hideSprite(SPRITE_LOOP_PIN_RIGHT);
872 }
873 
removeSamplerFiltersBox(void)874 void removeSamplerFiltersBox(void)
875 {
876 	displaySample();
877 }
878 
updatePosEd(void)879 void updatePosEd(void)
880 {
881 	if (!ui.posEdScreenShown || !ui.updatePosEd)
882 		return;
883 
884 	ui.updatePosEd = false;
885 
886 	if (!ui.disablePosEd)
887 	{
888 		int32_t posEdPosition = song->currOrder;
889 		if (posEdPosition > song->header.numOrders-1)
890 			posEdPosition = song->header.numOrders-1;
891 
892 		// top five
893 		for (int32_t y = 0; y < 5; y++)
894 		{
895 			if (posEdPosition-(5-y) >= 0)
896 			{
897 				printThreeDecimalsBg(128, 23+(y*6), posEdPosition-(5-y), video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
898 				printTwoDecimalsBg(160, 23+(y*6), song->header.order[posEdPosition-(5-y)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
899 			}
900 			else
901 			{
902 				fillRect(128, 23+(y*6), 22*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_BACKGRD]);
903 			}
904 		}
905 
906 		// middle
907 		printThreeDecimalsBg(128, 53, posEdPosition, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
908 		printTwoDecimalsBg(160, 53, *editor.currPosEdPattDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
909 
910 		// bottom six
911 		for (int32_t y = 0; y < 6; y++)
912 		{
913 			if (posEdPosition+y < song->header.numOrders-1)
914 			{
915 				printThreeDecimalsBg(128, 59+(y*6), posEdPosition+(y+1), video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
916 				printTwoDecimalsBg(160, 59+(y*6), song->header.order[posEdPosition+(y+1)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
917 			}
918 			else
919 			{
920 				fillRect(128, 59+(y*6), 22*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_BACKGRD]);
921 			}
922 		}
923 
924 		// kludge to fix bottom part of text edit marker in pos ed
925 		if (ui.editTextFlag && ui.editObject == PTB_PE_PATT)
926 			renderTextEditMarker();
927 	}
928 }
929 
renderPosEdScreen(void)930 void renderPosEdScreen(void)
931 {
932 	blit32(120, 0, 200, 99, posEdBMP);
933 }
934 
renderMuteButtons(void)935 void renderMuteButtons(void)
936 {
937 	const uint32_t *srcPtr;
938 	uint32_t *dstPtr, srcPitch;
939 
940 	if (ui.diskOpScreenShown || ui.posEdScreenShown)
941 		return;
942 
943 	dstPtr = &video.frameBuffer[(3 * SCREEN_W) + 310];
944 	for (uint32_t i = 0; i < AMIGA_VOICES; i++)
945 	{
946 		if (editor.muted[i])
947 		{
948 			srcPtr = &muteButtonsBMP[i * (6 * 7)];
949 			srcPitch = 7;
950 		}
951 		else
952 		{
953 			srcPtr = &trackerFrameBMP[((3 + (i * 11)) * SCREEN_W) + 310];
954 			srcPitch = SCREEN_W;
955 		}
956 
957 		for (uint32_t y = 0; y < 6; y++)
958 		{
959 			for (uint32_t x = 0; x < 7; x++)
960 				dstPtr[x] = srcPtr[x];
961 
962 			srcPtr += srcPitch;
963 			dstPtr += SCREEN_W;
964 		}
965 
966 		dstPtr += SCREEN_W * 5;
967 	}
968 }
969 
renderClearScreen(void)970 void renderClearScreen(void)
971 {
972 	ui.disablePosEd = true;
973 	ui.disableVisualizer = true;
974 
975 	blit32(160, 51, 104, 39, clearDialogBMP);
976 }
977 
removeClearScreen(void)978 void removeClearScreen(void)
979 {
980 	displayMainScreen();
981 
982 	ui.disablePosEd = false;
983 	ui.disableVisualizer = false;
984 }
985 
updateCurrSample(void)986 void updateCurrSample(void)
987 {
988 	ui.updateCurrSampleName = true;
989 	ui.updateSongSize = true;
990 
991 	if (!ui.diskOpScreenShown)
992 	{
993 		ui.updateCurrSampleFineTune = true;
994 		ui.updateCurrSampleNum = true;
995 		ui.updateCurrSampleVolume = true;
996 		ui.updateCurrSampleLength = true;
997 		ui.updateCurrSampleRepeat = true;
998 		ui.updateCurrSampleReplen = true;
999 	}
1000 
1001 	if (ui.samplerScreenShown)
1002 		redrawSample();
1003 
1004 	updateSamplePos();
1005 	recalcChordLength();
1006 
1007 	sampler.tmpLoopStart = 0;
1008 	sampler.tmpLoopLength = 0;
1009 }
1010 
updatePatternData(void)1011 void updatePatternData(void)
1012 {
1013 	if (ui.updatePatternData)
1014 	{
1015 		ui.updatePatternData = false;
1016 		if (!ui.samplerScreenShown)
1017 			redrawPattern();
1018 	}
1019 }
1020 
removeTextEditMarker(void)1021 void removeTextEditMarker(void)
1022 {
1023 	if (!ui.editTextFlag)
1024 		return;
1025 
1026 	if (ui.editObject == PTB_PE_PATT)
1027 	{
1028 		// position editor text editing
1029 		hLine(ui.lineCurX - 4, ui.lineCurY - 1, 7, video.palette[PAL_GENBKG2]);
1030 
1031 		// no need to clear the second row of pixels
1032 
1033 		ui.updatePosEd = true;
1034 	}
1035 	else
1036 	{
1037 		// all others
1038 		fillRect(ui.lineCurX - 4, ui.lineCurY - 1, 7, 2, video.palette[PAL_GENBKG]);
1039 	}
1040 }
1041 
renderTextEditMarker(void)1042 void renderTextEditMarker(void)
1043 {
1044 	if (!ui.editTextFlag)
1045 		return;
1046 
1047 	fillRect(ui.lineCurX - 4, ui.lineCurY - 1, 7, 2, video.palette[PAL_TEXTMARK]);
1048 }
1049 
sendMouseButtonUpEvent(uint8_t button)1050 static void sendMouseButtonUpEvent(uint8_t button)
1051 {
1052 	SDL_Event event;
1053 
1054 	memset(&event, 0, sizeof (event));
1055 
1056 	event.type = SDL_MOUSEBUTTONUP;
1057 	event.button.button = button;
1058 
1059 	SDL_PushEvent(&event);
1060 }
1061 
handleLastGUIObjectDown(void)1062 void handleLastGUIObjectDown(void)
1063 {
1064 	bool testMouseButtonRelease = false;
1065 
1066 	if (ui.sampleMarkingPos >= 0)
1067 	{
1068 		samplerSamplePressed(MOUSE_BUTTON_HELD);
1069 		testMouseButtonRelease = true;
1070 	}
1071 
1072 	if (ui.forceSampleDrag)
1073 	{
1074 		samplerBarPressed(MOUSE_BUTTON_HELD);
1075 		testMouseButtonRelease = true;
1076 	}
1077 
1078 	if (ui.forceSampleEdit)
1079 	{
1080 		samplerEditSample(MOUSE_BUTTON_HELD);
1081 		testMouseButtonRelease = true;
1082 	}
1083 
1084 	if (ui.forceVolDrag)
1085 	{
1086 		volBoxBarPressed(MOUSE_BUTTON_HELD);
1087 		testMouseButtonRelease = true;
1088 	}
1089 
1090 	/* Hack to send "mouse button up" events if we released the mouse button(s)
1091 	** outside of the window...
1092 	*/
1093 	if (testMouseButtonRelease)
1094 	{
1095 		if (mouse.x < 0 || mouse.x >= SCREEN_W || mouse.y < 0 || mouse.y >= SCREEN_H)
1096 		{
1097 			if (mouse.leftButtonPressed && !(mouse.buttonState & SDL_BUTTON_LMASK))
1098 				sendMouseButtonUpEvent(SDL_BUTTON_LEFT);
1099 
1100 			if (mouse.rightButtonPressed && !(mouse.buttonState & SDL_BUTTON_RMASK))
1101 				sendMouseButtonUpEvent(SDL_BUTTON_RIGHT);
1102 		}
1103 	}
1104 }
1105 
updateVisualizer(void)1106 void updateVisualizer(void)
1107 {
1108 	if (ui.disableVisualizer || ui.diskOpScreenShown ||
1109 		ui.posEdScreenShown  || ui.editOpScreenShown ||
1110 		ui.aboutScreenShown  || ui.askScreenShown    ||
1111 		editor.isWAVRendering || ui.samplingBoxShown)
1112 	{
1113 		return;
1114 	}
1115 
1116 	if (ui.visualizerMode == VISUAL_SPECTRUM)
1117 	{
1118 		// spectrum analyzer
1119 
1120 		uint32_t *dstPtr = &video.frameBuffer[(59 * SCREEN_W) + 129];
1121 		for (uint32_t i = 0; i < SPECTRUM_BAR_NUM; i++)
1122 		{
1123 			const uint32_t *srcPtr = analyzerColorsRGB24;
1124 			uint32_t pixel = video.palette[PAL_GENBKG];
1125 
1126 			int32_t tmpVol = editor.spectrumVolumes[i];
1127 			if (tmpVol > SPECTRUM_BAR_HEIGHT)
1128 				tmpVol = SPECTRUM_BAR_HEIGHT;
1129 
1130 			for (int32_t y = SPECTRUM_BAR_HEIGHT-1; y >= 0; y--)
1131 			{
1132 				if (y < tmpVol)
1133 					pixel = srcPtr[y];
1134 
1135 				for (uint32_t x = 0; x < SPECTRUM_BAR_WIDTH; x++)
1136 					dstPtr[x] = pixel;
1137 
1138 				dstPtr += SCREEN_W;
1139 			}
1140 
1141 			dstPtr -= (SCREEN_W * SPECTRUM_BAR_HEIGHT) - (SPECTRUM_BAR_WIDTH + 2);
1142 		}
1143 	}
1144 	else
1145 	{
1146 		drawScopes();
1147 	}
1148 }
1149 
renderQuadrascopeBg(void)1150 void renderQuadrascopeBg(void)
1151 {
1152 	const uint32_t *srcPtr;
1153 	uint32_t *dstPtr;
1154 
1155 	srcPtr = &trackerFrameBMP[(44 * SCREEN_W) + 120];
1156 	dstPtr = &video.frameBuffer[(44 * SCREEN_W) + 120];
1157 
1158 	for (uint32_t y = 0; y < 55; y++)
1159 	{
1160 		memcpy(dstPtr, srcPtr, 200 * sizeof (int32_t));
1161 
1162 		srcPtr += SCREEN_W;
1163 		dstPtr += SCREEN_W;
1164 	}
1165 
1166 	for (int32_t i = 0; i < AMIGA_VOICES; i++)
1167 		scope[i].emptyScopeDrawn = false;
1168 }
1169 
renderSpectrumAnalyzerBg(void)1170 void renderSpectrumAnalyzerBg(void)
1171 {
1172 	blit32(120, 44, 200, 55, spectrumVisualsBMP);
1173 }
1174 
renderAboutScreen(void)1175 void renderAboutScreen(void)
1176 {
1177 	char verString[16];
1178 	uint32_t verStringX;
1179 
1180 	if (!ui.aboutScreenShown || ui.diskOpScreenShown || ui.posEdScreenShown || ui.editOpScreenShown)
1181 		return;
1182 
1183 	blit32(120, 44, 200, 55, aboutScreenBMP);
1184 
1185 	// draw version string
1186 
1187 	sprintf(verString, "v%s", PROG_VER_STR);
1188 	verStringX = 260 + (((63 - ((uint32_t)strlen(verString) * (FONT_CHAR_W - 1))) + 1) / 2);
1189 	textOutTight(verStringX, 67, verString, video.palette[PAL_GENBKG2]);
1190 }
1191 
renderEditOpMode(void)1192 void renderEditOpMode(void)
1193 {
1194 	const uint32_t *srcPtr;
1195 
1196 	// select what character box to render
1197 
1198 	switch (ui.editOpScreen)
1199 	{
1200 		default:
1201 		case 0:
1202 			srcPtr = &editOpModeCharsBMP[editor.sampleAllFlag ? EDOP_MODE_BMP_A_OFS : EDOP_MODE_BMP_S_OFS];
1203 		break;
1204 
1205 		case 1:
1206 		{
1207 			     if (editor.trackPattFlag == 0) srcPtr = &editOpModeCharsBMP[EDOP_MODE_BMP_T_OFS];
1208 			else if (editor.trackPattFlag == 1) srcPtr = &editOpModeCharsBMP[EDOP_MODE_BMP_P_OFS];
1209 			else srcPtr = &editOpModeCharsBMP[EDOP_MODE_BMP_S_OFS];
1210 		}
1211 		break;
1212 
1213 		case 2:
1214 			srcPtr = &editOpModeCharsBMP[editor.halfClipFlag ? EDOP_MODE_BMP_C_OFS : EDOP_MODE_BMP_H_OFS];
1215 		break;
1216 
1217 		case 3:
1218 			srcPtr = (editor.newOldFlag == 0) ? &editOpModeCharsBMP[EDOP_MODE_BMP_N_OFS] : &editOpModeCharsBMP[EDOP_MODE_BMP_O_OFS];
1219 		break;
1220 	}
1221 
1222 	// render it...
1223 	blit32(310, 47, 7, 6, srcPtr);
1224 }
1225 
renderEditOpScreen(void)1226 void renderEditOpScreen(void)
1227 {
1228 	const uint32_t *srcPtr;
1229 
1230 	// select which graphics to render
1231 	switch (ui.editOpScreen)
1232 	{
1233 		default:
1234 		case 0: srcPtr = editOpScreen1BMP; break;
1235 		case 1: srcPtr = editOpScreen2BMP; break;
1236 		case 2: srcPtr = editOpScreen3BMP; break;
1237 		case 3: srcPtr = editOpScreen4BMP; break;
1238 	}
1239 
1240 	blit32(120, 44, 200, 55, srcPtr);
1241 
1242 	renderEditOpMode();
1243 
1244 	// render text and content
1245 	if (ui.editOpScreen == 0)
1246 	{
1247 		textOut(128, 47, "  TRACK      PATTERN  ", video.palette[PAL_GENTXT]);
1248 	}
1249 	else if (ui.editOpScreen == 1)
1250 	{
1251 		textOut(128, 47, "  RECORD     SAMPLES  ", video.palette[PAL_GENTXT]);
1252 
1253 		ui.updateRecordText = true;
1254 		ui.updateQuantizeText = true;
1255 		ui.updateMetro1Text = true;
1256 		ui.updateMetro2Text = true;
1257 		ui.updateFromText = true;
1258 		ui.updateKeysText = true;
1259 		ui.updateToText = true;
1260 	}
1261 	else if (ui.editOpScreen == 2)
1262 	{
1263 		textOut(128, 47, "    SAMPLE EDITOR     ", video.palette[PAL_GENTXT]);
1264 		charOut(272, 91, '%', video.palette[PAL_GENTXT]); // for Volume text
1265 
1266 		ui.updatePosText = true;
1267 		ui.updateModText = true;
1268 		ui.updateVolText = true;
1269 	}
1270 	else if (ui.editOpScreen == 3)
1271 	{
1272 		textOut(128, 47, " SAMPLE CHORD EDITOR  ", video.palette[PAL_GENTXT]);
1273 
1274 		ui.updateLengthText = true;
1275 		ui.updateNote1Text = true;
1276 		ui.updateNote2Text = true;
1277 		ui.updateNote3Text = true;
1278 		ui.updateNote4Text = true;
1279 	}
1280 }
1281 
renderMOD2WAVDialog(void)1282 void renderMOD2WAVDialog(void)
1283 {
1284 	blit32(64, 27, 192, 48, mod2wavBMP);
1285 }
1286 
updateMOD2WAVDialog(void)1287 void updateMOD2WAVDialog(void)
1288 {
1289 	if (!ui.updateMod2WavDialog)
1290 		return;
1291 
1292 	ui.updateMod2WavDialog = false;
1293 
1294 	if (editor.isWAVRendering)
1295 	{
1296 		if (ui.mod2WavFinished)
1297 		{
1298 			ui.mod2WavFinished = false;
1299 
1300 			resetSong();
1301 			pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
1302 
1303 			if (editor.abortMod2Wav)
1304 			{
1305 				displayErrorMsg("MOD2WAV ABORTED !");
1306 			}
1307 			else
1308 			{
1309 				displayMsg("MOD RENDERED !");
1310 				setMsgPointer();
1311 			}
1312 
1313 			editor.isWAVRendering = false;
1314 			modSetTempo(song->currBPM, true); // update BPM with normal audio output rate
1315 			displayMainScreen();
1316 		}
1317 		else
1318 		{
1319 			if (song->rowsInTotal == 0)
1320 				return;
1321 
1322 			// render progress bar
1323 
1324 			int32_t percent = (song->rowsCounter * 100) / song->rowsInTotal;
1325 			if (percent > 100)
1326 				percent = 100;
1327 
1328 			// foreground (progress)
1329 			const int32_t progressBarWidth = ((percent * 180) + 50) / 100;
1330 			if (progressBarWidth > 0)
1331 				fillRect(70, 42, progressBarWidth, 11, video.palette[PAL_GENBKG2]); // foreground (progress)
1332 
1333 			// background
1334 			int32_t bgWidth = 180 - progressBarWidth;
1335 			if (bgWidth > 0)
1336 				fillRect(70+progressBarWidth, 42, bgWidth, 11, video.palette[PAL_BORDER]);
1337 
1338 			// draw percentage text
1339 			if (percent > 99)
1340 				printThreeDecimals(144, 45, percent, video.palette[PAL_GENTXT]);
1341 			else
1342 				printTwoDecimals(152, 45, percent, video.palette[PAL_GENTXT]);
1343 
1344 			charOut(168, 45, '%', video.palette[PAL_GENTXT]);
1345 		}
1346 	}
1347 }
1348 
updateEditOp(void)1349 void updateEditOp(void)
1350 {
1351 	if (!ui.editOpScreenShown || ui.posEdScreenShown || ui.diskOpScreenShown)
1352 		return;
1353 
1354 	if (ui.editOpScreen == 1)
1355 	{
1356 		if (ui.updateRecordText)
1357 		{
1358 			ui.updateRecordText = false;
1359 			textOutBg(176, 58, (editor.recordMode == RECORD_PATT) ? "PATT" : "SONG", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1360 		}
1361 
1362 		if (ui.updateQuantizeText)
1363 		{
1364 			ui.updateQuantizeText = false;
1365 			printTwoDecimalsBg(192, 69, *editor.quantizeValueDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1366 		}
1367 
1368 		if (ui.updateMetro1Text)
1369 		{
1370 			ui.updateMetro1Text = false;
1371 			printTwoDecimalsBg(168, 80, *editor.metroSpeedDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1372 		}
1373 
1374 		if (ui.updateMetro2Text)
1375 		{
1376 			ui.updateMetro2Text = false;
1377 			printTwoDecimalsBg(192, 80, *editor.metroChannelDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1378 		}
1379 
1380 		if (ui.updateFromText)
1381 		{
1382 			ui.updateFromText = false;
1383 			printTwoHexBg(264, 80, *editor.sampleFromDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1384 		}
1385 
1386 		if (ui.updateKeysText)
1387 		{
1388 			ui.updateKeysText = false;
1389 			textOutBg(160, 91, editor.multiFlag ? "MULTI " : "SINGLE", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1390 		}
1391 
1392 		if (ui.updateToText)
1393 		{
1394 			ui.updateToText = false;
1395 			printTwoHexBg(264, 91, *editor.sampleToDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1396 		}
1397 	}
1398 	else if (ui.editOpScreen == 2)
1399 	{
1400 		if (ui.updateMixText)
1401 		{
1402 			ui.updateMixText = false;
1403 			if (editor.mixFlag)
1404 			{
1405 				textOutBg(128, 47, editor.mixText, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1406 				textOutBg(248, 47, "  ", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1407 			}
1408 			else
1409 			{
1410 				textOutBg(128, 47, "    SAMPLE EDITOR     ", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1411 			}
1412 		}
1413 
1414 		if (ui.updatePosText)
1415 		{
1416 			ui.updatePosText = false;
1417 			printFourHexBg(248, 58, *editor.samplePosDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1418 		}
1419 
1420 		if (ui.updateModText)
1421 		{
1422 			ui.updateModText = false;
1423 			printThreeDecimalsBg(256, 69,
1424 				(editor.modulateSpeed < 0) ? (0 - editor.modulateSpeed) : editor.modulateSpeed,
1425 				video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1426 
1427 			if (editor.modulateSpeed < 0)
1428 				charOutBg(248, 69, '-', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1429 			else
1430 				charOutBg(248, 69, ' ', video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1431 		}
1432 
1433 		if (ui.updateVolText)
1434 		{
1435 			ui.updateVolText = false;
1436 			printThreeDecimalsBg(248, 91, *editor.sampleVolDisp, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1437 		}
1438 	}
1439 	else if (ui.editOpScreen == 3)
1440 	{
1441 		if (ui.updateLengthText)
1442 		{
1443 			ui.updateLengthText = false;
1444 
1445 			// clear background
1446 			textOutBg(168, 91, "    ", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1447 			charOut(198, 91,    ':', video.palette[PAL_GENBKG]);
1448 
1449 			if (song->samples[editor.currSample].loopLength > 2 || song->samples[editor.currSample].loopStart >= 2)
1450 			{
1451 				textOut(168, 91, "LOOP", video.palette[PAL_GENTXT]);
1452 			}
1453 			else
1454 			{
1455 				printFourHex(168, 91, *editor.chordLengthDisp, video.palette[PAL_GENTXT]); // chord max length
1456 				charOut(198, 91, (editor.chordLengthMin) ? '.' : ':', video.palette[PAL_GENTXT]); // min/max flag
1457 			}
1458 		}
1459 
1460 		if (ui.updateNote1Text)
1461 		{
1462 			ui.updateNote1Text = false;
1463 			if (editor.note1 > 35)
1464 				textOutBg(256, 58, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1465 			else
1466 				textOutBg(256, 58, config.accidental ? noteNames2[2+editor.note1] : noteNames1[2+editor.note1],
1467 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1468 		}
1469 
1470 		if (ui.updateNote2Text)
1471 		{
1472 			ui.updateNote2Text = false;
1473 			if (editor.note2 > 35)
1474 				textOutBg(256, 69, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1475 			else
1476 				textOutBg(256, 69, config.accidental ? noteNames2[2+editor.note2] : noteNames1[2+editor.note2],
1477 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1478 		}
1479 
1480 		if (ui.updateNote3Text)
1481 		{
1482 			ui.updateNote3Text = false;
1483 			if (editor.note3 > 35)
1484 				textOutBg(256, 80, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1485 			else
1486 				textOutBg(256, 80, config.accidental ? noteNames2[2+editor.note3] : noteNames1[2+editor.note3],
1487 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1488 		}
1489 
1490 		if (ui.updateNote4Text)
1491 		{
1492 			ui.updateNote4Text = false;
1493 			if (editor.note4 > 35)
1494 				textOutBg(256, 91, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1495 			else
1496 				textOutBg(256, 91, config.accidental ? noteNames2[2+editor.note4] : noteNames1[2+editor.note4],
1497 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
1498 		}
1499 	}
1500 }
1501 
displayMainScreen(void)1502 void displayMainScreen(void)
1503 {
1504 	editor.blockMarkFlag = false;
1505 
1506 	ui.updateSongName = true;
1507 	ui.updateSongSize = true;
1508 	ui.updateSongTiming = true;
1509 	ui.updateTrackerFlags = true;
1510 	ui.updateStatusText = true;
1511 
1512 	ui.updateCurrSampleName = true;
1513 
1514 	if (!ui.diskOpScreenShown)
1515 	{
1516 		ui.updateCurrSampleFineTune = true;
1517 		ui.updateCurrSampleNum = true;
1518 		ui.updateCurrSampleVolume = true;
1519 		ui.updateCurrSampleLength = true;
1520 		ui.updateCurrSampleRepeat = true;
1521 		ui.updateCurrSampleReplen = true;
1522 	}
1523 
1524 	if (ui.samplerScreenShown)
1525 	{
1526 		if (!ui.diskOpScreenShown)
1527 			blit32(0, 0, 320, 121, trackerFrameBMP);
1528 	}
1529 	else
1530 	{
1531 		if (!ui.diskOpScreenShown)
1532 			blit32(0, 0, 320, 255, trackerFrameBMP);
1533 		else
1534 			blit32(0, 121, 320, 134, &trackerFrameBMP[121 * SCREEN_W]);
1535 
1536 		ui.updateSongBPM = true;
1537 		ui.updateCurrPattText = true;
1538 		ui.updatePatternData = true;
1539 	}
1540 
1541 	if (ui.diskOpScreenShown)
1542 	{
1543 		renderDiskOpScreen();
1544 	}
1545 	else
1546 	{
1547 		ui.updateSongPos = true;
1548 		ui.updateSongPattern = true;
1549 		ui.updateSongLength = true;
1550 
1551 		// draw zeroes that will never change (to the left of numbers)
1552 
1553 		charOut(64,  3, '0', video.palette[PAL_GENTXT]);
1554 		textOut(64, 14, "00", video.palette[PAL_GENTXT]);
1555 
1556 		if (!editor.isWAVRendering)
1557 		{
1558 			charOut(64, 25, '0', video.palette[PAL_GENTXT]);
1559 			textOut(64, 47, "00", video.palette[PAL_GENTXT]);
1560 			textOut(64, 58, "00", video.palette[PAL_GENTXT]);
1561 		}
1562 
1563 		if (ui.posEdScreenShown)
1564 		{
1565 			renderPosEdScreen();
1566 			ui.updatePosEd = true;
1567 		}
1568 		else
1569 		{
1570 			if (ui.editOpScreenShown)
1571 			{
1572 				renderEditOpScreen();
1573 			}
1574 			else
1575 			{
1576 				if (ui.aboutScreenShown)
1577 				{
1578 					renderAboutScreen();
1579 				}
1580 				else
1581 				{
1582 					     if (ui.visualizerMode == VISUAL_QUADRASCOPE) renderQuadrascopeBg();
1583 					else if (ui.visualizerMode == VISUAL_SPECTRUM) renderSpectrumAnalyzerBg();
1584 				}
1585 			}
1586 
1587 			renderMuteButtons();
1588 		}
1589 	}
1590 }
1591 
restoreStatusAndMousePointer(void)1592 static void restoreStatusAndMousePointer(void)
1593 {
1594 	editor.errorMsgActive = false;
1595 	editor.errorMsgBlock = false;
1596 	editor.errorMsgCounter = 0;
1597 	pointerSetPreviousMode();
1598 	setPrevStatusMessage();
1599 }
1600 
handleAskNo(void)1601 void handleAskNo(void)
1602 {
1603 	ui.pat2SmpDialogShown = false;
1604 
1605 	switch (ui.askScreenType)
1606 	{
1607 		case ASK_SAVEMOD_OVERWRITE:
1608 		{
1609 			restoreStatusAndMousePointer();
1610 			saveModule(DONT_CHECK_IF_FILE_EXIST, GIVE_NEW_FILENAME);
1611 		}
1612 		break;
1613 
1614 		case ASK_SAVESMP_OVERWRITE:
1615 		{
1616 			restoreStatusAndMousePointer();
1617 			saveSample(DONT_CHECK_IF_FILE_EXIST, GIVE_NEW_FILENAME);
1618 		}
1619 		break;
1620 
1621 		case ASK_LOAD_DOWNSAMPLE:
1622 		{
1623 			restoreStatusAndMousePointer();
1624 			extLoadWAVOrAIFFSampleCallback(DONT_DOWNSAMPLE);
1625 		}
1626 		break;
1627 
1628 		default:
1629 			restoreStatusAndMousePointer();
1630 		break;
1631 	}
1632 
1633 	removeAskDialog();
1634 }
1635 
handleAskYes(void)1636 void handleAskYes(void)
1637 {
1638 	char fileName[20 + 4 + 1];
1639 	int8_t oldSample;
1640 	uint32_t i;
1641 	moduleSample_t *s;
1642 
1643 	switch (ui.askScreenType)
1644 	{
1645 		case ASK_DISCARD_SONG:
1646 		{
1647 			restoreStatusAndMousePointer();
1648 			diskOpLoadFile2();
1649 		}
1650 		break;
1651 
1652 		case ASK_DISCARD_SONG_DRAGNDROP:
1653 		{
1654 			restoreStatusAndMousePointer();
1655 			loadDroppedFile2();
1656 		}
1657 		break;
1658 
1659 		case ASK_RESTORE_SAMPLE:
1660 		{
1661 			restoreStatusAndMousePointer();
1662 			redoSampleData(editor.currSample);
1663 		}
1664 		break;
1665 
1666 		case ASK_PAT2SMP:
1667 		{
1668 			restoreStatusAndMousePointer();
1669 			doPat2Smp();
1670 		}
1671 		break;
1672 
1673 		case ASK_SAVE_ALL_SAMPLES:
1674 		{
1675 			editor.errorMsgActive = false;
1676 			editor.errorMsgBlock = false;
1677 			editor.errorMsgCounter = 0;
1678 
1679 			oldSample = editor.currSample;
1680 			for (i = 0; i < MOD_SAMPLES; i++)
1681 			{
1682 				editor.currSample = (int8_t)i;
1683 				if (song->samples[i].length > 2)
1684 					saveSample(DONT_CHECK_IF_FILE_EXIST, GIVE_NEW_FILENAME);
1685 			}
1686 			editor.currSample = oldSample;
1687 
1688 			displayMsg("SAMPLES SAVED !");
1689 			setMsgPointer();
1690 		}
1691 		break;
1692 
1693 		case ASK_MAKE_CHORD:
1694 		{
1695 			restoreStatusAndMousePointer();
1696 			mixChordSample();
1697 		}
1698 		break;
1699 
1700 		case ASK_BOOST_ALL_SAMPLES:
1701 		{
1702 			restoreStatusAndMousePointer();
1703 
1704 			for (i = 0; i < MOD_SAMPLES; i++)
1705 				boostSample(i, true);
1706 
1707 			if (ui.samplerScreenShown)
1708 				redrawSample();
1709 
1710 			updateWindowTitle(MOD_IS_MODIFIED);
1711 		}
1712 		break;
1713 
1714 		case ASK_FILTER_ALL_SAMPLES:
1715 		{
1716 			restoreStatusAndMousePointer();
1717 
1718 			for (i = 0; i < MOD_SAMPLES; i++)
1719 				filterSample(i, true);
1720 
1721 			if (ui.samplerScreenShown)
1722 				redrawSample();
1723 
1724 			updateWindowTitle(MOD_IS_MODIFIED);
1725 		}
1726 		break;
1727 
1728 		case ASK_UPSAMPLE:
1729 		{
1730 			restoreStatusAndMousePointer();
1731 			upSample();
1732 		}
1733 		break;
1734 
1735 		case ASK_DOWNSAMPLE:
1736 		{
1737 			restoreStatusAndMousePointer();
1738 			downSample();
1739 		}
1740 		break;
1741 
1742 		case ASK_KILL_SAMPLE:
1743 		{
1744 			restoreStatusAndMousePointer();
1745 
1746 			if (editor.sampleZero)
1747 			{
1748 				statusNotSampleZero();
1749 				break;
1750 			}
1751 
1752 			turnOffVoices();
1753 			s = &song->samples[editor.currSample];
1754 
1755 			s->fineTune = 0;
1756 			s->volume = 0;
1757 			s->length = 0;
1758 			s->loopStart = 0;
1759 			s->loopLength = 2;
1760 
1761 			memset(s->text, 0, sizeof (s->text));
1762 			memset(&song->sampleData[(editor.currSample * MAX_SAMPLE_LEN)], 0, MAX_SAMPLE_LEN);
1763 
1764 			editor.samplePos = 0;
1765 			updateCurrSample();
1766 
1767 			ui.updateSongSize = true;
1768 			updateWindowTitle(MOD_IS_MODIFIED);
1769 		}
1770 		break;
1771 
1772 		case ASK_RESAMPLE:
1773 		{
1774 			restoreStatusAndMousePointer();
1775 			samplerResample();
1776 		}
1777 		break;
1778 
1779 		case ASK_LOAD_DOWNSAMPLE:
1780 		{
1781 			// for WAV and AIFF sample loader
1782 			restoreStatusAndMousePointer();
1783 			extLoadWAVOrAIFFSampleCallback(DO_DOWNSAMPLE);
1784 		}
1785 		break;
1786 
1787 		case ASK_MOD2WAV_OVERWRITE:
1788 		{
1789 			memset(fileName, 0, sizeof (fileName));
1790 
1791 			if (song->header.name[0] != '\0')
1792 			{
1793 				for (i = 0; i < 20; i++)
1794 				{
1795 					fileName[i] = (char)tolower(song->header.name[i]);
1796 					if (fileName[i] == '\0') break;
1797 					sanitizeFilenameChar(&fileName[i]);
1798 				}
1799 
1800 				strcat(fileName, ".wav");
1801 			}
1802 			else
1803 			{
1804 				strcpy(fileName, "untitled.wav");
1805 			}
1806 
1807 			renderToWav(fileName, DONT_CHECK_IF_FILE_EXIST);
1808 		}
1809 		break;
1810 
1811 		case ASK_MOD2WAV:
1812 		{
1813 			memset(fileName, 0, sizeof (fileName));
1814 
1815 			if (song->header.name[0] != '\0')
1816 			{
1817 				for (i = 0; i < 20; i++)
1818 				{
1819 					fileName[i] = (char)tolower(song->header.name[i]);
1820 					if (fileName[i] == '\0') break;
1821 					sanitizeFilenameChar(&fileName[i]);
1822 				}
1823 
1824 				strcat(fileName, ".wav");
1825 			}
1826 			else
1827 			{
1828 				strcpy(fileName, "untitled.wav");
1829 			}
1830 
1831 			renderToWav(fileName, CHECK_IF_FILE_EXIST);
1832 		}
1833 		break;
1834 
1835 		case ASK_QUIT:
1836 		{
1837 			restoreStatusAndMousePointer();
1838 			ui.throwExit = true;
1839 		}
1840 		break;
1841 
1842 		case ASK_SAVE_SAMPLE:
1843 		{
1844 			restoreStatusAndMousePointer();
1845 			saveSample(CHECK_IF_FILE_EXIST, DONT_GIVE_NEW_FILENAME);
1846 		}
1847 		break;
1848 
1849 		case ASK_SAVESMP_OVERWRITE:
1850 		{
1851 			restoreStatusAndMousePointer();
1852 			saveSample(DONT_CHECK_IF_FILE_EXIST, DONT_GIVE_NEW_FILENAME);
1853 		}
1854 		break;
1855 
1856 		case ASK_SAVE_MODULE:
1857 		{
1858 			restoreStatusAndMousePointer();
1859 			saveModule(CHECK_IF_FILE_EXIST, DONT_GIVE_NEW_FILENAME);
1860 		}
1861 		break;
1862 
1863 		case ASK_SAVEMOD_OVERWRITE:
1864 		{
1865 			restoreStatusAndMousePointer();
1866 			saveModule(DONT_CHECK_IF_FILE_EXIST, DONT_GIVE_NEW_FILENAME);
1867 		}
1868 		break;
1869 
1870 		default: break;
1871 	}
1872 
1873 	removeAskDialog();
1874 }
1875 
videoClose(void)1876 void videoClose(void)
1877 {
1878 	SDL_DestroyTexture(video.texture);
1879 	SDL_DestroyRenderer(video.renderer);
1880 	SDL_DestroyWindow(video.window);
1881 	free(video.frameBufferUnaligned);
1882 }
1883 
setupSprites(void)1884 void setupSprites(void)
1885 {
1886 	memset(sprites, 0, sizeof (sprites));
1887 
1888 	sprites[SPRITE_MOUSE_POINTER].data = mousePointerBMP;
1889 	sprites[SPRITE_MOUSE_POINTER].pixelType = SPRITE_TYPE_PALETTE;
1890 	sprites[SPRITE_MOUSE_POINTER].colorKey = PAL_COLORKEY;
1891 	sprites[SPRITE_MOUSE_POINTER].w = 16;
1892 	sprites[SPRITE_MOUSE_POINTER].h = 16;
1893 	hideSprite(SPRITE_MOUSE_POINTER);
1894 
1895 	sprites[SPRITE_PATTERN_CURSOR].data = patternCursorBMP;
1896 	sprites[SPRITE_PATTERN_CURSOR].pixelType = SPRITE_TYPE_RGB;
1897 	sprites[SPRITE_PATTERN_CURSOR].colorKey = video.palette[PAL_COLORKEY];
1898 	sprites[SPRITE_PATTERN_CURSOR].w = 11;
1899 	sprites[SPRITE_PATTERN_CURSOR].h = 14;
1900 	hideSprite(SPRITE_PATTERN_CURSOR);
1901 
1902 	sprites[SPRITE_LOOP_PIN_LEFT].data = loopPinsBMP;
1903 	sprites[SPRITE_LOOP_PIN_LEFT].pixelType = SPRITE_TYPE_RGB;
1904 	sprites[SPRITE_LOOP_PIN_LEFT].colorKey = video.palette[PAL_COLORKEY];
1905 	sprites[SPRITE_LOOP_PIN_LEFT].w = 4;
1906 	sprites[SPRITE_LOOP_PIN_LEFT].h = 64;
1907 	hideSprite(SPRITE_LOOP_PIN_LEFT);
1908 
1909 	sprites[SPRITE_LOOP_PIN_RIGHT].data = &loopPinsBMP[4 * 64];
1910 	sprites[SPRITE_LOOP_PIN_RIGHT].pixelType = SPRITE_TYPE_RGB;
1911 	sprites[SPRITE_LOOP_PIN_RIGHT].colorKey = video.palette[PAL_COLORKEY];
1912 	sprites[SPRITE_LOOP_PIN_RIGHT].w = 4;
1913 	sprites[SPRITE_LOOP_PIN_RIGHT].h = 64;
1914 	hideSprite(SPRITE_LOOP_PIN_RIGHT);
1915 
1916 	sprites[SPRITE_SAMPLING_POS_LINE].data = samplingPosBMP;
1917 	sprites[SPRITE_SAMPLING_POS_LINE].pixelType = SPRITE_TYPE_RGB;
1918 	sprites[SPRITE_SAMPLING_POS_LINE].colorKey = video.palette[PAL_COLORKEY];
1919 	sprites[SPRITE_SAMPLING_POS_LINE].w = 1;
1920 	sprites[SPRITE_SAMPLING_POS_LINE].h = 64;
1921 	hideSprite(SPRITE_SAMPLING_POS_LINE);
1922 
1923 	// setup refresh buffer (used to clear sprites after each frame)
1924 	for (uint32_t i = 0; i < SPRITE_NUM; i++)
1925 		sprites[i].refreshBuffer = (uint32_t *)malloc((sprites[i].w * sprites[i].h) * sizeof (int32_t));
1926 }
1927 
freeSprites(void)1928 void freeSprites(void)
1929 {
1930 	for (int32_t i = 0; i < SPRITE_NUM; i++)
1931 		free(sprites[i].refreshBuffer);
1932 }
1933 
setSpritePos(int32_t sprite,int32_t x,int32_t y)1934 void setSpritePos(int32_t sprite, int32_t x, int32_t y)
1935 {
1936 	sprites[sprite].newX = (int16_t)x;
1937 	sprites[sprite].newY = (int16_t)y;
1938 }
1939 
hideSprite(int32_t sprite)1940 void hideSprite(int32_t sprite)
1941 {
1942 	sprites[sprite].newX = SCREEN_W;
1943 }
1944 
eraseSprites(void)1945 void eraseSprites(void)
1946 {
1947 	int32_t sx, sy, x, y, sw, sh, srcPitch, dstPitch;
1948 	const uint32_t *src32;
1949 	uint32_t *dst32;
1950 	sprite_t *s;
1951 
1952 	for (int32_t i = SPRITE_NUM-1; i >= 0; i--) // erasing must be done in reverse order
1953 	{
1954 		s = &sprites[i];
1955 		if (s->x >= SCREEN_W || s->y >= SCREEN_H) // sprite is hidden, don't draw nor fill clear buffer
1956 			continue;
1957 
1958 		assert(s->refreshBuffer != NULL);
1959 
1960 		sw = s->w;
1961 		sh = s->h;
1962 		sx = s->x;
1963 		sy = s->y;
1964 
1965 		// if x is negative, adjust variables
1966 		if (sx < 0)
1967 		{
1968 			sw += sx; // subtraction
1969 			sx = 0;
1970 		}
1971 
1972 		// if y is negative, adjust variables
1973 		if (sy < 0)
1974 		{
1975 			sh += sy; // subtraction
1976 			sy = 0;
1977 		}
1978 
1979 		dst32 = &video.frameBuffer[(sy * SCREEN_W) + sx];
1980 		src32 = s->refreshBuffer;
1981 
1982 		// handle x/y clipping
1983 		if (sx+sw >= SCREEN_W) sw = SCREEN_W - sx;
1984 		if (sy+sh >= SCREEN_H) sh = SCREEN_H - sy;
1985 
1986 		srcPitch = s->w - sw;
1987 		dstPitch = SCREEN_W - sw;
1988 
1989 		for (y = 0; y < sh; y++)
1990 		{
1991 			for (x = 0; x < sw; x++)
1992 				*dst32++ = *src32++;
1993 
1994 			src32 += srcPitch;
1995 			dst32 += dstPitch;
1996 		}
1997 	}
1998 
1999 	fillFromVuMetersBgBuffer(); // let's put it here even though it's not sprite-based
2000 }
2001 
renderSprites(void)2002 void renderSprites(void)
2003 {
2004 	const uint8_t *src8;
2005 	int32_t sx, sy, x, y, srcPtrBias, sw, sh, srcPitch, dstPitch;
2006 	const uint32_t *src32;
2007 	uint32_t *dst32, *clr32, colorKey;
2008 	sprite_t *s;
2009 
2010 	renderVuMeters(); // let's put it here even though it's not sprite-based
2011 
2012 	for (int32_t i = 0; i < SPRITE_NUM; i++)
2013 	{
2014 		s = &sprites[i];
2015 
2016 		// set new sprite position
2017 		s->x = s->newX;
2018 		s->y = s->newY;
2019 
2020 		if (s->x >= SCREEN_W || s->y >= SCREEN_H) // sprite is hidden, don't draw nor fill clear buffer
2021 			continue;
2022 
2023 		assert(s->data != NULL && s->refreshBuffer != NULL);
2024 
2025 		sw = s->w;
2026 		sh = s->h;
2027 		sx = s->x;
2028 		sy = s->y;
2029 		srcPtrBias = 0;
2030 
2031 		// if x is negative, adjust variables
2032 		if (sx < 0)
2033 		{
2034 			sw += sx; // subtraction
2035 			srcPtrBias += -sx;
2036 			sx = 0;
2037 		}
2038 
2039 		// if y is negative, adjust variables
2040 		if (sy < 0)
2041 		{
2042 			sh += sy; // subtraction
2043 			srcPtrBias += (-sy * s->w);
2044 			sy = 0;
2045 		}
2046 
2047 		dst32 = &video.frameBuffer[(sy * SCREEN_W) + sx];
2048 		clr32 = s->refreshBuffer;
2049 
2050 		// handle x/y clipping
2051 		if (sx+sw >= SCREEN_W) sw = SCREEN_W - sx;
2052 		if (sy+sh >= SCREEN_H) sh = SCREEN_H - sy;
2053 
2054 		srcPitch = s->w - sw;
2055 		dstPitch = SCREEN_W - sw;
2056 
2057 		colorKey = sprites[i].colorKey;
2058 		if (sprites[i].pixelType == SPRITE_TYPE_RGB)
2059 		{
2060 			// 24-bit RGB sprite
2061 			src32 = ((uint32_t *)sprites[i].data) + srcPtrBias;
2062 			for (y = 0; y < sh; y++)
2063 			{
2064 				for (x = 0; x < sw; x++)
2065 				{
2066 					*clr32++ = *dst32; // fill clear buffer
2067 					if (*src32 != colorKey)
2068 						*dst32 = *src32;
2069 
2070 					dst32++;
2071 					src32++;
2072 				}
2073 
2074 				clr32 += srcPitch;
2075 				src32 += srcPitch;
2076 				dst32 += dstPitch;
2077 			}
2078 		}
2079 		else
2080 		{
2081 			// 8-bit paletted sprite
2082 			src8 = ((uint8_t *)sprites[i].data) + srcPtrBias;
2083 			for (y = 0; y < sh; y++)
2084 			{
2085 				for (x = 0; x < sw; x++)
2086 				{
2087 					*clr32++ = *dst32; // fill clear buffer
2088 					if (*src8 != colorKey)
2089 					{
2090 						assert(*src8 < PALETTE_NUM);
2091 						*dst32 = video.palette[*src8];
2092 					}
2093 
2094 					dst32++;
2095 					src8++;
2096 				}
2097 
2098 				clr32 += srcPitch;
2099 				src8 += srcPitch;
2100 				dst32 += dstPitch;
2101 			}
2102 		}
2103 	}
2104 }
2105 
flipFrame(void)2106 void flipFrame(void)
2107 {
2108 	const uint32_t windowFlags = SDL_GetWindowFlags(video.window);
2109 	bool minimized = (windowFlags & SDL_WINDOW_MINIMIZED) ? true : false;
2110 
2111 	renderSprites();
2112 	SDL_UpdateTexture(video.texture, NULL, video.frameBuffer, SCREEN_W * sizeof (int32_t));
2113 
2114 	// SDL 2.0.14 bug on Windows (?): This function consumes ever-increasing memory if the program is minimized
2115 	if (!minimized)
2116 		SDL_RenderClear(video.renderer);
2117 
2118 	SDL_RenderCopy(video.renderer, video.texture, NULL, NULL);
2119 	SDL_RenderPresent(video.renderer);
2120 	eraseSprites();
2121 
2122 	if (!video.vsync60HzPresent)
2123 	{
2124 		waitVBL(); // we have no VSync, do crude thread sleeping to sync to ~60Hz
2125 	}
2126 	else
2127 	{
2128 		/* We have VSync, but it can unexpectedly get inactive in certain scenarios.
2129 		** We have to force thread sleeping (to ~60Hz) if so.
2130 		*/
2131 #ifdef __APPLE__
2132 		// macOS: VSync gets disabled if the window is 100% covered by another window. Let's add a (crude) fix:
2133 		if (minimized || !(windowFlags & SDL_WINDOW_INPUT_FOCUS))
2134 			waitVBL();
2135 #elif __unix__
2136 		// *NIX: VSync gets disabled in fullscreen mode (at least on some distros/systems). Let's add a fix:
2137 		if (minimized || video.fullscreen)
2138 			waitVBL();
2139 #else
2140 		if (minimized)
2141 			waitVBL();
2142 #endif
2143 	}
2144 }
2145 
updateSpectrumAnalyzer(int8_t vol,int16_t period)2146 void updateSpectrumAnalyzer(int8_t vol, int16_t period)
2147 {
2148 	const uint8_t maxHeight = SPECTRUM_BAR_HEIGHT + 1; // +1 because of audio latency - allows full height to be seen
2149 	int8_t scaledVol;
2150 	int32_t scaledNote;
2151 
2152 	if (ui.visualizerMode != VISUAL_SPECTRUM || vol <= 0)
2153 		return;
2154 
2155 	scaledVol = (vol * 24600L) >> 16; // scaledVol = (vol << 8) / 682
2156 
2157 	period = CLAMP(period, 113, 856);
2158 
2159 	scaledNote = 856 - period;
2160 	scaledNote *= scaledNote;
2161 	scaledNote = ((int64_t)scaledNote * 171162) >> 32; // scaledNote /= 25093
2162 
2163 	// scaledNote now ranges 0..22, no need to clamp
2164 
2165 	// increment main spectrum bar
2166 	editor.spectrumVolumes[scaledNote] += scaledVol;
2167 	if (editor.spectrumVolumes[scaledNote] > maxHeight)
2168 		editor.spectrumVolumes[scaledNote] = maxHeight;
2169 
2170 	// increment left side of spectrum bar with half volume
2171 	if (scaledNote > 0)
2172 	{
2173 		editor.spectrumVolumes[scaledNote-1] += scaledVol >> 1;
2174 		if (editor.spectrumVolumes[scaledNote-1] > maxHeight)
2175 			editor.spectrumVolumes[scaledNote-1] = maxHeight;
2176 	}
2177 
2178 	// increment right side of spectrum bar with half volume
2179 	if (scaledNote < SPECTRUM_BAR_NUM-1)
2180 	{
2181 		editor.spectrumVolumes[scaledNote+1] += scaledVol >> 1;
2182 		if (editor.spectrumVolumes[scaledNote+1] > maxHeight)
2183 			editor.spectrumVolumes[scaledNote+1] = maxHeight;
2184 	}
2185 }
2186 
sinkVisualizerBars(void)2187 void sinkVisualizerBars(void)
2188 {
2189 	int32_t i;
2190 
2191 	// sink stuff @ 49.92Hz (Amiga PAL) rate
2192 
2193 	static uint64_t counter50Hz;
2194 	const uint64_t counter50HzDelta = (uint64_t)(((UINT32_MAX+1.0) * (AMIGA_PAL_VBLANK_HZ / (double)VBLANK_HZ)) + 0.5);
2195 
2196 	counter50Hz += counter50HzDelta; // 32.32 fixed-point counter
2197 	if (counter50Hz > 0xFFFFFFFF)
2198 	{
2199 		counter50Hz &= 0xFFFFFFFF;
2200 
2201 		// sink VU-meters
2202 		for (i = 0; i < AMIGA_VOICES; i++)
2203 		{
2204 			if (editor.vuMeterVolumes[i] > 0)
2205 				editor.vuMeterVolumes[i]--;
2206 		}
2207 
2208 		// sink "spectrum analyzer" bars
2209 		for (i = 0; i < SPECTRUM_BAR_NUM; i++)
2210 		{
2211 			if (editor.spectrumVolumes[i] > 0)
2212 				editor.spectrumVolumes[i]--;
2213 		}
2214 	}
2215 }
2216 
updateRenderSizeVars(void)2217 void updateRenderSizeVars(void)
2218 {
2219 	int32_t di;
2220 #ifdef __APPLE__
2221 	int32_t actualScreenW, actualScreenH;
2222 	double dXUpscale, dYUpscale;
2223 #endif
2224 	float fXScale, fYScale;
2225 	SDL_DisplayMode dm;
2226 
2227 	di = SDL_GetWindowDisplayIndex(video.window);
2228 	if (di < 0)
2229 		di = 0; // return display index 0 (default) on error
2230 
2231 	SDL_GetDesktopDisplayMode(di, &dm);
2232 	video.displayW = dm.w;
2233 	video.displayH = dm.h;
2234 
2235 	if (video.fullscreen)
2236 	{
2237 		if (config.fullScreenStretch)
2238 		{
2239 			video.renderW = video.displayW;
2240 			video.renderH = video.displayH;
2241 			video.renderX = 0;
2242 			video.renderY = 0;
2243 		}
2244 		else
2245 		{
2246 			SDL_RenderGetScale(video.renderer, &fXScale, &fYScale);
2247 
2248 			video.renderW = (int32_t)(SCREEN_W * fXScale);
2249 			video.renderH = (int32_t)(SCREEN_H * fYScale);
2250 
2251 #ifdef __APPLE__
2252 			// retina high-DPI hackery (SDL2 is bad at reporting actual rendering sizes on macOS w/ high-DPI)
2253 			SDL_GL_GetDrawableSize(video.window, &actualScreenW, &actualScreenH);
2254 			SDL_GetDesktopDisplayMode(0, &dm);
2255 
2256 			dXUpscale = (double)actualScreenW / video.displayW;
2257 			dYUpscale = (double)actualScreenH / video.displayH;
2258 
2259 			// downscale back to correct sizes
2260 			if (dXUpscale != 0.0) video.renderW = (int32_t)(video.renderW / dXUpscale);
2261 			if (dYUpscale != 0.0) video.renderH = (int32_t)(video.renderH / dYUpscale);
2262 #endif
2263 			video.renderX = (video.displayW - video.renderW) >> 1;
2264 			video.renderY = (video.displayH - video.renderH) >> 1;
2265 		}
2266 	}
2267 	else
2268 	{
2269 		SDL_GetWindowSize(video.window, &video.renderW, &video.renderH);
2270 
2271 		video.renderX = 0;
2272 		video.renderY = 0;
2273 	}
2274 
2275 	// for mouse cursor creation
2276 	video.xScale = (int32_t)((video.renderW * (1.0 / SCREEN_W)) + 0.5);
2277 	video.yScale = (int32_t)((video.renderH * (1.0 / SCREEN_H)) + 0.5);
2278 	createMouseCursors();
2279 }
2280 
toggleFullScreen(void)2281 void toggleFullScreen(void)
2282 {
2283 	SDL_DisplayMode dm;
2284 
2285 	video.fullscreen ^= 1;
2286 	if (video.fullscreen)
2287 	{
2288 		if (config.fullScreenStretch)
2289 		{
2290 			SDL_GetDesktopDisplayMode(0, &dm);
2291 			SDL_RenderSetLogicalSize(video.renderer, dm.w, dm.h);
2292 		}
2293 		else
2294 		{
2295 			SDL_RenderSetLogicalSize(video.renderer, SCREEN_W, SCREEN_H);
2296 		}
2297 
2298 		SDL_SetWindowSize(video.window, SCREEN_W, SCREEN_H);
2299 		SDL_SetWindowFullscreen(video.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
2300 		SDL_SetWindowGrab(video.window, SDL_TRUE);
2301 	}
2302 	else
2303 	{
2304 		SDL_SetWindowFullscreen(video.window, 0);
2305 		SDL_RenderSetLogicalSize(video.renderer, SCREEN_W, SCREEN_H);
2306 		SDL_SetWindowSize(video.window, SCREEN_W * config.videoScaleFactor, SCREEN_H * config.videoScaleFactor);
2307 
2308 		// this is not sensible on a multi-monitor setup
2309 		//SDL_SetWindowPosition(video.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
2310 
2311 		SDL_SetWindowGrab(video.window, SDL_FALSE);
2312 	}
2313 
2314 	updateRenderSizeVars();
2315 	updateMouseScaling();
2316 
2317 	if (video.fullscreen)
2318 	{
2319 		mouse.setPosX = video.displayW >> 1;
2320 		mouse.setPosY = video.displayH >> 1;
2321 	}
2322 	else
2323 	{
2324 		mouse.setPosX = video.renderW >> 1;
2325 		mouse.setPosY = video.renderH >> 1;
2326 	}
2327 
2328 	mouse.setPosFlag = true;
2329 }
2330 
setupVideo(void)2331 bool setupVideo(void)
2332 {
2333 	int32_t screenW, screenH;
2334 	uint32_t rendererFlags;
2335 	SDL_DisplayMode dm;
2336 
2337 	screenW = SCREEN_W * config.videoScaleFactor;
2338 	screenH = SCREEN_H * config.videoScaleFactor;
2339 
2340 	rendererFlags = 0;
2341 
2342 #ifdef _WIN32
2343 #if SDL_PATCHLEVEL >= 4
2344 	SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); // this is for Windows only
2345 #endif
2346 #endif
2347 
2348 #if SDL_PATCHLEVEL >= 5
2349 	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
2350 #endif
2351 
2352 	video.vsync60HzPresent = false;
2353 	if (!config.vsyncOff)
2354 	{
2355 		SDL_GetDesktopDisplayMode(0, &dm);
2356 		if (dm.refresh_rate >= 59 && dm.refresh_rate <= 61)
2357 		{
2358 			video.vsync60HzPresent = true;
2359 			rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
2360 		}
2361 	}
2362 
2363 	uint32_t windowFlags = SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI;
2364 
2365 	video.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED,
2366 		SDL_WINDOWPOS_CENTERED, screenW, screenH, windowFlags);
2367 
2368 	if (video.window == NULL)
2369 	{
2370 		showErrorMsgBox("Couldn't create SDL window:\n%s", SDL_GetError());
2371 		return false;
2372 	}
2373 
2374 	video.renderer = SDL_CreateRenderer(video.window, -1, rendererFlags);
2375 	if (video.renderer == NULL)
2376 	{
2377 		if (video.vsync60HzPresent) // try again without vsync flag
2378 		{
2379 			video.vsync60HzPresent = false;
2380 			rendererFlags &= ~SDL_RENDERER_PRESENTVSYNC;
2381 			video.renderer = SDL_CreateRenderer(video.window, -1, rendererFlags);
2382 		}
2383 
2384 		if (video.renderer == NULL)
2385 		{
2386 			showErrorMsgBox("Couldn't create SDL renderer:\n%s\n\n" \
2387 			                "Is your GPU (+ driver) too old?", SDL_GetError());
2388 			return false;
2389 		}
2390 	}
2391 
2392 	SDL_RenderSetLogicalSize(video.renderer, SCREEN_W, SCREEN_H);
2393 
2394 #if SDL_PATCHLEVEL >= 5
2395 	SDL_RenderSetIntegerScale(video.renderer, config.integerScaling ? SDL_TRUE : SDL_FALSE);
2396 #endif
2397 
2398 	SDL_SetRenderDrawBlendMode(video.renderer, SDL_BLENDMODE_NONE);
2399 
2400 	if (config.pixelFilter == PIXELFILTER_LINEAR)
2401 		SDL_SetHint("SDL_RENDER_SCALE_QUALITY", "linear");
2402 	else if (config.pixelFilter == PIXELFILTER_BEST)
2403 		SDL_SetHint("SDL_RENDER_SCALE_QUALITY", "best");
2404 	else
2405 		SDL_SetHint("SDL_RENDER_SCALE_QUALITY", "nearest");
2406 
2407 	video.texture = SDL_CreateTexture(video.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H);
2408 	if (video.texture == NULL)
2409 	{
2410 		showErrorMsgBox("Couldn't create %dx%d GPU texture:\n%s\n\n" \
2411 		                "Is your GPU (+ driver) too old?", SCREEN_W, SCREEN_H, SDL_GetError());
2412 		return false;
2413 	}
2414 
2415 	SDL_SetTextureBlendMode(video.texture, SDL_BLENDMODE_NONE);
2416 
2417 	// frame buffer used by SDL (for texture)
2418 	video.frameBufferUnaligned = (uint32_t *)MALLOC_PAD(SCREEN_W * SCREEN_H * sizeof (int32_t), 256);
2419 	if (video.frameBufferUnaligned == NULL)
2420 	{
2421 		showErrorMsgBox("Out of memory!");
2422 		return false;
2423 	}
2424 
2425 	// we want an aligned pointer
2426 	video.frameBuffer = (uint32_t *)ALIGN_PTR(video.frameBufferUnaligned, 256);
2427 
2428 	updateRenderSizeVars();
2429 	updateMouseScaling();
2430 
2431 	if (config.hwMouse)
2432 		SDL_ShowCursor(SDL_TRUE);
2433 	else
2434 		SDL_ShowCursor(SDL_FALSE);
2435 
2436 	// Workaround: SDL_GetGlobalMouseState() doesn't work with KMSDRM/Wayland
2437 	video.useDesktopMouseCoords = true;
2438 	const char *videoDriver = SDL_GetCurrentVideoDriver();
2439 	if (videoDriver != NULL && (strcmp("KMSDRM", videoDriver) == 0 || strcmp("wayland", videoDriver) == 0))
2440 		video.useDesktopMouseCoords = false;
2441 
2442 	SDL_SetRenderDrawColor(video.renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
2443 	return true;
2444 }
2445