Convert audio FIFOs to stereo sample pairs

This commit is contained in:
Ian Scott committed 2026-03-29 09:21:29 -06:00
1 parent db355ec4d6
commit 6afc44b2ad
4 files changed
+97 -80

No files matched your search

+13 -8
View File
@@ -26,20 +26,25 @@
#include <stdint.h>
#include <stdbool.h>
// Configuration
#define AUDIO_FIFO_SIZE 4096 // Must be power of 2
// Needs to be > 2*SAMPLES_PER_SECTOR (1176) to absorb
// slow FAT/USB sector reads without underrun (~93ms at 44100Hz)
// Packed stereo pair: left in low 16 bits, right in high 16 bits.
typedef union {
uint32_t data32;
int16_t data16[2];
} sample_pair;
// Each FIFO entry is a packed stereo pair.
typedef sample_pair audio_sample_t;
// CD audio FIFO size in stereo pairs — must be > SAMPLES_PER_SECTOR/2 (588
// stereo frames per CD sector) to absorb slow USB/FAT reads.
#define AUDIO_FIFO_SIZE 2048
#define AUDIO_FIFO_BITS (AUDIO_FIFO_SIZE - 1)
#define AUDIO_FIFO_START_THRESHOLD 512 // Start playback after 512 samples (~11ms); lower = faster post-seek response
#define AUDIO_FIFO_START_THRESHOLD 256
#ifdef __cplusplus
extern "C" {
#endif
// Audio sample type - adjust as needed for your audio format
typedef int16_t audio_sample_t;
// FIFO state
typedef enum {
FIFO_STATE_STOPPED,
+46 -46
View File
@@ -281,11 +281,11 @@ bool cdrom_audio_callback(cdrom_t *dev, uint32_t len) {
}
// --- Fill audio_fifo using data from audio_sector_buffer, reading new sectors as needed ---
// Loop until FIFO has 'len' samples, or is full, or an error/EoT occurs.
// Loop until FIFO has 'len' stereo pairs, or is full, or an error/EoT occurs.
while (fifo_level(&dev->audio_fifo) < len && ret) {
uint32_t samples_remaining_in_sector = SAMPLES_PER_SECTOR - dev->audio_sector_consumed_samples;
uint32_t pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs;
if (samples_remaining_in_sector == 0) { // Need to read a new sector
if (pairs_remaining_in_sector == 0) { // Need to read a new sector
if (dev->seek_pos < dev->cd_end) {
bool read_successful;
if (dev->audio_muted_soft) {
@@ -300,9 +300,8 @@ bool cdrom_audio_callback(cdrom_t *dev, uint32_t len) {
/* putchar('r'); */
cdrom_log("CD-ROM %i: Read LBA %08X successful\n", dev->id, dev->seek_pos);
dev->seek_pos++;
/* dev->audio_sector_total_samples = SAMPLES_PER_SECTOR; */
dev->audio_sector_consumed_samples = 0;
samples_remaining_in_sector = SAMPLES_PER_SECTOR; // Update for current iteration
dev->audio_sector_consumed_pairs = 0;
pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR; // Update for current iteration
} else {
cdrom_log("CD-ROM %i: Read LBA %08X failed\n", dev->id, dev->seek_pos);
dev->cd_status = CD_STATUS_STOPPED;
@@ -317,36 +316,35 @@ bool cdrom_audio_callback(cdrom_t *dev, uint32_t len) {
}
}
// At this point, if ret is still true, samples_remaining_in_sector should be > 0.
// At this point, if ret is still true, pairs_remaining_in_sector should be > 0.
uint32_t space_in_fifo = fifo_free_space(&dev->audio_fifo);
// Determine how many samples to transfer from staging to FIFO
uint32_t samples_to_transfer = (samples_remaining_in_sector < space_in_fifo) ?
samples_remaining_in_sector : space_in_fifo;
// Determine how many stereo pairs to transfer from staging to FIFO
uint32_t pairs_to_transfer = (pairs_remaining_in_sector < space_in_fifo) ?
pairs_remaining_in_sector : space_in_fifo;
/* printf("%u\n", samples_to_transfer); */
// Add samples from sector buffer to FIFO
/* printf("%u\n", pairs_to_transfer); */
// Add stereo pairs from sector buffer to FIFO
bool added_ok = fifo_add_samples(&dev->audio_fifo,
&dev->current_sector_samples[dev->audio_sector_consumed_samples],
/* &dev->current_sector_samples[dev->audio_sector_consumed_samples], */
samples_to_transfer);
&dev->current_sector_samples[dev->audio_sector_consumed_pairs],
pairs_to_transfer);
if (added_ok) {
/* putchar('y'); */
dev->audio_sector_consumed_samples += samples_to_transfer;
dev->audio_sector_consumed_pairs += pairs_to_transfer;
} else {
putchar('n');
// This implies fifo_add_samples failed even though we thought there was space.
// Could be a bug in fifo_free_space or fifo_add_samples, or a race condition in a threaded env (not here).
cdrom_log("CD-ROM %i: fifo_add_samples failed unexpectedly! Space was %u, tried to add %u.\n",
dev->id, space_in_fifo, samples_to_transfer);
dev->id, space_in_fifo, pairs_to_transfer);
ret = false; // Treat as an error
break;
}
} // End while (fifo_level < len && ret)
cdrom_log("CD-ROM %i: Audio cb. FIFO level: %u. Staging: %d/%d. Ret %d\n",
cdrom_log("CD-ROM %i: Audio cb. FIFO level: %u. Staging consumed: %d. Ret %d\n",
dev->id, fifo_level(&dev->audio_fifo),
dev->audio_sector_consumed_samples, dev->audio_sector_total_samples, ret);
dev->audio_sector_consumed_pairs, ret);
return ret;
}
#endif // USE_CD_AUDIO_FIFO
@@ -356,13 +354,14 @@ uint32_t cdrom_audio_callback_simple(cdrom_t *dev, int16_t *buffer, uint32_t len
return 0;
}
uint32_t samples_produced = 0;
uint32_t pairs_produced = 0;
uint32_t pairs_requested = len / 2;
// --- Fill buffer using data from audio_sector_buffer, reading new sectors as needed ---
// Loop until FIFO has 'len' samples, or is full, or an error/EoT occurs.
while (samples_produced < len) {
uint32_t samples_remaining_in_sector = SAMPLES_PER_SECTOR - dev->audio_sector_consumed_samples;
// Loop until buffer has 'len' samples, or an error/EoT occurs.
while (pairs_produced < pairs_requested) {
uint32_t pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs;
if (samples_remaining_in_sector == 0) { // Need to read a new sector
if (pairs_remaining_in_sector == 0) { // Need to read a new sector
if (dev->seek_pos < dev->cd_end) {
bool read_successful;
if (dev->audio_muted_soft) {
@@ -377,9 +376,8 @@ uint32_t cdrom_audio_callback_simple(cdrom_t *dev, int16_t *buffer, uint32_t len
/* putchar('r'); */
cdrom_log("CD-ROM %i: Read LBA %08X successful\n", dev->id, dev->seek_pos);
dev->seek_pos++;
/* dev->audio_sector_total_samples = SAMPLES_PER_SECTOR; */
dev->audio_sector_consumed_samples = 0;
samples_remaining_in_sector = SAMPLES_PER_SECTOR; // Update for current iteration
dev->audio_sector_consumed_pairs = 0;
pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR; // Update for current iteration
} else {
cdrom_log("CD-ROM %i: Read LBA %08X failed\n", dev->id, dev->seek_pos);
dev->cd_status = CD_STATUS_STOPPED;
@@ -392,25 +390,27 @@ uint32_t cdrom_audio_callback_simple(cdrom_t *dev, int16_t *buffer, uint32_t len
}
}
// At this point, if ret is still true, samples_remaining_in_sector should be > 0.
uint32_t space_in_buffer = len - samples_produced;
// Determine how many samples to transfer from staging to buffer
uint32_t samples_to_transfer = (samples_remaining_in_sector < space_in_buffer) ?
samples_remaining_in_sector : space_in_buffer;
// At this point, pairs_remaining_in_sector should be > 0.
uint32_t space_in_buffer = pairs_requested - pairs_produced;
// Determine how many stereo pairs to transfer from staging to buffer
uint32_t pairs_to_transfer = (pairs_remaining_in_sector < space_in_buffer) ?
pairs_remaining_in_sector : space_in_buffer;
/* printf("%u\n", samples_to_transfer); */
// Add samples from sector buffer to FIFO
//memcpy(buffer + samples_produced, &dev->current_sector_samples[dev->audio_sector_consumed_samples], samples_to_transfer * sizeof(int16_t));
for (uint32_t i = 0; i < samples_to_transfer; i++)
/* printf("%u\n", pairs_to_transfer); */
// Add samples from sector buffer to output buffer
//memcpy(buffer + pairs_produced * 2, &dev->current_sector_samples[dev->audio_sector_consumed_pairs], pairs_to_transfer * sizeof(sample_pair));
for (uint32_t i = 0; i < pairs_to_transfer; i++)
{
int32_t sample = dev->current_sector_samples[dev->audio_sector_consumed_samples + i];
buffer[samples_produced + i] = (int16_t)scale_sample(sample, cd_audio_volume, 0);
sample_pair sp = dev->current_sector_samples[dev->audio_sector_consumed_pairs + i];
buffer[(pairs_produced + i) * 2] = (int16_t)scale_sample(sp.data16[0], cd_audio_volume, 0);
buffer[(pairs_produced + i) * 2 + 1] = (int16_t)scale_sample(sp.data16[1], cd_audio_volume, 0);
}
samples_produced += samples_to_transfer;
dev->audio_sector_consumed_samples += samples_to_transfer;
} // End while (fifo_level < len && ret)
pairs_produced += pairs_to_transfer;
dev->audio_sector_consumed_pairs += pairs_to_transfer;
}
uint32_t samples_produced = pairs_produced * 2;
if (!pad) {
return samples_produced;
} else {
@@ -551,12 +551,12 @@ uint8_t cdrom_get_subq(cdrom_t *dev,uint8_t *b) {
/* seek_pos points to the next sector to be READ, not the one currently
* audible. Subtract the number of sectors already buffered in the FIFO
* so the reported position matches what the listener actually hears.
* Each sector holds SAMPLES_PER_SECTOR stereo pairs = 2*SAMPLES_PER_SECTOR
* individual int16 values. Guard against underflow when the FIFO is
* empty or seek_pos is at the start of the track. */
* Each sector holds STEREO_SAMPLES_PER_SECTOR stereo pairs.
* Guard against underflow when the FIFO is empty or seek_pos is at the
* start of the track. */
#if USE_CD_AUDIO_FIFO
{
uint32_t buffered_sectors = (dev->audio_fifo.write_idx - dev->audio_fifo.read_idx) / (SAMPLES_PER_SECTOR * 2);
uint32_t buffered_sectors = fifo_level(&dev->audio_fifo) / STEREO_SAMPLES_PER_SECTOR;
uint32_t report_pos = (buffered_sectors < dev->seek_pos) ?
(dev->seek_pos - buffered_sectors) : 0;
dev->ops->get_subchannel(dev, report_pos, &subc);
@@ -633,7 +633,7 @@ cdrom_global_init(void)
/* Clear the global data. */
memset(&cdrom, 0x00, sizeof(cdrom));
cdrom.error_str = cdrom_errorstr_get();
cdrom.current_sector_samples = (int16_t*)cdrom.audio_sector_buffer;
cdrom.current_sector_samples = (sample_pair*)cdrom.audio_sector_buffer;
set_volume(CMD_CDVOL);
}
+3 -3
View File
@@ -55,6 +55,7 @@
#define RAW_SECTOR_SIZE 2352
#define SAMPLES_PER_SECTOR 1176
#define STEREO_SAMPLES_PER_SECTOR 588
#define STAT_READY 0x01
#define STAT_PLAY 0x08
@@ -198,9 +199,8 @@ typedef struct cdrom {
// int16_t cd_buffer[BUF_SIZE];
uint8_t audio_sector_buffer[RAW_SECTOR_SIZE];
int16_t *current_sector_samples; // Convenience pointer: (int16_t*)audio_sector_buffer
int audio_sector_total_samples; // Samples available in current_sector_samples after a read
int audio_sector_consumed_samples; // Samples consumed from current_sector_samples
sample_pair *current_sector_samples; // Convenience pointer: (sample_pair*)audio_sector_buffer
int audio_sector_consumed_pairs; // Stereo pairs consumed from current_sector_samples
#if USE_CD_AUDIO_FIFO
audio_fifo_t audio_fifo;
+35 -23
View File
@@ -98,7 +98,26 @@ static constexpr uint32_t fixed_ratio(uint16_t a, uint16_t b) {
}
static constexpr uint32_t opl_ratio = fixed_ratio(49716, 44100);
static audio_fifo_t opl_out_fifo;
// OPL FIFO — smaller than CD audio FIFO since producer and consumer are on
// the same core. 256 stereo pairs ~= 5.8ms at 44100Hz.
#define OPL_FIFO_SIZE 256
#define OPL_FIFO_BITS (OPL_FIFO_SIZE - 1)
static struct {
sample_pair buffer[OPL_FIFO_SIZE];
volatile uint32_t write_idx;
volatile uint32_t read_idx;
volatile fifo_state_t state;
} opl_out_fifo;
static inline uint32_t opl_fifo_free_space() {
return OPL_FIFO_SIZE - (opl_out_fifo.write_idx - opl_out_fifo.read_idx);
}
static inline void opl_fifo_add_sample(sample_pair sample) {
opl_out_fifo.buffer[opl_out_fifo.write_idx & OPL_FIFO_BITS] = sample;
opl_out_fifo.write_idx++;
}
// Stereo OPL resampler state.
// For stereo backends (dbopl, ymfm), OPL_Pico_stereo provides true stereo.
@@ -137,11 +156,6 @@ static void opl_resample_tick()
}
}
typedef union {
uint32_t data32;
int16_t data16[2];
} sample_pair;
// Setup values for audio sample clock
// 8390 clock cycles per sample (370MHz / 8390 ~= 44100Hz)
static constexpr uint32_t clocks_per_sample_minus_one = (SYS_CLK_HZ / 44100) - 1;
@@ -175,22 +189,21 @@ void audio_sample_handler(void) {
#endif
#ifdef CDROM
if (cd_fifo->state != FIFO_STATE_STOPPED && cd_fifo->write_idx - cd_fifo->read_idx >= 2) {
uint32_t idx = cd_fifo->read_idx;
sample_l += scale_sample(cd_fifo->buffer[idx & AUDIO_FIFO_BITS], volume.cd_audio[0], 0);
sample_r += scale_sample(cd_fifo->buffer[(idx + 1) & AUDIO_FIFO_BITS], volume.cd_audio[1], 0);
cd_fifo->read_idx = idx + 2;
if (cd_fifo->state != FIFO_STATE_STOPPED && cd_fifo->write_idx - cd_fifo->read_idx >= 1) {
sample_pair cd = cd_fifo->buffer[cd_fifo->read_idx & AUDIO_FIFO_BITS];
cd_fifo->read_idx++;
if (cd_fifo->write_idx == cd_fifo->read_idx) cd_fifo->state = FIFO_STATE_STOPPED;
sample_l += scale_sample(cd.data16[0], volume.cd_audio[0], 0);
sample_r += scale_sample(cd.data16[1], volume.cd_audio[1], 0);
}
#endif
// Read OPL stereo pair from FIFO — always consume 2 samples (L then R).
if (opl_out_fifo.state != FIFO_STATE_STOPPED && opl_out_fifo.write_idx - opl_out_fifo.read_idx >= 2) {
uint32_t idx = opl_out_fifo.read_idx;
sample_l += scale_sample(opl_out_fifo.buffer[idx & AUDIO_FIFO_BITS], volume.opl[0], 0);
sample_r += scale_sample(opl_out_fifo.buffer[(idx + 1) & AUDIO_FIFO_BITS], volume.opl[1], 0);
opl_out_fifo.read_idx = idx + 2;
if (opl_out_fifo.state != FIFO_STATE_STOPPED && opl_out_fifo.write_idx - opl_out_fifo.read_idx >= 1) {
sample_pair opl = opl_out_fifo.buffer[opl_out_fifo.read_idx & OPL_FIFO_BITS];
opl_out_fifo.read_idx++;
if (opl_out_fifo.write_idx == opl_out_fifo.read_idx) opl_out_fifo.state = FIFO_STATE_STOPPED;
sample_l += scale_sample(opl.data16[0], volume.opl[0], 0);
sample_r += scale_sample(opl.data16[1], volume.opl[1], 0);
}
#ifdef SOUND_SB
@@ -266,7 +279,7 @@ void play_adlib() {
for (;;) {
#if CDROM
cdrom_audio_callback(&cdrom, AUDIO_FIFO_SIZE - SAMPLES_PER_SECTOR);
cdrom_audio_callback(&cdrom, AUDIO_FIFO_SIZE - STEREO_SAMPLES_PER_SECTOR);
#endif
#if 0
@@ -283,14 +296,13 @@ void play_adlib() {
// Process DSP commands
sbdsp_process();
// Generate OPL stereo pairs and add to output FIFO (interleaved L, R).
// Need 2 free slots per output sample.
// Generate OPL stereo pairs and add to output FIFO.
for (uint32_t opl_i = 0;
opl_i < OPL_FILL_PER_ITER && fifo_free_space(&opl_out_fifo) >= 2;
opl_i < OPL_FILL_PER_ITER && opl_fifo_free_space() >= 1;
opl_i++) {
opl_resample_tick();
fifo_add_sample(&opl_out_fifo, (int16_t)opl_resamp_l);
fifo_add_sample(&opl_out_fifo, (int16_t)opl_resamp_r);
sample_pair p = {.data16 = {(int16_t)opl_resamp_l, (int16_t)opl_resamp_r}};
opl_fifo_add_sample(p);
}
#ifdef USB_STACK
// Service TinyUSB events