NAudio - wave split and combine wave in realtime - c#

I am working with a multi input soundcard and I want to achieve live mixing of multiple inputs. All the inputs are stereo, so I need to split them in first place, mix a selection of channel and provide them as mono stream.
The goal would be something like this mix Channel1[left] + Channel3[right] + Channel4[right] -> mono stream.
I have already implemented a process chain like this:
1) WaveIn -> create BufferedWaveProvider for each channel -> add Samples (just the ones for current channel) to each BufferedWaveProvider by using wavein.DataAvailable += { buffwavprovider[channel].AddSamples(...)...
This gives me a nice list of multiple BufferdWaveProvider. The splitting audio part here is implemented correctly.
2) Select multiple BufferedWaveProviders and give them to MixingWaveProvider32. Then create a WaveStream (using WaveMixerStream32 and IWaveProvider).
3) A MultiChannelToMonoStream takes that WaveStream and generates a mixdown. This also works.
But result is, that audio is chopped. Like some trouble with the buffer....
Is this the correct way to handle this problem, or is there a way better solution around?
edit - code added:
public class AudioSplitter
{
public List<NamedBufferedWaveProvider> WaveProviders { private set; get; }
public string Name { private set; get; }
private WaveIn _wavIn;
private int bytes_per_sample = 4;
/// <summary>
/// Splits up one WaveIn into one BufferedWaveProvider for each channel
/// </summary>
/// <param name="wavein"></param>
/// <returns></returns>
public AudioSplitter(WaveIn wavein, string name)
{
if (wavein.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat)
throw new Exception("Format must be IEEE float");
WaveProviders = new List<NamedBufferedWaveProvider>(wavein.WaveFormat.Channels);
Name = name;
_wavIn = wavein;
_wavIn.StartRecording();
var outFormat = NAudio.Wave.WaveFormat.CreateIeeeFloatWaveFormat(wavein.WaveFormat.SampleRate, 1);
for (int i = 0; i < wavein.WaveFormat.Channels; i++)
{
WaveProviders.Add(new NamedBufferedWaveProvider(outFormat) { DiscardOnBufferOverflow = true, Name = Name + "_" + i });
}
bytes_per_sample = _wavIn.WaveFormat.BitsPerSample / 8;
wavein.DataAvailable += Wavein_DataAvailable;
}
/// <summary>
/// add samples for each channel to bufferedwaveprovider
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Wavein_DataAvailable(object sender, WaveInEventArgs e)
{
int channel = 0;
byte[] buffer = e.Buffer;
for (int i = 0; i < e.BytesRecorded - bytes_per_sample; i = i + bytes_per_sample)
{
byte[] channel_buffer = new byte[bytes_per_sample];
for (int j = 0; j < bytes_per_sample; j++)
{
channel_buffer[j] = buffer[i + j];
}
WaveProviders[channel].AddSamples(channel_buffer, 0, channel_buffer.Length);
channel++;
if (channel >= _wavIn.WaveFormat.Channels)
channel = 0;
}
}
}
Using the Audiosplitter for each channel gives a list of buffered wave provider (mono 32bit float).
var mix = new MixingWaveProvider32(_waveProviders);
var wps = new WaveProviderToWaveStream(mix);
MultiChannelToMonoStream mms = new MultiChannelToMonoStream(wps);
new Thread(() =>
{
byte[] buffer = new byte[4096];
while (mms.Read(buffer, 0, buffer.Length) > 0 && isrunning)
{
using (FileStream fs = new FileStream("C:\\temp\\audio\\mono_32.wav", FileMode.Append, FileAccess.Write))
{
fs.Write(buffer, 0, buffer.Length);
}
}
}).Start();

there is some space left for optimization, but basically this gets the job done:
private void Wavein_DataAvailable(object sender, WaveInEventArgs e)
{
int channel = 0;
byte[] buffer = e.Buffer;
List<List<byte>> channelbuffers = new List<List<byte>>();
for (int c = 0; c < _wavIn.WaveFormat.Channels; c++)
{
channelbuffers.Add(new List<byte>());
}
for (int i = 0; i < e.BytesRecorded; i++)
{
var byteList = channelbuffers[channel];
byteList.Add(buffer[i]);
if (i % bytes_per_sample == bytes_per_sample - 1)
channel++;
if (channel >= _wavIn.WaveFormat.Channels)
channel = 0;
}
for (int j = 0; j < channelbuffers.Count; j++)
{
WaveProviders[j].AddSamples(channelbuffers[j].ToArray(), 0, channelbuffers[j].Count());
}
}
We need to provide a WaveProvider (WaveProviders[j]) for each channel.

