Help needed with SDL audio

Hello,

I am working on a SDL port of Charles Mac Donald’s SMS Plus (emulator
for Sega Master System & Game Gear). The preliminary version is
available at

http://www.student.oulu.fi/~jakarppi/spnew.zip

I would like some advice on the audio output, which currently doesn’t
work very well.

Regards,
Jari__________________________________________________
Do You Yahoo!?
Make international calls for as low as $.04/minute with Yahoo! Messenger
http://phonecard.yahoo.com/

I don’t know what your exact problem is (I haven’t tried to run the
code), but from a quick look at it, the main issue I can see is that
there are two critical interfaces in the chain; the one between the
emulator engine and the SDL audio callback, and the one between the SDL
audio callback and the driver/hardware.

The easiest way in general to get reliable low latency output is to run
the entire audio engine inside the SDL audio callback, so that audio
mixing (which must meet all deadlines to avoid drop-outs) can run
undisturbed. That way, if the main loop (with the graphics etc) stalls,
it doesn’t have to affect audio mixing and output. The main program just
sends “commands” to the audio engine, that will be picked up and
processed for the next buffer. (Very similar to how a MIDI sound
generator is controlled.)

The problem with such a design in conjuction with an emulator is that if
you move the sound chip emulation to the audio callback (which runs in a
callback from the driver, or in a separate thread), real world timing
affects the emulated CPU<->sound timing.

One solution that might work is to add timestamps to the commands sent to
the audio engine. If commands should arrive late (due to the main loop
stalling), audio will keep playing in it’s current state, and the late
commands will be processed as soon as they arrive. Usually sounds better
than total drop-outs…

However, there could be problems with sound quality if there’s massive
traffic from the CPU to the sound chip - delays will case stalls in the
middle of sound effects, and affect all sound effects playing. Further,
running sound chip and CPU emulation in separate threads is not at all
possible if the CPU reads critical information from the sound chip.
(The CPU->sound chip communication must be one-way.)

