naudio / NAudio

Audio and MIDI library for .NET
MIT License
5.53k stars 1.1k forks source link

Play output audio in Naudio #697

Open hungcuiga1 opened 4 years ago

hungcuiga1 commented 4 years ago

Hi everyone!

I implement FFT with input audio from mic but when i play audio from mic with waveout the audio is breakdow and i can't speak it. This my code: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;

using NAudio.Wave; using NAudio.CoreAudioApi;

namespace ScottPlotMicrophoneFFT { public partial class Form1 : Form {

    // MICROPHONE ANALYSIS SETTINGS
    private int RATE = 44100; // sample rate of the sound card
    private int BUFFERSIZE = (int)Math.Pow(2, 11); // must be a multiple of 2
    public BufferedWaveProvider bufferedWaveProvider;
    // prepare class objects
    public BufferedWaveProvider bwp;
    public WaveOut player;
    public Form1()
    {
        InitializeComponent();
        SetupGraphLabels();
        StartListeningToMicrophone();
        timerReplot.Enabled = true;
    }

    void AudioDataAvailable(object sender, WaveInEventArgs e)
    {
        bwp.AddSamples(e.Buffer, 0, e.BytesRecorded);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
    }

    public void SetupGraphLabels()
    {
        scottPlotUC1.fig.labelTitle = "Microphone PCM Data";
        scottPlotUC1.fig.labelY = "Amplitude (PCM)";
        scottPlotUC1.fig.labelX = "Time (ms)";
        scottPlotUC1.Redraw();

        scottPlotUC2.fig.labelTitle = "Microphone FFT Data";
        scottPlotUC2.fig.labelY = "Power (raw)";
        scottPlotUC2.fig.labelX = "Frequency (Hz)";
        scottPlotUC2.Redraw();
    }

    public void StartListeningToMicrophone(int audioDeviceNumber = 0)
    {
        WaveIn wi = new WaveIn();
        wi.BufferMilliseconds = 50;
        wi.DeviceNumber = audioDeviceNumber;
        wi.WaveFormat = new NAudio.Wave.WaveFormat(RATE, 16,1);
        //wi.BufferMilliseconds = (int)((double)BUFFERSIZE / (double)RATE * 1000.0);
        wi.DataAvailable += new EventHandler<WaveInEventArgs>(AudioDataAvailable);
        bwp = new BufferedWaveProvider(wi.WaveFormat);
        bwp.BufferLength = BUFFERSIZE * 2;
        bwp.DiscardOnBufferOverflow = true;
        player = new WaveOut();
        player.Init(bwp);
        player.Play();
        try
        {
            wi.StartRecording();
        }
        catch
        {
            string msg = "Could not record from audio device!\n\n";
            msg += "Is your microphone plugged in?\n";
            msg += "Is it set as your default recording device?";
            MessageBox.Show(msg, "ERROR");
        }
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // turn off the timer, take as long as we need to plot, then turn the timer back on
        timerReplot.Enabled = false;
        PlotLatestData();
        timerReplot.Enabled = true;
    }

    public int numberOfDraws = 0;
    public bool needsAutoScaling = true;
    public void PlotLatestData()
    {
        // check the incoming microphone audio
        int frameSize = BUFFERSIZE;
        var audioBytes = new byte[frameSize];
        bwp.Read(audioBytes, 0, frameSize);
        textBox1.Text = BitConverter.ToString(audioBytes);
        // return if there's nothing new to plot
        if (audioBytes.Length == 0)
            return;
        if (audioBytes[frameSize - 2] == 0)
            return;

        // incoming data is 16-bit (2 bytes per audio point)
        int BYTES_PER_POINT = 2;

        // create a (32-bit) int array ready to fill with the 16-bit data
        int graphPointCount = audioBytes.Length / BYTES_PER_POINT;

        // create double arrays to hold the data we will graph
        double[] pcm = new double[graphPointCount];
        double[] fft = new double[graphPointCount];
        double[] fftReal = new double[graphPointCount/2];

        // populate Xs and Ys with double data
        for (int i = 0; i < graphPointCount; i++)
        {
            // read the int16 from the two bytes
            Int16 val = BitConverter.ToInt16(audioBytes, i * 2);

            // store the value in Ys as a percent (+/- 100% = 200%)
            pcm[i] = (double)(val) / Math.Pow(2,16) * 200.0;
        }

        // calculate the full FFT
        fft = FFT(pcm);

        // determine horizontal axis units for graphs
        double pcmPointSpacingMs = RATE / 1000;
        double fftMaxFreq = RATE / 2;
        double fftPointSpacingHz = fftMaxFreq / graphPointCount;

        // just keep the real half (the other half imaginary)
        Array.Copy(fft, fftReal, fftReal.Length);

        // plot the Xs and Ys for both graphs
        scottPlotUC1.Clear();
        scottPlotUC1.PlotSignal(pcm, pcmPointSpacingMs, Color.Blue);
        scottPlotUC2.Clear();
        scottPlotUC2.PlotSignal(fftReal, fftPointSpacingHz, Color.Blue);

        // optionally adjust the scale to automatically fit the data
        if (needsAutoScaling)
        {
            scottPlotUC1.AxisAuto();
            scottPlotUC2.AxisAuto();
            needsAutoScaling = false;
        }

        //scottPlotUC1.PlotSignal(Ys, RATE);

        numberOfDraws += 1;
        lblStatus.Text = $"Analyzed and graphed PCM and FFT data {numberOfDraws} times";

        // this reduces flicker and helps keep the program responsive
        Application.DoEvents(); 

    }

    private void autoScaleToolStripMenuItem_Click(object sender, EventArgs e)
    {
        needsAutoScaling = true;
    }

    private void infoMessageToolStripMenuItem_Click(object sender, EventArgs e)
    {
        string msg = "";
        msg += "left-click-drag to pan\n";
        msg += "right-click-drag to zoom\n";
        msg += "middle-click to auto-axis\n";
        msg += "double-click for graphing stats\n";
        MessageBox.Show(msg);
    }

    private void websiteToolStripMenuItem_Click(object sender, EventArgs e)
    {
        System.Diagnostics.Process.Start("https://github.com/swharden/Csharp-Data-Visualization");
    }

    public double[] FFT(double[] data)
    {
        double[] fft = new double[data.Length];
        System.Numerics.Complex[] fftComplex = new System.Numerics.Complex[data.Length];
        for (int i = 0; i < data.Length; i++)
            fftComplex[i] = new System.Numerics.Complex(data[i], 0.0);
        Accord.Math.FourierTransform.FFT(fftComplex, Accord.Math.FourierTransform.Direction.Forward);
        for (int i = 0; i < data.Length; i++)
            fft[i] = fftComplex[i].Magnitude;
        return fft;
    }
    private void textBox1_TextChanged(object sender, EventArgs e)
    {

    }
}

} Please help me! Thanks everyone!

jensmalmgren commented 1 year ago

I suggest not using a timer. I have been experimenting with this on my own project and found that it is more robust to declare delegates for all the things in the UI that you need to update, move all code from TimerTick to the AudioDataAvailable, and then invoke UI things via delegates.