Sound Matrix Decoder (#986)

This commit is contained in:
tortugaveloz
2026-01-25 18:05:56 -05:00
committed by GitHub
parent 18fc792bf4
commit 5c8b975de4
16 changed files with 614 additions and 43 deletions
@@ -14,6 +14,10 @@ AudioChannelsSetting GetAudioChannels();
int32_t GetNumAudioChannels();
void AudioPlayerPlayFrame(const uint8_t* buf, size_t len);
// Set audio channels configuration at runtime (stereo or 5.1 surround)
// This will reinitialize the audio backend without requiring a game restart
void SetAudioChannels(AudioChannelsSetting channels);
#ifdef __cplusplus
};
#endif
+5
View File
@@ -20,6 +20,11 @@ class Audio {
std::shared_ptr<std::vector<AudioBackend>> GetAvailableAudioBackends();
void SetCurrentAudioBackend(AudioBackend backend);
// Set audio channels configuration and reinitialize audio player
// This can be called at runtime without restarting the game
void SetAudioChannels(AudioChannelsSetting channels);
AudioChannelsSetting GetAudioChannels() const;
protected:
void InitAudioPlayer();
+14 -1
View File
@@ -1,3 +1,16 @@
#pragma once
typedef enum AudioChannelsSetting { audioStereo, audioSurround51, audioMax } AudioChannelsSetting;
typedef enum AudioChannelsSetting { audioStereo, audioMatrix51, audioRaw51, audioMax } AudioChannelsSetting;
inline const char* AudioChannelsSettingName(AudioChannelsSetting setting) {
switch (setting) {
case audioStereo:
return "Stereo";
case audioMatrix51:
return "5.1 Matrix";
case audioRaw51:
return "5.1 Raw";
default:
return "Unknown";
}
}
+25 -4
View File
@@ -2,7 +2,9 @@
#include "stdint.h"
#include "stddef.h"
#include <string>
#include <memory>
#include "ship/audio/AudioChannelsSetting.h"
#include "ship/audio/SoundMatrixDecoder.h"
namespace Ship {
@@ -10,7 +12,7 @@ struct AudioSettings {
int32_t SampleRate = 44100;
int32_t SampleLength = 1024;
int32_t DesiredBuffered = 2480;
AudioChannelsSetting AudioSurround = AudioChannelsSetting::audioStereo;
AudioChannelsSetting ChannelSetting = AudioChannelsSetting::audioStereo;
};
class AudioPlayer {
@@ -22,7 +24,11 @@ class AudioPlayer {
bool Init();
virtual int32_t Buffered() = 0;
virtual void Play(const uint8_t* buf, size_t len) = 0;
// Play audio
// buf: interleaved samples in either stereo: (L, R, L, R, ...), or surround: (FL, FR, C, LFE, SL, SR, ...)
// len: length in bytes
void Play(const uint8_t* buf, size_t len);
bool IsInitialized();
@@ -40,14 +46,29 @@ class AudioPlayer {
void SetDesiredBuffered(int32_t size);
void SetAudioChannels(AudioChannelsSetting surround);
// Change audio channels and reinitialize the audio device
// Returns true if successful
bool SetAudioChannels(AudioChannelsSetting channels);
// Get the number of output channels (2 for stereo, 6 for surround)
int32_t GetNumOutputChannels() const;
protected:
// Initialize the audio device.
virtual bool DoInit() = 0;
// Close the current audio device.
virtual void DoClose() = 0;
// Internal play method - receives audio in the output format (stereo or surround)
virtual void DoPlay(const uint8_t* buf, size_t len) = 0;
private:
bool mInitialized = false;
// Sound matrix decoder for surround mode
std::unique_ptr<SoundMatrixDecoder> mSoundMatrixDecoder;
AudioSettings mAudioSettings;
bool mInitialized = false;
};
} // namespace Ship
+4 -3
View File
@@ -8,10 +8,11 @@ class NullAudioPlayer final : public AudioPlayer {
}
~NullAudioPlayer();
int Buffered();
void Play(const uint8_t* buf, size_t len);
int Buffered() override;
protected:
bool DoInit();
bool DoInit() override;
void DoClose() override;
void DoPlay(const uint8_t* buf, size_t len) override;
};
} // namespace Ship
+5 -4
View File
@@ -9,14 +9,15 @@ class SDLAudioPlayer final : public AudioPlayer {
}
~SDLAudioPlayer();
int Buffered();
void Play(const uint8_t* buf, size_t len);
int Buffered() override;
protected:
bool DoInit();
bool DoInit() override;
void DoClose() override;
void DoPlay(const uint8_t* buf, size_t len) override;
private:
SDL_AudioDeviceID mDevice;
SDL_AudioDeviceID mDevice = 0;
int32_t mNumChannels = 2;
};
} // namespace Ship
+120
View File
@@ -0,0 +1,120 @@
#pragma once
#include <cstdint>
#include <cmath>
#include <array>
#include <vector>
#include <tuple>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace Ship {
/**
* Passive matrix decoder for stereo to 5.1 surround upmixing.
* Implements standard audio matrix decoding techniques using:
* - Linkwitz-Riley crossover filters for frequency band separation
* - All-pass filters for phase manipulation
* - Delay lines for surround channel timing
*/
class SoundMatrixDecoder {
public:
/**
* Construct and initialize the decoder with a specific sample rate.
* @param sampleRate The audio sample rate in Hz
*/
SoundMatrixDecoder(int32_t sampleRate);
~SoundMatrixDecoder() = default;
/**
* Reset filter states without recomputing coefficients.
* Useful when audio is interrupted to prevent clicks.
*/
void ResetState();
/**
* Decode stereo to 5.1 surround
* @param stereoInput Interleaved stereo samples [L0, R0, L1, R1, ...]
* @param samplePairs Number of stereo sample pairs to process
* @return Pointer to internal buffer with interleaved 5.1 samples [FL, FR, C, LFE, SL, SR, ...]
*/
std::tuple<const uint8_t*, int> Process(const uint8_t* buf, size_t len);
private:
// 4th-order IIR filter (Linkwitz-Riley) for 24dB/octave slopes
struct BiquadCascade {
double X[4] = {}; // Input history
double Y[4] = {}; // Output history
};
struct FilterCoefficients {
double A[5]; // Feedforward (numerator)
double B[4]; // Feedback (denominator, excluding b0=1)
};
// Sweeping all-pass for phase decorrelation
struct AllPassChain {
double Freq = 0;
double FreqMin = 0;
double FreqMax = 0;
double SweepRate = 0;
double XHist[4] = {};
double YHist[4] = {};
bool Ready = false;
};
// Circular delay buffer
static constexpr int gMaxDelay = 1024;
struct CircularDelay {
std::array<float, gMaxDelay> Data = {};
int Head = 0;
int Length = 0;
};
// Filter design
FilterCoefficients DesignLowPass(double frequency, int32_t sampleRate);
FilterCoefficients DesignHighPass(double frequency, int32_t sampleRate);
// Signal processing
float ProcessFilter(float sample, BiquadCascade& state, const FilterCoefficients& coef);
void PrepareAllPass(AllPassChain& chain, int32_t sampleRate);
float ProcessAllPass(float sample, AllPassChain& chain, bool negate);
float ProcessDelay(float sample, CircularDelay& buffer);
static int16_t Saturate(float value);
int32_t mDelayLength = 0;
double mAllPassBaseRate = 1.0; // Precomputed for ProcessAllPass
// Filter coefficients (computed once per sample rate)
FilterCoefficients mCoefCenterHP;
FilterCoefficients mCoefCenterLP;
FilterCoefficients mCoefSurroundHP;
FilterCoefficients mCoefSubLP;
// Per-channel filter states
BiquadCascade mCenterHighPass;
BiquadCascade mCenterLowPass;
BiquadCascade mSurrLeftMainHP;
BiquadCascade mSurrLeftCrossHP;
BiquadCascade mSurrRightMainHP;
BiquadCascade mSurrRightCrossHP;
BiquadCascade mSubLowPass;
// Phase processing
AllPassChain mPhaseLeftMain;
AllPassChain mPhaseLeftCross;
AllPassChain mPhaseRightMain;
AllPassChain mPhaseRightCross;
// Timing
CircularDelay mDelaySurrLeft;
CircularDelay mDelaySurrRight;
// Output buffer
std::vector<int16_t> mSurroundBuffer;
};
} // namespace Ship
+5 -3
View File
@@ -15,10 +15,13 @@ class WasapiAudioPlayer : public AudioPlayer, public IMMNotificationClient {
WasapiAudioPlayer(AudioSettings settings) : AudioPlayer(settings) {
}
int Buffered();
void Play(const uint8_t* buf, size_t len);
int Buffered() override;
protected:
bool DoInit() override;
void DoClose() override;
void DoPlay(const uint8_t* buf, size_t len) override;
virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState);
virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId);
virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId);
@@ -29,7 +32,6 @@ class WasapiAudioPlayer : public AudioPlayer, public IMMNotificationClient {
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
void ThrowIfFailed(HRESULT res);
bool SetupStream();
bool DoInit();
private:
ComPtr<IMMDeviceEnumerator> mDeviceEnumerator;
+15 -5
View File
@@ -41,12 +41,13 @@ AudioChannelsSetting GetAudioChannels() {
}
int32_t GetNumAudioChannels() {
switch (GetAudioChannels()) {
case audioSurround51:
return 6;
default:
return 2;
auto audio = Ship::Context::GetInstance()->GetAudio()->GetAudioPlayer();
if (audio == nullptr) {
return 2;
}
return audio->GetNumOutputChannels();
}
void AudioPlayerPlayFrame(const uint8_t* buf, size_t len) {
@@ -61,4 +62,13 @@ void AudioPlayerPlayFrame(const uint8_t* buf, size_t len) {
audio->Play(buf, len);
}
void SetAudioChannels(AudioChannelsSetting channels) {
auto audio = Ship::Context::GetInstance()->GetAudio();
if (audio == nullptr) {
return;
}
audio->SetAudioChannels(channels);
}
}
+14
View File
@@ -62,4 +62,18 @@ std::shared_ptr<std::vector<AudioBackend>> Audio::GetAvailableAudioBackends() {
return mAvailableAudioBackends;
}
void Audio::SetAudioChannels(AudioChannelsSetting channels) {
if (mAudioSettings.ChannelSetting != channels) {
mAudioSettings.ChannelSetting = channels;
// Reinitialize the existing audio player with the new channel configuration
if (mAudioPlayer) {
mAudioPlayer->SetAudioChannels(channels);
}
}
}
AudioChannelsSetting Audio::GetAudioChannels() const {
return mAudioSettings.ChannelSetting;
}
} // namespace Ship
+62 -3
View File
@@ -2,11 +2,17 @@
#include "spdlog/spdlog.h"
namespace Ship {
AudioPlayer::~AudioPlayer() {
SPDLOG_TRACE("destruct audio player");
}
bool AudioPlayer::Init() {
// Initialize sound matrix decoder if matrix surround mode is enabled
if (mAudioSettings.ChannelSetting == AudioChannelsSetting::audioMatrix51) {
SPDLOG_INFO("Initializing sound matrix decoder for surround");
mSoundMatrixDecoder = std::make_unique<SoundMatrixDecoder>(mAudioSettings.SampleRate);
}
mInitialized = DoInit();
return IsInitialized();
}
@@ -28,7 +34,7 @@ int32_t AudioPlayer::GetDesiredBuffered() const {
}
AudioChannelsSetting AudioPlayer::GetAudioChannels() const {
return mAudioSettings.AudioSurround;
return mAudioSettings.ChannelSetting;
}
void AudioPlayer::SetSampleRate(int32_t rate) {
@@ -43,7 +49,60 @@ void AudioPlayer::SetDesiredBuffered(int32_t size) {
mAudioSettings.DesiredBuffered = size;
}
void AudioPlayer::SetAudioChannels(AudioChannelsSetting surround) {
mAudioSettings.AudioSurround = surround;
bool AudioPlayer::SetAudioChannels(AudioChannelsSetting channels) {
if (mAudioSettings.ChannelSetting == channels) {
return true; // No change needed
}
SPDLOG_INFO("Changing audio channels from {} to {}", AudioChannelsSettingName(mAudioSettings.ChannelSetting),
AudioChannelsSettingName(channels));
// Close current audio device
DoClose();
// Update channel setting
mAudioSettings.ChannelSetting = channels;
// Setup or teardown sound matrix decoder
if (channels == AudioChannelsSetting::audioMatrix51) {
if (!mSoundMatrixDecoder) {
mSoundMatrixDecoder = std::make_unique<SoundMatrixDecoder>(mAudioSettings.SampleRate);
}
} else {
// When switching away from matrix mode, release the decoder
mSoundMatrixDecoder.reset();
}
return DoInit();
}
int32_t AudioPlayer::GetNumOutputChannels() const {
switch (mAudioSettings.ChannelSetting) {
case AudioChannelsSetting::audioMatrix51:
case AudioChannelsSetting::audioRaw51:
return 6;
case AudioChannelsSetting::audioStereo:
default:
return 2;
}
}
void AudioPlayer::Play(const uint8_t* buf, size_t len) {
if (mAudioSettings.ChannelSetting != AudioChannelsSetting::audioMatrix51) {
// Stereo or Raw 5.1 passthrough
DoPlay(buf, len);
return;
}
if (!mSoundMatrixDecoder) {
SPDLOG_ERROR("AudioPlayer: Matrix 5.1 mode enabled but SoundMatrixDecoder is not initialized");
return;
}
// Decode stereo to surround using sound matrix decoder
const auto [surroundOut, surroundLen] = mSoundMatrixDecoder->Process(buf, len);
// Play the audio
DoPlay(surroundOut, surroundLen);
}
} // namespace Ship
+5 -1
View File
@@ -11,10 +11,14 @@ bool NullAudioPlayer::DoInit() {
return true;
}
void NullAudioPlayer::DoClose() {
// Nothing to close for null player
}
int NullAudioPlayer::Buffered() {
return 0;
}
void NullAudioPlayer::Play(const uint8_t* buf, size_t len) {
void NullAudioPlayer::DoPlay(const uint8_t* buf, size_t len) {
}
} // namespace Ship
+21 -2
View File
@@ -5,15 +5,30 @@ namespace Ship {
SDLAudioPlayer::~SDLAudioPlayer() {
SPDLOG_TRACE("destruct SDL audio player");
DoClose();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
void SDLAudioPlayer::DoClose() {
if (mDevice != 0) {
// Pause playback first
SDL_PauseAudioDevice(mDevice, 1);
// Clear any queued audio to prevent glitches when reopening
SDL_ClearQueuedAudio(mDevice);
SDL_CloseAudioDevice(mDevice);
mDevice = 0;
}
}
bool SDLAudioPlayer::DoInit() {
if (SDL_Init(SDL_INIT_AUDIO) != 0) {
SPDLOG_ERROR("SDL init error: %s\n", SDL_GetError());
return false;
}
mNumChannels = this->GetAudioChannels() == AudioChannelsSetting::audioSurround51 ? 6 : 2;
// Always open with the correct number of output channels
mNumChannels = this->GetNumOutputChannels();
SDL_AudioSpec want, have;
SDL_zero(want);
want.freq = this->GetSampleRate();
@@ -21,11 +36,15 @@ bool SDLAudioPlayer::DoInit() {
want.channels = mNumChannels;
want.samples = this->GetSampleLength();
want.callback = NULL;
mDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (mDevice == 0) {
SPDLOG_ERROR("SDL_OpenAudio error: {}", SDL_GetError());
return false;
}
SPDLOG_INFO("SDL Audio initialized: {} channels, {} Hz", mNumChannels, this->GetSampleRate());
SDL_PauseAudioDevice(mDevice, 0);
return true;
}
@@ -34,7 +53,7 @@ int SDLAudioPlayer::Buffered() {
return SDL_GetQueuedAudioSize(mDevice) / (sizeof(int16_t) * mNumChannels);
}
void SDLAudioPlayer::Play(const uint8_t* buf, size_t len) {
void SDLAudioPlayer::DoPlay(const uint8_t* buf, size_t len) {
if (Buffered() < 6000) {
// Don't fill the audio buffer too much in case this happens
SDL_QueueAudio(mDevice, buf, len);
+284
View File
@@ -0,0 +1,284 @@
#include "ship/audio/SoundMatrixDecoder.h"
namespace Ship {
// Standard matrix decoding gains derived from psychoacoustic principles
namespace Gains {
constexpr float gCenter = 0.7071067811865476f * 0.5f; // -3dB (1/sqrt(2)) * 0.5
constexpr float gFront = 0.5f; // -6dB
constexpr float gSurroundPrimary = 0.4359f; // Primary surround contribution
constexpr float gSurroundSecondary = 0.2449f; // Cross-feed surround contribution
} // namespace Gains
// Timing constants
namespace Timing {
constexpr int gSurroundDelayMs = 10; // ITU-R BS.775 recommends 10-25ms
}
SoundMatrixDecoder::SoundMatrixDecoder(int32_t sampleRate) {
// Compute delay length from sample rate
mDelayLength = (sampleRate * Timing::gSurroundDelayMs) / 1000;
if (mDelayLength > gMaxDelay) {
mDelayLength = gMaxDelay;
}
// Precompute base rate for all-pass sweep
mAllPassBaseRate = std::pow(std::pow(2.0, 4.0), 0.1 / (sampleRate / 2.0));
// Design filters for this sample rate
mCoefCenterHP = DesignHighPass(70.0, sampleRate); // Remove rumble from center
mCoefCenterLP = DesignLowPass(20000.0, sampleRate); // Anti-alias center
mCoefSurroundHP = DesignHighPass(100.0, sampleRate); // Surround channels high-passed
mCoefSubLP = DesignLowPass(120.0, sampleRate); // LFE low-pass
// Initialize phase chains with sample rate
mPhaseLeftMain = {};
mPhaseLeftCross = {};
mPhaseRightMain = {};
mPhaseRightCross = {};
PrepareAllPass(mPhaseLeftMain, sampleRate);
PrepareAllPass(mPhaseLeftCross, sampleRate);
PrepareAllPass(mPhaseRightMain, sampleRate);
PrepareAllPass(mPhaseRightCross, sampleRate);
// Reset filter states
ResetState();
}
void SoundMatrixDecoder::ResetState() {
// Clear all filter states
mCenterHighPass = {};
mCenterLowPass = {};
mSurrLeftMainHP = {};
mSurrLeftCrossHP = {};
mSurrRightMainHP = {};
mSurrRightCrossHP = {};
mSubLowPass = {};
// Reset delay lines
mDelaySurrLeft = {};
mDelaySurrLeft.Length = mDelayLength;
mDelaySurrRight = {};
mDelaySurrRight.Length = mDelayLength;
}
SoundMatrixDecoder::FilterCoefficients SoundMatrixDecoder::DesignLowPass(double frequency, int32_t sampleRate) {
FilterCoefficients coef = {};
// Clamp to safe range for bilinear transform stability
double maxFreq = sampleRate * 0.475;
if (frequency > maxFreq) {
frequency = maxFreq;
}
// Linkwitz-Riley 4th order: two cascaded Butterworth 2nd order
double omega = 2.0 * M_PI * frequency;
double omega2 = omega * omega;
double omega3 = omega2 * omega;
double omega4 = omega2 * omega2;
// Bilinear transform warping
double kVal = omega / std::tan(M_PI * frequency / sampleRate);
double k2 = kVal * kVal;
double k3 = k2 * kVal;
double k4 = k2 * k2;
double rt2 = std::sqrt(2.0);
double term1 = rt2 * omega3 * kVal;
double term2 = rt2 * omega * k3;
double norm = 4.0 * omega2 * k2 + 2.0 * term1 + k4 + 2.0 * term2 + omega4;
// Feedback coefficients
coef.B[0] = (4.0 * (omega4 + term1 - k4 - term2)) / norm;
coef.B[1] = (6.0 * omega4 - 8.0 * omega2 * k2 + 6.0 * k4) / norm;
coef.B[2] = (4.0 * (omega4 - term1 + term2 - k4)) / norm;
coef.B[3] = (k4 - 2.0 * term1 + omega4 - 2.0 * term2 + 4.0 * omega2 * k2) / norm;
// Feedforward coefficients (low-pass response)
coef.A[0] = omega4 / norm;
coef.A[1] = 4.0 * omega4 / norm;
coef.A[2] = 6.0 * omega4 / norm;
coef.A[3] = coef.A[1];
coef.A[4] = coef.A[0];
return coef;
}
SoundMatrixDecoder::FilterCoefficients SoundMatrixDecoder::DesignHighPass(double frequency, int32_t sampleRate) {
FilterCoefficients coef = {};
double omega = 2.0 * M_PI * frequency;
double omega2 = omega * omega;
double omega3 = omega2 * omega;
double omega4 = omega2 * omega2;
double kVal = omega / std::tan(M_PI * frequency / sampleRate);
double k2 = kVal * kVal;
double k3 = k2 * kVal;
double k4 = k2 * k2;
double rt2 = std::sqrt(2.0);
double term1 = rt2 * omega3 * kVal;
double term2 = rt2 * omega * k3;
double norm = 4.0 * omega2 * k2 + 2.0 * term1 + k4 + 2.0 * term2 + omega4;
coef.B[0] = (4.0 * (omega4 + term1 - k4 - term2)) / norm;
coef.B[1] = (6.0 * omega4 - 8.0 * omega2 * k2 + 6.0 * k4) / norm;
coef.B[2] = (4.0 * (omega4 - term1 + term2 - k4)) / norm;
coef.B[3] = (k4 - 2.0 * term1 + omega4 - 2.0 * term2 + 4.0 * omega2 * k2) / norm;
// Feedforward coefficients (high-pass response)
coef.A[0] = k4 / norm;
coef.A[1] = -4.0 * k4 / norm;
coef.A[2] = 6.0 * k4 / norm;
coef.A[3] = coef.A[1];
coef.A[4] = coef.A[0];
return coef;
}
float SoundMatrixDecoder::ProcessFilter(float sample, BiquadCascade& state, const FilterCoefficients& coef) {
double in = sample;
double out = coef.A[0] * in + coef.A[1] * state.X[0] + coef.A[2] * state.X[1] + coef.A[3] * state.X[2] +
coef.A[4] * state.X[3] - coef.B[0] * state.Y[0] - coef.B[1] * state.Y[1] - coef.B[2] * state.Y[2] -
coef.B[3] * state.Y[3];
// Shift history
state.X[3] = state.X[2];
state.X[2] = state.X[1];
state.X[1] = state.X[0];
state.X[0] = in;
state.Y[3] = state.Y[2];
state.Y[2] = state.Y[1];
state.Y[1] = state.Y[0];
state.Y[0] = out;
return static_cast<float>(out);
}
void SoundMatrixDecoder::PrepareAllPass(AllPassChain& chain, int32_t sampleRate) {
// Sweeping all-pass parameters for decorrelation
constexpr double depth = 4.0;
constexpr double baseDelay = 100.0;
constexpr double sweepSpeed = 0.1;
chain.FreqMin = (M_PI * baseDelay) / sampleRate;
chain.Freq = chain.FreqMin;
double range = std::pow(2.0, depth);
chain.FreqMax = (M_PI * baseDelay * range) / sampleRate;
chain.SweepRate = std::pow(range, sweepSpeed / (sampleRate / 2.0));
chain.Ready = true;
}
float SoundMatrixDecoder::ProcessAllPass(float sample, AllPassChain& chain, bool negate) {
// First-order all-pass coefficient
double c = (1.0 - chain.Freq) / (1.0 + chain.Freq);
double input = static_cast<double>(sample);
// Cascade of 4 first-order all-pass sections
chain.YHist[0] = c * (chain.YHist[0] + input) - chain.XHist[0];
chain.XHist[0] = input;
chain.YHist[1] = c * (chain.YHist[1] + chain.YHist[0]) - chain.XHist[1];
chain.XHist[1] = chain.YHist[0];
chain.YHist[2] = c * (chain.YHist[2] + chain.YHist[1]) - chain.XHist[2];
chain.XHist[2] = chain.YHist[1];
chain.YHist[3] = c * (chain.YHist[3] + chain.YHist[2]) - chain.XHist[3];
chain.XHist[3] = chain.YHist[2];
double result = negate ? -chain.YHist[3] : chain.YHist[3];
// Sweep the frequency for time-varying decorrelation
chain.Freq *= chain.SweepRate;
if (chain.Freq > chain.FreqMax) {
chain.SweepRate = 1.0 / mAllPassBaseRate;
} else if (chain.Freq < chain.FreqMin) {
chain.SweepRate = mAllPassBaseRate;
}
return static_cast<float>(result);
}
float SoundMatrixDecoder::ProcessDelay(float sample, CircularDelay& buffer) {
float output = buffer.Data[buffer.Head];
buffer.Data[buffer.Head] = sample;
buffer.Head = (buffer.Head + 1) % buffer.Length;
return output;
}
int16_t SoundMatrixDecoder::Saturate(float value) {
if (value > 32767.0f) {
return 32767;
}
if (value < -32768.0f) {
return -32768;
}
return static_cast<int16_t>(value);
}
std::tuple<const uint8_t*, int> SoundMatrixDecoder::Process(const uint8_t* buf, size_t len) {
const int16_t* stereoInput = reinterpret_cast<const int16_t*>(buf);
int samplePairs = len / (2 * sizeof(int16_t));
// Resize output buffer if needed
size_t samplesNeeded = static_cast<size_t>(samplePairs) * 6;
if (mSurroundBuffer.size() < samplesNeeded) {
mSurroundBuffer.resize(samplesNeeded);
}
for (int i = 0; i < samplePairs; ++i) {
float inL = static_cast<float>(stereoInput[i * 2]);
float inR = static_cast<float>(stereoInput[i * 2 + 1]);
// Center: sum of L+R, band-limited
float ctr = (inL + inR) * Gains::gCenter;
ctr = ProcessFilter(ctr, mCenterHighPass, mCoefCenterHP);
ctr = ProcessFilter(ctr, mCenterLowPass, mCoefCenterLP);
// Front channels: attenuated direct signal
float frontL = inL * Gains::gFront;
float frontR = inR * Gains::gFront;
// Surround Left: L primary (inverted phase) + R secondary (shifted phase)
float slMain = inL * Gains::gSurroundPrimary;
slMain = ProcessFilter(slMain, mSurrLeftMainHP, mCoefSurroundHP);
slMain = ProcessAllPass(slMain, mPhaseLeftMain, true);
float slCross = inR * Gains::gSurroundSecondary;
slCross = ProcessFilter(slCross, mSurrLeftCrossHP, mCoefSurroundHP);
slCross = ProcessAllPass(slCross, mPhaseLeftCross, false);
float surrL = ProcessDelay(slMain + slCross, mDelaySurrLeft);
// Surround Right: R primary (shifted phase) + L secondary (inverted phase)
float srMain = inR * Gains::gSurroundPrimary;
srMain = ProcessFilter(srMain, mSurrRightMainHP, mCoefSurroundHP);
srMain = ProcessAllPass(srMain, mPhaseRightMain, false);
float srCross = inL * Gains::gSurroundSecondary;
srCross = ProcessFilter(srCross, mSurrRightCrossHP, mCoefSurroundHP);
srCross = ProcessAllPass(srCross, mPhaseRightCross, true);
float surrR = ProcessDelay(srMain + srCross, mDelaySurrRight);
// LFE: low-passed sum
float lfe = (inL + inR) * Gains::gCenter;
lfe = ProcessFilter(lfe, mSubLowPass, mCoefSubLP);
// Output: FL, FR, C, LFE, SL, SR
mSurroundBuffer[i * 6 + 0] = Saturate(frontL);
mSurroundBuffer[i * 6 + 1] = Saturate(frontR);
mSurroundBuffer[i * 6 + 2] = Saturate(ctr);
mSurroundBuffer[i * 6 + 3] = Saturate(lfe);
mSurroundBuffer[i * 6 + 4] = Saturate(surrL);
mSurroundBuffer[i * 6 + 5] = Saturate(surrR);
}
return { reinterpret_cast<const uint8_t*>(mSurroundBuffer.data()), samplePairs * 6 * sizeof(int16_t) };
}
} // namespace Ship
+25 -13
View File
@@ -28,31 +28,31 @@ bool WasapiAudioPlayer::SetupStream() {
ThrowIfFailed(mDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &mDevice));
ThrowIfFailed(mDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, IID_PPV_ARGS_Helper(&mClient)));
auto audioSurround = this->GetAudioChannels();
if (audioSurround == AudioChannelsSetting::audioStereo) {
mNumChannels = 2;
// Use GetNumOutputChannels() to determine stereo vs surround
mNumChannels = this->GetNumOutputChannels();
if (mNumChannels == 2) {
WAVEFORMATEX desired;
desired.wFormatTag = WAVE_FORMAT_PCM;
desired.nChannels = mNumChannels; // Stereo audio
desired.wBitsPerSample = 16; // 16-bit audio
desired.nSamplesPerSec = this->GetSampleRate();
desired.nBlockAlign = desired.nChannels * desired.wBitsPerSample / 8;
desired.nAvgBytesPerSec = desired.nSamplesPerSec * desired.nBlockAlign; // 2 bytes per sample (16-bit audio)
desired.nAvgBytesPerSec = desired.nSamplesPerSec * desired.nBlockAlign;
desired.cbSize = 0;
ThrowIfFailed(mClient->Initialize(
AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
2000000, 0, &desired, nullptr));
} else if (audioSurround == AudioChannelsSetting::audioSurround51) {
mNumChannels = 6;
} else if (mNumChannels == 6) {
// 5.1 surround (6 channels)
WAVEFORMATEXTENSIBLE desired;
desired.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
desired.Format.nChannels = mNumChannels; // 6 channels for 5.1 audio
desired.Format.wBitsPerSample = 16; // 16-bit audio
desired.Format.nSamplesPerSec = this->GetSampleRate();
desired.Format.nBlockAlign = desired.Format.nChannels * desired.Format.wBitsPerSample / 8;
desired.Format.nAvgBytesPerSec =
desired.Format.nSamplesPerSec * desired.Format.nBlockAlign; // 2 bytes per sample (16-bit audio)
desired.Format.nAvgBytesPerSec = desired.Format.nSamplesPerSec * desired.Format.nBlockAlign;
desired.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
desired.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
desired.Samples.wValidBitsPerSample = 16;
@@ -75,15 +75,27 @@ bool WasapiAudioPlayer::SetupStream() {
bool WasapiAudioPlayer::DoInit() {
try {
ThrowIfFailed(
CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&mDeviceEnumerator)));
if (!mDeviceEnumerator) {
ThrowIfFailed(
CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&mDeviceEnumerator)));
ThrowIfFailed(mDeviceEnumerator->RegisterEndpointNotificationCallback(this));
}
} catch (HRESULT res) { return false; }
ThrowIfFailed(mDeviceEnumerator->RegisterEndpointNotificationCallback(this));
return true;
}
void WasapiAudioPlayer::DoClose() {
if (mClient) {
mClient->Stop();
}
mRenderClient.Reset();
mClient.Reset();
mDevice.Reset();
mInitialized = false;
mStarted = false;
}
int WasapiAudioPlayer::Buffered() {
if (!mInitialized) {
if (!SetupStream()) {
@@ -97,7 +109,7 @@ int WasapiAudioPlayer::Buffered() {
} catch (HRESULT res) { return 0; }
}
void WasapiAudioPlayer::Play(const uint8_t* buf, size_t len) {
void WasapiAudioPlayer::DoPlay(const uint8_t* buf, size_t len) {
if (!mInitialized) {
if (!SetupStream()) {
return;
+6 -4
View File
@@ -261,11 +261,13 @@ AudioBackend Config::GetCurrentAudioBackend() {
}
AudioChannelsSetting Config::GetCurrentAudioChannelsSetting() {
int32_t surround =
int32_t channelsSetting =
GetInt("CVars." CVAR_AUDIO_CHANNELS_SETTING, static_cast<int32_t>(AudioChannelsSetting::audioMax));
switch (surround) {
case AudioChannelsSetting::audioSurround51:
return AudioChannelsSetting::audioSurround51;
switch (channelsSetting) {
case AudioChannelsSetting::audioMatrix51:
return AudioChannelsSetting::audioMatrix51;
case AudioChannelsSetting::audioRaw51:
return AudioChannelsSetting::audioRaw51;
case AudioChannelsSetting::audioStereo:
case AudioChannelsSetting::audioMax:
default: