弹来弹去跑马灯!

C#+Audio绘制麦克风波形

/*********************************************************

C#+Audio绘制麦克风波形

By:wgscd

Date:2016-8-1

*********************************************************/

<Window
    x:Class="MMWeather.MicroWaveWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:MMWeather"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="音量圆圈"
    Width="894"
    Height="288"
    mc:Ignorable="d">
    <Grid>
        <Canvas x:Name="canvas" Background="#111111" />
    </Grid>
</Window>

  

 

using NAudio.Wave;
using NAudio.Dsp;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Shapes;

namespace MMWeather
{

/*********************************************************

C#+Audio绘制麦克风波形

By:wgscd

Date:2016-8-1

*********************************************************/

    public partial class MicroWaveWindow : Window
    {
        private WaveInEvent waveIn;
        private const int BarCount = 15;
        private Rectangle[] bars;

        // FFT 相关
        private Complex[] fftBuffer;
        private int fftSize = 1024;
        private int sampleCount = 0;
        private float[] sampleBuffer;

        // 平滑参数
        private float[] previousHeights;
        private Brush[] barBrushes;

        public MicroWaveWindow()
        {
            InitializeComponent();
            InitAudioVisualizer();
        }

        private void InitAudioVisualizer()
        {
            bars = new Rectangle[BarCount];
            previousHeights = new float[BarCount];
            barBrushes = new Brush[BarCount];

            // 预计算颜色渐变 - 从蓝色到紫色到红色
            for (int i = 0; i < BarCount; i++)
            {
                float t = (float)i / BarCount;
                Color color = ColorFromHSV(220 - t * 200, 0.9, 0.4 + t * 0.5);
                barBrushes[i] = new SolidColorBrush(color);
            }

            // 创建柱状图
            for (int i = 0; i < BarCount; i++)
            {
                var bar = new Rectangle
                {
                    Width = 8,
                    Fill = barBrushes[i],
                    RadiusX = 3,
                    RadiusY = 3
                };
                Canvas.SetLeft(bar, i * 10 + 5);
                canvas.Children.Add(bar);
                bars[i] = bar;
            }

            // 初始化 FFT 缓冲区
            fftBuffer = new Complex[fftSize];
            sampleBuffer = new float[fftSize];

            // 初始化音频输入
            waveIn = new WaveInEvent
            {
                WaveFormat = new WaveFormat(44100, 16, 1),
                DeviceNumber = 0 // 默认麦克风
            };
            waveIn.DataAvailable += WaveIn_DataAvailable;
            waveIn.RecordingStopped += WaveIn_RecordingStopped;
            waveIn.StartRecording();

            // 窗口关闭时停止录音
            this.Closed += (s, e) =>
            {
                waveIn?.StopRecording();
                waveIn?.Dispose();
            };
        }

        private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
        {
            // 将音频数据转换为 float 并填充到缓冲区
            for (int i = 0; i < e.BytesRecorded / 2 && sampleCount < fftSize; i++)
            {
                short sample = BitConverter.ToInt16(e.Buffer, i * 2);
                sampleBuffer[sampleCount] = sample / 32768f;
                sampleCount++;
            }

            // 当收集够 FFT 所需的样本数时进行处理
            if (sampleCount >= fftSize)
            {
                ProcessFFT();
                sampleCount = 0;
            }
        }

        private void WaveIn_RecordingStopped(object sender, StoppedEventArgs e)
        {
            // 录音停止时的处理
        }

        private void ProcessFFT()
        {
            // 准备 FFT 输入 - 应用汉宁窗
            for (int i = 0; i < fftSize; i++)
            {
                double window = 0.5 * (1 - Math.Cos(2 * Math.PI * i / (fftSize - 1)));
                fftBuffer[i].X = sampleBuffer[i] * (float)window;
                fftBuffer[i].Y = 0;
            }

            // ===== 修复:正确计算 FFT 阶数 =====
            int fftOrder = (int)Math.Log(fftSize, 2);
            FastFourierTransform.FFT(true, fftOrder, fftBuffer);

            // 提取频谱幅度
            float[] magnitudes = new float[fftSize / 2];
            float maxMag = 0;
            for (int i = 0; i < fftSize / 2; i++)
            {
                magnitudes[i] = (float)Math.Sqrt(fftBuffer[i].X * fftBuffer[i].X + fftBuffer[i].Y * fftBuffer[i].Y);
                if (magnitudes[i] > maxMag) maxMag = magnitudes[i];
            }

            // 防止除零
            if (maxMag < 0.0001f) maxMag = 0.0001f;

            // 映射到柱状图
            float[] barValues = new float[BarCount];
            int binsPerBar = (fftSize / 2) / BarCount;

            for (int i = 0; i < BarCount; i++)
            {
                float sum = 0;
                int startBin = i * binsPerBar;
                int endBin = Math.Min(startBin + binsPerBar, fftSize / 2);

                for (int j = startBin; j < endBin; j++)
                {
                    sum += magnitudes[j];
                }

                float avg = sum / (endBin - startBin);

                // ===== 振幅映射优化 =====
                // 归一化
                float normalized = avg / maxMag;

                // 对数压缩(压低底噪,放大峰值)
                float logValue = (float)(Math.Log(1 + normalized * 300) / Math.Log(301));

                // 平方处理,让峰值更突出
                logValue = logValue * logValue * 1.2f;

                // 动态范围拉伸(切除底部噪音)
                logValue = (logValue - 0.03f) / 0.97f;
                if (logValue < 0) logValue = 0;

                barValues[i] = Math.Min(logValue, 1f);
            }

            // UI 更新
            Dispatcher.Invoke(() =>
            {
                for (int i = 0; i < BarCount; i++)
                {
                    // 平滑处理 - 平衡响应速度和流畅度
                    float smoothed = previousHeights[i] * 0.45f + barValues[i] * 0.55f;
                    previousHeights[i] = smoothed;

                    // 计算高度(最大 280px)
                    double height = smoothed * 80;
                    height = Math.Max(height, 2); // 最小高度保证可见

                    bars[i].Height = height;
                    Canvas.SetTop(bars[i], (canvas.ActualHeight - height) / 2);

                    // 根据高度动态调整透明度
                    bars[i].Opacity = 0.5 + smoothed * 0.5;
                }
            });
        }

        // HSV 转 Color 辅助方法
        private Color ColorFromHSV(double hue, double saturation, double value)
        {
            int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
            double f = hue / 60 - Math.Floor(hue / 60);

            double v = value;
            double p = value * (1 - saturation);
            double q = value * (1 - f * saturation);
            double t = value * (1 - (1 - f) * saturation);

            double r = 0, g = 0, b = 0;
            switch (hi)
            {
                case 0: r = v; g = t; b = p; break;
                case 1: r = q; g = v; b = p; break;
                case 2: r = p; g = v; b = t; break;
                case 3: r = p; g = q; b = v; break;
                case 4: r = t; g = p; b = v; break;
                case 5: r = v; g = p; b = q; break;
            }

            return Color.FromArgb(255, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
        }

        protected override void OnClosed(EventArgs e)
        {
            waveIn?.StopRecording();
            waveIn?.Dispose();
            base.OnClosed(e);
        }
    }
}

  

posted @ 2026-08-01 10:09  wgscd  阅读(6)  评论(0)    收藏  举报