I'm new to C# programming and trying to write an application which is part of my final thesis.
I have a microprocessor that continuously send data from a sensor to my computer via serial port. All I want is to plotting this data using Zedgraph.
The problem is that the graph got too much delay and time lag. It seems the problem happens because I continuously update the whole graph at a very high rate.
I have stucked on this problem in a week and still dont find out a solution. I'll be more than happy if someone can help me out.
This is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ZedGraph;
using System.IO.Ports;
using System.Threading;
namespace DynamicData
{
public partial class Form1 : Form
{
private SerialPort port;
private string buffer = "";
private void connect()
{
port = new SerialPort("COM8", 115200, Parity.None, 8, StopBits.One);
port.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived);
if (!port.IsOpen) port.Open();
}
public Form1()
{
InitializeComponent();
}
private void Form1_Load( object sender, EventArgs e )
{
connect();
GraphPane myPane = zedGraphControl1.GraphPane;
RollingPointPairList list = new RollingPointPairList(500);
LineItem curve = myPane.AddCurve( "Sensor", list, Color.Blue, SymbolType.None );
myPane.XAxis.Scale.Min = 0;
myPane.XAxis.Scale.Max = 10;
myPane.YAxis.Scale.Min = 0;
myPane.YAxis.Scale.Max = 300;
myPane.XAxis.Scale.MinorStep = 0.5;
myPane.XAxis.Scale.MajorStep = 1;
zedGraphControl1.AxisChange();
}
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
//sample data: ;100*100000:
//sampling rate ~100Hz
buffer += port.ReadExisting();
//flush incomplete package
while (buffer[0] != ';')
{
buffer = buffer.Remove(0, 1);
if (buffer.Length < 1) break;
}
//got a complete package, go to data handling
while (buffer.Contains(":"))
{
DataHandling();
}
}
private void DataHandling()
{
string[] nameArray = buffer.Split(new[] { ";", ":", "*" }, StringSplitOptions.RemoveEmptyEntries);
//plot sensor data vs. time
draw(Convert.ToInt32(nameArray[0]), Convert.ToInt32(nameArray[1]));
//remove handled package in buffer
var index = buffer.IndexOf(":");
buffer = buffer.Remove(0, index + 1);
}
double time = 0;
private void draw(int sensor, int t)
{
//convert tick to sec (uP clock rate = 16MHZ)
time = time + (t / 16000000.0);
// Get the first CurveItem in the graph
LineItem curve = zedGraphControl1.GraphPane.CurveList[0] as LineItem;
// Get the PointPairList
IPointListEdit list = curve.Points as IPointListEdit;
list.Add(time, sensor);
//Keep the X scale at a rolling 10 second interval, with one
//major step between the max X value and the end of the axis
Scale xScale = zedGraphControl1.GraphPane.XAxis.Scale;
if (time > xScale.Max - xScale.MajorStep)
{
xScale.Max = time + xScale.MajorStep;
xScale.Min = xScale.Max - 10.0;
}
//Display sensor data
this.Invoke(new Action(() => { textBox1.Text = byte1.ToString(); }));
axisChangeZedGraph(zedGraphControl1);
}
delegate void axisChangeZedGraphCallBack(ZedGraphControl zg);
private void axisChangeZedGraph(ZedGraphControl zg)
{
if (zg.InvokeRequired)
{
axisChangeZedGraphCallBack ad = new axisChangeZedGraphCallBack(axisChangeZedGraph);
zg.Invoke(ad, new object[] { zg });
}
else
{
// zg.AxisChange();
zg.Invalidate();
zg.Refresh();
}
}
}
}
Thank you for reading!
The problem is that you call invalidate with every point you draw. That produces a very high processor load. I'm working on a very similar project, USB device, realtime plotting data. I use a separate thread for data acquisition. This thread creates an event, every time it receives a datapacket. The data is put into a queue. The graph is updated with a timer. You find an example here. In this code the graph is updated every 50 ms, it is not really necessary to draw faster. In the timertick I check the size of the Queue and draw more or less points and then call invalidate. I don`t know if this is a good solution(just 6month experience in C#), but it works quite well with 7500 points. You should try to use a timer for the refresh of the graph first.
1). Create and format the the curve only once. store it at the module level.
2). When adding a point use curve.AddPoint(); then zg.Refresh();
//this is just to add a point to the plot, the curve object should have already been created
private void draw(int sensor, int t)
{
//convert tick to sec (uP clock rate = 16MHZ)
time = time + (t / 16000000.0);
//curve should be a module-level variable already set up with proper formatting,
//just no points yet
curve.AddPoint(time, sensor);
//Display sensor data
this.Invoke(new Action(() => { textBox1.Text = byte1.ToString(); }));
zg.AxisChange();
zg.Refresh();
}
Related
I am using NAudio to get the sample from the song that is currently playing and draw the waveform as the song plays. I am using AudioFileReader and ToSampleProvider to get all samples as float and then I plot them into an InkCanvas while the song is playing. My problem is that the samples don't seem to match the sound. I also have verify this by using this same song in the WPF example that is located in the source code of NAudio. In the example the waveform matches the sound, but in my application it doesn't. So I was wondering if someone could help me find out what I am doing (or reading) wrong or if maybe my drawing logic is wrong.
Here is my current code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ISampleProvider provider;
private DispatcherTimer timer;
private AudioFileReader reader;
private WaveOut waveOut;
private StylusPointCollection topPoints, bottomPoints;
private DrawingAttributes attr;
private double canvasHeight, canvasWidth;
private int samplesGroupSize;
private double drawPos = 0;
private StrokeCollection _WaveformLines;
public StrokeCollection WaveformLines
{
get { return _WaveformLines; }
set
{
_WaveformLines = value;
OnPropertyChanged("WaveformLines");
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
reader = new AudioFileReader("C:\\Users\\Agustin\\Desktop\\DragonRider.mp3");
waveOut = new WaveOut();
waveOut.Init(reader);
provider = reader.ToSampleProvider(); //Here I get the samples
reader.Position = 0; //Go to the position 0 after reading the samples
canvasHeight = Waveform.ActualHeight;
canvasWidth = Waveform.ActualWidth;
WaveformLines = new StrokeCollection();
topPoints = new StylusPointCollection();
topPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
topPoints.Changed += topPoints_Changed;
bottomPoints = new StylusPointCollection();
bottomPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
bottomPoints.Changed += topPoints_Changed;
WaveformLines.Add(new Stroke(topPoints));
WaveformLines.Add(new Stroke(bottomPoints));
attr = new DrawingAttributes();
attr.Color = Colors.Green;
attr.Width = 1.5;
attr.Height = 1;
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += timer_Tick;
timer.Start();
samplesGroupSize = (int)(timer.Interval.TotalSeconds * reader.WaveFormat.SampleRate); //The value for this is 44.
}
private void PlayButton_Click(object sender, RoutedEventArgs e)
{
waveOut.Play();
}
private void PauseButton_Click(object sender, RoutedEventArgs e)
{
waveOut.Pause();
}
private void timer_Tick(object sender, EventArgs e)
{
if (waveOut.PlaybackState == PlaybackState.Playing)
{
TimeLabel.Content = string.Format("Time: {0}", reader.CurrentTime.ToString(#"mm\:ss\:ff")); //NEED TO KEEP WORKING
float[] samps = new float[samplesGroupSize];
provider.Read(samps, 0, samps.Length);
float max = Max(samps);
float min = Min(samps);
topPoints.Add(new StylusPoint(drawPos, (canvasHeight / 2) - ((canvasHeight / 2) * max)));
bottomPoints.Add(new StylusPoint(drawPos, (canvasHeight / 2) - ((canvasHeight / 2) * min)));
drawPos += 2;
if (drawPos > canvasWidth)
{
WaveformLines.Clear();
topPoints = new StylusPointCollection();
topPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
bottomPoints = new StylusPointCollection();
bottomPoints.Add(new StylusPoint(0, (canvasHeight / 2)));
WaveformLines.Add(new Stroke(topPoints));
WaveformLines.Add(new Stroke(bottomPoints));
drawPos = 0;
}
}
}
private float Min(float[] samps)
{
float max = samps[0];
foreach (float s in samps)
{
if (s > max)
max = s;
}
return max;
}
private float Max(float[] samps)
{
float min = samps[0];
foreach (float s in samps)
{
if (s < min)
min = s;
}
return min;
}
//I excluded the INotifyPropertyChanged implementation, but in the
//actual code is located here
}
I know this drawing algorithm is not very good but I have tried others and they also seem to not follow the audio.
Thanks.
NOTE: I know that there are similar question, but the other questions suggest to use things like AudioFileReader or ToSampleProvider which I am already using. My error is probably more into how I am reading the samples, maybe I am missing some bytes or have to skip some byte, or maybe some missing property that I am not setting.
You should seriously consider using the portions of the code from the WFP example that handles read/play and the min / max calculation work.
It will take you a little bit of effort but I promise it will be worth it.
You then will be working with a dataset that you know is in the correct form and you can focus on the drawing part. Look closely at AudioPlayback.cs and its relationship to SampleAggregator.cs.
You'll also find that getting automatic callbacks while the samples are being read (and played) is a much better way to refresh your waveform drawing than trying to use a DispatchTimer. It will also get you out of re-reading the wave buffer as well -- you really want to avoid that if you can.
EDIT:
I tested your conversion code and the resulting float values appear to be correct (in the range of -1 through 1). So I think the problem is with the way you are plotting the waveform in WPF.
I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]
foreach (var node in stack)
{
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
Thread.Sleep(500);
}
The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?
For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0
Edit:
Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.
The code for using a Thread was just shorter and I wanted to give him a solution quickly.
Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.
This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.
Use the timer_Tick function to update the position.
You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.
public partial class Form1 : Form
{
private Point pos = new Point(1,1);
private float[] vel = new float[2];
private Size bounds = new Size(20,20);
private Timer ticky = new Timer(); //System.Windows.Forms.Timer
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ticky.Interval = 20;
ticky.Tick += ticky_Tick;
vel[0] = 4; vel[1] = 0;
ticky.Start();
}
void ticky_Tick(object sender, EventArgs e)
{
updatePosition();
//This tells our form to repaint itself (and call the OnPaint method)
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
}
private void updatePosition()
{
pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
vel[1] += .5f; //Apply some gravity
if (pos.X + bounds.Width > this.ClientSize.Width)
{
vel[0] *= -1;
pos.X = this.ClientSize.Width - bounds.Width;
}
else if (pos.X < 0)
{
vel[0] *= -1;
pos.X = 0;
}
if (pos.Y + bounds.Height > this.ClientSize.Height)
{
vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
pos.Y = this.ClientSize.Height - bounds.Height;
}
else if (pos.Y < 0)
{
vel[1] *= -1;
pos.Y = 0;
}
}
}
Results:
You can use timers to do all sorts of delayed form drawing:
Original Solution:
//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;
new Thread(() => {
foreach (var node in stack)
{
//The invoke only needs to be used when updating GUI Elements
this.Invoke((MethodInvoker)delegate() {
//Everything inside of this Invoke runs on the GUI Thread
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
});
Thread.Sleep(500);
}
}).Start();
Solution in 2022:
await Task.Delay(500);
Things really are better these days.
I am using C# and the WebClient class.
This is the code I am using, inspired by another post here on SO. This code worked well for large files, it accurately displayed the download speed. However, there is now the limitation of downloading individual files, many of which are small, with small being .5-5 MB. This has caused the speed counter to skyrocket, often into the hundreds of thousands of KBps. I'm not sure what else to try to combat this. I added a second progress bar showing individual file downloads which helps improve the image a bit, but the download speed counter should really be fixed. Is there a different class to use that would solve this problem?
The WebClient in this code is disposed of properly elsewhere.
private class NetSpeedCounter
{
private double[] DataPoints;
private DateTime LastUpdate;
private int NumCounts = 0;
private int PrevBytes = 0;
public double Speed { get; private set; }
public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
{
DataPoints = new double[maxPoints];
Array.Clear(DataPoints, 0, DataPoints.Length);
webClient.DownloadProgressChanged += (sender, e) =>
{
var msElapsed = DateTime.Now - LastUpdate;
int curBytes = (int)(e.BytesReceived - PrevBytes);
PrevBytes = (int)e.BytesReceived;
double dataPoint = ((double)curBytes) / msElapsed.TotalSeconds;
DataPoints[NumCounts++ % maxPoints] = dataPoint;
Speed = DataPoints.Average();
};
}
public void Reset()
{
PrevBytes = 0;
LastUpdate = DateTime.Now;
}
}
I download the files with this code, which is started afterwards by a call to DownloadFileAsync. This code just downloads them in a chain, one after another, asynchronously.
This is setting up for starting the download
Queue recordQ = new Queue(files);
progressBar.Value = 0;
progressBar.Maximum = recordQ.Count;
UpdateStatusText("Downloading " + recordQ.Count + " files");
var record = recordQ.Dequeue();
speedUpdater.Start();
CheckAndCreate(record.AbsolutePath);
Adding the event handler
wc.DownloadFileCompleted += (sender, e) =>
{
var nr = recordQ.Dequeue();
CheckAndCreate(nr.AbsolutePath);
this.Invoke((MethodInvoker)delegate
{
UpdateStatusText("Downloading " + recordQ.Count + " files", lblStatusR.Text);
});
counter.Reset();
// download the next one
wc.DownloadFileAsync(nr.DownloadPath, nr.AbsolutePath);
}
counter.Start();
wc.DownloadFileAsync(record.DownloadPath, record.AbsolutePath);
This last call is what starts everything off.
DateTime.Now is not accurate enough for some scenarios where timespans are recorded frequently(Eric Lippert mentions that they have a precision of 30 ms here), since DateTime.Now will return the previously used DateTime.Now when called quickly in succession. This might result in discrepencies in your speed counter due to inaccurate increases when downloads are finished very quickly. I'd recommend using StopWatch API for that purpose.
EDIT
I have created the following test Winforms application based on your code that works fine for small files. I'm getting a reasonable 200 kbps over my intranet for 5 files that are about 2MB each. Just make sure you're calling the stopwatch classes at the right places.
To replicate, create a winforms app, create 3 labels of Id lblSpeed, lblStatus, lblFile and copy\paste the code and rename the URI's below to the files you want to test on.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Queue<Record> recordQ;
WebClient wc;
NetSpeedCounter counter;
//We store downloaded files in C:\TestDir (hardcoded in the Record class below)
public Form1()
{
InitializeComponent();
recordQ = new Queue<Record>();
//replace the URI string below. Nothing else to replace.
//recordQ.Enqueue(new Record(#"URI1", "SQLtraining.exe"));
//recordQ.Enqueue(new Record(#"URI2", "Project Mgmt.pptx"));
//first uri to process. Second param is the file name that we store.
Record record = new Record(#"URI0","Agile.pptx"); // replace the URI
//Initialize a webclient and download the first record
using (wc = new WebClient())
{
counter = new NetSpeedCounter(wc);
wc.DownloadFileCompleted += (sender, e) =>
{
if (recordQ.Count == 0)
{
UpdateStatusText("Done");
return;
}
var nr = recordQ.Dequeue();
//just create directory. the code uses the same directory
CheckAndCreate(nr.Directory);
//need not even use invoke here. Just a plain method call will suffice.
this.Invoke((MethodInvoker)delegate
{
UpdateStatusText("Left to process: " + recordQ.Count + " files");
});
counter.Reset();
counter.Start();
//continue with rest of records
wc.DownloadFileAsync(nr.DownloadPath, nr.GetFullPath());
this.lblFile.Text = nr.DownloadPath.OriginalString;
};
//just update speed in UI
wc.DownloadProgressChanged += wc_DownloadProgressChanged;
counter.Start();
//display URI we are downloading
this.lblFile.Text = record.DownloadPath.OriginalString;
//start first download
wc.DownloadFileAsync(record.DownloadPath, record.GetFullPath());
}
}
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.lblSpeed.Text = counter.Speed.ToString();
}
public void UpdateStatusText(string msg)
{
this.lblStatus.Text = msg;
}
public void CheckAndCreate(string absPath)
{
if (!Directory.Exists(absPath))
Directory.CreateDirectory(absPath);
}
}
public class NetSpeedCounter
{
private int NumCounts = 0;
private int PrevBytes = 0;
private Stopwatch stopwatch;
public double Speed { get; private set; }
double[] DataPoints;
public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
{
DataPoints = new double[maxPoints];
stopwatch = new Stopwatch();
Array.Clear(DataPoints, 0, DataPoints.Length);
webClient.DownloadProgressChanged += (sender, e) =>
{
var msElapsed = DateTime.Now - LastUpdate;
stopwatch.Stop();
int curBytes = (int)(e.BytesReceived - PrevBytes);
PrevBytes = (int)e.BytesReceived;
//record in kbps
double dataPoint = (double)curBytes / (stopwatch.ElapsedMilliseconds);
DataPoints[NumCounts++ % maxPoints] = dataPoint;
//protect NumCount from overflow
if (NumCounts == Int32.MaxValue)
NumCounts = 0;
Speed = DataPoints.Average();
stopwatch.Start();
};
}
public void Start()
{
stopwatch.Start();
}
public void Reset()
{
PrevBytes = 0;
stopwatch.Reset();
}
}
public class Record
{
public string Directory;
public string File;
public Uri DownloadPath;
public Record(string uriPath, string fileOutputName)
{
this.Directory = #"C:\TestDir\";
this.DownloadPath = new Uri(uriPath);
this.File = fileOutputName;
}
public string GetFullPath()
{
return this.Directory + this.File;
}
}
}
I have come up with a different way of doing it.
Instead of adding a data point for each ProgressChanged event, I now increment a counter with the amount of bytes. This counter stays across multiple files, and then I simply take a datapoint using a timer, and get the time in between using a System.Diagnostic.Stopwatch
It works very good, the accuracy is much much better, I would highly recommend doing it this way.
Here is some code
class NetSpeedCounter
{
private Stopwatch watch;
private long NumCounts = 0;
private int PrevBytes = 0;
private double[] DataPoints;
private long CurrentBytesReceived = 0;
public double Speed { get; private set; }
private System.Timers.Timer ticker = new System.Timers.Timer(100);
public NetSpeedCounter(WebClient webClient, int maxPoints = 5)
{
watch = new System.Diagnostics.Stopwatch();
DataPoints = new double[maxPoints];
webClient.DownloadProgressChanged += (sender, e) =>
{
int curBytes = (int)(e.BytesReceived - PrevBytes);
if (curBytes < 0)
curBytes = (int)e.BytesReceived;
CurrentBytesReceived += curBytes;
PrevBytes = (int)e.BytesReceived;
};
ticker.Elapsed += (sender, e) =>
{
double dataPoint = (double)CurrentBytesReceived / watch.ElapsedMilliseconds;
DataPoints[NumCounts++ % maxPoints] = dataPoint;
Speed = DataPoints.Average();
CurrentBytesReceived = 0;
watch.Restart();
};
}
public void Stop()
{
watch.Stop();
ticker.Stop();
}
public void Start()
{
watch.Start();
ticker.Start();
}
public void Reset()
{
CurrentBytesReceived = 0;
PrevBytes = 0;
watch.Restart();
ticker.Start();
}
}
I'm building an application which records audio from the microphone to a file (.mp3). I want my application to write data to the file only when a high enough amplitude is detected. I'm having trouble trying to save the selected data. All I get is a fast distortion, not even close from what was recorded.
This is 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;
namespace NAudio_Testing
{
public partial class Form1 : Form
{
int counter = 0;
int passed = 0;
private BufferedWaveProvider bufferedWaveProvider;
WaveIn waveIn;
WaveFileWriter writer;
string output = "C:/Users/klein/AppData/Roaming/Microsoft/Windows/gravacao.mp3";
public Form1()
{
InitializeComponent();
waveIn = new WaveIn();
writer = new WaveFileWriter(output, waveIn.WaveFormat);
waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(new_dataAvailable);
bufferedWaveProvider = new BufferedWaveProvider(waveIn.WaveFormat);
bufferedWaveProvider.DiscardOnBufferOverflow = true;
waveIn.StartRecording();
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = 1000;
timer1.Start();
}
void new_dataAvailable(object sender, WaveInEventArgs e)
{
for (int i = 0; i < e.BytesRecorded; i+=2)
{
short sample = (short)((e.Buffer[i + 1] << 8) | e.Buffer[i + 0]);
float sample32 = sample / 32768f;
if (sample32 > 0.02) //0.02~0.03
{
writer.WriteSample(sample);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
waveIn.StopRecording();
waveIn.Dispose();
waveIn = null;
writer.Close();
writer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
textBox1.Text = counter++.ToString();
}
}
}
EDIT: I am able to record the audio successfully if I remove the selective if and just add the lines, the problem, of course is that then I'm unable to record or not according to the amplitude of the audio:
writer.WriteByte(e.Buffer[i + 0]);
writer.WriteByte(e.Buffer[i + 1]);
Like so:
void new_dataAvailable(object sender, WaveInEventArgs e)
{
for (int i = 0; i < e.BytesRecorded; i+=2)
{
short sample = (short)((e.Buffer[i + 1] << 8) | e.Buffer[i + 0]);
float sample32 = sample / 32768f;
writer.WriteByte(e.Buffer[i + 0]);
writer.WriteByte(e.Buffer[i + 1]);
}
}
Thank you.
Your approach is essentially throwing away all samples near a zero crossing, irrespective of whether the overall audio at that time is loud or not. It will distort the signal, and speed it up, since audio signals typically have lots of zero crossings every second.
Instead, you need to measure the maximum volume of all the samples in a short time period (how long is up to you, but you could do 1 second at a time). If during that time any sample is louder than the specified minimum value then you record the whole block.
The second problem with your code is that you are not actually creating an MP3 file. The WaveFileWriter makes a WAV file. Change your filename to end in WAV and listen to that to hear the results of your recording. Then you would need to convert to MP3 as a second step after recording. (I have an article on how to convert between formats here).
You should compare absolute value of sample, since samples are signed.
if (Abs(sample32) > 0.02) //0.02~0.03
{
writer.WriteSample(sample);
}
at the moment im trying to figure out how i can manage to play a wave file in C# by filling up the secondary buffer with data from the wave file through threading and then play the wave file.
Any help or sample coding i can use?
thanks
sample code being used:
public delegate void PullAudio(short[] buffer, int length);
public class SoundPlayer : IDisposable
{
private Device soundDevice;
private SecondaryBuffer soundBuffer;
private int samplesPerUpdate;
private AutoResetEvent[] fillEvent = new AutoResetEvent[2];
private Thread thread;
private PullAudio pullAudio;
private short channels;
private bool halted;
private bool running;
public SoundPlayer(Control owner, PullAudio pullAudio, short channels)
{
this.channels = channels;
this.pullAudio = pullAudio;
this.soundDevice = new Device();
this.soundDevice.SetCooperativeLevel(owner, CooperativeLevel.Priority);
// Set up our wave format to 44,100Hz, with 16 bit resolution
WaveFormat wf = new WaveFormat();
wf.FormatTag = WaveFormatTag.Pcm;
wf.SamplesPerSecond = 44100;
wf.BitsPerSample = 16;
wf.Channels = channels;
wf.BlockAlign = (short)(wf.Channels * wf.BitsPerSample / 8);
wf.AverageBytesPerSecond = wf.SamplesPerSecond * wf.BlockAlign;
this.samplesPerUpdate = 512;
// Create a buffer with 2 seconds of sample data
BufferDescription bufferDesc = new BufferDescription(wf);
bufferDesc.BufferBytes = this.samplesPerUpdate * wf.BlockAlign * 2;
bufferDesc.ControlPositionNotify = true;
bufferDesc.GlobalFocus = true;
this.soundBuffer = new SecondaryBuffer(bufferDesc, this.soundDevice);
Notify notify = new Notify(this.soundBuffer);
fillEvent[0] = new AutoResetEvent(false);
fillEvent[1] = new AutoResetEvent(false);
// Set up two notification events, one at halfway, and one at the end of the buffer
BufferPositionNotify[] posNotify = new BufferPositionNotify[2];
posNotify[0] = new BufferPositionNotify();
posNotify[0].Offset = bufferDesc.BufferBytes / 2 - 1;
posNotify[0].EventNotifyHandle = fillEvent[0].Handle;
posNotify[1] = new BufferPositionNotify();
posNotify[1].Offset = bufferDesc.BufferBytes - 1;
posNotify[1].EventNotifyHandle = fillEvent[1].Handle;
notify.SetNotificationPositions(posNotify);
this.thread = new Thread(new ThreadStart(SoundPlayback));
this.thread.Priority = ThreadPriority.Highest;
this.Pause();
this.running = true;
this.thread.Start();
}
public void Pause()
{
if (this.halted) return;
this.halted = true;
Monitor.Enter(this.thread);
}
public void Resume()
{
if (!this.halted) return;
this.halted = false;
Monitor.Pulse(this.thread);
Monitor.Exit(this.thread);
}
private void SoundPlayback()
{
lock (this.thread)
{
if (!this.running) return;
// Set up the initial sound buffer to be the full length
int bufferLength = this.samplesPerUpdate * 2 * this.channels;
short[] soundData = new short[bufferLength];
// Prime it with the first x seconds of data
this.pullAudio(soundData, soundData.Length);
this.soundBuffer.Write(0, soundData, LockFlag.None);
// Start it playing
this.soundBuffer.Play(0, BufferPlayFlags.Looping);
int lastWritten = 0;
while (this.running)
{
if (this.halted)
{
Monitor.Pulse(this.thread);
Monitor.Wait(this.thread);
}
// Wait on one of the notification events
WaitHandle.WaitAny(this.fillEvent, 3, true);
// Get the current play position (divide by two because we are using 16 bit samples)
int tmp = this.soundBuffer.PlayPosition / 2;
// Generate new sounds from lastWritten to tmp in the sound buffer
if (tmp == lastWritten)
{
continue;
}
else
{
soundData = new short[(tmp - lastWritten + bufferLength) % bufferLength];
}
this.pullAudio(soundData, soundData.Length);
// Write in the generated data
soundBuffer.Write(lastWritten * 2, soundData, LockFlag.None);
// Save the position we were at
lastWritten = tmp;
}
}
}
public void Dispose()
{
this.running = false;
this.Resume();
if (this.soundBuffer != null)
{
this.soundBuffer.Dispose();
}
if (this.soundDevice != null)
{
this.soundDevice.Dispose();
}
}
}
}
The concept is the same that im using but i can't manage to get a set on wave byte [] data to play
I have not done this.
But the first place i would look is XNA.
I know that the c# managed directx project was ditched in favor of XNA and i have found it to be good for graphics - i prefer using it to directx.
what is the reason that you decided not to just use soundplayer, as per this msdn entry below?
private SoundPlayer Player = new SoundPlayer();
private void loadSoundAsync()
{
// Note: You may need to change the location specified based on
// the location of the sound to be played.
this.Player.SoundLocation = http://www.tailspintoys.com/sounds/stop.wav";
this.Player.LoadAsync();
}
private void Player_LoadCompleted (
object sender,
System.ComponentModel.AsyncCompletedEventArgs e)
{
if (this.Player.IsLoadCompleted)
{
this.Player.PlaySync();
}
}
usually i just load them all up in a thread, or asynch delegate, then play or playsynch them when needed.
You can use the DirectSound support in SlimDX: http://slimdx.org/ :-)
You can use nBASS or better FMOD both are great audio libraries and can work nicely together with .NET.
DirectSound is where you want to go. It's a piece of cake to use, but I'm not sure what formats it can play besides .wav
http://msdn.microsoft.com/en-us/library/windows/desktop/ee416960(v=vs.85).aspx