From 54dbdfa9bd5f121f4fcda360b53c323c3c8971d4 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Thu, 9 Apr 2026 08:51:06 -0600 Subject: [PATCH 01/15] pgusinit general fixes --- pgusinit/pgusinit.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pgusinit/pgusinit.c b/pgusinit/pgusinit.c index 1cdd89c..f746ba1 100755 --- a/pgusinit/pgusinit.c +++ b/pgusinit/pgusinit.c @@ -461,6 +461,7 @@ static int wait_for_cd_load(void) print_string(CMD_CDERROR); return 98; } + return 0; } @@ -631,7 +632,7 @@ static int write_firmware(const char* fw_filename) for (uint16_t b = 0; b < 512; ++b) { // Write firmware byte outp(DATA_PORT_HIGH, uf2_buf.buf[b]); - if (b == 512 && protocol == 1) { + if (b == 511 && protocol == 1) { // Protocol 1 abuses IOCHRDY to pause during flash erase/write. Some chipsets give // up waiting on IOCHRDY and release the ISA bus after a certain amount of time before // the flash operation is finished. This is an extra delay to work around this issue. @@ -682,7 +683,7 @@ static void wifi_printStatus(void) uint16_t try = 0; char c; while (c = inp(DATA_PORT_HIGH)) { - if (c == 255) { + if ((unsigned char)c == 255) { if (++try == 10000) { printf("Error getting WiFI status\n"); break; @@ -772,7 +773,7 @@ static bool cmdSetMode(const char* arg, const int cmd, const int cmd2, const int return true; } } - fprintf(stderr, "Invalid mode %s. Valid modes: gus, sb, mpu, psg, adlib, usb\n"); + fprintf(stderr, "Invalid mode %s. Valid modes: gus, sb, mpu, psg, adlib, usb\n", arg); return false; } @@ -859,7 +860,9 @@ static bool cmdSendPort(const char* arg, const int cmd, const int cmd2, const in static bool cmdDefaults(const char* arg, const int cmd, const int cmd2, const int cmd3) { - return cmdSendUint8(CMD_DEFAULTS, 0xff, 0, 0); + outp(CONTROL_PORT, CMD_DEFAULTS); + outp(DATA_PORT_HIGH, 0xff); + return true; } static bool cmdSetVol(const char* arg, const int cmd, const int cmd2, const int cmd3) @@ -1081,16 +1084,15 @@ int parseCommand(int argc, char* argv[], int* i, ParseCommand commands[]) } else { printf("\n"); } - return retVal; + return retVal; } arg = argv[++(*i)]; - } - - if (!stricmp(argv[idx + 1], "default")) { - if (command->def) { - argv[idx + 1] = command->def; - } else { - return retVal; + if (!stricmp(arg, "default")) { + if (command->def) { + arg = command->def; + } else { + return retVal; + } } } return command->routine(arg, command->cmd, command->cmd2, command->cmd3); From 8c4bdc8e4d34eb1d7893d8eb9ab26b0136abd9d0 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Thu, 9 Apr 2026 08:51:53 -0600 Subject: [PATCH 02/15] SB: support ADPCM and >44.1k sample rates --- sw/sbdsp/sbdsp.cpp | 327 ++++++++++++++++++++++++++++++++++++--------- sw/sbdsp/sbdsp.h | 34 +++-- 2 files changed, 280 insertions(+), 81 deletions(-) diff --git a/sw/sbdsp/sbdsp.cpp b/sw/sbdsp/sbdsp.cpp index fbd3108..be9d300 100644 --- a/sw/sbdsp/sbdsp.cpp +++ b/sw/sbdsp/sbdsp.cpp @@ -12,7 +12,7 @@ Date : 2023-12-30 Author : Kevin Moonlight Copyright (C) 2023 Kevin Moonlight -Copyright (C) 2024 Ian Scott +Copyright (C) 2024-2026 Ian Scott Copyright (C) 2026 Artem Vasilev Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -73,7 +73,6 @@ static dma_inst_t dma_config; // Sound Blaster DSP commands. #define DSP_DMA_HS_SINGLE 0x91 #define DSP_DMA_HS_AUTO 0x90 -#define DSP_DMA_ADPCM 0x7F //creative ADPCM 8bit to 3bit #define DSP_DMA_SINGLE 0x14 //follosed by length #define DSP_DMA_AUTO 0X1C //length based on 48h #define DSP_DMA_BLOCK_SIZE 0x48 //block size for highspeed/dma @@ -159,6 +158,116 @@ union sample32 { sbdsp_t sbdsp; +// Creative ADPCM decoders. Each sub-sample is decoded as: +// delta = magnitude * accumulator + accumulator/2 +// reference += +/-delta (clamped to 0..255, sign from the sub-sample's sign bit) +// The accumulator doubles on max-magnitude sub-samples (up to a per-format +// cap) and halves on zero-magnitude sub-samples (down to 1). +// +// Algorithm derived from the SB v2.02 DSP firmware disassembly (TubeTime, +// https://github.com/schlae/sb-firmware) and VocTool (MIT, Torsten Stremlau). +// Not derived from DOSBox/dosbox-x or 86Box. + +// 4-bit: nibble is sign (bit 3) + 3 magnitude bits (bits 2..0). +// Accumulator range [1..8]. +// Structure adapted from VocTool's CreativeAdpcmDecoder4Bit (MIT, Torsten +// Stremlau); the sign formula below is inverted relative to VocTool's so that +// bit 3 set means subtract, matching the SB DSP firmware convention. +static inline uint8_t decode_ADPCM_4_sample(uint8_t bits) { + int32_t sign = 1 - ((bits & 8) >> 2); // bit 3 is the sign bit (0 -> +1, 1 -> -1) + int32_t data = bits & 7; // the lower 3 bits are the sample data + int32_t delta = + (data * sbdsp.adpcm.accum) + + (sbdsp.adpcm.accum >> 1); // scale sample data using accumulator value + int32_t result = sbdsp.adpcm.reference + sign * delta; // calculate the next value + if (result > 0xff) result = 0xff; // limit value to 0..255 + else if (result < 0) result = 0; + sbdsp.adpcm.reference = (uint8_t)result; + + if ((data == 0) && (sbdsp.adpcm.accum > 1)) // if input value is 0, and accumulator is + sbdsp.adpcm.accum >>= 1; // larger than 1, then halve accumulator. + if ((data >= 5) && (sbdsp.adpcm.accum < 8)) // if input value larger than 5, and accumulator is + sbdsp.adpcm.accum <<= 1; // lower than 8, then double accumulator. + + return sbdsp.adpcm.reference; +} + +// 2.6-bit: 3 samples per byte (3 bits, 3 bits, 2 bits). Accumulator range [1..16]. +// Caller passes magnitude bits (0..3 for the first two samples, 0..1 for the third) +// and sign separately since sign-bit position varies per sub-sample. +static inline uint8_t decode_ADPCM_3_sample(uint8_t bits, bool negative) { + int32_t sign = negative ? -1 : 1; // sign bit extracted by caller + int32_t data = bits; // 0..3, or 0..1 for the final sample + int32_t delta = + (data * sbdsp.adpcm.accum) + + (sbdsp.adpcm.accum >> 1); // scale sample data using accumulator value + int32_t result = sbdsp.adpcm.reference + sign * delta; // calculate the next value + if (result > 0xff) result = 0xff; // limit value to 0..255 + else if (result < 0) result = 0; + sbdsp.adpcm.reference = (uint8_t)result; + + if ((data == 0) && (sbdsp.adpcm.accum > 1)) // if input value is 0, and accumulator is + sbdsp.adpcm.accum >>= 1; // larger than 1, then halve accumulator. + if ((data >= 3) && (sbdsp.adpcm.accum < 0x10)) // if input value is 3, and accumulator is + sbdsp.adpcm.accum <<= 1; // lower than 0x10, then double accumulator. + + return sbdsp.adpcm.reference; +} + +// 2-bit: sign (bit 1) + 1 magnitude bit (bit 0). Accumulator range [1..32]. +static inline uint8_t decode_ADPCM_2_sample(uint8_t bits) { + int32_t sign = 1 - (bits & 2); // bit 1 is the sign bit (0 -> +1, 1 -> -1) + int32_t data = bits & 1; // the lower bit is the sample data + int32_t delta = + (data * sbdsp.adpcm.accum) + + (sbdsp.adpcm.accum >> 1); // scale sample data using accumulator value + int32_t result = sbdsp.adpcm.reference + sign * delta; // calculate the next value + if (result > 0xff) result = 0xff; // limit value to 0..255 + else if (result < 0) result = 0; + sbdsp.adpcm.reference = (uint8_t)result; + + if ((data == 0) && (sbdsp.adpcm.accum > 1)) // if input value is 0, and accumulator is + sbdsp.adpcm.accum >>= 1; // larger than 1, then halve accumulator. + if ((data >= 1) && (sbdsp.adpcm.accum < 0x20)) // if input value is 1, and accumulator is + sbdsp.adpcm.accum <<= 1; // lower than 0x20, then double accumulator. + + return sbdsp.adpcm.reference; +} + +// Convert decoded 8-bit unsigned mono sample to packed signed stereo +static inline uint32_t adpcm_to_stereo(uint8_t ref) { + int16_t s = ((int16_t)(int8_t)(ref ^ 0x80)) << 8; + return (uint32_t)(uint16_t)s | ((uint32_t)(uint16_t)s << 16); +} + +// Ring buffer helpers +static inline uint8_t ring_count() { + return (uint8_t)(sbdsp.rs.ring_head - sbdsp.rs.ring_tail); +} +static inline uint8_t ring_free() { + return SB_RING_SIZE - ring_count(); +} +static inline bool ring_empty() { + return sbdsp.rs.ring_head == sbdsp.rs.ring_tail; +} +static inline void ring_push(uint32_t sample) { + sbdsp.rs.ring[sbdsp.rs.ring_head & (SB_RING_SIZE - 1)] = sample; + sbdsp.rs.ring_head++; +} +static inline uint32_t ring_pop() { + uint32_t s = sbdsp.rs.ring[sbdsp.rs.ring_tail & (SB_RING_SIZE - 1)]; + sbdsp.rs.ring_tail++; + return s; +} +static inline uint8_t samples_per_transfer() { + switch (sbdsp.adpcm.format) { + case 4: return 2; + case 3: return 3; + case 2: return 4; + default: return 1; + } +} + static uint8_t mixer_state[256] = { 0 }; static uint8_t sb_8051_ram[256] = { 0 }; @@ -173,9 +282,11 @@ void sbdsp_set_wtvol_passthrough(uint8_t *wt_volume, sbmixer_wtvol_cb_t cb) { static __force_inline void sbdsp_dma_disable(bool pause) { sbdsp.dma_enabled = false; - memset(&sbdsp.rsm, 0, sizeof(sbdsp.rsm)); + sbdsp.rs.dma_pending = false; // sbdsp.cur_sample = 0; // hold last sample to prevent pops! if (!pause) { + sbdsp.rs.ring_head = sbdsp.rs.ring_tail = 0; + sbdsp.adpcm.format = 0; sbdsp.dma_16bit = false; sbdsp.dma_signed = false; sbdsp.dma_stereo = false; @@ -188,40 +299,20 @@ uint32_t sbdsp_generate_sample() { sbdsp.rs.phase_acc -= (1 << SB_RSM_FRAC); // advance interpolation buffer state - sbdsp.rs.buf[1] = sbdsp.rs.buf[0]; + sbdsp.rs.interp[1] = sbdsp.rs.interp[0]; - if (sbdsp.rsm.dma_sample_ready) { - // sample ready, advance DMA state - sbdsp.rs.buf[0] = sbdsp.rsm.dma_sample; - sbdsp.rsm.dma_sample_ready = false; + if (!ring_empty()) { + sbdsp.rs.interp[0] = ring_pop(); + } + // else: hold last sample (graceful degradation) + } - // is this last sample? - sbdsp.dma_xfer_count_left--; - if (!sbdsp.dma_xfer_count_left) { - // terminal count, current block is finished - sbdsp.dma_done = true; - if (sbdsp.dma_16bit) { - sbdsp.irq_16_pending = true; - } else { - sbdsp.irq_8_pending = true; - } - PIC_ActivateIRQ(); - if (sbdsp.autoinit) { - // reload with transfer count - sbdsp.dma_xfer_count_left = sbdsp.dma_xfer_count; - } else { - // stop DMA - sbdsp_dma_disable(false); - } - } - - if (sbdsp.dma_enabled && !sbdsp.rsm.dma_pending) { - // request DMA to transfer next sample - // TODO: handle rates above 44 KHz (may reqest 2 sample frames per single 44KHz sample) - // TODO: handle Creative ADPCM as well - sbdsp.rsm.dma_pending = true; - DMA_Multi_Start_Write(&dma_config, sbdsp.dma_bytes_per_frame); - } + // Restart DMA chain if ring drained and DMA still active + if (sbdsp.dma_enabled && !sbdsp.rs.dma_pending) { + uint8_t samples_per_xfer = samples_per_transfer(); + if (ring_free() >= samples_per_xfer) { + sbdsp.rs.dma_pending = true; + DMA_Multi_Start_Write(&dma_config, sbdsp.dma_bytes_per_frame); } } @@ -232,21 +323,21 @@ uint32_t sbdsp_generate_sample() { interp0->accum[1] = sbdsp.rs.phase_acc; // left channel: older_L in low 16, newer_L in high 16 - interp0->base01 = (sbdsp.rs.buf[1] & 0xFFFF) - | ((sbdsp.rs.buf[0] & 0xFFFF) << 16); + interp0->base01 = (sbdsp.rs.interp[1] & 0xFFFF) + | ((sbdsp.rs.interp[0] & 0xFFFF) << 16); int32_t out_l = interp0->peek[1]; // right channel: older_R in low 16, newer_R in high 16 - interp0->base01 = (sbdsp.rs.buf[1] >> 16) - | (sbdsp.rs.buf[0] & 0xFFFF0000); + interp0->base01 = (sbdsp.rs.interp[1] >> 16) + | (sbdsp.rs.interp[0] & 0xFFFF0000); int32_t out_r = interp0->peek[1]; return (uint32_t)((out_l & 0xFFFF) | (out_r << 16)); #else - int32_t l0 = (int16_t)(sbdsp.rs.buf[0] & 0xFFFF); - int32_t r0 = (int16_t)(sbdsp.rs.buf[0] >> 16); - int32_t l1 = (int16_t)(sbdsp.rs.buf[1] & 0xFFFF); - int32_t r1 = (int16_t)(sbdsp.rs.buf[1] >> 16); + int32_t l0 = (int16_t)(sbdsp.rs.interp[0] & 0xFFFF); + int32_t r0 = (int16_t)(sbdsp.rs.interp[0] >> 16); + int32_t l1 = (int16_t)(sbdsp.rs.interp[1] & 0xFFFF); + int32_t r1 = (int16_t)(sbdsp.rs.interp[1] >> 16); int32_t phase_frac = sbdsp.rs.phase_acc & ((1 << SB_RSM_FRAC) - 1); int32_t out_l = ((l0 * phase_frac) + (l1 * ((1 << SB_RSM_FRAC) - phase_frac))) >> SB_RSM_FRAC; @@ -300,8 +391,7 @@ static __force_inline void sbdsp_dma_enable() { // Set autopush bits to number of bits per audio frame. // 32 will get masked to 0 by this operation which is correct behavior. DMA_Multi_Set_Push_Threshold(&dma_config, sbdsp.dma_bytes_per_frame << 3); - memset(&sbdsp.rsm, 0, sizeof(sbdsp.rsm)); - sbdsp.rsm.dma_pending = true; + sbdsp.rs.dma_pending = true; DMA_Multi_Start_Write(&dma_config, sbdsp.dma_bytes_per_frame); } } @@ -310,27 +400,81 @@ static void sbdsp_dma_isr(void) { // dma_write_multi uses right-shift: bytes fill from MSB down. // Complete frame arrives as one push thanks to autopush threshold. const uint32_t dma_data = DMA_Complete_Write(&dma_config); - uint32_t sample; - if (sbdsp.dma_stereo) { - if (sbdsp.dma_16bit) { - sample = dma_data; + sbdsp.rs.dma_pending = false; + + // Decode and push to ring buffer. + // Reference byte case: consumes a DMA byte but pushes no samples. + if (sbdsp.adpcm.have_ref) { + sbdsp.adpcm.reference = dma_data >> 24; + sbdsp.adpcm.accum = 1; // accumulator starts at 1 in SB DSP firmware + sbdsp.adpcm.have_ref = false; + } else if (sbdsp.adpcm.format == 0) { + // PCM decode (existing logic), push 1 sample + uint32_t sample; + if (sbdsp.dma_stereo) { + if (sbdsp.dma_16bit) { + sample = dma_data; + } else { + // 8-bit stereo: L at [23:16], R at [31:24] to MSB of each 16-bit half + sample = (dma_data & 0xFF000000) | ((dma_data >> 8) & 0x0000FF00); + } } else { - // 8-bit stereo: L at [23:16], R at [31:24] to MSB of each 16-bit half - sample = (dma_data & 0xFF000000) | ((dma_data >> 8) & 0x0000FF00); + if (sbdsp.dma_16bit) { + // 16-bit mono: duplicate upper 16 to both halves + sample = (dma_data & 0xFFFF0000) | (dma_data >> 16); + } else { + // 8-bit mono: byte at [31:24], duplicate to MSB of each half + sample = (dma_data & 0xFF000000) | ((dma_data >> 16) & 0x0000FF00); + } } + if (!sbdsp.dma_signed) sample ^= 0x80008000; + ring_push(sample); } else { - if (sbdsp.dma_16bit) { - // 16-bit mono: duplicate upper 16 to both halves - sample = (dma_data & 0xFFFF0000) | (dma_data >> 16); - } else { - // 8-bit mono: byte at [31:24], duplicate to MSB of each half - sample = (dma_data & 0xFF000000) | ((dma_data >> 16) & 0x0000FF00); + // ADPCM decode: extract samples from byte, push each + uint8_t byte = dma_data >> 24; // ADPCM is always 1-byte mono + switch (sbdsp.adpcm.format) { + case 4: // 4-bit: 2 samples per byte + ring_push(adpcm_to_stereo(decode_ADPCM_4_sample(byte >> 4))); + ring_push(adpcm_to_stereo(decode_ADPCM_4_sample(byte & 0xf))); + break; + case 3: // 2.6-bit: 3 samples per byte (3 bits, 3 bits, 2 bits) + ring_push(adpcm_to_stereo(decode_ADPCM_3_sample((byte >> 5) & 3, byte & 0x80))); + ring_push(adpcm_to_stereo(decode_ADPCM_3_sample((byte >> 2) & 3, byte & 0x10))); + ring_push(adpcm_to_stereo(decode_ADPCM_3_sample( byte & 1, byte & 0x02))); + break; + case 2: // 2-bit: 4 samples per byte + ring_push(adpcm_to_stereo(decode_ADPCM_2_sample((byte >> 6) & 3))); + ring_push(adpcm_to_stereo(decode_ADPCM_2_sample((byte >> 4) & 3))); + ring_push(adpcm_to_stereo(decode_ADPCM_2_sample((byte >> 2) & 3))); + ring_push(adpcm_to_stereo(decode_ADPCM_2_sample(byte & 3))); + break; } } - if (!sbdsp.dma_signed) sample ^= 0x80008000; - sbdsp.rsm.dma_sample = sample; - sbdsp.rsm.dma_sample_ready = true; - sbdsp.rsm.dma_pending = false; + + // Transfer counting (runs for all cases including reference byte) + sbdsp.dma_xfer_count_left--; + if (!sbdsp.dma_xfer_count_left) { + sbdsp.dma_done = true; + if (sbdsp.dma_16bit) { + sbdsp.irq_16_pending = true; + } else { + sbdsp.irq_8_pending = true; + } + PIC_ActivateIRQ(); + if (sbdsp.autoinit) { + sbdsp.dma_xfer_count_left = sbdsp.dma_xfer_count; + } else { + sbdsp.dma_enabled = false; + return; + } + } + + // Chain next DMA if ring has space + uint8_t samples_per_xfer = samples_per_transfer(); + if (sbdsp.dma_enabled && ring_free() >= samples_per_xfer) { + sbdsp.rs.dma_pending = true; + DMA_Multi_Start_Write(&dma_config, sbdsp.dma_bytes_per_frame); + } } static uint32_t DSP_DAC_Resume_eventHandler(Bitu val) { @@ -436,10 +580,29 @@ static __force_inline void sbdsp_output(uint8_t value) { sbdsp.dav_pc = 1; } +// start ADPCM DMA transfer +static void sbdsp_start_adpcm_dma(int autoinit, uint16_t xfer_size, uint8_t format, bool with_ref) { + sbdsp.autoinit = autoinit; + sbdsp.adpcm.format = format; + sbdsp.adpcm.have_ref = with_ref; + sbdsp.adpcm.accum = 1; // SB DSP firmware initializes accumulator to 1 at DMA start + // (for have_ref it's overwritten again when the ref byte arrives) + sbdsp.dma_16bit = false; + sbdsp.dma_signed = false; + sbdsp.dma_stereo = false; + sbdsp.dma_stereo_sbpro = false; + sbdsp.dma_bytes_per_frame = 1; // ADPCM always 1 byte at a time + sbdsp.dma_xfer_count = xfer_size + 1; // byte count + sbdsp.dma_xfer_count_left = sbdsp.dma_xfer_count; + sbdsp_set_dma_interval(); + sbdsp.dma_done = false; + sbdsp_dma_enable(); +} + // start pre-SB16 PCM DMA transfer -// TODO: Creative ADPCM? static void sbdsp_start_pcm_dma(int autoinit, uint16_t xfer_size) { sbdsp.autoinit = autoinit; + sbdsp.adpcm.format = 0; // ensure PCM mode bool dma_stereo = (mixer_state[0x0E] & 2); if (dma_stereo && ((xfer_size + 1) == 1)) { @@ -567,6 +730,43 @@ void sbdsp_process(void) { sbdsp.current_command_index++; } break; + // Single-cycle ADPCM (2 param bytes: length-1 low, length-1 high) + case 0x74: case 0x75: // 4-bit ADPCM / with ref + case 0x76: case 0x77: // 2.6-bit ADPCM / with ref + case 0x16: case 0x17: // 2-bit ADPCM / with ref + if (sbdsp.dav_dsp) { + if (sbdsp.current_command_index == 1) { + sbdsp.dma_sample_count = sbdsp.inbox; + sbdsp.dav_dsp = 0; + } else if (sbdsp.current_command_index == 2) { + sbdsp.dma_sample_count += (sbdsp.inbox << 8); + uint8_t cmd = sbdsp.current_command; + uint8_t fmt = (cmd >= 0x74) ? ((cmd <= 0x75) ? 4 : 3) : 2; + bool ref = cmd & 1; + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + sbdsp_start_adpcm_dma(0, sbdsp.dma_sample_count, fmt, ref); + } + sbdsp.current_command_index++; + } + break; + // Auto-init ADPCM (no params, use dma_block_size) + case 0x7D: // 4-bit ADPCM auto-init + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + sbdsp_start_adpcm_dma(1, sbdsp.dma_block_size, 4, true); + break; + case 0x7F: // 2.6-bit ADPCM auto-init + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + sbdsp_start_adpcm_dma(1, sbdsp.dma_block_size, 3, true); + break; + case 0x1F: // 2-bit ADPCM auto-init + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + sbdsp_start_adpcm_dma(1, sbdsp.dma_block_size, 2, true); + break; + case DSP_IRQ_8: sbdsp.current_command = 0; sbdsp.irq_8_pending = true; @@ -718,6 +918,7 @@ void sbdsp_process(void) { // SB16 DMA transfer if (sbdsp.dav_dsp) { if (sbdsp.current_command_index == 0) { // bCommand + bMode + sbdsp.adpcm.format = 0; // SB16 commands are always PCM sbdsp.dma_16bit = (sbdsp.current_command & 0xf0) == 0xb0; sbdsp.autoinit = sbdsp.current_command & 0x4; sbdsp.dma_signed = sbdsp.inbox & 0x10; @@ -816,6 +1017,8 @@ static uint32_t DSP_Reset_EventHandler(Bitu val) { sbdsp.speaker_on = (sbdsp.dsp_version.major >= 4); // SB16 speaker always on sbdsp.dma_done = false; sbdsp.dac_resume_pending = false; + memset(&sbdsp.adpcm, 0, sizeof(sbdsp.adpcm)); + sbdsp.rs.ring_head = sbdsp.rs.ring_tail = 0; sbdsp.outbox = 0xAA; sbdsp.dav_pc = 1; diff --git a/sw/sbdsp/sbdsp.h b/sw/sbdsp/sbdsp.h index 5f9a6e0..40191e4 100644 --- a/sw/sbdsp/sbdsp.h +++ b/sw/sbdsp/sbdsp.h @@ -97,30 +97,26 @@ typedef struct sbdsp_t { #define SB_RSM_FRAC 12 volatile uint32_t cur_sample; // packed stereo pair: L in low 16, R in high 16 -#if 0 + + // ADPCM decode state struct { - uint32_t old_sample; - uint32_t new_sample; - volatile uint32_t dma_sample; - volatile bool dma_sample_ready; - bool dma_pending; // a DMA transfer is in flight - int32_t samplecnt; - } rsm; // resampling state, memset to zero on start/stop -#else + uint8_t reference; // current output level 0-255 + uint8_t accum; // accumulator (1..32 depending on format) + uint8_t format; // 0=PCM, 2=2-bit, 3=2.6-bit, 4=4-bit ADPCM + bool have_ref; // next DMA byte is reference byte + } adpcm; + +#define SB_RING_SIZE 8 + struct { int32_t phase_acc; - uint32_t buf[2]; // sample buffer, used for linear interpolation + uint32_t interp[2]; // interpolation buffer [0]=newer [1]=older - // DMA stuff - // TODO: make it a very short (max. 8 samples) ring buffer for Creative ADPCM - // and rates slightly higher than 44100Hz (SB16 cap is 45454Hz) + uint32_t ring[SB_RING_SIZE]; // decoded sample ring buffer + volatile uint8_t ring_head; // ISR writes here + uint8_t ring_tail; // consumer reads here + volatile bool dma_pending; // a DMA transfer is in flight } rs; - struct { - volatile uint32_t dma_sample; - volatile bool dma_sample_ready; - bool dma_pending; // a DMA transfer is in flight - } rsm; -#endif int32_t rateratio; } sbdsp_t; From 4e84c09d49808e1799511717e8a8cfb63521ddfe Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Fri, 10 Apr 2026 00:14:59 -0600 Subject: [PATCH 03/15] SB: support SBMIDI --- sw/mpu401/export.h | 1 + sw/sbdsp/sbdsp.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ sw/sbdsp/sbdsp.h | 2 ++ 3 files changed, 43 insertions(+) diff --git a/sw/mpu401/export.h b/sw/mpu401/export.h index a995770..1892921 100755 --- a/sw/mpu401/export.h +++ b/sw/mpu401/export.h @@ -44,6 +44,7 @@ Bit8u MPU401_ReadStatus(void); void MPU401_WriteData(Bit8u val, bool crit); Bit8u QueueUsed(); void send_midi_bytes(int maxbytes); +void MIDI_RawOutByte(Bit8u data); #ifdef __cplusplus } diff --git a/sw/sbdsp/sbdsp.cpp b/sw/sbdsp/sbdsp.cpp index be9d300..f9d6385 100644 --- a/sw/sbdsp/sbdsp.cpp +++ b/sw/sbdsp/sbdsp.cpp @@ -5,6 +5,9 @@ #include "system/pico_pic.h" #include "audio/volctrl.h" #include "sbdsp.h" +#ifdef SOUND_MPU +#include "mpu401/export.h" +#endif /* Title : SoundBlaster DSP Emulation @@ -993,6 +996,35 @@ void sbdsp_process(void) { sbdsp.current_command_index++; } break; + // SB MIDI commands + case DSP_MIDI_WRITE_POLL: // 0x38: write MIDI byte + if (sbdsp.dav_dsp) { + if (sbdsp.current_command_index == 1) { +#ifdef SOUND_MPU + MIDI_RawOutByte(sbdsp.inbox); +#endif + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + } + sbdsp.current_command_index++; + } + break; + case DSP_MIDI_READ_POLL: // 0x30: read MIDI byte + if (sbdsp.current_command_index == 0) { + sbdsp.current_command = 0; + sbdsp_output(0x00); // no MIDI input + } + break; + case 0x31: // MIDI Read Interrupt - accept and ignore + sbdsp.current_command = 0; + break; + case 0x34: // MIDI UART read+write poll + case 0x35: // MIDI UART read interrupt+Write poll + sbdsp.midi_uart_mode = true; + sbdsp.current_command = 0; + break; + sbdsp.current_command = 0; + break; case 0: //not in a command break; @@ -1037,6 +1069,7 @@ static __force_inline void sbdsp_reset(uint8_t value) { while (sbdsp.dsp_busy) tight_loop_contents(); // TODO timeout!!!!!! sbdsp.reset_state = 1; sbdsp.autoinit = 0; + sbdsp.midi_uart_mode = false; sbdsp_dma_disable(false); break; case 0: @@ -1282,6 +1315,13 @@ void sbdsp_write(uint8_t address, uint8_t value) { switch (address) { case DSP_WRITE: // 0xC case DSP_WRITE+1: // 0xD + // in MIDI UART mode, all writes to DSP data port are MIDI output + if (sbdsp.midi_uart_mode) { +#ifdef SOUND_MPU + MIDI_RawOutByte(value); +#endif + break; + } if (sbdsp.dav_dsp) printf("WARN - DAV_DSP OVERWRITE\n"); sbdsp.inbox = value; sbdsp.dav_dsp = 1; diff --git a/sw/sbdsp/sbdsp.h b/sw/sbdsp/sbdsp.h index 40191e4..1b5c531 100644 --- a/sw/sbdsp/sbdsp.h +++ b/sw/sbdsp/sbdsp.h @@ -119,6 +119,8 @@ typedef struct sbdsp_t { } rs; int32_t rateratio; + + bool midi_uart_mode; // DSP MIDI UART mode (commands 0x34/0x35) } sbdsp_t; void sbdsp_init(); From 6dab135cebee1377b814e20c4e217edf663f3248 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Fri, 10 Apr 2026 00:17:11 -0600 Subject: [PATCH 04/15] SB: general SBDSP fixes --- sw/sbdsp/sbdsp.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sw/sbdsp/sbdsp.cpp b/sw/sbdsp/sbdsp.cpp index f9d6385..4cd4351 100644 --- a/sw/sbdsp/sbdsp.cpp +++ b/sw/sbdsp/sbdsp.cpp @@ -887,7 +887,8 @@ void sbdsp_process(void) { } else if (sbdsp.current_command_index == 2) { sbdsp.dac_pause_duration = sbdsp.dac_pause_duration_low + (sbdsp.inbox << 8); sbdsp.dac_resume_pending = true; - PIC_AddEvent(&DSP_DAC_Resume_event, sbdsp.dma_interval * sbdsp.dac_pause_duration, 0); + // length of pause is samples - 1, so add 1 + PIC_AddEvent(&DSP_DAC_Resume_event, sbdsp.dma_interval * (sbdsp.dac_pause_duration + 1), 0); sbdsp.dav_dsp = 0; sbdsp.current_command = 0; } @@ -1016,6 +1017,8 @@ void sbdsp_process(void) { } break; case 0x31: // MIDI Read Interrupt - accept and ignore + case 0xA0: // Disable Stereo Input Mode + case 0xA8: // Enable Stereo Input Mode sbdsp.current_command = 0; break; case 0x34: // MIDI UART read+write poll @@ -1190,7 +1193,12 @@ static void sbmixer_16_set(uint8_t sb16_reg, uint8_t value) { } static void sbmixer_reset(void) { + // preserve IRQ/DMA select registers across mixer reset + uint8_t saved_irq = mixer_state[MIXER_INTERRUPT]; + uint8_t saved_dma = mixer_state[MIXER_DMA]; memset(mixer_state, 0, sizeof(mixer_state)); + mixer_state[MIXER_INTERRUPT] = saved_irq; + mixer_state[MIXER_DMA] = saved_dma; // initialize default values sbmixer_pro_set(MIXER_VOL_MASTER, 0xFF); @@ -1246,6 +1254,10 @@ static __force_inline void sbmixer_write(uint8_t value) { case 0x00: // Mixer reset sbmixer_reset(); break; + case 0x02: // SBPro Master Volume alias (chains to 0x22) + mixer_state[0x02] = value; + sbmixer_pro_set(MIXER_VOL_MASTER, value); + break; case MIXER_VOL_MASTER: case MIXER_VOL_MIDI: case MIXER_VOL_CD: @@ -1293,7 +1305,7 @@ uint8_t sbdsp_read(uint8_t address) { PIC_DeActivateIRQ(); } return (sbdsp.dav_pc << 7) | DSP_UNUSED_STATUS_BITS_PULLED_HIGH; - case DSP_IRQ_16_STATUS: // +0xD + case DSP_IRQ_16_STATUS: // +0xF if (sbdsp.irq_16_pending) { sbdsp.irq_16_pending = false; PIC_DeActivateIRQ(); From e014854bd502053d38f037fb2d7e8678b77fb5a0 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Fri, 10 Apr 2026 08:59:44 -0600 Subject: [PATCH 05/15] SB: add fake ADC --- sw/sbdsp/sbdsp.cpp | 79 +++++++++++++++++++++++++++++++++++++++++----- sw/sbdsp/sbdsp.h | 1 + 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/sw/sbdsp/sbdsp.cpp b/sw/sbdsp/sbdsp.cpp index 4cd4351..c32d3c3 100644 --- a/sw/sbdsp/sbdsp.cpp +++ b/sw/sbdsp/sbdsp.cpp @@ -283,7 +283,33 @@ void sbdsp_set_wtvol_passthrough(uint8_t *wt_volume, sbmixer_wtvol_cb_t cb) { wtvol_cb = cb; } +// Fake ADC (recording) - just fire IRQ after the expected transfer time so +// programs don't hang, but they will get garbage data +static uint32_t DSP_ADC_eventHandler(Bitu val); +static PIC_TimerEvent DSP_ADC_event = { + .handler = DSP_ADC_eventHandler, +}; +static uint32_t DSP_ADC_eventHandler(Bitu val) { + if (!sbdsp.adc_active) return 0; + if (sbdsp.dma_16bit) { + sbdsp.irq_16_pending = true; + } else { + sbdsp.irq_8_pending = true; + } + PIC_ActivateIRQ(); + // for auto-init, reschedule the next block + if (sbdsp.autoinit) { + PIC_AddEvent(&DSP_ADC_event, sbdsp.dma_interval * (sbdsp.dma_block_size + 1), 0); + } + return 0; +} + static __force_inline void sbdsp_dma_disable(bool pause) { + if (sbdsp.adc_active) { + PIC_RemoveEvent(&DSP_ADC_event); + if (!pause) sbdsp.adc_active = false; + return; + } sbdsp.dma_enabled = false; sbdsp.rs.dma_pending = false; // sbdsp.cur_sample = 0; // hold last sample to prevent pops! @@ -389,6 +415,11 @@ static __force_inline void sbdsp_set_dma_interval() { } static __force_inline void sbdsp_dma_enable() { + if (sbdsp.adc_active) { + // resume fake ADC + PIC_AddEvent(&DSP_ADC_event, sbdsp.dma_interval * (sbdsp.dma_block_size + 1), 0); + return; + } if (!sbdsp.dma_enabled) { sbdsp.dma_enabled = true; // Set autopush bits to number of bits per audio frame. @@ -952,16 +983,23 @@ void sbdsp_process(void) { // SB16: sample_count counts individual DMA transfers minus 1. // Each DMA event transfers one complete frame, so for stereo // we halve the count (2 DMA words per stereo frame). - sbdsp.dma_xfer_count = sbdsp.dma_stereo - ? (sbdsp.dma_sample_count + 1) >> 1 - : sbdsp.dma_sample_count + 1; - sbdsp.dma_xfer_count_left = sbdsp.dma_xfer_count; sbdsp.dav_dsp = 0; - sbdsp.current_command = 0; - sbdsp.speaker_on = true; sbdsp_set_dma_interval(); - sbdsp.dma_done = false; - sbdsp_dma_enable(); + if (sbdsp.current_command & 0x8) { + // ADC (recording) - schedule fake IRQ, no real DMA + sbdsp.adc_active = true; + PIC_AddEvent(&DSP_ADC_event, sbdsp.dma_interval * (sbdsp.dma_sample_count + 1), 0); + } else { + // DAC (playback) + sbdsp.dma_xfer_count = sbdsp.dma_stereo + ? (sbdsp.dma_sample_count + 1) >> 1 + : sbdsp.dma_sample_count + 1; + sbdsp.dma_xfer_count_left = sbdsp.dma_xfer_count; + sbdsp.speaker_on = true; + sbdsp.dma_done = false; + sbdsp_dma_enable(); + } + sbdsp.current_command = 0; } sbdsp.current_command_index++; } @@ -1026,7 +1064,32 @@ void sbdsp_process(void) { sbdsp.midi_uart_mode = true; sbdsp.current_command = 0; break; + // recording commands - fake ADC + case 0x24: // DMA ADC, 8-bit single-cycle (2 param bytes: length-1) + if (sbdsp.dav_dsp) { + if (sbdsp.current_command_index == 1) { + sbdsp.dma_sample_count = sbdsp.inbox; + sbdsp.dav_dsp = 0; + } else if (sbdsp.current_command_index == 2) { + sbdsp.dma_sample_count += (sbdsp.inbox << 8); + sbdsp.dav_dsp = 0; + sbdsp.current_command = 0; + sbdsp.autoinit = 0; + sbdsp.dma_16bit = false; + sbdsp.adc_active = true; + sbdsp_set_dma_interval(); + PIC_AddEvent(&DSP_ADC_event, sbdsp.dma_interval * (sbdsp.dma_sample_count + 1), 0); + } + sbdsp.current_command_index++; + } + break; + case 0x2C: // autoinit DMA ADC, 8-bit sbdsp.current_command = 0; + sbdsp.autoinit = 1; + sbdsp.dma_16bit = false; + sbdsp.adc_active = true; + sbdsp_set_dma_interval(); + PIC_AddEvent(&DSP_ADC_event, sbdsp.dma_interval * (sbdsp.dma_block_size + 1), 0); break; case 0: //not in a command diff --git a/sw/sbdsp/sbdsp.h b/sw/sbdsp/sbdsp.h index 1b5c531..43c65bc 100644 --- a/sw/sbdsp/sbdsp.h +++ b/sw/sbdsp/sbdsp.h @@ -121,6 +121,7 @@ typedef struct sbdsp_t { int32_t rateratio; bool midi_uart_mode; // DSP MIDI UART mode (commands 0x34/0x35) + bool adc_active; // fake ADC recording in progress (timer-based, no real DMA) } sbdsp_t; void sbdsp_init(); From 5529f2b598d37cb5c2da13dae6cc8638176120c6 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Fri, 10 Apr 2026 13:34:45 -0600 Subject: [PATCH 06/15] CDROM: eliminate sscanf usage --- sw/cdrom/cdrom_image_backend.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/sw/cdrom/cdrom_image_backend.c b/sw/cdrom/cdrom_image_backend.c index c6ff442..2f4224c 100644 --- a/sw/cdrom/cdrom_image_backend.c +++ b/sw/cdrom/cdrom_image_backend.c @@ -746,8 +746,9 @@ cdi_cue_get_number(char **line) if (!cdi_cue_get_buffer(temp, line, 0)) return 0; - /* if (sscanf(temp, "%" PRIu64, &num) != 1) */ - if (sscanf(temp, "%" PRIu32, &num) != 1) + char *end; + num = (uint32_t)strtoul(temp, &end, 10); + if (end == temp) return 0; return num; @@ -767,9 +768,18 @@ cdi_cue_get_frame(uint32_t *frames, char **line) if (!success) return 0; - success = sscanf(temp, "%d:%d:%d", &min, &sec, &fr) == 3; - if (!success) - return 0; + { + char *p = temp; + char *end; + min = (int)strtol(p, &end, 10); + if (*end != ':') return 0; + p = end + 1; + sec = (int)strtol(p, &end, 10); + if (*end != ':') return 0; + p = end + 1; + fr = (int)strtol(p, &end, 10); + if (end == p) return 0; + } *frames = MSF_TO_FRAMES(min, sec, fr); From e4fd9037a8f460a91b191bb6aaae5db98b7c2d07 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Fri, 10 Apr 2026 13:36:24 -0600 Subject: [PATCH 07/15] Add PGDEBUG CMake var to save runtime overhead of printf & save code size --- sw/CMakeLists.txt | 31 +++++++++++++++-- sw/ad1848/ad1848.cpp | 8 ++--- sw/cdrom/86box_compat.c | 20 +++-------- sw/cdrom/cdrom.c | 39 ++++++--------------- sw/cdrom/cdrom_error_msg.c | 23 +++++++++++-- sw/cdrom/cdrom_image.c | 23 ++----------- sw/cdrom/cdrom_image_backend.c | 35 ++----------------- sw/cdrom/cdrom_image_manager.c | 8 ++--- sw/cdrom/msc_app.c | 7 ++-- sw/gus/gus-x.cpp | 12 +++---- sw/gusplay.cpp | 20 +++++------ sw/include/dosbox-x-compat.h | 11 +++--- sw/include/pg_debug.h | 48 ++++++++++++++++++++++++++ sw/mke/mke.c | 6 ++-- sw/mpuplay.cpp | 6 ++-- sw/multifw/bootloader.c | 4 +-- sw/ne2000/ne2000.c | 22 ++++++------ sw/ne2000/ne2000.h | 2 +- sw/ne2000play.cpp | 7 ++-- sw/opl/emu8950.c | 4 +-- sw/opl/emu8950.h | 4 +-- sw/picogus.cpp | 62 +++++++++++++++++----------------- sw/psgplay.cpp | 7 ++-- sw/rp2040-psram | 2 +- sw/sbdsp/sbdsp.cpp | 6 ++-- sw/sbplay.cpp | 7 ++-- sw/system/flash_settings.c | 16 ++++----- sw/system/pico_reflash.c | 16 ++++----- sw/usb_hid/hid_app.c | 19 ++++++----- sw/usbplay.cpp | 6 ++-- 30 files changed, 250 insertions(+), 231 deletions(-) create mode 100644 sw/include/pg_debug.h diff --git a/sw/CMakeLists.txt b/sw/CMakeLists.txt index ba486a5..f68d055 100644 --- a/sw/CMakeLists.txt +++ b/sw/CMakeLists.txt @@ -55,6 +55,8 @@ function(set_linker_script TARGET script) endfunction() +option(PGDEBUG "Enable debug printf framework" OFF) + # Global definitions across all targets add_compile_definitions( # 370MHz is extremely closely evenly divisible by 44100Hz. @@ -212,15 +214,37 @@ function(config_target TARGET_NAME MULTIFW) ) pico_add_extra_outputs(${TARGET_NAME}) + # Determine which stdio drivers are available for this firmware type if (SOUND_MPU) pico_enable_stdio_uart(${TARGET_NAME} 0) else() - pico_enable_stdio_uart(${TARGET_NAME} 1) + set(HAS_STDIO_UART TRUE) endif() if (USB_STACK) pico_enable_stdio_usb(${TARGET_NAME} 0) else() - pico_enable_stdio_usb(${TARGET_NAME} 1) + set(HAS_STDIO_USB TRUE) + endif() + + if(PGDEBUG AND (HAS_STDIO_UART OR HAS_STDIO_USB)) + # Debug output is possible: enable available drivers and printf formatter + target_compile_definitions(${TARGET_NAME} PRIVATE PGDEBUG=1) + target_compile_definitions(rp2040-psram INTERFACE PSRAM_DEBUG=1) + if (HAS_STDIO_UART) + pico_enable_stdio_uart(${TARGET_NAME} 1) + endif() + if (HAS_STDIO_USB) + pico_enable_stdio_usb(${TARGET_NAME} 1) + endif() + else() + # No debug output possible or not requested: strip printf entirely + if (NOT SOUND_MPU) + pico_enable_stdio_uart(${TARGET_NAME} 0) + endif() + if (NOT USB_STACK) + pico_enable_stdio_usb(${TARGET_NAME} 0) + endif() + pico_set_printf_implementation(${TARGET_NAME} none) endif() pico_enable_stdio_semihosting(${TARGET_NAME} 0) endfunction() @@ -638,6 +662,9 @@ if(PROJECT_TYPE STREQUAL "MULTIFW") pico_enable_stdio_uart(bootloader 0) pico_enable_stdio_usb(bootloader 0) pico_enable_stdio_semihosting(bootloader 0) + if(NOT PGDEBUG) + pico_set_printf_implementation(bootloader none) + endif() add_dependencies(bootloader generate_flash_firmware_h) if (NOT UF2CREATE_FOUND) diff --git a/sw/ad1848/ad1848.cpp b/sw/ad1848/ad1848.cpp index 2530883..b7b19de 100644 --- a/sw/ad1848/ad1848.cpp +++ b/sw/ad1848/ad1848.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include "../include/pg_debug.h" #include #include "system/pico_pic.h" #include "audio/volctrl.h" @@ -191,7 +191,7 @@ static void ad1848_apply_iface(uint8_t iface) { if (!ad1848.play && (iface & 1)) { // start playback ad1848.ppio = (iface & 0x40); ad1848.play = true; - printf("start sr %u intvl %u 16b %u st %u bpf %u ct %u ppio %u\n", + DBG_PRINTF("start sr %u intvl %u 16b %u st %u bpf %u ct %u ppio %u\n", ad1848.sample_rate, ad1848.frame_interval, ad1848.playback_16bit, ad1848.playback_stereo, ad1848.frame_bytes, ad1848.current_count, ad1848.ppio); @@ -344,8 +344,8 @@ uint32_t ad1848_sample_stereo() { void ad1848_init() { - puts("Initing AD1848..."); - puts("Initing ISA DMA PIO..."); + DBG_PUTS("Initing AD1848..."); + DBG_PUTS("Initing ISA DMA PIO..."); AD1848_DMA_isr_pt = ad1848_dma_isr; dma_config = DMA_multi_init(pio0, DMA_PIO_SM, AD1848_DMA_isr_pt); diff --git a/sw/cdrom/86box_compat.c b/sw/cdrom/86box_compat.c index e1faef5..fb2d317 100644 --- a/sw/cdrom/86box_compat.c +++ b/sw/cdrom/86box_compat.c @@ -1,26 +1,14 @@ #include -#include -#include +#include "../include/pg_debug.h" #include "86box_compat.h" void fatal(const char *fmt, ...) { - char temp[1024]; - va_list ap; - char *sp; - va_start(ap, fmt); - vsprintf(temp, fmt, ap); - printf("%s",temp); - va_end(ap); + ERR_PUTS(fmt); while(1) { } } void pclog_ex(const char *fmt, va_list ap){ - char temp[1024]; - - if (strcmp(fmt, "") == 0) - return; - - vsprintf(temp, fmt, ap); - printf("%s\n\r",temp); + (void)fmt; + (void)ap; } diff --git a/sw/cdrom/cdrom.c b/sw/cdrom/cdrom.c index 4bce3df..1bae581 100644 --- a/sw/cdrom/cdrom.c +++ b/sw/cdrom/cdrom.c @@ -21,11 +21,11 @@ #include #include #include -#include #include #include #include #define HAVE_STDARG_H +#include "../include/pg_debug.h" #include "86box_compat.h" #include "cdrom.h" #include "cdrom_image_manager.h" @@ -93,25 +93,8 @@ static uint8_t extra_buffer[296]; /* int cdrom_interface_current; */ -//#define ENABLE_CDROM_LOG 1 - -#ifdef ENABLE_CDROM_LOG -int cdrom_do_log = ENABLE_CDROM_LOG; - -void -cdrom_log(const char *fmt, ...) -{ - va_list ap; - - if (cdrom_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -#else -# define cdrom_log(fmt, ...) -#endif +#define PGDEBUG_CDROM 0 +#define cdrom_log(fmt, ...) do { if (PGDEBUG_CDROM) DBG_PRINTF(fmt "\n", ##__VA_ARGS__); } while(0) /* static __inline int @@ -152,17 +135,17 @@ void cdrom_tasks(cdrom_t *dev) { cdman_load_image_index(dev, dev->image_data); break; case CD_COMMAND_IMAGE_LOAD: - printf("loading"); + DBG_PRINTF("loading"); cdrom_errorstr_clear(); if (dev->disk_loaded) { dev->disk_loaded = 0; dev->media_changed = 1; cdrom_image_close(dev); - printf("Disk Removed.\n"); + DBG_PRINTF("Disk Removed.\n"); cdrom_error(dev, 0x11); //media changed } if (dev->image_path[0]) { - printf("Opening %s...",dev->image_path); + DBG_PRINTF("Opening %s...",dev->image_path); if (cdrom_image_open(dev,dev->image_path)) { dev->image_command = CD_COMMAND_NONE; dev->image_status = CD_STATUS_ERROR; @@ -171,7 +154,7 @@ void cdrom_tasks(cdrom_t *dev) { } else { cdman_set_image_index(dev); } - printf("Done.\n"); + DBG_PRINTF("Done.\n"); dev->disk_loaded = 1; dev->media_changed = 1; cdrom_error(dev, 0x11); //media changed @@ -227,7 +210,7 @@ uint16_t __inline cdrom_fifo_level(cdrom_fifo_t *fifo) { uint8_t cdrom_fifo_read(cdrom_fifo_t *fifo) { uint8_t x; if(!cdrom_fifo_level(fifo)) { - printf("FIFO UNDERRUN(%p)\n",fifo); + DBG_PRINTF("FIFO UNDERRUN(%p)\n",fifo); return 0; } @@ -238,7 +221,7 @@ uint8_t cdrom_fifo_read(cdrom_fifo_t *fifo) { } void cdrom_fifo_clear(cdrom_fifo_t *fifo) { if(cdrom_fifo_level(fifo)) { - printf("*** DISCARD %u *** (%p)\n",cdrom_fifo_level(fifo),fifo); + DBG_PRINTF("*** DISCARD %u *** (%p)\n",cdrom_fifo_level(fifo),fifo); fifo->tail = fifo->head; } } @@ -333,7 +316,7 @@ bool cdrom_audio_callback(cdrom_t *dev, uint32_t len) { /* putchar('y'); */ dev->audio_sector_consumed_pairs += pairs_to_transfer; } else { - putchar('n'); + DBG_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", @@ -501,7 +484,7 @@ uint8_t cdrom_audio_playmsf(cdrom_t *dev, int m,int s, int f, int M, int S, int pos2 = MSFtoLBA(M,S,F) - 150; if (!(dev->ops->track_type(dev, pos) & CD_TRACK_AUDIO)) { - printf("CD-ROM %i: LBA %08X not on an audio track\n", dev->id, pos); + DBG_PRINTF("CD-ROM %i: LBA %08X not on an audio track\n", dev->id, pos); cdrom_log("CD-ROM %i: LBA %08X not on an audio track\n", dev->id, pos); cdrom_error(dev,0x0E); cdrom_error(dev,0x10); diff --git a/sw/cdrom/cdrom_error_msg.c b/sw/cdrom/cdrom_error_msg.c index e235dfb..15f1c17 100644 --- a/sw/cdrom/cdrom_error_msg.c +++ b/sw/cdrom/cdrom_error_msg.c @@ -1,15 +1,32 @@ #include "cdrom_error_msg.h" #include -#include +#include static char _error[256]; +/* Minimal vsnprintf that only handles %s — all cdrom_errorstr_set callers use %s exclusively. */ +static void simple_fmt(char *buf, size_t n, const char *fmt, va_list ap) { + size_t pos = 0; + while (*fmt && pos < n - 1) { + if (fmt[0] == '%' && fmt[1] == 's') { + const char *s = va_arg(ap, const char *); + if (s) { + while (*s && pos < n - 1) + buf[pos++] = *s++; + } + fmt += 2; + } else { + buf[pos++] = *fmt++; + } + } + buf[pos] = '\0'; +} + void cdrom_errorstr_set(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - vsnprintf(_error, 256, fmt, ap); - /* printf("setting cdrom error: %s\n", _error); */ + simple_fmt(_error, sizeof(_error), fmt, ap); va_end(ap); } diff --git a/sw/cdrom/cdrom_image.c b/sw/cdrom/cdrom_image.c index 1942bd4..ce0b9bb 100644 --- a/sw/cdrom/cdrom_image.c +++ b/sw/cdrom/cdrom_image.c @@ -24,36 +24,19 @@ */ #include #include -#include #include #include #include #include #define HAVE_STDARG_H +#include "../include/pg_debug.h" #include "cdrom_image_backend.h" #include "cdrom.h" #include "86box_compat.h" -//#define ENABLE_CDROM_IMAGE_LOG 1 - -#ifdef ENABLE_CDROM_IMAGE_LOG -int cdrom_image_do_log = ENABLE_CDROM_IMAGE_LOG; - -void -cdrom_image_log(const char *fmt, ...) -{ - va_list ap; - - if (cdrom_image_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -#else -# define cdrom_image_log(fmt, ...) -#endif +#define PGDEBUG_CDROM_IMAGE 0 +#define cdrom_image_log(fmt, ...) do { if (PGDEBUG_CDROM_IMAGE) DBG_PRINTF(fmt "\n", ##__VA_ARGS__); } while(0) /* The addresses sent from the guest are absolute, ie. a LBA of 0 corresponds to a MSF of 00:00:00. Otherwise, the counter displayed by the guest is wrong: there is a seeming 2 seconds in which audio plays but counter does not move, while a data track before audio jumps to 2 seconds before the actual start diff --git a/sw/cdrom/cdrom_image_backend.c b/sw/cdrom/cdrom_image_backend.c index 2f4224c..2d4c6e0 100644 --- a/sw/cdrom/cdrom_image_backend.c +++ b/sw/cdrom/cdrom_image_backend.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,7 @@ # include #endif #define HAVE_STDARG_H +#include "../include/pg_debug.h" #include "cdrom_image_backend.h" #include "cdrom_error_msg.h" #include "86box_compat.h" @@ -77,33 +77,8 @@ static inline void frames_to_msf(uint32_t total_frames, uint8_t *pM, uint8_t *pS static char temp_keyword[64]; -#ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG -int cdrom_image_backend_do_log = ENABLE_CDROM_IMAGE_BACKEND_LOG; - -void cdrom_image_backend_log(const char *fmt, ...) { - char temp[1024]; - va_list ap; - char *sp; - va_start(ap, fmt); - vsprintf(temp, fmt, ap); - printf("%s\n\r",temp); - va_end(ap); -} -/* -cdrom_image_backend_log(const char *fmt, ...) -{ - va_list ap; - - if (cdrom_image_backend_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -*/ -#else -# define cdrom_image_backend_log(fmt, ...) -#endif +#define PGDEBUG_CDROM_BACKEND 0 +#define cdrom_image_backend_log(fmt, ...) do { if (PGDEBUG_CDROM_BACKEND) DBG_PRINTF(fmt "\n", ##__VA_ARGS__); } while(0) /* #include "pico/stdlib.h" */ /* extern uint LED_PIN; */ @@ -123,17 +98,13 @@ bin_read(void *priv, uint8_t *buffer, uint32_t seek, size_t count) return 0; //if (fseeko64(tf->fp, seek, SEEK_SET) == -1) { if (f_lseek(tf->fp, seek) != FR_OK) { -#ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG cdrom_image_backend_log("CDROM: binary_read failed during seek!\n"); -#endif return 0; } //if (fread(buffer, count, 1, tf->fp) != 1) { if (f_read(tf->fp,buffer, count,&bytes_read) != FR_OK) { -#ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG cdrom_image_backend_log("CDROM: binary_read failed during read!\n"); -#endif return 0; } /* gpio_xor_mask(LED_PIN); */ diff --git a/sw/cdrom/cdrom_image_manager.c b/sw/cdrom/cdrom_image_manager.c index d022082..8ece360 100644 --- a/sw/cdrom/cdrom_image_manager.c +++ b/sw/cdrom/cdrom_image_manager.c @@ -5,7 +5,7 @@ #include #include -#include +#include "../include/pg_debug.h" #include "cdrom_error_msg.h" @@ -274,7 +274,7 @@ static uint32_t drive_serial; static bool autoadvance; void cdman_set_autoadvance(bool setting) { - printf("setting autoadvance to %u\n", setting); + DBG_PRINTF("setting autoadvance to %u\n", setting); autoadvance = setting; } @@ -287,11 +287,11 @@ void cdman_reload_image(cdrom_t *dev) { void cdman_set_serial(cdrom_t *dev, uint32_t serial) { if (drive_serial == serial) { // If we are re-inserting the same drive, maybe advance the disc image - printf("Inserting the same drive...\n"); + DBG_PRINTF("Inserting the same drive...\n"); cdman_reload_image(dev); } else { drive_serial = serial; - printf("New drive with serial %u inserted", serial); + DBG_PRINTF("New drive with serial %u inserted", serial); cdman_load_image_index(dev, 1); } } diff --git a/sw/cdrom/msc_app.c b/sw/cdrom/msc_app.c index 9bb4849..1ce903b 100644 --- a/sw/cdrom/msc_app.c +++ b/sw/cdrom/msc_app.c @@ -27,6 +27,7 @@ #include "tusb.h" /* #include "bsp/board_api.h" */ #include "pico/stdlib.h" +#include "../include/pg_debug.h" #include "ff.h" #include "diskio.h" @@ -75,7 +76,7 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const // For simplicity: we only mount 1 device if (f_mount(&fatfs, "", 1) != FR_OK) { - puts("mount failed"); + ERR_PUTS("mount failed"); return false; } @@ -132,7 +133,7 @@ static void wait_for_disk_io(void) while (_disk_busy) { tuh_task(); if ((int32_t)(time_us_32() - deadline) >= 0) { - printf("disk_io: timeout waiting for USB transfer\n"); + DBG_PRINTF("disk_io: timeout waiting for USB transfer\n"); _disk_busy = false; _disk_error = true; return; @@ -147,7 +148,7 @@ static bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * c * reported an error (e.g. medium error, illegal request). */ _disk_error = (cb_data->csw->status != 0); if (_disk_error) - printf("disk_io: SCSI error status %u\n", cb_data->csw->status); + DBG_PRINTF("disk_io: SCSI error status %u\n", cb_data->csw->status); _disk_busy = false; return true; } diff --git a/sw/gus/gus-x.cpp b/sw/gus/gus-x.cpp index 2a43e53..1833771 100644 --- a/sw/gus/gus-x.cpp +++ b/sw/gus/gus-x.cpp @@ -67,7 +67,7 @@ using namespace std; // #define GUS_BASE myGUS.portbase #define GUS_RATE myGUS.rate -#define LOG_GUS 0 +#define PGDEBUG_GUS 0 #define VOL_SHIFT 14 @@ -1051,7 +1051,7 @@ __force_inline static uint16_t ExecuteReadRegister(void) { critical_section_exit(&gus_crit); return (uint16_t)(tmpreg << 8); default: -#if LOG_GUS +#if PGDEBUG_GUS LOG_MSG("Read Register num 0x%x", myGUS.gRegSelect); #endif return myGUS.gRegData; @@ -1214,7 +1214,7 @@ __force_inline static void ExecuteGlobRegister(void) { // myGUS.basefreq = (uint32_t)(1000000.0/(1.619695497*(float)(myGUS.ActiveChannels))); myGUS.basefreq = myGUS.fixed_44k_output ? 44100 : sample_rates[myGUS.ActiveChannels - 1]; -#if LOG_GUS +#if PGDEBUG_GUS LOG_MSG("GUS set to %d channels freq=%luHz", myGUS.ActiveChannels,(unsigned long)myGUS.basefreq); #endif for (i=0;iUpdateWaveRamp(); @@ -1270,7 +1270,7 @@ __force_inline static void ExecuteGlobRegister(void) { GUSReset(); break; default: -#if LOG_GUS +#if PGDEBUG_GUS LOG_MSG("Unimplemented global register %x -- %x", myGUS.gRegSelect, myGUS.gRegData); #endif break; @@ -1364,7 +1364,7 @@ __force_inline uint8_t read_gus(Bitu port) { } case 0x106: default: -#if LOG_GUS +#if PGDEBUG_GUS LOG_MSG("Read GUS at port 0x%x", port); #endif break; @@ -1448,7 +1448,7 @@ __force_inline void write_gus(Bitu port, Bitu val) { } break; default: -#if LOG_GUS +#if PGDEBUG_GUS LOG_MSG("Write GUS at port 0x%x with %x", port, val); #endif break; diff --git a/sw/gusplay.cpp b/sw/gusplay.cpp index 7fb4bc1..0dea1c1 100644 --- a/sw/gusplay.cpp +++ b/sw/gusplay.cpp @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include "include/pg_debug.h" // #include "stdio_async_uart.h" #include @@ -103,12 +103,12 @@ static void update_gus_timing(uint8_t channels) { uint32_t divider = cps * 4; pio_sm_set_clkdiv_int_frac(audio_pio, PICO_AUDIO_I2S_SM, divider >> 8u, divider & 0xffu); - printf("GUS channels: %u\n", channels); + DBG_PRINTF("GUS channels: %u\n", channels); } // void __xip_cache("my_sub_section") (play_gus)(void) { void play_gus() { - puts("starting core 1"); + DBG_PUTS("starting core 1"); // flash_safe_execute_core_init(); uint32_t start, end; @@ -116,31 +116,31 @@ void play_gus() { PIC_Init(); // Init ISA DMA on this core so it handles the ISR - puts("Initing ISA DMA PIO..."); + DBG_PUTS("Initing ISA DMA PIO..."); dma_config = DMA_init(pio0, DMA_PIO_SM, GUS_DMA_isr_pt); #ifdef PSRAM_CORE1 #ifdef PSRAM - puts("Initing PSRAM..."); + DBG_PUTS("Initing PSRAM..."); psram_spi = psram_spi_init_clkdiv(pio1, -1, 1.6); #if TEST_PSRAM - puts("Writing PSRAM..."); + DBG_PUTS("Writing PSRAM..."); uint8_t deadbeef[8] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf}; for (uint32_t addr = 0; addr < (1024 * 1024); ++addr) { psram_write8_async(&psram_spi, addr, (addr & 0xFF)); } - puts("Reading PSRAM..."); + DBG_PUTS("Reading PSRAM..."); uint32_t psram_begin = time_us_32(); for (uint32_t addr = 0; addr < (1024 * 1024); ++addr) { uint8_t result = psram_read8(&psram_spi, addr); if (static_cast((addr & 0xFF)) != result) { - printf("\nPSRAM failure at address %x (%x != %x)\n", addr, addr & 0xFF, result); + DBG_PRINTF("\nPSRAM failure at address %x (%x != %x)\n", addr, addr & 0xFF, result); return; } } uint32_t psram_elapsed = time_us_32() - psram_begin; float psram_speed = 1000000.0 * 1024.0 * 1024 / psram_elapsed; - printf("8 bit: PSRAM read 1MB in %d us, %d B/s (target 705600 B/s)\n", psram_elapsed, (uint32_t)psram_speed); + DBG_PRINTF("8 bit: PSRAM read 1MB in %d us, %d B/s (target 705600 B/s)\n", psram_elapsed, (uint32_t)psram_speed); #endif #endif #endif @@ -157,7 +157,7 @@ void play_gus() { init_audio(); - printf("GUS IRQ-driven audio started\n"); + DBG_PRINTF("GUS IRQ-driven audio started\n"); // Use the PWM peripheral to trigger an IRQ at the GUS sample rate pwm_config pwm_c = pwm_get_default_config(); diff --git a/sw/include/dosbox-x-compat.h b/sw/include/dosbox-x-compat.h index 25f461e..e2e13cc 100644 --- a/sw/include/dosbox-x-compat.h +++ b/sw/include/dosbox-x-compat.h @@ -31,10 +31,7 @@ static INLINE uint16_t host_readw(ConstHostPt off) { return __builtin_bswap16(*(uint16_t *)off); } -#ifdef DEBUG -#define LOG_MSG(msg, ...) printf(msg "\n", ##__VA_ARGS__); -#define DEBUG_LOG_MSG(msg, ...) printf(msg "\n", ##__VA_ARGS__); -#else -#define LOG_MSG(...) (void)0 -#define DEBUG_LOG_MSG(...) (void)0 -#endif +#include "pg_debug.h" + +#define LOG_MSG(msg, ...) DBG_PRINTF(msg "\n", ##__VA_ARGS__) +#define DEBUG_LOG_MSG(msg, ...) DBG_PRINTF(msg "\n", ##__VA_ARGS__) diff --git a/sw/include/pg_debug.h b/sw/include/pg_debug.h new file mode 100644 index 0000000..9811362 --- /dev/null +++ b/sw/include/pg_debug.h @@ -0,0 +1,48 @@ +/* + * PicoGUS Debug Printing Framework + * + * Copyright (C) 2024 Ian Scott + * + * Master switch: PGDEBUG (set via CMake: -DPGDEBUG=ON) + * When PGDEBUG is not defined, all debug macros compile to ((void)0). + * + * Module-level switches (PGDEBUG_GUS, PGDEBUG_CDROM, etc.) provide + * per-module granularity within debug builds. + * + * ERR_PUTS outputs fatal error strings via UART directly, bypassing + * printf/stdio entirely. Works even with pico_set_printf_implementation(none). + */ + +#pragma once + +#include "hardware/uart.h" +#include "hardware/gpio.h" + +/* ---- Fatal error output: always active, bypasses printf ---- */ +static inline void _pg_err_puts(const char *s) { + uart_inst_t *uart = uart0; + if (!uart_is_enabled(uart)) { + uart_init(uart, PICO_DEFAULT_UART_BAUD_RATE); + gpio_set_function(PICO_DEFAULT_UART_TX_PIN, GPIO_FUNC_UART); + } + uart_puts(uart, s); + uart_puts(uart, "\r\n"); +} +#define ERR_PUTS(s) _pg_err_puts(s) + +/* ---- Debug macros: gated on PGDEBUG ---- */ +#ifdef PGDEBUG + +#include + +#define DBG_PRINTF(fmt, ...) printf(fmt, ##__VA_ARGS__) +#define DBG_PUTS(s) puts(s) +#define DBG_PUTCHAR(c) putchar(c) + +#else + +#define DBG_PRINTF(fmt, ...) ((void)0) +#define DBG_PUTS(s) ((void)0) +#define DBG_PUTCHAR(c) ((void)0) + +#endif /* PGDEBUG */ diff --git a/sw/mke/mke.c b/sw/mke/mke.c index 8c3aa05..049e124 100644 --- a/sw/mke/mke.c +++ b/sw/mke/mke.c @@ -59,7 +59,7 @@ Data (input) = base + 2 (base) #include "../cdrom/cdrom.h" #include "../cdrom/cdrom_image_manager.h" -#include +#include "../include/pg_debug.h" #define mke_log(fmt, ...) @@ -115,14 +115,14 @@ void MKE_COMMAND(uint8_t value) { switch(mke.command_buffer[0]) { case 06: //TRAY OUT cdrom_fifo_clear(&cdrom.info_fifo); - printf("TRAY OUT\n"); + DBG_PRINTF("TRAY OUT\n"); cdman_unload_image(&cdrom); cdrom_output_status(&cdrom); mke.tray_open=true; break; case 07: //TRAY IN cdrom_fifo_clear(&cdrom.info_fifo); - printf("TRAY IN\n"); + DBG_PRINTF("TRAY IN\n"); cdman_reload_image(&cdrom); cdrom_output_status(&cdrom); mke.tray_open=false; diff --git a/sw/mpuplay.cpp b/sw/mpuplay.cpp index 47f30c7..8ff7a65 100644 --- a/sw/mpuplay.cpp +++ b/sw/mpuplay.cpp @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include "include/pg_debug.h" #include "system/flash_settings.h" extern Settings settings; @@ -30,7 +30,7 @@ extern Settings settings; #include "mpu401/export.h" void play_mpu() { - puts("starting core 1 MPU"); + DBG_PUTS("starting core 1 MPU"); // flash_safe_execute_core_init(); #ifdef USB_STACK @@ -40,7 +40,7 @@ void play_mpu() { // Init PIC on this core so it handles timers PIC_Init(); - puts("pic inited on core 1"); + DBG_PUTS("pic inited on core 1"); MPU401_Init(settings.MPU.delaySysex, settings.MPU.fakeAllNotesOff); for (;;) { diff --git a/sw/multifw/bootloader.c b/sw/multifw/bootloader.c index 3a06602..4a8e55b 100644 --- a/sw/multifw/bootloader.c +++ b/sw/multifw/bootloader.c @@ -6,7 +6,7 @@ #include #include "flash_firmware.h" #include "system/flash_settings.h" -#include +#include "../include/pg_debug.h" static uint32_t sStart = 0; static const uint32_t offset[NR_OF_FIRMWARES] = {FLASH_FIRMWARE1, FLASH_FIRMWARE2, FLASH_FIRMWARE3, FLASH_FIRMWARE4, FLASH_FIRMWARE5, FLASH_FIRMWARE6}; @@ -27,7 +27,7 @@ uint8_t read_permMode(void) int main(void) { stdio_init_all(); - printf("picogus bootloader\n"); + DBG_PUTS("picogus bootloader"); stdio_flush(); uint8_t firmware_nr = (uint8_t) (0x000000FF & watchdog_hw->scratch[3]); diff --git a/sw/ne2000/ne2000.c b/sw/ne2000/ne2000.c index 8873072..c52ad66 100644 --- a/sw/ne2000/ne2000.c +++ b/sw/ne2000/ne2000.c @@ -34,7 +34,7 @@ Modifications for use with Pico by Kevin Moonlight (me@yyzkevin.com) */ -#include +#include "../include/pg_debug.h" #include #include #include @@ -142,31 +142,31 @@ void PG_Wifi_Connect(const char* ssid, const char* pass) { strlcpy(PG_Wifi_info.WIFI_KEY, pass, 64); } if (!PG_Wifi_info.WIFI_SSID[0]) { - printf("No SSID set\n"); + DBG_PRINTF("No SSID set\n"); return; } - printf("Joining SSID: %s\n", PG_Wifi_info.WIFI_SSID); + DBG_PRINTF("Joining SSID: %s\n", PG_Wifi_info.WIFI_SSID); int ConnectErr=cyw43_arch_wifi_connect_async( PG_Wifi_info.WIFI_SSID, PG_Wifi_info.WIFI_KEY, PG_Wifi_info.WIFI_KEY[0] == 0 ? CYW43_AUTH_OPEN : CYW43_AUTH_WPA2_MIXED_PSK ); - if (ConnectErr==0) printf("Connection started\n"); - else printf("Connection Error: %d", ConnectErr); + if (ConnectErr==0) DBG_PRINTF("Connection started\n"); + else DBG_PRINTF("Connection Error: %d", ConnectErr); } void PG_Wifi_Reconnect(void) { int32_t rssi; cyw43_ioctl(&cyw43_state, CYW43_IOCTL_GET_RSSI, sizeof(rssi), (uint8_t*)&rssi, CYW43_ITF_STA); - printf("rssi %d ", rssi); + DBG_PRINTF("rssi %d ", rssi); if (rssi == 0) { PG_Wifi_Connect(NULL, NULL); return; } int16_t status = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA); - printf("status: %d\n", status); + DBG_PRINTF("status: %d\n", status); switch(status) { case CYW43_LINK_JOIN : @@ -180,22 +180,22 @@ void PG_Wifi_Reconnect(void) uint8_t PG_EnableWifi(void) { if (cyw43_arch_init()) { - printf("Init failed\n"); + DBG_PRINTF("Init failed\n"); return 1; } cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); - printf("cyw43_wifi_set_up(&cyw43_state,CYW43_ITF_STA,true,CYW43_COUNTRY_WORLDWIDE);\n"); + DBG_PRINTF("cyw43_wifi_set_up(&cyw43_state,CYW43_ITF_STA,true,CYW43_COUNTRY_WORLDWIDE);\n"); cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_WORLDWIDE); cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_STA, PG_Wifi_info.cyw43_mac); - printf("WIFI Address: %02x:%02x:%02x:%02x:%02x:%02x\n\r", + DBG_PRINTF("WIFI Address: %02x:%02x:%02x:%02x:%02x:%02x\n\r", PG_Wifi_info.cyw43_mac[0], PG_Wifi_info.cyw43_mac[1], PG_Wifi_info.cyw43_mac[2], PG_Wifi_info.cyw43_mac[3], PG_Wifi_info.cyw43_mac[4], PG_Wifi_info.cyw43_mac[5]); cyw43_wifi_pm(&cyw43_state, cyw43_pm_value(CYW43_NO_POWERSAVE_MODE, 20, 1, 1, 1)); nic = ne2000_init(); - printf("Inited\n"); + DBG_PRINTF("Inited\n"); return 0; } diff --git a/sw/ne2000/ne2000.h b/sw/ne2000/ne2000.h index 119f7f1..2704618 100644 --- a/sw/ne2000/ne2000.h +++ b/sw/ne2000/ne2000.h @@ -20,7 +20,7 @@ typedef enum { NE2000_NE2000, NE2000_RTL8029AS } ne2000_type; #define NETBLOCKING 0 // we won't block our pcap -//#define NE2000_DEBUG +#define PGDEBUG_NE2000 0 #define BX_RESET_HARDWARE 0 #define BX_RESET_SOFTWARE 1 diff --git a/sw/ne2000play.cpp b/sw/ne2000play.cpp index 78a0e91..ff19255 100644 --- a/sw/ne2000play.cpp +++ b/sw/ne2000play.cpp @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include #include +#include "include/pg_debug.h" + #if PICO_ON_DEVICE #include "hardware/clocks.h" @@ -49,7 +50,7 @@ void play_ne2000() { // Init PIC on this core so it handles timers PIC_Init(); - puts("starting core 1 ne2000"); + DBG_PUTS("starting core 1 ne2000"); PG_EnableWifi(); PG_Wifi_Connect(settings.WiFi.ssid, settings.WiFi.password); @@ -69,7 +70,7 @@ void play_ne2000() { } if (((time_us_32() >> 21) & 0x1) == 0x1) { if (flag == false) { - putchar('='); + DBG_PUTCHAR('='); PG_Wifi_Reconnect(); flag = true; } diff --git a/sw/opl/emu8950.c b/sw/opl/emu8950.c index eb06065..d40ce30 100644 --- a/sw/opl/emu8950.c +++ b/sw/opl/emu8950.c @@ -445,7 +445,7 @@ static void initializeTables() { #define CAR(o, x) (&(o)->slot[((x) << 1) | 1]) #define BIT(s, b) (((s) >> (b)) & 1) -#if OPL_DEBUG +#if PGDEBUG_OPL static void _debug_print_patch(OPL_SLOT *slot) { OPL_PATCH *p = slot->patch; printf("[slot#%d am:%d pm:%d eg:%d kr:%d ml:%d kl:%d tl:%d ws:%d fb:%d A:%d D:%d S:%d R:%d]\n", slot->number, // @@ -566,7 +566,7 @@ static void commit_slot_update(OPL_SLOT *slot, uint8_t notesel) { } } -#if OPL_DEBUG +#if PGDEBUG_OPL if (slot->last_eg_state != slot->eg_state) { _debug_print_slot_info(slot); slot->last_eg_state = slot->eg_state; diff --git a/sw/opl/emu8950.h b/sw/opl/emu8950.h index 58c877d..c6158d6 100644 --- a/sw/opl/emu8950.h +++ b/sw/opl/emu8950.h @@ -8,7 +8,7 @@ extern "C" { #endif -#define OPL_DEBUG 0 +#define PGDEBUG_OPL 0 /* mask */ #define OPL_MASK_CH(x) (1 << (x)) @@ -61,7 +61,7 @@ typedef struct __OPL_SLOT { uint32_t update_requests; /* flags to debounce update */ -#if OPL_DEBUG +#if PGDEBUG_OPL uint8_t last_eg_state; #endif } OPL_SLOT; diff --git a/sw/picogus.cpp b/sw/picogus.cpp index 5bf0fec..131d522 100644 --- a/sw/picogus.cpp +++ b/sw/picogus.cpp @@ -16,8 +16,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include +#include "include/pg_debug.h" #include "pico/stdlib.h" #include "pico/multicore.h" #include "hardware/adc.h" @@ -426,7 +426,7 @@ __force_inline void write_picogus_high(uint8_t value) { /* printf("%s\n", settings.WiFi.password); */ break; case CMD_WIFIAPPLY: - printf("Applying wifi settings: %s %s\n", settings.WiFi.ssid, settings.WiFi.password); + DBG_PRINTF("Applying wifi settings: %s %s\n", settings.WiFi.ssid, settings.WiFi.password); #ifdef PICOW PG_Wifi_Connect(settings.WiFi.ssid, settings.WiFi.password); #endif @@ -507,14 +507,14 @@ __force_inline void write_picogus_high(uint8_t value) { // For multifw case CMD_BOOTMODE: settings.startupMode = value; - printf("requesting startup mode: %u\n", value); + DBG_PRINTF("requesting startup mode: %u\n", value); break; case CMD_SAVE: queueSaveSettings = true; break; case CMD_REBOOT: watchdog_hw->scratch[3] = settings.startupMode; - printf("rebooting into mode: %u\n", settings.startupMode); + DBG_PRINTF("rebooting into mode: %u\n", settings.startupMode); watchdog_reboot(0, 0, 0); break; case CMD_DEFAULTS: @@ -642,7 +642,7 @@ __force_inline uint8_t read_picogus_high(void) { return 0x04; // EOT } ret = cdrom.image_list[cur_read_idx][cur_read++]; - putchar(ret); + DBG_PUTCHAR(ret); if (ret == 0) { // Null terminated ++cur_read_idx; cur_read = 0; @@ -727,7 +727,7 @@ void processSettings(void) { #endif #ifdef CDROM cdrom_port_test = settings.CD.basePort >> 4; - printf("cdrom base port: %x\n", settings.CD.basePort); + DBG_PRINTF("cdrom base port: %x\n", settings.CD.basePort); cdman_set_autoadvance(settings.CD.autoAdvance); #endif if (BOARD_TYPE == PICOGUS_2) { @@ -1228,14 +1228,14 @@ int main() #else stdio_init_all(); #endif - puts(firmware_string); + DBG_PUTS(firmware_string); io_rw_32 *reset_reason = (io_rw_32 *) (VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET); if (*reset_reason & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_POR_BITS) { - puts("I was reset due to power on reset or brownout detection."); + DBG_PUTS("I was reset due to power on reset or brownout detection."); } else if (*reset_reason & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_RUN_BITS) { - puts("I was reset due to the RUN pin (either manually or due to ISA RESET signal)"); + DBG_PUTS("I was reset due to the RUN pin (either manually or due to ISA RESET signal)"); } else if(*reset_reason & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_PSM_RESTART_BITS) { - puts("I was reset due the debug port"); + DBG_PUTS("I was reset due the debug port"); } // Load settings from flash @@ -1254,12 +1254,12 @@ int main() // Read several times to let ADC stabilize adc_read(); adc_read(); adc_read(); adc_read(); adc_read(); uint16_t result = adc_read(); - printf("ADC value: 0x%03x... ", result); + DBG_PRINTF("ADC value: 0x%03x... ", result); gpio_put(25, 0); gpio_deinit(25); if (result > 0x100) { - puts("Running on Pico-based board (PicoGUS v1.1+, PicoGUS Femto)"); + DBG_PUTS("Running on Pico-based board (PicoGUS v1.1+, PicoGUS Femto)"); // On Pico-based board (PicoGUS v1.1+, PicoGUS Femto) #ifndef PICOW LED_PIN = 1 << PICO_DEFAULT_LED_PIN; @@ -1269,7 +1269,7 @@ int main() BOARD_TYPE = PICO_BASED; } else { // On chipdown board (PicoGUS v2.0) - puts("Running on PicoGUS v2.0"); + DBG_PUTS("Running on PicoGUS v2.0"); LED_PIN = 1 << 23; gpio_init(23); gpio_set_dir(23, GPIO_OUT); @@ -1294,7 +1294,7 @@ int main() gpio_set_drive_strength(IRQ_PIN, GPIO_DRIVE_STRENGTH_12MA); #ifdef SOUND_MPU - puts("Initing MIDI UART..."); + DBG_PUTS("Initing MIDI UART..."); uart_init(UART_ID, 31250); uart_set_translate_crlf(UART_ID, false); uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE); @@ -1308,18 +1308,18 @@ int main() #ifdef PSRAM_CORE0 #ifdef PSRAM - puts("Initing PSRAM..."); + DBG_PUTS("Initing PSRAM..."); // Try different PSRAM strategies if (BOARD_TYPE == PICOGUS_2) { psram_spi = psram_spi_init_clkdiv(pio1, -1, psram_clkdiv /* clkdiv */, false /* fudge */); #if TEST_PSRAM // Only bother to test every 97th address if (test_psram(&psram_spi, 97) == 1) { - printf("Default PSRAM strategy of no fudge not working, switching to fudge\n"); + DBG_PRINTF("Default PSRAM strategy of no fudge not working, switching to fudge\n"); psram_spi_uninit(psram_spi, false /* fudge */); psram_spi = psram_spi_init_clkdiv(pio1, -1, psram_clkdiv /* clkdiv */, true /* fudge */); if (test_psram(&psram_spi, 97) == 1) { - printf("Error: No PSRAM strategies found to work!\n"); + ERR_PUTS("Error: No PSRAM strategies found to work!"); err_blink(); } } @@ -1331,7 +1331,7 @@ int main() psram_spi_uninit(psram_spi, true /* fudge */); psram_spi = psram_spi_init_clkdiv(pio1, -1, psram_clkdiv /* clkdiv */, false /* fudge */); if (test_psram(&psram_spi, 97) == 1) { - printf("Error: No PSRAM strategies found to work!\n"); + ERR_PUTS("Error: No PSRAM strategies found to work!"); err_blink(); } } @@ -1343,14 +1343,14 @@ int main() #ifdef SOUND_SB #ifdef SOUND_WSS - puts("Initializing WSS AD1848"); + DBG_PUTS("Initializing WSS AD1848"); #else - puts("Initializing SoundBlaster DSP"); + DBG_PUTS("Initializing SoundBlaster DSP"); #endif // init actually happens on core 1 in play_adlib() #endif // SOUND_SB #ifdef SOUND_OPL - puts("Creating OPL"); + DBG_PUTS("Creating OPL"); OPL_Pico_Init(0); multicore_launch_core1(&play_adlib); #endif @@ -1361,7 +1361,7 @@ int main() #endif #ifdef SOUND_GUS - puts("Creating GUS"); + DBG_PUTS("Creating GUS"); GUS_OnReset(); multicore_launch_core1(&play_gus); #endif // SOUND_GUS @@ -1373,7 +1373,7 @@ int main() #endif // SOUND_MPU #if (SOUND_TANDY || SOUND_CMS) - puts("Creating psgsound"); + DBG_PUTS("Creating psgsound"); multicore_launch_core1(&play_psg); #endif // (SOUND_TANDY || SOUND_CMS) @@ -1381,14 +1381,14 @@ int main() extern void PIC_ActivateIRQ(void); extern void PIC_DeActivateIRQ(void); - puts("Creating NE2000"); + DBG_PUTS("Creating NE2000"); multicore_launch_core1(&play_ne2000); #endif #ifdef USB_JOYSTICK // Init joystick as centered with no buttons pressed joystate_struct = {127, 127, 127, 127, 0xf}; - puts("Config joystick PWM"); + DBG_PUTS("Config joystick PWM"); pwm_config pwm_c = pwm_get_default_config(); // TODO better calibrate this pwm_config_set_clkdiv(&pwm_c, pwm_clkdiv); @@ -1400,7 +1400,7 @@ extern void PIC_DeActivateIRQ(void); pwm_init(3, &pwm_c, true); #endif // USB_JOYSTICK #ifdef USB_MOUSE - puts("Config USB Mouse emulation"); + DBG_PUTS("Config USB Mouse emulation"); uartemu_init(0); sermouse_init(settings.Mouse.protocol, settings.Mouse.reportRate, settings.Mouse.sensitivity); sermouse_attach_uart(); @@ -1417,7 +1417,7 @@ extern void PIC_DeActivateIRQ(void); gpio_pull_down(IOCHRDY_PIN); gpio_set_dir(IOCHRDY_PIN, GPIO_OUT); - puts("Enabling bus transceivers..."); + DBG_PUTS("Enabling bus transceivers..."); // waggle ADS to set BUSOE latch gpio_init(ADS_PIN); gpio_set_dir(ADS_PIN, GPIO_OUT); @@ -1425,23 +1425,23 @@ extern void PIC_DeActivateIRQ(void); busy_wait_ms(10); gpio_put(ADS_PIN, 0); - puts("Starting ISA bus PIO..."); + DBG_PUTS("Starting ISA bus PIO..."); // gpio_set_drive_strength(ADS_PIN, GPIO_DRIVE_STRENGTH_12MA); gpio_set_slew_rate(ADS_PIN, GPIO_SLEW_RATE_FAST); uint iow_offset = pio_add_program(pio0, &iow_program); pio_sm_claim(pio0, IOW_PIO_SM); - printf("iow sm: %u\n", IOW_PIO_SM); + DBG_PRINTF("iow sm: %u\n", IOW_PIO_SM); uint ior_offset = pio_add_program(pio0, &ior_program); pio_sm_claim(pio0, IOR_PIO_SM); - printf("ior sm: %u\n", IOR_PIO_SM); + DBG_PRINTF("ior sm: %u\n", IOR_PIO_SM); iow_program_init(pio0, IOW_PIO_SM, iow_offset, iow_clkdiv); ior_program_init(pio0, IOR_PIO_SM, ior_offset); #ifdef USE_IRQ - puts("Enabling IRQ on ISA IOR/IOW events"); + DBG_PUTS("Enabling IRQ on ISA IOR/IOW events"); irq_set_enabled(PIO0_IRQ_1, false); pio_set_irq1_source_enabled(pio0, pio_get_rx_fifo_not_empty_interrupt_source(IOW_PIO_SM), true); pio_set_irq1_source_enabled(pio0, pio_get_rx_fifo_not_empty_interrupt_source(IOR_PIO_SM), true); diff --git a/sw/psgplay.cpp b/sw/psgplay.cpp index 7630560..244e874 100644 --- a/sw/psgplay.cpp +++ b/sw/psgplay.cpp @@ -16,9 +16,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include +#include "include/pg_debug.h" + #if PICO_ON_DEVICE #include "hardware/clocks.h" @@ -107,13 +108,13 @@ struct audio_buffer_pool *init_audio() { } void play_psg() { - puts("starting core 1 psg"); + DBG_PUTS("starting core 1 psg"); // flash_safe_execute_core_init(); #if defined(USB_MOUSE) || defined(SOUND_MPU) // Init PIC on this core so it handles timers PIC_Init(); - puts("pic inited on core 1"); + DBG_PUTS("pic inited on core 1"); #endif #ifdef USB_STACK // Init TinyUSB for joystick support diff --git a/sw/rp2040-psram b/sw/rp2040-psram index 7786c93..419375d 160000 --- a/sw/rp2040-psram +++ b/sw/rp2040-psram @@ -1 +1 @@ -Subproject commit 7786c93ec8d02dbb4f94a2e99645b25fb4abc2db +Subproject commit 419375d5af2af2c72af63cdab6c6df2c558a3a9c diff --git a/sw/sbdsp/sbdsp.cpp b/sw/sbdsp/sbdsp.cpp index c32d3c3..1c1bed8 100644 --- a/sw/sbdsp/sbdsp.cpp +++ b/sw/sbdsp/sbdsp.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include "../include/pg_debug.h" #include #include "system/pico_pic.h" #include "audio/volctrl.h" @@ -574,7 +574,7 @@ void sbdsp_set_dma(uint8_t dma) { static void sbmixer_reset(void); void sbdsp_init() { - puts("Initing ISA DMA PIO..."); + DBG_PUTS("Initing ISA DMA PIO..."); SBDSP_DMA_isr_pt = sbdsp_dma_isr; sbdsp.outbox = 0xAA; @@ -1397,7 +1397,7 @@ void sbdsp_write(uint8_t address, uint8_t value) { #endif break; } - if (sbdsp.dav_dsp) printf("WARN - DAV_DSP OVERWRITE\n"); + if (sbdsp.dav_dsp) DBG_PRINTF("WARN - DAV_DSP OVERWRITE\n"); sbdsp.inbox = value; sbdsp.dav_dsp = 1; break; diff --git a/sw/sbplay.cpp b/sw/sbplay.cpp index 669e154..c037c19 100644 --- a/sw/sbplay.cpp +++ b/sw/sbplay.cpp @@ -16,9 +16,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include +#include "include/pg_debug.h" + #if PICO_ON_DEVICE #include "hardware/clocks.h" @@ -223,7 +224,7 @@ void audio_sample_handler(void) { } void play_adlib() { - puts("starting core 1"); + DBG_PUTS("starting core 1"); // flash_safe_execute_core_init(); uint32_t start, end; set_volume(CMD_OPLVOL); @@ -274,7 +275,7 @@ void play_adlib() { set_volume(CMD_SBVOL); #endif - printf("OPL stereo pipeline started\n"); + DBG_PRINTF("OPL stereo pipeline started\n"); #ifdef CDROM cd_fifo = cdrom_audio_fifo_peek(&cdrom); diff --git a/sw/system/flash_settings.c b/sw/system/flash_settings.c index 6cc9d3b..1c6f1f8 100644 --- a/sw/system/flash_settings.c +++ b/sw/system/flash_settings.c @@ -17,9 +17,9 @@ */ #include -#include #include "flash_settings.h" +#include "../include/pg_debug.h" #include "overclock.h" #include "hardware/flash.h" #include "hardware/sync.h" @@ -173,13 +173,13 @@ static void migrateSettings(Settings* settings, uint8_t oldVersion) { void loadSettings(Settings* settings, bool migrate) { - printf("copying settings to %u from %u with size %u\n", settings, XIP_BASE + SETTINGS_SECTOR, sizeof(Settings)); + DBG_PRINTF("copying settings to %u from %u with size %u\n", settings, XIP_BASE + SETTINGS_SECTOR, sizeof(Settings)); memcpy(settings, (void *)(XIP_BASE + SETTINGS_SECTOR), sizeof(Settings)); /* stdio_flush(); */ if (settings->magic != SETTINGS_MAGIC || settings->version > SETTINGS_VERSION) { // No valid settings found, use defaults - puts("No valid settings found, using defaults"); + DBG_PUTS("No valid settings found, using defaults"); stdio_flush(); memcpy(settings, &defaultSettings, sizeof(Settings)); return; @@ -187,7 +187,7 @@ void loadSettings(Settings* settings, bool migrate) if (migrate && settings->version < SETTINGS_VERSION) { // Older version found - preserve existing fields, apply defaults to new ones - printf("Migrating settings from version %d to %d\n", settings->version, SETTINGS_VERSION); + DBG_PRINTF("Migrating settings from version %d to %d\n", settings->version, SETTINGS_VERSION); stdio_flush(); migrateSettings(settings, settings->version); } @@ -201,20 +201,20 @@ void saveSettings(const Settings* settings) uint8_t data[FLASH_PAGE_SIZE] = {0}; static_assert(sizeof(Settings) < FLASH_PAGE_SIZE, "Settings struct doesn't fit inside one flash page"); memcpy(data, settings, sizeof(Settings)); - printf("doing settings save: "); + DBG_PRINTF("doing settings save: "); // No need for flash_safe_execute or stop second core, since it will not touch flash // If the 2nd core will ever touch flash, reconsider this approach! // Clock down the RP2040 so the flash at its default 1/2 clock divider is within spec (<=133MHz) set_sys_clock_khz(240000, true); - putchar('/'); + DBG_PUTCHAR('/'); uint32_t ints = save_and_disable_interrupts(); flash_range_erase(SETTINGS_SECTOR, FLASH_SECTOR_SIZE); // last sector - putchar('/'); + DBG_PUTCHAR('/'); flash_range_program(SETTINGS_SECTOR, data, FLASH_PAGE_SIZE); restore_interrupts(ints); overclock_370mhz(); - printf("settings saved"); + DBG_PRINTF("settings saved"); } void getDefaultSettings(Settings* settings) diff --git a/sw/system/pico_reflash.c b/sw/system/pico_reflash.c index dbb41f6..c481703 100644 --- a/sw/system/pico_reflash.c +++ b/sw/system/pico_reflash.c @@ -18,8 +18,8 @@ #include "pico_reflash.h" -#include #include +#include "../include/pg_debug.h" #include "hardware/clocks.h" #include "pico/multicore.h" #include "pico/stdlib.h" @@ -61,15 +61,15 @@ static void pico_firmware_process_block(void) { if (uf2_buf.uf2.magicStart0 != 0x0A324655 || uf2_buf.uf2.magicStart1 != 0x9E5D5157 || uf2_buf.uf2.magicEnd != 0x0AB16F30) { // Invalid UF2 file - puts("Invalid UF2 data!"); + ERR_PUTS("Invalid UF2 data!"); pico_firmware_reset(PICO_FIRMWARE_ERROR); return; } pico_firmware_status = PICO_FIRMWARE_BUSY; if (pico_firmware_curBlock == 0) { - puts("Starting firmware write..."); + DBG_PUTS("Starting firmware write..."); pico_firmware_numBlocks = uf2_buf.uf2.numBlocks; - printf("numBlocks: %u\n", pico_firmware_numBlocks); + DBG_PRINTF("numBlocks: %u\n", pico_firmware_numBlocks); pico_firmware_payloadSize = uf2_buf.uf2.payloadSize; uint32_t totalSize = pico_firmware_numBlocks * pico_firmware_payloadSize; // Point of no return... erasing the flash! @@ -81,11 +81,11 @@ static void pico_firmware_process_block(void) uint32_t ints = save_and_disable_interrupts(); flash_range_program(curAddress, uf2_buf.uf2.data, pico_firmware_payloadSize); restore_interrupts(ints); - printf("curBlock: %u\n", pico_firmware_curBlock); + DBG_PRINTF("curBlock: %u\n", pico_firmware_curBlock); ++pico_firmware_curBlock; if (pico_firmware_curBlock == pico_firmware_numBlocks) { // Final block has been written - puts("Final block written."); + DBG_PUTS("Final block written."); pico_firmware_status = PICO_FIRMWARE_DONE; } else { pico_firmware_status = PICO_FIRMWARE_WRITING; @@ -99,7 +99,7 @@ void pico_firmware_write(uint8_t data) } void firmware_loop() { - puts("starting core 1"); + DBG_PUTS("starting core 1"); for (;;) { if (!multicore_fifo_rvalid()) { @@ -120,7 +120,7 @@ void firmware_loop() { static void pico_firmware_reboot() { - puts("Rebooting!"); + DBG_PUTS("Rebooting!"); // Stop second core multicore_reset_core1(); // Go back to stock speed diff --git a/sw/usb_hid/hid_app.c b/sw/usb_hid/hid_app.c index 8b8f885..4b2d4d7 100644 --- a/sw/usb_hid/hid_app.c +++ b/sw/usb_hid/hid_app.c @@ -25,6 +25,7 @@ * */ +#include "../include/pg_debug.h" #include "tusb.h" #ifdef USB_JOYSTICK #include "xinput_host.h" @@ -208,19 +209,19 @@ static inline void process_sony_ds4(uint8_t const* report, uint16_t len) // therefore report_desc = NULL, desc_len = 0 void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) { - printf("HID dev:%d inst:%d mounted\r\n", dev_addr, instance); + DBG_PRINTF("HID dev:%d inst:%d mounted\r\n", dev_addr, instance); // Interface protocol (hid_interface_protocol_enum_t) const char* protocol_str[] = { "n/a", "kbd", "mouse" }; uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); - printf("HID protocol=%s\r\n", protocol_str[itf_protocol]); + DBG_PRINTF("HID protocol=%s\r\n", protocol_str[itf_protocol]); // By default host stack will use activate boot protocol on supported interface. // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) if (itf_protocol == HID_ITF_PROTOCOL_NONE) { hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); - printf("HID has %u reports \r\n", hid_info[instance].report_count); + DBG_PRINTF("HID has %u reports \r\n", hid_info[instance].report_count); } else { // force boot protocol tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_BOOT); @@ -229,14 +230,14 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re // request to receive report // tuh_hid_report_received_cb() will be invoked when report is available if (!tuh_hid_receive_report(dev_addr, instance)) { - printf("err(%d:%d) can't recv HID report\r\n", dev_addr, instance); + DBG_PRINTF("err(%d:%d) can't recv HID report\r\n", dev_addr, instance); } } // Invoked when device with hid interface is un-mounted void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { - printf("HID dev:%d inst:%d unmounted\r\n", dev_addr, instance); + DBG_PRINTF("HID dev:%d inst:%d unmounted\r\n", dev_addr, instance); } //--------------------------------------------------------------------+ @@ -276,7 +277,7 @@ static inline void process_generic_report(uint8_t dev_addr, uint8_t instance, ui } if (!rpt_info) { - printf("Couldn't find the report info for this report !\r\n"); + DBG_PRINTF("Couldn't find the report info for this report !\r\n"); return; } @@ -332,7 +333,7 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons // continue to request to receive report if (!tuh_hid_receive_report(dev_addr, instance)) { - printf("err(%d:%d) can't recv HID report\r\n", dev_addr, instance); + DBG_PRINTF("err(%d:%d) can't recv HID report\r\n", dev_addr, instance); } } @@ -390,7 +391,7 @@ void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, xinputh_i void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf) { - printf("XINPUT MOUNTED %02x %d\n", dev_addr, instance); + DBG_PRINTF("XINPUT MOUNTED %02x %d\n", dev_addr, instance); // If this is a Xbox 360 Wireless controller we need to wait for a connection packet // on the in pipe before setting LEDs etc. So just start getting data until a controller is connected. if (xinput_itf->type == XBOX360_WIRELESS && xinput_itf->connected == false) { @@ -434,6 +435,6 @@ void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_inter void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance) { - printf("XINPUT UNMOUNTED %02x %d\n", dev_addr, instance); + DBG_PRINTF("XINPUT UNMOUNTED %02x %d\n", dev_addr, instance); } #endif diff --git a/sw/usbplay.cpp b/sw/usbplay.cpp index 0d31c37..cdfb5bb 100644 --- a/sw/usbplay.cpp +++ b/sw/usbplay.cpp @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include "include/pg_debug.h" #include "tusb.h" #include "mouse/8250uart.h" @@ -77,14 +77,14 @@ struct audio_buffer_pool *init_audio() { #endif // CDROM void play_usb() { - puts("starting core 1 USB"); + DBG_PUTS("starting core 1 USB"); // flash_safe_execute_core_init(); // board_init(); // Init PIC on this core so it handles timers PIC_Init(); - puts("pic inited on core 1"); + DBG_PUTS("pic inited on core 1"); // init host stack on configured roothub port tuh_init(BOARD_TUH_RHPORT); From 6a9915a908e36e90065763b839d2242ff7984ba9 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Sat, 11 Apr 2026 10:39:54 -0600 Subject: [PATCH 08/15] MPU: fix sysex delay - it's in ms, not us --- sw/mpu401/midi.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/sw/mpu401/midi.c b/sw/mpu401/midi.c index b4ad206..7f4f75f 100755 --- a/sw/mpu401/midi.c +++ b/sw/mpu401/midi.c @@ -293,23 +293,17 @@ __force_inline static bool send_midi_byte() { if (midi.sysex.start) { /* if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x7F) { */ if (midi.sysex.buf[5] == 0x7F) { - midi.sysex.delay = 290; // PicoGUS // All Parameters reset - /* } else if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) { */ + midi.sysex.delay = 290000; // All Parameters reset (290 ms) } else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) { - midi.sysex.delay = 145; // PicoGUS // Viking Child - /* } else if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) { */ + midi.sysex.delay = 145000; // Viking Child (145 ms) } else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) { - midi.sysex.delay = 30; // PicoGUS // Dark Sun 1 + midi.sysex.delay = 30000; // Dark Sun 1 (30 ms) } else { - // HardMPU: - /* midi.sysex.delay = ((((midi.sysex.usedbufs*SYSEX_SIZE)+midi.sysex.used)/2)+2); */ - // DOSBox: - /* midi.sysex.delay = (Bitu)(((float)(midi.sysex.used) * 1.25f) * 1000.0f / 3125.0f) + 2 */ - // PicoGUS: 1.25 * 1000.0 / 3125.0 = 0.4 - /* midi.sysex.delay = ((midi.sysex.usedbufs*SYSEX_SIZE)+midi.sysex.used) * 0.4f + 2; */ - midi.sysex.delay = midi.sysex.used * 0.4f + 2; - if (midi.sysex.extra_delay && midi.sysex.delay < 40) { - midi.sysex.delay = 40; + // DOSBox formula: (used * 1.25 * 1000 / 3125) + 2 ms = (used * 0.4 + 2) ms + // Convert to µs for time_us_32() comparison + midi.sysex.delay = (Bitu)(midi.sysex.used * 400.0f) + 2000; + if (midi.sysex.extra_delay && midi.sysex.delay < 40000) { + midi.sysex.delay = 40000; } } midi.sysex.start = time_us_32(); // PicoGUS From 7254e90245f29eabd20708fe554108582c5cfc09 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Sat, 11 Apr 2026 12:38:27 -0600 Subject: [PATCH 09/15] MPU: use 256 byte MIDI output buffer like real MPU-401 --- sw/mpu401/export.h | 1 + sw/mpu401/midi.c | 47 ++++++++++++++++++++++++---------------------- sw/mpu401/mpu401.c | 38 ++++++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/sw/mpu401/export.h b/sw/mpu401/export.h index 1892921..46c0f8e 100755 --- a/sw/mpu401/export.h +++ b/sw/mpu401/export.h @@ -44,6 +44,7 @@ Bit8u MPU401_ReadStatus(void); void MPU401_WriteData(Bit8u val, bool crit); Bit8u QueueUsed(); void send_midi_bytes(int maxbytes); +uint32_t MIDI_BufferFree(void); void MIDI_RawOutByte(Bit8u data); #ifdef __cplusplus diff --git a/sw/mpu401/midi.c b/sw/mpu401/midi.c index 7f4f75f..b5ddd88 100755 --- a/sw/mpu401/midi.c +++ b/sw/mpu401/midi.c @@ -45,19 +45,12 @@ static critical_section_t midi_crit; typedef uint32_t Bit32u; typedef int32_t Bits; -/* RAWBUF: This is the buffer for outgoing MIDI data. The larger the buffer, - the less likely it is to overrun when SysEx delay is enabled and large SysEx - transfers are occurring. */ -//#define RAWBUF 14336 -#ifdef MPU_ONLY -#define SYSEX_SIZE 8192 // sysex buffer for delay calculation -#define RAWBUF 65536 -#define RAWBUF_BITS 65535 -#else -#define SYSEX_SIZE 2048 // sysex buffer for delay calculation -#define RAWBUF 8192 -#define RAWBUF_BITS 8191 -#endif // MPU_ONLY +/* RAWBUF: Ring buffer for outgoing MIDI data. Flow control via the status + register (bit 6/DRR) throttles the host to MIDI wire speed. Matches + the 256 byte size of a real MPU-401 */ +#define SYSEX_SIZE 32 // sysex buffer for header inspection +#define RAWBUF 256 +#define RAWBUF_BITS 255 typedef struct ring_buffer { uint8_t buffer[RAWBUF]; @@ -67,6 +60,13 @@ typedef struct ring_buffer { static ring_buffer midi_out_buff = { {0}, 0, 0 }; +volatile bool midi_output_busy = false; + +uint32_t MIDI_BufferFree(void) { + uint32_t used = (midi_out_buff.head - midi_out_buff.tail) & RAWBUF_BITS; + return (RAWBUF - 1) - used; +} + /* SOFTMPU: Note tracking for RA-50 */ #define MAX_TRACKED_CHANNELS 16 #define MAX_TRACKED_NOTES 8 @@ -162,6 +162,8 @@ __force_inline static void PlayMsg(Bit8u* msg, Bitu len) } critical_section_exit(&midi_crit); } + uint32_t used = (midi_out_buff.head - midi_out_buff.tail) & RAWBUF_BITS; + midi_output_busy = used >= (RAWBUF - 2); } __force_inline static void send_midi_byte_now(Bit8u byte) { @@ -252,14 +254,11 @@ void MIDI_RawOutByte(Bit8u data) { } } -__force_inline static bool send_midi_byte() { +__force_inline static bool send_midi_byte() { if (uart_tx_status()) return false; // can't send yet + if (midi_out_buff.head == midi_out_buff.tail) return false; // nothing to send /* putchar('s'); */ critical_section_enter_blocking(&midi_crit); - if (midi_out_buff.head == midi_out_buff.tail) { - critical_section_exit(&midi_crit); - return false; // nothing to send - } if (midi.sysex.start) { Bit32u passed_ticks = time_us_32() - midi.sysex.start; if (passed_ticks < midi.sysex.delay) { // still waiting for sysex delay @@ -268,10 +267,12 @@ __force_inline static bool send_midi_byte() { } } Bit8u data = midi_out_buff.buffer[midi_out_buff.tail]; - midi_out_buff.tail = (midi_out_buff.tail + 1) & RAWBUF_BITS; // increment tail, wrap to 0 if we're at the end + midi_out_buff.tail = (midi_out_buff.tail + 1) & RAWBUF_BITS; + uint32_t used = (midi_out_buff.head - midi_out_buff.tail) & RAWBUF_BITS; + midi_output_busy = used >= (RAWBUF - 2); critical_section_exit(&midi_crit); - if (midi.sysex.status==0xf0) { // Start + if (midi.sysex.status==0xf0) { // Start /* putchar('s'); */ if (!(data&0x80)) { /* @@ -282,12 +283,14 @@ __force_inline static bool send_midi_byte() { */ output_to_uart(data); - if (midi.sysex.used<(SYSEX_SIZE-1)) midi.sysex.buf[midi.sysex.used++] = data; + if (midi.sysex.used < SYSEX_SIZE) midi.sysex.buf[midi.sysex.used] = data; + midi.sysex.used++; /* midi.sysex.buf[midi.sysex.used++] = data; */ return true; } else { output_to_uart(0xf7); - midi.sysex.buf[midi.sysex.used++] = 0xf7; + if (midi.sysex.used < SYSEX_SIZE) midi.sysex.buf[midi.sysex.used] = 0xf7; + midi.sysex.used++; midi.sysex.status = 0xf7; /*LOG(LOG_ALL,LOG_NORMAL)("Play sysex; address:%02X %02X %02X, length:%4d, delay:%3d", midi.sysex.buf[5], midi.sysex.buf[6], midi.sysex.buf[7], midi.sysex.used, midi.sysex.delay);*/ if (midi.sysex.start) { diff --git a/sw/mpu401/mpu401.c b/sw/mpu401/mpu401.c index 6bc5388..5c2cf3a 100755 --- a/sw/mpu401/mpu401.c +++ b/sw/mpu401/mpu401.c @@ -47,11 +47,17 @@ typedef uint16_t Bit16u; typedef int32_t Bits; typedef int8_t Bit8s; +// autodetect when Gateway runs by watching for it setting this message on the MT-32's LCD +static const char* gateway_msg = "\xf0\x41\x10\x16\x12\x20\x00\x00 Gateway"; +static uint8_t gateway_pos = 0; +static uint8_t gateway_len = 22; + #include "system/pico_pic.h" void MIDI_Init(bool delaysysex,bool fakeallnotesoff); void MIDI_RawOutByte(Bit8u data); bool MIDI_Available(void); +extern volatile bool midi_output_busy; static uint32_t MPU401_EventHandler(Bitu val); static PIC_TimerEvent MPU401_Event = { @@ -95,6 +101,7 @@ typedef enum MpuDataType MpuDataType; /* SOFTMPU */ #define MSG_MPU_CLOCK 0xfd #define MSG_MPU_ACK 0xfe +static bool config_versionfix = false; static bool config_delaysysex = false; static bool config_fakeallnotesoff = false; @@ -160,7 +167,7 @@ __force_inline static void ClrQueue(void) { __force_inline Bit8u MPU401_ReadStatus(void) { /* SOFTMPU */ critical_section_enter_blocking(&mpu_crit); uint8_t ret=0x3f; /* Bits 6 and 7 clear */ - if (mpu.state.cmd_pending) ret|=0x40; + if (mpu.state.cmd_pending || midi_output_busy) ret|=0x40; if (!mpu.queue_used) ret|=0x80; critical_section_exit(&mpu_crit); return ret; @@ -272,8 +279,14 @@ __force_inline void MPU401_WriteCommand(Bit8u val, bool crit) { /* SOFTMPU */ QueueByte(0); goto write_command_return; case 0xac: /* Request version */ - QueueByte(MSG_MPU_ACK); - QueueByte(MPU401_VERSION); + if (config_versionfix) { + // Hack for Gateway + QueueByte(MPU401_VERSION); + QueueByte(MSG_MPU_ACK); + } else { + QueueByte(MSG_MPU_ACK); + QueueByte(MPU401_VERSION); + } goto write_command_return; case 0xad: /* Request revision */ QueueByte(MSG_MPU_ACK); @@ -376,6 +389,16 @@ __force_inline void MPU401_WriteData(Bit8u val, bool crit) { /* SOFTMPU */ static Bit8u length,cnt,posd; /* SOFTMPU */ if (mpu.mode==M_UART) { MIDI_RawOutByte(val); + // autodetect when Gateway runs + if (val == gateway_msg[gateway_pos]) { + ++gateway_pos; + if (gateway_pos == gateway_len) { + config_versionfix = true; + gateway_pos = 0; + } + } else { + gateway_pos = 0; + } goto write_return; } switch (mpu.state.command_byte) { /* 0xe# command data */ @@ -581,6 +604,14 @@ __force_inline static void MPU401_IntelligentOut(Bit8u chan) { } __force_inline static void UpdateTrack(Bit8u chan) { + /* block until MIDI output buffer has room, matching real MPU-401 (busy-wait + with SCI TX interrupt draining), checks for >= 4 free bytes before writing + a message. */ + if (mpu.playbuf[chan].type != T_OVERFLOW) { + while (MIDI_BufferFree() < 4) { + send_midi_bytes(1); + } + } MPU401_IntelligentOut(chan); if (mpu.state.amask&(1< Date: Sat, 11 Apr 2026 14:48:57 -0600 Subject: [PATCH 10/15] pgusinit: print loaded CD image after /cdload --- pgusinit/pgusinit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgusinit/pgusinit.c b/pgusinit/pgusinit.c index f746ba1..dab95f2 100755 --- a/pgusinit/pgusinit.c +++ b/pgusinit/pgusinit.c @@ -461,7 +461,7 @@ static int wait_for_cd_load(void) print_string(CMD_CDERROR); return 98; } - return 0; + return print_cdimage_current(); } From 452f863936d37f8c557bc55f83e5416bb8b2fd8a Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Sun, 12 Apr 2026 23:57:35 -0600 Subject: [PATCH 11/15] MKE: enlarge sector buffer that audio sectors land in during playback --- sw/cdrom/cdrom.c | 171 ++++++++++++++++++--------------- sw/cdrom/cdrom.h | 14 ++- sw/cdrom/cdrom_image.c | 8 ++ sw/cdrom/cdrom_image_backend.c | 27 +++++- sw/cdrom/cdrom_image_backend.h | 1 + 5 files changed, 140 insertions(+), 81 deletions(-) diff --git a/sw/cdrom/cdrom.c b/sw/cdrom/cdrom.c index 1bae581..2f81306 100644 --- a/sw/cdrom/cdrom.c +++ b/sw/cdrom/cdrom.c @@ -232,6 +232,9 @@ cdrom_stop(cdrom_t *dev) { if (dev->cd_status > CD_STATUS_DATA_ONLY) dev->cd_status = CD_STATUS_STOPPED; + dev->audio_staging_sectors = 0; + dev->audio_staging_index = 0; + dev->audio_sector_consumed_pairs = 0; #if USE_CD_AUDIO_FIFO fifo_reset(&dev->audio_fifo); #endif @@ -264,71 +267,75 @@ bool cdrom_audio_callback(cdrom_t *dev, uint32_t len) { return false; } - // --- Fill audio_fifo using data from audio_sector_buffer, reading new sectors as needed --- - // Loop until FIFO has 'len' stereo pairs, or is full, or an error/EoT occurs. + // --- Fill audio_fifo from staging buffer, batch-reading new sectors as needed --- while (fifo_level(&dev->audio_fifo) < len && ret) { - uint32_t pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs; + // Advance to next sector in batch if current one is fully consumed + if (dev->audio_sector_consumed_pairs >= STEREO_SAMPLES_PER_SECTOR) { + dev->audio_staging_index++; + dev->audio_sector_consumed_pairs = 0; + } - 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) { - // Muted: "Fake" a read by filling the sector buffer with silence - cdrom_log("CD-ROM %i: Muted. Faking read of LBA %08X with silence.\n", dev->id, dev->seek_pos); - read_successful = true; - memset(dev->audio_sector_buffer, 0, RAW_SECTOR_SIZE); - } else { - read_successful = dev->ops->read_sector(dev, CD_READ_AUDIO, dev->audio_sector_buffer, dev->seek_pos); - } - if (read_successful) { - /* putchar('r'); */ - cdrom_log("CD-ROM %i: Read LBA %08X successful\n", dev->id, dev->seek_pos); - dev->seek_pos++; - 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; - ret = false; // Mark error - break; // Exit fill loop: read error - } - } else { // End of disc + // Read a new batch when the staging buffer is exhausted + if (dev->audio_staging_index >= dev->audio_staging_sectors) { + if (dev->seek_pos >= dev->cd_end) { cdrom_log("CD-ROM %i: Playing completed (reached cd_end)\n", dev->id); dev->cd_status = CD_STATUS_PLAYING_COMPLETED; - ret = false; // Mark end of track - break; // Exit fill loop: end of track + ret = false; + break; + } + + uint32_t avail = dev->cd_end - dev->seek_pos; + uint32_t batch = (avail < AUDIO_SECTOR_BATCH) ? avail : AUDIO_SECTOR_BATCH; + int sectors_read; + + if (dev->audio_muted_soft) { + cdrom_log("CD-ROM %i: Muted. Faking batch read of %u sectors with silence.\n", dev->id, batch); + memset(dev->audio_sector_buffer, 0, batch * RAW_SECTOR_SIZE); + sectors_read = batch; + } else { + sectors_read = dev->ops->read_audio_sectors(dev, dev->audio_sector_buffer, dev->seek_pos, batch); + } + + if (sectors_read > 0) { + cdrom_log("CD-ROM %i: Batch read %u sectors at LBA %08X\n", dev->id, sectors_read, dev->seek_pos); + dev->seek_pos += sectors_read; + dev->audio_staging_sectors = sectors_read; + dev->audio_staging_index = 0; + dev->audio_sector_consumed_pairs = 0; + } else { + cdrom_log("CD-ROM %i: Batch read at LBA %08X failed\n", dev->id, dev->seek_pos); + dev->cd_status = CD_STATUS_STOPPED; + ret = false; + break; } } - // At this point, if ret is still true, pairs_remaining_in_sector should be > 0. + // Point into the current sector within the staging buffer + dev->current_sector_samples = (sample_pair *)(dev->audio_sector_buffer + + dev->audio_staging_index * RAW_SECTOR_SIZE); + + uint32_t pairs_remaining = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs; uint32_t space_in_fifo = fifo_free_space(&dev->audio_fifo); + uint32_t pairs_to_transfer = (pairs_remaining < space_in_fifo) ? + pairs_remaining : 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", 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_pairs], pairs_to_transfer); if (added_ok) { - /* putchar('y'); */ dev->audio_sector_consumed_pairs += pairs_to_transfer; } else { DBG_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, pairs_to_transfer); - ret = false; // Treat as an error + ret = false; break; } - } // End while (fifo_level < len && ret) + } - cdrom_log("CD-ROM %i: Audio cb. FIFO level: %u. Staging consumed: %d. Ret %d\n", + cdrom_log("CD-ROM %i: Audio cb. FIFO level: %u. Staging consumed: %d/%d. Ret %d\n", dev->id, fifo_level(&dev->audio_fifo), - dev->audio_sector_consumed_pairs, ret); + dev->audio_staging_index, dev->audio_staging_sectors, ret); return ret; } #endif // USE_CD_AUDIO_FIFO @@ -340,49 +347,56 @@ uint32_t cdrom_audio_callback_simple(cdrom_t *dev, int16_t *buffer, uint32_t len 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 buffer has 'len' samples, or an error/EoT occurs. + // --- Fill buffer from staging buffer, batch-reading new sectors as needed --- while (pairs_produced < pairs_requested) { - uint32_t pairs_remaining_in_sector = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs; + // Advance to next sector in batch if current one is fully consumed + if (dev->audio_sector_consumed_pairs >= STEREO_SAMPLES_PER_SECTOR) { + dev->audio_staging_index++; + dev->audio_sector_consumed_pairs = 0; + } - 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) { - // Muted: "Fake" a read by filling the sector buffer with silence - cdrom_log("CD-ROM %i: Muted. Faking read of LBA %08X with silence.\n", dev->id, dev->seek_pos); - read_successful = true; - memset(dev->audio_sector_buffer, 0, RAW_SECTOR_SIZE); - } else { - read_successful = dev->ops->read_sector(dev, CD_READ_AUDIO, dev->audio_sector_buffer, dev->seek_pos); - } - if (read_successful) { - /* putchar('r'); */ - cdrom_log("CD-ROM %i: Read LBA %08X successful\n", dev->id, dev->seek_pos); - dev->seek_pos++; - 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; - break; // Exit fill loop: read error - } - } else { // End of disc + // Read a new batch when the staging buffer is exhausted + if (dev->audio_staging_index >= dev->audio_staging_sectors) { + if (dev->seek_pos >= dev->cd_end) { cdrom_log("CD-ROM %i: Playing completed (reached cd_end)\n", dev->id); dev->cd_status = CD_STATUS_PLAYING_COMPLETED; - break; // Exit fill loop: end of track + break; + } + + uint32_t avail = dev->cd_end - dev->seek_pos; + uint32_t batch = (avail < AUDIO_SECTOR_BATCH) ? avail : AUDIO_SECTOR_BATCH; + int sectors_read; + + if (dev->audio_muted_soft) { + cdrom_log("CD-ROM %i: Muted. Faking batch read of %u sectors with silence.\n", dev->id, batch); + memset(dev->audio_sector_buffer, 0, batch * RAW_SECTOR_SIZE); + sectors_read = batch; + } else { + sectors_read = dev->ops->read_audio_sectors(dev, dev->audio_sector_buffer, dev->seek_pos, batch); + } + + if (sectors_read > 0) { + cdrom_log("CD-ROM %i: Batch read %u sectors at LBA %08X\n", dev->id, sectors_read, dev->seek_pos); + dev->seek_pos += sectors_read; + dev->audio_staging_sectors = sectors_read; + dev->audio_staging_index = 0; + dev->audio_sector_consumed_pairs = 0; + } else { + cdrom_log("CD-ROM %i: Batch read at LBA %08X failed\n", dev->id, dev->seek_pos); + dev->cd_status = CD_STATUS_STOPPED; + break; } } - // At this point, pairs_remaining_in_sector should be > 0. + // Point into the current sector within the staging buffer + dev->current_sector_samples = (sample_pair *)(dev->audio_sector_buffer + + dev->audio_staging_index * RAW_SECTOR_SIZE); + + uint32_t pairs_remaining = STEREO_SAMPLES_PER_SECTOR - dev->audio_sector_consumed_pairs; 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; + uint32_t pairs_to_transfer = (pairs_remaining < space_in_buffer) ? + pairs_remaining : space_in_buffer; - /* 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++) { sample_pair sp = dev->current_sector_samples[dev->audio_sector_consumed_pairs + i]; @@ -499,6 +513,9 @@ uint8_t cdrom_audio_playmsf(cdrom_t *dev, int m,int s, int f, int M, int S, int dev->seek_pos = pos; dev->cd_end = pos2; + dev->audio_staging_sectors = 0; + dev->audio_staging_index = 0; + dev->audio_sector_consumed_pairs = 0; #if USE_CD_AUDIO_FIFO fifo_reset(&dev->audio_fifo); #endif diff --git a/sw/cdrom/cdrom.h b/sw/cdrom/cdrom.h index 7a237a1..4223fb8 100644 --- a/sw/cdrom/cdrom.h +++ b/sw/cdrom/cdrom.h @@ -57,6 +57,11 @@ #define SAMPLES_PER_SECTOR 1176 #define STEREO_SAMPLES_PER_SECTOR 588 +/* number of CD sectors to read at once. Larger values reduce USB command + * overhead (one SCSI READ10 instead of N) hopefully helping marginal USB drives + * keep up with 44.1 kHz audio. */ +#define AUDIO_SECTOR_BATCH 4 + #define STAT_READY 0x01 #define STAT_PLAY 0x08 #define STAT_ERROR 0x10 @@ -123,6 +128,7 @@ typedef struct cdrom_ops_t { int (*is_track_pre)(struct cdrom *dev, uint32_t lba); int (*sector_size)(struct cdrom *dev, uint32_t lba); int (*read_sector)(struct cdrom *dev, int type, uint8_t *b, uint32_t lba); + int (*read_audio_sectors)(struct cdrom *dev, uint8_t *b, uint32_t lba, uint32_t count); int (*track_type)(struct cdrom *dev, uint32_t lba); void (*exit)(struct cdrom *dev); } cdrom_ops_t; @@ -196,9 +202,11 @@ typedef struct cdrom { const char *error_str; // int16_t cd_buffer[BUF_SIZE]; - uint8_t audio_sector_buffer[RAW_SECTOR_SIZE]; - sample_pair *current_sector_samples; // Convenience pointer: (sample_pair*)audio_sector_buffer - int audio_sector_consumed_pairs; // Stereo pairs consumed from current_sector_samples + uint8_t audio_sector_buffer[AUDIO_SECTOR_BATCH * RAW_SECTOR_SIZE]; + sample_pair *current_sector_samples; // Points to current sector's audio data within staging buffer + int audio_sector_consumed_pairs; // Stereo pairs consumed from current sector (0..588) + int audio_staging_sectors; // Number of sectors loaded in staging buffer + int audio_staging_index; // Which sector in the batch is being consumed #if USE_CD_AUDIO_FIFO audio_fifo_t audio_fifo; diff --git a/sw/cdrom/cdrom_image.c b/sw/cdrom/cdrom_image.c index ce0b9bb..5241de7 100644 --- a/sw/cdrom/cdrom_image.c +++ b/sw/cdrom/cdrom_image.c @@ -176,6 +176,13 @@ image_read_sector(struct cdrom *dev, int type, uint8_t *b, uint32_t lba) } } +static int +image_read_audio_sectors(struct cdrom *dev, uint8_t *b, uint32_t lba, uint32_t count) +{ + cd_img_t *img = (cd_img_t *) dev->image; + return cdi_read_audio_sectors(img, b, lba, count); +} + static int image_track_type(cdrom_t *dev, uint32_t lba) { @@ -216,6 +223,7 @@ static const cdrom_ops_t cdrom_image_ops = { image_is_track_pre, image_sector_size, image_read_sector, + image_read_audio_sectors, image_track_type, image_exit }; diff --git a/sw/cdrom/cdrom_image_backend.c b/sw/cdrom/cdrom_image_backend.c index 2d4c6e0..9cd60a0 100644 --- a/sw/cdrom/cdrom_image_backend.c +++ b/sw/cdrom/cdrom_image_backend.c @@ -103,7 +103,7 @@ bin_read(void *priv, uint8_t *buffer, uint32_t seek, size_t count) } //if (fread(buffer, count, 1, tf->fp) != 1) { - if (f_read(tf->fp,buffer, count,&bytes_read) != FR_OK) { + if (f_read(tf->fp,buffer, count,&bytes_read) != FR_OK || bytes_read != count) { cdrom_image_backend_log("CDROM: binary_read failed during read!\n"); return 0; } @@ -484,6 +484,31 @@ cdi_read_sectors(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector, uint3 return success; } +/* Batch read consecutive audio sectors in a single file I/O. Returns the number + of sectors read, 0 on error. Clips to track boundary so the caller may get + fewer than requested. */ +int +cdi_read_audio_sectors(cd_img_t *cdi, uint8_t *buffer, uint32_t sector, uint32_t num) +{ + int track = cdi_get_track(cdi, sector) - 1; + if (track < 0 || num == 0) + return 0; + + track_t *trk = &cdi->tracks[track]; + + /* clip to track boundary */ + uint32_t track_end = trk->start + trk->length; + if (sector + num > track_end) + num = track_end - sector; + if (num == 0) + return 0; + + uint32_t seek = trk->skip + ((sector - trk->start) * trk->sector_size); + if (!trk->file->read(trk->file, buffer, seek, num * trk->sector_size)) + return 0; + return (int) num; +} + /* TODO: Do CUE+BIN images with a sector size of 2448 even exist? */ int cdi_read_sector_sub(cd_img_t *cdi, uint8_t *buffer, uint32_t sector) diff --git a/sw/cdrom/cdrom_image_backend.h b/sw/cdrom/cdrom_image_backend.h index 59d4f5f..4a70e22 100644 --- a/sw/cdrom/cdrom_image_backend.h +++ b/sw/cdrom/cdrom_image_backend.h @@ -106,6 +106,7 @@ extern int cdi_get_track(cd_img_t *cdi, uint32_t sector); extern int cdi_get_audio_sub(cd_img_t *cdi, uint32_t sector, uint8_t *attr, uint8_t *track, uint8_t *index, TMSF *rel_pos, TMSF *abs_pos); extern int cdi_read_sector(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector); extern int cdi_read_sectors(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector, uint32_t num); +extern int cdi_read_audio_sectors(cd_img_t *cdi, uint8_t *buffer, uint32_t sector, uint32_t count); extern int cdi_read_sector_sub(cd_img_t *cdi, uint8_t *buffer, uint32_t sector); extern int cdi_get_sector_size(cd_img_t *cdi, uint32_t sector); extern int cdi_is_mode2(cd_img_t *cdi, uint32_t sector); From 889dfa89c78479db659dc560acb5a88bb2fa4264 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Mon, 13 Apr 2026 08:37:56 -0600 Subject: [PATCH 12/15] Better messaging when BLASTER has problems --- pgusinit/pgusinit.c | 57 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/pgusinit/pgusinit.c b/pgusinit/pgusinit.c index dab95f2..1aa4d86 100755 --- a/pgusinit/pgusinit.c +++ b/pgusinit/pgusinit.c @@ -182,23 +182,48 @@ static void err_ultrasnd(void) } -static void err_blaster(void) +typedef enum { + BLASTER_UNSET, + BLASTER_MALFORMED, + BLASTER_DMA16_MISMATCH, + BLASTER_MISMATCH, +} blaster_err_t; + +static void err_blaster_format_help(void) { // "................................................................................\n" - fprintf(stderr, "ERROR: In SB mode but no BLASTER variable set or is malformed!\n"); fprintf(stderr, "The BLASTER environment variable must be set in the following format:\n"); fprintf(stderr, "\tset BLASTER=Axxx Iy Dz Hz Tw\n"); fprintf(stderr, "Where xxx = port, y = IRQ, z = DMA. w = SB type:\n"); fprintf(stderr, " 1 - SB 1.x, 2 - SB Pro 1 (dual OPL2), 3 - SB 2.0,\n"); fprintf(stderr, " 4 - SB Pro 2 (OPL3), 6 - SB 16\n"); - fprintf(stderr, "Port is set via /sbport xxx option; DMA and IRQ configued via jumper then also\n"); - fprintf(stderr, "set via /sbirq y and /sbdma y options.\n"); } -static void err_blaster16_dma_mismatch(void) +static void err_blaster(blaster_err_t reason) { - // "................................................................................\n" - fprintf(stderr, "ERROR: In SB 16 mode, low (Dx) and high (Hx) DMA in BLASTER variable must match!\n"); + // "................................................................................\n" + switch (reason) { + case BLASTER_UNSET: + fprintf(stderr, "ERROR: In SB mode but BLASTER environment variable is not set!\n"); + err_blaster_format_help(); + fprintf(stderr, "Then run pgusinit /sbenv to apply the BLASTER settings to the card.\n"); + break; + case BLASTER_MALFORMED: + fprintf(stderr, "ERROR: BLASTER environment variable is malformed!\n"); + fprintf(stderr, "Current value: BLASTER=%s\n", getenv("BLASTER")); + err_blaster_format_help(); + break; + case BLASTER_DMA16_MISMATCH: + fprintf(stderr, "ERROR: In SB 16 mode, low (Dx) and high (Hx) DMA in BLASTER variable must match!\n"); + fprintf(stderr, "Current value: BLASTER=%s\n", getenv("BLASTER")); + break; + case BLASTER_MISMATCH: + fprintf(stderr, "ERROR: SB settings on card don't match the BLASTER environment variable!\n"); + fprintf(stderr, "Double-check that PicoGUS is jumpered correctly for the desired IRQ and DMA,\n"); + fprintf(stderr, "then run pgusinit /sbenv to set card resources from the BLASTER variable.\n"); + fprintf(stderr, "Alternatively, use /sbport, /sbirq, /sbdma, /sbtype to set values directly.\n"); + break; + } } static void err_pigus(void) @@ -278,7 +303,7 @@ static int init_sb(void) char* blaster = getenv("BLASTER"); if (blaster == NULL) { - err_blaster(); + err_blaster(BLASTER_UNSET); return 1; } char blasterTemp[256]; @@ -299,8 +324,12 @@ static int init_sb(void) p = strtok(NULL, " "); } - if ((port == -1) || (irq == -1) || (dma8 == -1) || (sbtype > 6) || (dspver[sbtype] == 0) || ((sbtype == 6) && (dma8 != dma16))) { - err_blaster(); + if ((port == -1) || (irq == -1) || (dma8 == -1) || (sbtype > 6) || (dspver[sbtype] == 0)) { + err_blaster(BLASTER_MALFORMED); + return 2; + } + if ((sbtype == 6) && (dma8 != dma16)) { + err_blaster(BLASTER_DMA16_MISMATCH); return 2; } @@ -322,28 +351,28 @@ static int init_sb(void) outp(CONTROL_PORT, CMD_SBPORT); // Select port register uint16_t tmp_port = inpw(DATA_PORT_LOW); if (port != tmp_port) { - err_blaster(); + err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBIRQ); // Select IRQ register uint8_t tmp_irq = inp(DATA_PORT_HIGH); if (irq != tmp_irq) { - err_blaster(); + err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBDMA); // Select DMA register uint8_t tmp_dma = inp(DATA_PORT_HIGH); if (dma8 != tmp_dma) { - err_blaster(); + err_blaster(BLASTER_MISMATCH); return 2; } outp(CONTROL_PORT, CMD_SBTYPE); // Select SB type register uint8_t tmp_type = inp(DATA_PORT_HIGH); if (sbtype != tmp_type) { - err_blaster(); + err_blaster(BLASTER_MISMATCH); return 2; } } From 69a22d6b7bf49392ba999da614ac858348007f93 Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Mon, 13 Apr 2026 20:14:39 -0600 Subject: [PATCH 13/15] CDROM: Fix .iso in Win9x w/ MKEPanasonic driver by handling lead-out --- sw/cdrom/cdrom.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sw/cdrom/cdrom.c b/sw/cdrom/cdrom.c index 2f81306..95b49b1 100644 --- a/sw/cdrom/cdrom.c +++ b/sw/cdrom/cdrom.c @@ -582,8 +582,12 @@ uint8_t cdrom_read_toc(cdrom_t *dev, unsigned char *b, uint8_t track) { track_info_t ti; int first_track; int last_track; - dev->ops->get_tracks(dev, &first_track, &last_track); - if(track > last_track) return 0; + dev->ops->get_tracks(dev, &first_track, &last_track); + // Handle lead-out track + if (track == 0xaa) + track = last_track + 1; + else if (track > last_track) + return 0; dev->ops->get_track_info(dev, track, 0, &ti); b[0]=0x0; b[1]=ti.attr; From ded16d7b7aeb04b100f7bdfc9b9f0472cc1c21ca Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Mon, 13 Apr 2026 22:26:53 -0600 Subject: [PATCH 14/15] Update changelog for v4.1.0 --- sw/CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sw/CHANGELOG.md b/sw/CHANGELOG.md index 4f36eda..a0b4cb5 100644 --- a/sw/CHANGELOG.md +++ b/sw/CHANGELOG.md @@ -1,3 +1,32 @@ +# v4.1.0 + +## Fixes/improvements + +### SB mode + +- Sound Blaster ADPCM playback is now supported (4, 2.6, and 2-bit modes). This allows for sound effects in Duke Nukem 2 without needing to patch the game. Thanks to [Tube Time (@schlae)'s annotated disassembly of the SB DSP firmware](https://github.com/schlae/sb-firmware/tree/master) and [Torsten Stremlau (@TerrySoba)'s VocTool](https://github.com/TerrySoba/VocTool) for an MIT-licensed C implementation to use as a basis. +- Sample rates >44100Hz are now supported, for example 45454Hz, the fastest supported by SB16. +- SBMIDI is supported in addition to MPU-401 in SB mode. +- Fake ADC (recording) is supported. This records garbage data but should help SB16 sound card detection in some OSes. +- Fixes bug in passing DMA from BLASTER variable when running pgusinit. Thanks @andreacampanella for the fix in PR #134. +- More helpful error messages when the BLASTER variable does not match the port/IRQ/DMA settings stored on the PicoGUS. + +### MPU-401 + +- Behavior of the MPU-401 emulation is now much more in line with a real MPU-401: it has a 256 byte ring buffer for MIDI out data, and flow control is done via bit 6 of the status port. Previously, MPU support had no flow control and used a pretty enormous buffer to make up for that. The SRAM savings from the smaller buffer will allow for more PicoGUS features in the future. +- Sysex delay is now accurately calculated (we were using microseconds, not milliseconds). +- Re-introduces the song change fix for Frederik Pohl's Gateway. + +### CD-ROM + +- Read multiple sectors at a time during CD audio playback. Should help with pops/distortion on marginal USB drives. +- Fixes loading .iso images in Win9x w/ MKEPanasonic driver by handling lead-out. +- The loaded image is printed after a successful `pgusinit /cdload`. + +### General + +- SRAM usage is saved across the board by completely eliminating debug prints in release builds, as well as eliminating use of sscanf() type functions. These debug prints didn't go anywhere in release builds (since the UART is taken by MIDI out, and USB by joystick), but they still had runtime overhead in calling printf(), etc. + # v4.0.0 ## New features/changes From 2be3565e5cf21d6d57d68b032e8f6ea67d99c80c Mon Sep 17 00:00:00 2001 From: Ian Scott Date: Mon, 13 Apr 2026 22:28:47 -0600 Subject: [PATCH 15/15] Firmware v4.1.0 --- pgusinit/pgusinit.c | 2 +- sw/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pgusinit/pgusinit.c b/pgusinit/pgusinit.c index 1aa4d86..943f835 100755 --- a/pgusinit/pgusinit.c +++ b/pgusinit/pgusinit.c @@ -71,7 +71,7 @@ static void pageprintf(char *format, ...) static void banner(void) { - printf("PicoGUSinit v3.8.0 (c) 2025 Ian Scott - licensed under the GNU GPL v2\n"); + printf("PicoGUSinit v3.9.0 (c) 2025 Ian Scott - licensed under the GNU GPL v2\n"); } diff --git a/sw/CMakeLists.txt b/sw/CMakeLists.txt index f68d055..28245f6 100644 --- a/sw/CMakeLists.txt +++ b/sw/CMakeLists.txt @@ -19,7 +19,7 @@ set(PICO_COPY_TO_RAM 1) include(pico_sdk_import.cmake) project(picogus - VERSION "4.0.0" + VERSION "4.1.0" LANGUAGES C CXX ASM )