Related

Memory leakage in C# while using very big strings

Inspecting memory leakage on one of my apps, I've found that the next code "behaves strange".
public String DoTest()
{
String fileContent = "";
String fileName = "";
String[] filesNames = System.IO.Directory.GetFiles(logDir);
List<String> contents = new List<string>();
for (int i = 0; i < filesNames.Length; i++)
{
fileName = filesNames[i];
if (fileName.ToLower().Contains("aud"))
{
contents.Add(System.IO.File.ReadAllText(fileName));
}
}
fileContent = String.Join("", contents);
return fileContent;
}
Before running this piece of code, the memory used by object was approximatly 1.4 Mb. Once this method called, it used 70MB. waiting some minutes, nothing changed (the original object was being released a long time ago).
calling to
GC.Collect();
GC.WaitForFullGCComplete();
decreased memory to 21MB (Yet, far much more than the 1.4MB at the beginning).
Tested with console app (infinity loop) and winform app. Happens even on direct call (no need to create more objects).
Edit: full code (console app) to show the problem
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace memory_tester
{
/// <summary>
/// Class to show loosing of memory
/// </summary>
class memory_leacker
{
// path to folder with 250 text files, total of 80MB of text
const String logDir = #"d:\http_server_test\http_server_test\bin\Debug\logs\";
/// <summary>
/// Collecting all text from files in folder logDir and returns it.
/// </summary>
/// <returns></returns>
public String DoTest()
{
String fileContent = "";
String fileName = "";
String[] filesNames = System.IO.Directory.GetFiles(logDir);
List<String> contents = new List<string>();
for (int i = 0; i < filesNames.Length; i++)
{
fileName = filesNames[i];
if (fileName.ToLower().Contains("aud"))
{
//using string builder directly into fileContent shows same results.
contents.Add(System.IO.File.ReadAllText(fileName));
}
}
fileContent = String.Join("", contents);
return fileContent;
}
/// <summary>
/// demo call to see that no memory leaks here
/// </summary>
/// <returns></returns>
public String DoTestDemo()
{
return "";
}
}
class Program
{
/// <summary>
/// Get current proc's private memory
/// </summary>
/// <returns></returns>
public static long GetUsedMemory()
{
String procName = System.AppDomain.CurrentDomain.FriendlyName;
long mem = Process.GetCurrentProcess().PrivateMemorySize64 ;
return mem;
}
static void Main(string[] args)
{
const long waitTime = 10; //was 240
memory_leacker mleaker = new memory_leacker();
for (int i=0; i< waitTime; i++)
{
Console.Write($"Memory before {GetUsedMemory()} Please wait {i}\r");
Thread.Sleep(1000);
}
Console.Write("\r\n");
mleaker.DoTestDemo();
for (int i = 0; i < waitTime; i++)
{
Console.Write($"Memory after demo call {GetUsedMemory()} Please wait {i}\r");
Thread.Sleep(1000);
}
Console.Write("\r\n");
mleaker.DoTest();
for (int i = 0; i < waitTime; i++)
{
Console.Write($"Memory after real call {GetUsedMemory()} Please wait {i}\r");
Thread.Sleep(1000);
}
Console.Write("\r\n");
mleaker = null;
for (int i = 0; i < waitTime; i++)
{
Console.Write($"Memory after release objectg {GetUsedMemory()} Please wait {i}\r");
Thread.Sleep(1000);
}
Console.Write("\r\n");
GC.Collect();
GC.WaitForFullGCComplete();
for (int i = 0; i < waitTime; i++)
{
Console.Write($"Memory after GC {GetUsedMemory()} Please wait {i}\r");
Thread.Sleep(1000);
}
Console.Write("\r\n...pause...");
Console.ReadKey();
}
}
}
I believe that if you use stringbuilder on fileContent instead string, you can improve your performance and usage of memory.
public String DoTest()
{
var fileContent = new StringBuilder();
String fileName = "";
String[] filesNames = System.IO.Directory.GetFiles(logDir);
for (int i = 0; i < filesNames.Length; i++)
{
fileName = filesNames[i];
if (fileName.ToLower().Contains("aud"))
{
fileContent.Append(System.IO.File.ReadAllText(fileName));
}
}
return fileContent;
}
I refactored version of your code below, here I have removed the need for the list of strings named 'contents' in your original question.
public String DoTest()
{
string fileContent = "";
IEnumerable<string> filesNames = System.IO.Directory.GetFiles(logDir).Where(x => x.ToLower().Contains("aud"));
foreach (var fileName in filesNames)
{
fileContent = string.Join("", System.IO.File.ReadAllText(fileName));
}
return fileContent;
}

