Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output 32-bit float samples to preserve dynamic range and prevent clipping #59

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 94 additions & 29 deletions src/mcu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
#include "utf8main.h"
#include "utils/files.h"

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

#if __linux__
#include <unistd.h>
#include <limits.h>
Expand Down Expand Up @@ -105,7 +110,8 @@ static const int ROMSM_SIZE = 0x1000;

static int audio_buffer_size;
static int audio_page_size;
static short *sample_buffer;
static short *sample_buffer = nullptr;
static float *sample_buffer_float = nullptr;

static int sample_read_ptr;
static int sample_write_ptr;
Expand Down Expand Up @@ -924,6 +930,11 @@ void MCU_WorkThread_Unlock(void)

int SDLCALL work_thread(void* data)
{

#ifdef _WIN32
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
#endif

work_thread_lock = SDL_CreateMutex();

MCU_WorkThread_Lock();
Expand Down Expand Up @@ -1079,12 +1090,21 @@ void unscramble(uint8_t *src, uint8_t *dst, int len)
}
}

void audio_callback(void* /*userdata*/, Uint8* stream, int len)
{
len /= 2;
memcpy(stream, &sample_buffer[sample_read_ptr], len * 2);
memset(&sample_buffer[sample_read_ptr], 0, len * 2);
sample_read_ptr += len;
void audio_callback_short(void* /*userdata*/, Uint8* stream, int len)
{
memcpy(stream, &sample_buffer[sample_read_ptr], len);
memset(&sample_buffer[sample_read_ptr], 0, len);
sample_read_ptr += len / sizeof(short);

sample_read_ptr %= audio_buffer_size;
}

void audio_callback_float(void* /*userdata*/, Uint8* stream, int len)
{
memcpy(stream, &sample_buffer_float[sample_read_ptr], len);
memset(&sample_buffer_float[sample_read_ptr], 0, len);
sample_read_ptr += len / sizeof(float);

sample_read_ptr %= audio_buffer_size;
}

Expand Down Expand Up @@ -1116,22 +1136,36 @@ static const char* audio_format_to_str(int format)
return "UNK";
}

int MCU_OpenAudio(int deviceIndex, int pageSize, int pageNum)
enum class AudioFormat {
INT16,
FLOAT32,
};

