ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_sample_ed_features.c (33587B)


      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 
     38 static void pbExit(void)
     39 {
     40 	ui.sysReqShown = false;
     41 	exitFlag = true;
     42 }
     43 
     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 
     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 
     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 
     71 static void pbResampleTonesDown(void)
     72 {
     73 	if (smpEd_RelReSmp > -36)
     74 		smpEd_RelReSmp--;
     75 }
     76 
     77 static void pbResampleTonesUp(void)
     78 {
     79 	if (smpEd_RelReSmp < 36)
     80 		smpEd_RelReSmp++;
     81 }
     82 
     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 = pow(2.0, (int32_t)smpEd_RelReSmp * (1.0 / 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 	/* Fast nearest-neighbor resampling.
    119 	**
    120 	** Could benefit from windowed-sinc interpolation,
    121 	** but it seems like some people prefer it the way it is.
    122 	*/
    123 
    124 	if (newLen > 0)
    125 	{
    126 		if (sample16Bit)
    127 		{
    128 			const int16_t *src16 = (const int16_t *)src;
    129 			int16_t *dst16 = (int16_t *)dst;
    130 
    131 			for (uint32_t i = 0; i < newLen; i++)
    132 			{
    133 				dst16[i] = src16[posFrac64 >> 32];
    134 				posFrac64 += delta64;
    135 			}
    136 		}
    137 		else // 8-bit
    138 		{
    139 			const int8_t *src8 = src;
    140 			int8_t *dst8 = dst;
    141 
    142 			for (uint32_t i = 0; i < newLen; i++)
    143 			{
    144 				dst8[i] = src8[posFrac64 >> 32];
    145 				posFrac64 += delta64;
    146 			}
    147 		}
    148 	}
    149 
    150 	freeSmpData(s);
    151 	setSmpDataPtr(s, &sp);
    152 
    153 	s->relativeNote += smpEd_RelReSmp;
    154 	s->length = newLen;
    155 	s->loopStart = (int32_t)(s->loopStart * dRatio);
    156 	s->loopLength = (int32_t)(s->loopLength * dRatio);
    157 
    158 	sanitizeSample(s);
    159 
    160 	fixSample(s);
    161 	resumeAudio();
    162 
    163 	setSongModifiedFlag();
    164 	setMouseBusy(false);
    165 
    166 	ui.sysReqShown = false;
    167 	return true;
    168 
    169 	(void)ptr;
    170 }
    171 
    172 static void pbDoResampling(void)
    173 {
    174 	mouseAnimOn();
    175 	thread = SDL_CreateThread(resampleThread, NULL, NULL);
    176 	if (thread == NULL)
    177 	{
    178 		okBox(0, "System message", "Couldn't create thread!", NULL);
    179 		return;
    180 	}
    181 
    182 	SDL_DetachThread(thread);
    183 }
    184 
    185 static void drawResampleBox(void)
    186 {
    187 	char sign;
    188 	const int16_t x = 209;
    189 	const int16_t y = 230;
    190 	const int16_t w = 214;
    191 	const int16_t h = 54;
    192 
    193 	// main fill
    194 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
    195 
    196 	// outer border
    197 	vLine(x,         y,         h - 1, PAL_BUTTON1);
    198 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
    199 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
    200 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
    201 
    202 	// inner border
    203 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
    204 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
    205 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
    206 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
    207 
    208 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
    209 
    210 	double dLenMul = pow(2.0, smpEd_RelReSmp * (1.0 / 12.0));
    211 
    212 	double dNewLen = s->length * dLenMul;
    213 	if (dNewLen > (double)MAX_SAMPLE_LEN)
    214 		dNewLen = (double)MAX_SAMPLE_LEN;
    215 
    216 	textOutShadow(215, 236, PAL_FORGRND, PAL_BUTTON2, "Rel. h.tones");
    217 	textOutShadow(215, 250, PAL_FORGRND, PAL_BUTTON2, "New sample size");
    218 	hexOut(361, 250, PAL_FORGRND, (int32_t)dNewLen, 8);
    219 
    220 	     if (smpEd_RelReSmp == 0) sign = ' ';
    221 	else if (smpEd_RelReSmp  < 0) sign = '-';
    222 	else sign = '+';
    223 
    224 	uint16_t val = ABS(smpEd_RelReSmp);
    225 	if (val > 9)
    226 	{
    227 		charOut(291, 236, PAL_FORGRND, sign);
    228 		charOut(298, 236, PAL_FORGRND, '0' + ((val / 10) % 10));
    229 		charOut(305, 236, PAL_FORGRND, '0' + (val % 10));
    230 	}
    231 	else
    232 	{
    233 		charOut(298, 236, PAL_FORGRND, sign);
    234 		charOut(305, 236, PAL_FORGRND, '0' + (val % 10));
    235 	}
    236 }
    237 
    238 static void setupResampleBoxWidgets(void)
    239 {
    240 	pushButton_t *p;
    241 	scrollBar_t *s;
    242 
    243 	// "Apply" pushbutton
    244 	p = &pushButtons[0];
    245 	memset(p, 0, sizeof (pushButton_t));
    246 	p->caption = "Apply";
    247 	p->x = 214;
    248 	p->y = 264;
    249 	p->w = 73;
    250 	p->h = 16;
    251 	p->callbackFuncOnUp = pbDoResampling;
    252 	p->visible = true;
    253 
    254 	// "Exit" pushbutton
    255 	p = &pushButtons[1];
    256 	memset(p, 0, sizeof (pushButton_t));
    257 	p->caption = "Exit";
    258 	p->x = 345;
    259 	p->y = 264;
    260 	p->w = 73;
    261 	p->h = 16;
    262 	p->callbackFuncOnUp = pbExit;
    263 	p->visible = true;
    264 
    265 	// scrollbar buttons
    266 
    267 	p = &pushButtons[2];
    268 	memset(p, 0, sizeof (pushButton_t));
    269 	p->caption = ARROW_LEFT_STRING;
    270 	p->x = 314;
    271 	p->y = 234;
    272 	p->w = 23;
    273 	p->h = 13;
    274 	p->preDelay = 1;
    275 	p->delayFrames = 3;
    276 	p->callbackFuncOnDown = pbResampleTonesDown;
    277 	p->visible = true;
    278 
    279 	p = &pushButtons[3];
    280 	memset(p, 0, sizeof (pushButton_t));
    281 	p->caption = ARROW_RIGHT_STRING;
    282 	p->x = 395;
    283 	p->y = 234;
    284 	p->w = 23;
    285 	p->h = 13;
    286 	p->preDelay = 1;
    287 	p->delayFrames = 3;
    288 	p->callbackFuncOnDown = pbResampleTonesUp;
    289 	p->visible = true;
    290 
    291 	// echo num scrollbar
    292 	s = &scrollBars[0];
    293 	memset(s, 0, sizeof (scrollBar_t));
    294 	s->x = 337;
    295 	s->y = 234;
    296 	s->w = 58;
    297 	s->h = 13;
    298 	s->callbackFunc = sbSetResampleTones;
    299 	s->visible = true;
    300 	setScrollBarPageLength(0, 1);
    301 	setScrollBarEnd(0, 36 * 2);
    302 }
    303 
    304 void pbSampleResample(void)
    305 {
    306 	uint16_t i;
    307 
    308 	if (editor.curInstr == 0 ||
    309 		instr[editor.curInstr] == NULL ||
    310 		instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
    311 	{
    312 		return;
    313 	}
    314 
    315 	setupResampleBoxWidgets();
    316 	windowOpen();
    317 
    318 	outOfMemory = false;
    319 
    320 	exitFlag = false;
    321 	while (ui.sysReqShown)
    322 	{
    323 		readInput();
    324 		if (ui.sysReqEnterPressed)
    325 			pbDoResampling();
    326 
    327 		setSyncedReplayerVars();
    328 		handleRedrawing();
    329 
    330 		drawResampleBox();
    331 		setScrollBarPos(0, smpEd_RelReSmp + 36, false);
    332 		drawCheckBox(0);
    333 		for (i = 0; i < 4; i++) drawPushButton(i);
    334 		drawScrollBar(0);
    335 
    336 		flipFrame();
    337 	}
    338 
    339 	for (i = 0; i < 4; i++) hidePushButton(i);
    340 	hideScrollBar(0);
    341 
    342 	windowClose(false);
    343 
    344 	if (outOfMemory)
    345 		okBox(0, "System message", "Not enough memory!", NULL);
    346 }
    347 
    348 static void cbEchoAddMemory(void)
    349 {
    350 	echo_AddMemory ^= 1;
    351 }
    352 
    353 static void sbSetEchoNumPos(uint32_t pos)
    354 {
    355 	if (echo_nEcho != (int32_t)pos)
    356 		echo_nEcho = (int16_t)pos;
    357 }
    358 
    359 static void sbSetEchoDistPos(uint32_t pos)
    360 {
    361 	if (echo_Distance != (int32_t)pos)
    362 		echo_Distance = (int32_t)pos;
    363 }
    364 
    365 static void sbSetEchoFadeoutPos(uint32_t pos)
    366 {
    367 	if (echo_VolChange != (int32_t)pos)
    368 		echo_VolChange = (int16_t)pos;
    369 }
    370 
    371 static void pbEchoNumDown(void)
    372 {
    373 	if (echo_nEcho > 0)
    374 		echo_nEcho--;
    375 }
    376 
    377 static void pbEchoNumUp(void)
    378 {
    379 	if (echo_nEcho < 64)
    380 		echo_nEcho++;
    381 }
    382 
    383 static void pbEchoDistDown(void)
    384 {
    385 	if (echo_Distance > 0)
    386 		echo_Distance--;
    387 }
    388 
    389 static void pbEchoDistUp(void)
    390 {
    391 	if (echo_Distance < 16384)
    392 		echo_Distance++;
    393 }
    394 
    395 static void pbEchoFadeoutDown(void)
    396 {
    397 	if (echo_VolChange > 0)
    398 		echo_VolChange--;
    399 }
    400 
    401 static void pbEchoFadeoutUp(void)
    402 {
    403 	if (echo_VolChange < 100)
    404 		echo_VolChange++;
    405 }
    406 
    407 static int32_t SDLCALL createEchoThread(void *ptr)
    408 {
    409 	smpPtr_t sp;
    410 
    411 	if (echo_nEcho < 1)
    412 	{
    413 		ui.sysReqShown = false;
    414 		return true;
    415 	}
    416 
    417 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
    418 
    419 	int32_t readLen = s->length;
    420 	int8_t *readPtr = s->dataPtr;
    421 	bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
    422 	int32_t distance = echo_Distance * 16;
    423 	double dVolChange = echo_VolChange / 100.0;
    424 
    425 	// calculate real number of echoes
    426 	double dSmp = sample16Bit ? 32768.0 : 128.0;
    427 	int32_t k = 0;
    428 	while (k < echo_nEcho && dSmp >= 1.0)
    429 	{
    430 		dSmp *= dVolChange;
    431 		k++;
    432 	}
    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 
    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!", NULL);
    563 		return;
    564 	}
    565 
    566 	SDL_DetachThread(thread);
    567 }
    568 
    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 
    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 
    759 void handleEchoToolPanic(void)
    760 {
    761 	stopThread = true;
    762 }
    763 
    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!", NULL);
    807 }
    808 
    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 
    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!", NULL);
    950 		return;
    951 	}
    952 
    953 	SDL_DetachThread(thread);
    954 }
    955 
    956 static void sbSetMixBalancePos(uint32_t pos)
    957 {
    958 	if (mix_Balance != (int8_t)pos)
    959 		mix_Balance = (int8_t)pos;
    960 }
    961 
    962 static void pbMixBalanceDown(void)
    963 {
    964 	if (mix_Balance > 0)
    965 		mix_Balance--;
    966 }
    967 
    968 static void pbMixBalanceUp(void)
    969 {
    970 	if (mix_Balance < 100)
    971 		mix_Balance++;
    972 }
    973 
    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 
   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 
   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!", NULL);
   1105 }
   1106 
   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 
   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 
   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 
   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 
   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 
   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 
   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 	const bool mustInterpolate = (dVol_StartVol != dVol_EndVol);
   1200 	const double dVolDelta = ((dVol_EndVol - dVol_StartVol) / 100.0) / len;
   1201 	double dVol = dVol_StartVol / 100.0;
   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 				int32_t smp32 = (int32_t)((int32_t)ptr16[i] * dVol);
   1213 				CLAMP16(smp32);
   1214 				ptr16[i] = (int16_t)smp32;
   1215 
   1216 				dVol += dVolDelta;
   1217 			}
   1218 		}
   1219 		else // no interpolation needed
   1220 		{
   1221 			for (int32_t i = 0; i < len; i++)
   1222 			{
   1223 				int32_t smp32 = (int32_t)((int32_t)ptr16[i] * dVol);
   1224 				CLAMP16(smp32);
   1225 				ptr16[i] = (int16_t)smp32;
   1226 			}
   1227 		}
   1228 	}
   1229 	else // 8-bit sample
   1230 	{
   1231 		int8_t *ptr8 = s->dataPtr + x1;
   1232 		if (mustInterpolate)
   1233 		{
   1234 			for (int32_t i = 0; i < len; i++)
   1235 			{
   1236 				int32_t smp32 = (int32_t)((int32_t)ptr8[i] * dVol);
   1237 				CLAMP8(smp32);
   1238 				ptr8[i] = (int8_t)smp32;
   1239 
   1240 				dVol += dVolDelta;
   1241 			}
   1242 		}
   1243 		else // no interpolation needed
   1244 		{
   1245 			for (int32_t i = 0; i < len; i++)
   1246 			{
   1247 				int32_t smp32 = (int32_t)((int32_t)ptr8[i] * dVol);
   1248 				CLAMP8(smp32);
   1249 				ptr8[i] = (int8_t)smp32;
   1250 			}
   1251 		}
   1252 	}
   1253 	fixSample(s);
   1254 	resumeAudio();
   1255 
   1256 	setSongModifiedFlag();
   1257 
   1258 applyVolumeExit:
   1259 	setMouseBusy(false);
   1260 	ui.sysReqShown = false;
   1261 
   1262 	return true;
   1263 
   1264 	(void)ptr;
   1265 }
   1266 
   1267 static void pbApplyVolume(void)
   1268 {
   1269 	if (dVol_StartVol == 100.0 && dVol_EndVol == 100.0)
   1270 	{
   1271 		ui.sysReqShown = false;
   1272 		return; // no volume change to be done
   1273 	}
   1274 
   1275 	mouseAnimOn();
   1276 	thread = SDL_CreateThread(applyVolumeThread, NULL, NULL);
   1277 	if (thread == NULL)
   1278 	{
   1279 		okBox(0, "System message", "Couldn't create thread!", NULL);
   1280 		return;
   1281 	}
   1282 
   1283 	SDL_DetachThread(thread);
   1284 }
   1285 
   1286 static int32_t SDLCALL getMaxScaleThread(void *ptr)
   1287 {
   1288 	int32_t x1, x2;
   1289 
   1290 	if (instr[editor.curInstr] == NULL)
   1291 		goto getScaleExit;
   1292 
   1293 	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
   1294 
   1295 	if (smpEd_Rx1 < smpEd_Rx2)
   1296 	{
   1297 		x1 = smpEd_Rx1;
   1298 		x2 = smpEd_Rx2;
   1299 
   1300 		if (x2 > s->length)
   1301 			x2 = s->length;
   1302 
   1303 		if (x1 < 0)
   1304 			x1 = 0;
   1305 
   1306 		if (x2 <= x1)
   1307 			goto getScaleExit;
   1308 	}
   1309 	else
   1310 	{
   1311 		// no sample marking, operate on the whole sample
   1312 		x1 = 0;
   1313 		x2 = s->length;
   1314 	}
   1315 
   1316 	uint32_t len = x2 - x1;
   1317 	if (len <= 0)
   1318 	{
   1319 		dVol_StartVol = dVol_EndVol = 100.0;
   1320 		goto getScaleExit;
   1321 	}
   1322 
   1323 	double dVolChange = 100.0;
   1324 
   1325 	/* If sample is looped and the loopEnd point is inside the marked range,
   1326 	** we need to unfix the fixed interpolation sample before scanning,
   1327 	** and fix it again after we're done.
   1328 	*/
   1329 	bool hasLoop = GET_LOOPTYPE(s->flags) != LOOP_OFF;
   1330 	const int32_t loopEnd = s->loopStart + s->loopLength;
   1331 	bool fixedSampleInRange = hasLoop && (x1 <= loopEnd) && (x2 >= loopEnd);
   1332 
   1333 	if (fixedSampleInRange)
   1334 		unfixSample(s);
   1335 
   1336 	int32_t maxAmp = 0;
   1337 	if (s->flags & SAMPLE_16BIT)
   1338 	{
   1339 		const int16_t *ptr16 = (const int16_t *)s->dataPtr + x1;
   1340 		for (uint32_t i = 0; i < len; i++)
   1341 		{
   1342 			const int32_t absSmp = ABS(ptr16[i]);
   1343 			if (absSmp > maxAmp)
   1344 				maxAmp = absSmp;
   1345 		}
   1346 
   1347 		if (maxAmp > 0)
   1348 			dVolChange = (32767.0 / maxAmp) * 100.0;
   1349 	}
   1350 	else // 8-bit
   1351 	{
   1352 		const int8_t *ptr8 = (const int8_t *)&s->dataPtr[x1];
   1353 		for (uint32_t i = 0; i < len; i++)
   1354 		{
   1355 			const int32_t absSmp = ABS(ptr8[i]);
   1356 			if (absSmp > maxAmp)
   1357 				maxAmp = absSmp;
   1358 		}
   1359 
   1360 		if (maxAmp > 0)
   1361 			dVolChange = (127.0 / maxAmp) * 100.0;
   1362 	}
   1363 
   1364 	if (fixedSampleInRange)
   1365 		fixSample(s);
   1366 
   1367 	if (dVolChange < 100.0) // yes, this can happen...
   1368 		dVolChange = 100.0;
   1369 
   1370 	dVol_StartVol = dVol_EndVol = dVolChange;
   1371 
   1372 getScaleExit:
   1373 	setMouseBusy(false);
   1374 	return true;
   1375 
   1376 	(void)ptr;
   1377 }
   1378 
   1379 static void pbGetMaxScale(void)
   1380 {
   1381 	mouseAnimOn();
   1382 	thread = SDL_CreateThread(getMaxScaleThread, NULL, NULL);
   1383 	if (thread == NULL)
   1384 	{
   1385 		okBox(0, "System message", "Couldn't create thread!", NULL);
   1386 		return;
   1387 	}
   1388 
   1389 	SDL_DetachThread(thread);
   1390 }
   1391 
   1392 static void drawSampleVolumeBox(void)
   1393 {
   1394 	char sign;
   1395 	const int16_t x = 166;
   1396 	const int16_t y = 230;
   1397 	const int16_t w = 301;
   1398 	const int16_t h = 52;
   1399 	uint32_t val;
   1400 
   1401 	// main fill
   1402 	fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS);
   1403 
   1404 	// outer border
   1405 	vLine(x,         y,         h - 1, PAL_BUTTON1);
   1406 	hLine(x + 1,     y,         w - 2, PAL_BUTTON1);
   1407 	vLine(x + w - 1, y,         h,     PAL_BUTTON2);
   1408 	hLine(x,         y + h - 1, w - 1, PAL_BUTTON2);
   1409 
   1410 	// inner border
   1411 	vLine(x + 2,     y + 2,     h - 5, PAL_BUTTON2);
   1412 	hLine(x + 3,     y + 2,     w - 6, PAL_BUTTON2);
   1413 	vLine(x + w - 3, y + 2,     h - 4, PAL_BUTTON1);
   1414 	hLine(x + 2,     y + h - 3, w - 4, PAL_BUTTON1);
   1415 
   1416 	textOutShadow(172, 236, PAL_FORGRND, PAL_BUTTON2, "Start volume");
   1417 	textOutShadow(172, 250, PAL_FORGRND, PAL_BUTTON2, "End volume");
   1418 	charOutShadow(282, 236, PAL_FORGRND, PAL_BUTTON2, '%');
   1419 	charOutShadow(282, 250, PAL_FORGRND, PAL_BUTTON2, '%');
   1420 
   1421 	const int32_t startVol = (int32_t)dVol_StartVol;
   1422 	const int32_t endVol = (int32_t)dVol_EndVol;
   1423 
   1424 	if (startVol > 200)
   1425 	{
   1426 		charOut(253, 236, PAL_FORGRND, '>');
   1427 		charOut(260, 236, PAL_FORGRND, '2');
   1428 		charOut(267, 236, PAL_FORGRND, '0');
   1429 		charOut(274, 236, PAL_FORGRND, '0');
   1430 	}
   1431 	else
   1432 	{
   1433 		     if (startVol == 0) sign = ' ';
   1434 		else if (startVol  < 0) sign = '-';
   1435 		else sign = '+';
   1436 
   1437 		val = ABS(startVol);
   1438 		if (val > 99)
   1439 		{
   1440 			charOut(253, 236, PAL_FORGRND, sign);
   1441 			charOut(260, 236, PAL_FORGRND, '0' + (char)(val / 100));
   1442 			charOut(267, 236, PAL_FORGRND, '0' + ((val / 10) % 10));
   1443 			charOut(274, 236, PAL_FORGRND, '0' + (val % 10));
   1444 		}
   1445 		else if (val > 9)
   1446 		{
   1447 			charOut(260, 236, PAL_FORGRND, sign);
   1448 			charOut(267, 236, PAL_FORGRND, '0' + (char)(val / 10));
   1449 			charOut(274, 236, PAL_FORGRND, '0' + (val % 10));
   1450 		}
   1451 		else
   1452 		{
   1453 			charOut(267, 236, PAL_FORGRND, sign);
   1454 			charOut(274, 236, PAL_FORGRND, '0' + (char)val);
   1455 		}
   1456 	}
   1457 
   1458 	if (endVol > 200)
   1459 	{
   1460 		charOut(253, 250, PAL_FORGRND, '>');
   1461 		charOut(260, 250, PAL_FORGRND, '2');
   1462 		charOut(267, 250, PAL_FORGRND, '0');
   1463 		charOut(274, 250, PAL_FORGRND, '0');
   1464 	}
   1465 	else
   1466 	{
   1467 		     if (endVol == 0) sign = ' ';
   1468 		else if (endVol  < 0) sign = '-';
   1469 		else sign = '+';
   1470 
   1471 		val = ABS(endVol);
   1472 		if (val > 99)
   1473 		{
   1474 			charOut(253, 250, PAL_FORGRND, sign);
   1475 			charOut(260, 250, PAL_FORGRND, '0' + (char)(val / 100));
   1476 			charOut(267, 250, PAL_FORGRND, '0' + ((val / 10) % 10));
   1477 			charOut(274, 250, PAL_FORGRND, '0' + (val % 10));
   1478 		}
   1479 		else if (val > 9)
   1480 		{
   1481 			charOut(260, 250, PAL_FORGRND, sign);
   1482 			charOut(267, 250, PAL_FORGRND, '0' + (char)(val / 10));
   1483 			charOut(274, 250, PAL_FORGRND, '0' + (val % 10));
   1484 		}
   1485 		else
   1486 		{
   1487 			charOut(267, 250, PAL_FORGRND, sign);
   1488 			charOut(274, 250, PAL_FORGRND, '0' + (char)val);
   1489 		}
   1490 	}
   1491 }
   1492 
   1493 static void setupVolumeBoxWidgets(void)
   1494 {
   1495 	pushButton_t *p;
   1496 	scrollBar_t *s;
   1497 
   1498 	// "Apply" pushbutton
   1499 	p = &pushButtons[0];
   1500 	memset(p, 0, sizeof (pushButton_t));
   1501 	p->caption = "Apply";
   1502 	p->x = 171;
   1503 	p->y = 262;
   1504 	p->w = 73;
   1505 	p->h = 16;
   1506 	p->callbackFuncOnUp = pbApplyVolume;
   1507 	p->visible = true;
   1508 
   1509 	// "Get maximum scale" pushbutton
   1510 	p = &pushButtons[1];
   1511 	memset(p, 0, sizeof (pushButton_t));
   1512 	p->caption = "Get maximum scale";
   1513 	p->x = 245;
   1514 	p->y = 262;
   1515 	p->w = 143;
   1516 	p->h = 16;
   1517 	p->callbackFuncOnUp = pbGetMaxScale;
   1518 	p->visible = true;
   1519 
   1520 	// "Exit" pushbutton
   1521 	p = &pushButtons[2];
   1522 	memset(p, 0, sizeof (pushButton_t));
   1523 	p->caption = "Exit";
   1524 	p->x = 389;
   1525 	p->y = 262;
   1526 	p->w = 73;
   1527 	p->h = 16;
   1528 	p->callbackFuncOnUp = pbExit;
   1529 	p->visible = true;
   1530 
   1531 	// scrollbar buttons
   1532 
   1533 	p = &pushButtons[3];
   1534 	memset(p, 0, sizeof (pushButton_t));
   1535 	p->caption = ARROW_LEFT_STRING;
   1536 	p->x = 292;
   1537 	p->y = 234;
   1538 	p->w = 23;
   1539 	p->h = 13;
   1540 	p->preDelay = 1;
   1541 	p->delayFrames = 3;
   1542 	p->callbackFuncOnDown = pbSampStartVolDown;
   1543 	p->visible = true;
   1544 
   1545 	p = &pushButtons[4];
   1546 	memset(p, 0, sizeof (pushButton_t));
   1547 	p->caption = ARROW_RIGHT_STRING;
   1548 	p->x = 439;
   1549 	p->y = 234;
   1550 	p->w = 23;
   1551 	p->h = 13;
   1552 	p->preDelay = 1;
   1553 	p->delayFrames = 3;
   1554 	p->callbackFuncOnDown = pbSampStartVolUp;
   1555 	p->visible = true;
   1556 
   1557 	p = &pushButtons[5];
   1558 	memset(p, 0, sizeof (pushButton_t));
   1559 	p->caption = ARROW_LEFT_STRING;
   1560 	p->x = 292;
   1561 	p->y = 248;
   1562 	p->w = 23;
   1563 	p->h = 13;
   1564 	p->preDelay = 1;
   1565 	p->delayFrames = 3;
   1566 	p->callbackFuncOnDown = pbSampEndVolDown;
   1567 	p->visible = true;
   1568 
   1569 	p = &pushButtons[6];
   1570 	memset(p, 0, sizeof (pushButton_t));
   1571 	p->caption = ARROW_RIGHT_STRING;
   1572 	p->x = 439;
   1573 	p->y = 248;
   1574 	p->w = 23;
   1575 	p->h = 13;
   1576 	p->preDelay = 1;
   1577 	p->delayFrames = 3;
   1578 	p->callbackFuncOnDown = pbSampEndVolUp;
   1579 	p->visible = true;
   1580 
   1581 	// volume start scrollbar
   1582 	s = &scrollBars[0];
   1583 	memset(s, 0, sizeof (scrollBar_t));
   1584 	s->x = 315;
   1585 	s->y = 234;
   1586 	s->w = 124;
   1587 	s->h = 13;
   1588 	s->callbackFunc = sbSetStartVolPos;
   1589 	s->visible = true;
   1590 	setScrollBarPageLength(0, 1);
   1591 	setScrollBarEnd(0, 200 * 2);
   1592 	setScrollBarPos(0, 200, false);
   1593 
   1594 	// volume end scrollbar
   1595 	s = &scrollBars[1];
   1596 	memset(s, 0, sizeof (scrollBar_t));
   1597 	s->x = 315;
   1598 	s->y = 248;
   1599 	s->w = 124;
   1600 	s->h = 13;
   1601 	s->callbackFunc = sbSetEndVolPos;
   1602 	s->visible = true;
   1603 	setScrollBarPageLength(1, 1);
   1604 	setScrollBarEnd(1, 200 * 2);
   1605 	setScrollBarPos(1, 200, false);
   1606 }
   1607 
   1608 void pbSampleVolume(void)
   1609 {
   1610 	uint16_t i;
   1611 
   1612 	if (editor.curInstr == 0 ||
   1613 		instr[editor.curInstr] == NULL ||
   1614 		instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
   1615 	{
   1616 		return;
   1617 	}
   1618 
   1619 	setupVolumeBoxWidgets();
   1620 	windowOpen();
   1621 
   1622 	exitFlag = false;
   1623 	while (ui.sysReqShown)
   1624 	{
   1625 		readInput();
   1626 		if (ui.sysReqEnterPressed)
   1627 		{
   1628 			pbApplyVolume();
   1629 			keyb.ignoreCurrKeyUp = true; // don't handle key up event for this key release
   1630 		}
   1631 
   1632 		setSyncedReplayerVars();
   1633 		handleRedrawing();
   1634 
   1635 		// this is needed for the "Get maximum scale" button
   1636 		if (ui.setMouseIdle) mouseAnimOff();
   1637 
   1638 		drawSampleVolumeBox();
   1639 
   1640 		const int32_t startVol = (int32_t)dVol_StartVol;
   1641 		const int32_t endVol = (int32_t)dVol_EndVol;
   1642 
   1643 		setScrollBarPos(0, 200 + startVol, false);
   1644 		setScrollBarPos(1, 200 + endVol, false);
   1645 		for (i = 0; i < 7; i++) drawPushButton(i);
   1646 		for (i = 0; i < 2; i++) drawScrollBar(i);
   1647 
   1648 		flipFrame();
   1649 	}
   1650 
   1651 	for (i = 0; i < 7; i++) hidePushButton(i);
   1652 	for (i = 0; i < 2; i++) hideScrollBar(i);
   1653 
   1654 	windowClose(true);
   1655 }