Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7d03e5a

Browse files
committedMay 9, 2024
Support f32 output in main_frontend
Sample conversion implementation by @Falcosoft Upstream PR: nukeykt#59
1 parent 28a5f45 commit 7d03e5a

File tree

2 files changed

+166
-32
lines changed

2 files changed

+166
-32
lines changed
 

‎src/main_frontend.cpp

+151-30
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,39 @@
4141
#include <optional>
4242

4343
using Ringbuffer_S16 = Ringbuffer<int16_t>;
44+
using Ringbuffer_F32 = Ringbuffer<float>;
45+
46+
enum class FE_OutputFormat
47+
{
48+
S16,
49+
F32,
50+
};
4451

4552
struct fe_emu_instance_t {
46-
Emulator emu;
47-
Ringbuffer_S16 sample_buffer;
48-
std::thread thread;
49-
bool running;
53+
Emulator emu;
54+
Ringbuffer_S16 sample_buffer_s16;
55+
Ringbuffer_F32 sample_buffer_f32;
56+
std::thread thread;
57+
bool running;
58+
FE_OutputFormat format;
59+
60+
// Statically selects the correct ringbuffer field into based on SampleT.
61+
template <typename SampleT>
62+
Ringbuffer<SampleT>& StaticSelectBuffer()
63+
{
64+
if constexpr (std::is_same_v<SampleT, int16_t>)
65+
{
66+
return sample_buffer_s16;
67+
}
68+
else if constexpr (std::is_same_v<SampleT, float>)
69+
{
70+
return sample_buffer_f32;
71+
}
72+
else
73+
{
74+
static_assert("No valid case for SampleT");
75+
}
76+
}
5077
};
5178

5279
const size_t FE_MAX_INSTANCES = 16;
@@ -73,6 +100,7 @@ struct FE_Parameters
73100
size_t instances = 1;
74101
Romset romset = Romset::MK2;
75102
std::optional<std::filesystem::path> rom_directory;
103+
FE_OutputFormat output_format = FE_OutputFormat::S16;
76104
};
77105

78106
bool FE_AllocateInstance(frontend_t& container, fe_emu_instance_t** result)
@@ -128,36 +156,50 @@ void FE_RouteMIDI(frontend_t& fe, uint8_t* first, uint8_t* last)
128156
}
129157
}
130158

131-
void FE_ReceiveSample(void* userdata, int32_t left, int32_t right)
159+
void FE_ReceiveSample_S16(void* userdata, int32_t left, int32_t right)
132160
{
133161
fe_emu_instance_t& fe = *(fe_emu_instance_t*)userdata;
134162

135163
AudioFrame<int16_t> frame;
136164
frame.left = (int16_t)clamp<int32_t>(left >> 15, INT16_MIN, INT16_MAX);
137165
frame.right = (int16_t)clamp<int32_t>(right >> 15, INT16_MIN, INT16_MAX);
138166

139-
fe.sample_buffer.Write(frame);
167+
fe.sample_buffer_s16.Write(frame);
168+
}
169+
170+
void FE_ReceiveSample_F32(void* userdata, int32_t left, int32_t right)
171+
{
172+
constexpr float DIV_REC = 1.0f / 536870912.0f;
173+
174+
fe_emu_instance_t& fe = *(fe_emu_instance_t*)userdata;
175+
176+
AudioFrame<float> frame;
177+
frame.left = (float)left * DIV_REC;
178+
frame.right = (float)right * DIV_REC;
179+
180+
fe.sample_buffer_f32.Write(frame);
140181
}
141182

