fix(c1): flush FPS pipeline + mute output to remove FPS on/off pop

FPS buffer (dsp_fps_out_buf) and algorithm delay-line state held stale samples across FPS-off periods, causing an occasional pop on re-enable. On CLOCKED_DOWN->ACTIVE, request fps1 to flush (zero buffers, reset positions, re-init library) before g_tile1_audio_ready=1, via a new FLUSHING state with req/ack handshake. Also arm a ~30ms proactive output mute in UserBufferManagement on every FPS toggle (g_fps_switch_mute) to mask the path-switch discontinuity in both directions. All C1_DFS_EN-guarded; factory unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Steven Dan
2026-06-22 20:25:34 +08:00
parent 23cd7498e5
commit c61e5f4db5
5 changed files with 88 additions and 2 deletions

View File

@@ -21,6 +21,9 @@
#include "xua_hid_report.h" #include "xua_hid_report.h"
#include "nau88c21.h" #include "nau88c21.h"
#include "tile1_clk.h" #include "tile1_clk.h"
#if C1_DFS_EN
extern unsigned g_fps_switch_mute; /* tile1_clk.xc: proactive mute on FPS toggle */
#endif
#include "dfu_upgrade.h" #include "dfu_upgrade.h"
#if MQA_EN #if MQA_EN
#include "MQA_XMOS.h" #include "MQA_XMOS.h"
@@ -219,6 +222,11 @@ void switch_mode_by_c1_mode(unsigned c1_mode, unsigned force_reboot)
(void)force_reboot; (void)force_reboot;
{ {
unsigned fps_val = (c1_mode == 4) ? 1u : 0u; unsigned fps_val = (c1_mode == 4) ? 1u : 0u;
/* Mute the output around the path switch so neither the stale FPS
* buffer/algorithm tail (off->on) nor the FPS<->bypass discontinuity
* (on<->off) is audible. UserBufferManagement clears the mute after
* ~C1_SWITCH_MUTE_SAMPLES. */
SET_SHARED_GLOBAL(g_fps_switch_mute, 1);
SET_SHARED_GLOBAL(g_3d_fps, fps_val); SET_SHARED_GLOBAL(g_3d_fps, fps_val);
debug_printf("switch_mode_by_c1_mode: c1_mode=%d -> g_3d_fps=%d (no reboot)\n", c1_mode, fps_val); debug_printf("switch_mode_by_c1_mode: c1_mode=%d -> g_3d_fps=%d (no reboot)\n", c1_mode, fps_val);
} }

View File

