hifior 发表于 2025-6-26 18:03

耳机模拟箱子的声场和质感

using NAudio.Wave;
using NAudio.Dsp;


namespace NAudioPlayer
{
    public class LowFreqCrossFeedProvider : ISampleProvider
    {
      private readonly ISampleProvider source;
      private readonly int channels;
      private readonly BiQuadFilter lowpassLeft;
      private readonly BiQuadFilter lowpassRight;
      private readonly BiQuadFilter highpassLeft;
      private readonly BiQuadFilter highpassRight;

      private readonly BiQuadFilter lowShelfL, lowShelfR;    // 低频补色
      private readonly BiQuadFilter highLowpassL, highLowpassR; // 高频柔和

      private readonly float mixRatioLow;
      private readonly float mixRatioHigh;
      private readonly int delaySamples; // 延迟采样数
      public float drive = 0.2f;   // 驱动强度:谐波/饱和感,0=原味,增大更明显
      private readonly Random rng = new Random();

      // 环形缓冲,存每个高频分量
      private readonly Queue<float> leftHighDelayBuffer;
      private readonly Queue<float> rightHighDelayBuffer;
      public bool enable = false;

      public LowFreqCrossFeedProvider(ISampleProvider source, float lowCut = 500f, float highCut = 5000f, float delayMs = 8.8f, float ratioLow = 0.5f, float ratioHigh = 0.5f, float q = 0.707f)
      {
            if (source.WaveFormat.Channels != 2)
                throw new InvalidOperationException("Only supports stereo.");
            this.source = source;
            this.channels = source.WaveFormat.Channels;
            this.mixRatioLow = ratioLow;
            this.mixRatioHigh = ratioHigh;
            // 每个声道一个低通
            lowpassLeft = BiQuadFilter.LowPassFilter(source.WaveFormat.SampleRate, lowCut, q);   // Q值可微调
            lowpassRight = BiQuadFilter.LowPassFilter(source.WaveFormat.SampleRate, lowCut, q);

            highpassLeft = BiQuadFilter.HighPassFilter(source.WaveFormat.SampleRate, highCut, q);
            highpassRight = BiQuadFilter.HighPassFilter(source.WaveFormat.SampleRate, highCut, q);

            lowShelfL = BiQuadFilter.LowShelf(source.WaveFormat.SampleRate, 80, 0.7f, MathF.Pow(10, 1.5f / 20f));
            lowShelfR = BiQuadFilter.LowShelf(source.WaveFormat.SampleRate, 80, 0.7f, MathF.Pow(10, 1.5f / 20f));
            // 高频cut,模拟柔和
            highLowpassL = BiQuadFilter.LowPassFilter(source.WaveFormat.SampleRate, 11000, 0.8f);
            highLowpassR = BiQuadFilter.LowPassFilter(source.WaveFormat.SampleRate, 11000, 0.8f);

            // 延迟高频的sample数量,0.2ms在44100Hz下是8.8采样,取整9个样本
            delaySamples = (int)Math.Round(source.WaveFormat.SampleRate * (delayMs / 1000f)); // delayMs单位是ms
            if (delaySamples < 1) delaySamples = 1;

            leftHighDelayBuffer = new Queue<float>(new float);
            rightHighDelayBuffer = new Queue<float>(new float);
      }

      public WaveFormat WaveFormat => source.WaveFormat;

