XAudio2 - Play generated sine, when changing frequency clicking sound - c#

I want to develop an app to match your tinnitus frequency : A frequency is played and the user decrease or increase the freqency by pressing a plus or minus button. (see part of the codes, based on some coding from stackoverflow thx :-))
public static short[] BufferSamples = new short[44100 * 1 * 2];
private SourceVoice sourceVoice;
private AudioBuffer buffer;
private int Tfreq;
public MatchTinn()
{
InitializeComponent();
Loaded += MatchTinn_Loaded;
TFreq = 5000;
}
private void MatchTinn_Loaded(object sender, RoutedEventArgs e)
{
var dataStream = DataStream.Create(BufferSamples, true, true);
buffer = new AudioBuffer
{
LoopCount = AudioBuffer.LoopInfinite,
Stream = dataStream,
AudioBytes = (int)dataStream.Length,
Flags = BufferFlags.EndOfStream
};
FillBuffer(BufferSamples, 44100, Tfreq);
var waveFormat = new WaveFormat();
XAudio2 xaudio = new XAudio2();
MasteringVoice masteringVoice = new MasteringVoice(xaudio);
sourceVoice = new SourceVoice(xaudio, waveFormat, true);
// Submit the buffer
sourceVoice.SubmitSourceBuffer(buffer, null);
}
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
double totalTime = 0;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
double time = (double)totalTime / (double)sampleRate;
short currentSample = (short)(Math.Sin(2 * Math.PI * frequency * time) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
totalTime++;
}
private void m1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq - 1;
if (Tfreq < 0)
{
Tfreq = 0;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
private void p1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq + 1;
if (Tfreq > 16000)
{
Tfreq = 16000;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
Playing the frequency is fine, but when the user presses a button you here a clicking sound when the frequency is updated. Do you have any idea what makes the sound and how i can get rid of it?
Thanks.

When you change the frequency, you're causing a discontinuity in the waveform that manifests as a click. Instead of making your signal calculations against absolute time, you should keep track of the phase of your sine calculation (e.g. a value from 0 to 2*pi), and figure out how much you need to add to your phase (subtracting 2*pi every time you exceed 2*pi) for the next sample when playing a specific frequency. This way, when you change frequency, the phase that you supply as a parameter to Math.Sin doesn't change abruptly causing a click.

Expanding on the answer #spender gave (I need 50 rep to add comment to his answer), I had a similar problem with naudio. I was able to solve the issue by adding two bool values that monitored the current sign of the sine value and the previous sign of the sine value. If the previous sine was negative and the current sine is positive, we know we can safely adjust the frequency of the sine wave.
double sine = amplitude * Math.Sin(Math.PI * 2 * frequency * time);
isPreviousSineWaveValPositive = isSineWaveValPositive;
if (sine < 0)
{
isSineWaveValPositive = false;
}
else
{
isSineWaveValPositive = true;
}
// When the time is right, change the frequency
if ( false == isPreviousSineWaveValPositive && true == isSineWaveValPositive )
{
time = 0.0;
frequency = newFrequency;
}

Here's an example how you can get rid of the clicking. Instead of using a time, you should keep track of the current phase and calculate how much the phase is changed on the required frequency. Also this _currentPhase must be persistent so it will have the previous value. (declaring it within the method would result in a click aswell (on most frequencies)
private double _currentPhase = 0;
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
var phaseStep = ((Math.PI * 2) / (double)sampleRate) * frequency;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
_currentPhase += phaseStep;
short currentSample = (short)(Math.Sin(_currentPhase) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
}
}

Related

Emgu.CV Object Marker

I built a pan-tilt system with 2 servos and laser diode. The system was controlled by Arduino Nano. By using Emgu.CV in C#, I created a grayscale image and convert it to a black-white image using GrayImg.ThresholdBinaryInv. I'm using Inverse Kinematics calculations but the laser diode couldn't mark the object well. Where did I make a mistake? Image coordinate system and servos coordinate systems are not same. Thus, I did mapping and scaling. It should work but the accuracy of the laser diode is inaccurate.
Arduino Code
#include <Servo.h>
int Th1, Th2, tmp;
Servo M1;
Servo M2;
void setup()
{
Serial.begin(9600);
pinMode(6,OUTPUT; // Laser Diode
digitalWrite(6,0);
Th1 = 0;
Th2 = 0;
M1.attach(3);
M1.write(90);
M2.attach(9);
M2.write(90);
}
void loop()
{
delay(200); //sync issue only
if(Serial.available()>=2)
{
Th1 = Serial.read(); //read only one byte
Th2 = Serial.read();
M1.write(Th1);
M2.write(Th2);
//Remove any extra worng reading
while(Serial.available()) tmp = Serial.read();
// Run the robotic arm here. For testing, we will
digitalWrite(6,1);
delay(500);
digitalWrite(6,0);
delay(500);
//switch On or switch off a LED according to Th1 value
//Serial.print('1'); // This tell the PC that Arduino is Ready for next angles
}
}
C# Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.Util;
using Emgu.CV.Structure;
namespace ObjectMarker
{
public partial class Form1 : Form
{
private Capture capture;
private Image<Bgr, Byte> IMG;
private Image<Gray,Byte> blackWhite;
private Image<Gray, Byte> GrayImg;
private int N, Xpx, Ypx; // N -> Number of non-black pixels
private double Xcm, Ycm;
private double myScale;
private double Th1, Th2;
static SerialPort _serialPort;
public byte []Buff = new byte[1];
public Form1() //Constructor of the Form1
{
InitializeComponent();
//myScale = 65.0 / 480.0;
myScale = 1.7 / 13;
_serialPort = new SerialPort();
_serialPort.PortName = "COM4";//Set your board COM
_serialPort.BaudRate = 9600;
_serialPort.Open();
}
private void processFrame(object sender, EventArgs e) // Most important function
{ //You're not connected any camera - null
if (capture == null)//very important to handel excption - capture - point of the camera
{
try
{
capture = new Capture(1); //creatine a object
}
catch (NullReferenceException excpt)
{
MessageBox.Show(excpt.Message);
}
}
IMG = capture.QueryFrame();// capture the current frame. Get an image.
GrayImg = IMG.Convert<Gray, Byte>();
blackWhite = GrayImg.ThresholdBinaryInv(new Gray(25),new Gray (255));
Xpx = 0;
Ypx = 0;
N = 0;
for (int i = 0; i < blackWhite.Width; i++) {
for (int j = 0; j < blackWhite.Height; j++) {
if(blackWhite[j,i].Intensity > 128){
N++;
Xpx += i;
Ypx += j;
}
}
}
if(N>0){
Xpx = Xpx / N;
Ypx = Ypx / N;
Xpx = (blackWhite.Width / 2) - Xpx; //320 - xpx
Ypx = Ypx - (blackWhite.Height / 2); // ypx - 240
Xcm = Xpx * myScale;
Ycm = Ypx * myScale;
double d3 = 28; // Laser to wall dist. (?)
double Zcm = 108; // Cam to wall dist.
double d1 = 3.50; // Joint to joint dist.
double l2 = 4.50; // Joint to laser dist. (?)
textBox1.Text = Xcm.ToString();
textBox2.Text = Ycm.ToString();
textBox3.Text = N.ToString();
textBox11.Text = Zcm.ToString();
textBox12.Text = myScale.ToString();
//Inverse Calculations
double Px, Py, Pz,Diff = 0;
// Mapping
//Px = Zcm;
Px = -1 * Zcm;
Py = -1 * Xcm;
Pz = Ycm + Diff; // The laptop has built-in camera and the pan-tilt above the keyboard so there's a distance between camera and pan-tilt system (laser diode)
textBox8.Text = Px.ToString();
textBox9.Text = Py.ToString();
textBox10.Text = Pz.ToString();
Th1 = Math.Atan((Py / Px));
Th2 = Math.Atan((Math.Sin(Th1) * (Pz - d1)) / Py);
//Th1 = Math.Atan(Ycm / Xcm);
//Th2 = Math.Atan((Math.Sin(Th1) * (Zcm - d1)) / Ycm);
textBox4.Text = Th1.ToString();
textBox5.Text = Th2.ToString();
Th1 = (Th1 * (180 / Math.PI));
Th2 = (Th2 * (180 / Math.PI));
Th1 += 90;
Th2 += 90;
textBox6.Text = Th1.ToString();
textBox7.Text = Th2.ToString();
label11.Text = trackBar1.Value.ToString();
label12.Text = trackBar2.Value.ToString();
Buff[0] = (byte)trackBar1.Value; //Th1
Buff[2] = (byte)trackBar2.Value; //Th2
_serialPort.Write(Buff,0,2);
}
else
{
textBox1.Text = "";
textBox2.Text = "";
textBox3.Text = N.ToString();
Buff[0] = (byte)90; //Th1
Buff[2] = (byte)90; //Th2
_serialPort.Write(Buff, 0, 2);
}
try
{
imageBox1.Image = IMG;
imageBox2.Image = GrayImg;
imageBox3.Image = blackWhite;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void button1_Click(object sender, EventArgs e)
{
//Application.Idle += processFrame; //Serial comm between arduino and computer
timer1.Enabled = true;
button1.Enabled = false;
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
//Application.Idle -= processFrame;
timer1.Enabled = false;
button1.Enabled = true;
button2.Enabled = false;
}
private void button3_Click(object sender, EventArgs e) // I measure the object size from paint. It gave me pixel size. Then I measure the real size of the image. Real size divided by pixel size gives scale.
{
IMG.Save("G:\\CurrentFrame" + ".jpg");
}
void Timer1Tick(object sender, EventArgs e)
{
processFrame(sender,e);
}
}
}
EDIT: Images has been added. The problem is if I change the tape location inaccuracy of the system isn't accurate. In some points laser marked the tape very well but mostly on corners there's huge difference between laser and tape.
https://i.stack.imgur.com/fmm1x.png
https://i.stack.imgur.com/Quadr.png
https://i.stack.imgur.com/9SWa4.png
This question is very hard to answer, as there are many places where an error could have crept in. Without further diagnosis and knowledge of the system, it's almost impossible to give a clear answer. I suggest you perform some more diagnosis, and if the answer still eludes you, reformulate your question (or close it when you find the answer yourself).
Some possible sources of error (and points for diagnosis) could be:
In the Arduino code, you throw away anything after the first two bytes of Serial communication. Why would there be extra bytes? Are you sure the extra bytes are garbage, and, inversely, the first two bytes are the correct ones?
In the C# mapping of coordinates, you perform a translation of the coordinates. Are you sure the system is exactly axis-parallel? An error of just a few degrees quickly adds up. You might need to add a rotational transformation.
Your C# code returns an angle (in degrees) for the servo. This could be negative, or above 255, so converting it to a byte could, on overflow, lead to passing a (completely) wrong value to the Arduino.

Adding delay while taking in account execution time

So, I'm trying to create a method that animates the movement of a control on a form. I generate all the points the control is going to travel to beforehand, like this:
private static List<decimal> TSIncrement(int durationInMilliseconds, decimal startPoint, decimal endPoint)
{
List<decimal> tempPoints = new List<decimal>();
decimal distance = endPoint - startPoint;
decimal increment = distance / durationInMilliseconds;
decimal tempPoint = (decimal)startPoint;
for (decimal i = durationInMilliseconds; i > 0; i--)
{
tempPoint += increment;
tempPoints.Add(tempPoint);
}
return tempPoints;
}
This outputs a list with as many points as there are milliseconds in the duration of the animation. I think you can guess what I'm doing afterwards:
public static void ControlAnimation(Control control, Point locationEndpoint, int delay)
{
if (delay > 0)
{
List<decimal> tempXpoints = TSIncrement(delay, control.Location.X, locationEndpoint.X);
List<decimal> tempYpoints = TSIncrement(delay, control.Location.Y, locationEndpoint.Y);
for (int i = 0; i < delay; i++)
{
control.Location = new Point((int)Math.Round(tempXpoints[i]), (int)Math.Round(tempYpoints[i]));
Thread.Sleep(1); //I won't leave this obviously, it's just easier for now
}
}
}
In the actual method, I go through this list of points and use those to create the new location of the control (I actually use two lists for the abscissa and the ordinate).
My problem lies in creating one millisecond of delay between each shifting. Since the code in the loop takes a bit of time to execute, I usually end up with approximately 5 seconds more duration.
I tried using a stopwatch to measure the time it takes to set control.location, and subtracting that to the 1 millisecond delay. The stopwatch adds some delay as well though, since I gotta start, stop and reset it everytime.
So what should I do, and how could I improve my code? Any feedback is greatly appreciated :)
You won't get a reliable delay below around 50 milliseconds in WinForms, so that is the delay I used below:
private Random R = new Random();
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.AutoSize = false;
label1.Size = button2.Size;
Point p = new Point(R.Next(this.Width - button2.Width), R.Next(this.Height - button2.Height));
label1.Location = p;
label1.SendToBack();
await MoveControl(button2, p, R.Next(2000, 7001));
button1.Enabled = true;
}
private Task MoveControl(Control control, Point LocationEndPoint, int delayInMilliseconds)
{
return Task.Run(new Action(() =>
{
decimal p;
int startX = control.Location.X;
int startY = control.Location.Y;
int deltaX = LocationEndPoint.X - startX;
int deltaY = LocationEndPoint.Y - startY;
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
while(sw.ElapsedMilliseconds < delayInMilliseconds)
{
System.Threading.Thread.Sleep(50);
p = Math.Min((decimal)1.0, (decimal)sw.ElapsedMilliseconds / (decimal)delayInMilliseconds);
control.Invoke((MethodInvoker)delegate {
control.Location = new Point(startX + (int)(p * deltaX), startY + (int)(p * deltaY));
});
}
}));
}

Trying to figure out how to pause and resume in Cocossharp (BouncyGame)

So I'm playing around with the BouncyGame. I made it so that when you start the game you need to press the screen for it to start. I would like to implement this whenever you play a new round as well. I tried to reuse this att the bottom of my code but it made it extremely laggy.
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
touchListener.OnTouchesMoved = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
Schedule(RunGameLogic);
scoreLabel.Text = "Score: 0";
paddleSprite.RunAction(new CCMoveTo(.1f, new CCPoint(touches[0].Location.X, paddleSprite.PositionY)));
}
}
I have no idea how to do this, tried for 2 hours with 0 results. Any suggestions are welcome.
Here's the full code.
using System;
using System.Collections.Generic;
using CocosSharp;
using Microsoft.Xna.Framework;
namespace CocosSharpGameTest
{
public class IntroLayer : CCLayerColor
{
// Define a label variable
CCLabel scoreLabel;
CCSprite paddleSprite, ballSprite;
public IntroLayer() : base(CCColor4B.Black)
{
// create and initialize a Label
scoreLabel = new CCLabel("Tap to GO!", "Arial", 80, CCLabelFormat.SystemFont);
// add the label as a child to this Layer
scoreLabel.PositionX = 50;
scoreLabel.PositionY = 1000;
scoreLabel.AnchorPoint = CCPoint.AnchorUpperLeft;
AddChild(scoreLabel);
paddleSprite = new CCSprite("paddle.png");
AddChild(paddleSprite);
ballSprite = new CCSprite("ball.png");
AddChild(ballSprite);
}
protected override void AddedToScene()
{
base.AddedToScene();
// Use the bounds to layout the positioning of our drawable assets
CCRect bounds = VisibleBoundsWorldspace;
// position the label on the center of the screen
paddleSprite.PositionX = 100;
paddleSprite.PositionY = 100;
ballSprite.PositionX = 320;
ballSprite.PositionY = 640;
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
touchListener.OnTouchesMoved = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
Schedule(RunGameLogic);
scoreLabel.Text = "Score: 0";
paddleSprite.RunAction(new CCMoveTo(.1f, new CCPoint(touches[0].Location.X, paddleSprite.PositionY)));
}
}
float ballXVelocity;
float ballYVelocity;
// How much to modify the ball's y velocity per second:
const float gravity = 140;
int score = 0;
void RunGameLogic(float frameTimeInSeconds)
{
// This is a linear approximation, so not 100% accurate
ballYVelocity += frameTimeInSeconds * -gravity;
ballSprite.PositionX += ballXVelocity * frameTimeInSeconds;
ballSprite.PositionY += ballYVelocity * frameTimeInSeconds;
bool overlap = ballSprite.BoundingBoxTransformedToParent.IntersectsRect(paddleSprite.BoundingBoxTransformedToParent);
bool movingDown = ballYVelocity < 0;
if (overlap && movingDown)
{
ballYVelocity *= -1;
const float minXVelocity = -300;
const float maxXVelocity = 300;
ballXVelocity = CCRandom.GetRandomFloat(minXVelocity, maxXVelocity);
score++;
scoreLabel.Text = "Score: " + score;
}
float ballRight = ballSprite.BoundingBoxTransformedToParent.MaxX;
float ballLeft = ballSprite.BoundingBoxTransformedToParent.MinX;
float screenRight = VisibleBoundsWorldspace.MaxX;
float screenLeft = VisibleBoundsWorldspace.MinX;
bool shouldReflectXVelocity =
(ballRight > screenRight && ballXVelocity > 0) ||
(ballLeft < screenLeft && ballXVelocity < 0);
if (shouldReflectXVelocity)
{
ballXVelocity *= -1;
}
if (ballSprite.PositionY < VisibleBoundsWorldspace.MinY)
{
ballSprite.PositionX = 320;
ballSprite.PositionY = 640;
ballXVelocity = 0;
ballYVelocity = 0;
ballYVelocity *= -1;
scoreLabel.Text = "Score: 0";
score = 0;
}
}
}
}
Thanks in advance!
Figured it out!
There is an "Unschedule" Method built into Cocossharp.
Ref. https://developer.xamarin.com/api/namespace/CocosSharp/
I just added
Unschedule(RunGameLogic);
at the very en of my RunGameLogic method under
if (ballSprite.PositionY < VisibleBoundsWorldspace.MinY)
So once the ballSprite is out of bounds it will Unschedule what i Scheduled in my OntouchesEnded method. That means the code goes back to listening for touches.
Might have made some errors, but this is as best I could figure it out and it works!