@@ -44,6 +44,7 @@ extern unsigned g_dnr_enable;
extern unsigned g_adc_loop; extern unsigned g_adc_loop;
#if C1_DFS_EN #if C1_DFS_EN
extern unsigned g_tile1_audio_ready; /* tile1_clk.xc: state-machine output */ extern unsigned g_tile1_audio_ready; /* tile1_clk.xc: state-machine output */
extern unsigned g_fps_switch_mute; /* tile1_clk.xc: proactive mute on FPS toggle */
#endif #endif
#if UAC1 #if UAC1
#pragma unsafe arrays #pragma unsafe arrays
@@ -74,6 +75,30 @@ void UserBufferManagement(unsigned sampsFromUsbToAudio[], unsigned sampsFromAudi
buffer_exchange((chanend)uc_br_data, sampsFromUsbToAudio, sampsFromAudioToUsb, 2); buffer_exchange((chanend)uc_br_data, sampsFromUsbToAudio, sampsFromAudioToUsb, 2);
} }
#if C1_DFS_EN
/* Proactive output mute around FPS on/off: zeroes the playback path for
* ~C1_SWITCH_MUTE_SAMPLES after each toggle, masking the path-switch pop
* and the stale FPS data tail until the flushed pipeline refills. */
{
static unsigned sw_mute_samps = 0;
static unsigned prev_sw_mute = 0;
unsigned sw_mute;
GET_SHARED_GLOBAL(sw_mute, g_fps_switch_mute);
if (sw_mute)
{
if (!prev_sw_mute) sw_mute_samps = 0; /* re-armed: restart count */
sampsFromUsbToAudio[0] = 0;
sampsFromUsbToAudio[1] = 0;
if (++sw_mute_samps >= C1_SWITCH_MUTE_SAMPLES)
{
SET_SHARED_GLOBAL(g_fps_switch_mute, 0);
sw_mute_samps = 0;
}
}
prev_sw_mute = sw_mute;
}
#endif
if (dnr_enable) if (dnr_enable)
{ {
dnr_exchange_buffer((int32_t *)&sampsFromAudioToUsb[0]); dnr_exchange_buffer((int32_t *)&sampsFromAudioToUsb[0]);

View File

@@ -29,6 +29,10 @@ short __attribute__((aligned (4))) dsp_fps_out_buf[FPS_SLOT_NUM][FPS_PLANAR_SAMP
unsigned fps_buf_ready = 0; unsigned fps_buf_ready = 0;
unsigned fps_write_pos = 0; unsigned fps_write_pos = 0;
unsigned fps_process_pos = 0; unsigned fps_process_pos = 0;
/* Flush handshake (tile0 -> tile1): tile0 requests a flush before resuming audio
* after FPS re-enable; fps1 clears stale buffer + algorithm state, then acks. */
unsigned g_fps_flush_req = 0;
unsigned g_fps_flush_ack = 0;
/* 改动原因tile0/tile1 内存不共享game/level/使能/fps_eq 须经 channel sync 写入 tile1 副本 */ /* 改动原因tile0/tile1 内存不共享game/level/使能/fps_eq 须经 channel sync 写入 tile1 副本 */
unsigned g_fps_game_select = 1; unsigned g_fps_game_select = 1;
unsigned g_fps_level_select = 2; unsigned g_fps_level_select = 2;
@@ -239,12 +243,36 @@ static void fps_xmos_modules_init(void)
fps_apply_module_enable(); fps_apply_module_enable();
} }
/* Clear stale FPS pipeline state (zero the triple-buffer, reset positions, and
* re-initialise the algorithm library to flush its delay-line state). Called by
* fps1 on a flush request from tile0 so old samples/state are not played out. */
static void fps_flush_pipeline(void)
{
memset(dsp_fps_input_buf, 0, sizeof(dsp_fps_input_buf));
memset(dsp_fps_out_buf, 0, sizeof(dsp_fps_out_buf));
SET_SHARED_GLOBAL(fps_write_pos, 0);
SET_SHARED_GLOBAL(fps_process_pos, 0);
SET_SHARED_GLOBAL(fps_buf_ready, 0);
fps_xmos_modules_init(); /* reset algorithm library (delay lines etc.) */
}
void fps1_dsp_proc_task() void fps1_dsp_proc_task()
{ {
fps_xmos_modules_init(); fps_xmos_modules_init();
while (1) while (1)
{ {
/* Service a flush request from tile0 (FPS re-enable): clear stale data
* before audio resumes, so old samples/state are not played out. */
unsigned flush_req;
GET_SHARED_GLOBAL(flush_req, g_fps_flush_req);
if (flush_req)
{
fps_flush_pipeline();
SET_SHARED_GLOBAL(g_fps_flush_req, 0);
SET_SHARED_GLOBAL(g_fps_flush_ack, 1);
}
unsigned ready; unsigned ready;
GET_SHARED_GLOBAL(ready, fps_buf_ready); GET_SHARED_GLOBAL(ready, fps_buf_ready);

View File

@@ -55,6 +55,12 @@
#define C1_TILE1_ACTIVE 0 #define C1_TILE1_ACTIVE 0
#define C1_TILE1_PENDING_SHUTDOWN 1 #define C1_TILE1_PENDING_SHUTDOWN 1
#define C1_TILE1_CLOCKED_DOWN 2 #define C1_TILE1_CLOCKED_DOWN 2
#define C1_TILE1_FLUSHING 3 /* waiting for FPS pipeline flush before audio resume */
/* Duration of the proactive output mute around an FPS on/off toggle, in audio
* samples (@ 48 kHz). Masks the path-switch discontinuity and covers the FPS
* pipeline flush + 1-frame refill. 2880 ~= 60 ms. */
#define C1_SWITCH_MUTE_SAMPLES 2880
/* One-time enable of the tile[1] core divider output path. MUST run on tile[1]. */ /* One-time enable of the tile[1] core divider output path. MUST run on tile[1]. */
void c1_tile1_clk_enable(void); void c1_tile1_clk_enable(void);

View File

@@ -16,8 +16,11 @@
unsigned g_audio_streaming = 0; /* 1 while USB audio is streaming */ unsigned g_audio_streaming = 0; /* 1 while USB audio is streaming */
unsigned g_tile1_audio_ready = 0; /* state-machine output: 1 => tile[0] may send to tile[1] */ unsigned g_tile1_audio_ready = 0; /* state-machine output: 1 => tile[0] may send to tile[1] */
unsigned g_tile1_lp_div = C1_TILE1_LP_XCORE_DIV_DEFAULT; unsigned g_tile1_lp_div = C1_TILE1_LP_XCORE_DIV_DEFAULT;
unsigned g_fps_switch_mute = 0; /* set on FPS toggle: UserBufferManagement zeroes output for C1_SWITCH_MUTE_SAMPLES */
extern unsigned g_3d_fps; /* defined in extra_i2s.xc: 0=bypass, 1=FPS */ extern unsigned g_3d_fps; /* defined in extra_i2s.xc: 0=bypass, 1=FPS */
extern unsigned g_fps_flush_req; /* defined in fps_wrapper.c: flush FPS pipeline before audio resume */
extern unsigned g_fps_flush_ack; /* defined in fps_wrapper.c: ack flush complete */
/* ---- state (tile[0]) ---- */ /* ---- state (tile[0]) ---- */
static unsigned c1_tile1_state = C1_TILE1_ACTIVE; static unsigned c1_tile1_state = C1_TILE1_ACTIVE;
@@ -152,8 +155,10 @@ void c1_tile1_power_tick(void)
c1_tile1_clock_up(); /* restore-before-send */ c1_tile1_clock_up(); /* restore-before-send */
unsigned t; c1_tile1_tmr :> t; unsigned t; c1_tile1_tmr :> t;
c1_tile1_tmr when timerafter(t + C1_TILE1_CLK_SETTLE_TICKS) :> void; c1_tile1_tmr when timerafter(t + C1_TILE1_CLK_SETTLE_TICKS) :> void;
SET_SHARED_GLOBAL(g_tile1_audio_ready, 1); /* Request FPS pipeline flush (clear stale buffer + algorithm)
c1_tile1_state = C1_TILE1_ACTIVE; * BEFORE audio resumes, so old samples are not played out. */
SET_SHARED_GLOBAL(g_fps_flush_req, 1);
c1_tile1_state = C1_TILE1_FLUSHING;
} }
else else
{ {
@@ -161,6 +166,20 @@ void c1_tile1_power_tick(void)
c1_tile1_clock_down(g_tile1_lp_div); c1_tile1_clock_down(g_tile1_lp_div);
} }
break; break;
case C1_TILE1_FLUSHING:
{
/* Wait for fps1 to finish flushing the FPS pipeline, then resume audio. */
unsigned ack;
GET_SHARED_GLOBAL(ack, g_fps_flush_ack);
if (ack)
{
SET_SHARED_GLOBAL(g_fps_flush_ack, 0);
SET_SHARED_GLOBAL(g_tile1_audio_ready, 1);
c1_tile1_state = C1_TILE1_ACTIVE;
}
}
break;
} }
} }