ref: 9435ca2b06a5108b9314e700967ad4cb97ca791f
dir: /src/ft2_sample_ed_features.c/
/* This file contains the routines for the following sample editor functions: ** - Resampler ** - Echo ** - Mix ** - Volume **/ // for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <math.h> #include "ft2_header.h" #include "ft2_mouse.h" #include "ft2_audio.h" #include "ft2_gui.h" #include "ft2_events.h" #include "ft2_video.h" #include "ft2_inst_ed.h" #include "ft2_sample_ed.h" #include "ft2_keyboard.h" #include "ft2_tables.h" #include "ft2_structs.h" static volatile bool stopThread; static int8_t smpEd_RelReSmp, mix_Balance = 50; static bool echo_AddMemory, exitFlag, outOfMemory; static int16_t vol_StartVol = 100, vol_EndVol = 100, echo_nEcho = 1, echo_VolChange = 30; static int32_t echo_Distance = 0x100; static SDL_Thread *thread; static void pbExit(void) { ui.sysReqShown = false; exitFlag = true; } static void windowOpen(void) { ui.sysReqShown = true; ui.sysReqEnterPressed = false; unstuckLastUsedGUIElement(); SDL_EventState(SDL_DROPFILE, SDL_DISABLE); } static void windowClose(bool rewriteSample) { SDL_EventState(SDL_DROPFILE, SDL_ENABLE); if (exitFlag || rewriteSample) writeSample(true); else updateNewSample(); mouseAnimOff(); } static void sbSetResampleTones(uint32_t pos) { if (smpEd_RelReSmp != (int8_t)(pos - 36)) smpEd_RelReSmp = (int8_t)(pos - 36); } static void pbResampleTonesDown(void) { if (smpEd_RelReSmp > -36) smpEd_RelReSmp--; } static void pbResampleTonesUp(void) { if (smpEd_RelReSmp < 36) smpEd_RelReSmp++; } static int32_t SDLCALL resampleThread(void *ptr) { if (instr[editor.curInstr] == NULL) return true; sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp]; const uint32_t mask = (s->typ & 16) ? 0xFFFFFFFE : 0xFFFFFFFF; const double dRatio = exp2(smpEd_RelReSmp / 12.0); double dNewLen = s->len * dRatio; if (dNewLen > (double)MAX_SAMPLE_LEN) dNewLen = (double)MAX_SAMPLE_LEN; const uint32_t newLen = (int32_t)dNewLen & mask; int8_t *p2 = (int8_t *)malloc(newLen + LOOP_FIX_LEN); if (p2 == NULL) { outOfMemory = true; setMouseBusy(false); ui.sysReqShown = false; return true; } int8_t *newPtr = p2 + SMP_DAT_OFFSET; int8_t *p1 = s->pek; // 32.32 fixed-point logic const uint64_t delta64 = (const uint64_t)round((UINT32_MAX+1.0) / dRatio); uint64_t posFrac64 = 0; pauseAudio(); restoreSample(s); /* Nearest-neighbor resampling (no interpolation). ** ** Could benefit from windowed-sinc interpolation, ** but it seems like some people prefer no resampling ** interpolation (like FT2). */ if (newLen > 0) { if (s->typ & 16) { const int16_t *src16 = (const int16_t *)p1; int16_t *dst16 = (int16_t *)newPtr; const uint32_t resampleLen = newLen >> 1; for (uint32_t i = 0; i < resampleLen; i++) { dst16[i] = src16[posFrac64 >> 32]; posFrac64 += delta64; } } else { const int8_t *src8 = p1; int8_t *dst8 = newPtr; for (uint32_t i = 0; i < newLen; i++) { dst8[i] = src8[posFrac64 >> 32]; posFrac64 += delta64; } } } free(s->origPek); s->relTon = CLAMP(s->relTon + smpEd_RelReSmp, -48, 71); s->len = newLen & mask; s->origPek = p2; s->pek = s->origPek + SMP_DAT_OFFSET; s->repS = (int32_t)(s->repS * dRatio); s->repL = (int32_t)(s->repL * dRatio); s->repS &= mask; s->repL &= mask; if (s->repS >= s->len) s->repS = s->len-1; if (s->repS+s->repL > s->len) s->repL = s->len - s->repS; if (s->typ & 16) { s->len &= 0xFFFFFFFE; s->repS &= 0xFFFFFFFE; s->repL &= 0xFFFFFFFE; } if (s->repL <= 0) s->typ &= ~3; // disable loop fixSample(s); resumeAudio(); setSongModifiedFlag(); setMouseBusy(false); ui.sysReqShown = false; return true; (void)ptr; } static void pbDoResampling(void) { mouseAnimOn(); thread = SDL_CreateThread(resampleThread, NULL, NULL); if (thread == NULL) { okBox(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); } static void drawResampleBox(void) { char sign; const int16_t x = 209; const int16_t y = 230; const int16_t w = 214; const int16_t h = 54; // main fill fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS); // outer border vLine(x, y, h - 1, PAL_BUTTON1); hLine(x + 1, y, w - 2, PAL_BUTTON1); vLine(x + w - 1, y, h, PAL_BUTTON2); hLine(x, y + h - 1, w - 1, PAL_BUTTON2); // inner border vLine(x + 2, y + 2, h - 5, PAL_BUTTON2); hLine(x + 3, y + 2, w - 6, PAL_BUTTON2); vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1); hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1); sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp]; uint32_t mask = (s->typ & 16) ? 0xFFFFFFFE : 0xFFFFFFFF; double dLenMul = exp2(smpEd_RelReSmp * (1.0 / 12.0)); double dNewLen = s->len * dLenMul; if (dNewLen > (double)MAX_SAMPLE_LEN) dNewLen = (double)MAX_SAMPLE_LEN; textOutShadow(215, 236, PAL_FORGRND, PAL_BUTTON2, "Rel. h.tones"); textOutShadow(215, 250, PAL_FORGRND, PAL_BUTTON2, "New sample size"); hexOut(361, 250, PAL_FORGRND, (uint32_t)dNewLen & mask, 8); if (smpEd_RelReSmp == 0) sign = ' '; else if (smpEd_RelReSmp < 0) sign = '-'; else sign = '+'; uint16_t val = ABS(smpEd_RelReSmp); if (val > 9) { charOut(291, 236, PAL_FORGRND, sign); charOut(298, 236, PAL_FORGRND, '0' + ((val / 10) % 10)); charOut(305, 236, PAL_FORGRND, '0' + (val % 10)); } else { charOut(298, 236, PAL_FORGRND, sign); charOut(305, 236, PAL_FORGRND, '0' + (val % 10)); } } static void setupResampleBoxWidgets(void) { pushButton_t *p; scrollBar_t *s; // "Apply" pushbutton p = &pushButtons[0]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Apply"; p->x = 214; p->y = 264; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbDoResampling; p->visible = true; // "Exit" pushbutton p = &pushButtons[1]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Exit"; p->x = 345; p->y = 264; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbExit; p->visible = true; // scrollbar buttons p = &pushButtons[2]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 314; p->y = 234; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbResampleTonesDown; p->visible = true; p = &pushButtons[3]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 395; p->y = 234; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbResampleTonesUp; p->visible = true; // echo num scrollbar s = &scrollBars[0]; memset(s, 0, sizeof (scrollBar_t)); s->x = 337; s->y = 234; s->w = 58; s->h = 13; s->callbackFunc = sbSetResampleTones; s->visible = true; setScrollBarPageLength(0, 1); setScrollBarEnd(0, 36 * 2); } void pbSampleResample(void) { uint16_t i; if (editor.curInstr == 0 || instr[editor.curInstr] == NULL || instr[editor.curInstr]->samp[editor.curSmp].pek == NULL) { return; } setupResampleBoxWidgets(); windowOpen(); outOfMemory = false; exitFlag = false; while (ui.sysReqShown) { readInput(); if (ui.sysReqEnterPressed) pbDoResampling(); setSyncedReplayerVars(); handleRedrawing(); drawResampleBox(); setScrollBarPos(0, smpEd_RelReSmp + 36, false); drawCheckBox(0); for (i = 0; i < 4; i++) drawPushButton(i); drawScrollBar(0); flipFrame(); } for (i = 0; i < 4; i++) hidePushButton(i); hideScrollBar(0); windowClose(false); if (outOfMemory) okBox(0, "System message", "Not enough memory!"); } static void cbEchoAddMemory(void) { echo_AddMemory ^= 1; } static void sbSetEchoNumPos(uint32_t pos) { if (echo_nEcho != (int32_t)pos) echo_nEcho = (int16_t)pos; } static void sbSetEchoDistPos(uint32_t pos) { if (echo_Distance != (int32_t)pos) echo_Distance = (int32_t)pos; } static void sbSetEchoFadeoutPos(uint32_t pos) { if (echo_VolChange != (int32_t)pos) echo_VolChange = (int16_t)pos; } static void pbEchoNumDown(void) { if (echo_nEcho > 0) echo_nEcho--; } static void pbEchoNumUp(void) { if (echo_nEcho < 64) echo_nEcho++; } static void pbEchoDistDown(void) { if (echo_Distance > 0) echo_Distance--; } static void pbEchoDistUp(void) { if (echo_Distance < 16384) echo_Distance++; } static void pbEchoFadeoutDown(void) { if (echo_VolChange > 0) echo_VolChange--; } static void pbEchoFadeoutUp(void) { if (echo_VolChange < 100) echo_VolChange++; } static int32_t SDLCALL createEchoThread(void *ptr) { if (echo_nEcho < 1) { ui.sysReqShown = false; return true; } sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp]; int32_t readLen = s->len; int8_t *readPtr = s->pek; bool is16Bit = (s->typ & 16) ? true : false; int32_t distance = echo_Distance * 16; double dVolChange = echo_VolChange / 100.0; // calculate real number of echoes double dSmp = is16Bit ? 32768.0 : 128.0; int32_t k = 0; while (k++ < echo_nEcho && dSmp >= 1.0) dSmp *= dVolChange; int32_t nEchoes = k + 1; if (nEchoes < 1) { ui.sysReqShown = false; return true; } // set write length (either original length or full echo length) int32_t writeLen = readLen; if (echo_AddMemory) { int64_t tmp64 = (int64_t)distance * (nEchoes - 1); if (is16Bit) tmp64 <<= 1; tmp64 += writeLen; if (tmp64 > MAX_SAMPLE_LEN) tmp64 = MAX_SAMPLE_LEN; writeLen = (int32_t)tmp64; if (is16Bit) writeLen &= 0xFFFFFFFE; } int8_t *writePtr = (int8_t *)malloc(writeLen + LOOP_FIX_LEN); if (writePtr == NULL) { outOfMemory = true; setMouseBusy(false); ui.sysReqShown = false; return false; } pauseAudio(); restoreSample(s); int32_t writeIdx = 0; if (is16Bit) { const int16_t *readPtr16 = (const int16_t *)readPtr; int16_t *writePtr16 = (int16_t *)&writePtr[SMP_DAT_OFFSET]; writeLen >>= 1; readLen >>= 1; while (writeIdx < writeLen) { double dSmpOut = 0.0; double dSmpMul = 1.0; int32_t echoRead = writeIdx; int32_t echoCycle = nEchoes; while (!stopThread) { if (echoRead < readLen) dSmpOut += (int32_t)readPtr16[echoRead] * dSmpMul; dSmpMul *= dVolChange; echoRead -= distance; if (echoRead <= 0 || --echoCycle <= 0) break; } // rounding (faster than calling round()) if (dSmpOut < 0.0) dSmpOut -= 0.5; else if (dSmpOut > 0.0) dSmpOut += 0.5; int32_t smpOut = (int32_t)dSmpOut; CLAMP16(smpOut); writePtr16[writeIdx++] = (int16_t)smpOut; } writeLen <<= 1; } else { int8_t *writePtr8 = writePtr + SMP_DAT_OFFSET; while (writeIdx < writeLen) { double dSmpOut = 0.0; double dSmpMul = 1.0; int32_t echoRead = writeIdx; int32_t echoCycle = nEchoes; while (!stopThread) { if (echoRead < readLen) dSmpOut += (int32_t)readPtr[echoRead] * dSmpMul; dSmpMul *= dVolChange; echoRead -= distance; if (echoRead <= 0 || --echoCycle <= 0) break; } // rounding (faster than calling round()) if (dSmpOut < 0.0) dSmpOut -= 0.5; else if (dSmpOut > 0.0) dSmpOut += 0.5; int32_t smpOut = (int32_t)dSmpOut; if (smpOut < -128) smpOut = -128; else if (smpOut > 127) smpOut = 127; writePtr8[writeIdx++] = (int8_t)smpOut; } } free(s->origPek); if (stopThread) { writeLen = writeIdx; int8_t *newPtr = (int8_t *)realloc(writePtr, writeIdx + LOOP_FIX_LEN); if (newPtr != NULL) { s->origPek = newPtr; s->pek = s->origPek + SMP_DAT_OFFSET; } else { if (writePtr != NULL) free(writePtr); s->origPek = s->pek = NULL; writeLen = 0; } editor.updateCurSmp = true; } else { s->origPek = writePtr; s->pek = s->origPek + SMP_DAT_OFFSET; } if (is16Bit) writeLen &= 0xFFFFFFFE; s->len = writeLen; fixSample(s); resumeAudio(); setSongModifiedFlag(); setMouseBusy(false); ui.sysReqShown = false; return true; (void)ptr; } static void pbCreateEcho(void) { stopThread = false; mouseAnimOn(); thread = SDL_CreateThread(createEchoThread, NULL, NULL); if (thread == NULL) { okBox(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); } static void drawEchoBox(void) { const int16_t x = 171; const int16_t y = 220; const int16_t w = 291; const int16_t h = 66; // main fill fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS); // outer border vLine(x, y, h - 1, PAL_BUTTON1); hLine(x + 1, y, w - 2, PAL_BUTTON1); vLine(x + w - 1, y, h, PAL_BUTTON2); hLine(x, y + h - 1, w - 1, PAL_BUTTON2); // inner border vLine(x + 2, y + 2, h - 5, PAL_BUTTON2); hLine(x + 3, y + 2, w - 6, PAL_BUTTON2); vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1); hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1); textOutShadow(177, 226, PAL_FORGRND, PAL_BUTTON2, "Number of echoes"); textOutShadow(177, 240, PAL_FORGRND, PAL_BUTTON2, "Echo distance"); textOutShadow(177, 254, PAL_FORGRND, PAL_BUTTON2, "Fade out"); textOutShadow(192, 270, PAL_FORGRND, PAL_BUTTON2, "Add memory to sample"); assert(echo_nEcho <= 64); charOut(315 + (2 * 7), 226, PAL_FORGRND, '0' + (char)(echo_nEcho / 10)); charOut(315 + (3 * 7), 226, PAL_FORGRND, '0' + (echo_nEcho % 10)); assert(echo_Distance <= 0x4000); hexOut(308, 240, PAL_FORGRND, (uint32_t)echo_Distance << 4, 5); assert(echo_VolChange <= 100); textOutFixed(312, 254, PAL_FORGRND, PAL_BUTTONS, dec3StrTab[echo_VolChange]); charOutShadow(313 + (3 * 7), 254, PAL_FORGRND, PAL_BUTTON2, '%'); } static void setupEchoBoxWidgets(void) { checkBox_t *c; pushButton_t *p; scrollBar_t *s; // "Add memory to sample" checkbox c = &checkBoxes[0]; memset(c, 0, sizeof (checkBox_t)); c->x = 176; c->y = 268; c->clickAreaWidth = 146; c->clickAreaHeight = 12; c->callbackFunc = cbEchoAddMemory; c->checked = echo_AddMemory ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED; c->visible = true; // "Apply" pushbutton p = &pushButtons[0]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Apply"; p->x = 345; p->y = 266; p->w = 56; p->h = 16; p->callbackFuncOnUp = pbCreateEcho; p->visible = true; // "Exit" pushbutton p = &pushButtons[1]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Exit"; p->x = 402; p->y = 266; p->w = 55; p->h = 16; p->callbackFuncOnUp = pbExit; p->visible = true; // scrollbar buttons p = &pushButtons[2]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 345; p->y = 224; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoNumDown; p->visible = true; p = &pushButtons[3]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 434; p->y = 224; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoNumUp; p->visible = true; p = &pushButtons[4]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 345; p->y = 238; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoDistDown; p->visible = true; p = &pushButtons[5]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 434; p->y = 238; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoDistUp; p->visible = true; p = &pushButtons[6]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 345; p->y = 252; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoFadeoutDown; p->visible = true; p = &pushButtons[7]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 434; p->y = 252; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbEchoFadeoutUp; p->visible = true; // echo num scrollbar s = &scrollBars[0]; memset(s, 0, sizeof (scrollBar_t)); s->x = 368; s->y = 224; s->w = 66; s->h = 13; s->callbackFunc = sbSetEchoNumPos; s->visible = true; setScrollBarPageLength(0, 1); setScrollBarEnd(0, 64); // echo distance scrollbar s = &scrollBars[1]; memset(s, 0, sizeof (scrollBar_t)); s->x = 368; s->y = 238; s->w = 66; s->h = 13; s->callbackFunc = sbSetEchoDistPos; s->visible = true; setScrollBarPageLength(1, 1); setScrollBarEnd(1, 16384); // echo fadeout scrollbar s = &scrollBars[2]; memset(s, 0, sizeof (scrollBar_t)); s->x = 368; s->y = 252; s->w = 66; s->h = 13; s->callbackFunc = sbSetEchoFadeoutPos; s->visible = true; setScrollBarPageLength(2, 1); setScrollBarEnd(2, 100); } void handleEchoToolPanic(void) { stopThread = true; } void pbSampleEcho(void) { uint16_t i; if (editor.curInstr == 0 || instr[editor.curInstr] == NULL || instr[editor.curInstr]->samp[editor.curSmp].pek == NULL) { return; } setupEchoBoxWidgets(); windowOpen(); outOfMemory = false; exitFlag = false; while (ui.sysReqShown) { readInput(); if (ui.sysReqEnterPressed) pbCreateEcho(); setSyncedReplayerVars(); handleRedrawing(); drawEchoBox(); setScrollBarPos(0, echo_nEcho, false); setScrollBarPos(1, echo_Distance, false); setScrollBarPos(2, echo_VolChange, false); drawCheckBox(0); for (i = 0; i < 8; i++) drawPushButton(i); for (i = 0; i < 3; i++) drawScrollBar(i); flipFrame(); } hideCheckBox(0); for (i = 0; i < 8; i++) hidePushButton(i); for (i = 0; i < 3; i++) hideScrollBar(i); windowClose(echo_AddMemory ? false : true); if (outOfMemory) okBox(0, "System message", "Not enough memory!"); } static int32_t SDLCALL mixThread(void *ptr) { int8_t *destPtr, *mixPtr; uint8_t mixTyp, destTyp; int32_t destLen, mixLen; int16_t destIns = editor.curInstr; int16_t destSmp = editor.curSmp; int16_t mixIns = editor.srcInstr; int16_t mixSmp = editor.srcSmp; if (destIns == mixIns && destSmp == mixSmp) { setMouseBusy(false); ui.sysReqShown = false; return true; } if (instr[mixIns] == NULL) { mixLen = 0; mixPtr = NULL; mixTyp = 0; } else { mixLen = instr[mixIns]->samp[mixSmp].len; mixPtr = instr[mixIns]->samp[mixSmp].pek; mixTyp = instr[mixIns]->samp[mixSmp].typ; if (mixPtr == NULL) { mixLen = 0; mixTyp = 0; } } if (instr[destIns] == NULL) { destLen = 0; destPtr = NULL; destTyp = 0; } else { destLen = instr[destIns]->samp[destSmp].len; destPtr = instr[destIns]->samp[destSmp].pek; destTyp = instr[destIns]->samp[destSmp].typ; if (destPtr == NULL) { destLen = 0; destTyp = 0; } } bool src16Bits = (mixTyp >> 4) & 1; bool dst16Bits = (destTyp >> 4) & 1; int32_t mix8Size = src16Bits ? (mixLen >> 1) : mixLen; int32_t dest8Size = dst16Bits ? (destLen >> 1) : destLen; int32_t max8Size = (dest8Size > mix8Size) ? dest8Size : mix8Size; int32_t maxLen = dst16Bits ? (max8Size << 1) : max8Size; if (maxLen <= 0) { setMouseBusy(false); ui.sysReqShown = false; return true; } int8_t *p = (int8_t *)calloc(maxLen + LOOP_FIX_LEN, sizeof (int8_t)); if (p == NULL) { outOfMemory = true; setMouseBusy(false); ui.sysReqShown = false; return true; } if (instr[destIns] == NULL && !allocateInstr(destIns)) { outOfMemory = true; setMouseBusy(false); ui.sysReqShown = false; return true; } pauseAudio(); restoreSample(&instr[destIns]->samp[destSmp]); // restore source sample if (instr[mixIns] != NULL) restoreSample(&instr[mixIns]->samp[mixSmp]); const double dAmp1 = mix_Balance / 100.0; const double dAmp2 = 1.0 - dAmp1; int8_t *destPek = p + SMP_DAT_OFFSET; for (int32_t i = 0; i < max8Size; i++) { int32_t index16 = i << 1; int32_t smp1 = (i >= mix8Size) ? 0 : getSampleValue(mixPtr, mixTyp, src16Bits ? index16 : i); int32_t smp2 = (i >= dest8Size) ? 0 : getSampleValue(destPtr, destTyp, dst16Bits ? index16 : i); if (!src16Bits) smp1 <<= 8; if (!dst16Bits) smp2 <<= 8; double dSmp = (smp1 * dAmp1) + (smp2 * dAmp2); if (!dst16Bits) dSmp *= 1.0 / 256.0; // rounding (faster than calling round()) if (dSmp < 0.0) dSmp -= 0.5; else if (dSmp > 0.0) dSmp += 0.5; int32_t smp32 = (int32_t)dSmp; if (dst16Bits) { CLAMP16(smp32); } else { if (smp32 < -128) smp32 = -128; else if (smp32 > 127) smp32 = 127; } putSampleValue(destPek, destTyp, dst16Bits ? index16 : i, (int16_t)smp32); } if (instr[destIns]->samp[destSmp].origPek != NULL) free(instr[destIns]->samp[destSmp].origPek); instr[destIns]->samp[destSmp].origPek = p; instr[destIns]->samp[destSmp].pek = instr[destIns]->samp[destSmp].origPek + SMP_DAT_OFFSET; instr[destIns]->samp[destSmp].len = maxLen; instr[destIns]->samp[destSmp].typ = destTyp; if (dst16Bits) instr[destIns]->samp[destSmp].len &= 0xFFFFFFFE; fixSample(&instr[destIns]->samp[destSmp]); // fix source sample if (instr[mixIns] != NULL) fixSample(&instr[mixIns]->samp[mixSmp]); resumeAudio(); setSongModifiedFlag(); setMouseBusy(false); ui.sysReqShown = false; return true; (void)ptr; } static void pbMix(void) { mouseAnimOn(); thread = SDL_CreateThread(mixThread, NULL, NULL); if (thread == NULL) { okBox(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); } static void sbSetMixBalancePos(uint32_t pos) { if (mix_Balance != (int8_t)pos) mix_Balance = (int8_t)pos; } static void pbMixBalanceDown(void) { if (mix_Balance > 0) mix_Balance--; } static void pbMixBalanceUp(void) { if (mix_Balance < 100) mix_Balance++; } static void drawMixSampleBox(void) { const int16_t x = 192; const int16_t y = 240; const int16_t w = 248; const int16_t h = 38; // main fill fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS); // outer border vLine(x, y, h - 1, PAL_BUTTON1); hLine(x + 1, y, w - 2, PAL_BUTTON1); vLine(x + w - 1, y, h, PAL_BUTTON2); hLine(x, y + h - 1, w - 1, PAL_BUTTON2); // inner border vLine(x + 2, y + 2, h - 5, PAL_BUTTON2); hLine(x + 3, y + 2, w - 6, PAL_BUTTON2); vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1); hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1); textOutShadow(198, 246, PAL_FORGRND, PAL_BUTTON2, "Mixing balance"); assert((mix_Balance >= 0) && (mix_Balance <= 100)); textOutFixed(299, 246, PAL_FORGRND, PAL_BUTTONS, dec3StrTab[mix_Balance]); } static void setupMixBoxWidgets(void) { pushButton_t *p; scrollBar_t *s; // "Apply" pushbutton p = &pushButtons[0]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Apply"; p->x = 197; p->y = 258; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbMix; p->visible = true; // "Exit" pushbutton p = &pushButtons[1]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Exit"; p->x = 361; p->y = 258; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbExit; p->visible = true; // scrollbar buttons p = &pushButtons[2]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 322; p->y = 244; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbMixBalanceDown; p->visible = true; p = &pushButtons[3]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 411; p->y = 244; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbMixBalanceUp; p->visible = true; // mixing balance scrollbar s = &scrollBars[0]; memset(s, 0, sizeof (scrollBar_t)); s->x = 345; s->y = 244; s->w = 66; s->h = 13; s->callbackFunc = sbSetMixBalancePos; s->visible = true; setScrollBarPageLength(0, 1); setScrollBarEnd(0, 100); } void pbSampleMix(void) { uint16_t i; if (editor.curInstr == 0) return; setupMixBoxWidgets(); windowOpen(); outOfMemory = false; exitFlag = false; while (ui.sysReqShown) { readInput(); if (ui.sysReqEnterPressed) pbMix(); setSyncedReplayerVars(); handleRedrawing(); drawMixSampleBox(); setScrollBarPos(0, mix_Balance, false); for (i = 0; i < 4; i++) drawPushButton(i); drawScrollBar(0); flipFrame(); } for (i = 0; i < 4; i++) hidePushButton(i); hideScrollBar(0); windowClose(false); if (outOfMemory) okBox(0, "System message", "Not enough memory!"); } static void sbSetStartVolPos(uint32_t pos) { int16_t val = (int16_t)(pos - 500); if (val != vol_StartVol) { if (ABS(val) < 10) val = 0; else if (ABS(val - 100) < 10) val = 100; else if (ABS(val - 200) < 10) val = 200; else if (ABS(val - 300) < 10) val = 300; else if (ABS(val - 400) < 10) val = 400; else if (ABS(val + 100) < 10) val = -100; else if (ABS(val + 200) < 10) val = -200; else if (ABS(val + 300) < 10) val = -300; else if (ABS(val + 400) < 10) val = -400; vol_StartVol = val; } } static void sbSetEndVolPos(uint32_t pos) { int16_t val = (int16_t)(pos - 500); if (val != vol_EndVol) { if (ABS(val) < 10) val = 0; else if (ABS(val - 100) < 10) val = 100; else if (ABS(val - 200) < 10) val = 200; else if (ABS(val - 300) < 10) val = 300; else if (ABS(val - 400) < 10) val = 400; else if (ABS(val + 100) < 10) val = -100; else if (ABS(val + 200) < 10) val = -200; else if (ABS(val + 300) < 10) val = -300; else if (ABS(val + 400) < 10) val = -400; vol_EndVol = val; } } static void pbSampStartVolDown(void) { if (vol_StartVol > -500) vol_StartVol--; } static void pbSampStartVolUp(void) { if (vol_StartVol < 500) vol_StartVol++; } static void pbSampEndVolDown(void) { if (vol_EndVol > -500) vol_EndVol--; } static void pbSampEndVolUp(void) { if (vol_EndVol < 500) vol_EndVol++; } static int32_t SDLCALL applyVolumeThread(void *ptr) { int32_t x1, x2; if (instr[editor.curInstr] == NULL) goto applyVolumeExit; sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp]; if (smpEd_Rx1 < smpEd_Rx2) { x1 = smpEd_Rx1; x2 = smpEd_Rx2; if (x2 > s->len) x2 = s->len; if (x1 < 0) x1 = 0; if (x2 <= x1) goto applyVolumeExit; if (s->typ & 16) { x1 &= 0xFFFFFFFE; x2 &= 0xFFFFFFFE; } } else { x1 = 0; x2 = s->len; } if (s->typ & 16) { x1 >>= 1; x2 >>= 1; } const int32_t len = x2 - x1; if (len <= 0) goto applyVolumeExit; const double dVol1 = vol_StartVol / 100.0; const double dVol2 = vol_EndVol / 100.0; const double dPosMul = (dVol2 - dVol1) / len; pauseAudio(); restoreSample(s); if (s->typ & 16) { int16_t *ptr16 = (int16_t *)s->pek + x1; for (int32_t i = 0; i < len; i++) { const double dAmp = dVol1 + (i * dPosMul); // linear interpolation double dSmp = ptr16[i] * dAmp; // rounding (faster than calling round()) if (dSmp < 0.0) dSmp -= 0.5; else if (dSmp > 0.0) dSmp += 0.5; int32_t amp32 = (int32_t)dSmp; CLAMP16(amp32); ptr16[i] = (int16_t)amp32; } } else { int8_t *ptr8 = s->pek + x1; for (int32_t i = 0; i < len; i++) { const double dAmp = dVol1 + (i * dPosMul); // linear interpolation double dSmp = ptr8[i] * dAmp; // rounding (faster than calling round()) if (dSmp < 0.0) dSmp -= 0.5; else if (dSmp > 0.0) dSmp += 0.5; int32_t amp32 = (int32_t)dSmp; CLAMP8(amp32); ptr8[i] = (int8_t)amp32; } } fixSample(s); resumeAudio(); setSongModifiedFlag(); applyVolumeExit: setMouseBusy(false); ui.sysReqShown = false; return true; (void)ptr; } static void pbApplyVolume(void) { if (vol_StartVol == 100 && vol_EndVol == 100) { ui.sysReqShown = false; return; // no volume change to be done } mouseAnimOn(); thread = SDL_CreateThread(applyVolumeThread, NULL, NULL); if (thread == NULL) { okBox(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); } static int32_t SDLCALL getMaxScaleThread(void *ptr) { int32_t x1, x2; if (instr[editor.curInstr] == NULL) goto getScaleExit; sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp]; if (smpEd_Rx1 < smpEd_Rx2) { x1 = smpEd_Rx1; x2 = smpEd_Rx2; if (x2 > s->len) x2 = s->len; if (x1 < 0) x1 = 0; if (x2 <= x1) goto getScaleExit; if (s->typ & 16) { x1 &= 0xFFFFFFFE; x2 &= 0xFFFFFFFE; } } else { // no sample marking, operate on the whole sample x1 = 0; x2 = s->len; } uint32_t len = x2 - x1; if (s->typ & 16) len >>= 1; if (len <= 0) { vol_StartVol = 0; vol_EndVol = 0; goto getScaleExit; } restoreSample(s); int32_t maxAmp = 0; if (s->typ & 16) { const int16_t *ptr16 = (const int16_t *)&s->pek[x1]; for (uint32_t i = 0; i < len; i++) { const int32_t absSmp = ABS(ptr16[i]); if (absSmp > maxAmp) maxAmp = absSmp; } } else { const int8_t *ptr8 = (const int8_t *)&s->pek[x1]; for (uint32_t i = 0; i < len; i++) { const int32_t absSmp = ABS(ptr8[i]); if (absSmp > maxAmp) maxAmp = absSmp; } maxAmp <<= 8; } fixSample(s); if (maxAmp <= 0) { vol_StartVol = 0; vol_EndVol = 0; } else { int32_t vol = (100 * 32768) / maxAmp; if (vol > 500) vol = 500; vol_StartVol = (int16_t)vol; vol_EndVol = (int16_t)vol; } getScaleExit: setMouseBusy(false); return true; (void)ptr; } static void pbGetMaxScale(void) { mouseAnimOn(); thread = SDL_CreateThread(getMaxScaleThread, NULL, NULL); if (thread == NULL) { okBox(0, "System message", "Couldn't create thread!"); return; } SDL_DetachThread(thread); } static void drawSampleVolumeBox(void) { char sign; const int16_t x = 166; const int16_t y = 230; const int16_t w = 301; const int16_t h = 52; uint16_t val; // main fill fillRect(x + 1, y + 1, w - 2, h - 2, PAL_BUTTONS); // outer border vLine(x, y, h - 1, PAL_BUTTON1); hLine(x + 1, y, w - 2, PAL_BUTTON1); vLine(x + w - 1, y, h, PAL_BUTTON2); hLine(x, y + h - 1, w - 1, PAL_BUTTON2); // inner border vLine(x + 2, y + 2, h - 5, PAL_BUTTON2); hLine(x + 3, y + 2, w - 6, PAL_BUTTON2); vLine(x + w - 3, y + 2, h - 4, PAL_BUTTON1); hLine(x + 2, y + h - 3, w - 4, PAL_BUTTON1); textOutShadow(172, 236, PAL_FORGRND, PAL_BUTTON2, "Start volume"); textOutShadow(172, 250, PAL_FORGRND, PAL_BUTTON2, "End volume"); charOutShadow(282, 236, PAL_FORGRND, PAL_BUTTON2, '%'); charOutShadow(282, 250, PAL_FORGRND, PAL_BUTTON2, '%'); if (vol_StartVol == 0) sign = ' '; else if (vol_StartVol < 0) sign = '-'; else sign = '+'; val = ABS(vol_StartVol); if (val > 99) { charOut(253, 236, PAL_FORGRND, sign); charOut(260, 236, PAL_FORGRND, '0' + (char)(val / 100)); charOut(267, 236, PAL_FORGRND, '0' + ((val / 10) % 10)); charOut(274, 236, PAL_FORGRND, '0' + (val % 10)); } else if (val > 9) { charOut(260, 236, PAL_FORGRND, sign); charOut(267, 236, PAL_FORGRND, '0' + (char)(val / 10)); charOut(274, 236, PAL_FORGRND, '0' + (val % 10)); } else { charOut(267, 236, PAL_FORGRND, sign); charOut(274, 236, PAL_FORGRND, '0' + (char)val); } if (vol_EndVol == 0) sign = ' '; else if (vol_EndVol < 0) sign = '-'; else sign = '+'; val = ABS(vol_EndVol); if (val > 99) { charOut(253, 250, PAL_FORGRND, sign); charOut(260, 250, PAL_FORGRND, '0' + (char)(val / 100)); charOut(267, 250, PAL_FORGRND, '0' + ((val / 10) % 10)); charOut(274, 250, PAL_FORGRND, '0' + (val % 10)); } else if (val > 9) { charOut(260, 250, PAL_FORGRND, sign); charOut(267, 250, PAL_FORGRND, '0' + (char)(val / 10)); charOut(274, 250, PAL_FORGRND, '0' + (val % 10)); } else { charOut(267, 250, PAL_FORGRND, sign); charOut(274, 250, PAL_FORGRND, '0' + (char)val); } } static void setupVolumeBoxWidgets(void) { pushButton_t *p; scrollBar_t *s; // "Apply" pushbutton p = &pushButtons[0]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Apply"; p->x = 171; p->y = 262; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbApplyVolume; p->visible = true; // "Get maximum scale" pushbutton p = &pushButtons[1]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Get maximum scale"; p->x = 245; p->y = 262; p->w = 143; p->h = 16; p->callbackFuncOnUp = pbGetMaxScale; p->visible = true; // "Exit" pushbutton p = &pushButtons[2]; memset(p, 0, sizeof (pushButton_t)); p->caption = "Exit"; p->x = 389; p->y = 262; p->w = 73; p->h = 16; p->callbackFuncOnUp = pbExit; p->visible = true; // scrollbar buttons p = &pushButtons[3]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 292; p->y = 234; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbSampStartVolDown; p->visible = true; p = &pushButtons[4]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 439; p->y = 234; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbSampStartVolUp; p->visible = true; p = &pushButtons[5]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_LEFT_STRING; p->x = 292; p->y = 248; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbSampEndVolDown; p->visible = true; p = &pushButtons[6]; memset(p, 0, sizeof (pushButton_t)); p->caption = ARROW_RIGHT_STRING; p->x = 439; p->y = 248; p->w = 23; p->h = 13; p->preDelay = 1; p->delayFrames = 3; p->callbackFuncOnDown = pbSampEndVolUp; p->visible = true; // volume start scrollbar s = &scrollBars[0]; memset(s, 0, sizeof (scrollBar_t)); s->x = 315; s->y = 234; s->w = 124; s->h = 13; s->callbackFunc = sbSetStartVolPos; s->visible = true; setScrollBarPageLength(0, 1); setScrollBarEnd(0, 500 * 2); setScrollBarPos(0, 500, false); // volume end scrollbar s = &scrollBars[1]; memset(s, 0, sizeof (scrollBar_t)); s->x = 315; s->y = 248; s->w = 124; s->h = 13; s->callbackFunc = sbSetEndVolPos; s->visible = true; setScrollBarPageLength(1, 1); setScrollBarEnd(1, 500 * 2); setScrollBarPos(1, 500, false); } void pbSampleVolume(void) { uint16_t i; if (editor.curInstr == 0 || instr[editor.curInstr] == NULL || instr[editor.curInstr]->samp[editor.curSmp].pek == NULL) { return; } setupVolumeBoxWidgets(); windowOpen(); exitFlag = false; while (ui.sysReqShown) { readInput(); if (ui.sysReqEnterPressed) { pbApplyVolume(); keyb.ignoreCurrKeyUp = true; // don't handle key up event for this key release } setSyncedReplayerVars(); handleRedrawing(); // this is needed for the "Get maximum scale" button if (ui.setMouseIdle) mouseAnimOff(); drawSampleVolumeBox(); setScrollBarPos(0, 500 + vol_StartVol, false); setScrollBarPos(1, 500 + vol_EndVol, false); for (i = 0; i < 7; i++) drawPushButton(i); for (i = 0; i < 2; i++) drawScrollBar(i); flipFrame(); } for (i = 0; i < 7; i++) hidePushButton(i); for (i = 0; i < 2; i++) hideScrollBar(i); windowClose(true); }