      public int Read(float[] buffer, int offset, int count)
      {
            int read = source.Read(buffer, offset, count);
            if (!enable)
            {
                return read;
            }
            for (int i = 0; i < read; i += channels)
            {
                // 左声道、右声道原始采样
                float lSample = buffer;
                float rSample = buffer;

                // 低频润色
                lSample = lowShelfL.Transform(lSample);
                rSample = lowShelfR.Transform(rSample);

                // 高频柔化
                lSample = highLowpassL.Transform(lSample);
                rSample = highLowpassR.Transform(rSample);

                // 低通高通分量
                float lLow = lowpassLeft.Transform(lSample);
                float lHigh = highpassLeft.Transform(lSample);

                float rLow = lowpassRight.Transform(rSample);
                float rHigh = highpassRight.Transform(rSample);

                // 高频延时环形缓冲
                // 将当前高频分量写入队列, 得到延迟后的输出
                float lHighDelayed = 0;
                float rHighDelayed = 0;
                leftHighDelayBuffer.Enqueue(lHigh);
                rightHighDelayBuffer.Enqueue(rHigh);
                if (leftHighDelayBuffer.Count > delaySamples)
                  lHighDelayed = leftHighDelayBuffer.Dequeue();
                if (rightHighDelayBuffer.Count > delaySamples)
                  rHighDelayed = rightHighDelayBuffer.Dequeue();

                // 右声道混入左侧低频(即刻)和高频(延迟)
                float rCliped = SoftClip(rSample);
                rSample = rSample * (1 - drive) + rCliped * drive;
                buffer = rSample
                + lLow * mixRatioLow
                + lHighDelayed * mixRatioHigh;
                // 左声道混入右侧低频(即刻)和高频(延迟)
                float lCliped = SoftClip(lSample);
                lSample = lSample * (1 - drive) + lCliped * drive;
                buffer = lSample
                + rLow * mixRatioLow
                + rHighDelayed * mixRatioHigh;

                // 环境噪声模拟
                // buffer += (float)(rng.NextDouble() - 0.5) * 0.01f;
                // buffer += (float)(rng.NextDouble() - 0.5) * 0.01f;
            }
            return read;
      }

      // 常用软削波(soft clip)函数,tanh风格
      private float SoftClip(float x)
      {
            // 经典tanh
            // return (float)Math.Tanh(x);

            // Cubic (三次), 强调偶次谐波
            // return x - (x * x * x) / 3f;

            // 更模拟“电子管柔化”
            const float k = 2.5f;            // 驱动感,可调
            float a = MathF.Min(MathF.Max(x * k, -3f), 3f);
            return MathF.Tanh(a) / MathF.Tanh(3f) * 1.0f / k;

            // 或试试 f(x)=x+αx²+βx³
            //float α = 0.2f, β = 0.15f;
            //return x + α * x * x + β * x * x * x;
      }
    }
}

hifior 发表于 2025-6-26 18:04

直接上代码,DSP滤波

pkwangjian 发表于 2025-6-26 18:20

音箱的HIEND目的就是像耳机才对!参考 惠威10-寸1寸2分频 分频点2500 一个喇叭内的分频https://www.bilibili.com/video/BV1zPKczcETp/?vd_source=5b195ef86d95e7e8bcc179bd002d9ba0

死翘翘 发表于 2025-6-27 13:01

这是做了立体声交叉馈送、低频增强、高频柔化和谐波饱和,属于通用性处理。
现在比较先进的做法是拍照测量耳朵,根据头部耳朵反推度身定制的HRTF函数叠加到DSP里。

榆木耳朵 发表于 2025-6-27 21:42

死翘翘 发表于 2025-6-27 13:01
这是做了立体声交叉馈送、低频增强、高频柔化和谐波饱和,属于通用性处理。
现在比较先进的做法是拍照测量 ...

还有头部追踪技术

swsw4321 发表于 2025-6-28 13:21

耳机失真要小于音箱的,除了低频空气扑面而来感

hifior 发表于 2025-6-28 22:32

swsw4321 发表于 2025-6-28 13:21
耳机失真要小于音箱的,除了低频空气扑面而来感

主要两点,一是环境反射声,也就是会有混响,二是左右会有信号交叉,耳机则完全隔开了,主要就是模拟这两点,有纯模拟的网红耳放也在做这个模拟,但是仅仅指示混入了左右交叉信号,更真实的模拟还要考虑反射声

zxhdoop 发表于 2025-6-29 19:03

云音乐APP里有类似的,不知道哪个好听,我一般音效全关。
页: [1]
查看完整版本: 耳机模拟箱子的声场和质感