Is there an equivalent to mmap.mmap.rfind in C#?

While looking at memory-mapped files in C#, there was some difficulty in identifying how to search a file quickly forward and in reverse. My goal is to rewrite the following function in the language, but nothing could be found like the find and rfind methods used below. Is there a way in C# to quickly search a memory-mapped file using a particular substring?
#! /usr/bin/env python3
import mmap
import pathlib
# noinspection PyUnboundLocalVariable
def drop_last_line(path):
with path.open('r+b') as file:
with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as search:
for next_line in b'\r\n', b'\r', b'\n':
if search.find(next_line) >= 0:
break
else:
raise ValueError('cannot find any line delimiters')
end_1st = search.rfind(next_line)
end_2nd = search.rfind(next_line, 0, end_1st - 1)
file.truncate(0 if end_2nd < 0 else end_2nd + len(next_line))
Is there a way in C# to quickly search a memory-mapped file using a particular substring?
Do you know of any way to memory-map an entire file in C# and then treat it as a byte array?
Yes, it's quite easy to map an entire file into a view then to read it into a single byte array as the following code shows:
static void Main(string[] args)
{
var sourceFile= new FileInfo(#"C:\Users\Micky\Downloads\20180112.zip");
int length = (int) sourceFile.Length; // length of target file
// Create the memory-mapped file.
using (var mmf = MemoryMappedFile.CreateFromFile(sourceFile.FullName,
FileMode.Open,
"ImgA"))
{
var buffer = new byte[length]; // allocate a buffer with the same size as the file
using (var accessor = mmf.CreateViewAccessor())
{
var read=accessor.ReadArray(0, buffer, 0, length); // read the whole thing
}
// let's try searching for a known byte sequence. Change this to suit your file
var target = new byte[] {71, 213, 62, 204,231};
var foundAt = IndexOf(buffer, target);
}
}
I couldn't seem to find any byte searching method in Marshal or Array but you can use this search algorithm courtesy of Social MSDN as a start:
private static int IndexOf2(byte[] input, byte[] pattern)
{
byte firstByte = pattern[0];
int index = -1;
if ((index = Array.IndexOf(input, firstByte)) >= 0)
{
for (int i = 0; i < pattern.Length; i++)
{
if (index + i >= input.Length ||
pattern[i] != input[index + i]) return -1;
}
}
return index;
}
...or even this more verbose example (also courtesy Social MSDN, same link)
public static int IndexOf(byte[] arrayToSearchThrough, byte[] patternToFind)
{
if (patternToFind.Length > arrayToSearchThrough.Length)
return -1;
for (int i = 0; i < arrayToSearchThrough.Length - patternToFind.Length; i++)
{
bool found = true;
for (int j = 0; j < patternToFind.Length; j++)
{
if (arrayToSearchThrough[i + j] != patternToFind[j])
{
found = false;
break;
}
}
if (found)
{
return i;
}
}
return -1;
}

How to use BiQuadFilter of NAudio?

I use NAudio for recording sound from the microphone and save it in a file. I use for this:
public WaveFileWriter m_WaveFile = null;
m_WaveFile = new WaveFileWriter(strFile, m_WaveSource.WaveFormat);
void DataAvailable(object sender, WaveInEventArgs e)
{
if (m_WaveFile != null)
{
m_WaveFile.Write(e.Buffer, 0, e.BytesRecorded);
}
}
Now I would like to apply a highpassfilter and a lowpassfilter to the recorded sound. So far I found that BiQuadFilters would do this for me but so far I don't understand how to use that.
The examples I found look all like this:
var r = BiQuadFilter.LowPassFilter(44100, 1500, 1);
var reader = new WaveFileReader(File.OpenRead(strFile));
var filter = new MyWaveProvider(reader, r); // reader is the source for filter
var waveOut = new WaveOut();
waveOut.Init(filter); // filter is the source for waveOut
waveOut.Play();
If I understand this correctly then the Filter is applied to the class that is playing the sound. But I don't want to play the sound, I want the high and log pass filter applied to the file and save the result in a file. How can I do that?
edit:
This is MyWaveProvider class:
class MyWaveProvider : ISampleProvider
{
private ISampleProvider sourceProvider;
private float cutOffFreq;
private int channels;
private BiQuadFilter[] filters;
public MyWaveProvider (ISampleProvider sourceProvider, int cutOffFreq)
{
this.sourceProvider = sourceProvider;
this.cutOffFreq = cutOffFreq;
channels = sourceProvider.WaveFormat.Channels;
filters = new BiQuadFilter[channels];
CreateFilters();
}
private void CreateFilters()
{
for (int n = 0; n < channels; n++)
if (filters[n] == null)
filters[n] = BiQuadFilter.LowPassFilter(44100, cutOffFreq, 1);
else
filters[n].SetLowPassFilter(44100, cutOffFreq, 1);
}
public WaveFormat WaveFormat { get { return sourceProvider.WaveFormat; } }
public int Read(float[] buffer, int offset, int count)
{
int samplesRead = sourceProvider.Read(buffer, offset, count);
for (int i = 0; i < samplesRead; i++)
buffer[offset + i] = filters[(i % channels)].Transform(buffer[offset + i]);
return samplesRead;
}
}
The following code should satisfy what you are trying to do:
using (var reader = new WaveFileReader(File.OpenRead(strFile)))
{
var r = BiQuadFilter.LowPassFilter(44100, 1500, 1);
// reader is the source for filter
using (var filter = new MyWaveProvider(reader, r))
{
WaveFileWriter.CreateWaveFile("filteroutput.wav", filter);
}
}
There's an example of using the BiQuadFilter to make a multi-band equalizer in the NAudio WPF demo code. You can see the Equalizer code here

For-loop has Unexpected Side-Effect

So I have been writing a small byte cipher in C#, and everything was going well until I tried to do some for loops to test runtime performance. This is where things started to get really weird. Allow me to show you, instead of trying to explain it:
First off, here is the working code (for loops commented out):
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DreamforceFramework.Framework.Cryptography;
namespace TestingApp
{
static class Program
{
static void Main(string[] args)
{
string myData = "This is a test.";
byte[] myDataEncrypted;
string myDecryptedData = null;
Stopwatch watch = new Stopwatch();
Console.WriteLine("Warming up for Encryption...");
//for (int i = 0; i < 20; i++)
//{
// // Warm up the algorithm for a proper speed benchmark.
// myDataEncrypted = DreamforceByteCipher.Encrypt(myData, "Dreamforce");
//}
watch.Start();
myDataEncrypted = DreamforceByteCipher.Encrypt(myData, "Dreamforce");
watch.Stop();
Console.WriteLine("Encryption Time: " + watch.Elapsed);
Console.WriteLine("Warming up for Decryption...");
//for (int i = 0; i < 20; i++)
//{
// // Warm up the algorithm for a proper speed benchmark.
// myDecryptedData = DreamforceByteCipher.Decrypt(myDataEncrypted, "Dreamforce");
//}
watch.Reset();
watch.Start();
myDecryptedData = DreamforceByteCipher.Decrypt(myDataEncrypted, "Dreamforce");
watch.Stop();
Console.WriteLine("Decryption Time: " + watch.Elapsed);
Console.WriteLine(myDecryptedData);
Console.Read();
}
}
}
and my ByteCipher(I highly simplified it after the error originally occurred as an attempt to pinpoint the problem):
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using DreamforceFramework.Framework.Utilities;
namespace DreamforceFramework.Framework.Cryptography
{
/// <summary>
/// DreamforceByteCipher
/// Gordon Kyle Wallace, "Krythic"
/// Copyright (C) 2015 Gordon Kyle Wallace, "Krythic" - All Rights Reserved
/// </summary>
public static class DreamforceByteCipher
{
public static byte[] Encrypt(string data, string password)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
string passwordHash = DreamforceHashing.GenerateSHA256(password);
byte[] hashedPasswordBytes = Encoding.ASCII.GetBytes(passwordHash);
int passwordShiftIndex = 0;
bool twistPath = false;
for (int i = 0; i < bytes.Length; i++)
{
int shift = hashedPasswordBytes[passwordShiftIndex];
bytes[i] = twistPath
? (byte)(
(data[i] + (shift * i)))
: (byte)(
(data[i] - (shift * i)));
passwordShiftIndex = (passwordShiftIndex + 1) % 64;
twistPath = !twistPath;
}
return bytes;
}
/// <summary>
/// Decrypts a byte array back into a string.
/// </summary>
/// <param name="data"></param>
/// <param name="password"></param>
/// <returns></returns>
public static string Decrypt(byte[] data, string password)
{
string passwordHash = DreamforceHashing.GenerateSHA256(password);
byte[] hashedPasswordBytes = Encoding.UTF8.GetBytes(passwordHash);
int passwordShiftIndex = 0;
bool twistPath = false;
for (int i = 0; i < data.Length; i++)
{
int shift = hashedPasswordBytes[passwordShiftIndex];
data[i] = twistPath
? (byte)(
(data[i] - (shift * i)))
: (byte)(
(data[i] + (shift * i)));
passwordShiftIndex = (passwordShiftIndex + 1) % 64;
twistPath = !twistPath;
}
return Encoding.ASCII.GetString(data);
}
}
}
With the for loops commented out, this is the output that I get:
The very last line shows that everything was decrypted successfully.
Now...this is where things get weird. If you uncomment the for loops, and run the program, this is the output:
The decryption did not work. This makes absolutely no sense, because the variable holding the decrypted data should be rewritten each and every time. Did I encounter a bug in C#/.NET that is causing this strange behavior?
A simple solution:
http://pastebin.com/M3xa9yQK
Your Decrypt method modifies the data input array in place. Therefore, you can only call Decrypt a single time with any given input byte array before the data is no longer encrypted. Take a simple console application for example:
class Program
{
public static void Main(string[] args)
{
var arr = new byte[] { 10 };
Console.WriteLine(arr[0]); // prints 10
DoSomething(arr);
Console.WriteLine(arr[0]); // prints 11
}
private static void DoSomething(byte[] arr)
{
arr[0] = 11;
}
}
So, to answer your question, no. You haven't found a bug in .NET. You've found a very simple bug in your code.