Window Refresh Audio

So i'm in a bit of a problem, i'm coding a clap sensor, that hears when someone claps and executes a certain command.
//CLAP
private float bigValue;
WaveIn waveIn;
private double MaxValue;
private void button1_Loaded(object sender, RoutedEventArgs e)
{
if (Convert.ToInt16(textBox1.Text) > 100)
{
MessageBox.Show("Invalid Value");
return;
}
else
MaxValue = Convert.ToDouble(textBox1.Text) / 100;
bigValue = 0;
waveIn = new WaveIn();
int waveInDevices = waveIn.DeviceNumber;
//Get Device Count
for ( int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
}
waveIn.DeviceNumber = 0;
waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(waveIn_DataAvailable);
int sampleRate = 8000;
int channels = 1;
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
}
//CLAP
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
float sample32 = sample / 32768f;
label1.Content = sample32.ToString();
if (bigValue < sample32)
{
bigValue = sample32;
label2.Content = bigValue.ToString();
if (bigValue > MaxValue)
{
waveIn.StopRecording();
SendMessage(MONITOR_ON, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_ON);
MessageBox.Show("Did you Clap?");
}
}
}
}
The code itself works as is, but I need it to be able to reset itself as many times as I need. This program basically listens for a clap and wakes up the monitor and starts it up. The program breaks any time I add in another "waveIn.StartRecording();"
Any ideas on how I could refresh the page or make it listen for ever?
What basically your code is doing is opening waveIn to receive audio data, then examining the data for loud samples. When it receives a sample that exceeds a threshold it then stops listening and issues the command.
As written, the code stops after the first large sample is detected. No more audio data is received, etc. Probably not what you want. Instead you need to refine your clap detection so that it will stop processing the incoming data for a period of time - a few seconds say - after it detects the first big sample. Don't stop receiving the audio data, just stop reacting to it.
Add a DataTime field to your class that records the timestamp of the last clap detection. At the start of your waveIn_DataAvailable method check if the elapsed time since the last detection is less than your silence time, and if so just return without processing the audio block. When you detect a large enough sample, fire off the event and update the last clap detection field.
Something like this:
DateTime LastDetection = DateTime.Now.AddMinutes(-1);
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
if (LastDetection.AddSeconds(3) >= DateTime.Now)
return;
if (DetectClap(e.Buffer))
{
LastDetection = DateTime.Now;
SendMessage(MONITOR_ON, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_ON);
MessageBox.Show("Clap detected.");
}
}
bool DetectClap(byte[] audiobytes)
{
for (int i = 0; i < audiobytes.Length; i += 2)
{
float sample32 = (float)((short)((audiobytes[0] << 8) | audiobytes[1]))/32768f;
if (sample32 > MaxValue)
return true;
}
return false;
}
Here is an example that moves the WaveIn logic to a background thread. It should give you enough to start. Please check the documentation for a complete example that includes the background thread cancellation.
//CLAP
private float bigValue;
WaveIn waveIn;
private double MaxValue;
private BackgroundWorker worker;
private void button1_Loaded(object sender, RoutedEventArgs e)
{
if (Convert.ToInt16(textBox1.Text) > 100)
{
MessageBox.Show("Invalid Value");
return;
}
else
MaxValue = Convert.ToDouble(textBox1.Text) / 100;
bigValue = 0;
// You'll need to handle the thread cancellation
// when the user clicks the button again
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s, e) =>
{
waveIn = new WaveIn();
int waveInDevices = waveIn.DeviceNumber;
//Get Device Count
for ( int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
}
waveIn.DeviceNumber = 0;
waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(waveIn_DataAvailable);
int sampleRate = 8000;
int channels = 1;
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
};
worker.ProgressChanged += (s, e) =>
{
SendMessage(MONITOR_ON, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_ON);
MessageBox.Show("Did you Clap?");
};
worker.RunWorkerAsync();
}
//CLAP
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
float sample32 = sample / 32768f;
label1.Content = sample32.ToString();
if (bigValue < sample32)
{
bigValue = sample32;
label2.Content = bigValue.ToString();
if (bigValue > MaxValue)
{
worker.ReportProgress(0);
break;
}
}
}
}
So in the end I went with a different way than both suggested answers.
private float bigValue;
WaveIn waveIn;
private double MaxValue;
private void button1_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Convert.ToInt16(textBox1.Text) > 100)
{
MessageBox.Show("Invalid Value");
return;
}
else
MaxValue = Convert.ToDouble(textBox1.Text) / 100;
bigValue = 0;
waveIn = new WaveIn();
int waveInDevices = waveIn.DeviceNumber;
//Get Device Count
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
}
waveIn.DeviceNumber = 0;
waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(waveIn_DataAvailable);
int sampleRate = 8000;
int channels = 1;
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
}
private void button1_Loaded(object sender, RoutedEventArgs e)
{
if (Convert.ToInt16(textBox1.Text) > 100)
{
MessageBox.Show("Invalid Value");
return;
}
else
MaxValue = Convert.ToDouble(textBox1.Text) / 100;
bigValue = 0;
waveIn = new WaveIn();
int waveInDevices = waveIn.DeviceNumber;
for (int i = 0; i <= 100; i++)
{
}
//Get Device Count
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
}
waveIn.DeviceNumber = 0;
waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(waveIn_DataAvailable);
int sampleRate = 8000;
int channels = 1;
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
}
int i = 0;
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
float sample32 = sample / 32768f;
label1.Content = sample32.ToString();
if (bigValue < sample32)
{
bigValue = sample32;
label2.Content = bigValue.ToString();
if (bigValue > MaxValue)
{
waveIn.StopRecording();
if (IsOdd(i))
{
button1.IsEnabled = false;
}
else
{
button1.IsEnabled = true;
}
MessageBox.Show("Did you Clap?");
i++;
}
}
}
}
public static bool IsOdd(int value)
{
return value % 2 != 0;
}
}
The first load event sets it off. The second one goes back in forth between button on and button off using the IsEnabled event. The on and off are acheived by and if statement choosing between an odd number and even.
That is how I achieved this infinite loop.
NOTE: This way probably isn't the most efficient way but it got the job done.
Also I left the (Open Window) code out of this answer.

