C# Real time waveform data plot using NAudio - c#

I am new on processing wav file and C#.My goal is to real time data plotting in waveform of wavfile.I mean while recording sound(wav) file,i want to plot its graph simultaneously.I searched some sound libiraries and decide to use NAudio.(Dont know it is the best choice for me.I am open to any suggestions about choosing audio libirary). However i have no idea about real time data plotting using sound. Some people suggest GDI but as i said i am new and i think it will take too much time to use GDI efficiently.If i must learn GDI,pls share any article that can help a beginner like me. Actually i look like dont know where should i start. Need to be guided :)) And i have a question.
One of the tutorial of NAudio,he works with byte array to plot the waveform in Chart.It is fine if you know the size of wav file.However it works too slow and gives Out of Memory Exception for bigger wav files than 10mb.The code below refers to what i mean.
OpenFileDialog open = new OpenFileDialog();
open.Filter = "Wave File (*.wav)|*.wav;";
if (open.ShowDialog() != DialogResult.OK) return;
chart1.Series.Add("wave");
chart1.Series["wave"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine;
chart1.Series["wave"].ChartArea = "ChartArea1";
NAudio.Wave.WaveChannel32 wave = new NAudio.Wave.WaveChannel32(new NAudio.Wave.WaveFileReader(open.FileName));
byte[] buffer = new byte[426565];
int read;
while (wave.Position < wave.Length)
{
read = wave.Read(buffer, 0, 426565);
for (int i = 0; i < read / 4; i++)
{
chart1.Series["wave"].Points.Add(BitConverter.ToSingle(buffer, i * 4));
}
}
Is there a way to perform this operation faster?

If you plot every single sample, you will end up with a waveform that is unmanageably large since audio usually contains many thousands of samples per second. A common way waveforms are drawn is by selecting the maximum value over a period of time, and then drawing a vertical line to represent it. For example, if you had a three minute song, and wanted a waveform around 600 pixels wide, each pixel would represent about a third of a second. So you'd find the largest sample value in that third of a second and use that to plot your waveform.
Also, in your sample code you are reading an odd number of bytes. But since this is floating point audio, you should always read in multiples of four bytes.

This worked for me
WaveChannel32 wave = new WaveChannel32(new WaveFileReader(txtWave.Text));
int sampleSize = 1024;
var bufferSize = 16384 * sampleSize;
var buffer = new byte[bufferSize];
int read = 0;
chart.Series.Add("wave");
chart.Series["wave"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine;
chart.Series["wave"].ChartArea = "ChartArea1";
while (wave.Position < wave.Length)
{
read = wave.Read(buffer, 0, bufferSize);
for (int i = 0; i < read / sampleSize; i++)
{
var point = BitConverter.ToSingle(buffer, i * sampleSize);
chart.Series["wave"].Points.Add(point);
}
}

Related

How to know if audio is going dead in Naudio C#`

I am trying to find out when the song is going dead (inaudible sound for few seconds). I am using Naudio library in C#. Till now i am able to get the PCM data and plot the amplitude of the audio. I am guessing the dead audio through this amplitude i am obtaining. But i am bit confused about audio channels. Following is the piece of code i wrote.
NAudio.Wave.WaveChannel32 wave = new NAudio.Wave.WaveChannel32(new NAudio.Wave.WaveFileReader(open.FileName));
int songLength = (int)wave.Length;
byte[] songPCM = new byte[songLength];
int sampleRate = (int)wave.WaveFormat.SampleRate;
int bitsPerSample = (int)wave.WaveFormat.BitsPerSample;
int numChannels = (int)wave.WaveFormat.Channels;
wave.Read(songPCM, 0, songLength);
double[] _waveLeft = new double[songLength / 8];
double[] _waveRight = new double[songLength / 8];
System.IO.StreamWriter fileoutLeft = new System.IO.StreamWriter("E:\\LOutputSongPCM.dat", true);
System.IO.StreamWriter fileoutRight = new System.IO.StreamWriter("E:\\ROutputSongPCM.dat", true);
int h = 0;
for (int i = 0; i < songLength; i += 8)
{
_waveLeft[h] = (double)BitConverter.ToSingle(songPCM, i);
_waveRight[h] = (double)BitConverter.ToSingle(songPCM, i + 4);
chart1.Series["wave"].Points.Add(_waveLeft[h]);
//chart1.Series["wave"].Points.Add(_waveRight[h]);
fileoutLeft.WriteLine(_waveLeft[h]);
fileoutRight.WriteLine(_waveRight[h]);
h++;
}
fileoutLeft.Close();
fileoutRight.Close();
Now for this piece of code i know the audio is 2 channel. So i referred many links and threads and got confused if i am reading my pcm data for each channel correctly. However i compared the plots of each channel and they look good(Matching with original song) but i am not sure about their accuracy. Can you guide me to get the exact raw data for any channel. for mono, stereo and 5.1.
Thanks.
You'll find it easier to get at the samples by using the AudioFileReader class, whose Read method takes a float array. The samples are stored interleaved for multi-channel audio, so for stereo, you'll get left sample, then right sample, then another left and so on.

analyse the data chunck from WAVE file with NAUDIO in C#

I would like to draw out a little period of the waveform from a .wav file to the screen. Here is the code i created so far:
NAudio.Wave.WaveFileReader wave = new NAudio.Wave.WaveFileReader(#"C:\test.wav");
long le = wave.Length;
byte[] data = new byte[le];
wave.Read(data, 0, (int)le);
for (int i = 0; i < 100; i++)
{
System.Console.WriteLine(data[i]);
}
System.Console.ReadKey();
I just tried to get the first 100 sample of the datachunk but i dont fully understand the results. Are these numbers the amplitude values of the voice wave?
It is likely that your WAV file is 16 bit (you can check this by looking at the WaveFileReader's WaveFormat property and looking at the BitDepth). In that case, every two bytes represents a single sample. You can use BitConverter.ToInt16 to examine the value of each sample one by one. So for example, you could modify your code to be something like this:
NAudio.Wave.WaveFileReader wave = new NAudio.Wave.WaveFileReader(#"C:\test.wav");
byte[] data = new byte[200];
int read = wave.Read(data, 0, data.Length);
for (int i = 0; i < read; i+=2)
{
System.Console.WriteLine(BitConverter.ToInt16(data,i));
}
System.Console.ReadKey();
They are amplitude, but that 'amplitude' changes 44100 times per second for each channel.
Try this article: http://en.wikipedia.org/wiki/Pulse-code_modulation
If it fails, just remember this. Sound is change in air pressure. Air pressure change is produced by speakers by voltage change. Voltage change is produced by rapidly activating various input levels on digital to analog converters. Input levels (numbers) are what your are getting when you read your PCM data from a file.

Play dynamically-created simple sounds in C# without external libraries

I need to be able to generate dynamically a waveform and play it, using C#, without any external libraries, and without having to store sound files on the hard disk. Latency isn't an issue; the sounds would be generated well before they are needed by the application.
Actually the Console.Beep() method might meet my needs if it weren't for the fact that Microsoft says it isn't supported in 64-bit versions of Windows.
If I generate my own sound dynamically I can get a more fancy than a simple beep. For example, I could make a waveform from a triangle wave that increases in frequency from 2 KHz to 4 KHz while decaying in volume. I don't need fancy 16-bit stereo, just 8-bit mono is fine. I don't need dynamic control over volume and pitch, just basically generate a soundfile in memory and play it without storing it.
Last time I needed to generate sounds was many years ago, on Apple II, on HP workstations, and on my old Amiga computer. Haven't needed to do it since then, and it seems that something simple that I describe has gotten a lot more complicated. I am having trouble believing that something so simple seems so hard. Most of the answers I see refer to NAudio or similar libraries, and that isn't an option for this project (aside from the fact that pulling in an entire library just to play a tone seems like a waste).
Based on one of the links in the answers I received, and some other pages I found about .wav header formats, here is my working code for a little class that generates an 8-bit "ding!" sound with a user-specified frequency and duration. It's basically a beep that decays linearly to zero in amplitude during the specified duration.
public class AlertDing {
private SoundPlayer player = null;
private BinaryWriter writer = null;
/// <summary>
/// Dynamically generate a "ding" sound and save it to a memory stream
/// </summary>
/// <param name="freq">Frequency in Hertz, e.g. 880</param>
/// <param name="tenthseconds">Duration in multiple of 1/10 second</param>
public AlertDing(double freq, uint tenthseconds) {
string header_GroupID = "RIFF"; // RIFF
uint header_FileLength = 0; // total file length minus 8, which is taken up by RIFF
string header_RiffType = "WAVE"; // always WAVE
string fmt_ChunkID = "fmt "; // Four bytes: "fmt "
uint fmt_ChunkSize = 16; // Length of header in bytes
ushort fmt_FormatTag = 1; // 1 for PCM
ushort fmt_Channels = 1; // Number of channels, 2=stereo
uint fmt_SamplesPerSec = 14000; // sample rate, e.g. CD=44100
ushort fmt_BitsPerSample = 8; // bits per sample
ushort fmt_BlockAlign =
(ushort)(fmt_Channels * (fmt_BitsPerSample / 8)); // sample frame size, in bytes
uint fmt_AvgBytesPerSec =
fmt_SamplesPerSec * fmt_BlockAlign; // for estimating RAM allocation
string data_ChunkID = "data"; // "data"
uint data_ChunkSize; // Length of header in bytes
byte [] data_ByteArray;
// Fill the data array with sample data
// Number of samples = sample rate * channels * bytes per sample * duration in seconds
uint numSamples = fmt_SamplesPerSec * fmt_Channels * tenthseconds / 10;
data_ByteArray = new byte[numSamples];
//int amplitude = 32760, offset=0; // for 16-bit audio
int amplitude = 127, offset = 128; // for 8-audio
double period = (2.0*Math.PI*freq) / (fmt_SamplesPerSec * fmt_Channels);
double amp;
for (uint i = 0; i < numSamples - 1; i += fmt_Channels) {
amp = amplitude * (double)(numSamples - i) / numSamples; // amplitude decay
// Fill with a waveform on each channel with amplitude decay
for (int channel = 0; channel < fmt_Channels; channel++) {
data_ByteArray[i+channel] = Convert.ToByte(amp * Math.Sin(i*period) + offset);
}
}
// Calculate file and data chunk size in bytes
data_ChunkSize = (uint)(data_ByteArray.Length * (fmt_BitsPerSample / 8));
header_FileLength = 4 + (8 + fmt_ChunkSize) + (8 + data_ChunkSize);
// write data to a MemoryStream with BinaryWriter
MemoryStream audioStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(audioStream);
// Write the header
writer.Write(header_GroupID.ToCharArray());
writer.Write(header_FileLength);
writer.Write(header_RiffType.ToCharArray());
// Write the format chunk
writer.Write(fmt_ChunkID.ToCharArray());
writer.Write(fmt_ChunkSize);
writer.Write(fmt_FormatTag);
writer.Write(fmt_Channels);
writer.Write(fmt_SamplesPerSec);
writer.Write(fmt_AvgBytesPerSec);
writer.Write(fmt_BlockAlign);
writer.Write(fmt_BitsPerSample);
// Write the data chunk
writer.Write(data_ChunkID.ToCharArray());
writer.Write(data_ChunkSize);
foreach (byte dataPoint in data_ByteArray) {
writer.Write(dataPoint);
}
player = new SoundPlayer(audioStream);
}
/// <summary>
/// Call this to clean up when program is done using this sound
/// </summary>
public void Dispose() {
if (writer != null) writer.Close();
if (player != null) player.Dispose();
writer = null;
player = null;
}
/// <summary>
/// Play "ding" sound
/// </summary>
public void Play() {
if (player != null) {
player.Stream.Seek(0, SeekOrigin.Begin); // rewind stream
player.Play();
}
}
}
Hopefully this should help others who are trying to produce a simple alert sound dynamically without needing a sound file.
The following article explains how *.wav file can be generated and played using SoundPlayer. Be aware that SoundPlayer can take a stream as an argument, so you can generate wav-file contents in a MemoryStream and avoid saving to a file.
http://blogs.msdn.com/b/dawate/archive/2009/06/24/intro-to-audio-programming-part-3-synthesizing-simple-wave-audio-using-c.aspx
I tried out the code-snipped from Anachronist (2012-10) - and it is working for me.
biggest hurdle for me:
get rid of the systematic "clicking-noise" at the end of "AlertDing" wav.
This is caused by a "soft-bug" in the code snipped:
for (uint i = 0; i < numSamples - 1; i += fmt_Channels)
needs to change to
for (uint i = 0; i < numSamples; i += fmt_Channels)
if not changed, a systematic "zero" will be generated at the end of each "play", causing a sharp clicking noise. (= amplitude jumps 0->min->0)
the original question implies "without clicking noise" of course :)

Use NAudio to get Ulaw samples for RTP

I've been looking over the NAudio examples trying to work out how I can get ulaw samples suitable for packaging up as an RTP payload. I'm attempting to generate the samples from an mp3 file using the code below. Not surprisingly, since I don't really have a clue what I'm doing with NAudio, when I transmit the samples across the network to a softphone all I get is static.
Can anyone provide any direction on how I should be getting 160 bytes (8Khz # 20ms) ULAW samples from an MP3 file using NAudio?
private void GetAudioSamples()
{
var pcmStream = WaveFormatConversionStream.CreatePcmStream(new Mp3FileReader("whitelight.mp3"));
byte[] buffer = new byte[2];
byte[] sampleBuffer = new byte[160];
int sampleIndex = 0;
int bytesRead = pcmStream.Read(buffer, 0, 2);
while (bytesRead > 0)
{
var ulawByte = MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(buffer, 0));
sampleBuffer[sampleIndex++] = ulawByte;
if (sampleIndex == 160)
{
m_rtpChannel.AddSample(sampleBuffer);
sampleBuffer = new byte[160];
sampleIndex = 0;
}
bytesRead = pcmStream.Read(buffer, 0, 2);
}
logger.Debug("Finished adding audio samples.");
}
Here's a few pointers. First of all, as long as you are using NAudio 1.5, no need for the additional WaveFormatConversionStream - Mp3FileReader's Read method returns PCM.
However, you will not be getting 8kHz out, so you need to resample it first. WaveFormatConversionStream can do this, although it uses the built-in Windows ACM sample rate conversion, which doesn't seem to filter the incoming audio well, so there could be aliasing artefacts.
Also, you usually read bigger blocks than just two bytes at a time as the MP3 decoder needs to decode frames one at a time (the resampler also will want to deal with bigger block sizes). I would try reading at least 20ms worth of bytes at a time.
Your use of BitConverter.ToInt16 is correct for getting the 16 bit sample value, but bear in mind that an MP3 is likely stereo, with left, right samples. Are you sure your phone expects stereo.
Finally, I recommend making a mu-law WAV file as a first step, using WaveFileWriter. Then you can easily listen to it in Windows Media Player and check that what you are sending to your softphone is what you intended.
Below is the way I eventually got it working. I do lose one of the channels from the mp3, and I guess there's some way to combine the channels as part of a conversion, but that doesn't matter for my situation.
The 160 byte buffer size gives me 20ms ulaw samples which work perfectly with the SIP softphone I'm testing with.
var pcmFormat = new WaveFormat(8000, 16, 1);
var ulawFormat = WaveFormat.CreateMuLawFormat(8000, 1);
using (WaveFormatConversionStream pcmStm = new WaveFormatConversionStream(pcmFormat, new Mp3FileReader("whitelight.mp3")))
{
using (WaveFormatConversionStream ulawStm = new WaveFormatConversionStream(ulawFormat, pcmStm))
{
byte[] buffer = new byte[160];
int bytesRead = ulawStm.Read(buffer, 0, 160);
while (bytesRead > 0)
{
byte[] sample = new byte[bytesRead];
Array.Copy(buffer, sample, bytesRead);
m_rtpChannel.AddSample(sample);
bytesRead = ulawStm.Read(buffer, 0, 160);
}
}
}

Converting an OLE Image Object from MS Access for use in .NET

I'm working on redeveloping an Access based system into c#.net, however when MS went from office 2003 to office 2007 they removed the picture editor within access - which meant that previously stored pictures would no longer display in the system. The guys at the company did a hack that basically saved the images with VBA using excel in the background (I can get more information if you need it) but basically it meant the access image controls could still be used (Object bound frames).
However, I now have the problem of trying to display these in .NET applications, and after countless days of trying different ways of manipulating the byte array I'm close to giving up. I have tried at least 8 different suggested solutions and each one ends with a 'Parameter not recognised' exception when doing Image.fromStream(). Below is the code which has got me the closest so far:
private void imageExtractTest()
{
LogOnDataSetTableAdapters.QueriesTableAdapter qa =
new LogOnDataSetTableAdapters.QueriesTableAdapter();
object docO = qa.GetLogonImage();
if (docO == null || !(docO is byte[]))
{
return;
}
byte[] doc = (byte[])docO;
MemoryStream ms = new MemoryStream();
ms.Write(doc, 0, doc.Length);
int firstByte;
int secondByte;
ms.Seek(0, SeekOrigin.Begin);
firstByte = ms.ReadByte();
secondByte = ms.ReadByte();
if (firstByte != 0x15 && secondByte != 0x1C)
{
//ErrorResponse("Stored object is not an Access File.");
return;
}
int fileTypeLoc = 20; // begin of the file type
short offset; // end of the file type
byte[] buffer = new byte[2];
ms.Read(buffer, 0, 2);
offset = BitConverter.ToInt16(buffer, 0);
long seekTotal = 0;
seekTotal += offset;
string docType = String.Empty;
for (int i = fileTypeLoc; i < offset; i++)
{
docType += (char)doc[i];
}
//if I query docType now I get 'Picture\0\0'
// magic eight bytes 01 05 00 00 03 00 00 00
ms.Seek(seekTotal, SeekOrigin.Begin);
buffer = new byte[8];
ms.Read(buffer, 0, 8);
seekTotal += 8;
// Second offset to move to
buffer = new byte[4];
ms.Read(buffer, 0, 4);
seekTotal += 4;
long offset2 = BitConverter.ToInt32(buffer, 0);
seekTotal += offset2;
ms.Seek(seekTotal, SeekOrigin.Begin);
// eight empty bytes
buffer = new byte[8];
ms.Read(buffer, 0, 8);
seekTotal += 8;
// next n bytes are the length of the file
buffer = new byte[4];
ms.Read(buffer, 0, 4);
seekTotal += 4;
long fileByteLength = BitConverter.ToInt32(buffer, 0);
// next N bytes are the file
byte[] data = new byte[fileByteLength];
// store file bytes in data buffer
ms.Read(data, 0, Convert.ToInt32(fileByteLength));
MemoryStream imageStream = new MemoryStream(data);
Image test = Image.FromStream(imageStream);
}
This code was adapted from here, I didn't need the various doctypes identification as I'm only dealing with images, however the image type could be any number of things - jpg, bmp, gif, png etc.
I've also tried saving the outputted byte array but I've had no luck viewing that either. But when I point access to the database and get it to view it, everything is fine. Also the .NET Crystal Report designer is able to get these images some how - so they must be in there somewhere...
Has anyone got any ideas?
Marlon
Trying to retrieve an MS-access OLE image field from .NET is way more headache than it is worth. There is some good discussion and info about this topic in this post.
Ultimately, your best, and easiest, solution to be successful with this is to use your working viewing method to save these images as separate files, then import those files into the database as BLOB fields, rather than image fields. Then you can easily read them into .NET.
It is not C# code but here is an Delphi example of a way to solve this problem.
It uses the IOLEObject to draw whatever is stored instead of trying to read out the raw data.
Steps:
Read the Access Header in front of the OLE Object
Read out the OLE1 stream
Convert the OLE1 stream to an OLE2 IStorage Object
Use OLELoad to "run" the OLE Ojbect.
Call OLEDraw to draw out the image to a canvas of your choice.
In my case the following function worked. The data are stored by a VB6 application.
public static byte[] ConvertOleBytesToRawBytes(byte[] oleBytes)
{
// The default encoding is in my case - Western European (Windows), Code Page 1252
return Encoding.Convert(Encoding.Unicode, Encoding.Default, (byte[])oleBytes);
}
Try this KB http://support.microsoft.com/kb/317701 article from microsoft. It contains information on how to access the image blob from access and display that in winforms application.
I needed to do the exact same thing for some 1600 object of different extension types. In my case it was a legacy database that had literally been used for decades. Over the years many different types of files had been added via an OLE Object frame. Some of the items that looked like "Images" turned out to be Word documents with an embedded image... no telling what other file types were in there? All I know is that I researched and tried different extraction methods for more than a week. Even all of Steven Leban's extraction tools like OLEtoDisk, A2KExportOLEtoJPEG, and SaveOLEtoBitmap. Each of which would extract some images... but their was no one size fits all... it was a mess!
In the end I ended up performing an automated screen shot of each image via VBA using the method below. While this may not have been the most ideal it worked for every file type. However the screen shot method captures a screen shot of the full screen. Once I had all of them extracted I then had to use PhotoShop to do another automated process to batch crop all of the photos. Not ideal but it worked!
Private Sub CaptureAllImages()
On Error Resume Next
Me.RecordsetClone.MoveFirst
Do While Not Me.RecordsetClone.EOF
Me.Bookmark = Me.RecordsetClone.Bookmark
Call Pause(2)
Call SaveClip2Bit("C:\Users\agriggs\Desktop\Parts Images\MasterPart_" & Me.MasterPartNumber & ".bmp")
Me.RecordsetClone.MoveNext
Loop
End Sub
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Error_GoTo
Dim PauseTime As Variant
Dim start As Variant
Dim Elapsed As Variant
PauseTime = NumberOfSeconds
start = Timer
Elapsed = 0
Do While Timer < start + PauseTime
Elapsed = Elapsed + 1
If Timer = 0 Then
' Crossing midnight
PauseTime = PauseTime - Elapsed
start = 0
Elapsed = 0
End If
DoEvents
Loop
Exit_GoTo:
On Error GoTo 0
Exit Function
Error_GoTo:
Debug.Print Err.Number, Err.Description, Erl
GoTo Exit_GoTo
End Function
I tried a couple different screen shot modules but found that the SaveClip2Bit worked the best. Lastly I added a common Pause function to insure the image got saved to disk before moving on to the next. As you can imagine 1600 images took a long time to extract but I can now but this project to rest!

Categories