int MCU_OpenAudio(int deviceIndex, int pageSize, int pageNum, AudioFormat audioFormat)
{
SDL_AudioSpec spec = {};
SDL_AudioSpec spec_actual = {};

audio_page_size = (pageSize/2)*2; // must be even
audio_buffer_size = audio_page_size*pageNum;
audio_page_size = (pageSize / 2) * 2; // must be even
audio_buffer_size = audio_page_size * pageNum;

spec.format = AUDIO_S16SYS;
spec.format = audioFormat == AudioFormat::INT16 ? AUDIO_S16SYS : AUDIO_F32SYS;
spec.freq = (mcu_mk1 || mcu_jv880) ? 64000 : 66207;
spec.channels = 2;
spec.callback = audio_callback;
spec.channels = 2;
spec.samples = audio_page_size / 4;

sample_buffer = (short*)calloc(audio_buffer_size, sizeof(short));
if (!sample_buffer)
if (audioFormat == AudioFormat::INT16)
{
spec.callback = audio_callback_short;
sample_buffer = (short*)calloc(audio_buffer_size, sizeof(short));
}
else if (audioFormat == AudioFormat::FLOAT32)
{
spec.callback = audio_callback_float;
sample_buffer_float = (float*)calloc(audio_buffer_size, sizeof(float));
}

if (!sample_buffer && !sample_buffer_float)
{
printf("Cannot allocate audio buffer.\n");
return 0;
Expand Down Expand Up @@ -1184,22 +1218,33 @@ void MCU_CloseAudio(void)
{
SDL_CloseAudio();
if (sample_buffer) free(sample_buffer);
else if (sample_buffer_float) free(sample_buffer_float);
}

void MCU_PostSample(int *sample)
{
sample[0] >>= 15;
if (sample[0] > INT16_MAX)
sample[0] = INT16_MAX;
else if (sample[0] < INT16_MIN)
sample[0] = INT16_MIN;
sample[1] >>= 15;
if (sample[1] > INT16_MAX)
sample[1] = INT16_MAX;
else if (sample[1] < INT16_MIN)
sample[1] = INT16_MIN;
sample_buffer[sample_write_ptr + 0] = sample[0];
sample_buffer[sample_write_ptr + 1] = sample[1];
if (sample_buffer)
{
sample[0] >>= 15;
if (sample[0] > INT16_MAX)
sample[0] = INT16_MAX;
else if (sample[0] < INT16_MIN)
sample[0] = INT16_MIN;
sample[1] >>= 15;
if (sample[1] > INT16_MAX)
sample[1] = INT16_MAX;
else if (sample[1] < INT16_MIN)
sample[1] = INT16_MIN;
sample_buffer[sample_write_ptr + 0] = sample[0];
sample_buffer[sample_write_ptr + 1] = sample[1];
}
else if (sample_buffer_float)
{
const float divRec = 1 / 536870912.0f;
sample_buffer_float[sample_write_ptr + 0] = sample[0] * divRec;
sample_buffer_float[sample_write_ptr + 1] = sample[1] * divRec;
}

sample_write_ptr = (sample_write_ptr + 2) % audio_buffer_size;
}

Expand Down Expand Up @@ -1250,6 +1295,7 @@ enum class ResetType {
GM_RESET,
};


void MIDI_Reset(ResetType resetType)
{
const unsigned char gmReset[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
Expand Down Expand Up @@ -1277,16 +1323,23 @@ int main(int argc, char *argv[])
(void)argc;
std::string basePath;

#ifdef _WIN32
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
#endif

int port = 0;
int audioDeviceIndex = -1;
int pageSize = 512;
int pageNum = 32;
bool autodetect = true;
ResetType resetType = ResetType::NONE;
AudioFormat audioFormat = AudioFormat::INT16;

romset = ROM_SET_MK2;

{
bool isBufferSet = false;

for (int i = 1; i < argc; i++)
{
if (!strncmp(argv[i], "-p:", 3))
Expand All @@ -1297,6 +1350,10 @@ int main(int argc, char *argv[])
{
audioDeviceIndex = atoi(argv[i] + 3);
}
else if (!strcmp(argv[i], "-af"))
{
audioFormat = AudioFormat::FLOAT32;
}
else if (!strncmp(argv[i], "-ab:", 4))
{
char* pColon = argv[i] + 3;
Expand All @@ -1308,6 +1365,7 @@ int main(int argc, char *argv[])
if (pColon && pColon[1] != 0)
{
pageNum = atoi(++pColon);
isBufferSet = true;
}
}

Expand All @@ -1316,6 +1374,7 @@ int main(int argc, char *argv[])
{
pageSize = 512;
pageNum = 32;
isBufferSet = false;
}
}
else if (!strcmp(argv[i], "-mk2"))
Expand Down Expand Up @@ -1350,7 +1409,13 @@ int main(int argc, char *argv[])
else if (!strcmp(argv[i], "-gm"))
{
resetType = ResetType::GM_RESET;
}
}
}

if (audioFormat == AudioFormat::FLOAT32 && !isBufferSet)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this flag would be not needed if you move the pageSize,pageNum set to inside the else if (!strcmp(argv[i], "-af")) if case?

Copy link
Contributor Author

@Falcosoft Falcosoft Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case the argument order would be mandatory otherwise at
(!strcmp(argv[i], "-af")) you cannot know if -ab is set before, not set at all or only will be set after -af.
The if (audioFormat == AudioFormat::FLOAT32 && !isBufferSet) check is added to make sure that only change the dafault if -ab is not set manually.
But correct me if I'm wrong, maybe I overlook something.

pageSize = 6144;
pageNum = 3;
}
}

Expand Down Expand Up @@ -1582,7 +1647,7 @@ int main(int argc, char *argv[])
return 2;
}

if (!MCU_OpenAudio(audioDeviceIndex, pageSize, pageNum))
if (!MCU_OpenAudio(audioDeviceIndex, pageSize, pageNum, audioFormat))
{
fprintf(stderr, "FATAL ERROR: Failed to open the audio stream.\n");
fflush(stderr);
Expand Down