NAudio Peak Volume Meter

I'm attempting to write a peak volume meter using NAudio. My code is very similar to http://channel9.msdn.com/coding4fun/articles/NET-Voice-Recorder, but both my code and the linked Voice Recorder project suffer from an issue.
When playing a sound of constant frequency and volume, the volume meter initially begins at a reasonable level, but then decays to a very small value. I'm not sure why this is the case, for the peak volume meter in the NAudioDemo does not do this. I attempted to replicate the code from NAudioDemo in my program, but I was unable to find the code file containing the peak volume meter code.
Can somebody guide me to an alternative solution for creating a peak volume meter or help me determine why my solution (and the one provided at the link) both don't work?
public MainWindow()
{
int waveInDevices = WaveIn.DeviceCount;
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
Console.WriteLine("Device {0}: {1}, {2} channels",
waveInDevice, deviceInfo.ProductName, deviceInfo.Channels);
WaveIn waveIn = new WaveIn();
waveIn.DeviceNumber = 0; //TODO: Let the user choose which device, this comes from the device numbers above
waveIn.DataAvailable += waveIn_DataAvailable;
int sampleRate = SAMPLE_RATE; // 8 kHz
int channels = 1; // mono
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
}
}
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) |
e.Buffer[index + 0]);
float sample32 = sample / 32768f;
ProcessSample(sample32);
}
}
void ProcessSample(float sample1)
{
samplenumber += 1;
if (sample1 > maxval)
{
maxval = sample1;
}
if (sample1 < minval)
{
minval = sample1;
}
//Run updateView every few loops
if (samplenumber > (double)SAMPLE_RATE / DISPLAY_UPDATE_RATE)
{
samplenumber = 0;
updateView(); //needs to be fast!
}
}
void updateView()
{
Console.WriteLine(maxval);
Console.WriteLine(minval);
progressBar1.Value = (maxval - minval)*50;
maxval = 0;
minval = 0;
}
All that is happening in that article is that it is finding the maximum audio peak over a small interval (e.g. 20ms) and then plotting that on a decibel scale. To find the peak, examine the value of each sample in the interval and select the max value (It's what the SampleAggregator class is doing). To convert to decibels, take the log base 10 of the maximum value, and multiply by 10. So 0dB is the loudest, and anything below say -96dB is effectively silence. (actually, looking back at the article, I don't think I even bothered to convert to a decibel scale, which I probably should have done)
This was my little solution for getting peak from output device. I'm using NAudio version 1.7.0.15
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active);
comboboxDevices.Items.AddRange(devices.ToArray());
}
private void timer1_Tick(object sender, EventArgs e)
{
if (comboboxDevices.SelectedItem != null)
{
var device = (MMDevice)comboboxDevices.SelectedItem;
progressBar1.Value = (int)(Math.Round(device.AudioMeterInformation.MasterPeakValue * 100));
}
}
}
Trying to get the levels with MasterPeakValue appears to be more complicated than just calling the method, which defeats its simplicity.
I accidentally realized that you have to open the device for recording, even if you don't use with the incoming data. Since you are starting a WaveIn, MasterPeakValue should return a non-0 value.
A simple alternative, just for testing, is to open the properties of system's recording devices (right-click on system volume icon and choose "Recording devices").
(Tested on 2 different computers.)

Categories

Resources