ref: 3e7b18472cba687a04f6cd9be66ad531ed24283c
dir: /src/ft2_sampling.c/
// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif
#include <stdint.h>
#include <stdbool.h>
#include "ft2_config.h"
#include "ft2_gui.h"
#include "ft2_mouse.h"
#include "ft2_sample_ed.h"
#include "ft2_video.h"
#include "ft2_sampling.h"
#include "ft2_structs.h"
// these may very well change after opening the audio input device
#define SAMPLING_BUFFER_SIZE 2048
static bool sampleInStereo;
static volatile bool drawSamplingBufferFlag, outOfMemoryFlag, noMoreRoomFlag;
static int16_t *currWriteBuf;
static int16_t displayBuffer1[SAMPLING_BUFFER_SIZE * 2], displayBuffer2[SAMPLING_BUFFER_SIZE * 2];
static int32_t bytesSampled, samplingBufferBytes;
static uint32_t samplingRate;
static volatile int32_t currSampleLen;
static SDL_AudioDeviceID recordDev;
static int16_t rightChSmpSlot = -1;
static void SDLCALL samplingCallback(void *userdata, Uint8 *stream, int len)
{
if (instr[editor.curInstr] == NULL || len < 0 || len > samplingBufferBytes)
return;
sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp];
int8_t *newPtr = (int8_t *)realloc(s->origPek, (s->len + len) + LOOP_FIX_LEN);
if (newPtr == NULL)
{
drawSamplingBufferFlag = false;
outOfMemoryFlag = true;
return;
}
s->origPek = newPtr;
s->pek = s->origPek + SMP_DAT_OFFSET;
memcpy(&s->pek[s->len], stream, len);
s->len += len;
if (s->len > MAX_SAMPLE_LEN) // length overflow
{
s->len -= len;
noMoreRoomFlag = true;
return;
}
bytesSampled += len;
if (bytesSampled >= samplingBufferBytes)
{
bytesSampled -= samplingBufferBytes;
currSampleLen = s->len - samplingBufferBytes;
// fill display buffer
memcpy(currWriteBuf, &s->pek[currSampleLen], samplingBufferBytes);
// swap write buffer (double-buffering)
if (currWriteBuf == displayBuffer1)
currWriteBuf = displayBuffer2;
else
currWriteBuf = displayBuffer1;
drawSamplingBufferFlag = true;
}
(void)userdata;
}
void stopSampling(void)
{
int8_t *newPtr;
int16_t *dst16, *src16;
int32_t i, len;
resumeAudio();
mouseAnimOff();
SDL_CloseAudioDevice(recordDev);
editor.samplingAudioFlag = false;
sampleTyp *currSmp = NULL;
sampleTyp *nextSmp = NULL;
if (instr[editor.curInstr] != NULL)
currSmp = &instr[editor.curInstr]->samp[editor.curSmp];
if (sampleInStereo)
{
// read right channel data
if (currSmp->pek != NULL && rightChSmpSlot != -1)
{
nextSmp = &instr[editor.curInstr]->samp[rightChSmpSlot];
nextSmp->origPek = (int8_t *)malloc((currSmp->len >> 1) + LOOP_FIX_LEN);
if (nextSmp->origPek != NULL)
{
nextSmp->pek = nextSmp->origPek + SMP_DAT_OFFSET;
nextSmp->len = currSmp->len >> 1;
src16 = (int16_t *)currSmp->pek;
dst16 = (int16_t *)nextSmp->pek;
len = nextSmp->len >> 1;
for (i = 0; i < len; i++)
dst16[i] = src16[(i << 1) + 1];
}
else
{
freeSample(editor.curInstr, rightChSmpSlot);
}
currSmp->len >>= 1;
// read left channel data by skipping every other sample
dst16 = (int16_t *)currSmp->pek;
len = currSmp->len >> 1;
for (i = 0; i < len; i++)
dst16[i] = dst16[i << 1];
}
}
if (currSmp->origPek != NULL)
{
newPtr = (int8_t *)realloc(currSmp->origPek, currSmp->len + LOOP_FIX_LEN);
if (newPtr != NULL)
{
currSmp->origPek = newPtr;
currSmp->pek = currSmp->origPek + SMP_DAT_OFFSET;
}
fixSample(currSmp);
}
else
{
freeSample(editor.curInstr, editor.curSmp);
}
if (nextSmp != NULL && nextSmp->origPek != NULL)
fixSample(nextSmp);
updateSampleEditorSample();
editor.updateCurInstr = true;
}
static uint8_t getDispBuffPeakMono(const int16_t *smpData, int32_t smpNum)
{
uint32_t max = 0;
for (int32_t i = 0; i < smpNum; i++)
{
const int16_t smp16 = smpData[i];
const uint32_t smpAbs = ABS(smp16);
if (smpAbs > max)
max = smpAbs;
}
max = (max * SAMPLE_AREA_HEIGHT) >> 16;
if (max > 76)
max = 76;
return (uint8_t)max;
}
static uint8_t getDispBuffPeakLeft(const int16_t *smpData, int32_t smpNum)
{
smpNum <<= 1;
uint32_t max = 0;
for (int32_t i = 0; i < smpNum; i += 2)
{
const int16_t smp16 = smpData[i];
const uint32_t smpAbs = ABS(smp16);
if (smpAbs > max)
max = smpAbs;
}
max = (max * SAMPLE_AREA_HEIGHT) >> (16 + 1);
if (max > 38)
max = 38;
return (uint8_t)max;
}
static uint8_t getDispBuffPeakRight(const int16_t *smpData, int32_t smpNum)
{
smpNum <<= 1;
uint32_t max = 0;
for (int32_t i = 0; i < smpNum; i += 2)
{
const int16_t smp16 = smpData[i];
const uint32_t smpAbs = ABS(smp16);
if (smpAbs > max)
max = smpAbs;
}
max = (max * SAMPLE_AREA_HEIGHT) >> (16 + 1);
if (max > 38)
max = 38;
return (uint8_t)max;
}
static inline int32_t scrPos2SmpBufPos(int32_t x) // x = 0..SAMPLE_AREA_WIDTH
{
return (x * ((SAMPLING_BUFFER_SIZE << 16) / SAMPLE_AREA_WIDTH)) >> 16;
}
static void drawSamplingPreview(void)
{
uint8_t smpAbs;
int16_t *readBuf;
uint16_t x;
int32_t smpIdx, smpNum;
uint32_t *centerPtrL, *centerPtrR;
const uint32_t pixVal = video.palette[PAL_PATTEXT];
// select buffer currently not being written to (double-buffering)
if (currWriteBuf == displayBuffer1)
readBuf = displayBuffer2;
else
readBuf = displayBuffer1;
if (sampleInStereo)
{
// stereo sampling
const uint16_t centerL = SAMPLE_AREA_Y_CENTER - (SAMPLE_AREA_HEIGHT / 4);
const uint16_t centerR = SAMPLE_AREA_Y_CENTER + (SAMPLE_AREA_HEIGHT / 4);
centerPtrL = &video.frameBuffer[centerL*SCREEN_W];
centerPtrR = &video.frameBuffer[centerR*SCREEN_W];
for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
{
smpIdx = scrPos2SmpBufPos(x);
smpNum = scrPos2SmpBufPos(x+1) - smpIdx;
if (smpIdx+smpNum >= SAMPLING_BUFFER_SIZE)
smpNum = SAMPLING_BUFFER_SIZE - smpIdx;
// left channel samples
smpAbs = getDispBuffPeakLeft(&readBuf[(smpIdx * 2) + 0], smpNum);
if (smpAbs == 0)
centerPtrL[x] = pixVal;
else
vLine(x, centerL - smpAbs, (smpAbs * 2) + 1, PAL_PATTEXT);
// right channel samples
smpAbs = getDispBuffPeakRight(&readBuf[(smpIdx * 2) + 1], smpNum);
if (smpAbs == 0)
centerPtrR[x] = pixVal;
else
vLine(x, centerR - smpAbs, (smpAbs * 2) + 1, PAL_PATTEXT);
}
}
else
{
// mono sampling
centerPtrL = &video.frameBuffer[SAMPLE_AREA_Y_CENTER * SCREEN_W];
for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
{
smpIdx = scrPos2SmpBufPos(x);
smpNum = scrPos2SmpBufPos(x+1) - smpIdx;
if (smpIdx+smpNum >= SAMPLING_BUFFER_SIZE)
smpNum = SAMPLING_BUFFER_SIZE - smpIdx;
smpAbs = getDispBuffPeakMono(&readBuf[smpIdx], smpNum);
if (smpAbs == 0)
centerPtrL[x] = pixVal;
else
vLine(x, SAMPLE_AREA_Y_CENTER - smpAbs, (smpAbs * 2) + 1, PAL_PATTEXT);
}
}
}
void handleSamplingUpdates(void)
{
if (outOfMemoryFlag)
{
outOfMemoryFlag = false;
stopSampling();
okBox(0, "System message", "Not enough memory!");
return;
}
if (noMoreRoomFlag)
{
noMoreRoomFlag = false;
stopSampling();
okBox(0, "System message", "Not more room in sample!");
return;
}
if (drawSamplingBufferFlag)
{
drawSamplingBufferFlag = false;
// clear sample data area
memset(&video.frameBuffer[174 * SCREEN_W], 0, SAMPLE_AREA_WIDTH * SAMPLE_AREA_HEIGHT * sizeof (int32_t));
drawSamplingPreview();
// clear and draw new sample length number
fillRect(536, 362, 56, 10, PAL_DESKTOP);
if (sampleInStereo)
hexOut(536, 362, PAL_FORGRND, currSampleLen >> 1, 8);
else
hexOut(536, 362, PAL_FORGRND, currSampleLen, 8);
}
}
void startSampling(void)
{
#if SDL_PATCHLEVEL < 5
okBox(0, "System message", "This program needs to be compiled with SDL 2.0.5 or later to support audio sampling.");
return;
#else
SDL_AudioSpec want, have;
if (editor.samplingAudioFlag || editor.curInstr == 0)
return;
int16_t result = okBox(9, "System request", "Stereo sampling will use the next sample slot. While ongoing, press any key to stop.");
if (result == 0 || result == 3)
return;
sampleInStereo = (result == 2);
samplingBufferBytes = sampleInStereo ? (SAMPLING_BUFFER_SIZE * 4) : (SAMPLING_BUFFER_SIZE * 2);
mouseAnimOn();
switch (config.audioInputFreq)
{
case INPUT_FREQ_96KHZ: samplingRate = 96000; break;
case INPUT_FREQ_44KHZ: samplingRate = 44100; break;
default: samplingRate = 48000; break;
}
memset(&want, 0, sizeof (SDL_AudioSpec));
want.freq = samplingRate;
want.format = AUDIO_S16;
want.channels = 1 + sampleInStereo;
want.callback = samplingCallback;
want.userdata = NULL;
want.samples = SAMPLING_BUFFER_SIZE;
recordDev = SDL_OpenAudioDevice(audio.currInputDevice, true, &want, &have, 0);
if (recordDev == 0)
{
okBox(0, "System message", "Couldn't open the input device! Try adjusting the input rate at the config screen.");
return;
}
pauseAudio();
if (instr[editor.curInstr] == NULL && !allocateInstr(editor.curInstr))
{
resumeAudio();
okBox(0, "System message", "Not enough memory!");
return;
}
sampleTyp *s = &instr[editor.curInstr]->samp[editor.curSmp];
// wipe current sample and prepare it
freeSample(editor.curInstr, editor.curSmp);
s->typ |= 16; // we always sample in 16-bit
tuneSample(s, samplingRate, audio.linearPeriodsFlag); // tune sample (relTone/finetune) to the sampling frequency we obtained
if (sampleInStereo)
{
strcpy(s->name, "Left sample");
s->pan = 0;
if (editor.curSmp+1 < MAX_SMP_PER_INST)
rightChSmpSlot = editor.curSmp+1;
else
rightChSmpSlot = -1;
if (rightChSmpSlot != -1)
{
// wipe current sample and prepare it
freeSample(editor.curInstr, rightChSmpSlot);
sampleTyp *nextSmp = &instr[editor.curInstr]->samp[rightChSmpSlot];
strcpy(nextSmp->name, "Right sample");
nextSmp->typ |= 16; // we always sample in 16-bit
nextSmp->pan = 255;
tuneSample(nextSmp, samplingRate, audio.linearPeriodsFlag); // tune sample (relTone/finetune) to the sampling frequency we obtained
}
}
else
{
strcpy(s->name, "Mono-mixed sample");
}
updateSampleEditorSample();
updateSampleEditor();
setSongModifiedFlag();
currWriteBuf = displayBuffer1;
memset(displayBuffer1, 0, sizeof (displayBuffer1));
memset(displayBuffer2, 0, sizeof (displayBuffer2));
editor.samplingAudioFlag = true;
bytesSampled = 0;
currSampleLen = 0;
SDL_PauseAudioDevice(recordDev, false);
#endif
}