1 /* This file contains the routines for the following sample editor functions:
2 ** - Resampler
3 ** - Echo
4 ** - Mix
5 ** - Volume
6 **/
7 
8 // for finding memory leaks in debug mode with Visual Studio
9 #if defined _DEBUG && defined _MSC_VER
10 #include <crtdbg.h>
11 #endif
12 
13 #include <stdio.h>
14 #include <stdint.h>
15 #include <stdbool.h>
16 #include <math.h>
17 #include "ft2_header.h"
18 #include "ft2_mouse.h"
19 #include "ft2_audio.h"
20 #include "ft2_gui.h"
21 #include "ft2_events.h"
22 #include "ft2_video.h"
23 #include "ft2_inst_ed.h"
24 #include "ft2_sample_ed.h"
25 #include "ft2_keyboard.h"
26 #include "ft2_tables.h"
27 #include "ft2_structs.h"
28 
29 static volatile bool stopThread;
30 
31 static int8_t smpEd_RelReSmp, mix_Balance = 50;
32 static bool echo_AddMemory, exitFlag, outOfMemory;
33 static int16_t echo_nEcho = 1, echo_VolChange = 30;
34 static int32_t echo_Distance = 0x100;
35 static double dVol_StartVol = 100.0, dVol_EndVol = 100.0;
36 static SDL_Thread *thread;
37 
pbExit(void)38 static void pbExit(void)
39 {
40 	ui.sysReqShown = false;
41 	exitFlag = true;
42 }
43 
windowOpen(void)44 static void windowOpen(void)
45 {
46 	ui.sysReqShown = true;
47 	ui.sysReqEnterPressed = false;
48 
49 	unstuckLastUsedGUIElement();
50 	SDL_EventState(SDL_DROPFILE, SDL_DISABLE);
51 }
52 
windowClose(bool rewriteSample)53 static void windowClose(bool rewriteSample)
54 {
55 	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
56 
57 	if (exitFlag || rewriteSample)
58 		writeSample(true);
59 	else
60 		updateNewSample();
61 
62 	mouseAnimOff();
63 }
64 
sbSetResampleTones(uint32_t pos)65 static void sbSetResampleTones(uint32_t pos)
66 {
67 	if (smpEd_RelReSmp != (int8_t)(pos - 36))
68 		smpEd_RelReSmp = (int8_t)(pos - 36);
69 }
70 
pbResampleTonesDown(void)71 static void pbResampleTonesDown(void)
72 {
73 	if (smpEd_RelReSmp > -36)
74 		smpEd_RelReSmp--;
75 }
76 
pbResampleTonesUp(void)77 static void pbResampleTonesUp(void)
78 {
79 	if (smpEd_RelReSmp < 36)
80 		smpEd_RelReSmp++;
81 }
82 
resampleThread(void * ptr)83 static int32_t SDLCALL resampleThread(void *ptr)
84 {
85 	smpPtr_t sp;
86 
87 	if (instr[editor.curInstr] == NULL)
88 		return true;
89 
90 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
91 	bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
92 
93 	const double dRatio = exp2((int32_t)smpEd_RelReSmp / 12.0);
94 
95 	double dNewLen = s->length * dRatio;
96 	if (dNewLen > (double)MAX_SAMPLE_LEN)
97 		dNewLen = (double)MAX_SAMPLE_LEN;
98 
99 	const uint32_t newLen = (int32_t)floor(dNewLen);
100 	if (!allocateSmpDataPtr(&sp, newLen, sample16Bit))
101 	{
102 		outOfMemory = true;
103 		setMouseBusy(false);
104 		ui.sysReqShown = false;
105 		return true;
106 	}
107 
108 	int8_t *dst = sp.ptr;
109 	int8_t *src = s->dataPtr;
110 
111 	// 32.32 fixed-point logic
112 	const uint64_t delta64 = (const uint64_t)round((UINT32_MAX+1.0) / dRatio);
113 	uint64_t posFrac64 = 0;
114 
115 	pauseAudio();
116 	unfixSample(s);
117 
118 	/* Nearest-neighbor resampling (no interpolation).
119 	**
120 	** Could benefit from windowed-sinc interpolation,
121 	** but it seems like some people prefer no resampling
122 	** interpolation (like FT2).
123 	*/
124 
125 	if (newLen > 0)
126 	{
127 		if (sample16Bit)
128 		{
129 			const int16_t *src16 = (const int16_t *)src;
130 			int16_t *dst16 = (int16_t *)dst;
131 
132 			for (uint32_t i = 0; i < newLen; i++)
133 			{
134 				const uint32_t position = posFrac64 >> 32;
135 				dst16[i] = src16[position];
136 				posFrac64 += delta64;
137 			}
138 		}
139 		else // 8-bit
140 		{
141 			const int8_t *src8 = src;
142 			int8_t *dst8 = dst;
143 
144 			for (uint32_t i = 0; i < newLen; i++)
145 			{
146 				const uint32_t position = posFrac64 >> 32;
147 				dst8[i] = src8[position];
148 				posFrac64 += delta64;
149 			}
150 		}
151 	}
152 
153 	freeSmpData(s);
154 	setSmpDataPtr(s, &sp);
155 
156 	s->relativeNote += smpEd_RelReSmp;
157 	s->length = newLen;
158 	s->loopStart = (int32_t)(s->loopStart * dRatio);
159 	s->loopLength = (int32_t)(s->loopLength * dRatio);
160 
161 	sanitizeSample(s);
162 
163 	fixSample(s);
164 	resumeAudio();
165 
166 	setSongModifiedFlag();
167 	setMouseBusy(false);
168 
169 	ui.sysReqShown = false;
170 	return true;
171 
172 	(void)ptr;
173 }
174 
pbDoResampling(void)175 static void pbDoResampling(void)
176 {
177 	mouseAnimOn();
178 	thread = SDL_CreateThread(resampleThread, NULL, NULL);
179 	if (thread == NULL)
180 	{
181 		okBox(0, "System message", "Couldn't create thread!");
182 		return;
183 	}
184 
185 	SDL_DetachThread(thread);
186 }
187 
drawResampleBox(void)188 static void drawResampleBox(void)
189 {
190 	char sign;
191 	const int16_t x = 209;
192 	const int16_t y = 230;
193 	const int16_t w = 214;
194 	const int16_t h = 54;
195 
196 	// main fill
197 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
198 
199 	// outer border
200 	vLine(x,         y,         h - 1, PAL_BUTTON1);
201 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
202 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
203 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
204 
205 	// inner border
206 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
207 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
208 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
209 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
210 
211 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
212 
213 	double dLenMul = exp2(smpEd_RelReSmp * (1.0 / 12.0));
214 
215 	double dNewLen = s->length * dLenMul;
216 	if (dNewLen > (double)MAX_SAMPLE_LEN)
217 		dNewLen = (double)MAX_SAMPLE_LEN;
218 
219 	textOutShadow(215, 236, PAL_FORGRND, PAL_BUTTON2, "Rel. h.tones");
220 	textOutShadow(215, 250, PAL_FORGRND, PAL_BUTTON2, "New sample size");
221 	hexOut(361, 250, PAL_FORGRND, (int32_t)dNewLen, 8);
222 
223 	     if (smpEd_RelReSmp == 0) sign = ' ';
224 	else if (smpEd_RelReSmp  < 0) sign = '-';
225 	else sign = '+';
226 
227 	uint16_t val = ABS(smpEd_RelReSmp);
228 	if (val > 9)
229 	{
230 		charOut(291, 236, PAL_FORGRND, sign);
231 		charOut(298, 236, PAL_FORGRND, '0' + ((val / 10) % 10));
232 		charOut(305, 236, PAL_FORGRND, '0' + (val % 10));
233 	}
234 	else
235 	{
236 		charOut(298, 236, PAL_FORGRND, sign);
237 		charOut(305, 236, PAL_FORGRND, '0' + (val % 10));
238 	}
239 }
240 
setupResampleBoxWidgets(void)241 static void setupResampleBoxWidgets(void)
242 {
243 	pushButton_t *p;
244 	scrollBar_t *s;
245 
246 	// "Apply" pushbutton
247 	p = &pushButtons[0];
248 	memset(p, 0, sizeof (pushButton_t));
249 	p->caption = "Apply";
250 	p->x = 214;
251 	p->y = 264;
252 	p->w = 73;
253 	p->h = 16;
254 	p->callbackFuncOnUp = pbDoResampling;
255 	p->visible = true;
256 
257 	// "Exit" pushbutton
258 	p = &pushButtons[1];
259 	memset(p, 0, sizeof (pushButton_t));
260 	p->caption = "Exit";
261 	p->x = 345;
262 	p->y = 264;
263 	p->w = 73;
264 	p->h = 16;
265 	p->callbackFuncOnUp = pbExit;
266 	p->visible = true;
267 
268 	// scrollbar buttons
269 
270 	p = &pushButtons[2];
271 	memset(p, 0, sizeof (pushButton_t));
272 	p->caption = ARROW_LEFT_STRING;
273 	p->x = 314;
274 	p->y = 234;
275 	p->w = 23;
276 	p->h = 13;
277 	p->preDelay = 1;
278 	p->delayFrames = 3;
279 	p->callbackFuncOnDown = pbResampleTonesDown;
280 	p->visible = true;
281 
282 	p = &pushButtons[3];
283 	memset(p, 0, sizeof (pushButton_t));
284 	p->caption = ARROW_RIGHT_STRING;
285 	p->x = 395;
286 	p->y = 234;
287 	p->w = 23;
288 	p->h = 13;
289 	p->preDelay = 1;
290 	p->delayFrames = 3;
291 	p->callbackFuncOnDown = pbResampleTonesUp;
292 	p->visible = true;
293 
294 	// echo num scrollbar
295 	s = &scrollBars[0];
296 	memset(s, 0, sizeof (scrollBar_t));
297 	s->x = 337;
298 	s->y = 234;
299 	s->w = 58;
300 	s->h = 13;
301 	s->callbackFunc = sbSetResampleTones;
302 	s->visible = true;
303 	setScrollBarPageLength(0, 1);
304 	setScrollBarEnd(0, 36 * 2);
305 }
306 
pbSampleResample(void)307 void pbSampleResample(void)
308 {
309 	uint16_t i;
310 
311 	if (editor.curInstr == 0 ||
312 		instr[editor.curInstr] == NULL ||
313 		instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
314 	{
315 		return;
316 	}
317 
318 	setupResampleBoxWidgets();
319 	windowOpen();
320 
321 	outOfMemory = false;
322 
323 	exitFlag = false;
324 	while (ui.sysReqShown)
325 	{
326 		readInput();
327 		if (ui.sysReqEnterPressed)
328 			pbDoResampling();
329 
330 		setSyncedReplayerVars();
331 		handleRedrawing();
332 
333 		drawResampleBox();
334 		setScrollBarPos(0, smpEd_RelReSmp + 36, false);
335 		drawCheckBox(0);
336 		for (i = 0; i < 4; i++) drawPushButton(i);
337 		drawScrollBar(0);
338 
339 		flipFrame();
340 	}
341 
342 	for (i = 0; i < 4; i++) hidePushButton(i);
343 	hideScrollBar(0);
344 
345 	windowClose(false);
346 
347 	if (outOfMemory)
348 		okBox(0, "System message", "Not enough memory!");
349 }
350 
cbEchoAddMemory(void)351 static void cbEchoAddMemory(void)
352 {
353 	echo_AddMemory ^= 1;
354 }
355 
sbSetEchoNumPos(uint32_t pos)356 static void sbSetEchoNumPos(uint32_t pos)
357 {
358 	if (echo_nEcho != (int32_t)pos)
359 		echo_nEcho = (int16_t)pos;
360 }
361 
sbSetEchoDistPos(uint32_t pos)362 static void sbSetEchoDistPos(uint32_t pos)
363 {
364 	if (echo_Distance != (int32_t)pos)
365 		echo_Distance = (int32_t)pos;
366 }
367 
sbSetEchoFadeoutPos(uint32_t pos)368 static void sbSetEchoFadeoutPos(uint32_t pos)
369 {
370 	if (echo_VolChange != (int32_t)pos)
371 		echo_VolChange = (int16_t)pos;
372 }
373 
pbEchoNumDown(void)374 static void pbEchoNumDown(void)
375 {
376 	if (echo_nEcho > 0)
377 		echo_nEcho--;
378 }
379 
pbEchoNumUp(void)380 static void pbEchoNumUp(void)
381 {
382 	if (echo_nEcho < 64)
383 		echo_nEcho++;
384 }
385 
pbEchoDistDown(void)386 static void pbEchoDistDown(void)
387 {
388 	if (echo_Distance > 0)
389 		echo_Distance--;
390 }
391 
pbEchoDistUp(void)392 static void pbEchoDistUp(void)
393 {
394 	if (echo_Distance < 16384)
395 		echo_Distance++;
396 }
397 
pbEchoFadeoutDown(void)398 static void pbEchoFadeoutDown(void)
399 {
400 	if (echo_VolChange > 0)
401 		echo_VolChange--;
402 }
403 
pbEchoFadeoutUp(void)404 static void pbEchoFadeoutUp(void)
405 {
406 	if (echo_VolChange < 100)
407 		echo_VolChange++;
408 }
409 
createEchoThread(void * ptr)410 static int32_t SDLCALL createEchoThread(void *ptr)
411 {
412 	smpPtr_t sp;
413 
414 	if (echo_nEcho < 1)
415 	{
416 		ui.sysReqShown = false;
417 		return true;
418 	}
419 
420 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
421 
422 	int32_t readLen = s->length;
423 	int8_t *readPtr = s->dataPtr;
424 	bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
425 	int32_t distance = echo_Distance * 16;
426 	double dVolChange = echo_VolChange / 100.0;
427 
428 	// calculate real number of echoes
429 	double dSmp = sample16Bit ? 32768.0 : 128.0;
430 	int32_t k = 0;
431 	while (k++ < echo_nEcho && dSmp >= 1.0)
432 		dSmp *= dVolChange;
433 	int32_t nEchoes = k + 1;
434 
435 	if (nEchoes < 1)
436 	{
437 		ui.sysReqShown = false;
438 		return true;
439 	}
440 
441 	// set write length (either original length or full echo length)
442 	int32_t writeLen = readLen;
443 	if (echo_AddMemory)
444 	{
445 		int64_t tmp64 = (int64_t)distance * (nEchoes - 1);
446 
447 		tmp64 += writeLen;
448 		if (tmp64 > MAX_SAMPLE_LEN)
449 			tmp64 = MAX_SAMPLE_LEN;
450 
451 		writeLen = (uint32_t)tmp64;
452 	}
453 
454 	if (!allocateSmpDataPtr(&sp, writeLen, sample16Bit))
455 	{
456 		outOfMemory = true;
457 		setMouseBusy(false);
458 		ui.sysReqShown = false;
459 		return false;
460 	}
461 
462 	pauseAudio();
463 	unfixSample(s);
464 
465 	int32_t writeIdx = 0;
466 
467 	if (sample16Bit)
468 	{
469 		const int16_t *readPtr16 = (const int16_t *)readPtr;
470 		int16_t *writePtr16 = (int16_t *)sp.ptr;
471 
472 		while (writeIdx < writeLen)
473 		{
474 			double dSmpOut = 0.0;
475 			double dSmpMul = 1.0;
476 
477 			int32_t echoRead = writeIdx;
478 			int32_t echoCycle = nEchoes;
479 
480 			while (!stopThread)
481 			{
482 				if (echoRead < readLen)
483 					dSmpOut += (int32_t)readPtr16[echoRead] * dSmpMul;
484 
485 				dSmpMul *= dVolChange;
486 
487 				echoRead -= distance;
488 				if (echoRead <= 0 || --echoCycle <= 0)
489 					break;
490 			}
491 
492 			DROUND(dSmpOut);
493 
494 			int32_t smp32 = (int32_t)dSmpOut;
495 			CLAMP16(smp32);
496 			writePtr16[writeIdx++] = (int16_t)smp32;
497 		}
498 	}
499 	else // 8-bit
500 	{
501 		int8_t *writePtr8 = sp.ptr;
502 		while (writeIdx < writeLen)
503 		{
504 			double dSmpOut = 0.0;
505 			double dSmpMul = 1.0;
506 
507 			int32_t echoRead = writeIdx;
508 			int32_t echoCycle = nEchoes;
509 
510 			while (!stopThread)
511 			{
512 				if (echoRead < readLen)
513 					dSmpOut += (int32_t)readPtr[echoRead] * dSmpMul;
514 
515 				dSmpMul *= dVolChange;
516 
517 				echoRead -= distance;
518 				if (echoRead <= 0 || --echoCycle <= 0)
519 					break;
520 			}
521 
522 			DROUND(dSmpOut);
523 
524 			int32_t smp32 = (int32_t)dSmpOut;
525 			CLAMP8(smp32);
526 			writePtr8[writeIdx++] = (int8_t)smp32;
527 		}
528 	}
529 
530 	freeSmpData(s);
531 	setSmpDataPtr(s, &sp);
532 
533 	if (stopThread) // we stopped before echo was done, realloc length
534 	{
535 		writeLen = writeIdx;
536 		reallocateSmpData(s, writeLen, sample16Bit);
537 		editor.updateCurSmp = true;
538 	}
539 
540 	s->length = writeLen;
541 
542 	fixSample(s);
543 	resumeAudio();
544 
545 	setSongModifiedFlag();
546 	setMouseBusy(false);
547 
548 	ui.sysReqShown = false;
549 	return true;
550 
551 	(void)ptr;
552 }
553 
pbCreateEcho(void)554 static void pbCreateEcho(void)
555 {
556 	stopThread = false;
557 
558 	mouseAnimOn();
559 	thread = SDL_CreateThread(createEchoThread, NULL, NULL);
560 	if (thread == NULL)
561 	{
562 		okBox(0, "System message", "Couldn't create thread!");
563 		return;
564 	}
565 
566 	SDL_DetachThread(thread);
567 }
568 
drawEchoBox(void)569 static void drawEchoBox(void)
570 {
571 	const int16_t x = 171;
572 	const int16_t y = 220;
573 	const int16_t w = 291;
574 	const int16_t h = 66;
575 
576 	// main fill
577 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
578 
579 	// outer border
580 	vLine(x,         y,         h - 1, PAL_BUTTON1);
581 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
582 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
583 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
584 
585 	// inner border
586 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
587 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
588 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
589 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
590 
591 	textOutShadow(177, 226, PAL_FORGRND, PAL_BUTTON2, "Number of echoes");
592 	textOutShadow(177, 240, PAL_FORGRND, PAL_BUTTON2, "Echo distance");
593 	textOutShadow(177, 254, PAL_FORGRND, PAL_BUTTON2, "Fade out");
594 	textOutShadow(192, 270, PAL_FORGRND, PAL_BUTTON2, "Add memory to sample");
595 
596 	assert(echo_nEcho <= 64);
597 	charOut(315 + (2 * 7), 226, PAL_FORGRND, '0' + (char)(echo_nEcho / 10));
598 	charOut(315 + (3 * 7), 226, PAL_FORGRND, '0' + (echo_nEcho % 10));
599 
600 	assert(echo_Distance <= 0x4000);
601 	hexOut(308, 240, PAL_FORGRND, echo_Distance << 4, 5);
602 
603 	assert(echo_VolChange <= 100);
604 	textOutFixed(312, 254, PAL_FORGRND, PAL_BUTTONS, dec3StrTab[echo_VolChange]);
605 
606 	charOutShadow(313 + (3 * 7), 254, PAL_FORGRND, PAL_BUTTON2, '%');
607 }
608 
setupEchoBoxWidgets(void)609 static void setupEchoBoxWidgets(void)
610 {
611 	checkBox_t *c;
612 	pushButton_t *p;
613 	scrollBar_t *s;
614 
615 	// "Add memory to sample" checkbox
616 	c = &checkBoxes[0];
617 	memset(c, 0, sizeof (checkBox_t));
618 	c->x = 176;
619 	c->y = 268;
620 	c->clickAreaWidth = 146;
621 	c->clickAreaHeight = 12;
622 	c->callbackFunc = cbEchoAddMemory;
623 	c->checked = echo_AddMemory ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED;
624 	c->visible = true;
625 
626 	// "Apply" pushbutton
627 	p = &pushButtons[0];
628 	memset(p, 0, sizeof (pushButton_t));
629 	p->caption = "Apply";
630 	p->x = 345;
631 	p->y = 266;
632 	p->w = 56;
633 	p->h = 16;
634 	p->callbackFuncOnUp = pbCreateEcho;
635 	p->visible = true;
636 
637 	// "Exit" pushbutton
638 	p = &pushButtons[1];
639 	memset(p, 0, sizeof (pushButton_t));
640 	p->caption = "Exit";
641 	p->x = 402;
642 	p->y = 266;
643 	p->w = 55;
644 	p->h = 16;
645 	p->callbackFuncOnUp = pbExit;
646 	p->visible = true;
647 
648 	// scrollbar buttons
649 
650 	p = &pushButtons[2];
651 	memset(p, 0, sizeof (pushButton_t));
652 	p->caption = ARROW_LEFT_STRING;
653 	p->x = 345;
654 	p->y = 224;
655 	p->w = 23;
656 	p->h = 13;
657 	p->preDelay = 1;
658 	p->delayFrames = 3;
659 	p->callbackFuncOnDown = pbEchoNumDown;
660 	p->visible = true;
661 
662 	p = &pushButtons[3];
663 	memset(p, 0, sizeof (pushButton_t));
664 	p->caption = ARROW_RIGHT_STRING;
665 	p->x = 434;
666 	p->y = 224;
667 	p->w = 23;
668 	p->h = 13;
669 	p->preDelay = 1;
670 	p->delayFrames = 3;
671 	p->callbackFuncOnDown = pbEchoNumUp;
672 	p->visible = true;
673 
674 	p = &pushButtons[4];
675 	memset(p, 0, sizeof (pushButton_t));
676 	p->caption = ARROW_LEFT_STRING;
677 	p->x = 345;
678 	p->y = 238;
679 	p->w = 23;
680 	p->h = 13;
681 	p->preDelay = 1;
682 	p->delayFrames = 3;
683 	p->callbackFuncOnDown = pbEchoDistDown;
684 	p->visible = true;
685 
686 	p = &pushButtons[5];
687 	memset(p, 0, sizeof (pushButton_t));
688 	p->caption = ARROW_RIGHT_STRING;
689 	p->x = 434;
690 	p->y = 238;
691 	p->w = 23;
692 	p->h = 13;
693 	p->preDelay = 1;
694 	p->delayFrames = 3;
695 	p->callbackFuncOnDown = pbEchoDistUp;
696 	p->visible = true;
697 
698 	p = &pushButtons[6];
699 	memset(p, 0, sizeof (pushButton_t));
700 	p->caption = ARROW_LEFT_STRING;
701 	p->x = 345;
702 	p->y = 252;
703 	p->w = 23;
704 	p->h = 13;
705 	p->preDelay = 1;
706 	p->delayFrames = 3;
707 	p->callbackFuncOnDown = pbEchoFadeoutDown;
708 	p->visible = true;
709 
710 	p = &pushButtons[7];
711 	memset(p, 0, sizeof (pushButton_t));
712 	p->caption = ARROW_RIGHT_STRING;
713 	p->x = 434;
714 	p->y = 252;
715 	p->w = 23;
716 	p->h = 13;
717 	p->preDelay = 1;
718 	p->delayFrames = 3;
719 	p->callbackFuncOnDown = pbEchoFadeoutUp;
720 	p->visible = true;
721 
722 	// echo num scrollbar
723 	s = &scrollBars[0];
724 	memset(s, 0, sizeof (scrollBar_t));
725 	s->x = 368;
726 	s->y = 224;
727 	s->w = 66;
728 	s->h = 13;
729 	s->callbackFunc = sbSetEchoNumPos;
730 	s->visible = true;
731 	setScrollBarPageLength(0, 1);
732 	setScrollBarEnd(0, 64);
733 
734 	// echo distance scrollbar
735 	s = &scrollBars[1];
736 	memset(s, 0, sizeof (scrollBar_t));
737 	s->x = 368;
738 	s->y = 238;
739 	s->w = 66;
740 	s->h = 13;
741 	s->callbackFunc = sbSetEchoDistPos;
742 	s->visible = true;
743 	setScrollBarPageLength(1, 1);
744 	setScrollBarEnd(1, 16384);
745 
746 	// echo fadeout scrollbar
747 	s = &scrollBars[2];
748 	memset(s, 0, sizeof (scrollBar_t));
749 	s->x = 368;
750 	s->y = 252;
751 	s->w = 66;
752 	s->h = 13;
753 	s->callbackFunc = sbSetEchoFadeoutPos;
754 	s->visible = true;
755 	setScrollBarPageLength(2, 1);
756 	setScrollBarEnd(2, 100);
757 }
758 
handleEchoToolPanic(void)759 void handleEchoToolPanic(void)
760 {
761 	stopThread = true;
762 }
763 
pbSampleEcho(void)764 void pbSampleEcho(void)
765 {
766 	if (editor.curInstr == 0 ||
767 		instr[editor.curInstr] == NULL ||
768 		instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
769 	{
770 		return;
771 	}
772 
773 	setupEchoBoxWidgets();
774 	windowOpen();
775 
776 	outOfMemory = false;
777 
778 	exitFlag = false;
779 	while (ui.sysReqShown)
780 	{
781 		readInput();
782 		if (ui.sysReqEnterPressed)
783 			pbCreateEcho();
784 
785 		setSyncedReplayerVars();
786 		handleRedrawing();
787 
788 		drawEchoBox();
789 		setScrollBarPos(0, echo_nEcho, false);
790 		setScrollBarPos(1, echo_Distance, false);
791 		setScrollBarPos(2, echo_VolChange, false);
792 		drawCheckBox(0);
793 		for (uint16_t i = 0; i < 8; i++) drawPushButton(i);
794 		for (uint16_t i = 0; i < 3; i++) drawScrollBar(i);
795 
796 		flipFrame();
797 	}
798 
799 	hideCheckBox(0);
800 	for (uint16_t i = 0; i < 8; i++) hidePushButton(i);
801 	for (uint16_t i = 0; i < 3; i++) hideScrollBar(i);
802 
803 	windowClose(echo_AddMemory ? false : true);
804 
805 	if (outOfMemory)
806 		okBox(0, "System message", "Not enough memory!");
807 }
808 
mixThread(void * ptr)809 static int32_t SDLCALL mixThread(void *ptr)
810 {
811 	smpPtr_t sp;
812 
813 	int8_t *dstPtr, *mixPtr;
814 	uint8_t mixFlags, dstFlags;
815 	int32_t dstLen, mixLen;
816 
817 	int16_t dstIns = editor.curInstr;
818 	int16_t dstSmp = editor.curSmp;
819 	int16_t mixIns = editor.srcInstr;
820 	int16_t mixSmp = editor.srcSmp;
821 
822 	sample_t *s = &instr[dstIns]->smp[dstSmp];
823 	sample_t *sSrc = &instr[mixIns]->smp[mixSmp];
824 
825 	if (dstIns == mixIns && dstSmp == mixSmp)
826 	{
827 		setMouseBusy(false);
828 		ui.sysReqShown = false;
829 		return true;
830 	}
831 
832 	if (instr[mixIns] == NULL)
833 	{
834 		mixLen = 0;
835 		mixPtr = NULL;
836 		mixFlags = 0;
837 	}
838 	else
839 	{
840 		mixLen = sSrc->length;
841 		mixPtr = sSrc->dataPtr;
842 		mixFlags = sSrc->flags;
843 
844 		if (mixPtr == NULL)
845 		{
846 			mixLen = 0;
847 			mixFlags = 0;
848 		}
849 	}
850 
851 	if (instr[dstIns] == NULL)
852 	{
853 		dstLen = 0;
854 		dstPtr = NULL;
855 		dstFlags = 0;
856 	}
857 	else
858 	{
859 		dstLen = s->length;
860 		dstPtr = s->dataPtr;
861 		dstFlags = s->flags;
862 
863 		if (dstPtr == NULL)
864 		{
865 			dstLen = 0;
866 			dstFlags = 0;
867 		}
868 	}
869 
870 	bool src16Bits = !!(mixFlags & SAMPLE_16BIT);
871 	bool dst16Bits = !!(dstFlags & SAMPLE_16BIT);
872 
873 	int32_t maxLen = (dstLen > mixLen) ? dstLen : mixLen;
874 	if (maxLen == 0)
875 	{
876 		setMouseBusy(false);
877 		ui.sysReqShown = false;
878 		return true;
879 	}
880 
881 	if (!allocateSmpDataPtr(&sp, maxLen, dst16Bits))
882 	{
883 		outOfMemory = true;
884 		setMouseBusy(false);
885 		ui.sysReqShown = false;
886 		return true;
887 	}
888 	memset(sp.ptr, 0, maxLen);
889 
890 	if (instr[dstIns] == NULL && !allocateInstr(dstIns))
891 	{
892 		outOfMemory = true;
893 		setMouseBusy(false);
894 		ui.sysReqShown = false;
895 		return true;
896 	}
897 
898 	pauseAudio();
899 	unfixSample(s);
900 
901 	// unfix source sample
902 	if (instr[mixIns] != NULL)
903 		unfixSample(sSrc);
904 
905 	const double dAmp1 = mix_Balance / 100.0;
906 	const double dAmp2 = 1.0 - dAmp1;
907 	const double dSmp1ScaleMul = src16Bits ? (1.0 / 32768.0) : (1.0 / 128.0);
908 	const double dSmp2ScaleMul = dst16Bits ? (1.0 / 32768.0) : (1.0 / 128.0);
909 	const double dNormalizeMul = dst16Bits ? 32768.0 : 128.0;
910 
911 	for (int32_t i = 0; i < maxLen; i++)
912 	{
913 		double dSmp1 = (i >= mixLen) ? 0.0 : (getSampleValue(mixPtr, i, src16Bits) * dSmp1ScaleMul); // -1.0 .. 0.999inf
914 		double dSmp2 = (i >= dstLen) ? 0.0 : (getSampleValue(dstPtr, i, dst16Bits) * dSmp2ScaleMul); // -1.0 .. 0.999inf
915 
916 		const double dSmp = ((dSmp1 * dAmp1) + (dSmp2 * dAmp2)) * dNormalizeMul;
917 		putSampleValue(sp.ptr, i, dSmp, dst16Bits);
918 	}
919 
920 	freeSmpData(s);
921 	setSmpDataPtr(s, &sp);
922 
923 	s->length = maxLen;
924 	s->flags = dstFlags;
925 
926 	fixSample(s);
927 
928 	// re-fix source sample again
929 	if (instr[mixIns] != NULL)
930 		fixSample(sSrc);
931 
932 	resumeAudio();
933 
934 	setSongModifiedFlag();
935 	setMouseBusy(false);
936 
937 	ui.sysReqShown = false;
938 	return true;
939 
940 	(void)ptr;
941 }
942 
pbMix(void)943 static void pbMix(void)
944 {
945 	mouseAnimOn();
946 	thread = SDL_CreateThread(mixThread, NULL, NULL);
947 	if (thread == NULL)
948 	{
949 		okBox(0, "System message", "Couldn't create thread!");
950 		return;
951 	}
952 
953 	SDL_DetachThread(thread);
954 }
955 
sbSetMixBalancePos(uint32_t pos)956 static void sbSetMixBalancePos(uint32_t pos)
957 {
958 	if (mix_Balance != (int8_t)pos)
959 		mix_Balance = (int8_t)pos;
960 }
961 
pbMixBalanceDown(void)962 static void pbMixBalanceDown(void)
963 {
964 	if (mix_Balance > 0)
965 		mix_Balance--;
966 }
967 
pbMixBalanceUp(void)968 static void pbMixBalanceUp(void)
969 {
970 	if (mix_Balance < 100)
971 		mix_Balance++;
972 }
973 
drawMixSampleBox(void)974 static void drawMixSampleBox(void)
975 {
976 	const int16_t x = 192;
977 	const int16_t y = 240;
978 	const int16_t w = 248;
979 	const int16_t h = 38;
980 
981 	// main fill
982 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
983 
984 	// outer border
985 	vLine(x,         y,         h - 1, PAL_BUTTON1);
986 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
987 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
988 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
989 
990 	// inner border
991 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
992 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
993 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
994 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
995 
996 	textOutShadow(198, 246, PAL_FORGRND, PAL_BUTTON2, "Mixing balance");
997 
998 	assert((mix_Balance >= 0) && (mix_Balance <= 100));
999 	textOutFixed(299, 246, PAL_FORGRND, PAL_BUTTONS, dec3StrTab[mix_Balance]);
1000 }
1001 
setupMixBoxWidgets(void)1002 static void setupMixBoxWidgets(void)
1003 {
1004 	pushButton_t *p;
1005 	scrollBar_t *s;
1006 
1007 	// "Apply" pushbutton
1008 	p = &pushButtons[0];
1009 	memset(p, 0, sizeof (pushButton_t));
1010 	p->caption = "Apply";
1011 	p->x = 197;
1012 	p->y = 258;
1013 	p->w = 73;
1014 	p->h = 16;
1015 	p->callbackFuncOnUp = pbMix;
1016 	p->visible = true;
1017 
1018 	// "Exit" pushbutton
1019 	p = &pushButtons[1];
1020 	memset(p, 0, sizeof (pushButton_t));
1021 	p->caption = "Exit";
1022 	p->x = 361;
1023 	p->y = 258;
1024 	p->w = 73;
1025 	p->h = 16;
1026 	p->callbackFuncOnUp = pbExit;
1027 	p->visible = true;
1028 
1029 	// scrollbar buttons
1030 
1031 	p = &pushButtons[2];
1032 	memset(p, 0, sizeof (pushButton_t));
1033 	p->caption = ARROW_LEFT_STRING;
1034 	p->x = 322;
1035 	p->y = 244;
1036 	p->w = 23;
1037 	p->h = 13;
1038 	p->preDelay = 1;
1039 	p->delayFrames = 3;
1040 	p->callbackFuncOnDown = pbMixBalanceDown;
1041 	p->visible = true;
1042 
1043 	p = &pushButtons[3];
1044 	memset(p, 0, sizeof (pushButton_t));
1045 	p->caption = ARROW_RIGHT_STRING;
1046 	p->x = 411;
1047 	p->y = 244;
1048 	p->w = 23;
1049 	p->h = 13;
1050 	p->preDelay = 1;
1051 	p->delayFrames = 3;
1052 	p->callbackFuncOnDown = pbMixBalanceUp;
1053 	p->visible = true;
1054 
1055 	// mixing balance scrollbar
1056 	s = &scrollBars[0];
1057 	memset(s, 0, sizeof (scrollBar_t));
1058 	s->x = 345;
1059 	s->y = 244;
1060 	s->w = 66;
1061 	s->h = 13;
1062 	s->callbackFunc = sbSetMixBalancePos;
1063 	s->visible = true;
1064 	setScrollBarPageLength(0, 1);
1065 	setScrollBarEnd(0, 100);
1066 }
1067 
pbSampleMix(void)1068 void pbSampleMix(void)
1069 {
1070 	uint16_t i;
1071 
1072 	if (editor.curInstr == 0)
1073 		return;
1074 
1075 	setupMixBoxWidgets();
1076 	windowOpen();
1077 
1078 	outOfMemory = false;
1079 
1080 	exitFlag = false;
1081 	while (ui.sysReqShown)
1082 	{
1083 		readInput();
1084 		if (ui.sysReqEnterPressed)
1085 			pbMix();
1086 
1087 		setSyncedReplayerVars();
1088 		handleRedrawing();
1089 
1090 		drawMixSampleBox();
1091 		setScrollBarPos(0, mix_Balance, false);
1092 		for (i = 0; i < 4; i++) drawPushButton(i);
1093 		drawScrollBar(0);
1094 
1095 		flipFrame();
1096 	}
1097 
1098 	for (i = 0; i < 4; i++) hidePushButton(i);
1099 	hideScrollBar(0);
1100 
1101 	windowClose(false);
1102 
1103 	if (outOfMemory)
1104 		okBox(0, "System message", "Not enough memory!");
1105 }
1106 
sbSetStartVolPos(uint32_t pos)1107 static void sbSetStartVolPos(uint32_t pos)
1108 {
1109 	int32_t val = (int32_t)(pos - 200);
1110 	if (val != (int32_t)dVol_StartVol)
1111 	{
1112 		     if (ABS(val)       < 10) val =    0;
1113 		else if (ABS(val - 100) < 10) val =  100;
1114 		else if (ABS(val + 100) < 10) val = -100;
1115 
1116 		dVol_StartVol = (double)val;
1117 	}
1118 }
1119 
sbSetEndVolPos(uint32_t pos)1120 static void sbSetEndVolPos(uint32_t pos)
1121 {
1122 	int32_t val = (int32_t)(pos - 200);
1123 	if (val != (int32_t)dVol_EndVol)
1124 	{
1125 		     if (ABS(val)       < 10) val =    0;
1126 		else if (ABS(val - 100) < 10) val =  100;
1127 		else if (ABS(val + 100) < 10) val = -100;
1128 
1129 		dVol_EndVol = val;
1130 	}
1131 }
1132 
pbSampStartVolDown(void)1133 static void pbSampStartVolDown(void)
1134 {
1135 	if (dVol_StartVol > -200.0)
1136 		dVol_StartVol -= 1.0;
1137 
1138 	dVol_StartVol = floor(dVol_StartVol);
1139 }
1140 
pbSampStartVolUp(void)1141 static void pbSampStartVolUp(void)
1142 {
1143 	if (dVol_StartVol < 200.0)
1144 		dVol_StartVol += 1.0;
1145 
1146 	dVol_StartVol = floor(dVol_StartVol);
1147 }
1148 
pbSampEndVolDown(void)1149 static void pbSampEndVolDown(void)
1150 {
1151 	if (dVol_EndVol > -200.0)
1152 		dVol_EndVol -= 1.0;
1153 
1154 	dVol_EndVol = floor(dVol_EndVol);
1155 }
1156 
pbSampEndVolUp(void)1157 static void pbSampEndVolUp(void)
1158 {
1159 	if (dVol_EndVol < 200.0)
1160 		dVol_EndVol += 1.0;
1161 
1162 	dVol_EndVol = floor(dVol_EndVol);
1163 }
1164 
applyVolumeThread(void * ptr)1165 static int32_t SDLCALL applyVolumeThread(void *ptr)
1166 {
1167 	int32_t x1, x2;
1168 
1169 	if (instr[editor.curInstr] == NULL)
1170 		goto applyVolumeExit;
1171 
1172 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
1173 
1174 	if (smpEd_Rx1 < smpEd_Rx2)
1175 	{
1176 		x1 = smpEd_Rx1;
1177 		x2 = smpEd_Rx2;
1178 
1179 		if (x2 > s->length)
1180 			x2 = s->length;
1181 
1182 		if (x1 < 0)
1183 			x1 = 0;
1184 
1185 		if (x2 <= x1)
1186 			goto applyVolumeExit;
1187 	}
1188 	else
1189 	{
1190 		// no mark, operate on whole sample
1191 		x1 = 0;
1192 		x2 = s->length;
1193 	}
1194 
1195 	const int32_t len = x2 - x1;
1196 	if (len <= 0)
1197 		goto applyVolumeExit;
1198 
1199 	bool mustInterpolate = (dVol_StartVol != dVol_EndVol);
1200 	const double dVol = dVol_StartVol / 100.0;
1201 	const double dPosMul = ((dVol_EndVol / 100.0) - dVol) / len;
1202 
1203 	pauseAudio();
1204 	unfixSample(s);
1205 	if (s->flags & SAMPLE_16BIT)
1206 	{
1207 		int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
1208 		if (mustInterpolate)
1209 		{
1210 			for (int32_t i = 0; i < len; i++)
1211 			{
1212 				double dSmp = (int32_t)ptr16[i] * (dVol + (i * dPosMul)); // linear interpolation
1213 				DROUND(dSmp);
1214 
1215 				int32_t smp32 = (int32_t)dSmp;
1216 				CLAMP16(smp32);
1217 				ptr16[i] = (int16_t)smp32;
1218 			}
1219 
1220 		}
1221 		else // no interpolation needed
1222 		{
1223 			for (int32_t i = 0; i < len; i++)
1224 			{
1225 				double dSmp = (int32_t)ptr16[i] * dVol;
1226 				DROUND(dSmp);
1227 
1228 				int32_t smp32 = (int32_t)dSmp;
1229 				CLAMP16(smp32);
1230 				ptr16[i] = (int16_t)smp32;
1231 			}
1232 		}
1233 	}
1234 	else // 8-bit sample
1235 	{
1236 		int8_t *ptr8 = s->dataPtr + x1;
1237 		if (mustInterpolate)
1238 		{
1239 			for (int32_t i = 0; i < len; i++)
1240 			{
1241 				double dSmp = (int32_t)ptr8[i] * (dVol + (i * dPosMul)); // linear interpolation
1242 				DROUND(dSmp);
1243 
1244 				int32_t smp32 = (int32_t)dSmp;
1245 				CLAMP8(smp32);
1246 				ptr8[i] = (int8_t)smp32;
1247 			}
1248 		}
1249 		else // no interpolation needed
1250 		{
1251 			for (int32_t i = 0; i < len; i++)
1252 			{
1253 				double dSmp = (int32_t)ptr8[i] * dVol;
1254 				DROUND(dSmp);
1255 
1256 				int32_t smp32 = (int32_t)dSmp;
1257 				CLAMP8(smp32);
1258 				ptr8[i] = (int8_t)smp32;
1259 			}
1260 		}
1261 	}
1262 	fixSample(s);
1263 	resumeAudio();
1264 
1265 	setSongModifiedFlag();
1266 
1267 applyVolumeExit:
1268 	setMouseBusy(false);
1269 	ui.sysReqShown = false;
1270 
1271 	return true;
1272 
1273 	(void)ptr;
1274 }
1275 
pbApplyVolume(void)1276 static void pbApplyVolume(void)
1277 {
1278 	if (dVol_StartVol == 100.0 && dVol_EndVol == 100.0)
1279 	{
1280 		ui.sysReqShown = false;
1281 		return; // no volume change to be done
1282 	}
1283 
1284 	mouseAnimOn();
1285 	thread = SDL_CreateThread(applyVolumeThread, NULL, NULL);
1286 	if (thread == NULL)
1287 	{
1288 		okBox(0, "System message", "Couldn't create thread!");
1289 		return;
1290 	}
1291 
1292 	SDL_DetachThread(thread);
1293 }
1294 
getMaxScaleThread(void * ptr)1295 static int32_t SDLCALL getMaxScaleThread(void *ptr)
1296 {
1297 	int32_t x1, x2;
1298 
1299 	if (instr[editor.curInstr] == NULL)
1300 		goto getScaleExit;
1301 
1302 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
1303 
1304 	if (smpEd_Rx1 < smpEd_Rx2)
1305 	{
1306 		x1 = smpEd_Rx1;
1307 		x2 = smpEd_Rx2;
1308 
1309 		if (x2 > s->length)
1310 			x2 = s->length;
1311 
1312 		if (x1 < 0)
1313 			x1 = 0;
1314 
1315 		if (x2 <= x1)
1316 			goto getScaleExit;
1317 	}
1318 	else
1319 	{
1320 		// no sample marking, operate on the whole sample
1321 		x1 = 0;
1322 		x2 = s->length;
1323 	}
1324 
1325 	uint32_t len = x2 - x1;
1326 	if (len <= 0)
1327 	{
1328 		dVol_StartVol = dVol_EndVol = 100.0;
1329 		goto getScaleExit;
1330 	}
1331 
1332 	double dVolChange = 100.0;
1333 
1334 	/* If sample is looped and the loopEnd point is inside the marked range,
1335 	** we need to unfix the fixed interpolation sample before scanning,
1336 	** and fix it again after we're done.
1337 	*/
1338 	bool hasLoop = GET_LOOPTYPE(s->flags) != LOOP_OFF;
1339 	const int32_t loopEnd = s->loopStart + s->loopLength;
1340 	bool fixedSampleInRange = hasLoop && (x1 <= loopEnd) && (x2 >= loopEnd);
1341 
1342 	if (fixedSampleInRange)
1343 		unfixSample(s);
1344 
1345 	int32_t maxAmp = 0;
1346 	if (s->flags & SAMPLE_16BIT)
1347 	{
1348 		const int16_t *ptr16 = (const int16_t *)s->dataPtr + x1;
1349 		for (uint32_t i = 0; i < len; i++)
1350 		{
1351 			const int32_t absSmp = ABS(ptr16[i]);
1352 			if (absSmp > maxAmp)
1353 				maxAmp = absSmp;
1354 		}
1355 
1356 		if (maxAmp > 0)
1357 			dVolChange = (32767.0 / maxAmp) * 100.0;
1358 	}
1359 	else // 8-bit
1360 	{
1361 		const int8_t *ptr8 = (const int8_t *)&s->dataPtr[x1];
1362 		for (uint32_t i = 0; i < len; i++)
1363 		{
1364 			const int32_t absSmp = ABS(ptr8[i]);
1365 			if (absSmp > maxAmp)
1366 				maxAmp = absSmp;
1367 		}
1368 
1369 		if (maxAmp > 0)
1370 			dVolChange = (127.0 / maxAmp) * 100.0;
1371 	}
1372 
1373 	if (fixedSampleInRange)
1374 		fixSample(s);
1375 
1376 	if (dVolChange < 100.0) // yes, this can happen...
1377 		dVolChange = 100.0;
1378 
1379 	dVol_StartVol = dVol_EndVol = dVolChange;
1380 
1381 getScaleExit:
1382 	setMouseBusy(false);
1383 	return true;
1384 
1385 	(void)ptr;
1386 }
1387 
pbGetMaxScale(void)1388 static void pbGetMaxScale(void)
1389 {
1390 	mouseAnimOn();
1391 	thread = SDL_CreateThread(getMaxScaleThread, NULL, NULL);
1392 	if (thread == NULL)
1393 	{
1394 		okBox(0, "System message", "Couldn't create thread!");
1395 		return;
1396 	}
1397 
1398 	SDL_DetachThread(thread);
1399 }
1400 
drawSampleVolumeBox(void)1401 static void drawSampleVolumeBox(void)
1402 {
1403 	char sign;
1404 	const int16_t x = 166;
1405 	const int16_t y = 230;
1406 	const int16_t w = 301;
1407 	const int16_t h = 52;
1408 	uint32_t val;
1409 
1410 	// main fill
1411 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
1412 
1413 	// outer border
1414 	vLine(x,         y,         h - 1, PAL_BUTTON1);
1415 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
1416 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
1417 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
1418 
1419 	// inner border
1420 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
1421 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
1422 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
1423 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
1424 
1425 	textOutShadow(172, 236, PAL_FORGRND, PAL_BUTTON2, "Start volume");
1426 	textOutShadow(172, 250, PAL_FORGRND, PAL_BUTTON2, "End volume");
1427 	charOutShadow(282, 236, PAL_FORGRND, PAL_BUTTON2, '%');
1428 	charOutShadow(282, 250, PAL_FORGRND, PAL_BUTTON2, '%');
1429 
1430 	const int32_t startVol = (int32_t)dVol_StartVol;
1431 	const int32_t endVol = (int32_t)dVol_EndVol;
1432 
1433 	if (startVol > 200)
1434 	{
1435 		charOut(253, 236, PAL_FORGRND, '>');
1436 		charOut(260, 236, PAL_FORGRND, '2');
1437 		charOut(267, 236, PAL_FORGRND, '0');
1438 		charOut(274, 236, PAL_FORGRND, '0');
1439 	}
1440 	else
1441 	{
1442 		     if (startVol == 0) sign = ' ';
1443 		else if (startVol  < 0) sign = '-';
1444 		else sign = '+';
1445 
1446 		val = ABS(startVol);
1447 		if (val > 99)
1448 		{
1449 			charOut(253, 236, PAL_FORGRND, sign);
1450 			charOut(260, 236, PAL_FORGRND, '0' + (char)(val / 100));
1451 			charOut(267, 236, PAL_FORGRND, '0' + ((val / 10) % 10));
1452 			charOut(274, 236, PAL_FORGRND, '0' + (val % 10));
1453 		}
1454 		else if (val > 9)
1455 		{
1456 			charOut(260, 236, PAL_FORGRND, sign);
1457 			charOut(267, 236, PAL_FORGRND, '0' + (char)(val / 10));
1458 			charOut(274, 236, PAL_FORGRND, '0' + (val % 10));
1459 		}
1460 		else
1461 		{
1462 			charOut(267, 236, PAL_FORGRND, sign);
1463 			charOut(274, 236, PAL_FORGRND, '0' + (char)val);
1464 		}
1465 	}
1466 
1467 	if (endVol > 200)
1468 	{
1469 		charOut(253, 250, PAL_FORGRND, '>');
1470 		charOut(260, 250, PAL_FORGRND, '2');
1471 		charOut(267, 250, PAL_FORGRND, '0');
1472 		charOut(274, 250, PAL_FORGRND, '0');
1473 	}
1474 	else
1475 	{
1476 		     if (endVol == 0) sign = ' ';
1477 		else if (endVol  < 0) sign = '-';
1478 		else sign = '+';
1479 
1480 		val = ABS(endVol);
1481 		if (val > 99)
1482 		{
1483 			charOut(253, 250, PAL_FORGRND, sign);
1484 			charOut(260, 250, PAL_FORGRND, '0' + (char)(val / 100));
1485 			charOut(267, 250, PAL_FORGRND, '0' + ((val / 10) % 10));
1486 			charOut(274, 250, PAL_FORGRND, '0' + (val % 10));
1487 		}
1488 		else if (val > 9)
1489 		{
1490 			charOut(260, 250, PAL_FORGRND, sign);
1491 			charOut(267, 250, PAL_FORGRND, '0' + (char)(val / 10));
1492 			charOut(274, 250, PAL_FORGRND, '0' + (val % 10));
1493 		}
1494 		else
1495 		{
1496 			charOut(267, 250, PAL_FORGRND, sign);
1497 			charOut(274, 250, PAL_FORGRND, '0' + (char)val);
1498 		}
1499 	}
1500 }
1501 
setupVolumeBoxWidgets(void)1502 static void setupVolumeBoxWidgets(void)
1503 {
1504 	pushButton_t *p;
1505 	scrollBar_t *s;
1506 
1507 	// "Apply" pushbutton
1508 	p = &pushButtons[0];
1509 	memset(p, 0, sizeof (pushButton_t));
1510 	p->caption = "Apply";
1511 	p->x = 171;
1512 	p->y = 262;
1513 	p->w = 73;
1514 	p->h = 16;
1515 	p->callbackFuncOnUp = pbApplyVolume;
1516 	p->visible = true;
1517 
1518 	// "Get maximum scale" pushbutton
1519 	p = &pushButtons[1];
1520 	memset(p, 0, sizeof (pushButton_t));
1521 	p->caption = "Get maximum scale";
1522 	p->x = 245;
1523 	p->y = 262;
1524 	p->w = 143;
1525 	p->h = 16;
1526 	p->callbackFuncOnUp = pbGetMaxScale;
1527 	p->visible = true;
1528 
1529 	// "Exit" pushbutton
1530 	p = &pushButtons[2];
1531 	memset(p, 0, sizeof (pushButton_t));
1532 	p->caption = "Exit";
1533 	p->x = 389;
1534 	p->y = 262;
1535 	p->w = 73;
1536 	p->h = 16;
1537 	p->callbackFuncOnUp = pbExit;
1538 	p->visible = true;
1539 
1540 	// scrollbar buttons
1541 
1542 	p = &pushButtons[3];
1543 	memset(p, 0, sizeof (pushButton_t));
1544 	p->caption = ARROW_LEFT_STRING;
1545 	p->x = 292;
1546 	p->y = 234;
1547 	p->w = 23;
1548 	p->h = 13;
1549 	p->preDelay = 1;
1550 	p->delayFrames = 3;
1551 	p->callbackFuncOnDown = pbSampStartVolDown;
1552 	p->visible = true;
1553 
1554 	p = &pushButtons[4];
1555 	memset(p, 0, sizeof (pushButton_t));
1556 	p->caption = ARROW_RIGHT_STRING;
1557 	p->x = 439;
1558 	p->y = 234;
1559 	p->w = 23;
1560 	p->h = 13;
1561 	p->preDelay = 1;
1562 	p->delayFrames = 3;
1563 	p->callbackFuncOnDown = pbSampStartVolUp;
1564 	p->visible = true;
1565 
1566 	p = &pushButtons[5];
1567 	memset(p, 0, sizeof (pushButton_t));
1568 	p->caption = ARROW_LEFT_STRING;
1569 	p->x = 292;
1570 	p->y = 248;
1571 	p->w = 23;
1572 	p->h = 13;
1573 	p->preDelay = 1;
1574 	p->delayFrames = 3;
1575 	p->callbackFuncOnDown = pbSampEndVolDown;
1576 	p->visible = true;
1577 
1578 	p = &pushButtons[6];
1579 	memset(p, 0, sizeof (pushButton_t));
1580 	p->caption = ARROW_RIGHT_STRING;
1581 	p->x = 439;
1582 	p->y = 248;
1583 	p->w = 23;
1584 	p->h = 13;
1585 	p->preDelay = 1;
1586 	p->delayFrames = 3;
1587 	p->callbackFuncOnDown = pbSampEndVolUp;
1588 	p->visible = true;
1589 
1590 	// volume start scrollbar
1591 	s = &scrollBars[0];
1592 	memset(s, 0, sizeof (scrollBar_t));
1593 	s->x = 315;
1594 	s->y = 234;
1595 	s->w = 124;
1596 	s->h = 13;
1597 	s->callbackFunc = sbSetStartVolPos;
1598 	s->visible = true;
1599 	setScrollBarPageLength(0, 1);
1600 	setScrollBarEnd(0, 200 * 2);
1601 	setScrollBarPos(0, 200, false);
1602 
1603 	// volume end scrollbar
1604 	s = &scrollBars[1];
1605 	memset(s, 0, sizeof (scrollBar_t));
1606 	s->x = 315;
1607 	s->y = 248;
1608 	s->w = 124;
1609 	s->h = 13;
1610 	s->callbackFunc = sbSetEndVolPos;
1611 	s->visible = true;
1612 	setScrollBarPageLength(1, 1);
1613 	setScrollBarEnd(1, 200 * 2);
1614 	setScrollBarPos(1, 200, false);
1615 }
1616 
pbSampleVolume(void)1617 void pbSampleVolume(void)
1618 {
1619 	uint16_t i;
1620 
1621 	if (editor.curInstr == 0 ||
1622 		instr[editor.curInstr] == NULL ||
1623 		instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
1624 	{
1625 		return;
1626 	}
1627 
1628 	setupVolumeBoxWidgets();
1629 	windowOpen();
1630 
1631 	exitFlag = false;
1632 	while (ui.sysReqShown)
1633 	{
1634 		readInput();
1635 		if (ui.sysReqEnterPressed)
1636 		{
1637 			pbApplyVolume();
1638 			keyb.ignoreCurrKeyUp = true; // don't handle key up event for this key release
1639 		}
1640 
1641 		setSyncedReplayerVars();
1642 		handleRedrawing();
1643 
1644 		// this is needed for the "Get maximum scale" button
1645 		if (ui.setMouseIdle) mouseAnimOff();
1646 
1647 		drawSampleVolumeBox();
1648 
1649 		const int32_t startVol = (int32_t)dVol_StartVol;
1650 		const int32_t endVol = (int32_t)dVol_EndVol;
1651 
1652 		setScrollBarPos(0, 200 + startVol, false);
1653 		setScrollBarPos(1, 200 + endVol, false);
1654 		for (i = 0; i < 7; i++) drawPushButton(i);
1655 		for (i = 0; i < 2; i++) drawScrollBar(i);
1656 
1657 		flipFrame();
1658 	}
1659 
1660 	for (i = 0; i < 7; i++) hidePushButton(i);
1661 	for (i = 0; i < 2; i++) hideScrollBar(i);
1662 
1663 	windowClose(true);
1664 }
1665