diff --git a/src/audio.cpp b/src/audio.cpp index 3ebdbad..1c9bf2e 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -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,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 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++) { - #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 + 1] / 32768.0f * audio_gain; if (audio_buf_pos == 512 * 2) { @@ -80,10 +145,45 @@ void audio_loop() { audio_buf_pos = 0; } #endif - in_buf[i * 2] = static_cast(clamp(raw[i * INPUT_CHANNELS + 2] / 32768.0f * haptics_gain, - -1.0f, 1.0f)); - in_buf[i * 2 + 1] = static_cast(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(clamp(h_l, -1.0f, 1.0f)); + in_buf[i * 2 + 1] = static_cast(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; } } diff --git a/src/audio.h b/src/audio.h index 38aa1b0..b42411d 100644 --- a/src/audio.h +++ b/src/audio.h @@ -5,9 +5,21 @@ #ifndef DS5_BRIDGE_AUDIO_H #define DS5_BRIDGE_AUDIO_H +#include + 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 \ No newline at end of file