ref: 5dca43d1a307ba6d2288c4be9ff7d3f705aaabc4
dir: /src/ft2_sample_ed.c/
// 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>
#ifndef _WIN32
#include <unistd.h> // chdir() in UNICHAR_CHDIR()
#endif
#if defined _WIN32 || defined __amd64__ || (defined __i386__ && defined __SSE2__)
#include <emmintrin.h>
#endif
#include "ft2_header.h"
#include "ft2_config.h"
#include "ft2_audio.h"
#include "ft2_pattern_ed.h"
#include "ft2_gui.h"
#include "scopes/ft2_scopes.h"
#include "ft2_video.h"
#include "ft2_inst_ed.h"
#include "ft2_sample_ed.h"
#include "ft2_sample_saver.h"
#include "ft2_mouse.h"
#include "ft2_diskop.h"
#include "ft2_keyboard.h"
#include "ft2_structs.h"
#include "ft2_replayer.h"
#include "mixer/ft2_windowed_sinc.h" // SINC_TAPS, SINC_NEGATIVE_TAPS
static const char sharpNote1Char[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' };
static const char sharpNote2Char[12] = { '-', '#', '-', '#', '-', '-', '#', '-', '#', '-', '#', '-' };
static const char flatNote1Char[12] = { 'C', 'D', 'D', 'E', 'E', 'F', 'G', 'G', 'A', 'A', 'B', 'B' };
static const char flatNote2Char[12] = { '-', 'b', '-', 'b', '-', '-', 'b', '-', 'b', '-', 'b', '-' };
static char smpEd_SysReqText[64];
static int8_t *smpCopyBuff;
static bool updateLoopsOnMouseUp, writeSampleFlag, smpCopyDidCopyWholeSample;
static int32_t smpEd_OldSmpPosLine = -1;
static int32_t smpEd_ViewSize, smpEd_ScrPos, smpCopySize, smpCopyBits;
static int32_t old_Rx1, old_Rx2, old_ViewSize, old_SmpScrPos;
static int32_t lastMouseX, lastMouseY, lastDrawX, lastDrawY, mouseXOffs, curSmpLoopStart, curSmpLoopLength;
static double dScrPosScaled, dPos2ScrMul, dScr2SmpPosMul;
static sample_t smpCopySample;
static SDL_Thread *thread;
// globals
int32_t smpEd_Rx1 = 0, smpEd_Rx2 = 0;
// allocs sample with proper alignment and padding for branchless resampling interpolation
bool allocateSmpData(sample_t *s, int32_t length, bool sample16Bit)
{
if (sample16Bit)
length <<= 1;
s->origDataPtr = (int8_t *)malloc(length + SAMPLE_PAD_LENGTH);
if (s->origDataPtr == NULL)
{
s->dataPtr = NULL;
return false;
}
s->dataPtr = s->origDataPtr + SMP_DAT_OFFSET;
return true;
}
bool allocateSmpDataPtr(smpPtr_t *sp, int32_t length, bool sample16Bit)
{
if (sample16Bit)
length <<= 1;
int8_t *newPtr = (int8_t *)malloc(length + SAMPLE_PAD_LENGTH);
if (newPtr == NULL)
return false;
sp->origPtr = newPtr;
sp->ptr = sp->origPtr + SMP_DAT_OFFSET;
return true;
}
// reallocs sample with proper alignment and padding for branchless resampling interpolation
bool reallocateSmpData(sample_t *s, int32_t length, bool sample16Bit)
{
if (s->origDataPtr == NULL)
return allocateSmpData(s, length, sample16Bit);
if (sample16Bit)
length <<= 1;
int8_t *newPtr = (int8_t *)realloc(s->origDataPtr, length + SAMPLE_PAD_LENGTH);
if (newPtr == NULL)
return false;
s->origDataPtr = newPtr;
s->dataPtr = s->origDataPtr + SMP_DAT_OFFSET;
return true;
}
// reallocs sample with proper alignment and padding for branchless resampling interpolation
bool reallocateSmpDataPtr(smpPtr_t *sp, int32_t length, bool sample16Bit)
{
if (sp->origPtr == NULL)
return allocateSmpDataPtr(sp, length, sample16Bit);
if (sample16Bit)
length <<= 1;
int8_t *newPtr = (int8_t *)realloc(sp->origPtr, length + SAMPLE_PAD_LENGTH);
if (newPtr == NULL)
return false;
sp->origPtr = newPtr;
sp->ptr = sp->origPtr + SMP_DAT_OFFSET;
return true;
}
void setSmpDataPtr(sample_t *s, smpPtr_t *sp)
{
s->origDataPtr = sp->origPtr;
s->dataPtr = sp->ptr;
}
void freeSmpDataPtr(smpPtr_t *sp)
{
if (sp->origPtr != NULL)
{
free(sp->origPtr);
sp->origPtr = NULL;
}
sp->ptr = NULL;
}
void freeSmpData(sample_t *s)
{
if (s->origDataPtr != NULL)
{
free(s->origDataPtr);
s->origDataPtr = NULL;
}
s->dataPtr = NULL;
s->isFixed = false;
}
bool cloneSample(sample_t *src, sample_t *dst)
{
smpPtr_t sp;
freeSmpData(dst);
memcpy(dst, src, sizeof (sample_t));
dst->origDataPtr = dst->dataPtr = NULL;
dst->isFixed = false;
if (src->length > 0 && src->dataPtr != NULL)
{
bool sample16Bit = !!(src->flags & SAMPLE_16BIT);
if (!allocateSmpDataPtr(&sp, src->length, sample16Bit))
return false;
memcpy(sp.ptr, src->dataPtr, src->length);
setSmpDataPtr(dst, &sp);
fixSample(dst);
}
return true;
}
sample_t *getCurSample(void)
{
if (editor.curInstr == 0 || instr[editor.curInstr] == NULL)
return NULL;
return &instr[editor.curInstr]->smp[editor.curSmp];
}
void sanitizeSample(sample_t *s)
{
if (s == NULL)
return;
// if a sample has both forward loop and pingpong loop set, it means pingpong loop (FT2 mixer behavior)
if (GET_LOOPTYPE(s->flags) == (LOOP_FWD | LOOP_BIDI))
s->flags &= ~LOOP_FWD; // remove forward loop flag
if (s->volume > 64)
s->volume = 64;
s->relativeNote = CLAMP(s->relativeNote, -48, 71);
s->length = CLAMP(s->length, 0, MAX_SAMPLE_LEN);
if (s->loopStart < 0 || s->loopLength <= 0 || s->loopStart+s->loopLength > s->length)
{
s->loopStart = 0;
s->loopLength = 0;
DISABLE_LOOP(s->flags);
}
}
static int32_t myMod(int32_t a, int32_t b) // works on negative numbers!
{
int32_t c = a % b;
return (c < 0) ? (c + b) : c;
}
// modifies samples before index 0, and after loop/end (for branchless mixer interpolation (kinda))
void fixSample(sample_t *s)
{
int32_t pos;
bool backwards;
assert(s != NULL);
if (s->dataPtr == NULL || s->length <= 0)
{
s->isFixed = false;
s->fixedPos = 0;
return; // empty sample
}
const bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
int16_t *ptr16 = (int16_t *)s->dataPtr;
uint8_t loopType = GET_LOOPTYPE(s->flags);
int32_t length = s->length;
int32_t loopStart = s->loopStart;
int32_t loopLength = s->loopLength;
int32_t loopEnd = s->loopStart + s->loopLength;
// treat loop as disabled if loopLen == 0 (FT2 does this)
if (loopType != 0 && loopLength <= 0)
{
loopType = 0;
loopStart = loopLength = loopEnd = 0;
}
/* All negative taps should be equal to the first sample point when at sampling
** position #0 (on sample trigger), until an eventual loop cycle, where we do
** a special left edge case with replaced tap data.
** The sample pointer is offset and has allocated data before it, so this is
** safe.
*/
if (sample16Bit)
{
for (int32_t i = 0; i < SINC_LEFT_TAPS; i++)
ptr16[i-SINC_LEFT_TAPS] = ptr16[0];
}
else
{
for (int32_t i = 0; i < SINC_LEFT_TAPS; i++)
s->dataPtr[i-SINC_LEFT_TAPS] = s->dataPtr[0];
}
if (loopType == LOOP_OFF) // no loop
{
if (sample16Bit)
{
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
ptr16[length+i] = ptr16[length-1];
}
else
{
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
s->dataPtr[length+i] = s->dataPtr[length-1];
}
s->fixedPos = 0; // this value is not used for non-looping samples, set to zero
s->isFixed = false; // no fixed samples inside actual sample data
return;
}
s->fixedPos = loopEnd;
s->isFixed = true;
if (loopType == LOOP_FWD) // forward loop
{
if (sample16Bit)
{
// left edge (we need SINC_TAPS amount of taps starting from the center tap)
for (int32_t i = -SINC_LEFT_TAPS; i < SINC_TAPS; i++)
{
pos = loopStart + myMod(i, loopLength);
s->leftEdgeTapSamples16[SINC_LEFT_TAPS+i] = ptr16[pos];
}
// right edge (change actual sample data since data after loop is never used)
pos = loopStart;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
{
s->fixedSmp[i] = ptr16[loopEnd+i];
ptr16[loopEnd+i] = ptr16[pos];
if (++pos >= loopEnd)
pos -= loopLength;
}
}
else // 8-bit
{
// left edge (we need SINC_TAPS amount of taps starting from the center tap)
for (int32_t i = -SINC_LEFT_TAPS; i < SINC_TAPS; i++)
{
pos = loopStart + myMod(i, loopLength);
s->leftEdgeTapSamples8[SINC_LEFT_TAPS+i] = s->dataPtr[pos];
}
// right edge (change actual sample data since data after loop is never used)
pos = loopStart;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
{
s->fixedSmp[i] = s->dataPtr[loopEnd+i];
s->dataPtr[loopEnd+i] = s->dataPtr[pos];
if (++pos >= loopEnd)
pos -= loopLength;
}
}
}
else // pingpong loop
{
if (sample16Bit)
{
// left edge (positive taps, we need SINC_TAPS amount of taps starting from the center tap)
pos = loopStart;
backwards = false;
for (int32_t i = 0; i < SINC_TAPS; i++)
{
if (backwards)
{
if (pos < loopStart)
{
pos = loopStart;
backwards = false;
}
}
else if (pos >= loopEnd) // forwards
{
pos = loopEnd-1;
backwards = true;
}
s->leftEdgeTapSamples16[SINC_LEFT_TAPS+i] = ptr16[pos];
if (backwards)
pos--;
else
pos++;
}
// left edge (negative taps)
for (int32_t i = 0; i < SINC_LEFT_TAPS; i++)
s->leftEdgeTapSamples16[(SINC_LEFT_TAPS-1)-i] = s->leftEdgeTapSamples16[SINC_LEFT_TAPS+1+i];
// right edge (change actual sample data since data after loop is never used)
pos = loopEnd-1;
backwards = true;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
{
if (backwards)
{
if (pos < loopStart)
{
pos = loopStart;
backwards = false;
}
}
else if (pos >= loopEnd) // forwards
{
pos = loopEnd-1;
backwards = true;
}
s->fixedSmp[i] = ptr16[loopEnd+i];
ptr16[loopEnd+i] = ptr16[pos];
if (backwards)
pos--;
else
pos++;
}
}
else // 8-bit
{
// left edge (positive taps, we need SINC_TAPS amount of taps starting from the center tap)
pos = loopStart;
backwards = false;
for (int32_t i = 0; i < SINC_TAPS; i++)
{
if (backwards)
{
if (pos < loopStart)
{
pos = loopStart;
backwards = false;
}
}
else if (pos >= loopEnd) // forwards
{
pos = loopEnd-1;
backwards = true;
}
s->leftEdgeTapSamples8[SINC_LEFT_TAPS+i] = s->dataPtr[pos];
if (backwards)
pos--;
else
pos++;
}
// left edge (negative taps)
for (int32_t i = 0; i < SINC_LEFT_TAPS; i++)
s->leftEdgeTapSamples8[(SINC_LEFT_TAPS-1)-i] = s->leftEdgeTapSamples8[SINC_LEFT_TAPS+1+i];
// right edge (change actual sample data since data after loop is never used)
pos = loopEnd-1;
backwards = true;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
{
if (backwards)
{
if (pos < loopStart)
{
pos = loopStart;
backwards = false;
}
}
else if (pos >= loopEnd) // forwards
{
pos = loopEnd-1;
backwards = true;
}
s->fixedSmp[i] = s->dataPtr[loopEnd+i];
s->dataPtr[loopEnd+i] = s->dataPtr[pos];
if (backwards)
pos--;
else
pos++;
}
}
}
}
// restores interpolation tap samples after loop/end
void unfixSample(sample_t *s)
{
assert(s != NULL);
if (s->dataPtr == NULL || !s->isFixed)
return; // empty sample or not fixed (f.ex. no loop)
if (s->flags & SAMPLE_16BIT)
{
int16_t *ptr16 = (int16_t *)s->dataPtr + s->fixedPos;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
ptr16[i] = s->fixedSmp[i];
}
else // 8-bit
{
int8_t *ptr8 = s->dataPtr + s->fixedPos;
for (int32_t i = 0; i < SINC_RIGHT_TAPS; i++)
ptr8[i] = (int8_t)s->fixedSmp[i];
}
s->isFixed = false;
}
double getSampleValue(int8_t *smpData, int32_t position, bool sample16Bit)
{
if (smpData == NULL)
return 0;
if (sample16Bit)
{
position <<= 1;
return *((int16_t *)&smpData[position]);
}
else
{
return smpData[position];
}
}
void putSampleValue(int8_t *smpData, int32_t position, double dSample, bool sample16Bit)
{
DROUND(dSample);
int32_t sample = (int32_t)dSample;
if (sample16Bit)
{
CLAMP16(sample);
*((int16_t *)&smpData[position<<1]) = (int16_t)sample;
}
else
{
CLAMP8(sample);
smpData[position] = (int8_t)sample;
}
}
void clearCopyBuffer(void)
{
if (smpCopyBuff != NULL)
{
free(smpCopyBuff);
smpCopyBuff = NULL;
}
smpCopySize = 0;
smpCopyBits = 8;
smpCopyDidCopyWholeSample = false;
}
int32_t getSampleMiddleCRate(sample_t *s)
{
return (int32_t)(getSampleC4Rate(s) + 0.5); // rounded
}
int32_t getSampleRangeStart(void)
{
return smpEd_Rx1;
}
int32_t getSampleRangeEnd(void)
{
return smpEd_Rx2;
}
int32_t getSampleRangeLength(void)
{
return smpEd_Rx2 - smpEd_Rx1;
}
// for smpPos2Scr() / scr2SmpPos()
static void updateViewSize(void)
{
if (smpEd_ViewSize == 0)
dPos2ScrMul = 1.0;
else
dPos2ScrMul = (double)SAMPLE_AREA_WIDTH / smpEd_ViewSize;
dScr2SmpPosMul = smpEd_ViewSize * (1.0 / SAMPLE_AREA_WIDTH);
}
static void updateScrPos(void)
{
dScrPosScaled = floor(smpEd_ScrPos * dPos2ScrMul);
}
// sample pos -> screen x pos (if outside of visible area, will return <0 or >=SCREEN_W)
static int32_t smpPos2Scr(int32_t pos)
{
if (smpEd_ViewSize <= 0)
return -1;
sample_t *s = getCurSample();
if (s == NULL)
return -1;
if (pos > s->length)
pos = s->length;
double dPos = (pos * dPos2ScrMul) + 0.5; // pre-rounding bias is needed here
dPos -= dScrPosScaled;
// this is important, or else the result can mess up in some cases
dPos = CLAMP(dPos, INT32_MIN, INT32_MAX);
pos = (int32_t)dPos;
return pos;
}
// screen x pos -> sample pos
static int32_t scr2SmpPos(int32_t x)
{
if (smpEd_ViewSize <= 0)
return 0;
sample_t *s = getCurSample();
if (s == NULL)
return 0;
if (x < 0)
x = 0;
double dPos = (dScrPosScaled + x) * dScr2SmpPosMul;
x = (int32_t)dPos;
if (x > s->length)
x = s->length;
return x;
}
static void hideLoopPinSprites(void)
{
hideSprite(SPRITE_LEFT_LOOP_PIN);
hideSprite(SPRITE_RIGHT_LOOP_PIN);
}
static void fixLoopGadgets(void)
{
if (!ui.sampleEditorShown)
{
hideLoopPinSprites();
return;
}
sample_t *s = getCurSample();
bool showLoopPins = true;
if (s == NULL || s->dataPtr == NULL || s->length <= 0 || GET_LOOPTYPE(s->flags) == LOOP_OFF)
showLoopPins = false;
// draw Repeat/Replen. numbers
hexOutBg(536, 375, PAL_FORGRND, PAL_DESKTOP, curSmpLoopStart, 8);
hexOutBg(536, 387, PAL_FORGRND, PAL_DESKTOP, curSmpLoopLength, 8);
if (!showLoopPins)
{
hideLoopPinSprites();
}
else
{
// draw sample loop points
const int32_t loopStart = smpPos2Scr(curSmpLoopStart);
const int32_t loopEnd = smpPos2Scr(curSmpLoopStart+curSmpLoopLength);
// do -8 test because part of the loop sprite sticks out on the left/right
if (loopStart >= -8 && loopStart <= SAMPLE_AREA_WIDTH+8)
setSpritePos(SPRITE_LEFT_LOOP_PIN, (int16_t)(loopStart - 8), 174);
else
hideSprite(SPRITE_LEFT_LOOP_PIN);
if (loopEnd >= -8)
{
if (loopEnd <= SAMPLE_AREA_WIDTH+8)
setSpritePos(SPRITE_RIGHT_LOOP_PIN, (int16_t)(loopEnd - 8), 174);
else
hideSprite(SPRITE_RIGHT_LOOP_PIN);
}
else
{
hideSprite(SPRITE_RIGHT_LOOP_PIN);
}
}
}
static void fixSampleScrollbar(void)
{
sample_t *s = getCurSample();
if (s == NULL)
{
setScrollBarPageLength(SB_SAMP_SCROLL, 0);
setScrollBarEnd(SB_SAMP_SCROLL, 0);
setScrollBarPos(SB_SAMP_SCROLL, 0, false);
return;
}
setScrollBarPageLength(SB_SAMP_SCROLL, smpEd_ViewSize);
setScrollBarEnd(SB_SAMP_SCROLL, instr[editor.curInstr]->smp[editor.curSmp].length);
setScrollBarPos(SB_SAMP_SCROLL, smpEd_ScrPos, false);
}
static bool getCopyBuffer(int32_t size, bool sample16Bit)
{
if (smpCopyBuff != NULL)
free(smpCopyBuff);
if (size > MAX_SAMPLE_LEN)
size = MAX_SAMPLE_LEN;
smpCopyBuff = (int8_t *)malloc(size << sample16Bit);
if (smpCopyBuff == NULL)
{
smpCopySize = 0;
return false;
}
smpCopySize = size;
return true;
}
static int32_t SDLCALL copySampleThread(void *ptr)
{
sample_t *src = &instr[editor.srcInstr]->smp[editor.srcSmp];
sample_t *dst = &instr[editor.curInstr]->smp[editor.curSmp];
pauseAudio();
if (instr[editor.curInstr] == NULL && !allocateInstr(editor.curInstr))
goto error;
if (!cloneSample(src, dst))
goto error;
resumeAudio();
editor.updateCurSmp = true;
setSongModifiedFlag();
setMouseBusy(false);
return true;
error:
resumeAudio();
okBoxThreadSafe(0, "System message", "Not enough memory!");
return true;
(void)ptr;
}
void copySmp(void) // copy sample from srcInstr->srcSmp to curInstr->curSmp
{
if (editor.curInstr == 0 || (editor.curInstr == editor.srcInstr && editor.curSmp == editor.srcSmp))
return;
mouseAnimOn();
thread = SDL_CreateThread(copySampleThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
void xchgSmp(void) // dstSmp <-> srcSmp
{
if (editor.curInstr == 0 ||
(editor.curInstr == editor.srcInstr && editor.curSmp == editor.srcSmp) ||
instr[editor.curInstr] == NULL)
{
return;
}
sample_t *src = &instr[editor.curInstr]->smp[editor.srcSmp];
sample_t *dst = &instr[editor.curInstr]->smp[editor.curSmp];
lockMixerCallback();
const sample_t dstTmp = *dst;
*dst = *src;
*src = dstTmp;
unlockMixerCallback();
updateNewSample();
setSongModifiedFlag();
}
static void writeRange(void)
{
// very first sample (rx1=0,rx2=0) is the "no range" special case
if (!ui.sampleEditorShown || smpEd_ViewSize == 0 || (smpEd_Rx1 == 0 && smpEd_Rx2 == 0))
return;
// test if range is outside of view (passed it by scrolling)
int32_t start = smpPos2Scr(smpEd_Rx1);
if (start >= SAMPLE_AREA_WIDTH)
return;
// test if range is outside of view (passed it by scrolling)
int32_t end = smpPos2Scr(smpEd_Rx2);
if (end < 0)
return;
start = CLAMP(start, 0, SAMPLE_AREA_WIDTH-1);
end = CLAMP(end, 0, SAMPLE_AREA_WIDTH-1);
int32_t rangeLen = (end + 1) - start;
assert(start+rangeLen <= SCREEN_W);
uint32_t *ptr32 = &video.frameBuffer[(174 * SCREEN_W) + start];
for (int32_t y = 0; y < SAMPLE_AREA_HEIGHT; y++)
{
for (int32_t x = 0; x < rangeLen; x++)
ptr32[x] = video.palette[(ptr32[x] >> 24) ^ 2]; // ">> 24" to get palette, XOR 2 to switch between mark/normal palette
ptr32 += SCREEN_W;
}
}
static int32_t getScaledSample(sample_t *s, int32_t index) // for sample data viewer
{
int32_t tmp32, sample;
const int32_t loopEnd = s->loopStart + s->loopLength;
if (s->dataPtr == NULL || index < 0 || index >= s->length)
return 0;
if (s->flags & SAMPLE_16BIT)
{
int16_t *ptr16 = (int16_t *)s->dataPtr;
// don't read fixed mixer interpolation samples, read the prestine ones instead
if (index >= s->fixedPos && index < s->fixedPos+SINC_RIGHT_TAPS && s->length > loopEnd && s->isFixed)
tmp32 = s->fixedSmp[index-s->fixedPos];
else
tmp32 = ptr16[index];
sample = (int8_t)((tmp32 * SAMPLE_AREA_HEIGHT) >> 16);
}
else // 8-bit
{
if (index >= s->fixedPos && index < s->fixedPos+SINC_RIGHT_TAPS && s->length > loopEnd && s->isFixed)
tmp32 = s->fixedSmp[index-s->fixedPos];
else
tmp32 = s->dataPtr[index];
sample = (int8_t)((tmp32 * SAMPLE_AREA_HEIGHT) >> 8);
}
return SAMPLE_AREA_Y_CENTER-sample;
}
void sampleLine(int32_t x1, int32_t x2, int32_t y1, int32_t y2)
{
int32_t d;
const int32_t dx = x2 - x1;
const int32_t ax = ABS(dx) * 2;
const int32_t sx = SGN(dx);
const int32_t dy = y2 - y1;
const int32_t ay = ABS(dy) * 2;
const int32_t sy = SGN(dy);
int32_t x = x1;
int32_t y = y1;
const uint32_t pal1 = video.palette[PAL_DESKTOP];
const uint32_t pal2 = video.palette[PAL_FORGRND];
const uint32_t pixVal = video.palette[PAL_PATTEXT];
const int32_t pitch = sy * SCREEN_W;
uint32_t *dst32 = &video.frameBuffer[(y * SCREEN_W) + x];
// draw line
if (ax > ay)
{
d = ay - (ax >> 1);
while (true)
{
// invert certain colors
if (*dst32 != pal2)
{
if (*dst32 == pal1)
*dst32 = pal2;
else
*dst32 = pixVal;
}
if (x == x2)
break;
if (d >= 0)
{
d -= ax;
dst32 += pitch;
}
x += sx;
d += ay;
dst32 += sx;
}
}
else
{
d = ax - (ay >> 1);
while (true)
{
// invert certain colors
if (*dst32 != pal2)
{
if (*dst32 == pal1)
*dst32 = pal2;
else
*dst32 = pixVal;
}
if (y == y2)
break;
if (d >= 0)
{
d -= ay;
dst32 += sx;
}
y += sy;
d += ax;
dst32 += pitch;
}
}
}
static void getMinMax16(const void *p, uint32_t scanLen, int16_t *min16, int16_t *max16)
{
#if defined _WIN32 || defined __amd64__ || (defined __i386__ && defined __SSE2__)
if (cpu.hasSSE2)
{
/* Taken with permission from the OpenMPT project (and slightly modified).
**
** SSE2 implementation for min/max finder, packs 8*int16 in a 128-bit XMM register.
** scanLen = How many samples to process
*/
const int16_t *p16;
uint32_t scanLen8;
const __m128i *v;
__m128i minVal, maxVal, minVal2, maxVal2, curVals;
// Put minimum / maximum in 8 packed int16 values
minVal = _mm_set1_epi16(32767);
maxVal = _mm_set1_epi16(-32768);
scanLen8 = scanLen / 8;
if (scanLen8 > 0)
{
v = (__m128i *)p;
p = (const __m128i *)p + scanLen8;
while (scanLen8--)
{
curVals = _mm_loadu_si128(v++);
minVal = _mm_min_epi16(minVal, curVals);
maxVal = _mm_max_epi16(maxVal, curVals);
}
/* Now we have 8 minima and maxima each.
** Move the upper 4 values to the lower half and compute the minima/maxima of that. */
minVal2 = _mm_unpackhi_epi64(minVal, minVal);
maxVal2 = _mm_unpackhi_epi64(maxVal, maxVal);
minVal = _mm_min_epi16(minVal, minVal2);
maxVal = _mm_max_epi16(maxVal, maxVal2);
/* Now we have 4 minima and maxima each.
** Move the upper 2 values to the lower half and compute the minima/maxima of that. */
minVal2 = _mm_shuffle_epi32(minVal, _MM_SHUFFLE(1, 1, 1, 1));
maxVal2 = _mm_shuffle_epi32(maxVal, _MM_SHUFFLE(1, 1, 1, 1));
minVal = _mm_min_epi16(minVal, minVal2);
maxVal = _mm_max_epi16(maxVal, maxVal2);
// Compute the minima/maxima of the both remaining values
minVal2 = _mm_shufflelo_epi16(minVal, _MM_SHUFFLE(1, 1, 1, 1));
maxVal2 = _mm_shufflelo_epi16(maxVal, _MM_SHUFFLE(1, 1, 1, 1));
minVal = _mm_min_epi16(minVal, minVal2);
maxVal = _mm_max_epi16(maxVal, maxVal2);
}
p16 = (const int16_t *)p;
while (scanLen-- & 7)
{
curVals = _mm_set1_epi16(*p16++);
minVal = _mm_min_epi16(minVal, curVals);
maxVal = _mm_max_epi16(maxVal, curVals);
}
*min16 = (int16_t)_mm_cvtsi128_si32(minVal);
*max16 = (int16_t)_mm_cvtsi128_si32(maxVal);
}
else
#endif
{
// non-SSE version (really slow for big samples while zoomed out)
int16_t minVal = 32767;
int16_t maxVal = -32768;
const int16_t *ptr16 = (const int16_t *)p;
for (uint32_t i = 0; i < scanLen; i++)
{
const int16_t smp16 = ptr16[i];
if (smp16 < minVal) minVal = smp16;
if (smp16 > maxVal) maxVal = smp16;
}
*min16 = minVal;
*max16 = maxVal;
}
}
static void getMinMax8(const void *p, uint32_t scanLen, int8_t *min8, int8_t *max8)
{
#if defined _WIN32 || defined __amd64__ || (defined __i386__ && defined __SSE2__)
if (cpu.hasSSE2)
{
/* Taken with permission from the OpenMPT project (and slightly modified).
**
** SSE2 implementation for min/max finder, packs 16*int8 in a 128-bit XMM register.
** scanLen = How many samples to process
*/
const int8_t *p8;
uint32_t scanLen16;
const __m128i *v;
__m128i xorVal, minVal, maxVal, minVal2, maxVal2, curVals;
// Put minimum / maximum in 8 packed int16 values (-1 and 0 because unsigned)
minVal = _mm_set1_epi8(-1);
maxVal = _mm_set1_epi8(0);
// For signed <-> unsigned conversion (_mm_min_epi8/_mm_max_epi8 is SSE4)
xorVal = _mm_set1_epi8(0x80);
scanLen16 = scanLen / 16;
if (scanLen16 > 0)
{
v = (__m128i *)p;
p = (const __m128i *)p + scanLen16;
while (scanLen16--)
{
curVals = _mm_loadu_si128(v++);
curVals = _mm_xor_si128(curVals, xorVal);
minVal = _mm_min_epu8(minVal, curVals);
maxVal = _mm_max_epu8(maxVal, curVals);
}
/* Now we have 16 minima and maxima each.
** Move the upper 8 values to the lower half and compute the minima/maxima of that. */
minVal2 = _mm_unpackhi_epi64(minVal, minVal);
maxVal2 = _mm_unpackhi_epi64(maxVal, maxVal);
minVal = _mm_min_epu8(minVal, minVal2);
maxVal = _mm_max_epu8(maxVal, maxVal2);
/* Now we have 8 minima and maxima each.
** Move the upper 4 values to the lower half and compute the minima/maxima of that. */
minVal2 = _mm_shuffle_epi32(minVal, _MM_SHUFFLE(1, 1, 1, 1));
maxVal2 = _mm_shuffle_epi32(maxVal, _MM_SHUFFLE(1, 1, 1, 1));
minVal = _mm_min_epu8(minVal, minVal2);
maxVal = _mm_max_epu8(maxVal, maxVal2);
/* Now we have 4 minima and maxima each.
** Move the upper 2 values to the lower half and compute the minima/maxima of that. */
minVal2 = _mm_srai_epi32(minVal, 16);
maxVal2 = _mm_srai_epi32(maxVal, 16);
minVal = _mm_min_epu8(minVal, minVal2);
maxVal = _mm_max_epu8(maxVal, maxVal2);
// Compute the minima/maxima of the both remaining values
minVal2 = _mm_srai_epi16(minVal, 8);
maxVal2 = _mm_srai_epi16(maxVal, 8);
minVal = _mm_min_epu8(minVal, minVal2);
maxVal = _mm_max_epu8(maxVal, maxVal2);
}
p8 = (const int8_t *)p;
while (scanLen-- & 15)
{
curVals = _mm_set1_epi8(*p8++ ^ 0x80);
minVal = _mm_min_epu8(minVal, curVals);
maxVal = _mm_max_epu8(maxVal, curVals);
}
*min8 = (int8_t)(_mm_cvtsi128_si32(minVal) ^ 0x80);
*max8 = (int8_t)(_mm_cvtsi128_si32(maxVal) ^ 0x80);
}
else
#endif
{
// non-SSE version (really slow for big samples while zoomed out)
int8_t minVal = 127;
int8_t maxVal = -128;
const int8_t *ptr8 = (const int8_t *)p;
for (uint32_t i = 0; i < scanLen; i++)
{
const int8_t smp8 = ptr8[i];
if (smp8 < minVal) minVal = smp8;
if (smp8 > maxVal) maxVal = smp8;
}
*min8 = minVal;
*max8 = maxVal;
}
}
// for scanning sample data peak where loopEnd+SINC_RIGHT_TAPS is within scan range (fixed interpolation tap samples)
static void getSpecialMinMax16(sample_t *s, int32_t index, int32_t scanEnd, int16_t *min16, int16_t *max16)
{
int16_t minVal2, maxVal2;
const int16_t *ptr16 = (const int16_t *)s->dataPtr;
int16_t minVal = 32767;
int16_t maxVal = -32768;
// read samples before fixed samples (if needed)
if (index < s->fixedPos)
{
getMinMax16(&ptr16[index], s->fixedPos-index, &minVal, &maxVal);
index = s->fixedPos;
}
// read fixed samples (we are guaranteed to be within the fixed samples here)
const int32_t tapIndex = index-s->fixedPos;
const int32_t scanLength = SINC_RIGHT_TAPS-tapIndex;
int32_t tmpScanEnd = index+scanLength;
if (tmpScanEnd > scanEnd)
tmpScanEnd = scanEnd;
const int16_t *smpReadPtr = s->fixedSmp + tapIndex;
for (; index < tmpScanEnd; index++)
{
const int16_t smp16 = *smpReadPtr++;
if (smp16 < minVal) minVal = smp16;
if (smp16 > maxVal) maxVal = smp16;
}
// read samples after fixed samples (if needed)
if (index < scanEnd)
{
getMinMax16(&ptr16[index], scanEnd-index, &minVal2, &maxVal2);
if (minVal2 < minVal) minVal = minVal2;
if (maxVal2 > maxVal) maxVal = maxVal2;
}
*min16 = minVal;
*max16 = maxVal;
}
// for scanning sample data peak where loopEnd+SINC_RIGHT_TAPS is within scan range (fixed interpolation tap samples)
static void getSpecialMinMax8(sample_t *s, int32_t index, int32_t scanEnd, int8_t *min8, int8_t *max8)
{
int8_t minVal2, maxVal2;
const int8_t *ptr8 = (const int8_t *)s->dataPtr;
int8_t minVal = 127;
int8_t maxVal = -128;
// read samples before fixed samples (if needed)
if (index < s->fixedPos)
{
getMinMax8(&ptr8[index], s->fixedPos-index, &minVal, &maxVal);
index = s->fixedPos;
}
// read fixed samples (we are guaranteed to be within the fixed samples here)
const int32_t tapIndex = index-s->fixedPos;
const int32_t scanLength = SINC_RIGHT_TAPS-tapIndex;
int32_t tmpScanEnd = index+scanLength;
if (tmpScanEnd > scanEnd)
tmpScanEnd = scanEnd;
const int16_t *smpReadPtr = (const int16_t *)s->fixedSmp + tapIndex;
for (; index < tmpScanEnd; index++)
{
const int8_t smp8 = (int8_t)(*smpReadPtr++);
if (smp8 < minVal) minVal = smp8;
if (smp8 > maxVal) maxVal = smp8;
}
// read samples after fixed samples (if needed)
if (index < scanEnd)
{
getMinMax8(&ptr8[index], scanEnd-index, &minVal2, &maxVal2);
if (minVal2 < minVal) minVal = minVal2;
if (maxVal2 > maxVal) maxVal = maxVal2;
}
*min8 = minVal;
*max8 = maxVal;
}
static void getSampleDataPeak(sample_t *s, int32_t index, int32_t length, int16_t *outMin, int16_t *outMax)
{
int8_t min8, max8;
int16_t min16, max16;
if (length == 0 || s->dataPtr == NULL || s->length <= 0)
{
*outMin = SAMPLE_AREA_Y_CENTER;
*outMax = SAMPLE_AREA_Y_CENTER;
return;
}
if (s->isFixed && s->length > s->loopLength+s->loopStart)
{
const int32_t scanEnd = index + length;
/* If the scan area is including the fixed samples (for branchless mixer interpolation),
** do a special procedure to scan the original non-touched samples when needed.
*/
const bool insideRange = index >= s->fixedPos && index < s->fixedPos+SINC_RIGHT_TAPS;
if (insideRange || (index < s->fixedPos && scanEnd >= s->fixedPos))
{
if (s->flags & SAMPLE_16BIT)
{
getSpecialMinMax16(s, index, scanEnd, &min16, &max16);
*outMin = SAMPLE_AREA_Y_CENTER - ((min16 * SAMPLE_AREA_HEIGHT) >> 16);
*outMax = SAMPLE_AREA_Y_CENTER - ((max16 * SAMPLE_AREA_HEIGHT) >> 16);
}
else // 8-bit
{
getSpecialMinMax8(s, index, scanEnd, &min8, &max8);
*outMin = SAMPLE_AREA_Y_CENTER - ((min8 * SAMPLE_AREA_HEIGHT) >> 8);
*outMax = SAMPLE_AREA_Y_CENTER - ((max8 * SAMPLE_AREA_HEIGHT) >> 8);
}
return;
}
}
if (s->flags & SAMPLE_16BIT)
{
const int16_t *smpPtr16 = (int16_t *)s->dataPtr;
getMinMax16(&smpPtr16[index], length, &min16, &max16);
*outMin = SAMPLE_AREA_Y_CENTER - ((min16 * SAMPLE_AREA_HEIGHT) >> 16);
*outMax = SAMPLE_AREA_Y_CENTER - ((max16 * SAMPLE_AREA_HEIGHT) >> 16);
}
else // 8-bit
{
getMinMax8(&s->dataPtr[index], length, &min8, &max8);
*outMin = SAMPLE_AREA_Y_CENTER - ((min8 * SAMPLE_AREA_HEIGHT) >> 8);
*outMax = SAMPLE_AREA_Y_CENTER - ((max8 * SAMPLE_AREA_HEIGHT) >> 8);
}
}
static void writeWaveform(void)
{
// clear sample data area
memset(&video.frameBuffer[174 * SCREEN_W], 0, SAMPLE_AREA_WIDTH * SAMPLE_AREA_HEIGHT * sizeof (int32_t));
// draw center line
hLine(0, SAMPLE_AREA_Y_CENTER, SAMPLE_AREA_WIDTH, PAL_DESKTOP);
if (instr[editor.curInstr] == NULL || smpEd_ViewSize == 0)
return;
sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
if (s->dataPtr == NULL || s->length <= 0)
return;
if (smpEd_ViewSize <= SAMPLE_AREA_WIDTH) // zoomed in (or 1:1)
{
for (int32_t x = 0; x <= SAMPLE_AREA_WIDTH; x++)
{
int32_t currSmpPos = scr2SmpPos(x+0);
int32_t nextSmpPos = scr2SmpPos(x+1);
if (currSmpPos >= s->length) currSmpPos = s->length-1;
if (nextSmpPos >= s->length) nextSmpPos = s->length-1;
int32_t x1 = smpPos2Scr(currSmpPos);
int32_t x2 = smpPos2Scr(nextSmpPos);
int32_t y1 = getScaledSample(s, currSmpPos);
int32_t y2 = getScaledSample(s, nextSmpPos);
x1 = CLAMP(x1, 0, SAMPLE_AREA_WIDTH-1);
x2 = CLAMP(x2, 0, SAMPLE_AREA_WIDTH-1);
// kludge: sometimes the last point wouldn't reach the end of the sample window
if (x == SAMPLE_AREA_WIDTH)
x2 = SAMPLE_AREA_WIDTH-1;
sampleLine(x1, x2, y1, y2);
}
}
else // zoomed out
{
const int32_t firstSamplePoint = getScaledSample(s, scr2SmpPos(0));
int32_t oldMin = firstSamplePoint;
int32_t oldMax = firstSamplePoint;
for (int16_t x = 0; x < SAMPLE_AREA_WIDTH; x++)
{
int32_t smpIdx = scr2SmpPos(x+0);
int32_t smpNum = scr2SmpPos(x+1) - smpIdx;
// prevent look-up overflow (yes, this can happen near the end of the sample)
if (smpIdx+smpNum > s->length)
smpNum = s->length - smpIdx;
if (smpNum > 0)
{
int16_t min, max;
getSampleDataPeak(s, smpIdx, smpNum, &min, &max);
if (x != 0)
{
if (min > oldMax) sampleLine(x-1, x, oldMax, min);
if (max < oldMin) sampleLine(x-1, x, oldMin, max);
}
sampleLine(x, x, max, min);
oldMin = min;
oldMax = max;
}
}
}
}
void writeSample(bool forceSmpRedraw)
{
int32_t tmpRx1, tmpRx2;
sample_t *s;
// update sample loop points for visuals
if (instr[editor.curInstr] == NULL)
s = &instr[0]->smp[0];
else
s = &instr[editor.curInstr]->smp[editor.curSmp];
curSmpLoopStart = s->loopStart;
curSmpLoopLength = s->loopLength;
// exchange range variables if x1 is after x2
if (smpEd_Rx1 > smpEd_Rx2)
{
tmpRx2 = smpEd_Rx2;
smpEd_Rx2 = smpEd_Rx1;
smpEd_Rx1 = tmpRx2;
}
// clamp range
smpEd_Rx1 = CLAMP(smpEd_Rx1, 0, s->length);
smpEd_Rx2 = CLAMP(smpEd_Rx2, 0, s->length);
// sanitize sample scroll position
if (smpEd_ScrPos+smpEd_ViewSize > s->length)
{
smpEd_ScrPos = s->length - smpEd_ViewSize;
updateScrPos();
}
if (smpEd_ScrPos < 0)
{
smpEd_ScrPos = 0;
updateScrPos();
if (smpEd_ViewSize > s->length)
{
smpEd_ViewSize = s->length;
updateViewSize();
}
}
// handle updating
if (ui.sampleEditorShown)
{
// check if we need to redraw sample data
if (forceSmpRedraw || (old_SmpScrPos != smpEd_ScrPos || old_ViewSize != smpEd_ViewSize))
{
if (ui.sampleEditorShown)
writeWaveform();
old_SmpScrPos = smpEd_ScrPos;
old_ViewSize = smpEd_ViewSize;
if (ui.sampleEditorShown)
writeRange(); // range was overwritten, draw it again
smpEd_OldSmpPosLine = -1;
old_Rx1 = smpEd_Rx1;
old_Rx2 = smpEd_Rx2;
}
// check if we need to write new range
if (old_Rx1 != smpEd_Rx1 || old_Rx2 != smpEd_Rx2)
{
tmpRx1 = smpEd_Rx1;
tmpRx2 = smpEd_Rx2;
// remove old range
smpEd_Rx1 = old_Rx1;
smpEd_Rx2 = old_Rx2;
if (ui.sampleEditorShown)
writeRange();
// write new range
smpEd_Rx1 = tmpRx1;
smpEd_Rx2 = tmpRx2;
if (ui.sampleEditorShown)
writeRange();
old_Rx1 = smpEd_Rx1;
old_Rx2 = smpEd_Rx2;
}
fixLoopGadgets();
}
if (ui.sampleEditorShown)
fixSampleScrollbar();
updateSampleEditor();
}
static void setSampleRange(int32_t start, int32_t end)
{
if (instr[editor.curInstr] == NULL)
{
smpEd_Rx1 = 0;
smpEd_Rx2 = 0;
return;
}
if (start < 0)
start = 0;
if (end < 0)
end = 0;
smpEd_Rx1 = scr2SmpPos(start);
smpEd_Rx2 = scr2SmpPos(end);
}
void updateSampleEditorSample(void)
{
smpEd_Rx1 = 0;
smpEd_Rx2 = 0;
smpEd_ScrPos = 0;
updateScrPos();
if (instr[editor.curInstr] == NULL)
smpEd_ViewSize = 0;
else
smpEd_ViewSize = instr[editor.curInstr]->smp[editor.curSmp].length;
updateViewSize();
writeSample(true);
}
void updateSampleEditor(void)
{
char noteChar1, noteChar2;
uint8_t flags;
int32_t sampleLength;
if (!ui.sampleEditorShown)
return;
if (instr[editor.curInstr] == NULL)
{
flags = 0;
sampleLength = 0;
}
else
{
flags = instr[editor.curInstr]->smp[editor.curSmp].flags;
sampleLength = instr[editor.curInstr]->smp[editor.curSmp].length;
}
// sample bit depth radio buttons
uncheckRadioButtonGroup(RB_GROUP_SAMPLE_DEPTH);
if (flags & SAMPLE_16BIT)
radioButtons[RB_SAMPLE_16BIT].state = RADIOBUTTON_CHECKED;
else
radioButtons[RB_SAMPLE_8BIT].state = RADIOBUTTON_CHECKED;
showRadioButtonGroup(RB_GROUP_SAMPLE_DEPTH);
// sample loop radio buttons
uncheckRadioButtonGroup(RB_GROUP_SAMPLE_LOOP);
uint8_t loopType = GET_LOOPTYPE(flags);
if (loopType == LOOP_OFF)
radioButtons[RB_SAMPLE_NO_LOOP].state = RADIOBUTTON_CHECKED;
else if (loopType == LOOP_FWD)
radioButtons[RB_SAMPLE_FORWARD_LOOP].state = RADIOBUTTON_CHECKED;
else
radioButtons[RB_SAMPLE_PINGPONG_LOOP].state = RADIOBUTTON_CHECKED;
showRadioButtonGroup(RB_GROUP_SAMPLE_LOOP);
// draw sample play note
const uint8_t note = (editor.smpEd_NoteNr - 1) % 12;
if (config.ptnAcc == 0)
{
noteChar1 = sharpNote1Char[note];
noteChar2 = sharpNote2Char[note];
}
else
{
noteChar1 = flatNote1Char[note];
noteChar2 = flatNote2Char[note];
}
char octaChar = '0' + ((editor.smpEd_NoteNr - 1) / 12);
charOutBg(7, 369, PAL_FORGRND, PAL_BCKGRND, noteChar1);
charOutBg(15, 369, PAL_FORGRND, PAL_BCKGRND, noteChar2);
charOutBg(23, 369, PAL_FORGRND, PAL_BCKGRND, octaChar);
// draw sample display/length
hexOutBg(536, 350, PAL_FORGRND, PAL_DESKTOP, smpEd_ViewSize, 8);
hexOutBg(536, 362, PAL_FORGRND, PAL_DESKTOP, sampleLength, 8);
}
void sampPlayNoteUp(void)
{
if (editor.smpEd_NoteNr < 96)
{
editor.smpEd_NoteNr++;
updateSampleEditor();
}
}
void sampPlayNoteDown(void)
{
if (editor.smpEd_NoteNr > 1)
{
editor.smpEd_NoteNr--;
updateSampleEditor();
}
}
void scrollSampleDataLeft(void)
{
int32_t scrollAmount, sampleLen;
if (instr[editor.curInstr] == NULL)
sampleLen = 0;
else
sampleLen = instr[editor.curInstr]->smp[editor.curSmp].length;
if (smpEd_ViewSize == 0 || smpEd_ViewSize == sampleLen)
return;
if (mouse.rightButtonPressed)
scrollAmount = smpEd_ViewSize / SCALE_VBLANK_DELTA(16);
else
scrollAmount = smpEd_ViewSize / SCALE_VBLANK_DELTA(32);
if (scrollAmount < 1)
scrollAmount = 1;
smpEd_ScrPos -= scrollAmount;
if (smpEd_ScrPos < 0)
smpEd_ScrPos = 0;
updateScrPos();
}
void scrollSampleDataRight(void)
{
int32_t scrollAmount, sampleLen;
if (instr[editor.curInstr] == NULL)
sampleLen = 0;
else
sampleLen = instr[editor.curInstr]->smp[editor.curSmp].length;
if (smpEd_ViewSize == 0 || smpEd_ViewSize == sampleLen)
return;
if (mouse.rightButtonPressed)
scrollAmount = smpEd_ViewSize / SCALE_VBLANK_DELTA(16);
else
scrollAmount = smpEd_ViewSize / SCALE_VBLANK_DELTA(32);
if (scrollAmount < 1)
scrollAmount = 1;
smpEd_ScrPos += scrollAmount;
if (smpEd_ScrPos+smpEd_ViewSize > sampleLen)
smpEd_ScrPos = sampleLen - smpEd_ViewSize;
updateScrPos();
}
void scrollSampleData(uint32_t pos)
{
int32_t sampleLen;
if (instr[editor.curInstr] == NULL)
sampleLen = 0;
else
sampleLen = instr[editor.curInstr]->smp[editor.curSmp].length;
if (smpEd_ViewSize == 0 || smpEd_ViewSize == sampleLen)
return;
smpEd_ScrPos = pos;
updateScrPos();
}
void sampPlayWave(void)
{
playSample(cursor.ch, editor.curInstr, editor.curSmp, editor.smpEd_NoteNr, 0, 0);
}
void sampPlayDisplay(void)
{
playRange(cursor.ch, editor.curInstr, editor.curSmp, editor.smpEd_NoteNr, 0, 0, smpEd_ScrPos, smpEd_ViewSize);
}
void sampPlayRange(void)
{
playRange(cursor.ch, editor.curInstr, editor.curSmp, editor.smpEd_NoteNr, 0, 0, smpEd_Rx1, smpEd_Rx2 - smpEd_Rx1);
}
void showRange(void)
{
if (editor.curInstr == 0 || instr[editor.curInstr] == NULL)
return;
sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
if (s->dataPtr == NULL)
return;
if (smpEd_Rx1 < smpEd_Rx2)
{
smpEd_ViewSize = smpEd_Rx2 - smpEd_Rx1;
if (smpEd_ViewSize < 2)
smpEd_ViewSize = 2;
updateViewSize();
smpEd_ScrPos = smpEd_Rx1;
updateScrPos();
}
else
{
okBox(0, "System message", "Cannot show empty range!");
}
}
void rangeAll(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
smpEd_Rx1 = smpEd_ScrPos;
smpEd_Rx2 = smpEd_ScrPos + smpEd_ViewSize;
}
static void zoomSampleDataIn(int32_t step, int32_t x)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
if (old_ViewSize <= 2)
return;
if (step < 1)
step = 1;
smpEd_ViewSize = old_ViewSize - (step * 2);
if (smpEd_ViewSize < 2)
smpEd_ViewSize = 2;
updateViewSize();
int32_t tmp32 = (x - (SAMPLE_AREA_WIDTH / 2)) * step;
tmp32 += SAMPLE_AREA_WIDTH/4; // rounding bias
tmp32 /= SAMPLE_AREA_WIDTH/2;
step += tmp32;
int64_t newScrPos64 = old_SmpScrPos + step;
if (newScrPos64+smpEd_ViewSize > s->length)
newScrPos64 = s->length - smpEd_ViewSize;
smpEd_ScrPos = (uint32_t)newScrPos64;
updateScrPos();
}
static void zoomSampleDataOut(int32_t step, int32_t x)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
if (old_ViewSize == s->length)
return;
if (step < 1)
step = 1;
int64_t newViewSize64 = (int64_t)old_ViewSize + (step * 2);
if (newViewSize64 > s->length)
{
smpEd_ViewSize = s->length;
smpEd_ScrPos = 0;
}
else
{
int32_t tmp32 = (x - (SAMPLE_AREA_WIDTH / 2)) * step;
tmp32 += SAMPLE_AREA_WIDTH/4; // rounding bias
tmp32 /= SAMPLE_AREA_WIDTH/2;
step += tmp32;
smpEd_ViewSize = newViewSize64 & 0xFFFFFFFF;
smpEd_ScrPos = old_SmpScrPos - step;
if (smpEd_ScrPos < 0)
smpEd_ScrPos = 0;
if (smpEd_ScrPos+smpEd_ViewSize > s->length)
smpEd_ScrPos = s->length - smpEd_ViewSize;
}
updateViewSize();
updateScrPos();
}
void mouseZoomSampleDataIn(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
zoomSampleDataIn((old_ViewSize+5) / 10, mouse.x);
}
void mouseZoomSampleDataOut(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
zoomSampleDataOut((old_ViewSize+5) / 10, mouse.x);
}
void zoomOut(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
if (old_ViewSize == s->length)
return;
int32_t tmp32 = old_ViewSize;
tmp32++; // rounding bias
tmp32 >>= 1;
smpEd_ScrPos = old_SmpScrPos - tmp32;
if (smpEd_ScrPos < 0)
smpEd_ScrPos = 0;
smpEd_ViewSize = old_ViewSize * 2;
if (smpEd_ViewSize < old_ViewSize)
{
smpEd_ViewSize = s->length;
smpEd_ScrPos = 0;
}
else if (smpEd_ViewSize+smpEd_ScrPos > s->length)
{
smpEd_ViewSize = s->length - smpEd_ScrPos;
}
updateViewSize();
updateScrPos();
}
void showAll(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
smpEd_ScrPos = 0;
updateScrPos();
smpEd_ViewSize = instr[editor.curInstr]->smp[editor.curSmp].length;
updateViewSize();
}
void saveRange(void)
{
if (editor.curInstr == 0 ||
instr[editor.curInstr] == NULL ||
instr[editor.curInstr]->smp[editor.curSmp].dataPtr == NULL)
{
return;
}
if (smpEd_Rx1 == smpEd_Rx2)
{
okBox(0, "System message", "No range specified!");
return;
}
smpEd_SysReqText[0] = '\0';
if (inputBox(1, "Enter filename:", smpEd_SysReqText, sizeof (smpEd_SysReqText) - 1) != 1)
return;
if (smpEd_SysReqText[0] == '\0')
{
okBox(0, "System message", "Filename can't be empty!");
return;
}
if (smpEd_SysReqText[0] == '.')
{
okBox(0, "System message", "The very first character in the filename can't be '.' (dot)!");
return;
}
if (strpbrk(smpEd_SysReqText, "\\/:*?\"<>|") != NULL)
{
okBox(0, "System message", "The filename can't contain the following characters: \\ / : * ? \" < > |");
return;
}
switch (editor.sampleSaveMode)
{
case SMP_SAVE_MODE_RAW: changeFilenameExt(smpEd_SysReqText, ".raw", sizeof (smpEd_SysReqText) - 1); break;
case SMP_SAVE_MODE_IFF: changeFilenameExt(smpEd_SysReqText, ".iff", sizeof (smpEd_SysReqText) - 1); break;
default: case SMP_SAVE_MODE_WAV: changeFilenameExt(smpEd_SysReqText, ".wav", sizeof (smpEd_SysReqText) - 1); break;
}
UNICHAR *filenameU = cp437ToUnichar(smpEd_SysReqText);
if (filenameU == NULL)
{
okBox(0, "System message", "Out of memory!");
return;
}
if (fileExistsAnsi(smpEd_SysReqText))
{
char buf[256];
createFileOverwriteText(smpEd_SysReqText, buf);
if (okBox(2, "System request", buf) != 1)
return;
}
saveSample(filenameU, SAVE_RANGE);
free(filenameU);
}
static bool cutRange(bool cropMode, int32_t r1, int32_t r2)
{
sample_t *s = getCurSample();
if (s == NULL)
return false;
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
if (!cropMode)
{
if (editor.curInstr == 0 || s->dataPtr == NULL || s->length == 0)
return false;
pauseAudio();
unfixSample(s);
if (config.smpCutToBuffer)
{
if (!getCopyBuffer(r2-r1, sample16Bit))
{
fixSample(s);
resumeAudio();
okBoxThreadSafe(0, "System message", "Not enough memory!");
return false;
}
memcpy(smpCopyBuff, &s->dataPtr[r1], (r2-r1) << sample16Bit);
smpCopyBits = sample16Bit ? 16 : 8;
}
}
memmove(&s->dataPtr[r1 << sample16Bit], &s->dataPtr[r2 << sample16Bit], (s->length-r2) << sample16Bit);
int32_t length = s->length - r2+r1;
if (length > 0)
{
if (!reallocateSmpData(s, length, sample16Bit))
{
freeSample(editor.curInstr, editor.curSmp);
editor.updateCurSmp = true;
if (!cropMode)
resumeAudio();
okBoxThreadSafe(0, "System message", "Not enough memory!");
return false;
}
s->length = length;
int32_t loopEnd = s->loopStart + s->loopLength;
if (s->loopStart > r1)
{
s->loopStart -= r2-r1;
if (s->loopStart < r1)
s->loopStart = r1;
}
if (loopEnd > r1)
{
loopEnd -= r2-r1;
if (loopEnd < r1)
loopEnd = r1;
}
s->loopLength = loopEnd - s->loopStart;
if (s->loopLength < 0)
s->loopLength = 0;
if (s->loopStart+s->loopLength > length)
s->loopLength = length - s->loopStart;
if (s->loopLength <= 0)
{
s->loopStart = 0;
DISABLE_LOOP(s->flags);
}
if (!cropMode)
fixSample(s);
}
else
{
freeSample(editor.curInstr, editor.curSmp);
editor.updateCurSmp = true;
}
if (!cropMode)
{
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
smpEd_Rx2 = r1;
writeSampleFlag = true;
}
return true;
}
static int32_t SDLCALL sampCutThread(void *ptr)
{
if (!cutRange(false, smpEd_Rx1, smpEd_Rx2))
okBoxThreadSafe(0, "System message", "Not enough memory! (Disable \"cut to buffer\")");
else
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampCut(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0 || smpEd_Rx2 == 0 || smpEd_Rx2 < smpEd_Rx1)
return;
mouseAnimOn();
thread = SDL_CreateThread(sampCutThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static int32_t SDLCALL sampCopyThread(void *ptr)
{
sample_t *s = getCurSample();
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
if (!getCopyBuffer(smpEd_Rx2- smpEd_Rx1, sample16Bit))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return true;
}
unfixSample(s);
memcpy(smpCopyBuff, &s->dataPtr[smpEd_Rx1 << sample16Bit], (smpEd_Rx2-smpEd_Rx1) << sample16Bit);
fixSample(s);
setMouseBusy(false);
// copy sample information (in case we paste over an empty sample)
if (smpEd_Rx1 == 0 && smpEd_Rx2 == s->length)
{
smpCopySample = *s;
smpCopyDidCopyWholeSample = true;
}
smpCopyBits = sample16Bit? 16 : 8;
return true;
(void)ptr;
}
void sampCopy(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0 || smpEd_Rx2 == 0 || smpEd_Rx2 < smpEd_Rx1)
return;
mouseAnimOn();
thread = SDL_CreateThread(sampCopyThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static void pasteOverwrite(sample_t *s)
{
bool sample16Bit = (smpCopyBits == 16);
if (!reallocateSmpData(s, smpCopySize, sample16Bit))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return;
}
pauseAudio();
memcpy(s->dataPtr, smpCopyBuff, smpCopySize << sample16Bit);
if (smpCopyDidCopyWholeSample)
{
sample_t *src = &smpCopySample;
memcpy(s->name, src->name, 23);
s->length = src->length;
s->loopStart = src->loopStart;
s->loopLength = src->loopLength;
s->volume = src->volume;
s->panning = src->panning;
s->finetune = src->finetune;
s->relativeNote = src->relativeNote;
s->flags = src->flags;
}
else
{
s->name[0] = '\0';
s->length = smpCopySize;
s->loopStart = 0;
s->loopLength = 0;
s->volume = 64;
s->panning = 128;
s->finetune = 0;
s->relativeNote = 0;
s->flags = (smpCopyBits == 16) ? SAMPLE_16BIT : 0;
}
s->isFixed = false;
fixSample(s);
resumeAudio();
editor.updateCurSmp = true;
setSongModifiedFlag();
setMouseBusy(false);
}
static void pasteCopiedData(int8_t *dataPtr, int32_t offset, int32_t length, bool sample16Bit)
{
if (sample16Bit) // destination sample is 16-bits
{
if (smpCopyBits == 16)
{
// src/dst bits are equal, do direct copy
memcpy(&dataPtr[offset<<1], smpCopyBuff, length * sizeof (int16_t));
}
else
{
// convert copied data to 16-bit then paste
int16_t *ptr16 = (int16_t *)dataPtr + offset;
for (int32_t i = 0; i < length; i++)
ptr16[i] = smpCopyBuff[i] << 8;
}
}
else // destination sample is 8-bits
{
if (smpCopyBits == 8)
{
// src/dst bits are equal, do direct copy
memcpy(&dataPtr[offset], smpCopyBuff, length * sizeof (int8_t));
}
else
{
// convert copied data to 8-bit then paste
int8_t *ptr8 = (int8_t *)&dataPtr[offset];
int16_t *ptr16 = (int16_t *)smpCopyBuff;
for (int32_t i = 0; i < length; i++)
ptr8[i] = ptr16[i] >> 8;
}
}
}
static int32_t SDLCALL sampPasteThread(void *ptr)
{
smpPtr_t sp;
if (instr[editor.curInstr] == NULL && !allocateInstr(editor.curInstr))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return true;
}
sample_t *s = getCurSample();
if (smpEd_Rx2 == 0 || s == NULL || s->dataPtr == NULL)
{
pasteOverwrite(s);
return true;
}
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
if (s->length+smpCopySize > MAX_SAMPLE_LEN)
{
okBoxThreadSafe(0, "System message", "Not enough room in sample!");
return true;
}
int32_t newLength = s->length + smpCopySize - (smpEd_Rx2 - smpEd_Rx1);
if (newLength <= 0)
return true;
if (newLength > MAX_SAMPLE_LEN)
{
okBoxThreadSafe(0, "System message", "Not enough room in sample!");
return true;
}
if (!allocateSmpDataPtr(&sp, newLength, sample16Bit))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return true;
}
pauseAudio();
unfixSample(s);
// paste left part of original sample
if (smpEd_Rx1 > 0)
memcpy(sp.ptr, s->dataPtr, smpEd_Rx1 << sample16Bit);
// paste copied data
pasteCopiedData(sp.ptr, smpEd_Rx1, smpCopySize, sample16Bit);
// paste right part of original sample
if (smpEd_Rx2 < s->length)
memmove(&sp.ptr[(smpEd_Rx1+smpCopySize) << sample16Bit], &s->dataPtr[smpEd_Rx2 << sample16Bit], (s->length-smpEd_Rx2) << sample16Bit);
freeSmpData(s);
setSmpDataPtr(s, &sp);
// adjust loop points if necessary
if (smpEd_Rx2-smpEd_Rx1 != smpCopySize)
{
int32_t loopAdjust = smpCopySize - (smpEd_Rx1 - smpEd_Rx2);
if (s->loopStart > smpEd_Rx2)
{
s->loopStart += loopAdjust;
s->loopLength -= loopAdjust;
}
if (s->loopStart+s->loopLength > smpEd_Rx2)
s->loopLength += loopAdjust;
if (s->loopStart > newLength)
{
s->loopStart = 0;
s->loopLength = 0;
}
if (s->loopStart+s->loopLength > newLength)
s->loopLength = newLength - s->loopStart;
}
s->length = newLength;
fixSample(s);
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
// set new range
smpEd_Rx2 = smpEd_Rx1 + smpCopySize;
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampPaste(void)
{
if (editor.curInstr == 0 || smpEd_Rx2 < smpEd_Rx1 || smpCopyBuff == NULL || smpCopySize == 0)
return;
if (smpEd_Rx2 == 0) // no sample data marked, overwrite sample with copy buffer
{
sample_t *s = getCurSample();
if (s != NULL && s->dataPtr != NULL && s->length > 0)
{
if (okBox(2, "System request", "The current sample is not empty. Do you really want to overwrite it?") != 1)
return;
}
}
mouseAnimOn();
thread = SDL_CreateThread(sampPasteThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static int32_t SDLCALL sampCropThread(void *ptr)
{
sample_t *s = getCurSample();
int32_t r1 = smpEd_Rx1;
int32_t r2 = smpEd_Rx2;
pauseAudio();
unfixSample(s);
if (!cutRange(true, 0, r1) || !cutRange(true, r2-r1, s->length))
{
fixSample(s);
resumeAudio();
return true;
}
fixSample(s);
resumeAudio();
r1 = 0;
r2 = s->length;
setSongModifiedFlag();
setMouseBusy(false);
smpEd_Rx1 = r1;
smpEd_Rx2 = r2;
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampCrop(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0 || smpEd_Rx1 == smpEd_Rx2)
return;
if (smpEd_Rx1 == 0 && smpEd_Rx2 == s->length)
return; // nothing to crop (the whole sample is marked)
mouseAnimOn();
thread = SDL_CreateThread(sampCropThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
void sampXFade(void)
{
int32_t y1, y2, d1, d2, d3;
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
// check if the sample has the loop flag enabled
if (GET_LOOPTYPE(s->flags) == LOOP_OFF)
{
okBox(0, "System message", "X-Fade can only be used on a loop-enabled sample!");
return;
}
// check if we selected a range
if (smpEd_Rx2 == 0)
{
okBox(0, "System message", "No range selected! Make a small range that includes loop start or loop end.");
return;
}
// check if we selected a valid range length
if (smpEd_Rx2-smpEd_Rx1 <= 2)
{
okBox(0, "System message", "Invalid range!");
return;
}
int32_t x1 = smpEd_Rx1;
int32_t x2 = smpEd_Rx2;
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
if (GET_LOOPTYPE(s->flags) == LOOP_BIDI)
{
y1 = s->loopStart;
if (x1 <= y1) // first loop point
{
if (x2 <= y1 || x2 >= s->loopStart+s->loopLength)
{
okBox(0, "System message", "Error: No loop point found inside marked data.");
return;
}
d1 = y1 - x1;
if (x2-y1 > d1)
d1 = x2 - y1;
d2 = y1 - x1;
d3 = x2 - y1;
if (d1 < 1 || d2 < 1 || d3 < 1)
{
okBox(0, "System message", "Invalid range! Try to mark more data.");
return;
}
if (y1-d1 < 0 || y1+d1 >= s->length)
{
okBox(0, "System message", "Not enough sample data outside loop!");
return;
}
const double dD2Mul = 1.0 / d2;
const double dD3Mul = 1.0 / d3;
pauseAudio();
unfixSample(s);
for (int32_t i = 0; i < d1; i++)
{
const int32_t aIdx = y1-i-1;
const int32_t bIdx = y1+i;
const double dI = i;
const double dA = getSampleValue(s->dataPtr, aIdx, sample16Bit);
const double dB = getSampleValue(s->dataPtr, bIdx, sample16Bit);
if (i < d2)
{
const double dS1 = 1.0 - (dI * dD2Mul);
const double dS2 = 2.0 - dS1;
double dSample = (dA * dS2 + dB * dS1) / (dS1 + dS2);
putSampleValue(s->dataPtr, aIdx, dSample, sample16Bit);
}
if (i < d3)
{
const double dS1 = 1.0 - (dI * dD3Mul);
const double dS2 = 2.0 - dS1;
double dSample = (dB * dS2 + dA * dS1) / (dS1 + dS2);
putSampleValue(s->dataPtr, bIdx, dSample, sample16Bit);
}
}
fixSample(s);
resumeAudio();
}
else // last loop point
{
y1 += s->loopLength;
if (x1 >= y1 || x2 <= y1 || x2 >= s->length)
{
okBox(0, "System message", "Error: No loop point found inside marked data.");
return;
}
d1 = y1 - x1;
if (x2-y1 > d1)
d1 = x2 - y1;
d2 = y1 - x1;
d3 = x2 - y1;
if (d1 < 1 || d2 < 1 || d3 < 1)
{
okBox(0, "System message", "Invalid range! Try to mark more data.");
return;
}
if (y1-d1 < 0 || y1+d1 >= s->length)
{
okBox(0, "System message", "Not enough sample data outside loop!");
return;
}
const double dD2Mul = 1.0 / d2;
const double dD3Mul = 1.0 / d3;
pauseAudio();
unfixSample(s);
for (int32_t i = 0; i < d1; i++)
{
const int32_t aIdx = y1-i-1;
const int32_t bIdx = y1+i;
const double dI = i;
const double dA = getSampleValue(s->dataPtr, aIdx, sample16Bit);
const double dB = getSampleValue(s->dataPtr, bIdx, sample16Bit);
if (i < d2)
{
const double dS1 = 1.0 - (dI * dD2Mul);
const double dS2 = 2.0 - dS1;
double dSample = (dA * dS2 + dB * dS1) / (dS1 + dS2);
putSampleValue(s->dataPtr, aIdx, dSample, sample16Bit);
}
if (i < d3)
{
const double dS1 = 1.0 - (dI * dD3Mul);
const double dS2 = 2.0 - dS1;
double dSample = (dB * dS2 + dA * dS1) / (dS1 + dS2);
putSampleValue(s->dataPtr, bIdx, dSample, sample16Bit);
}
}
fixSample(s);
resumeAudio();
}
}
else // forward loop
{
if (x1 > s->loopStart)
{
x1 -= s->loopLength;
x2 -= s->loopLength;
}
if (x1 < 0 || x2 <= x1 || x2 >= s->length)
{
okBox(0, "System message", "Invalid range!");
return;
}
const int32_t length = x2 - x1;
int32_t x = (length + 1) >> 1;
y1 = s->loopStart - x;
y2 = s->loopStart+s->loopLength - x;
if (y1 < 0 || y2+length >= s->length)
{
okBox(0, "System message", "Not enough sample data outside loop!");
return;
}
d1 = length;
d2 = s->loopStart - y1;
d3 = length - d2;
if (y1+length <= s->loopStart || d1 == 0 || d3 == 0 || d1 > s->loopLength)
{
okBox(0, "System message", "Invalid range!");
return;
}
const double dR = (s->loopStart - x) / (double)length;
const double dD1 = d1;
const double dD1Mul = 1.0 / d1;
const double dD2Mul = 1.0 / d2;
const double dD3Mul = 1.0 / d3;
pauseAudio();
unfixSample(s);
for (int32_t i = 0; i < length; i++)
{
const int32_t aIdx = y1+i;
const int32_t bIdx = y2+i;
const double dI = i;
const double dA = getSampleValue(s->dataPtr, aIdx, sample16Bit);
const double dB = getSampleValue(s->dataPtr, bIdx, sample16Bit);
const double dS2 = dI * dD1Mul;
const double dS1 = 1.0 - dS2;
double dC, dD;
if (y1+i < s->loopStart)
{
const double dS3 = 1.0 - (1.0 - dR) * dI * dD2Mul;
const double dS4 = dR * dI * dD2Mul;
dC = (dA * dS3 + dB * dS4) / (dS3 + dS4);
dD = (dA * dS2 + dB * dS1) / (dS1 + dS2);
}
else
{
const double dS3 = 1.0 - (1.0 - dR) * (dD1 - dI) * dD3Mul;
const double dS4 = dR * (dD1 - dI) * dD3Mul;
dC = (dA * dS2 + dB * dS1) / (dS1 + dS2);
dD = (dA * dS4 + dB * dS3) / (dS3 + dS4);
}
putSampleValue(s->dataPtr, aIdx, dC, sample16Bit);
putSampleValue(s->dataPtr, bIdx, dD, sample16Bit);
}
fixSample(s);
resumeAudio();
}
writeSample(true);
setSongModifiedFlag();
}
void rbSampleNoLoop(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
lockMixerCallback();
unfixSample(s);
DISABLE_LOOP(s->flags);
fixSample(s);
unlockMixerCallback();
updateSampleEditor();
writeSample(true);
setSongModifiedFlag();
}
void rbSampleForwardLoop(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
lockMixerCallback();
unfixSample(s);
DISABLE_LOOP(s->flags);
s->flags |= LOOP_FWD;
if (s->loopStart+s->loopLength == 0)
{
s->loopStart = 0;
s->loopLength = s->length;
}
fixSample(s);
unlockMixerCallback();
updateSampleEditor();
writeSample(true);
setSongModifiedFlag();
}
void rbSamplePingpongLoop(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
lockMixerCallback();
unfixSample(s);
DISABLE_LOOP(s->flags);
s->flags |= LOOP_BIDI;
if (s->loopStart+s->loopLength == 0)
{
s->loopStart = 0;
s->loopLength = s->length;
}
fixSample(s);
unlockMixerCallback();
updateSampleEditor();
writeSample(true);
setSongModifiedFlag();
}
static int32_t SDLCALL convSmp8Bit(void *ptr)
{
sample_t *s = getCurSample();
assert(s->dataPtr != NULL);
pauseAudio();
unfixSample(s);
const int16_t *src16 = (const int16_t *)s->dataPtr;
for (int32_t i = 0; i < s->length; i++)
s->dataPtr[i] = src16[i] >> 8;
reallocateSmpData(s, s->length, false);
s->flags &= ~SAMPLE_16BIT; // remove 16-bit flag
fixSample(s);
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
editor.updateCurSmp = true;
return true;
(void)ptr;
}
void rbSample8bit(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
if (okBox(2, "System request", "Convert sampledata?") == 1)
{
mouseAnimOn();
thread = SDL_CreateThread(convSmp8Bit, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
return;
}
else
{
lockMixerCallback();
unfixSample(s);
s->flags &= ~SAMPLE_16BIT; // remove 16-bit flag
s->length <<= 1;
// no need to call reallocateSmpData, number of bytes allocated is the same
fixSample(s);
unlockMixerCallback();
updateSampleEditorSample();
updateSampleEditor();
setSongModifiedFlag();
}
}
static int32_t SDLCALL convSmp16Bit(void *ptr)
{
sample_t *s = getCurSample();
pauseAudio();
unfixSample(s);
if (!reallocateSmpData(s, s->length, true))
{
okBoxThreadSafe(0, "System message", "Not enough memory!");
return true;
}
int16_t *dst16 = (int16_t *)s->dataPtr;
for (int32_t i = s->length-1; i >= 0; i--)
dst16[i] = s->dataPtr[i] << 8;
s->flags |= SAMPLE_16BIT;
fixSample(s);
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
editor.updateCurSmp = true;
return true;
(void)ptr;
}
void rbSample16bit(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
if (okBox(2, "System request", "Convert sampledata?") == 1)
{
mouseAnimOn();
thread = SDL_CreateThread(convSmp16Bit, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
return;
}
else
{
lockMixerCallback();
unfixSample(s);
s->flags |= SAMPLE_16BIT;
s->length >>= 1;
// no need to call reallocateSmpData, number of bytes allocated is the same
fixSample(s);
unlockMixerCallback();
updateSampleEditorSample();
updateSampleEditor();
setSongModifiedFlag();
}
}
void clearSample(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
if (okBox(1, "System request", "Clear sample?") != 1)
return;
freeSample(editor.curInstr, editor.curSmp);
updateNewSample();
setSongModifiedFlag();
}
void sampMinimize(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
const bool hasLoop = GET_LOOPTYPE(s->flags) != LOOP_OFF;
if (!hasLoop)
{
okBox(0, "System message", "Only a looped sample can be minimized!");
return;
}
if (s->loopStart+s->loopLength >= s->length)
{
okBox(0, "System message", "The sample can't be minimized any further.");
return;
}
if (okBox(1, "System request", "Minimize sample?") != 1)
return;
lockMixerCallback();
s->length = s->loopStart + s->loopLength;
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
reallocateSmpData(s, s->length, sample16Bit);
// note: we don't need to make a call to fixSample()
unlockMixerCallback();
updateSampleEditorSample();
updateSampleEditor();
setSongModifiedFlag();
}
void sampRepeatUp(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t loopStart = curSmpLoopStart;
int32_t loopLength = curSmpLoopLength;
if (loopStart < s->length-2)
loopStart++;
if (loopStart+loopLength > s->length)
loopLength = s->length - loopStart;
curSmpLoopStart = loopStart;
curSmpLoopLength = loopLength;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
void sampRepeatDown(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t loopStart = curSmpLoopStart - 1;
if (loopStart < 0)
loopStart = 0;
curSmpLoopStart = loopStart;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
void sampReplenUp(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t loopLength = curSmpLoopLength + 1;
if (curSmpLoopStart+loopLength > s->length)
loopLength = s->length - curSmpLoopStart;
curSmpLoopLength = loopLength;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
void sampReplenDown(void)
{
int32_t loopLength;
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
loopLength = curSmpLoopLength - 1;
if (loopLength < 0)
loopLength = 0;
curSmpLoopLength = loopLength;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
void hideSampleEditor(void)
{
hidePushButton(PB_SAMP_SCROLL_LEFT);
hidePushButton(PB_SAMP_SCROLL_RIGHT);
hidePushButton(PB_SAMP_PNOTE_UP);
hidePushButton(PB_SAMP_PNOTE_DOWN);
hidePushButton(PB_SAMP_STOP);
hidePushButton(PB_SAMP_PWAVE);
hidePushButton(PB_SAMP_PRANGE);
hidePushButton(PB_SAMP_PDISPLAY);
hidePushButton(PB_SAMP_SHOW_RANGE);
hidePushButton(PB_SAMP_RANGE_ALL);
hidePushButton(PB_SAMP_CLR_RANGE);
hidePushButton(PB_SAMP_ZOOM_OUT);
hidePushButton(PB_SAMP_SHOW_ALL);
hidePushButton(PB_SAMP_SAVE_RNG);
hidePushButton(PB_SAMP_CUT);
hidePushButton(PB_SAMP_COPY);
hidePushButton(PB_SAMP_PASTE);
hidePushButton(PB_SAMP_CROP);
hidePushButton(PB_SAMP_VOLUME);
hidePushButton(PB_SAMP_XFADE);
hidePushButton(PB_SAMP_EXIT);
hidePushButton(PB_SAMP_CLEAR);
hidePushButton(PB_SAMP_MIN);
hidePushButton(PB_SAMP_REPEAT_UP);
hidePushButton(PB_SAMP_REPEAT_DOWN);
hidePushButton(PB_SAMP_REPLEN_UP);
hidePushButton(PB_SAMP_REPLEN_DOWN);
hideRadioButtonGroup(RB_GROUP_SAMPLE_LOOP);
hideRadioButtonGroup(RB_GROUP_SAMPLE_DEPTH);
hideScrollBar(SB_SAMP_SCROLL);
ui.sampleEditorShown = false;
hideSprite(SPRITE_LEFT_LOOP_PIN);
hideSprite(SPRITE_RIGHT_LOOP_PIN);
}
void exitSampleEditor(void)
{
hideSampleEditor();
if (ui.sampleEditorExtShown)
hideSampleEditorExt();
showPatternEditor();
}
void showSampleEditor(void)
{
if (ui.extended)
exitPatternEditorExtended();
hideInstEditor();
hidePatternEditor();
ui.sampleEditorShown = true;
drawFramework(0, 329, 632, 17, FRAMEWORK_TYPE1);
drawFramework(0, 346, 115, 54, FRAMEWORK_TYPE1);
drawFramework(115, 346, 133, 54, FRAMEWORK_TYPE1);
drawFramework(248, 346, 49, 54, FRAMEWORK_TYPE1);
drawFramework(297, 346, 56, 54, FRAMEWORK_TYPE1);
drawFramework(353, 346, 74, 54, FRAMEWORK_TYPE1);
drawFramework(427, 346, 205, 54, FRAMEWORK_TYPE1);
drawFramework(2, 366, 34, 15, FRAMEWORK_TYPE2);
textOutShadow(5, 352, PAL_FORGRND, PAL_DSKTOP2, "Play:");
textOutShadow(371, 352, PAL_FORGRND, PAL_DSKTOP2, "No loop");
textOutShadow(371, 369, PAL_FORGRND, PAL_DSKTOP2, "Forward");
textOutShadow(371, 386, PAL_FORGRND, PAL_DSKTOP2, "Pingpong");
textOutShadow(446, 369, PAL_FORGRND, PAL_DSKTOP2, "8-bit");
textOutShadow(445, 384, PAL_FORGRND, PAL_DSKTOP2, "16-bit");
textOutShadow(488, 350, PAL_FORGRND, PAL_DSKTOP2, "Display");
textOutShadow(488, 362, PAL_FORGRND, PAL_DSKTOP2, "Length");
textOutShadow(488, 375, PAL_FORGRND, PAL_DSKTOP2, "Repeat");
textOutShadow(488, 387, PAL_FORGRND, PAL_DSKTOP2, "Replen.");
showPushButton(PB_SAMP_SCROLL_LEFT);
showPushButton(PB_SAMP_SCROLL_RIGHT);
showPushButton(PB_SAMP_PNOTE_UP);
showPushButton(PB_SAMP_PNOTE_DOWN);
showPushButton(PB_SAMP_STOP);
showPushButton(PB_SAMP_PWAVE);
showPushButton(PB_SAMP_PRANGE);
showPushButton(PB_SAMP_PDISPLAY);
showPushButton(PB_SAMP_SHOW_RANGE);
showPushButton(PB_SAMP_RANGE_ALL);
showPushButton(PB_SAMP_CLR_RANGE);
showPushButton(PB_SAMP_ZOOM_OUT);
showPushButton(PB_SAMP_SHOW_ALL);
showPushButton(PB_SAMP_SAVE_RNG);
showPushButton(PB_SAMP_CUT);
showPushButton(PB_SAMP_COPY);
showPushButton(PB_SAMP_PASTE);
showPushButton(PB_SAMP_CROP);
showPushButton(PB_SAMP_VOLUME);
showPushButton(PB_SAMP_XFADE);
showPushButton(PB_SAMP_EXIT);
showPushButton(PB_SAMP_CLEAR);
showPushButton(PB_SAMP_MIN);
showPushButton(PB_SAMP_REPEAT_UP);
showPushButton(PB_SAMP_REPEAT_DOWN);
showPushButton(PB_SAMP_REPLEN_UP);
showPushButton(PB_SAMP_REPLEN_DOWN);
showRadioButtonGroup(RB_GROUP_SAMPLE_LOOP);
showRadioButtonGroup(RB_GROUP_SAMPLE_DEPTH);
showScrollBar(SB_SAMP_SCROLL);
// clear two lines in the sample data view that are never written to when the sampler is open
hLine(0, 173, SAMPLE_AREA_WIDTH, PAL_BCKGRND);
hLine(0, 328, SAMPLE_AREA_WIDTH, PAL_BCKGRND);
updateSampleEditor();
writeSample(true);
}
void toggleSampleEditor(void)
{
hideInstEditor();
if (ui.sampleEditorShown)
{
exitSampleEditor();
}
else
{
hidePatternEditor();
showSampleEditor();
}
}
static void invertSamplePosLine(int32_t x)
{
if (x < 0 || x >= SCREEN_W)
return;
uint32_t *ptr32 = &video.frameBuffer[(174 * SCREEN_W) + x];
for (int32_t y = 0; y < SAMPLE_AREA_HEIGHT; y++, ptr32 += SCREEN_W)
*ptr32 = video.palette[(*ptr32 >> 24) ^ 1]; // ">> 24" to get palette, XOR 1 to switch between normal/inverted mode
}
static void writeSamplePosLine(void)
{
uint8_t ins, smp;
assert(editor.curSmpChannel < MAX_CHANNELS);
lastChInstr_t *c = &lastChInstr[editor.curSmpChannel];
if (c->instrNum == 130) // "Play Wave/Range/Display" in Smp. Ed.
{
ins = editor.curPlayInstr;
smp = editor.curPlaySmp;
}
else
{
ins = c->instrNum;
smp = c->smpNum;
}
if (editor.curInstr == ins && editor.curSmp == smp)
{
const int32_t smpPos = getSamplePosition(editor.curSmpChannel);
if (smpPos != -1)
{
// convert sample position to screen position
const int32_t scrPos = smpPos2Scr(smpPos);
if (scrPos != -1)
{
if (scrPos != smpEd_OldSmpPosLine)
{
invertSamplePosLine(smpEd_OldSmpPosLine); // remove old line
invertSamplePosLine(scrPos); // write new line
}
smpEd_OldSmpPosLine = scrPos;
return;
}
}
}
if (smpEd_OldSmpPosLine != -1)
invertSamplePosLine(smpEd_OldSmpPosLine);
smpEd_OldSmpPosLine = -1;
}
void handleSamplerRedrawing(void)
{
// update sample editor
if (!ui.sampleEditorShown || editor.samplingAudioFlag)
return;
if (writeSampleFlag)
{
writeSampleFlag = false;
writeSample(true);
}
else if (smpEd_Rx1 != old_Rx1 || smpEd_Rx2 != old_Rx2 || smpEd_ScrPos != old_SmpScrPos || smpEd_ViewSize != old_ViewSize)
{
writeSample(false);
}
writeSamplePosLine();
}
static void setLeftLoopPinPos(int32_t x)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t newPos = scr2SmpPos(x) - curSmpLoopStart;
int32_t loopStart = curSmpLoopStart + newPos;
int32_t loopLength = curSmpLoopLength - newPos;
if (loopStart < 0)
{
loopLength += loopStart;
loopStart = 0;
}
if (loopLength < 0)
{
loopLength = 0;
loopStart = curSmpLoopStart + curSmpLoopLength;
}
curSmpLoopStart = loopStart;
curSmpLoopLength = loopLength;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
static void setRightLoopPinPos(int32_t x)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t loopLength = scr2SmpPos(x) - curSmpLoopStart;
if (loopLength < 0)
loopLength = 0;
if (loopLength+curSmpLoopStart > s->length)
loopLength = s->length - curSmpLoopStart;
if (loopLength < 0)
loopLength = 0;
curSmpLoopLength = loopLength;
fixLoopGadgets();
updateLoopsOnMouseUp = true;
}
static int32_t mouseYToSampleY(int32_t my)
{
my -= 174; // 0..SAMPLE_AREA_HEIGHT-1
const double dTmp = my * (256.0 / SAMPLE_AREA_HEIGHT);
const int32_t tmp32 = (const int32_t)(dTmp + 0.5); // rounded
return 255 - CLAMP(tmp32, 0, 255);
}
static void editSampleData(bool mouseButtonHeld)
{
int8_t *ptr8;
int16_t *ptr16;
int32_t tmp32, p, vl, tvl, r, rl, rvl, start, end;
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
int32_t mx = mouse.x;
if (mx > SCREEN_W)
mx = SCREEN_W;
int32_t my = mouse.y;
if (!mouseButtonHeld)
{
pauseAudio();
unfixSample(s);
editor.editSampleFlag = true;
lastDrawX = scr2SmpPos(mx);
lastDrawY = mouseYToSampleY(my);
lastMouseX = mx;
lastMouseY = my;
}
else if (mx == lastMouseX && my == lastMouseY)
{
return; // don't continue if we didn't move the mouse
}
if (mx != lastMouseX)
p = scr2SmpPos(mx);
else
p = lastDrawX;
if (!keyb.leftShiftPressed && my != lastMouseY)
vl = mouseYToSampleY(my);
else
vl = lastDrawY;
lastMouseX = mx;
lastMouseY = my;
r = p;
rvl = vl;
// swap x/y if needed
if (p > lastDrawX)
{
// swap x
tmp32 = p;
p = lastDrawX;
lastDrawX = tmp32;
// swap y
tmp32 = lastDrawY;
lastDrawY = vl;
vl = tmp32;
}
if (s->flags & SAMPLE_16BIT)
{
ptr16 = (int16_t *)s->dataPtr;
start = p;
if (start < 0)
start = 0;
end = lastDrawX+1;
if (end > s->length)
end = s->length;
if (p == lastDrawX)
{
const int16_t smpVal = (int16_t)((vl << 8) ^ 0x8000);
for (rl = start; rl < end; rl++)
ptr16[rl] = smpVal;
}
else
{
int32_t y = lastDrawY - vl;
int32_t x = lastDrawX - p;
if (x != 0)
{
double dMul = 1.0 / x;
int32_t i = 0;
for (rl = start; rl < end; rl++)
{
tvl = y * i;
tvl = (int32_t)(tvl * dMul); // tvl /= x
tvl += vl;
tvl <<= 8;
tvl ^= 0x8000;
ptr16[rl] = (int16_t)tvl;
i++;
}
}
}
}
else // 8-bit
{
ptr8 = s->dataPtr;
start = p;
if (start < 0)
start = 0;
end = lastDrawX+1;
if (end > s->length)
end = s->length;
if (p == lastDrawX)
{
const int8_t smpVal = (int8_t)(vl ^ 0x80);
for (rl = start; rl < end; rl++)
ptr8[rl] = smpVal;
}
else
{
int32_t y = lastDrawY - vl;
int32_t x = lastDrawX - p;
if (x != 0)
{
double dMul = 1.0 / x;
int32_t i = 0;
for (rl = start; rl < end; rl++)
{
tvl = y * i;
tvl = (int32_t)(tvl * dMul); // tvl /= x
tvl += vl;
tvl ^= 0x80;
ptr8[rl] = (int8_t)tvl;
i++;
}
}
}
}
lastDrawY = rvl;
lastDrawX = r;
writeSample(true);
}
void handleSampleDataMouseDown(bool mouseButtonHeld)
{
if (editor.curInstr == 0)
return;
const int32_t mx = CLAMP(mouse.x, 0, SCREEN_W+8); // allow some pixels outside of the screen
const int32_t my = CLAMP(mouse.y, 0, SCREEN_H-1);
if (!mouseButtonHeld)
{
ui.rightLoopPinMoving = false;
ui.leftLoopPinMoving = false;
ui.sampleDataOrLoopDrag = -1;
mouseXOffs = 0;
lastMouseX = mx;
lastMouseY = my;
mouse.lastUsedObjectType = OBJECT_SMPDATA;
if (mouse.leftButtonPressed)
{
// move loop pins
if (my < 183)
{
const int32_t leftLoopPinPos = getSpritePosX(SPRITE_LEFT_LOOP_PIN);
if (mx >= leftLoopPinPos && mx <= leftLoopPinPos+16)
{
mouseXOffs = (leftLoopPinPos + 8) - mx;
ui.sampleDataOrLoopDrag = true;
setLeftLoopPinState(true);
lastMouseX = mx;
ui.leftLoopPinMoving = true;
return;
}
}
else if (my > 318)
{
const int32_t rightLoopPinPos = getSpritePosX(SPRITE_RIGHT_LOOP_PIN);
if (mx >= rightLoopPinPos && mx <= rightLoopPinPos+16)
{
mouseXOffs = (rightLoopPinPos + 8) - mx;
ui.sampleDataOrLoopDrag = true;
setRightLoopPinState(true);
lastMouseX = mx;
ui.rightLoopPinMoving = true;
return;
}
}
// mark data
lastMouseX = mx;
ui.sampleDataOrLoopDrag = mx;
setSampleRange(mx, mx);
}
else if (mouse.rightButtonPressed)
{
// edit data
ui.sampleDataOrLoopDrag = true;
editSampleData(false);
}
return;
}
if (mouse.rightButtonPressed)
{
editSampleData(true);
return;
}
if (mx != lastMouseX)
{
if (mouse.leftButtonPressed)
{
if (ui.leftLoopPinMoving)
{
lastMouseX = mx;
setLeftLoopPinPos(mouseXOffs + mx);
}
else if (ui.rightLoopPinMoving)
{
lastMouseX = mx;
setRightLoopPinPos(mouseXOffs + mx);
}
else if (ui.sampleDataOrLoopDrag >= 0)
{
// mark data
lastMouseX = mx;
if (lastMouseX > ui.sampleDataOrLoopDrag)
setSampleRange(ui.sampleDataOrLoopDrag, mx);
else if (lastMouseX == ui.sampleDataOrLoopDrag)
setSampleRange(ui.sampleDataOrLoopDrag, ui.sampleDataOrLoopDrag);
else if (lastMouseX < ui.sampleDataOrLoopDrag)
setSampleRange(mx, ui.sampleDataOrLoopDrag);
}
}
}
}
// SAMPLE EDITOR EXTENSION
void handleSampleEditorExtRedrawing(void)
{
hexOutBg(35, 96, PAL_FORGRND, PAL_DESKTOP, smpEd_Rx1, 8);
hexOutBg(99, 96, PAL_FORGRND, PAL_DESKTOP, smpEd_Rx2, 8);
hexOutBg(99, 110, PAL_FORGRND, PAL_DESKTOP, smpEd_Rx2 - smpEd_Rx1, 8);
hexOutBg(99, 124, PAL_FORGRND, PAL_DESKTOP, smpCopySize, 8);
hexOutBg(226, 96, PAL_FORGRND, PAL_DESKTOP, editor.srcInstr, 2);
hexOutBg(274, 96, PAL_FORGRND, PAL_DESKTOP, editor.srcSmp, 2);
hexOutBg(226, 109, PAL_FORGRND, PAL_DESKTOP, editor.curInstr, 2);
hexOutBg(274, 109, PAL_FORGRND, PAL_DESKTOP, editor.curSmp, 2);
}
void drawSampleEditorExt(void)
{
drawFramework(0, 92, 158, 44, FRAMEWORK_TYPE1);
drawFramework(0, 136, 158, 37, FRAMEWORK_TYPE1);
drawFramework(158, 92, 133, 81, FRAMEWORK_TYPE1);
textOutShadow( 4, 96, PAL_FORGRND, PAL_DSKTOP2, "Rng.:");
charOutShadow(91, 95, PAL_FORGRND, PAL_DSKTOP2, '-');
textOutShadow( 4, 110, PAL_FORGRND, PAL_DSKTOP2, "Range size");
textOutShadow( 4, 124, PAL_FORGRND, PAL_DSKTOP2, "Copy buf. size");
textOutShadow(162, 96, PAL_FORGRND, PAL_DSKTOP2, "Src.instr.");
textOutShadow(245, 96, PAL_FORGRND, PAL_DSKTOP2, "smp.");
textOutShadow(162, 109, PAL_FORGRND, PAL_DSKTOP2, "Dest.instr.");
textOutShadow(245, 109, PAL_FORGRND, PAL_DSKTOP2, "smp.");
showPushButton(PB_SAMP_EXT_CLEAR_COPYBUF);
showPushButton(PB_SAMP_EXT_CONV);
showPushButton(PB_SAMP_EXT_ECHO);
showPushButton(PB_SAMP_EXT_BACKWARDS);
showPushButton(PB_SAMP_EXT_CONV_W);
showPushButton(PB_SAMP_EXT_MORPH);
showPushButton(PB_SAMP_EXT_COPY_INS);
showPushButton(PB_SAMP_EXT_COPY_SMP);
showPushButton(PB_SAMP_EXT_XCHG_INS);
showPushButton(PB_SAMP_EXT_XCHG_SMP);
showPushButton(PB_SAMP_EXT_RESAMPLE);
showPushButton(PB_SAMP_EXT_MIX_SAMPLE);
}
void showSampleEditorExt(void)
{
hideTopScreen();
showTopScreen(false);
if (ui.extended)
exitPatternEditorExtended();
if (!ui.sampleEditorShown)
showSampleEditor();
ui.sampleEditorExtShown = true;
ui.scopesShown = false;
drawSampleEditorExt();
}
void hideSampleEditorExt(void)
{
ui.sampleEditorExtShown = false;
hidePushButton(PB_SAMP_EXT_CLEAR_COPYBUF);
hidePushButton(PB_SAMP_EXT_CONV);
hidePushButton(PB_SAMP_EXT_ECHO);
hidePushButton(PB_SAMP_EXT_BACKWARDS);
hidePushButton(PB_SAMP_EXT_CONV_W);
hidePushButton(PB_SAMP_EXT_MORPH);
hidePushButton(PB_SAMP_EXT_COPY_INS);
hidePushButton(PB_SAMP_EXT_COPY_SMP);
hidePushButton(PB_SAMP_EXT_XCHG_INS);
hidePushButton(PB_SAMP_EXT_XCHG_SMP);
hidePushButton(PB_SAMP_EXT_RESAMPLE);
hidePushButton(PB_SAMP_EXT_MIX_SAMPLE);
ui.scopesShown = true;
drawScopeFramework();
}
void toggleSampleEditorExt(void)
{
if (ui.sampleEditorExtShown)
hideSampleEditorExt();
else
showSampleEditorExt();
}
static int32_t SDLCALL sampleBackwardsThread(void *ptr)
{
int8_t tmp8, *ptrStart, *ptrEnd;
int16_t tmp16, *ptrStart16, *ptrEnd16;
const bool sampleDataMarked = (smpEd_Rx1 != smpEd_Rx2);
sample_t *s = getCurSample();
const bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
if (sample16Bit)
{
if (!sampleDataMarked)
{
ptrStart16 = (int16_t *)s->dataPtr;
ptrEnd16 = (int16_t *)s->dataPtr + (s->length-1);
}
else
{
ptrStart16 = (int16_t *)s->dataPtr + smpEd_Rx1;
ptrEnd16 = (int16_t *)s->dataPtr + (smpEd_Rx2-1);
}
pauseAudio();
unfixSample(s);
while (ptrStart16 < ptrEnd16)
{
tmp16 = *ptrStart16;
*ptrStart16++ = *ptrEnd16;
*ptrEnd16-- = tmp16;
}
fixSample(s);
resumeAudio();
}
else
{
if (!sampleDataMarked)
{
ptrStart = s->dataPtr;
ptrEnd = &s->dataPtr[s->length-1];
}
else
{
ptrStart = &s->dataPtr[smpEd_Rx1];
ptrEnd = &s->dataPtr[smpEd_Rx2-1];
}
pauseAudio();
unfixSample(s);
while (ptrStart < ptrEnd)
{
tmp8 = *ptrStart;
*ptrStart++ = *ptrEnd;
*ptrEnd-- = tmp8;
}
fixSample(s);
resumeAudio();
}
setSongModifiedFlag();
setMouseBusy(false);
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampleBackwards(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length < 2)
return;
mouseAnimOn();
thread = SDL_CreateThread(sampleBackwardsThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static int32_t SDLCALL sampleChangeSignThread(void *ptr)
{
sample_t *s = getCurSample();
pauseAudio();
unfixSample(s);
if (s->flags & SAMPLE_16BIT)
{
int16_t *ptr16 = (int16_t *)s->dataPtr;
for (int32_t i = 0; i < s->length; i++)
ptr16[i] ^= 0x8000;
}
else
{
int8_t *ptr8 = s->dataPtr;
for (int32_t i = 0; i < s->length; i++)
ptr8[i] ^= 0x80;
}
fixSample(s);
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampleChangeSign(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
mouseAnimOn();
thread = SDL_CreateThread(sampleChangeSignThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static int32_t SDLCALL sampleByteSwapThread(void *ptr)
{
sample_t *s = getCurSample();
pauseAudio();
unfixSample(s);
int32_t length = s->length;
if (!(s->flags & SAMPLE_16BIT))
length >>= 1;
int8_t *ptr8 = s->dataPtr;
for (int32_t i = 0; i < length; i++, ptr8 += 2)
{
const int8_t tmp = ptr8[0];
ptr8[0] = ptr8[1];
ptr8[1] = tmp;
}
fixSample(s);
resumeAudio();
setSongModifiedFlag();
setMouseBusy(false);
writeSampleFlag = true;
return true;
(void)ptr;
}
void sampleByteSwap(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
if (!(s->flags & SAMPLE_16BIT))
{
if (okBox(2, "System request", "Byte swapping only makes sense on a 16-bit sample. Continue?") != 1)
return;
}
mouseAnimOn();
thread = SDL_CreateThread(sampleByteSwapThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
static int32_t SDLCALL fixDCThread(void *ptr)
{
int8_t *ptr8;
int16_t *ptr16;
int32_t length;
const bool sampleDataMarked = (smpEd_Rx1 != smpEd_Rx2);
sample_t *s = getCurSample();
if (s->flags & SAMPLE_16BIT)
{
if (!sampleDataMarked)
{
ptr16 = (int16_t *)s->dataPtr;
length = s->length;
}
else
{
ptr16 = (int16_t *)&s->dataPtr + smpEd_Rx1;
length = smpEd_Rx2 - smpEd_Rx1;
}
if (length < 0 || length > s->length)
{
setMouseBusy(false);
return true;
}
pauseAudio();
unfixSample(s);
int64_t averageDC = 0;
for (int32_t i = 0; i < length; i++)
averageDC += ptr16[i];
averageDC = (averageDC + (length>>1)) / length; // rounded
const int32_t smpSub = (int32_t)averageDC;
for (int32_t i = 0; i < length; i++)
{
int32_t smp32 = ptr16[i] - smpSub;
CLAMP16(smp32);
ptr16[i] = (int16_t)smp32;
}
fixSample(s);
resumeAudio();
}
else // 8-bit
{
if (!sampleDataMarked)
{
ptr8 = s->dataPtr;
length = s->length;
}
else
{
ptr8 = &s->dataPtr[smpEd_Rx1];
length = smpEd_Rx2 - smpEd_Rx1;
}
if (length < 0 || length > s->length)
{
setMouseBusy(false);
return true;
}
pauseAudio();
unfixSample(s);
int64_t averageDC = 0;
for (int32_t i = 0; i < length; i++)
averageDC += ptr8[i];
averageDC = (averageDC + (length>>1)) / length; // rounded
const int32_t smpSub = (int32_t)averageDC;
for (int32_t i = 0; i < length; i++)
{
int32_t smp32 = ptr8[i] - smpSub;
CLAMP8(smp32);
ptr8[i] = (int8_t)smp32;
}
fixSample(s);
resumeAudio();
}
setSongModifiedFlag();
setMouseBusy(false);
writeSampleFlag = true;
return true;
(void)ptr;
}
void fixDC(void)
{
sample_t *s = getCurSample();
if (s == NULL || s->dataPtr == NULL || s->length <= 0)
return;
mouseAnimOn();
thread = SDL_CreateThread(fixDCThread, NULL, NULL);
if (thread == NULL)
{
okBox(0, "System message", "Couldn't create thread!");
return;
}
SDL_DetachThread(thread);
}
void smpEdStop(void)
{
// safely kills all voices
lockMixerCallback();
unlockMixerCallback();
}
void testSmpEdMouseUp(void) // used for setting new loop points
{
if (updateLoopsOnMouseUp)
{
updateLoopsOnMouseUp = false;
sample_t *s = getCurSample();
if (s == NULL)
return;
if (s->loopStart != curSmpLoopStart || s->loopLength != curSmpLoopLength)
{
lockMixerCallback();
unfixSample(s);
s->loopStart = curSmpLoopStart;
s->loopLength = curSmpLoopLength;
fixSample(s);
unlockMixerCallback();
setSongModifiedFlag();
writeSample(true);
}
}
}