41
41
#include < optional>
42
42
43
43
using Ringbuffer_S16 = Ringbuffer<int16_t >;
44
+ using Ringbuffer_F32 = Ringbuffer<float >;
45
+
46
+ enum class FE_OutputFormat
47
+ {
48
+ S16,
49
+ F32,
50
+ };
44
51
45
52
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
+ }
50
77
};
51
78
52
79
const size_t FE_MAX_INSTANCES = 16 ;
@@ -73,6 +100,7 @@ struct FE_Parameters
73
100
size_t instances = 1 ;
74
101
Romset romset = Romset::MK2;
75
102
std::optional<std::filesystem::path> rom_directory;
103
+ FE_OutputFormat output_format = FE_OutputFormat::S16;
76
104
};
77
105
78
106
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)
128
156
}
129
157
}
130
158
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)
132
160
{
133
161
fe_emu_instance_t & fe = *(fe_emu_instance_t *)userdata;
134
162
135
163
AudioFrame<int16_t > frame;
136
164
frame.left = (int16_t )clamp<int32_t >(left >> 15 , INT16_MIN, INT16_MAX);
137
165
frame.right = (int16_t )clamp<int32_t >(right >> 15 , INT16_MIN, INT16_MAX);
138
166
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);
140
181
}
141
182
183
+ template <typename SampleT>
142
184
void FE_AudioCallback (void * userdata, Uint8* stream, int len)
143
185
{
144
186
frontend_t & frontend = *(frontend_t *)userdata;
145
187
146
- const size_t num_frames = len / sizeof (AudioFrame<int16_t >);
188
+ const size_t num_frames = len / sizeof (AudioFrame<SampleT >);
147
189
memset (stream, 0 , len);
148
190
149
191
size_t renderable_count = num_frames;
150
192
for (size_t i = 0 ; i < frontend.instances_in_use ; ++i)
151
193
{
152
194
renderable_count = min (
153
195
renderable_count,
154
- frontend.instances [i].sample_buffer .ReadableFrameCount ()
196
+ frontend.instances [i].StaticSelectBuffer <SampleT>() .ReadableFrameCount ()
155
197
);
156
198
}
157
199
158
200
for (size_t i = 0 ; i < frontend.instances_in_use ; ++i)
159
201
{
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);
161
203
}
162
204
}
163
205
@@ -189,42 +231,55 @@ static const char* audio_format_to_str(int format)
189
231
return " UNK" ;
190
232
}
191
233
192
- bool FE_OpenAudio (frontend_t & fe, int deviceIndex, int pageSize, int pageNum )
234
+ bool FE_OpenAudio (frontend_t & fe, const FE_Parameters& params )
193
235
{
194
236
SDL_AudioSpec spec = {};
195
237
SDL_AudioSpec spec_actual = {};
196
238
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 ;
199
241
200
242
// TODO: we just assume the first instance has the correct mcu type for
201
243
// all instances, which is PROBABLY correct but maybe we want to do some
202
244
// crazy stuff like running different mcu types concurrently in the future?
203
245
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
+ }
206
261
spec.freq = MCU_GetOutputFrequency (mcu);
207
262
spec.channels = 2 ;
208
- spec.callback = FE_AudioCallback;
209
263
spec.userdata = &fe;
210
264
spec.samples = fe.audio_page_size / 4 ;
211
-
265
+
212
266
int num = SDL_GetNumAudioDevices (0 );
213
267
if (num == 0 )
214
268
{
215
269
printf (" No audio output device found.\n " );
216
270
return false ;
217
271
}
218
-
219
- if (deviceIndex < -1 || deviceIndex >= num)
272
+
273
+ int device_index = params.audio_device_index ;
274
+ if (device_index < -1 || device_index >= num)
220
275
{
221
276
printf (" Out of range audio device index is requested. Default audio output device is selected.\n " );
222
- deviceIndex = -1 ;
277
+ device_index = -1 ;
223
278
}
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 );
228
283
if (!fe.sdl_audio )
229
284
{
230
285
return false ;
@@ -250,16 +305,22 @@ bool FE_OpenAudio(frontend_t& fe, int deviceIndex, int pageSize, int pageNum)
250
305
return true ;
251
306
}
252
307
308
+ template <typename SampleT>
253
309
void FE_RunInstance (fe_emu_instance_t & instance)
254
310
{
255
311
MCU_WorkThread_Lock (instance.emu .GetMCU ());
256
312
while (instance.running )
257
313
{
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 ())
260
321
{
261
322
MCU_WorkThread_Unlock (instance.emu .GetMCU ());
262
- while (instance. sample_buffer .IsFull ())
323
+ while (sample_buffer.IsFull ())
263
324
{
264
325
SDL_Delay (1 );
265
326
}
@@ -278,7 +339,18 @@ void FE_Run(frontend_t& fe)
278
339
for (size_t i = 0 ; i < fe.instances_in_use ; ++i)
279
340
{
280
341
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
+ }
282
354
}
283
355
284
356
while (working)
@@ -332,13 +404,26 @@ bool FE_CreateInstance(frontend_t& container, const std::filesystem::path& base_
332
404
return false ;
333
405
}
334
406
407
+ fe->format = params.output_format ;
408
+
335
409
if (!fe->emu .Init (EMU_Options { .enable_lcd = true }))
336
410
{
337
411
fprintf (stderr, " ERROR: Failed to init emulator.\n " );
338
412
return false ;
339
413
}
340
414
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
+ }
342
427
343
428
LCD_LoadBack (fe->emu .GetLCD (), base_path / " back.data" );
344
429
@@ -383,6 +468,7 @@ enum class FE_ParseError
383
468
PortInvalid,
384
469
AudioDeviceInvalid,
385
470
RomDirectoryNotFound,
471
+ FormatInvalid,
386
472
};
387
473
388
474
const char * FE_ParseErrorStr (FE_ParseError err)
@@ -409,6 +495,8 @@ const char* FE_ParseErrorStr(FE_ParseError err)
409
495
return " Audio device invalid" ;
410
496
case FE_ParseError::RomDirectoryNotFound:
411
497
return " Rom directory doesn't exist" ;
498
+ case FE_ParseError::FormatInvalid:
499
+ return " Output format invalid" ;
412
500
}
413
501
return " Unknown error" ;
414
502
}
@@ -448,6 +536,26 @@ FE_ParseError FE_ParseCommandLine(int argc, char* argv[], FE_Parameters& result)
448
536
return FE_ParseError::AudioDeviceInvalid;
449
537
}
450
538
}
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
+ }
451
559
else if (reader.Any (" -b" , " --buffer-size" ))
452
560
{
453
561
if (!reader.Next ())
@@ -582,6 +690,7 @@ void FE_Usage()
582
690
printf (" -p, --port <port_number> Set MIDI port.\n " );
583
691
printf (" -a, --audio-device <device_number> Set Audio Device index.\n " );
584
692
printf (" -b, --buffer-size <page_size>:[page_count] Set Audio Buffer size.\n " );
693
+ printf (" -f, --format s16|f32 Set output format.\n " );
585
694
printf (" -n, --instances <count> Set number of emulator instances.\n " );
586
695
printf (" \n " );
587
696
printf (" -d, --rom-directory <dir> Set directory to look for ROMs in.\n " );
@@ -649,7 +758,7 @@ int main(int argc, char *argv[])
649
758
}
650
759
}
651
760
652
- if (!FE_OpenAudio (frontend, params. audio_device_index , params. page_size , params. page_num ))
761
+ if (!FE_OpenAudio (frontend, params))
653
762
{
654
763
fprintf (stderr, " FATAL ERROR: Failed to open the audio stream.\n " );
655
764
fflush (stderr);
@@ -659,7 +768,19 @@ int main(int argc, char *argv[])
659
768
for (size_t i = 0 ; i < frontend.instances_in_use ; ++i)
660
769
{
661
770
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
+ }
663
784
}
664
785
665
786
if (!MIDI_Init (frontend, params.port ))
0 commit comments