ref: 0d1a2cf15c023a03f0ee62017133b7035e142a56
dir: /src/pt2_sampler.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 <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "pt2_header.h"
#include "pt2_helpers.h"
#include "pt2_textout.h"
#include "pt2_audio.h"
#include "pt2_palette.h"
#include "pt2_tables.h"
#include "pt2_visuals.h"
#include "pt2_blep.h"
#include "pt2_mouse.h"
#include "pt2_scopes.h"
#define CENTER_LINE_COLOR 0x303030
#define MARK_COLOR_1 0x666666 /* inverted background */
#define MARK_COLOR_2 0xCCCCCC /* inverted waveform */
#define MARK_COLOR_3 0x7D7D7D /* inverted center line */
#define SAMPLE_AREA_Y_CENTER 169
#define SAMPLE_AREA_HEIGHT 64
typedef struct sampleMixer_t
{
int32_t length, pos;
uint32_t posFrac, delta;
} sampleMixer_t;
static int32_t samOffsetScaled;
static uint32_t waveInvertTable[8];
static const int8_t tuneToneData[32] = // Tuning Tone (Sine Wave)
{
0, 25, 49, 71, 91, 106, 118, 126,
127, 126, 118, 106, 91, 71, 49, 25,
0, -25, -49, -71, -91,-106,-118,-126,
-127,-126,-118,-106, -91, -71, -49, -25
};
extern uint32_t *pixelBuffer; // pt_main.c
void setLoopSprites(void);
void createSampleMarkTable(void)
{
// used for invertRange() (sample data marking)
waveInvertTable[0] = 0x00000000 | palette[PAL_BACKGRD];
waveInvertTable[1] = 0x01000000 | palette[PAL_QADSCP];
waveInvertTable[2] = 0x02000000 | CENTER_LINE_COLOR;
waveInvertTable[3] = 0x03000000; // spacer, not used
waveInvertTable[4] = 0x04000000 | MARK_COLOR_1;
waveInvertTable[5] = 0x05000000 | MARK_COLOR_2;
waveInvertTable[6] = 0x06000000 | MARK_COLOR_3;
waveInvertTable[7] = 0x07000000; // spacer, not used
}
static void updateSamOffset(void)
{
if (editor.sampler.samDisplay == 0)
samOffsetScaled = 0;
else
samOffsetScaled = (editor.sampler.samOffset * SAMPLE_AREA_WIDTH) / editor.sampler.samDisplay; // truncate here
}
void fixSampleBeep(moduleSample_t *s)
{
if (s->length >= 2 && s->loopStart+s->loopLength <= 2)
{
modEntry->sampleData[s->offset+0] = 0;
modEntry->sampleData[s->offset+1] = 0;
}
}
void updateSamplePos(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.currSample >= 0 && editor.currSample <= 30)
{
s = &modEntry->samples[editor.currSample];
if (editor.samplePos > s->length)
editor.samplePos = s->length;
if (editor.ui.editOpScreenShown && editor.ui.editOpScreen == 2)
editor.ui.updatePosText = true;
}
}
void fillSampleFilterUndoBuffer(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.currSample >= 0 && editor.currSample <= 30)
{
s = &modEntry->samples[editor.currSample];
memcpy(editor.tempSample, &modEntry->sampleData[s->offset], s->length);
}
}
static void sampleLine(uint32_t *frameBuffer, int16_t line_x1, int16_t line_x2, int16_t line_y1, int16_t line_y2)
{
int16_t d, x, y, ax, ay, sx, sy, dx, dy;
uint32_t color = 0x01000000 | palette[PAL_QADSCP];
assert(line_x1 >= 0 || line_x2 >= 0 || line_x1 < SCREEN_W || line_x2 < SCREEN_W);
assert(line_y1 >= 0 || line_y2 >= 0 || line_y1 < SCREEN_H || line_y2 < SCREEN_H);
dx = line_x2 - line_x1;
ax = ABS(dx) * 2;
sx = SGN(dx);
dy = line_y2 - line_y1;
ay = ABS(dy) * 2;
sy = SGN(dy);
x = line_x1;
y = line_y1;
if (ax > ay)
{
d = ay - ((uint16_t)ax / 2);
while (true)
{
assert(y >= 0 || x >= 0 || y < SCREEN_H || x < SCREEN_W);
frameBuffer[(y * SCREEN_W) + x] = color;
if (x == line_x2)
break;
if (d >= 0)
{
y += sy;
d -= ax;
}
x += sx;
d += ay;
}
}
else
{
d = ax - ((uint16_t)ay / 2);
while (true)
{
assert(y >= 0 || x >= 0 || y < SCREEN_H || x < SCREEN_W);
frameBuffer[(y * SCREEN_W) + x] = color;
if (y == line_y2)
break;
if (d >= 0)
{
x += sx;
d -= ay;
}
y += sy;
d += ax;
}
}
}
static void setDragBar(void)
{
int32_t pos;
uint32_t *dstPtr, pixel, bgPixel;
if (editor.sampler.samLength > 0 && editor.sampler.samDisplay != editor.sampler.samLength)
{
int32_t roundingBias = (uint32_t)editor.sampler.samLength / 2;
// update drag bar coordinates
pos = ((editor.sampler.samOffset * 311) + roundingBias) / editor.sampler.samLength;
editor.sampler.dragStart = pos + 4;
editor.sampler.dragStart = CLAMP(editor.sampler.dragStart, 4, 315);
pos = (((editor.sampler.samDisplay + editor.sampler.samOffset) * 311) + roundingBias) / editor.sampler.samLength;
editor.sampler.dragEnd = pos + 5;
editor.sampler.dragEnd = CLAMP(editor.sampler.dragEnd, 5, 316);
if (editor.sampler.dragStart > editor.sampler.dragEnd-1)
editor.sampler.dragStart = editor.sampler.dragEnd-1;
// draw drag bar
dstPtr = &pixelBuffer[206 * SCREEN_W];
pixel = palette[PAL_QADSCP];
bgPixel = palette[PAL_BACKGRD];
for (int32_t y = 0; y < 4; y++)
{
for (int32_t x = 4; x < 316; x++)
{
if (x >= editor.sampler.dragStart && x <= editor.sampler.dragEnd)
dstPtr[x] = pixel; // drag bar
else
dstPtr[x] = bgPixel; // background
}
dstPtr += SCREEN_W;
}
}
else
{
// clear drag bar background
dstPtr = &pixelBuffer[(206 * SCREEN_W) + 4];
pixel = palette[PAL_BACKGRD];
for (int32_t y = 0; y < 4; y++)
{
for (int32_t x = 0; x < 312; x++)
dstPtr[x] = pixel;
dstPtr += SCREEN_W;
}
}
}
static int8_t getScaledSample(int32_t index)
{
const int8_t *ptr8;
if (editor.sampler.samLength <= 0 || index < 0 || index > editor.sampler.samLength)
return 0;
ptr8 = editor.sampler.samStart;
if (ptr8 == NULL)
return 0;
return ptr8[index] >> 2;
}
int32_t smpPos2Scr(int32_t pos) // sample pos -> screen x pos
{
if (editor.sampler.samDisplay == 0)
return 0;
uint32_t roundingBias = (uint32_t)editor.sampler.samDisplay >> 1;
pos = (((uint32_t)pos * SAMPLE_AREA_WIDTH) + roundingBias) / (uint32_t)editor.sampler.samDisplay; // rounded
pos -= samOffsetScaled;
return pos;
}
int32_t scr2SmpPos(int32_t x) // screen x pos -> sample pos
{
if (editor.sampler.samDisplay == 0)
return 0;
if (x < 0)
x = 0;
x += samOffsetScaled;
x = (uint32_t)(x * editor.sampler.samDisplay) / SAMPLE_AREA_WIDTH; // truncate here
return x;
}
static void getSampleDataPeak(int8_t *smpPtr, int32_t numBytes, int16_t *outMin, int16_t *outMax)
{
int8_t smp, smpMin, smpMax;
smpMin = 127;
smpMax = -128;
for (int32_t i = 0; i < numBytes; i++)
{
smp = smpPtr[i];
if (smp < smpMin) smpMin = smp;
if (smp > smpMax) smpMax = smp;
}
*outMin = SAMPLE_AREA_Y_CENTER - (smpMin >> 2);
*outMax = SAMPLE_AREA_Y_CENTER - (smpMax >> 2);
}
static void renderSampleData(void)
{
int8_t *smpPtr;
int16_t y1, y2, min, max, oldMin, oldMax;
int32_t x, y, smpIdx, smpNum;
uint32_t *dstPtr, pixel;
moduleSample_t *s;
s = &modEntry->samples[editor.currSample];
// clear sample data background
dstPtr = &pixelBuffer[(138 * SCREEN_W) + 3];
pixel = palette[PAL_BACKGRD];
for (y = 0; y < SAMPLE_VIEW_HEIGHT; y++)
{
for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
dstPtr[x] = pixel;
dstPtr += SCREEN_W;
}
// display center line
if (ptConfig.dottedCenterFlag)
{
dstPtr = &pixelBuffer[(SAMPLE_AREA_Y_CENTER * SCREEN_W) + 3];
for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
dstPtr[x] = 0x02000000 | CENTER_LINE_COLOR;
}
// render sample data
if (editor.sampler.samDisplay >= 0 && editor.sampler.samDisplay <= MAX_SAMPLE_LEN)
{
y1 = SAMPLE_AREA_Y_CENTER - getScaledSample(scr2SmpPos(0));
if (editor.sampler.samDisplay <= SAMPLE_AREA_WIDTH)
{
// 1:1 or zoomed in
for (x = 1; x < SAMPLE_AREA_WIDTH; x++)
{
y2 = SAMPLE_AREA_Y_CENTER - getScaledSample(scr2SmpPos(x));
sampleLine(pixelBuffer, x + 2, x + 3, y1, y2);
y1 = y2;
}
}
else
{
// zoomed out
oldMin = y1;
oldMax = y1;
smpPtr = &modEntry->sampleData[s->offset];
for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
{
smpIdx = scr2SmpPos(x);
smpNum = scr2SmpPos(x+1) - smpIdx;
// prevent look-up overflow (yes, this can happen near the end of the sample)
if (smpIdx+smpNum > editor.sampler.samLength)
smpNum = editor.sampler.samLength - smpNum;
if (smpNum < 1)
smpNum = 1;
getSampleDataPeak(&smpPtr[smpIdx], smpNum, &min, &max);
if (x > 0)
{
if (min > oldMax) sampleLine(pixelBuffer, x + 2, x + 3, oldMax, min);
if (max < oldMin) sampleLine(pixelBuffer, x + 2, x + 3, oldMin, max);
}
sampleLine(pixelBuffer, x + 3, x + 3, max, min);
oldMin = min;
oldMax = max;
}
}
}
// render "sample display" text
if (editor.sampler.samStart == editor.sampler.blankSample)
printFiveDecimalsBg(pixelBuffer, 272, 214, 0, palette[PAL_GENTXT], palette[PAL_GENBKG]);
else
printFiveDecimalsBg(pixelBuffer, 272, 214, editor.sampler.samDisplay, palette[PAL_GENTXT], palette[PAL_GENBKG]);
setDragBar();
setLoopSprites();
}
void invertRange(void)
{
int32_t x, y, rangeLen, start, end;
uint32_t *dstPtr;
if (editor.markStartOfs == -1)
return; // no marking
start = smpPos2Scr(editor.markStartOfs);
end = smpPos2Scr(editor.markEndOfs);
if (editor.sampler.samDisplay < editor.sampler.samLength && (start >= SAMPLE_AREA_WIDTH || end < 0))
return; // range is outside of view
start = CLAMP(start, 0, SAMPLE_AREA_WIDTH-1);
end = CLAMP(end, 0, SAMPLE_AREA_WIDTH-1);
rangeLen = (end + 1) - start;
if (rangeLen < 1)
rangeLen = 1;
dstPtr = &pixelBuffer[(138 * SCREEN_W) + (start + 3)];
for (y = 0; y < 64; y++)
{
for (x = 0; x < rangeLen; x++)
dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // It's magic! ptr[x]>>24 = wave/invert color number
dstPtr += SCREEN_W;
}
}
void displaySample(void)
{
if (!editor.ui.samplerScreenShown)
return;
renderSampleData();
if (editor.markStartOfs != -1)
invertRange();
editor.ui.update9xxPos = true;
}
void redrawSample(void)
{
moduleSample_t *s;
if (!editor.ui.samplerScreenShown)
return;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.currSample >= 0 && editor.currSample <= 30)
{
editor.markStartOfs = -1;
editor.sampler.samOffset = 0;
updateSamOffset();
s = &modEntry->samples[editor.currSample];
if (s->length > 0)
{
editor.sampler.samStart = &modEntry->sampleData[s->offset];
editor.sampler.samDisplay = s->length;
editor.sampler.samLength = s->length;
}
else
{
// "blank sample" template
editor.sampler.samStart = editor.sampler.blankSample;
editor.sampler.samLength = SAMPLE_AREA_WIDTH;
editor.sampler.samDisplay = SAMPLE_AREA_WIDTH;
}
renderSampleData();
updateSamplePos();
editor.ui.update9xxPos = true;
editor.ui.lastSampleOffset = 0x900;
// for quadrascope
editor.sampler.samDrawStart = s->offset;
editor.sampler.samDrawEnd = s->offset + s->length;
}
}
void highPassSample(int32_t cutOff)
{
int32_t smp32, i, from, to;
double *dSampleData, dBaseFreq, dCutOff;
moduleSample_t *s;
lossyIntegrator_t filterHi;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (cutOff == 0)
{
displayErrorMsg("CUTOFF CAN'T BE 0");
return;
}
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
from = 0;
to = s->length;
if (editor.markStartOfs != -1)
{
from = editor.markStartOfs;
to = editor.markEndOfs;
if (to > s->length)
to = s->length;
if (from == to)
{
from = 0;
to = s->length;
}
}
dSampleData = (double *)malloc(s->length * sizeof (double));
if (dSampleData == NULL)
{
statusOutOfMemory();
return;
}
fillSampleFilterUndoBuffer();
// setup filter coefficients
dBaseFreq = FILTERS_BASE_FREQ;
dCutOff = (double)cutOff;
if (dCutOff >= dBaseFreq/2.0)
{
dCutOff = dBaseFreq/2.0;
editor.hpCutOff = (uint16_t)dCutOff;
}
calcCoeffLossyIntegrator(dBaseFreq, dCutOff, &filterHi);
// copy over sample data to double buffer
for (i = 0; i < s->length; i++)
dSampleData[i] = modEntry->sampleData[s->offset+i];
filterHi.dBuffer[0] = 0.0;
if (to <= s->length)
{
for (i = from; i < to; i++)
lossyIntegratorHighPassMono(&filterHi, dSampleData[i], &dSampleData[i]);
}
if (editor.normalizeFiltersFlag)
normalize8bitDoubleSigned(dSampleData, s->length);
for (i = from; i < to; i++)
{
smp32 = (int32_t)dSampleData[i];
CLAMP8(smp32);
modEntry->sampleData[s->offset + i] = (int8_t)smp32;
}
free(dSampleData);
fixSampleBeep(s);
displaySample();
updateWindowTitle(MOD_IS_MODIFIED);
}
void lowPassSample(int32_t cutOff)
{
int32_t smp32, i, from, to;
double *dSampleData, dBaseFreq, dCutOff;
moduleSample_t *s;
lossyIntegrator_t filterLo;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (cutOff == 0)
{
displayErrorMsg("CUTOFF CAN'T BE 0");
return;
}
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
from = 0;
to = s->length;
if (editor.markStartOfs != -1)
{
from = editor.markStartOfs;
to = editor.markEndOfs;
if (to > s->length)
to = s->length;
if (from == to)
{
from = 0;
to = s->length;
}
}
dSampleData = (double *)malloc(s->length * sizeof (double));
if (dSampleData == NULL)
{
statusOutOfMemory();
return;
}
fillSampleFilterUndoBuffer();
// setup filter coefficients
dBaseFreq = FILTERS_BASE_FREQ;
dCutOff = (double)cutOff;
if (dCutOff >= dBaseFreq/2.0)
{
dCutOff = dBaseFreq/2.0;
editor.lpCutOff = (uint16_t)dCutOff;
}
calcCoeffLossyIntegrator(dBaseFreq, dCutOff, &filterLo);
// copy over sample data to double buffer
for (i = 0; i < s->length; i++)
dSampleData[i] = modEntry->sampleData[s->offset+i];
filterLo.dBuffer[0] = 0.0;
if (to <= s->length)
{
for (i = from; i < to; i++)
lossyIntegratorMono(&filterLo, dSampleData[i], &dSampleData[i]);
}
if (editor.normalizeFiltersFlag)
normalize8bitDoubleSigned(dSampleData, s->length);
for (i = from; i < to; i++)
{
smp32 = (int32_t)dSampleData[i];
CLAMP8(smp32);
modEntry->sampleData[s->offset + i] = (int8_t)smp32;
}
free(dSampleData);
fixSampleBeep(s);
displaySample();
updateWindowTitle(MOD_IS_MODIFIED);
}
void redoSampleData(int8_t sample)
{
moduleSample_t *s;
assert(sample >= 0 && sample <= 30);
s = &modEntry->samples[sample];
turnOffVoices();
if (editor.smpRedoBuffer[sample] != NULL && editor.smpRedoLengths[sample] > 0)
{
memcpy(&modEntry->sampleData[s->offset], editor.smpRedoBuffer[sample], editor.smpRedoLengths[sample]);
if (editor.smpRedoLengths[sample] < MAX_SAMPLE_LEN)
memset(&modEntry->sampleData[s->offset + editor.smpRedoLengths[sample]], 0, MAX_SAMPLE_LEN - editor.smpRedoLengths[sample]);
}
else
{
memset(&modEntry->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
}
s->fineTune = editor.smpRedoFinetunes[sample];
s->volume = editor.smpRedoVolumes[sample];
s->length = editor.smpRedoLengths[sample];
s->loopStart = editor.smpRedoLoopStarts[sample];
s->loopLength = (editor.smpRedoLoopLengths[sample] < 2) ? 2 : editor.smpRedoLoopLengths[sample];
displayMsg("SAMPLE RESTORED !");
editor.samplePos = 0;
updateCurrSample();
// this routine can be called while the sampler toolboxes are open, so redraw them
if (editor.ui.samplerScreenShown)
{
if (editor.ui.samplerVolBoxShown)
renderSamplerVolBox();
else if (editor.ui.samplerFiltersBoxShown)
renderSamplerFiltersBox();
}
}
void fillSampleRedoBuffer(int8_t sample)
{
moduleSample_t *s;
assert(sample >= 0 && sample <= 30);
s = &modEntry->samples[sample];
if (editor.smpRedoBuffer[sample] != NULL)
{
free(editor.smpRedoBuffer[sample]);
editor.smpRedoBuffer[sample] = NULL;
}
editor.smpRedoFinetunes[sample] = s->fineTune;
editor.smpRedoVolumes[sample] = s->volume;
editor.smpRedoLengths[sample] = s->length;
editor.smpRedoLoopStarts[sample] = s->loopStart;
editor.smpRedoLoopLengths[sample] = s->loopLength;
if (s->length > 0)
{
editor.smpRedoBuffer[sample] = (int8_t *)malloc(s->length);
if (editor.smpRedoBuffer[sample] != NULL)
memcpy(editor.smpRedoBuffer[sample], &modEntry->sampleData[s->offset], s->length);
}
}
bool allocSamplerVars(void)
{
editor.sampler.copyBuf = (int8_t *)malloc(MAX_SAMPLE_LEN);
editor.sampler.blankSample = (int8_t *)calloc(MAX_SAMPLE_LEN, 1);
if (editor.sampler.copyBuf == NULL || editor.sampler.blankSample == NULL)
return false;
return true;
}
void deAllocSamplerVars(void)
{
if (editor.sampler.copyBuf != NULL)
{
free(editor.sampler.copyBuf);
editor.sampler.copyBuf = NULL;
}
if (editor.sampler.blankSample != NULL)
{
free(editor.sampler.blankSample);
editor.sampler.blankSample = NULL;
}
for (uint8_t i = 0; i < MOD_SAMPLES; i++)
{
if (editor.smpRedoBuffer[i] != NULL)
{
free(editor.smpRedoBuffer[i]);
editor.smpRedoBuffer[i] = NULL;
}
}
}
void samplerRemoveDcOffset(void)
{
int8_t *smpDat;
int32_t smp32, i, from, to, offset;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
smpDat = &modEntry->sampleData[s->offset];
from = 0;
to = s->length;
if (editor.markStartOfs != -1)
{
from = editor.markStartOfs;
to = editor.markEndOfs;
if (to > s->length)
to = s->length;
if (from == to)
{
from = 0;
to = s->length;
}
}
if (to <= 0)
return;
// calculate offset value
offset = 0;
for (i = from; i < to; i++)
offset += smpDat[i];
offset /= to;
// remove DC offset
for (i = from; i < to; i++)
{
smp32 = smpDat[i] - offset;
CLAMP8(smp32);
smpDat[i] = (int8_t)smp32;
}
fixSampleBeep(s);
displaySample();
updateWindowTitle(MOD_IS_MODIFIED);
}
#define INTRP_QUADRATIC_TAPS 3
#define INTRP8_QUADRATIC(s1, s2, s3, f) /* output: -32768..32767 (+ spline overshoot) */ \
{ \
int32_t s4, frac = (f) >> 1; \
\
s2 <<= 8; \
s4 = ((s1 + s3) << (8 - 1)) - s2; \
s4 = ((s4 * frac) >> 16) + s2; \
s3 = (s1 + s3) << (8 - 1); \
s1 <<= 8; \
s3 = (s1 + s3) >> 1; \
s1 += ((s4 - s3) * frac) >> 14; \
} \
#define INTRP_LINEAR_TAPS 2
#define INTRP8_LINEAR(s1, s2, f) /* output: -127..128 */ \
s2 -= s1; \
s2 *= (int32_t)(f); \
s1 <<= 8; \
s2 >>= (16 - 8); \
s1 += s2; \
s1 >>= 8; \
void mixChordSample(void)
{
bool smpLoopFlag;
char smpText[22 + 1];
int8_t *smpData, sameNotes, smpVolume;
uint8_t smpFinetune, finetune;
int32_t channels, samples[INTRP_QUADRATIC_TAPS], *mixData, i, j, k, pos, smpLoopStart, smpLoopLength, smpEnd;
sampleMixer_t mixCh[4], *v;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
assert(editor.tuningNote <= 35);
if (editor.note1 == 36)
{
displayErrorMsg("NO BASENOTE!");
return;
}
if (modEntry->samples[editor.currSample].length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
// check if all notes are the same (illegal)
sameNotes = true;
if ((editor.note2 != 36) && (editor.note2 != editor.note1)) sameNotes = false; else editor.note2 = 36;
if ((editor.note3 != 36) && (editor.note3 != editor.note1)) sameNotes = false; else editor.note3 = 36;
if ((editor.note4 != 36) && (editor.note4 != editor.note1)) sameNotes = false; else editor.note4 = 36;
if (sameNotes)
{
displayErrorMsg("ONLY ONE NOTE!");
return;
}
// sort the notes
for (i = 0; i < 3; i++)
{
if (editor.note2 == 36)
{
editor.note2 = editor.note3;
editor.note3 = editor.note4;
editor.note4 = 36;
}
}
for (i = 0; i < 3; i++)
{
if (editor.note3 == 36)
{
editor.note3 = editor.note4;
editor.note4 = 36;
}
}
// remove eventual note duplicates
if (editor.note4 == editor.note3) editor.note4 = 36;
if (editor.note4 == editor.note2) editor.note4 = 36;
if (editor.note3 == editor.note2) editor.note3 = 36;
editor.ui.updateNote1Text = true;
editor.ui.updateNote2Text = true;
editor.ui.updateNote3Text = true;
editor.ui.updateNote4Text = true;
// setup some variables
smpLoopStart = modEntry->samples[editor.currSample].loopStart;
smpLoopLength = modEntry->samples[editor.currSample].loopLength;
smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : modEntry->samples[editor.currSample].length;
smpData = &modEntry->sampleData[modEntry->samples[editor.currSample].offset];
if (editor.newOldFlag == 0)
{
// find a free sample slot for the new sample
for (i = 0; i < MOD_SAMPLES; i++)
{
if (modEntry->samples[i].length == 0)
break;
}
if (i == MOD_SAMPLES)
{
displayErrorMsg("NO EMPTY SAMPLE!");
return;
}
smpFinetune = modEntry->samples[editor.currSample].fineTune;
smpVolume = modEntry->samples[editor.currSample].volume;
memcpy(smpText, modEntry->samples[editor.currSample].text, sizeof (smpText));
s = &modEntry->samples[i];
s->fineTune = smpFinetune;
s->volume = smpVolume;
memcpy(s->text, smpText, sizeof (smpText));
editor.currSample = (int8_t)i;
}
else
{
// overwrite current sample
s = &modEntry->samples[editor.currSample];
}
mixData = (int32_t *)calloc(MAX_SAMPLE_LEN, sizeof (int32_t));
if (mixData == NULL)
{
statusOutOfMemory();
return;
}
s->length = smpLoopFlag ? MAX_SAMPLE_LEN : editor.chordLength; // if sample loops, set max length
s->loopLength = 2;
s->loopStart = 0;
s->text[21] = '!'; // chord sample indicator
s->text[22] = '\0';
memset(mixCh, 0, sizeof (mixCh));
// setup mixing lengths and deltas
finetune = s->fineTune & 0xF;
channels = 0;
if (editor.note1 < 36)
{
mixCh[0].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note1]);
mixCh[0].length = (smpEnd * periodTable[(finetune * 37) + editor.note1]) / periodTable[editor.tuningNote];
channels++;
}
if (editor.note2 < 36)
{
mixCh[1].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note2]);
mixCh[1].length = (smpEnd * periodTable[(finetune * 37) + editor.note2]) / periodTable[editor.tuningNote];
channels++;
}
if (editor.note3 < 36)
{
mixCh[2].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note3]);
mixCh[2].length = (smpEnd * periodTable[(finetune * 37) + editor.note3]) / periodTable[editor.tuningNote];
channels++;
}
if (editor.note4 < 36)
{
mixCh[3].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note4]);
mixCh[3].length = (smpEnd * periodTable[(finetune * 37) + editor.note4]) / periodTable[editor.tuningNote];
channels++;
}
// start mixing
turnOffVoices();
for (i = 0; i < channels; i++)
{
v = &mixCh[i];
if (v->length <= 0)
continue; // mix active channels only
for (j = 0; j < MAX_SAMPLE_LEN; j++) // don't mix more than we can handle in a sample slot
{
// collect samples for interpolation
for (k = 0; k < INTRP_QUADRATIC_TAPS; k++)
{
pos = v->pos + k;
if (smpLoopFlag)
{
while (pos >= smpEnd)
pos -= smpLoopLength;
samples[k] = smpData[pos];
}
else
{
if (pos >= smpEnd)
samples[k] = 0;
else
samples[k] = smpData[pos];
}
}
INTRP8_QUADRATIC(samples[0], samples[1], samples[2], v->posFrac);
mixData[j] += samples[0];
v->posFrac += v->delta;
if (v->posFrac > 0xFFFF)
{
v->pos += v->posFrac >> 16;
v->posFrac &= 0xFFFF;
if (smpLoopFlag)
{
while (v->pos >= smpEnd)
v->pos -= smpLoopLength;
}
}
}
}
normalize32bitSigned(mixData, s->length);
// normalize gain and quantize to 8-bit
for (i = 0; i < s->length; i++)
modEntry->sampleData[s->offset + i] = (int8_t)(mixData[i] >> 24);
if (s->length < MAX_SAMPLE_LEN)
memset(&modEntry->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
// we're done
free(mixData);
editor.samplePos = 0;
fixSampleBeep(s);
updateCurrSample();
updateWindowTitle(MOD_IS_MODIFIED);
}
void samplerResample(void)
{
int8_t *readData, *writeData;
int16_t refPeriod, newPeriod;
int32_t samples[INTRP_LINEAR_TAPS], i, pos, readPos, writePos;
int32_t readLength, writeLength, loopStart, loopLength;
uint32_t posFrac, delta;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
assert(editor.tuningNote <= 35 && editor.resampleNote <= 35);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
// setup resampling variables
readPos = 0;
writePos = 0;
writeData = &modEntry->sampleData[s->offset];
refPeriod = periodTable[editor.tuningNote];
newPeriod = periodTable[(37 * (s->fineTune & 0xF)) + editor.resampleNote];
readLength = s->length;
writeLength = (readLength * newPeriod) / refPeriod;
if (readLength == writeLength)
return; // no resampling needed
// allocate memory for our sample duplicate
readData = (int8_t *)malloc(s->length);
if (readData == NULL)
{
statusOutOfMemory();
return;
}
if (writeLength <= 0)
{
free(readData);
displayErrorMsg("RESAMPLE ERROR !");
return;
}
delta = ((uint32_t)readLength << 16) / (uint32_t)writeLength;
assert(delta != 0);
writeLength = writeLength & 0xFFFFFFFE;
if (writeLength > MAX_SAMPLE_LEN)
writeLength = MAX_SAMPLE_LEN;
memcpy(readData, writeData, readLength);
// resample
posFrac = 0;
turnOffVoices();
while (writePos < writeLength)
{
// collect samples for interpolation
for (i = 0; i < INTRP_LINEAR_TAPS; i++)
{
pos = readPos + i;
if (pos >= readLength)
samples[i] = 0;
else
samples[i] = readData[pos];
}
INTRP8_LINEAR(samples[0], samples[1], posFrac);
writeData[writePos++] = (int8_t)samples[0];
posFrac += delta;
readPos += posFrac >> 16;
posFrac &= 0xFFFF;
}
free(readData);
// wipe non-used data in new sample
if (writeLength < MAX_SAMPLE_LEN)
memset(&writeData[writePos], 0, MAX_SAMPLE_LEN - writeLength);
// update sample attributes
s->length = writeLength;
s->fineTune = 0;
// scale loop points (and deactivate if overflowing)
if ((s->loopStart + s->loopLength) > 2)
{
loopStart = (int32_t)(((uint32_t)s->loopStart << 16) / delta) & 0xFFFFFFFE;
loopLength = (int32_t)(((uint32_t)s->loopLength << 16) / delta) & 0xFFFFFFFE;
if (loopStart+loopLength > s->length)
{
s->loopStart = 0;
s->loopLength = 2;
}
else
{
s->loopStart = (uint16_t)loopStart;
s->loopLength = (uint16_t)loopLength;
}
}
fixSampleBeep(s);
updateCurrSample();
updateWindowTitle(MOD_IS_MODIFIED);
}
static uint8_t hexToInteger2(char *ptr)
{
char lo, hi;
/* This routine must ONLY be used on an address
** where two bytes can be read. It will mess up
** if the ASCII values are not '0 .. 'F' */
hi = ptr[0];
lo = ptr[1];
// high nybble
if (hi >= 'a')
hi -= ' ';
hi -= '0';
if (hi > 9)
hi -= 7;
// low nybble
if (lo >= 'a')
lo -= ' ';
lo -= '0';
if (lo > 9)
lo -= 7;
return (hi << 4) | lo;
}
void doMix(void)
{
int8_t *fromPtr1, *fromPtr2, *mixPtr;
uint8_t smpFrom1, smpFrom2, smpTo;
int16_t tmp16;
int32_t i, mixLength;
moduleSample_t *s1, *s2, *s3;
smpFrom1 = hexToInteger2(&editor.mixText[4]);
smpFrom2 = hexToInteger2(&editor.mixText[7]);
smpTo = hexToInteger2(&editor.mixText[13]);
if (smpFrom1 == 0 || smpFrom1 > 0x1F || smpFrom2 == 0 || smpFrom2 > 0x1F || smpTo == 0 || smpTo > 0x1F)
{
displayErrorMsg("NOT RANGE 01-1F !");
return;
}
s1 = &modEntry->samples[--smpFrom1];
s2 = &modEntry->samples[--smpFrom2];
s3 = &modEntry->samples[--smpTo];
if (s1->length == 0 || s2->length == 0)
{
displayErrorMsg("EMPTY SAMPLES !!!");
return;
}
if (s1->length > s2->length)
{
fromPtr1 = &modEntry->sampleData[s1->offset];
fromPtr2 = &modEntry->sampleData[s2->offset];
mixLength = s1->length;
}
else
{
fromPtr1 = &modEntry->sampleData[s2->offset];
fromPtr2 = &modEntry->sampleData[s1->offset];
mixLength = s2->length;
}
mixPtr = (int8_t *)malloc(mixLength);
if (mixPtr == NULL)
{
statusOutOfMemory();
return;
}
turnOffVoices();
if (mixLength <= MAX_SAMPLE_LEN)
{
for (i = 0; i < mixLength; i++)
{
tmp16 = (i < s2->length) ? (fromPtr1[i] + fromPtr2[i]) : fromPtr1[i];
if (editor.halfClipFlag == 0)
tmp16 >>= 1;
CLAMP8(tmp16);
mixPtr[i] = (int8_t)tmp16;
}
memcpy(&modEntry->sampleData[s3->offset], mixPtr, mixLength);
if (mixLength < MAX_SAMPLE_LEN)
memset(&modEntry->sampleData[s3->offset + mixLength], 0, MAX_SAMPLE_LEN - mixLength);
}
free(mixPtr);
s3->length = mixLength;
s3->volume = 64;
s3->fineTune = 0;
s3->loopStart = 0;
s3->loopLength = 2;
editor.currSample = smpTo;
editor.samplePos = 0;
fixSampleBeep(s3);
updateCurrSample();
updateWindowTitle(MOD_IS_MODIFIED);
}
// this is actually treble increase
void boostSample(int8_t sample, bool ignoreMark)
{
int8_t *smpDat;
int16_t tmp16_0, tmp16_1, tmp16_2;
int32_t i, from, to;
moduleSample_t *s;
assert(sample >= 0 && sample <= 30);
s = &modEntry->samples[sample];
if (s->length == 0)
return; // don't display warning/show warning pointer, it is done elsewhere
smpDat = &modEntry->sampleData[s->offset];
from = 0;
to = s->length;
if (!ignoreMark)
{
if (editor.markStartOfs != -1)
{
from = editor.markStartOfs;
to = editor.markEndOfs;
if (to > s->length)
to = s->length;
if (from == to)
{
from = 0;
to = s->length;
}
}
}
tmp16_0 = 0;
for (i = from; i < to; i++)
{
tmp16_1 = smpDat[i];
tmp16_2 = tmp16_1;
tmp16_1 -= tmp16_0;
tmp16_0 = tmp16_2;
tmp16_1 >>= 2;
tmp16_2 += tmp16_1;
CLAMP8(tmp16_2);
smpDat[i] = (int8_t)tmp16_2;
}
fixSampleBeep(s);
// don't redraw sample here, it is done elsewhere
}
// this is actually treble decrease
void filterSample(int8_t sample, bool ignoreMark)
{
int8_t *smpDat;
int16_t tmp16;
int32_t i, from, to;
moduleSample_t *s;
assert(sample >= 0 && sample <= 30);
s = &modEntry->samples[sample];
if (s->length == 0)
return; // don't display warning/show warning pointer, it is done elsewhere
smpDat = &modEntry->sampleData[s->offset];
from = 1;
to = s->length;
if (!ignoreMark)
{
if (editor.markStartOfs != -1)
{
from = editor.markStartOfs;
to = editor.markEndOfs;
if (to > s->length)
to = s->length;
if (from == to)
{
from = 0;
to = s->length;
}
}
}
if (to < 1)
return;
to--;
for (i = from; i < to; i++)
{
tmp16 = (smpDat[i+0] + smpDat[i+1]) >> 1;
CLAMP8(tmp16);
smpDat[i] = (int8_t)tmp16;
}
fixSampleBeep(s);
// don't redraw sample here, it is done elsewhere
}
void toggleTuningTone(void)
{
if (editor.currMode == MODE_PLAY || editor.currMode == MODE_RECORD)
return;
editor.tuningFlag ^= 1;
if (editor.tuningFlag)
{
// turn tuning tone on
editor.tuningChan = (editor.cursor.channel + 1) & 3;
if (editor.tuningNote > 35)
editor.tuningNote = 35;
modEntry->channels[editor.tuningChan].n_volume = 64; // we need this for the scopes
paulaSetPeriod(editor.tuningChan, periodTable[editor.tuningNote]);
paulaSetVolume(editor.tuningChan, 64);
paulaSetData(editor.tuningChan, tuneToneData);
paulaSetLength(editor.tuningChan, sizeof (tuneToneData) / 2);
paulaStartDMA(editor.tuningChan);
// force loop flag on for scopes
scopeExt[editor.tuningChan].newLoopFlag = scope[editor.tuningChan].loopFlag = true;
}
else
{
// turn tuning tone off
mixerKillVoice(editor.tuningChan);
}
}
void sampleMarkerToBeg(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
invertRange();
editor.markStartOfs = -1;
editor.samplePos = 0;
}
else
{
invertRange();
if (input.keyb.shiftPressed && editor.markStartOfs != -1)
{
editor.markStartOfs = editor.sampler.samOffset;
}
else
{
editor.markStartOfs = editor.sampler.samOffset;
editor.markEndOfs = editor.markStartOfs;
}
invertRange();
editor.samplePos = (uint16_t)editor.markEndOfs;
}
updateSamplePos();
}
void sampleMarkerToCenter(void)
{
int32_t middlePos;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
invertRange();
editor.markStartOfs = -1;
editor.samplePos = 0;
}
else
{
middlePos = editor.sampler.samOffset + ((editor.sampler.samDisplay + 1) / 2);
invertRange();
if (input.keyb.shiftPressed && editor.markStartOfs != -1)
{
if (editor.markStartOfs < middlePos)
editor.markEndOfs = middlePos;
else if (editor.markEndOfs > middlePos)
editor.markStartOfs = middlePos;
}
else
{
editor.markStartOfs = middlePos;
editor.markEndOfs = editor.markStartOfs;
}
invertRange();
editor.samplePos = (uint16_t)editor.markEndOfs;
}
updateSamplePos();
}
void sampleMarkerToEnd(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
invertRange();
editor.markStartOfs = -1;
editor.samplePos = 0;
}
else
{
invertRange();
if (input.keyb.shiftPressed && editor.markStartOfs != -1)
{
editor.markEndOfs = s->length;
}
else
{
editor.markStartOfs = s->length;
editor.markEndOfs = editor.markStartOfs;
}
invertRange();
editor.samplePos = (uint16_t)editor.markEndOfs;
}
updateSamplePos();
}
void samplerSamCopy(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.markStartOfs == -1)
{
displayErrorMsg("NO RANGE SELECTED");
return;
}
if (editor.markEndOfs-editor.markStartOfs <= 0)
{
displayErrorMsg("SET LARGER RANGE");
return;
}
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
editor.sampler.copyBufSize = editor.markEndOfs - editor.markStartOfs;
if ((int32_t)(editor.markStartOfs + editor.sampler.copyBufSize) > MAX_SAMPLE_LEN)
{
displayErrorMsg("COPY ERROR !");
return;
}
memcpy(editor.sampler.copyBuf, &modEntry->sampleData[s->offset+editor.markStartOfs], editor.sampler.copyBufSize);
}
void samplerSamDelete(uint8_t cut)
{
int8_t *tmpBuf;
int32_t val32, sampleLength, copyLength, markEnd, markStart;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.markStartOfs == -1)
{
displayErrorMsg("NO RANGE SELECTED");
return;
}
if (editor.markEndOfs-editor.markStartOfs <= 0)
{
displayErrorMsg("SET LARGER RANGE");
return;
}
if (cut)
samplerSamCopy();
s = &modEntry->samples[editor.currSample];
sampleLength = s->length;
if (sampleLength == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
turnOffVoices();
// if whole sample is marked, wipe it
if (editor.markEndOfs-editor.markStartOfs >= sampleLength)
{
memset(&modEntry->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
invertRange();
editor.markStartOfs = -1;
editor.sampler.samStart = editor.sampler.blankSample;
editor.sampler.samDisplay = SAMPLE_AREA_WIDTH;
editor.sampler.samLength = SAMPLE_AREA_WIDTH;
s->length = 0;
s->loopStart = 0;
s->loopLength = 2;
s->volume = 0;
s->fineTune = 0;
editor.samplePos = 0;
updateCurrSample();
updateWindowTitle(MOD_IS_MODIFIED);
return;
}
markEnd = (editor.markEndOfs > sampleLength) ? sampleLength : editor.markEndOfs;
markStart = editor.markStartOfs;
copyLength = (editor.markStartOfs + sampleLength) - markEnd;
if (copyLength < 2 || copyLength > MAX_SAMPLE_LEN)
{
displayErrorMsg("SAMPLE CUT FAIL !");
return;
}
tmpBuf = (int8_t *)malloc(copyLength);
if (tmpBuf == NULL)
{
statusOutOfMemory();
return;
}
// copy start part
memcpy(tmpBuf, &modEntry->sampleData[s->offset], editor.markStartOfs);
// copy end part
if (sampleLength-markEnd > 0)
memcpy(&tmpBuf[editor.markStartOfs], &modEntry->sampleData[s->offset+markEnd], sampleLength - markEnd);
// nuke sample data and copy over the result
memcpy(&modEntry->sampleData[s->offset], tmpBuf, copyLength);
if (copyLength < MAX_SAMPLE_LEN)
memset(&modEntry->sampleData[s->offset+copyLength], 0, MAX_SAMPLE_LEN - copyLength);
free(tmpBuf);
editor.sampler.samLength = copyLength;
if (editor.sampler.samOffset+editor.sampler.samDisplay >= editor.sampler.samLength)
{
if (editor.sampler.samDisplay < editor.sampler.samLength)
{
if (editor.sampler.samLength-editor.sampler.samDisplay < 0)
{
editor.sampler.samOffset = 0;
editor.sampler.samDisplay = editor.sampler.samLength;
}
else
{
editor.sampler.samOffset = editor.sampler.samLength - editor.sampler.samDisplay;
}
}
else
{
editor.sampler.samOffset = 0;
editor.sampler.samDisplay = editor.sampler.samLength;
}
updateSamOffset();
}
if (s->loopLength > 2) // loop enabled?
{
if (markEnd > s->loopStart)
{
if (markStart < s->loopStart+s->loopLength)
{
// we cut data inside the loop, increase loop length
val32 = (s->loopLength - (markEnd - markStart)) & 0xFFFFFFFE;
if (val32 < 2)
val32 = 2;
s->loopLength = (uint16_t)val32;
}
// we cut data after the loop, don't modify loop points
}
else
{
// we cut data before the loop, adjust loop start point
val32 = (s->loopStart - (markEnd - markStart)) & 0xFFFFFFFE;
if (val32 < 0)
{
s->loopStart = 0;
s->loopLength = 2;
}
else
{
s->loopStart = (uint16_t)val32;
}
}
}
s->length = copyLength & 0xFFFE;
if (editor.sampler.samDisplay <= 2)
{
editor.sampler.samStart = editor.sampler.blankSample;
editor.sampler.samLength = SAMPLE_AREA_WIDTH;
editor.sampler.samDisplay = SAMPLE_AREA_WIDTH;
}
invertRange();
if (editor.sampler.samDisplay == 0)
{
editor.markStartOfs = -1; // clear marking
}
else
{
if (editor.markStartOfs >= s->length)
editor.markStartOfs = s->length - 1;
editor.markEndOfs = editor.markStartOfs;
invertRange();
}
editor.samplePos = editor.markStartOfs;
fixSampleBeep(s);
updateSamplePos();
recalcChordLength();
displaySample();
editor.ui.updateCurrSampleLength = true;
editor.ui.updateCurrSampleRepeat = true;
editor.ui.updateCurrSampleReplen = true;
editor.ui.updateSongSize = true;
updateWindowTitle(MOD_IS_MODIFIED);
}
void samplerSamPaste(void)
{
bool wasZooming;
int8_t *tmpBuf;
int32_t markStart;
uint32_t readPos;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (editor.sampler.copyBuf == NULL || editor.sampler.copyBufSize == 0)
{
displayErrorMsg("BUFFER IS EMPTY");
return;
}
s = &modEntry->samples[editor.currSample];
if (s->length > 0 && editor.markStartOfs == -1)
{
displayErrorMsg("SET CURSOR POS");
return;
}
markStart = editor.markStartOfs;
if (s->length == 0)
markStart = 0;
if (s->length+editor.sampler.copyBufSize > MAX_SAMPLE_LEN)
{
displayErrorMsg("NOT ENOUGH ROOM");
return;
}
tmpBuf = (int8_t *)malloc(MAX_SAMPLE_LEN);
if (tmpBuf == NULL)
{
statusOutOfMemory();
return;
}
readPos = 0;
turnOffVoices();
wasZooming = (editor.sampler.samDisplay != editor.sampler.samLength);
// copy start part
if (markStart > 0)
{
memcpy(&tmpBuf[readPos], &modEntry->sampleData[s->offset], markStart);
readPos += markStart;
}
// copy buffer
memcpy(&tmpBuf[readPos], editor.sampler.copyBuf, editor.sampler.copyBufSize);
// copy end part
if (markStart >= 0)
{
readPos += editor.sampler.copyBufSize;
if (s->length-markStart > 0)
memcpy(&tmpBuf[readPos], &modEntry->sampleData[s->offset+markStart], s->length - markStart);
}
s->length = (s->length + editor.sampler.copyBufSize) & 0xFFFFFFFE;
if (s->length > MAX_SAMPLE_LEN)
s->length = MAX_SAMPLE_LEN;
editor.sampler.samLength = s->length;
if (s->loopLength > 2) // loop enabled?
{
if (markStart > s->loopStart)
{
if (markStart < s->loopStart+s->loopLength)
{
// we pasted data inside the loop, increase loop length
s->loopLength += editor.sampler.copyBufSize & 0xFFFFFFFE;
if (s->loopStart+s->loopLength > s->length)
{
s->loopStart = 0;
s->loopLength = 2;
}
}
// we pasted data after the loop, don't modify loop points
}
else
{
// we pasted data before the loop, adjust loop start point
s->loopStart = (s->loopStart + editor.sampler.copyBufSize) & 0xFFFFFFFE;
if (s->loopStart+s->loopLength > s->length)
{
s->loopStart = 0;
s->loopLength = 2;
}
}
}
memcpy(&modEntry->sampleData[s->offset], tmpBuf, s->length);
if (s->length < MAX_SAMPLE_LEN)
memset(&modEntry->sampleData[s->offset+s->length], 0, MAX_SAMPLE_LEN - s->length);
free(tmpBuf);
invertRange();
editor.markStartOfs = -1;
fixSampleBeep(s);
updateSamplePos();
recalcChordLength();
if (wasZooming)
displaySample();
else
redrawSample();
editor.ui.updateCurrSampleLength = true;
editor.ui.updateSongSize = true;
updateWindowTitle(MOD_IS_MODIFIED);
}
static void playCurrSample(uint8_t chn, int32_t startOffset, int32_t endOffset, bool playWaveformFlag)
{
moduleChannel_t *ch;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
assert(chn < AMIGA_VOICES);
assert(editor.currPlayNote <= 35);
s = &modEntry->samples[editor.currSample];
ch = &modEntry->channels[chn];
ch->n_samplenum = editor.currSample;
ch->n_volume = s->volume;
ch->n_period = periodTable[(37 * (s->fineTune & 0xF)) + editor.currPlayNote];
if (playWaveformFlag)
{
ch->n_start = &modEntry->sampleData[s->offset];
ch->n_length = (s->loopStart > 0) ? (uint32_t)(s->loopStart + s->loopLength) / 2 : s->length / 2;
ch->n_loopstart = &modEntry->sampleData[s->offset + s->loopStart];
ch->n_replen = s->loopLength / 2;
}
else
{
ch->n_start = &modEntry->sampleData[s->offset + startOffset];
ch->n_length = (endOffset - startOffset) / 2;
ch->n_loopstart = &modEntry->sampleData[s->offset];
ch->n_replen = 1;
}
if (ch->n_length == 0)
ch->n_length = 1;
paulaSetVolume(chn, ch->n_volume);
paulaSetPeriod(chn, ch->n_period);
paulaSetData(chn, ch->n_start);
paulaSetLength(chn, ch->n_length);
if (!editor.muted[chn])
paulaStartDMA(chn);
else
paulaStopDMA(chn);
// these take effect after the current DMA cycle is done
if (playWaveformFlag)
{
paulaSetData(chn, ch->n_loopstart);
paulaSetLength(chn, ch->n_replen);
}
else
{
paulaSetData(chn, NULL);
paulaSetLength(chn, 1);
}
updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
}
void samplerPlayWaveform(void)
{
playCurrSample(editor.cursor.channel, 0, 0, true);
}
void samplerPlayDisplay(void)
{
int32_t start = editor.sampler.samOffset;
int32_t end = editor.sampler.samOffset + editor.sampler.samDisplay;
playCurrSample(editor.cursor.channel, start, end, false);
}
void samplerPlayRange(void)
{
if (editor.markStartOfs == -1)
{
displayErrorMsg("NO RANGE SELECTED");
return;
}
if (editor.markEndOfs-editor.markStartOfs < 2)
{
displayErrorMsg("SET LARGER RANGE");
return;
}
playCurrSample(editor.cursor.channel, editor.markStartOfs, editor.markEndOfs, false);
}
void setLoopSprites(void)
{
moduleSample_t *s;
if (!editor.ui.samplerScreenShown)
{
hideSprite(SPRITE_LOOP_PIN_LEFT);
hideSprite(SPRITE_LOOP_PIN_RIGHT);
return;
}
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->loopStart+s->loopLength > 2)
{
if (editor.sampler.samDisplay > 0)
{
editor.sampler.loopStartPos = smpPos2Scr(s->loopStart);
if (editor.sampler.loopStartPos >= 0 && editor.sampler.loopStartPos <= SAMPLE_AREA_WIDTH)
setSpritePos(SPRITE_LOOP_PIN_LEFT, editor.sampler.loopStartPos, 138);
else
hideSprite(SPRITE_LOOP_PIN_LEFT);
editor.sampler.loopEndPos = smpPos2Scr(s->loopStart + s->loopLength);
if (editor.sampler.loopEndPos >= 0 && editor.sampler.loopEndPos <= SAMPLE_AREA_WIDTH)
setSpritePos(SPRITE_LOOP_PIN_RIGHT, editor.sampler.loopEndPos + 3, 138);
else
hideSprite(SPRITE_LOOP_PIN_RIGHT);
}
}
else
{
editor.sampler.loopStartPos = 0;
editor.sampler.loopEndPos = 0;
hideSprite(SPRITE_LOOP_PIN_LEFT);
hideSprite(SPRITE_LOOP_PIN_RIGHT);
}
textOutBg(pixelBuffer, 288, 225, (s->loopStart+s->loopLength > 2) ? "ON " : "OFF", palette[PAL_GENTXT], palette[PAL_GENBKG]);
}
void samplerShowAll(void)
{
if (editor.sampler.samDisplay == editor.sampler.samLength)
return; // don't attempt to show all if already showing all! }
editor.sampler.samOffset = 0;
editor.sampler.samDisplay = editor.sampler.samLength;
updateSamOffset();
displaySample();
}
static void samplerZoomIn(int32_t step, int16_t x)
{
int32_t tmpDisplay, tmpOffset;
if (modEntry->samples[editor.currSample].length == 0 || editor.sampler.samDisplay <= 2)
return;
if (step < 1)
step = 1;
tmpDisplay = editor.sampler.samDisplay - (step * 2);
if (tmpDisplay < 2)
tmpDisplay = 2;
const int32_t roundingBias = SCREEN_W / 4;
step += (((x - (SCREEN_W / 2)) * step) + roundingBias) / (SCREEN_W / 2);
tmpOffset = editor.sampler.samOffset + step;
if (tmpOffset < 0)
tmpOffset = 0;
if (tmpOffset+tmpDisplay > editor.sampler.samLength)
tmpOffset = editor.sampler.samLength-tmpDisplay;
editor.sampler.samOffset = tmpOffset;
editor.sampler.samDisplay = tmpDisplay;
updateSamOffset();
displaySample();
}
static void samplerZoomOut(int32_t step, int16_t x)
{
int32_t tmpDisplay, tmpOffset;
if (modEntry->samples[editor.currSample].length == 0 || editor.sampler.samDisplay == editor.sampler.samLength)
return;
if (step < 1)
step = 1;
tmpDisplay = editor.sampler.samDisplay + (step * 2);
if (tmpDisplay > editor.sampler.samLength)
{
tmpOffset = 0;
tmpDisplay = editor.sampler.samLength;
}
else
{
const int32_t roundingBias = SCREEN_W / 4;
step += (((x - (SCREEN_W / 2)) * step) + roundingBias) / (SCREEN_W / 2);
tmpOffset = editor.sampler.samOffset - step;
if (tmpOffset < 0)
tmpOffset = 0;
if (tmpOffset+tmpDisplay > editor.sampler.samLength)
tmpOffset = editor.sampler.samLength-tmpDisplay;
}
editor.sampler.samOffset = tmpOffset;
editor.sampler.samDisplay = tmpDisplay;
updateSamOffset();
displaySample();
}
void samplerZoomInMouseWheel(void)
{
samplerZoomIn((editor.sampler.samDisplay + 5) / 10, input.mouse.x);
}
void samplerZoomOutMouseWheel(void)
{
samplerZoomOut((editor.sampler.samDisplay + 5) / 10, input.mouse.x);
}
void samplerZoomOut2x(void)
{
samplerZoomOut((editor.sampler.samDisplay + 1) / 2, SCREEN_W / 2);
}
void samplerRangeAll(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
invertRange();
editor.markStartOfs = -1;
}
else
{
invertRange();
editor.markStartOfs = editor.sampler.samOffset;
editor.markEndOfs = editor.sampler.samOffset + editor.sampler.samDisplay;
invertRange();
}
}
void samplerShowRange(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length == 0)
{
displayErrorMsg("SAMPLE IS EMPTY");
return;
}
if (editor.markStartOfs == -1)
{
displayErrorMsg("NO RANGE SELECTED");
return;
}
if (editor.markEndOfs-editor.markStartOfs < 2)
{
displayErrorMsg("SET LARGER RANGE");
return;
}
editor.sampler.samDisplay = editor.markEndOfs - editor.markStartOfs;
editor.sampler.samOffset = editor.markStartOfs;
if (editor.sampler.samDisplay+editor.sampler.samOffset > editor.sampler.samLength)
editor.sampler.samOffset = editor.sampler.samLength-editor.sampler.samDisplay;
updateSamOffset();
invertRange();
editor.markStartOfs = -1;
displaySample();
}
void volBoxBarPressed(bool mouseButtonHeld)
{
int32_t mouseX;
if (input.mouse.y < 0 || input.mouse.x < 0 || input.mouse.y >= SCREEN_H || input.mouse.x >= SCREEN_W)
return;
if (!mouseButtonHeld)
{
if (input.mouse.x >= 72 && input.mouse.x <= 173)
{
if (input.mouse.y >= 154 && input.mouse.y <= 174) editor.ui.forceVolDrag = 1;
if (input.mouse.y >= 165 && input.mouse.y <= 175) editor.ui.forceVolDrag = 2;
}
}
else
{
if (editor.sampler.lastMouseX != input.mouse.x)
{
editor.sampler.lastMouseX = input.mouse.x;
mouseX = CLAMP(editor.sampler.lastMouseX - 107, 0, 60);
if (editor.ui.forceVolDrag == 1)
{
editor.vol1 = (int16_t)(((mouseX * 200) + (60/2)) / 60); // rounded
editor.ui.updateVolFromText = true;
showVolFromSlider();
}
else if (editor.ui.forceVolDrag == 2)
{
editor.vol2 = (int16_t)(((mouseX * 200) + (60/2)) / 60); // rounded
editor.ui.updateVolToText = true;
showVolToSlider();
}
}
}
}
void samplerBarPressed(bool mouseButtonHeld)
{
int32_t tmp32;
if (input.mouse.y < 0 || input.mouse.x < 0 || input.mouse.y >= SCREEN_H || input.mouse.x >= SCREEN_W)
return;
if (!mouseButtonHeld)
{
if (input.mouse.x >= 4 && input.mouse.x <= 315)
{
if (input.mouse.x < editor.sampler.dragStart)
{
tmp32 = editor.sampler.samOffset - editor.sampler.samDisplay;
if (tmp32 < 0)
tmp32 = 0;
if (tmp32 == editor.sampler.samOffset)
return;
editor.sampler.samOffset = tmp32;
updateSamOffset();
displaySample();
return;
}
if (input.mouse.x > editor.sampler.dragEnd)
{
tmp32 = editor.sampler.samOffset + editor.sampler.samDisplay;
if (tmp32+editor.sampler.samDisplay <= editor.sampler.samLength)
{
if (tmp32 == editor.sampler.samOffset)
return;
editor.sampler.samOffset = tmp32;
}
else
{
tmp32 = editor.sampler.samLength - editor.sampler.samDisplay;
if (tmp32 == editor.sampler.samOffset)
return;
editor.sampler.samOffset = tmp32;
}
updateSamOffset();
displaySample();
return;
}
editor.sampler.lastSamPos = (uint16_t)input.mouse.x;
editor.sampler.saveMouseX = editor.sampler.lastSamPos - editor.sampler.dragStart;
editor.ui.forceSampleDrag = true;
}
}
if (input.mouse.x != editor.sampler.lastSamPos)
{
editor.sampler.lastSamPos = (uint16_t)input.mouse.x;
tmp32 = editor.sampler.lastSamPos - editor.sampler.saveMouseX - 4;
if (tmp32 < 0)
tmp32 = 0;
tmp32 = (int32_t)(((tmp32 * editor.sampler.samLength) + (311/2)) / 311); // rounded
if (tmp32+editor.sampler.samDisplay <= editor.sampler.samLength)
{
if (tmp32 == editor.sampler.samOffset)
return;
editor.sampler.samOffset = tmp32;
}
else
{
tmp32 = editor.sampler.samLength - editor.sampler.samDisplay;
if (tmp32 == editor.sampler.samOffset)
return;
editor.sampler.samOffset = tmp32;
}
updateSamOffset();
displaySample();
}
}
static int32_t x2LoopX(int32_t mouseX)
{
moduleSample_t *s = &modEntry->samples[editor.currSample];
mouseX -= 3;
if (mouseX < 0)
mouseX = 0;
mouseX = scr2SmpPos(mouseX);
mouseX = CLAMP(mouseX, 0, s->length);
return mouseX;
}
static int32_t xToSmpX(int32_t x, int32_t smpLen)
{
x = scr2SmpPos(x);
x = CLAMP(x, 0, smpLen - 1);
return x;
}
static int8_t yToSmpY(int32_t mouseY)
{
mouseY = (SAMPLE_AREA_Y_CENTER - mouseY) * 4;
CLAMP8(mouseY);
return mouseY;
}
void samplerEditSample(bool mouseButtonHeld)
{
int8_t y;
int32_t mouseY, x, smp_x0, smp_x1, xDistance, smp_y0, smp_y1, yDistance, smp;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (input.mouse.y < 0 || input.mouse.x < 0 || input.mouse.y >= SCREEN_H || input.mouse.x >= SCREEN_W)
return;
s = &modEntry->samples[editor.currSample];
if (!mouseButtonHeld)
{
if (input.mouse.x >= 3 && input.mouse.x <= 316 && input.mouse.y >= 138 && input.mouse.y <= 201)
{
if (s->length == 0)
{
displayErrorMsg("SAMPLE LENGTH = 0");
}
else
{
editor.sampler.lastMouseX = input.mouse.x;
editor.sampler.lastMouseY = input.mouse.y;
editor.ui.forceSampleEdit = true;
updateWindowTitle(MOD_IS_MODIFIED);
}
}
return;
}
mouseY = input.keyb.shiftPressed ? editor.sampler.lastMouseY : input.mouse.y;
x = xToSmpX(input.mouse.x - 3, s->length);
y = yToSmpY(mouseY);
modEntry->sampleData[s->offset+x] = y;
// interpolate x gaps
if (input.mouse.x != editor.sampler.lastMouseX)
{
smp_y0 = yToSmpY(editor.sampler.lastMouseY);
smp_y1 = y;
yDistance = smp_y1 - smp_y0;
if (input.mouse.x > editor.sampler.lastMouseX)
{
smp_x1 = x;
smp_x0 = xToSmpX(editor.sampler.lastMouseX - 3, s->length);
xDistance = smp_x1 - smp_x0;
if (xDistance > 0)
{
for (x = smp_x0; x < smp_x1; x++)
{
assert(x < s->length);
smp = smp_y0 + (((x - smp_x0) * yDistance) / xDistance);
CLAMP8(smp);
modEntry->sampleData[s->offset + x] = (int8_t)smp;
}
}
}
else if (input.mouse.x < editor.sampler.lastMouseX)
{
smp_x0 = x;
smp_x1 = xToSmpX(editor.sampler.lastMouseX - 3, s->length);
xDistance = smp_x1 - smp_x0;
if (xDistance > 0)
{
for (x = smp_x0; x < smp_x1; x++)
{
assert(x < s->length);
smp = smp_y0 + (((smp_x1 - x) * yDistance) / xDistance);
CLAMP8(smp);
modEntry->sampleData[s->offset + x] = (int8_t)smp;
}
}
}
editor.sampler.lastMouseX = input.mouse.x;
if (!input.keyb.shiftPressed)
editor.sampler.lastMouseY = input.mouse.y;
}
displaySample();
}
void samplerSamplePressed(bool mouseButtonHeld)
{
int16_t mouseX;
int32_t tmpPos;
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
if (!mouseButtonHeld)
{
if (input.mouse.y < 142)
{
if (input.mouse.x >= editor.sampler.loopStartPos && input.mouse.x <= editor.sampler.loopStartPos+3)
{
editor.ui.leftLoopPinMoving = true;
editor.ui.rightLoopPinMoving = false;
editor.ui.sampleMarkingPos = 1;
editor.sampler.lastMouseX = input.mouse.x;
return;
}
else if (input.mouse.x >= editor.sampler.loopEndPos+3 && input.mouse.x <= editor.sampler.loopEndPos+6)
{
editor.ui.rightLoopPinMoving = true;
editor.ui.leftLoopPinMoving = false;
editor.ui.sampleMarkingPos = 1;
editor.sampler.lastMouseX = input.mouse.x;
return;
}
}
}
mouseX = (int16_t)input.mouse.x;
s = &modEntry->samples[editor.currSample];
if (editor.ui.leftLoopPinMoving)
{
if (editor.sampler.lastMouseX != mouseX)
{
editor.sampler.lastMouseX = mouseX;
tmpPos = (x2LoopX(mouseX + 2) - s->loopStart) & 0xFFFFFFFE;
if (tmpPos > MAX_SAMPLE_LEN)
tmpPos = MAX_SAMPLE_LEN;
if (s->loopStart+tmpPos >= (s->loopStart+s->loopLength)-2)
{
s->loopStart = (s->loopStart + s->loopLength) - 2;
s->loopLength = 2;
}
else
{
s->loopStart = s->loopStart + tmpPos;
if (s->loopLength-tmpPos > 2)
s->loopLength -= tmpPos;
else
s->loopLength = 2;
}
editor.ui.updateCurrSampleRepeat = true;
editor.ui.updateCurrSampleReplen = true;
setLoopSprites();
mixerUpdateLoops();
updateWindowTitle(MOD_IS_MODIFIED);
}
return;
}
if (editor.ui.rightLoopPinMoving)
{
if (editor.sampler.lastMouseX != mouseX)
{
editor.sampler.lastMouseX = mouseX;
s = &modEntry->samples[editor.currSample];
tmpPos = (x2LoopX(mouseX - 1) - s->loopStart) & 0xFFFFFFFE;
tmpPos = CLAMP(tmpPos, 2, MAX_SAMPLE_LEN);
s->loopLength = tmpPos;
editor.ui.updateCurrSampleRepeat = true;
editor.ui.updateCurrSampleReplen = true;
setLoopSprites();
mixerUpdateLoops();
updateWindowTitle(MOD_IS_MODIFIED);
}
return;
}
if (!mouseButtonHeld)
{
if (mouseX < 3 || mouseX > 319)
return;
editor.ui.sampleMarkingPos = (int16_t)mouseX;
editor.sampler.lastSamPos = editor.ui.sampleMarkingPos;
invertRange();
if (s->length == 0)
{
editor.markStartOfs = -1; // clear marking
}
else
{
editor.markStartOfs = scr2SmpPos(editor.ui.sampleMarkingPos - 3);
editor.markEndOfs = scr2SmpPos(editor.ui.sampleMarkingPos - 3);
if (editor.markEndOfs > s->length)
editor.markEndOfs = s->length;
invertRange();
}
if (s->length == 0)
{
editor.samplePos = 0;
}
else
{
tmpPos = scr2SmpPos(mouseX - 3);
if (tmpPos > s->length)
tmpPos = s->length;
editor.samplePos = (uint16_t)tmpPos;
}
updateSamplePos();
return;
}
mouseX = CLAMP(mouseX, 3, 319);
if (mouseX != editor.sampler.lastSamPos)
{
editor.sampler.lastSamPos = (uint16_t)mouseX;
invertRange();
if (s->length == 0)
{
editor.markStartOfs = -1; // clear marking
}
else
{
if (editor.sampler.lastSamPos > editor.ui.sampleMarkingPos)
{
editor.markStartOfs = scr2SmpPos(editor.ui.sampleMarkingPos - 3);
editor.markEndOfs = scr2SmpPos(editor.sampler.lastSamPos - 3);
}
else
{
editor.markStartOfs = scr2SmpPos(editor.sampler.lastSamPos - 3);
editor.markEndOfs = scr2SmpPos(editor.ui.sampleMarkingPos - 3);
}
if (editor.markEndOfs > s->length)
editor.markEndOfs = s->length;
invertRange();
}
}
if (s->length == 0)
{
editor.samplePos = 0;
}
else
{
tmpPos = scr2SmpPos(mouseX - 3);
if (tmpPos > s->length)
tmpPos = s->length;
editor.samplePos = (uint16_t)tmpPos;
}
updateSamplePos();
}
void samplerLoopToggle(void)
{
moduleSample_t *s;
assert(editor.currSample >= 0 && editor.currSample <= 30);
s = &modEntry->samples[editor.currSample];
if (s->length < 2)
return;
turnOffVoices();
if (s->loopStart+s->loopLength > 2)
{
// disable loop
editor.sampler.tmpLoopStart = s->loopStart;
editor.sampler.tmpLoopLength = s->loopLength;
s->loopStart = 0;
s->loopLength = 2;
}
else
{
// enable loop
if (editor.sampler.tmpLoopStart == 0 && editor.sampler.tmpLoopLength == 0)
{
s->loopStart = 0;
s->loopLength = s->length;
}
else
{
s->loopStart = editor.sampler.tmpLoopStart;
s->loopLength = editor.sampler.tmpLoopLength;
if (s->loopStart+s->loopLength > s->length)
{
s->loopStart = 0;
s->loopLength = s->length;
}
}
}
editor.ui.updateCurrSampleRepeat = true;
editor.ui.updateCurrSampleReplen = true;
displaySample();
mixerUpdateLoops();
recalcChordLength();
updateWindowTitle(MOD_IS_MODIFIED);
}
void exitFromSam(void)
{
editor.ui.samplerScreenShown = false;
memcpy(&pixelBuffer[121 * SCREEN_W], &trackerFrameBMP[121 * SCREEN_W], 320 * 134 * sizeof (int32_t));
updateCursorPos();
setLoopSprites();
editor.ui.updateStatusText = true;
editor.ui.updateSongSize = true;
editor.ui.updateSongTiming = true;
editor.ui.updateSongBPM = true;
editor.ui.updateCurrPattText = true;
editor.ui.updatePatternData = true;
editor.markStartOfs = -1;
}
void samplerScreen(void)
{
if (editor.ui.samplerScreenShown)
{
exitFromSam();
return;
}
editor.ui.samplerScreenShown = true;
memcpy(&pixelBuffer[(121 * SCREEN_W)], samplerScreenBMP, 320 * 134 * sizeof (int32_t));
hideSprite(SPRITE_PATTERN_CURSOR);
editor.ui.updateStatusText = true;
editor.ui.updateSongSize = true;
editor.ui.updateSongTiming = true;
editor.ui.updateResampleNote = true;
editor.ui.update9xxPos = true;
redrawSample();
}
void drawSamplerLine(void)
{
uint8_t i;
int32_t pos;
hideSprite(SPRITE_SAMPLING_POS_LINE);
if (!editor.ui.samplerScreenShown || editor.ui.samplerVolBoxShown || editor.ui.samplerFiltersBoxShown)
return;
for (i = 0; i < AMIGA_VOICES; i++)
{
if (modEntry->channels[i].n_samplenum == editor.currSample && !editor.muted[i])
{
pos = getSampleReadPos(i, editor.currSample);
if (pos >= 0)
{
pos = 3 + smpPos2Scr(pos);
if (pos >= 3 && pos <= 316)
setSpritePos(SPRITE_SAMPLING_POS_LINE, pos, 138);
}
}
}
}