Resolved using multiple progress bars as volume meters for multiple recordings using naudio

timotech

Member
Joined
Aug 15, 2022
Messages
12
Programming Experience
10+
Good day guys,
Please I am doing multiple recordings from different mic inputs using naudio, but I have issue displaying the volume meters for each recording.
Please how can I resolve this, please check my code below.
Thanks
C#:
class VolumeSignal
{
    public ProgressBar Progress { get; set; }
    public double[] AudioValues { get; set; }
}

public partial class frmTestTabs : Form
{
    static WaveInEvent waveSource;
   
        readonly int SampleRate = 44100;
        readonly int BitDepth = 16;
        readonly int ChannelCount = 1;
        readonly int BufferMilliseconds = 20;
       
        readonly double[] AudioValues;
        object baton = new object();
  
public frmTestTabs()
{
            InitializeComponent();

    TabControl.TabPageCollection pages = tabControl1.TabPages;
    for (int i = 0; i < pages.Count; i++) //tab page is 8, pages.Count is 8
    {
        var AudioValues = new double[SampleRate * BufferMilliseconds / 1000];
        _ = Task.Factory.StartNew(() => RecordMicNAudio(0, AudioValues));
      
        //Each progress bar in the tabs
        var firstProgressBar = (from t in pages[i].Controls.OfType<ProgressBar>()
                                        select t).FirstOrDefault();
      
        //Timer event for progress bar
        TimerCallback timerCallback = new TimerCallback(timer_Elapsed);
        System.Threading.Timer timer = new System.Threading.Timer(timerCallback, new VolumeSignal { Progress = firstProgressBar, AudioValues = AudioValues }, 1000, 500);
    } 
}
  
private void timer_Elapsed(object msg)
        {
            lock (baton)
            {
                VolumeSignal volumeSignal = (VolumeSignal)msg;
                int level = (int)volumeSignal.AudioValues.Max();
                var prBar = volumeSignal.Progress;

                // auto-scale the maximum progressbar level
                if (level > prBar.Maximum)
                    prBar.Maximum = level;
                prBar.Value = level;
            }
        }

void RecordMicNAudio(int deviceNum, double[] AudioVals)
        {
            waveSource = new WaveInEvent();
            waveSource.DeviceNumber = deviceNum;
            waveSource.WaveFormat = new NAudio.Wave.WaveFormat(rate: sampleRate, bits: 16, channels: 1);
            waveSource.DataAvailable += (_, e) =>
            {
                //Recording code here
                //volume meter below
                    int peakValue = 0;
                    for (int i = 0; i < e.Buffer.Length / 2; i++)
                    {
                        int value = BitConverter.ToInt16(e.Buffer, i * 2);
                        peakValue = Math.Max(peakValue, value);
                    }
                    AudioVals[0] = peakValue;
                }
            };
            waveSource.RecordingStopped += (_, _) => MessageBox.Show("Sound Stopped! Cannot capture sound from device...");
            waveSource.BufferMilliseconds = 1000;
            waveSource.StartRecording();
        }
}
 
Last edited:
Solution
Ok guys, finally, i've been able to reduce the code, as well as got everything working.
The reduced and working code below:
C#:
private List<Listener> listeners = new List<Listener>();

public frmTestRecordSignal()
{
    InitializeComponent();
    LoadThreads();
}

private void LoadThreads()
        {
            TabControl.TabPageCollection pages = tabControl1.TabPages;

            for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
            {
                var cbDevices = (from t in pages[i].Controls.OfType<ComboBox>()
                                 select t).FirstOrDefault();

                cbDevices.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName);
            }

            for (int i = 0; i < pages.Count...
Could you please describe the actual issue in detail? What EXACTLY happens that you don't expect or doesn't happen that you do expect? Have you debugged the code to see exactly how it behaves? If not then you ought to do so, although it can be tricky in multithreaded scenarios. You can then tell us about the difference in behaviour not just from the perspective of the application but also the code.

I notice that you have some comments in the code. That's not where you should describe the issue. Code comments may help but your post should contain a FULL and CLEAR explanation of the problem, i.e. what you're trying to achieve, how you're trying to achieve it, what happens when you try and how that differs from your expectations.

I don't really understand your code. You have a Main method - Main should be the entry point for the application so you should not use that name anywhere else - but it's referring to controls as though a form already exists. Is that Main method ever actually executed? If so then it definitely ought to have a different name.
 
Are you sure that the callbsck is not being called? That would be a serious bug in that .NET Framework if that were the case. Set a breakpoint on timer_Elapsed() to verify.

If it is being called, but the progress bars are not updating, it's likely because of the callbacks are called on a thread pool thread, but your UI was created and runs on the UI thread.

Also note that questions above regarding Main(). Things just doesn't look right if that were truly your main entry point for the program.
 
Hi @jmcilhinney and @Skydiver apologies for the confusion. I have pasted the right code.
The challenge I'm facing is that I expect that for each progress bars on each tab to be functional based on the timer_Elapsed.
As you suggested, I debugged the timer_Elapsed event, my initial thought was that it was not called, but I noticed that it was called only once after putting a breakpoint. Its supposed to be called for each iteration.
Also, immediately after the first iteration the waveSource.RecordingStopped += (_, _) => MessageBox.Show("Sound Stopped! Cannot capture sound from device...");
is now being called continuously for like 8 times, then the program hangs.
No error is being displayed.
Is it the for loop or what could be the error, and now its affecting the recording.
I'm a bit confused now.
I don't know if you can reproduce the error. But I really need help in resolving it.
Thanks
 
Update guys,
I was able to put the recording method into a helper class, and calling it helped to resolve the recording issue.
Same as the audio samples collection for the progress bar. This was based on a sample I saw here: Spectrogram/src/Spectrogram.MicrophoneDemo at main · swharden/Spectrogram
Now I call the recording with the code below, also I used a timer for the progress bars:

C#:
public partial class frmTestRecordSignal : Form
    {
        public frmTestRecordSignal()
        {
            InitializeComponent();

            if (NAudio.Wave.WaveIn.DeviceCount == 0)
            {
                MessageBox.Show("No audio input devices found.\n\nThis program will now exit.",
                    "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Close();
            }
            else
            {
                cbDevice.Items.Clear();
                for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
                {
                    cbDevice.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName);
                    cbDevice2.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName);                   
                }
                cbDevice.SelectedIndex = 0;
                cbDevice2.SelectedIndex = 0;
            }
        }

        private void cbDevice_SelectedIndexChanged(object sender, EventArgs e) => StartListening();
        private void cbDevice2_SelectedIndexChanged(object sender, EventArgs e) => StartListening();

        private Listener listener;
        private Listener listener2;
        private void StartListening()
        {
            int sampleRate = 6000;

            listener?.Dispose();
            listener2?.Dispose();

            listener = new Listener(cbDevice.SelectedIndex, sampleRate);
            listener2 = new Listener(cbDevice2.SelectedIndex, sampleRate);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //double[] newAudio = listener.GetNewAudio();
            
            progressBar1.Value = (int)(listener.AmplitudeFrac * progressBar1.Maximum);
            progressBar2.Value = (int)(listener2.AmplitudeFrac * progressBar2.Maximum);
        }

        
    }

Is there a way to reduce the code to avoid a lot of repeatitions?
Thanks
 
Ok guys, finally, i've been able to reduce the code, as well as got everything working.
The reduced and working code below:
C#:
private List<Listener> listeners = new List<Listener>();

public frmTestRecordSignal()
{
    InitializeComponent();
    LoadThreads();
}

private void LoadThreads()
        {
            TabControl.TabPageCollection pages = tabControl1.TabPages;

            for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
            {
                var cbDevices = (from t in pages[i].Controls.OfType<ComboBox>()
                                 select t).FirstOrDefault();

                cbDevices.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName);
            }

            for (int i = 0; i < pages.Count; i++)
            {
                var cbDevices = (from t in pages[i].Controls.OfType<ComboBox>()
                                 select t).FirstOrDefault();

                Listener listener = new(cbDevices.SelectedIndex, sampleRate);
                
                listeners.Add(listener);
            }
}

private void timer1_Tick(object sender, EventArgs e)
        {
            if (listeners.Count > 0)
            {
                for (int i = 1; i <= listeners.Count; i++)
                {
                    if (Controls.Find("progressBar" + i, true)[0] != null)
                    {
                        var ctrl = (ProgressBar)Controls.Find("progressBar" + i, true)[0];
                        ctrl.Value = (int)(listeners[i-1].AmplitudeFrac * ctrl.Maximum);
                    }
                }
            }
        }
 
Solution
You don't need line 34, 35, and 44. If listeners.Count is les than or equal to 0, then your for loop will never be entered. So there is no need to have the guard before the for loop.
 
You are searching through the controls collection twice on lines 38 and 40. Just search once and store that result in a variable.
 
Instead of constantly searching for controls, store references to your controls in an array, and just access that array.
 
Instead of constantly searching for controls, store references to your controls in an array, and just access that array.
Hi @Skydiver, thanks a lot for the suggestions. They have made the code quite smaller. I totally forgot about list<T>.Add,
Sometimes when programming, all the ideas just fly away.
Thanks so much. If you feel I can still reduce the code, I don't mind. Many upvotes for you. Thanks
 

Latest posts

Back
Top Bottom