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
+106
-5
@@ -45,6 +45,34 @@ void set_headset(bool state) {
|
|||||||
plug_headset = 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() {
|
void audio_loop() {
|
||||||
// 1. 读取 USB 音频数据
|
// 1. 读取 USB 音频数据
|
||||||
if (!tud_audio_available()) return;
|
if (!tud_audio_available()) return;
|
||||||
@@ -55,6 +83,7 @@ void audio_loop() {
|
|||||||
if (frames == 0) {
|
if (frames == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
g_usb_frames += (uint32_t)frames;
|
||||||
|
|
||||||
static float audio_buf[512 * 2];
|
static float audio_buf[512 * 2];
|
||||||
static uint audio_buf_pos = 0;
|
static uint audio_buf_pos = 0;
|
||||||
@@ -64,8 +93,44 @@ void audio_loop() {
|
|||||||
|
|
||||||
const float audio_gain = mute[0] ? 0.0f : powf(10.0f, get_config().speaker_volume / 20.0f);
|
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;
|
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++) {
|
for (int i = 0; i < nframes; i++) {
|
||||||
#if !DISABLE_SPEAKER_PROC
|
// 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] / 32768.0f * audio_gain;
|
||||||
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * audio_gain;
|
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * audio_gain;
|
||||||
if (audio_buf_pos == 512 * 2) {
|
if (audio_buf_pos == 512 * 2) {
|
||||||
@@ -80,10 +145,45 @@ void audio_loop() {
|
|||||||
audio_buf_pos = 0;
|
audio_buf_pos = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
in_buf[i * 2] = static_cast<WDL_ResampleSample>(clamp(raw[i * INPUT_CHANNELS + 2] / 32768.0f * haptics_gain,
|
float h_l = raw[i * INPUT_CHANNELS + 2] / 32768.0f * haptics_gain;
|
||||||
-1.0f, 1.0f));
|
float h_r = raw[i * INPUT_CHANNELS + 3] / 32768.0f * haptics_gain;
|
||||||
in_buf[i * 2 + 1] = static_cast<WDL_ResampleSample>(clamp(raw[i * INPUT_CHANNELS + 3] / 32768.0f * haptics_gain,
|
|
||||||
-1.0f, 1.0f));
|
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 重采样
|
// 3. 48kHz -> 3kHz 重采样
|
||||||
@@ -138,6 +238,7 @@ void audio_loop() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
bt_write(pkt, sizeof(pkt));
|
bt_write(pkt, sizeof(pkt));
|
||||||
|
g_bt_packets++;
|
||||||
haptic_buf_pos = 0;
|
haptic_buf_pos = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -5,9 +5,21 @@
|
|||||||
#ifndef DS5_BRIDGE_AUDIO_H
|
#ifndef DS5_BRIDGE_AUDIO_H
|
||||||
#define DS5_BRIDGE_AUDIO_H
|
#define DS5_BRIDGE_AUDIO_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
void audio_init();
|
void audio_init();
|
||||||
void audio_loop();
|
void audio_loop();
|
||||||
void core1_entry();
|
void core1_entry();
|
||||||
void set_headset(bool state);
|
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
|
#endif //DS5_BRIDGE_AUDIO_H
|
||||||
Reference in New Issue
Block a user