Suppressing Frequencies From FFT

What I am trying to do is to retrieve the frequencies from some song and suppress all the frequencies that do not appear in the human vocal range or in general any range. Here is my suppress function.
public void SupressAndWrite(Func<FrequencyUnit, bool> func)
{
this.WaveManipulated = true;
while (this.mainWave.WAVFile.NumSamplesRemaining > 0)
{
FrequencyUnit[] freqUnits = this.mainWave.NextFrequencyUnits();
Complex[] compUnits = (from item
in freqUnits
select (func(item)
? new Complex(item.Frequency, 0) :Complex.Zero))
.ToArray();
FourierTransform.FFT(compUnits, FourierTransform.Direction.Backward);
short[] shorts = (from item
in compUnits
select (short)item.Real).ToArray();
foreach (short item in shorts)
{
this.ManipulatedFile.AddSample16bit(item);
}
}
this.ManipulatedFile.Close();
}
Here is my class for my wave.
public sealed class ComplexWave
{
public readonly WAVFile WAVFile;
public readonly Int32 SampleSize;
private FourierTransform.Direction fourierDirection { get; set; }
private long position;
/// <param name="file"></param>
/// <param name="sampleSize in BLOCKS"></param>
public ComplexWave(WAVFile file, int sampleSize)
{
file.NullReferenceExceptionCheck();
this.WAVFile = file;
this.SampleSize = sampleSize;
if (this.SampleSize % 8 != 0)
{
if (this.SampleSize % 16 != 0)
{
throw new ArgumentException("Sample Size");
}
}
if (!MathTools.IsPowerOf2(sampleSize))
{
throw new ArgumentException("Sample Size");
}
this.fourierDirection = FourierTransform.Direction.Forward;
}
public Complex[] NextSampleFourierTransform()
{
short[] newInput = this.GetNextSample();
Complex[] data = newInput.CopyToComplex();
if (newInput.Any((x) => x != 0))
{
Debug.Write("done");
}
FourierTransform.FFT(data, this.fourierDirection);
return data;
}
public FrequencyUnit[] NextFrequencyUnits()
{
Complex[] cm = this.NextSampleFourierTransform();
FrequencyUnit[] freqUn = new FrequencyUnit[(cm.Length / 2)];
int max = (cm.Length / 2);
for (int i = 0; i < max; i++)
{
freqUn[i] = new FrequencyUnit(cm[i], this.WAVFile.SampleRateHz, i, cm.Length);
}
Array.Sort(freqUn);
return freqUn;
}
private short[] GetNextSample()
{
short[] retval = new short[this.SampleSize];
for (int i = 0; i < this.SampleSize; i++)
{
if (this.WAVFile.NumSamplesRemaining > 0)
{
retval[i] = this.WAVFile.GetNextSampleAs16Bit();
this.position++;
}
}
return retval;
}
}
Both FFT forward and FFT backwards work correctly. Could you please tell me what my error is.
Unfortunately, human voice, even when singing, isn't in 'frequency range'. It usually has one main frequency and multitude of harmonics that follow it, depending on the phoneme.
Use this https://play.google.com/store/apps/details?id=radonsoft.net.spectralview&hl=en or some similar app to see what I mean - and then re-define your strategy. Also google 'karaoke' effect.
NEXT:
It's not obvious from your example, but you should scan whole file in windows (google 'fft windowing') to process it whole.

Categories