Now, if the hardware emulated hardware has a dedicated CPU for sound (as
is the case with many arcade machines), you could move emulation of that
CPU to the audio thread as well. That would ensure that sound effects
(which usually involve frenetic, time critical fiddling with sound chip
control registers :slight_smile: are generated properly - delayed commands would
just cause sound effects to start later.

If the original hardware doesn’t have a dedicated sound CPU, it might
still be possible to use a variation the above strategy: If the sound
code runs in interrupt handlers generated by a specific timer (ie one
that comes with the sound hardware), it might be possible to run only
those interrupts in a “main CPU” emulator from within the audio callback
(as if the audio callback was generating those hardware interrupts),
while keeping all other “main CPU” emulation in the main loop.

If games on your emulated system use various different ways of driving
their sound code, and need bidirectional CPU<->sound chip
communication, then you’re in trouble… You have to run the entire
emulator in real time with sufficient timing accuracy to drive the sound
directly, which moves the problem over to the graphics emulation side,
where you have a lot more data to shuffle around.

I’m not suggesting that any of the proposed solutions would be trivial to
apply in the case of an emulator! :slight_smile: Mixing hard and soft real time with
good results is inherently complicated in any non-trivial setting.
Emulators are probably worse, as you can’t redesign the game<->audio
interaction to suit the way your host platform works.

//David Olofson — Programmer, Reologica Instruments AB

.- M A I A -------------------------------------------------.
| Multimedia Application Integration Architecture |
| A Free/Open Source Plugin API for Professional Multimedia |
----------------------------> http://www.linuxdj.com/maia -' .- David Olofson -------------------------------------------. | Audio Hacker - Open Source Advocate - Singer - Songwriter |--------------------------------------> david at linuxdj.com -'On Tuesday 28 August 2001 09:50, you wrote:

Hello,

I am working on a SDL port of Charles Mac Donald’s SMS Plus (emulator
for Sega Master System & Game Gear). The preliminary version is
available at

http://www.student.oulu.fi/~jakarppi/spnew.zip

I would like some advice on the audio output, which currently doesn’t
work very well.

when trying to build and link a program using sdl, I get the following
linker error:
/usr/bin/ld: Undefined symbols:
_SDL_main
sdl-config --cflags and --libs returns:
-I/usr/local/include -I/usr/local/include/SDL -D_THREAD_SAFE -F/System/Library/
Frameworks/Carbon.framework
-L/usr/local/lib -lSDLmain -lSDL -framework OpenGL -framework AGL
-framework Carbon
is something needed missing or out of order?

Thanks in advance,

Jayson Baird
jayson.baird at ndsu.nodak.edu

Hm, I could have been more descriptive about the problem with audio
output. I will paste here the instructions written by the author of the
emulation core.

Here’s a brief overview of how to use all this:

  • do machine dependant initialization (audio, video, init input, etc.)
  • set up bitmap structure
  • set up cart structure (load game)
  • call system_init()
  • if snd.enabled is set, we can use sound
  • load sram data if it exists for the game
    in a loop:
    • update input structure based on gamepad/keyboard
    • call sms_frame()
    • play sound using ‘snd.buffer’
    • copy ‘bitmap.data’ to the video display
    • quit if needed
  • save sram data if the game used it
  • call system_shutdown()

This plan is very clear, except for the sound part. The dos port of the
emulator simply pushes the data in sound buffers to the soundcard. With
SDL, the audio callback is supposed to get more data when it is needed.
I don’t understand how to get the emulation core work in synch with the
audio callback. I can get something played, but it doesn’t sound right
(I don’t know enough english to describe it).

I’m hoping someone with more experience could look at the code and give
me some advice. David proposed some good solutions, for example running
the audio emulation inside callback seems to be used in some emulators
(e.g. gnuboy). However, I don’t know a lot of the internal workings of
the emulation core, so this solution is beyond me.

Perhaps I’ll ignore the sound emulation for now and implement the other
features like joystick support first.

Regards,
Jari__________________________________________________
Do You Yahoo!?
Make international calls for as low as $.04/minute with Yahoo! Messenger
http://phonecard.yahoo.com/

Hm, I could have been more descriptive about the problem with audio
output. I will paste here the instructions written by the author of the
emulation core.

Here’s a brief overview of how to use all this:

  • do machine dependant initialization (audio, video, init input, etc.)
  • set up bitmap structure
  • set up cart structure (load game)
  • call system_init()
  • if snd.enabled is set, we can use sound
  • load sram data if it exists for the game
    in a loop:
    • update input structure based on gamepad/keyboard
    • call sms_frame()
    • play sound using ‘snd.buffer’
    • copy ‘bitmap.data’ to the video display
    • quit if needed
  • save sram data if the game used it
  • call system_shutdown()

This plan is very clear, except for the sound part. The dos port of the
emulator simply pushes the data in sound buffers to the soundcard. With
SDL, the audio callback is supposed to get more data when it is needed.
I don’t understand how to get the emulation core work in synch with the
audio callback.

This is the problem; you can’t. The emulator runs at one speed, and the
audio card at another, and even if you had full control over the refresh
and sample rates, you’d still have to deal with drift problems.

I’m hoping someone with more experience could look at the code and give
me some advice. David proposed some good solutions, for example running
the audio emulation inside callback seems to be used in some emulators
(e.g. gnuboy). However, I don’t know a lot of the internal workings of
the emulation core, so this solution is beyond me.

I looked at the sound chip emulators, and they seems pretty simple. They
have a “write_register()” type call, and a “generate_output()” type call

  • no reading from the chips is supported. (OPLRead() is never used.)

Wrap the “write_register()” call so that the rest of the emulator
actually writes timestamped commands to a lock-free FIFO, for example my
little sfifo hack. (Rather than directly fiddling with the inner workins
of the sound emulators - that would wreck timing and introduce a ton of
multithreading related issues.)

For example; emulator call:

void ym2413_write(int chip, int address, int data);

your wrapper:

typedef struct
{
	int timestamp;
	int chip;
	int address;
	int data;
} ym2413_command_t;

sfifo_t ym2413_fifo;

void b_ym2413_write(int chip, int address, int data, int timestamp)
{
	ym2413_command_t cmd;
	cmd.timestamp = timestamp;
	cmd.chip = chip;
	cmd.address = address;
	cmd.data = data;
	if(sfifo_space(&ym2413_fifo) >= sizeof(cmd))
		sfifo_write(&ym2413_fifo, cmd, sizeof(cmd));
	else
		fprintf(stderr, "WARNING: Command FIFO full!\n");
}

Now, move the sound chip emulator into the SDL audio callback, and set it
up to generate one buffer of the requested size each time it’s called.
Inside the audio callback you’d basically do this to get one buffer with
(if you like) single sample timing accuracy:

while(1)
{
	ym2413_command_t cmd;
	int todo;
	if(sfifo_used(&ym2413_fifo) >= sizeof(cmd))
	{
		sfifo_read(&ym2413_fifo, cmd, sizeof(cmd));
		todo = cmd.timestamp - tnow;
	}
	else
	{
		todo = total_buffer_size - tnow;
		chip = -1;
	}
	if(todo)
	{
		for(<all chips>)
			Update(<chip>, buffer, todo);
		buffer += todo;
		tnow += todo;
	}
	if(cmd.chip != -1)
		ym2413_write(cmd.chip, cmd.address, cmd.data);
}

//David Olofson — Programmer, Reologica Instruments AB

.- M A I A -------------------------------------------------.
| Multimedia Application Integration Architecture |
| A Free/Open Source Plugin API for Professional Multimedia |
----------------------------> http://www.linuxdj.com/maia -' .- David Olofson -------------------------------------------. | Audio Hacker - Open Source Advocate - Singer - Songwriter |--------------------------------------> david at linuxdj.com -'On Wednesday 29 August 2001 11:00, Jari Karppinen wrote: