[DIY制作] 耳机模拟箱子的声场和质感

[复制链接] 查看: 488|回复: 7

33

主题

0

好友

662

积分

职业侠客 当前在线

Rank: 5Rank: 5

UID
1102940
帖子
627
精华
0
经验
662 点
金钱
594 ¥
注册时间
2024-6-15
发表于 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[delaySamples]);
            rightHighDelayBuffer = new Queue<float>(new float[delaySamples]);
        }

        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[offset + i];
                float rSample = buffer[offset + i + 1];

                // 低频润色
                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[offset + i + 1] = rSample
                + lLow * mixRatioLow
                + lHighDelayed * mixRatioHigh;
                // 左声道混入右侧低频(即刻)和高频(延迟)
                float lCliped = SoftClip(lSample);
                lSample = lSample * (1 - drive) + lCliped * drive;
                buffer[offset + i] = lSample
                + rLow * mixRatioLow
                + rHighDelayed * mixRatioHigh;

                // 环境噪声模拟
                // buffer[offset + i] += (float)(rng.NextDouble() - 0.5) * 0.01f;
                // buffer[offset + i + 1] += (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;
        }
    }
}

评分

参与人数 1经验 +2 魅力 +2 收起 理由
ls0001 + 2 + 2 给c#点赞

查看全部评分

33

主题

0

好友

662

积分

职业侠客 当前在线

Rank: 5Rank: 5

UID
1102940
帖子
627
精华
0
经验
662 点
金钱
594 ¥
注册时间
2024-6-15
 楼主| 发表于 2025-6-26 18:04 | 显示全部楼层
直接上代码,DSP滤波

98

主题

6

好友

4063

积分
     

罗宾汉 当前离线

Rank: 7Rank: 7Rank: 7

UID
29737
帖子
4066
精华
0
经验
4063 点
金钱
3888 ¥
注册时间
2007-11-1
发表于 2025-6-26 18:20 | 显示全部楼层
音箱的HIEND目的就是像耳机才对!参考 惠威10-寸1寸2分频 分频点2500 一个喇叭内的分频  https://www.bilibili.com/video/B ... 7e8bcc179bd002d9ba0

1

主题

1

好友

1181

积分

侠之大者 当前离线

Rank: 6Rank: 6

UID
882685
帖子
1173
精华
0
经验
1181 点
金钱
1169 ¥
注册时间
2020-8-27
发表于 2025-6-27 13:01 | 显示全部楼层
这是做了立体声交叉馈送、低频增强、高频柔化和谐波饱和,属于通用性处理。
现在比较先进的做法是拍照测量耳朵,根据头部耳朵反推度身定制的HRTF函数叠加到DSP里。

17

主题

1

好友

1320

积分

侠之大者 当前离线

Rank: 6Rank: 6

UID
42642
帖子
1573
精华
0
经验
1320 点
金钱
1261 ¥
注册时间
2008-5-8
发表于 2025-6-27 21:42 | 显示全部楼层
死翘翘 发表于 2025-6-27 13:01
这是做了立体声交叉馈送、低频增强、高频柔化和谐波饱和,属于通用性处理。
现在比较先进的做法是拍照测量 ...

还有头部追踪技术

312

主题

2

好友

4978

积分

罗宾汉 当前离线

Rank: 7Rank: 7Rank: 7

UID
1109
帖子
4639
精华
0
经验
4978 点
金钱
4326 ¥
注册时间
2004-7-28
发表于 2025-6-28 13:21 | 显示全部楼层
耳机失真要小于音箱的,除了低频空气扑面而来感

33

主题

0

好友

662

积分

职业侠客 当前在线

Rank: 5Rank: 5

UID
1102940
帖子
627
精华
0
经验
662 点
金钱
594 ¥
注册时间
2024-6-15
 楼主| 发表于 2025-6-28 22:32 | 显示全部楼层
swsw4321 发表于 2025-6-28 13:21
耳机失真要小于音箱的,除了低频空气扑面而来感

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

59

主题

1

好友

2069

积分

罗宾汉 当前在线

Rank: 7Rank: 7Rank: 7

UID
186195
帖子
2010
精华
0
经验
2069 点
金钱
1947 ¥
注册时间
2010-3-29
发表于 2025-6-29 19:03 | 显示全部楼层
云音乐APP里有类似的,不知道哪个好听,我一般音效全关。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Powered by Discuz! X3.4

© 2001-2012 Comsenz Inc.

返回顶部