Files
blastem/render_audio.h
WizzardSK f1bc932187 libretro: mix audio the way the standalone build does (#70)
* libretro: do not ask the emulator to stop when presenting a frame it did not

A frame ends when the emulator asks to be let go: render_framebuffer_updated()
hands the frontend the picture and calls system_request_exit(), which is what
returns control from retro_run(). A system that comes back without presenting
anything - the media player, which has no video at all - would leave the
frontend without a frame, so this file presents one on its behalf, and it did
that by calling render_framebuffer_updated() as well.

That request to stop does not expire. It is still standing when the next
retro_run() enters the emulation loop, which is written as

	while (!gen->m68k->should_return)

so the loop ends before a single instruction runs, clears the flag, and returns
with nothing presented - which makes this file present a frame again, and ask
the emulator to stop again. One missed frame and the machine never runs again.

A Mega Drive never hit it because it presents a frame on the very first call. A
Sega CD does not: the call that starts it returns while the BIOS is still
bringing the machine up, and the deadlock closes over it. Sonic CD sat on a
black screen with both 68000s frozen - the main one at 420 cycles, the sub one
still held in reset - for as long as it was left running.

Presenting and asking to stop are two things, so separate them: the frame this
file owes the frontend goes out through present_framebuffer(), and only the
emulator's own frame end asks for the exit. The media player does not need the
request either; it sets should_return itself in the IS_LIB build.

Sonic CD now boots and runs. Measured over 600 frames: 95.7% of the audio the
declared sample rate calls for, where before there was none at all, and the
picture changes rather than staying the black frame it started on.

* libretro: mix audio the way the standalone build does

render_do_audio_ready() was the whole mixer: it swapped a source's buffers and,
once every source had one ready, mixed four frames and handed them over. Four is
what a source's buffer holds in the sync-to-audio path, so with the FM and the
PSG - clocked from the same master clock, resampled to the same rate - the two
arrive together and every buffer is consumed as it is filled.

Nothing else about a Mega Drive holds. A Sega CD adds CD-DA and the PCM chip,
which fill at a pace of their own, so a source can fill twice before another
fills once. The second fill swaps over a buffer nobody mixed: four frames gone,
which is what a click is. The standalone does not have this problem because its
emulation thread waits on the audio callback there, or breaks the wait with
audio_deadlock_hack(), and neither is available to a core that must return from
retro_run().

Since a frame here ends when the VDP hands over a framebuffer, audio does not
have to pace anything, so use the arrangement render_audio.c already has for
that case: report render_is_audio_sync() as false, give each source a ring
buffer of its own, and let render_do_audio_ready() do nothing but publish what
has been written. retro_run() then mixes with mix_and_convert() and hands the
frontend a batch, the same call the standalone's audio callback makes.

How much to mix is what every source that has anything can cover, which
audio_buffered() reports; the rest stays queued rather than being mixed past
what a source has written. Sources with nothing at all are left out of that
count, or a CD-DA source with no track playing - which never writes anything,
ever - would hold up the mixing forever, and taking the busiest source instead
pads the ones behind it with silence.

Measured over 600 frames each. Sonic 1: 100.0% of the frames the declared
sample rate calls for, before and after, in 1200 batches rather than 133305.
Sonic CD, which has all four sources going: 95.7% before, 99.8% after, and no
silent frame in either. Neither shows a step between samples larger than a real
waveform takes.

It also fixes the soft reset, which was reported separately as "reset the
game freezes". Measured with the same harness, Sonic 1, 180 frames either
side of retro_reset(): before this change the core delivers 159876 audio
frames before the reset and 0 after it, and the picture stops changing
altogether - the frontend still gets its frames, but every one of them is
the same. After this change it delivers 159106 audio frames after the
reset and the picture goes on changing.

That follows from the same cause. The old path mixes only when
all_sources_ready() says every source has a buffer, and it reports
render_is_audio_sync() as true, so the emulator throttles itself to audio
being consumed. A reset leaves one source that never fills again, the AND
never holds, nothing is consumed, and the emulation stops waiting for an
audio callback that cannot come. Mixing what each source actually has, and
not pacing on audio at all, removes both halves of that.
2026-09-19 12:47:42 -05:00

62 lines
2.1 KiB
C

#ifndef RENDER_AUDIO_H_
#define RENDER_AUDIO_H_
#include <stdint.h>
typedef enum {
RENDER_AUDIO_S16,
RENDER_AUDIO_FLOAT,
RENDER_AUDIO_UNKNOWN
} render_audio_format;
typedef struct {
const char *name;
void *opaque;
int16_t *front;
int16_t *back;
double dt;
uint64_t buffer_fraction;
uint64_t buffer_inc;
float gain_mult;
uint32_t buffer_pos;
uint32_t read_start;
uint32_t read_end;
uint32_t lowpass_alpha;
uint32_t mask;
int16_t last_left;
int16_t last_right;
uint8_t num_channels;
uint8_t front_populated;
} audio_source;
//public interface
audio_source *render_audio_source(const char *name, uint64_t master_clock, uint64_t sample_divider, uint8_t channels);
void render_audio_source_gaindb(audio_source *src, float gain);
void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider);
void render_put_mono_sample(audio_source *src, int16_t value);
void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right);
void render_pause_source(audio_source *src);
void render_resume_source(audio_source *src);
void render_free_source(audio_source *src);
void render_end_audio(void);
void render_save_audio(char *path);
//interface for render backends
void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size);
int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out);
uint32_t audio_buffered(void);
uint8_t all_sources_ready(void);
void render_audio_adjust_speed(float adjust_ratio);
//to be implemented by render backend
uint8_t render_is_audio_sync(void);
void render_buffer_consumed(audio_source *src);
void *render_new_audio_opaque(void);
void render_free_audio_opaque(void *opaque);
void render_lock_audio(void);
void render_unlock_audio(void);
uint32_t render_min_buffered(void);
uint32_t render_audio_syncs_per_sec(void);
void render_audio_created(audio_source *src);
void render_do_audio_ready(audio_source *src);
void render_source_paused(audio_source *src, uint8_t remaining_sources);
void render_source_resumed(audio_source *src);
#endif //RENDER_AUDIO_H_