183+
template <typename SampleT>
142184
void FE_AudioCallback(void* userdata, Uint8* stream, int len)
143185
{
144186
frontend_t& frontend = *(frontend_t*)userdata;
145187

146-
const size_t num_frames = len / sizeof(AudioFrame<int16_t>);
188+
const size_t num_frames = len / sizeof(AudioFrame<SampleT>);
147189
memset(stream, 0, len);
148190

149191
size_t renderable_count = num_frames;
150192
for (size_t i = 0; i < frontend.instances_in_use; ++i)
151193
{
152194
renderable_count = min(
153195
renderable_count,
154-
frontend.instances[i].sample_buffer.ReadableFrameCount()
196+
frontend.instances[i].StaticSelectBuffer<SampleT>().ReadableFrameCount()
155197
);
156198
}
157199

158200
for (size_t i = 0; i < frontend.instances_in_use; ++i)
159201
{
160-
frontend.instances[i].sample_buffer.ReadMix((AudioFrame<int16_t>*)stream, renderable_count);
202+
frontend.instances[i].StaticSelectBuffer<SampleT>().ReadMix((AudioFrame<SampleT>*)stream, renderable_count);
161203
}
162204
}
163205

@@ -189,42 +231,55 @@ static const char* audio_format_to_str(int format)
189231
return "UNK";
190232
}
191233

192-
bool FE_OpenAudio(frontend_t& fe, int deviceIndex, int pageSize, int pageNum)
234+
bool FE_OpenAudio(frontend_t& fe, const FE_Parameters& params)
193235
{
194236
SDL_AudioSpec spec = {};
195237
SDL_AudioSpec spec_actual = {};
196238

197-
fe.audio_page_size = (pageSize/2)*2; // must be even
198-
fe.audio_buffer_size = fe.audio_page_size*pageNum;
239+
fe.audio_page_size = (params.page_size / 2) * 2; // must be even
240+
fe.audio_buffer_size = fe.audio_page_size * params.page_num;
199241

200242
// TODO: we just assume the first instance has the correct mcu type for
201243
// all instances, which is PROBABLY correct but maybe we want to do some
202244
// crazy stuff like running different mcu types concurrently in the future?
203245
const mcu_t& mcu = fe.instances[0].emu.GetMCU();
204-
205-
spec.format = AUDIO_S16SYS;
246+
247+
switch (params.output_format)
248+
{
249+
case FE_OutputFormat::S16:
250+
spec.format = AUDIO_S16SYS;
251+
spec.callback = FE_AudioCallback<int16_t>;
252+
break;
253+
case FE_OutputFormat::F32:
254+
spec.format = AUDIO_F32SYS;
255+
spec.callback = FE_AudioCallback<float>;
256+
break;
257+
default:
258+
printf("Invalid output format\n");
259+
return false;
260+
}
206261
spec.freq = MCU_GetOutputFrequency(mcu);
207262
spec.channels = 2;
208-
spec.callback = FE_AudioCallback;
209263
spec.userdata = &fe;
210264
spec.samples = fe.audio_page_size / 4;
211-
265+
212266
int num = SDL_GetNumAudioDevices(0);
213267
if (num == 0)
214268
{
215269
printf("No audio output device found.\n");
216270
return false;
217271
}
218-
219-
if (deviceIndex < -1 || deviceIndex >= num)
272+
273+
int device_index = params.audio_device_index;
274+
if (device_index < -1 || device_index >= num)
220275
{
221276
printf("Out of range audio device index is requested. Default audio output device is selected.\n");
222-
deviceIndex = -1;
277+
device_index = -1;
223278
}
224-
225-
const char* audioDevicename = deviceIndex == -1 ? "Default device" : SDL_GetAudioDeviceName(deviceIndex, 0);
226-
227-
fe.sdl_audio = SDL_OpenAudioDevice(deviceIndex == -1 ? NULL : audioDevicename, 0, &spec, &spec_actual, 0);
279+
280+
const char* audioDevicename = device_index == -1 ? "Default device" : SDL_GetAudioDeviceName(device_index, 0);
281+
282+
fe.sdl_audio = SDL_OpenAudioDevice(device_index == -1 ? NULL : audioDevicename, 0, &spec, &spec_actual, 0);
228283
if (!fe.sdl_audio)
229284
{
230285
return false;
@@ -250,16 +305,22 @@ bool FE_OpenAudio(frontend_t& fe, int deviceIndex, int pageSize, int pageNum)
250305
return true;
251306
}
252307

308+
template <typename SampleT>
253309
void FE_RunInstance(fe_emu_instance_t& instance)
254310
{
255311
MCU_WorkThread_Lock(instance.emu.GetMCU());
256312
while (instance.running)
257313
{
258-
instance.sample_buffer.SetOversamplingEnabled(instance.emu.GetPCM().config_reg_3c & 0x40);
259-
if (instance.sample_buffer.IsFull())
314+
auto& sample_buffer = instance.StaticSelectBuffer<SampleT>();
315+
// TODO: this could probably be cleaned up, oversampling being a
316+
// property of the ringbuffer is kind of gross. It's really a property
317+
// of the read/write heads.
318+
sample_buffer.SetOversamplingEnabled(instance.emu.GetPCM().config_reg_3c & 0x40);
319+
320+
if (sample_buffer.IsFull())
260321
{
261322
MCU_WorkThread_Unlock(instance.emu.GetMCU());
262-
while (instance.sample_buffer.IsFull())
323+
while (sample_buffer.IsFull())
263324
{
264325
SDL_Delay(1);
265326
}
@@ -278,7 +339,18 @@ void FE_Run(frontend_t& fe)
278339
for (size_t i = 0; i < fe.instances_in_use; ++i)
279340
{
280341
fe.instances[i].running = true;
281-
fe.instances[i].thread = std::thread(FE_RunInstance, std::ref(fe.instances[i]));
342+
switch (fe.instances[i].format)
343+
{
344+
case FE_OutputFormat::S16:
345+
fe.instances[i].thread = std::thread(FE_RunInstance<int16_t>, std::ref(fe.instances[i]));
346+
break;
347+
case FE_OutputFormat::F32:
348+
fe.instances[i].thread = std::thread(FE_RunInstance<float>, std::ref(fe.instances[i]));
349+
break;
350+
default:
351+
fprintf(stderr, "warning: instance %" PRIu64 " has an invalid output format; it will not run\n", i);
352+
break;
353+
}
282354
}
283355

284356
while (working)
@@ -332,13 +404,26 @@ bool FE_CreateInstance(frontend_t& container, const std::filesystem::path& base_
332404
return false;
333405
}
334406

407+
fe->format = params.output_format;
408+
335409
if (!fe->emu.Init(EMU_Options { .enable_lcd = true }))
336410
{
337411
fprintf(stderr, "ERROR: Failed to init emulator.\n");
338412
return false;
339413
}
340414

341-
fe->emu.SetSampleCallback(FE_ReceiveSample, fe);
415+
switch (fe->format)
416+
{
417+
case FE_OutputFormat::S16:
418+
fe->emu.SetSampleCallback(FE_ReceiveSample_S16, fe);
419+
break;
420+
case FE_OutputFormat::F32:
421+
fe->emu.SetSampleCallback(FE_ReceiveSample_F32, fe);
422+
break;
423+
default:
424+
fprintf(stderr, "ERROR: Instance has an invalid output format.\n");
425+
return false;
426+
}
342427

343428
LCD_LoadBack(fe->emu.GetLCD(), base_path / "back.data");
344429

@@ -383,6 +468,7 @@ enum class FE_ParseError
383468
PortInvalid,
384469
AudioDeviceInvalid,
385470
RomDirectoryNotFound,
471+
FormatInvalid,
386472
};
387473

388474
const char* FE_ParseErrorStr(FE_ParseError err)
@@ -409,6 +495,8 @@ const char* FE_ParseErrorStr(FE_ParseError err)
409495
return "Audio device invalid";
410496
case FE_ParseError::RomDirectoryNotFound:
411497
return "Rom directory doesn't exist";
498+
case FE_ParseError::FormatInvalid:
499+
return "Output format invalid";
412500
}
413501
return "Unknown error";
414502
}
@@ -448,6 +536,26 @@ FE_ParseError FE_ParseCommandLine(int argc, char* argv[], FE_Parameters& result)
448536
return FE_ParseError::AudioDeviceInvalid;
449537
}
450538
}
539+
else if (reader.Any("-f", "--format"))
540+
{
541+
if (!reader.Next())
542+
{
543+
return FE_ParseError::UnexpectedEnd;
544+
}
545+
546+
if (reader.Arg() == "s16")
547+
{
548+
result.output_format = FE_OutputFormat::S16;
549+
}
550+
else if (reader.Arg() == "f32")
551+
{
552+
result.output_format = FE_OutputFormat::F32;
553+
}
554+
else
555+
{
556+
return FE_ParseError::FormatInvalid;
557+
}
558+
}
451559
else if (reader.Any("-b", "--buffer-size"))
452560
{
453561
if (!reader.Next())
@@ -582,6 +690,7 @@ void FE_Usage()
582690
printf(" -p, --port <port_number> Set MIDI port.\n");
583691
printf(" -a, --audio-device <device_number> Set Audio Device index.\n");
584692
printf(" -b, --buffer-size <page_size>:[page_count] Set Audio Buffer size.\n");
693+
printf(" -f, --format s16|f32 Set output format.\n");
585694
printf(" -n, --instances <count> Set number of emulator instances.\n");
586695
printf("\n");
587696
printf(" -d, --rom-directory <dir> Set directory to look for ROMs in.\n");
@@ -649,7 +758,7 @@ int main(int argc, char *argv[])
649758
}
650759
}
651760

652-
if (!FE_OpenAudio(frontend, params.audio_device_index, params.page_size, params.page_num))
761+
if (!FE_OpenAudio(frontend, params))
653762
{
654763
fprintf(stderr, "FATAL ERROR: Failed to open the audio stream.\n");
655764
fflush(stderr);
@@ -659,7 +768,19 @@ int main(int argc, char *argv[])
659768
for (size_t i = 0; i < frontend.instances_in_use; ++i)
660769
{
661770
fe_emu_instance_t& fe = frontend.instances[i];
662-
fe.sample_buffer = Ringbuffer<int16_t>(frontend.audio_buffer_size / 2);
771+
const size_t rb_size = frontend.audio_buffer_size / 2;
772+
switch (fe.format)
773+
{
774+
case FE_OutputFormat::S16:
775+
fe.sample_buffer_s16 = Ringbuffer<int16_t>(rb_size);
776+
break;
777+
case FE_OutputFormat::F32:
778+
fe.sample_buffer_f32 = Ringbuffer<float>(rb_size);
779+
break;
780+
default:
781+
fprintf(stderr, "ERROR: Instance has an invalid output format.\n");
782+
return 1;
783+
}
663784
}
664785

665786
if (!MIDI_Init(frontend, params.port))

‎src/ringbuffer.h

+15-2
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,21 @@ class Ringbuffer {
120120
size_t working_read_head = m_read_head;
121121
for (size_t i = 0; i < read_count; ++i)
122122
{
123-
dest[i].left = saturating_add(dest[i].left, m_frames[working_read_head].left);
124-
dest[i].right = saturating_add(dest[i].right, m_frames[working_read_head].right);
123+
// TODO: extract implementation selection
124+
if constexpr (std::is_same_v<T, int16_t>)
125+
{
126+
dest[i].left = saturating_add(dest[i].left, m_frames[working_read_head].left);
127+
dest[i].right = saturating_add(dest[i].right, m_frames[working_read_head].right);
128+
}
129+
else if constexpr (std::is_same_v<T, float>)
130+
{
131+
dest[i].left = dest[i].left + m_frames[working_read_head].left;
132+
dest[i].right = dest[i].right + m_frames[working_read_head].right;
133+
}
134+
else
135+
{
136+
static_assert(false, "No implementation for T in Ringbuffer::ReadMix");
137+
}
125138
working_read_head = (working_read_head + 1) % m_frames.size();
126139
}
127140
m_read_head = working_read_head;

0 commit comments

Comments
 (0)
Please sign in to comment.