What I'm trying to make: Receive data from microphone (IWaveIn) => lower the amplitude of the sound(lower the volume)(problem) => play it to the speakers(IWaveProvider)
The problem is: whenever I try to multiply sample by x!=1.0f gives me very noisy feedback.
I think it could be a format of bytes but I do not know how to check it. Any help/suggestion will be appreciated.
Count = 17640; Offset =0;
public int Read(byte[] buffer, int offset, int count)
{
int read = bufferedWaveProvider.Read(buffer, offset, count);
/*
waveIn.WaveFormat.Channels; //2
waveIn.WaveFormat.BlockAlign;//4
waveIn.WaveFormat.BitsPerSample;//16
waveIn.WaveFormat.SampleRate;//44100
*/
for (int i = 0; i < read / 4; i++)
{
int firstByte = i * 4;
float sample = BitConverter.ToSingle(buffer, firstByte);
sample = sample * 1.0f;
byte[] bytes = BitConverter.GetBytes(sample);
buffer[firstByte + 0] = bytes[0];
buffer[firstByte + 1] = bytes[1];
buffer[firstByte + 2] = bytes[2];
buffer[firstByte + 3] = bytes[3];
}
return read;
}
private void OnDataAvailable(object sender, WaveInEventArgs e)
{
bufferedWaveProvider.AddSamples(e.Buffer, 0, e.BytesRecorded);
}
You're receiving audio in the format specified in WaveIn.WaveFormat. Your comment shows 16 bits per sample, which means you are receiving the audio as signed 16 bit samples. So you could use BitConverter.ToInt16
But there are easier ways of accomplishing this. If you call ToSampleProvider() on your BufferedWaveProvider then you can pass that into a VolumeSampleProvider which will let you adjust the volume directly without needing to unpack samples yourself.
Related
I'm developping a simple program that analyses frequencies of audio files.
Using an fft length of 8192, samplerate of 44100, if I use as input a constant frequency wav file - say 65Hz, 200Hz or 300Hz - the output is a constant graph at that value.
If I use a recording of someone speaking, the frequencies has peaks as high as 4000Hz, with an average at 450+ish on a 90 seconds file.
At first I thought it was because of the recording being stereo sound, but converting it to mono with the exact same bitrate as the test files doesn't change much. (average goes down from 492 to 456 but that's still way too high)
Has anyone got an idea as to what could cause this ?
I think I shouldn't find the highest value but perhaps take either an average or a median value ?
EDIT : using the average of the magnitudes per 8192 bytes buffer and getting the index that's closest to that magnitude messes everything up.
This is the code for the handler of the event the Sample Aggregator fires when it has calculated fft for current buffer
void FftCalculated(object sender, FftEventArgs e)
{
int length = e.Result.Length;
float[] magnitudes = new float[length];
for (int i = 0; i < length / 2; i++)
{
float real = e.Result[i].X;
float imaginary = e.Result[i].Y;
magnitudes[i] = (float)(10 * Math.Log10(Math.Sqrt((real * real) + (imaginary * imaginary))));
}
float max_mag = float.MinValue;
float max_index = -1;
for (int i = 0; i < length / 2; i++)
if (magnitudes[i] > max_mag)
{
max_mag = magnitudes[i];
max_index = i;
}
var currentFrequency = max_index * SAMPLERATE / 8192;
Console.WriteLine("frequency be " + currentFrequency);
}
ADDITION : this is the code that reads and sends the file to the analysing part
using (var rdr = new WaveFileReader(audioFilePath))
{
var newFormat = new WaveFormat(Convert.ToInt32(SAMPLERATE/*44100*/), 16, 1);
byte[] buffer = new byte[8192];
var audioData = new AudioData(); //custom class for project
using (var conversionStream = new WaveFormatConversionStream(newFormat, rdr))
{
// Used to send audio in realtime, it's a timestamps issue for the graphs
// I'm working on fixing this, but it has lower priority so disregard it :p
TimeSpan audioDuration = conversionStream.TotalTime;
long audioLength = conversionStream.Length;
int waitTime = (int)(audioDuration.TotalMilliseconds / audioLength * 8192);
while (conversionStream.Read(buffer, 0, buffer.Length) != 0)
{
audioData.AudioDataBase64 = Utils.Base64Encode(buffer);
Thread.Sleep(waitTime);
SendMessage("AudioData", Utils.StringToAscii(AudioData.GetJSON(audioData)));
}
Console.WriteLine("Reached End of File");
}
}
This is the code that receives the audio data
{
var audioData = new AudioData();
audioData =
AudioData.GetStateFromJSON(Utils.AsciiToString(receivedMessage));
QueueAudio(Utils.Base64Decode(audioData.AudioDataBase64)));
}
followed by
var waveFormat = new WaveFormat(Convert.ToInt32(SAMPLERATE/*44100*/), 16, 1);
_bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
_bufferedWaveProvider.BufferDuration = new TimeSpan(0, 2, 0);
{
void QueueAudio(byte[] data)
{
_bufferedWaveProvider.AddSamples(data, 0, data.Length);
if (_bufferedWaveProvider.BufferedBytes >= fftLength)
{
byte[] buffer = new byte[_bufferedWaveProvider.BufferedBytes];
_bufferedWaveProvider.Read(buffer, 0, _bufferedWaveProvider.BufferedBytes);
for (int index = 0; index < buffer.Length; index += 2)
{
short sample = (short)((buffer[index] | buffer[index + 1] << 8));
float sample32 = (sample) / 32767f;
sampleAggregator.Add(sample32);
}
}
}
}
And then the SampleAggregator fires the event above when it's done with the fft.
I'm using NAudio to generate a WAV file. The Wav file contains environment noise (detected and recorded via mic).
I need to process this file to show average loudness (dB) against different frequency bands.
I read much about 1:3 Octave band analysis where the frequency windows are 31, 62, 125, 250, 500 Hz and so on. And we can have an average loudness against each window.
This is exactly what I want to do but HOW to achieve this seems confusing.
What I have done so far is (using NAudio tutorial) to read a WAV file and process it. Here is the code:
private void RenderFile()
{
using (WaveFileReader reader = new WaveFileReader(this.voiceRecorderState.ActiveFile))
{
this.samplesPerSecond = reader.WaveFormat.SampleRate;
SampleAggregator.NotificationCount = reader.WaveFormat.SampleRate/10;
//Sample rate is 44100
byte[] buffer = new byte[1024];
WaveBuffer waveBuffer = new WaveBuffer(buffer);
waveBuffer.ByteBufferCount = buffer.Length;
int bytesRead;
do
{
bytesRead = reader.Read(waveBuffer, 0, buffer.Length);
int samples = bytesRead / 2;
double sum = 0;
for (int sample = 0; sample < samples; sample++)
{
if (bytesRead > 0)
{
sampleAggregator.Add(waveBuffer.ShortBuffer[sample] / 32768f);
double sample1 = waveBuffer.ShortBuffer[sample] / 32768.0;
sum += (sample1 * sample1);
}
}
double rms = Math.Sqrt(sum / (SampleAggregator.NotificationCount));
double decibel = (double)(20 * Math.Log10(rms));
if (calculatedBCount == 0)
{
dBList.Add(decibel);
// System.Diagnostics.Debug.WriteLine(decibel.ToString() + " in dB");
}
} while (bytesRead > 0);
int totalSamples = (int)reader.Length / 2;
TotalWaveFormSamples = totalSamples / sampleAggregator.NotificationCount;
calculatedBCount++;
SelectAll();
//System.Diagnostics.Debug.WriteLine("Average Noise: " + avg.ToString() + " dB");
}
audioPlayer.LoadFile(this.voiceRecorderState.ActiveFile);
}
public int Read(byte[] buffer, int offset, int count)
{
if (waveBuffer == null || waveBuffer.MaxSize < count)
{
waveBuffer = new WaveBuffer(count);
}
int bytesRead = source.Read(waveBuffer, 0, count);
if (bytesRead > 0) bytesRead = count;
int frames = bytesRead / sizeof(float); // MRH: was count
float pitch = pitchDetector.DetectPitch(waveBuffer.FloatBuffer, frames);
PitchList.Add(pitch);
return frames * 4;
}
Using a 5 second WAV file, from above two methods, I get a list of Pitches and Decibels
The decibels list contains 484 values like:
-56.19945639
-55.13139952
-55.06947441
-56.70789076
-57.24140093
-55.98546603
-55.67407176
-55.53060998
-55.98480268
-54.85796943
-57.00735818
-55.64980974
-57.07235475
PitchList contains 62 values which include:
75.36621
247.631836
129.199219
75.36621
96.8994141
96.8994141
86.13281
75.36621
129.199219
107.666016
How can I use these results for identifying what is average loudness against 31Hz, 62Hz, 125Hz, 250Hz and so on.
Am I doing some wrong or everything wrong, maybe?
Please correct me if I am wrong but...I'm afraid You cannot convert HZ to DB because there is no relation between them.
Hz is a measure of frequency and Db is a measure of amplitude, sort like kilos to meters.
I successfully captured sound from Wasapi using the following code:
IWaveIn waveIn = new WasapiLoopbackCapture();
waveIn.DataAvailable += OnDataReceivedFromWaveOut;
What I need to do now, is to resample the in-memory data to pcm with a sample rate of 8000 and 16 bits per sample mono.
I can't use ACMStream to resample the example, because the recorded audio is 32 bits per second.
I have tried this code to convert bytes from 32 to 16 bits, but all I get every time is just blank audio.
byte[] newArray16Bit = new byte[e.BytesRecorded / 2];
short two;
float value;
for (int i = 0, j = 0; i < e.BytesRecorded; i += 4, j += 2)
{
value = (BitConverter.ToSingle(e.Buffer, i));
two = (short)(value * short.MaxValue);
newArray16Bit[j] = (byte)(two & 0xFF);
newArray16Bit[j + 1] = (byte)((two >> 8) & 0xFF);
}
source = newArray16Bit;
I use this routine to resample on the fly from WASAPI IeeeFloat to the format I need in my app, which is 16kHz, 16 bit, 1 channel. My formats are fixed, so I'm hardcoding the conversions I need, but it can be adapted as needed.
private void ResampleWasapi(object sender, WaveInEventArgs e)
{
//the result of downsampling
var resampled = new byte[e.BytesRecorded / 12];
var indexResampled = 0;
//a variable to flag the mod 3-ness of the current sample
var arity = -1;
var runningSamples = new short[3];
for(var offset = 0; offset < e.BytesRecorded; offset += 8)
{
var float1 = BitConverter.ToSingle(e.Buffer, offset);
var float2 = BitConverter.ToSingle(e.Buffer, offset + 4);
//simple average to collapse 2 channels into 1
float mono = (float)((double)float1 + (double)float2) / 2;
//convert (-1, 1) range int to short
short sixteenbit = (short)(mono * 32767);
//the input is 48000Hz and the output is 16000Hz, so we need 1/3rd of the data points
//so save up 3 running samples and then mix and write to the file
arity = (arity + 1) % 3;
//record the value
runningSamples[arity] = sixteenbit;
//if we've hit the third one
if (arity == 2)
{
//simple average of the 3 and put in the 0th position
runningSamples[0] = (short)(((int)runningSamples[0] + (int)runningSamples[1] + (int)runningSamples[2]) / 3);
//copy that short (2 bytes) into the result array at the current location
Buffer.BlockCopy(runningSamples, 0, resampled, indexResampled, 2);
//for next pass
indexResampled += 2;
}
}
//and tell listeners that we've got data
RaiseDataEvent(resampled, resampled.Length);
}
I have a circuit that sends me two different data from sensors. Data is coming as packets. First data is '$' to separate one packet to another. After '$' it sends 16 bytes microphone data and 1 byte pulse sensor data. I have an array to store incoming data and after plotting the data in each 20 ms, i start to write new bytes from zero index of array. I need to plot these data to different graphs using ZedGraph. However i could not separate those data correctly. Sometimes one or more data of audio are shown in other graph. Here is my code:
for (int i = 0; i < 4; i++)
{
if (data[i * 18] == Convert.ToByte('$'))
{
for (int x = ((i * 18) + 1); x < ((i * 18) + 17); x++)
{
listAuido.Add(time, data[x]);
}
for (int a = ((i * 18) + 17); a < ((i * 18) + 18); a++)
{
listPulse.Add(time, data[a]);
}
}
}
How can i solve this issue?
Circuit settings: BaudRate: 38400, Frequency: 200hz, CommunicationType: RS232.
Port Settings:ReadTimeOut=5 WrtieTimeOut=5;
While reading data i am using codes below. Read_Data1 refers data[] the code above. I have a counter and after plotting the data its value equals zero and i prevent my buffer index out of range exception
byte[] Read_Data1 = new byte[1000];
private void myPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (!myPort.IsOpen)
return;
if (myPort.BytesToRead > 0)
{
byte[] buffer = new byte[myPort.BytesToRead];
myPort.Read(buffer, 0, buffer.Length);
DataConversion.bytetobyte(Read_Data1, buffer, buffer.Length, count);
count += buffer.Length;
DataRecord.SaveBytesToFile(buffer, save.FileName);
}
}
public static void bytetobyte(byte[] Storage, byte[] databyte, int datacount, int count)
{
int abc;
for (abc = 0; abc < datacount; abc++)
{
Storage[abc + count] = databyte[abc];
}
}
Without seeing the data stream its hard to understand exactly what's going on but when you multiply by 18 the offset will be different as i increases. in order for that to work you need to be sure that exactly 4 packets are actually in the buffer at this time or things might get weird. Having worked a far bit with rs232 connected measurement hardware I often find that their software is not all that consistent, so I'd be careful about assuming the data is there :)
Look into how and when you're reading data into the data buffer, are you sure it contains fresh data everytime you call your code?
The loop looks correct but its a little difficult to read, I'd rewrite it as
for (int i = 0; i < 4; i++)
{
if (data[i * 18] == Convert.ToByte('$'))
{
for (int x = 0; x < 16; x++)
{
listAuido.Add(time, data[(i * 18) +1 + x]);
}
for (int a = 0; a < 1; a++) // this doesn't really need to be a loop
{
listPulse.Add(time, data[ ((i * 18) + 17)+ a]);
}
}
I looked at the code you added but its not immediately clear how the first bit of code is called by the second. I still think that there is something with the buffer handling that is causing your issue, but you could possibly eliminate that but using the buffer built into the serial port and just one byte at the time:
while(true){ //you could do this on a separate thread or on a timer
if(port.ReadByte() == Convert.ToByte('$')){
for (int x = 0; x < 16; x++)
listAuido.Add(time, port.ReadByte());
listPulse.Add(time, port.ReadByte());
}
}
I've been trying to convert a 32 bit stereo wav to 16 bit mono wav. I use naudio to capture the sound I and thought that using just the two of four more significant bytes will work.
Here is the DataAvailable implementation:
void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
byte[] newArray = new byte[e.BytesRecorded / 2];
short two;
for (int i = 0, j = 0; i < e.BytesRecorded; i = i + 4, j = j + 2)
{
two = (short)BitConverter.ToInt16(e.Buffer, i + 2);
newArray[j] = (byte)(two & 0xFF);
newArray[j + 1] = (byte)((two >> 8) & 0xFF);
}
//do something with the new array:
}
Any help would be greatly appreciated!
I finally found the solution. I just had to multiply the converted value by 32767 and cast it to short:
void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
byte[] newArray16Bit = new byte[e.BytesRecorded / 2];
short two;
float value;
for (int i = 0, j = 0; i < e.BytesRecorded; i += 4, j += 2)
{
value = (BitConverter.ToSingle(e.Buffer, i));
two = (short)(value * short.MaxValue);
newArray16Bit[j] = (byte)(two & 0xFF);
newArray16Bit[j + 1] = (byte)((two >> 8) & 0xFF);
}
}
A 32-bit sample can be as high as 4,294,967,295 and a 16-bit sample can be as high as 65,536. So you'll have to scale down the 32-bit sample to fit into the 16-bit sample's range. More or less, you're doing something like this...
SixteenBitSample = ( ThirtyTwoBitSample / 4294967295 ) * 65536;
EDIT:
For the stereo to mono portion, if the two channels have the same data, just dump one of them, otherwise, add the wave forms together, and if they fall outside of the sample range (65,536) then you'll have to scale them down similarly to the above equation.
Hope that helps.