rebase: Phase E — VU peaks, frame counters, Audio Auto Haptics
Build now compiles. audio.cpp on top of upstream's state_mgr-based audio_loop adds three additive pieces: 1. VU peak meters for ch0/1 (spk_max) and ch2/3 (hap_max) tracked per-sample in the existing loop; written back to g_peak_spk / g_peak_hap after. Accessors decay 12.5% per read so the OLED VU bars fall back naturally over a few frames. 2. Monotonic byte-flow counters: g_usb_frames++ after tud_audio_read (the OLED Diagnostics screen + web emulator compute USB-aud/s and BT-0x32/s as delta over time). g_bt_packets++ after each 0x36 bt_write. 3. Audio Auto Haptics DSP — 1-pole LP + envelope follower + modulation + soft-clip per loteran/DS5Dongle 5d6bc2f, with our added 4th "Fallback" mode that fires only after the game's native haptic path has been silent for ~1 s (NATIVE_SILENT_TIMEOUT = 100 audio_loop calls). Preserves native HD haptics (Spider-Man Remastered) while filling in for games that send no haptic data (Ghost of Tsushima). Modes: 0=Off, 1=Fallback (default), 2=Mix, 3=Replace. Crucially the state_mgr-based state_set(pkt+13, 63) call is left untouched — that's upstream's correct replacement for our prior state_data restore hack, and it's load-bearing for speaker output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0593d79f5b
commit
8217d3a414
+105
-4
@@ -45,6 +45,34 @@ void set_headset(bool state) {
|
||||
plug_headset = state;
|
||||
}
|
||||
|
||||
// Stubs kept for OLED diag-screen compatibility. Upstream removed the opus
|
||||
// queue and audio FIFO drop tracking isn't wired here; OLED shows 0.
|
||||
uint32_t audio_fifo_drops() { return 0; }
|
||||
uint32_t opus_fifo_drops() { return 0; }
|
||||
|
||||
// Monotonic byte-flow counters for the OLED Diagnostics screen and the web
|
||||
// emulator's USB / BT rate display. Updated below.
|
||||
static volatile uint32_t g_usb_frames = 0;
|
||||
static volatile uint32_t g_bt_packets = 0;
|
||||
uint32_t audio_usb_frames() { return g_usb_frames; }
|
||||
uint32_t audio_bt_packets() { return g_bt_packets; }
|
||||
|
||||
// Rolling-peak meters for the OLED VU screen. Updated during audio_loop's
|
||||
// per-sample iteration, decayed 12.5 % on each read (so the bar falls back
|
||||
// over a few frames if the signal goes quiet).
|
||||
static volatile uint16_t g_peak_spk = 0;
|
||||
static volatile uint16_t g_peak_hap = 0;
|
||||
uint8_t audio_peak_speaker() {
|
||||
const uint16_t v = g_peak_spk;
|
||||
g_peak_spk = (uint16_t)((v * 7) / 8);
|
||||
return (uint8_t)(v >> 7);
|
||||
}
|
||||
uint8_t audio_peak_haptic() {
|
||||
const uint16_t v = g_peak_hap;
|
||||
g_peak_hap = (uint16_t)((v * 7) / 8);
|
||||
return (uint8_t)(v >> 7);
|
||||
}
|
||||
|
||||
void audio_loop() {
|
||||
// 1. 读取 USB 音频数据
|
||||
if (!tud_audio_available()) return;
|
||||
@@ -55,6 +83,7 @@ void audio_loop() {
|
||||
if (frames == 0) {
|
||||
return;
|
||||
}
|
||||
g_usb_frames += (uint32_t)frames;
|
||||
|
||||
static float audio_buf[512 * 2];
|
||||
static uint audio_buf_pos = 0;
|
||||
@@ -64,7 +93,43 @@ void audio_loop() {
|
||||
|
||||
const float audio_gain = mute[0] ? 0.0f : powf(10.0f, get_config().speaker_volume / 20.0f);
|
||||
const float haptics_gain = get_config().haptics_gain;
|
||||
uint16_t spk_max = g_peak_spk;
|
||||
uint16_t hap_max = g_peak_hap;
|
||||
|
||||
// ---- Audio Auto Haptics (borrowed from loteran/DS5Dongle 5d6bc2f) ----
|
||||
// Derives a haptic-feedback waveform from the speaker audio so games that
|
||||
// never write haptic data (e.g. Ghost of Tsushima on Linux+Steam) still
|
||||
// produce rumble. Mode 1 (Fallback, default) fires only when native is
|
||||
// silent → preserves native HD haptics in games that do send them.
|
||||
const uint8_t auto_mode = get_config().auto_haptics_enable;
|
||||
const float auto_gain = (auto_mode > 0) ? (get_config().auto_haptics_gain / 100.0f) * haptics_gain : 0.0f;
|
||||
static const float LP_COEFF[4] = { 0.01039f, 0.02074f, 0.03095f, 0.05123f };
|
||||
const float lp_a = LP_COEFF[get_config().auto_haptics_lowpass & 3];
|
||||
static float lp_l = 0.0f, lp_r = 0.0f;
|
||||
static float env_l = 0.0f, env_r = 0.0f;
|
||||
constexpr float ENV_ATK = 0.40f;
|
||||
constexpr float ENV_REL = 0.025f;
|
||||
constexpr int NATIVE_SILENT_TIMEOUT = 100;
|
||||
constexpr uint16_t NATIVE_THRESHOLD = 256;
|
||||
static int native_silent_count = NATIVE_SILENT_TIMEOUT * 2;
|
||||
const bool fallback_active = (auto_mode == 1) && (native_silent_count >= NATIVE_SILENT_TIMEOUT);
|
||||
|
||||
for (int i = 0; i < nframes; i++) {
|
||||
// VU peak tracking
|
||||
{
|
||||
int16_t sl = raw[i * INPUT_CHANNELS];
|
||||
int16_t sr = raw[i * INPUT_CHANNELS + 1];
|
||||
int16_t hl = raw[i * INPUT_CHANNELS + 2];
|
||||
int16_t hr = raw[i * INPUT_CHANNELS + 3];
|
||||
uint16_t a = (uint16_t)(sl < 0 ? -sl : sl);
|
||||
uint16_t b = (uint16_t)(sr < 0 ? -sr : sr);
|
||||
if (a > spk_max) spk_max = a;
|
||||
if (b > spk_max) spk_max = b;
|
||||
a = (uint16_t)(hl < 0 ? -hl : hl);
|
||||
b = (uint16_t)(hr < 0 ? -hr : hr);
|
||||
if (a > hap_max) hap_max = a;
|
||||
if (b > hap_max) hap_max = b;
|
||||
}
|
||||
#if !DISABLE_SPEAKER_PROC
|
||||
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS] / 32768.0f * audio_gain;
|
||||
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * audio_gain;
|
||||
@@ -80,10 +145,45 @@ void audio_loop() {
|
||||
audio_buf_pos = 0;
|
||||
}
|
||||
#endif
|
||||
in_buf[i * 2] = static_cast<WDL_ResampleSample>(clamp(raw[i * INPUT_CHANNELS + 2] / 32768.0f * haptics_gain,
|
||||
-1.0f, 1.0f));
|
||||
in_buf[i * 2 + 1] = static_cast<WDL_ResampleSample>(clamp(raw[i * INPUT_CHANNELS + 3] / 32768.0f * haptics_gain,
|
||||
-1.0f, 1.0f));
|
||||
float h_l = raw[i * INPUT_CHANNELS + 2] / 32768.0f * haptics_gain;
|
||||
float h_r = raw[i * INPUT_CHANNELS + 3] / 32768.0f * haptics_gain;
|
||||
|
||||
if (auto_mode > 0) {
|
||||
const float spk_l = raw[i * INPUT_CHANNELS ] / 32768.0f;
|
||||
const float spk_r = raw[i * INPUT_CHANNELS + 1] / 32768.0f;
|
||||
lp_l += lp_a * (spk_l - lp_l);
|
||||
lp_r += lp_a * (spk_r - lp_r);
|
||||
const float abs_l = lp_l < 0.0f ? -lp_l : lp_l;
|
||||
const float abs_r = lp_r < 0.0f ? -lp_r : lp_r;
|
||||
env_l = (abs_l > env_l) ? env_l + ENV_ATK * (abs_l - env_l)
|
||||
: env_l + ENV_REL * (abs_l - env_l);
|
||||
env_r = (abs_r > env_r) ? env_r + ENV_ATK * (abs_r - env_r)
|
||||
: env_r + ENV_REL * (abs_r - env_r);
|
||||
float al = lp_l * (1.0f + 3.0f * env_l) * auto_gain;
|
||||
float ar = lp_r * (1.0f + 3.0f * env_r) * auto_gain;
|
||||
al = al / (1.0f + (al < 0.0f ? -al : al));
|
||||
ar = ar / (1.0f + (ar < 0.0f ? -ar : ar));
|
||||
|
||||
if (auto_mode == 3) { // Replace
|
||||
h_l = al; h_r = ar;
|
||||
} else if (auto_mode == 2) { // Mix
|
||||
float m_l = h_l + al, m_r = h_r + ar;
|
||||
h_l = m_l / (1.0f + (m_l < 0.0f ? -m_l : m_l));
|
||||
h_r = m_r / (1.0f + (m_r < 0.0f ? -m_r : m_r));
|
||||
} else if (auto_mode == 1 && fallback_active) { // Fallback (default)
|
||||
h_l = al; h_r = ar;
|
||||
}
|
||||
}
|
||||
|
||||
in_buf[i * 2] = static_cast<WDL_ResampleSample>(clamp(h_l, -1.0f, 1.0f));
|
||||
in_buf[i * 2 + 1] = static_cast<WDL_ResampleSample>(clamp(h_r, -1.0f, 1.0f));
|
||||
}
|
||||
g_peak_spk = spk_max;
|
||||
g_peak_hap = hap_max;
|
||||
if (hap_max > NATIVE_THRESHOLD) {
|
||||
native_silent_count = 0;
|
||||
} else if (native_silent_count < NATIVE_SILENT_TIMEOUT * 2) {
|
||||
native_silent_count++;
|
||||
}
|
||||
|
||||
// 3. 48kHz -> 3kHz 重采样
|
||||
@@ -138,6 +238,7 @@ void audio_loop() {
|
||||
#endif
|
||||
|
||||
bt_write(pkt, sizeof(pkt));
|
||||
g_bt_packets++;
|
||||
haptic_buf_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -5,9 +5,21 @@
|
||||
#ifndef DS5_BRIDGE_AUDIO_H
|
||||
#define DS5_BRIDGE_AUDIO_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void audio_init();
|
||||
void audio_loop();
|
||||
void core1_entry();
|
||||
void set_headset(bool state);
|
||||
|
||||
// Accessors used by the optional OLED add-on (diag + VU meter screens).
|
||||
uint32_t audio_fifo_drops();
|
||||
uint32_t opus_fifo_drops();
|
||||
uint8_t audio_peak_speaker(); // 0..255, decays on read
|
||||
uint8_t audio_peak_haptic(); // 0..255, decays on read
|
||||
|
||||
// Byte-flow counters for the Diagnostics screen + web emulator.
|
||||
uint32_t audio_usb_frames();
|
||||
uint32_t audio_bt_packets();
|
||||
|
||||
#endif //DS5_BRIDGE_AUDIO_H
|
||||
Reference in New Issue
Block a user