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 "nau88c21.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"
#if MQA_EN
#include "MQA_XMOS.h"
@@ -219,6 +222,11 @@ void switch_mode_by_c1_mode(unsigned c1_mode, unsigned force_reboot)
(void)force_reboot;
{
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);
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;
#if C1_DFS_EN
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
#if UAC1
#pragma unsafe arrays
@@ -74,6 +75,30 @@ void UserBufferManagement(unsigned sampsFromUsbToAudio[], unsigned sampsFromAudi
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)
{
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_write_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 副本 */
unsigned g_fps_game_select = 1;
unsigned g_fps_level_select = 2;
@@ -239,12 +243,36 @@ static void fps_xmos_modules_init(void)
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()
{
fps_xmos_modules_init();
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;
GET_SHARED_GLOBAL(ready, fps_buf_ready);

View File

@@ -55,6 +55,12 @@
#define C1_TILE1_ACTIVE 0
#define C1_TILE1_PENDING_SHUTDOWN 1
#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]. */
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_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_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_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]) ---- */
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 */
unsigned t; c1_tile1_tmr :> t;
c1_tile1_tmr when timerafter(t + C1_TILE1_CLK_SETTLE_TICKS) :> void;
SET_SHARED_GLOBAL(g_tile1_audio_ready, 1);
c1_tile1_state = C1_TILE1_ACTIVE;
/* Request FPS pipeline flush (clear stale buffer + algorithm)
* 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
{
@@ -161,6 +166,20 @@ void c1_tile1_power_tick(void)
c1_tile1_clock_down(g_tile1_lp_div);
}
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;
}
}