I'm currently learning how to code games and have designed a biom generation algorithm.
As long as I run that algorithm below syncron, it generates the same output every time and works perfectly fine.
Now I tried to speed it up and make it multithreaded, but every time I call the method, it results in a different result.
As far as I know, I used Threadsave Collections, whenever necessary, but it still doesn't work.
Also, I tried to lock the collection, but this didn't work either.
So I'm completely clueless as to why this doesn't work.
If you see anything that I could make better or how I could fix that problem, please let me know.
This code is working:
private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
//Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
int chunkSize = 8;
//Create List that contains all centroids of the chunk
List<(Vector2Int, Biome)> centroids = new();
//Create Centdroids for every chunk of the generated region around the targetchunk
for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
{
for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
{
List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
{
centroids.Add(generatedCentdroid);
}
}
}
Biome[,] biomeMap = new Biome[chunkSize, chunkSize];
//---Generate biomeMap of the target Chunk---
for (int tx = 0; tx < chunkSize; tx++)
{
for (int tz = 0; tz < chunkSize; tz++)
{
int x = chunkSize * chunkNeighboursToGenerate + tx;
int z = chunkSize * chunkNeighboursToGenerate + tz;
biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
};
};
//Return the biome map of the target chunk
return biomeMap;
}
private static List<(Vector2Int, Biome)> GetCentdroidsByChunk(string worldSeed, Vector2Int chunkToGenerate, float centroidsPerChunk, int chunkSize, List<(Biome, float)> multiplier, Vector2Int targetChunk, int chunkNeighboursToGenerate)
{
List<(Vector2Int, Biome)> centroids = new();
//---Generate Cendroids of a single chunk---
float centroidsInThisChunk = centroidsPerChunk;
//Init randomizer
System.Random randomInstance = new(Randomizer.GetSeed(worldSeed, chunkToGenerate.x, chunkToGenerate.y));
while (centroidsInThisChunk > 0.0f)
{
//if at least one more centroid is to generate do it
//if not randomize by the given probability if another one should be generated
if (centroidsInThisChunk >= 1 || (float)randomInstance.NextDouble() * (1 - 0) + 0 <= centroidsInThisChunk)
{
//Generate random point for a new centroid
Vector2Int pos = new(randomInstance.Next(0, chunkSize + 1), randomInstance.Next(0, chunkSize + 1));
//map the point to a zerobased coordinatesystem
int mappedX = (((chunkToGenerate.x - targetChunk.x) + chunkNeighboursToGenerate) * chunkSize) + pos.x;
int mappedZ = (((chunkToGenerate.y - targetChunk.y) + chunkNeighboursToGenerate) * chunkSize) + pos.y;
Vector2Int mappedPos = new Vector2Int(mappedX, mappedZ);
//Select the biom randomized
Biome biome = Randomizer.GetRandomBiom(randomInstance, multiplier);
centroids.Add(new(mappedPos, biome));
centroidsInThisChunk -= 1.0f;
}
//if no centroid is left to generate, end the loop
else
{
break;
}
}
return centroids;
}
//Calculates the closest Centroid to the given possition
Biome GetClosestCentroidBiome(Vector2Int pixelPos, IEnumerable<(Vector2Int, Biome)> centroids)
{
//Warp the possition so the biom borders won't be straight
//Vector2 warpedPos = pixelPos + Get2DTurbulence(pixelPos);
Vector2 warpedPos = pixelPos;
float smallestDst = float.MaxValue;
Biome closestBiome = Biome.Empty;
foreach ((Vector2Int, Biome) centroid in centroids)
{
float distance = Vector2.Distance(warpedPos, centroid.Item1);
if (distance < smallestDst)
{
smallestDst = distance;
closestBiome = centroid.Item2;
}
}
return closestBiome;
}
public static class Randomizer
{
//Generates a random integerseed by combining an hashing the inputvalues
public static int GetSeed(string worldSeed, int chunkx, int chunkz)
{
var stringSeed = worldSeed + ":" + chunkx + ";" + chunkz;
MD5 md5Hasher = MD5.Create();
byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(stringSeed));
return BitConverter.ToInt32(hashed, 0);
}
//Returns a random biome based on the given properbilities/multiplier
//multiplier = 2 for example means the biom is generated twice as often as usually
public static Biome GetRandomBiom(System.Random rndm, List<(Biome, float)> multiplier)
{
float multmax = 0.0f;
multiplier.ForEach(x => multmax += x.Item2);
//Generate a random value that is in the range of all multiplieres added
float biome = (float)rndm.NextDouble() * (multmax + 0.01f);
//Map the biome to the multipliers and return the biome
float multcalc = 0.0f;
for (int r = 0; r < multiplier.Count; r++)
{
multcalc += multiplier[r].Item2;
if (multcalc >= biome)
{
return multiplier[r].Item1;
}
}
//Return Biome.Empty if something did't worked correct
return Biome.Empty;
}
}
This doesn't work:
private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
//Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
int chunkSize = 8;
//Create List that contains all centroids of the chunk
ConcurrentBag<(Vector2Int, Biome)> centroids = new();
ConcurrentQueue<Task> tasks = new();
//Create Centdroids for every chunk of the generated region around the targetchunk
for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
{
for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
{
tasks.Enqueue(Task.Run(() =>
{
List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
{
centroids.Add(generatedCentdroid);
}
}));
}
}
Biome[,] biomeMap = new Biome[chunkSize, chunkSize];
Task.WaitAll(tasks.ToArray());
//---Generate biomeMap of the target Chunk---
for (int tx = 0; tx < chunkSize; tx++)
{
for (int tz = 0; tz < chunkSize; tz++)
{
int x = chunkSize * chunkNeighboursToGenerate + tx;
int z = chunkSize * chunkNeighboursToGenerate + tz;
biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
};
};
//Return the biome map of the target chunk
return biomeMap;
}
If you're starting to get into programming and you want to learn multi-threading, converting a large piece of complex code like this is not where you want to start. I highly recommend you pick up a book or tutorial on threading/async in C#/.NET before starting something like this. Unity also has its own multi-threading library with its Job System, which is built for the Unity workflow: https://docs.unity3d.com/Manual/JobSystemMultithreading.html
I don't think most people could find what's causing the problem in these two code snippets alone. But I have a couple of suggestions
Change your tasks collection to a List<T>, tasks is only ever accessed on one thread so there's no need to use ConcurrentQueue<T>
Is Biome a class? Cause if so it's technically fine but modifying data structures from multiple threads gets hairy fast. And while I can't see that you're modifying data from these snippets, without the full code I can't say for sure. Turn Biome into a struct or make a struct equivalent for threading purposes.
Also avoid calling centroids.ToArray() in your loop, as doing so will actually copy the original array over and over and over again. Call it once outside of your loop and that alone should be a pretty huge performance bump.
Just find a full-blown tutorial for threading/async/Unity's Job system (depending on which you'd rather learn for your use case) and start from there, I can tell from your use of the concurrent libraries and List<T> inside your tasks that you're new to threading. Understanding what code is ran on another thread and the repercussions from that (race conditions, and so on) is huge.
Related
I'm trying to read a binaryfile. I don't know the structure, but I do have some code written in R that can read it. I'm not familiar with R but have made some progress converting it to C# and struggle at the last bit.
I'm at a point where I need to list out results which I would expect to be a series of float or double.
The R code looks like this (I've removed some of the logic to keep it short):
Rcpp::NumericVector GetSwmmResult(int iType, int iIndex, int vIndex)
{
int offset;
std::vector<float> resultvec(SWMM_Nperiods);
size_t size;
// --- compute offset into output file
for ( int i=1; i<=SWMM_Nperiods; ++i)
{
offset = StartPos + (i-1)*BytesPerPeriod + 2*RECORDSIZE;
if ( iType == SUBCATCH )
{
offset += RECORDSIZE*(iIndex*SubcatchVars + vIndex);
}
else return wrap(resultvec);
// --- re-position the file and read the result
fseek(Fout, offset, SEEK_SET);
size = fread(&resultvec[i-1], RECORDSIZE, 1, Fout);
}
return wrap(resultvec);
}
In C# I expected to do something as follows, where br is my BinaryReader object:
public List<double> GetSwmmResult(int iType, int iIndex, int vIndex)
{
int offset;
List<double> resultvec = new();
int size;
// --- compute offset into output file
Debug.WriteLine("SWMM_Nperiods count = " + SWMM_Nperiods);
for (int i = 1; i <= SWMM_Nperiods; i++)
{
Debug.WriteLine("SWMM_Nperiods " + i);
offset = StartPos + (i - 1) * BytesPerPeriod + 2 * RECORDSIZE;
if (iType == SUBCATCH)
{
offset += RECORDSIZE * (iIndex * SubcatchVars + vIndex);
}
else
{
return resultvec;
}
// --- re-position the file and read the result
br.BaseStream.Position = offset;
resultvec.Add(br.ReadDouble());
Debug.WriteLine(resultvec[i - 1]);
}
return resultvec;
}
But my C# just returns a load of very large numbers like:
5.058993159887922E-15
3.10628841909217E-16
5.477524451492502E-17
I'm expecting a series of numbers, but in the 100's or 1000's, not such large numbers.
Can anybody see how I should be returning values in my C# code using the R function above as a guide? There's a variable in the R code called SEEK_SET. It's not declared anywhere, so I don't understand how it's being used, but suspect it may be what I'm missing.
So how can I update the position every time I call the StartRandomizingRightSpikePosition
private bool CheckOverlap(GameObject o1, GameObject o2)
{
return spikeRight.Select(t => t.GetComponent<Collider>().bounds.Intersects(t.GetComponent<Collider>().bounds)).FirstOrDefault();
}
public void StartRandomizingRightSpikesPosition()
{
foreach (var t in spikeRight)
{
foreach (var t1 in spikeRight)
{
if (t == t1) continue;
if (!CheckOverlap(t, t1)) continue;
yPosition = Random.Range(-7, 7);
var position = t1.transform.position;
desiredPosition = new Vector3(position.x, yPosition, position.z);
t1.transform.position = desiredPosition;
Debug.Log(t.gameObject + " intersects " + t1.gameObject);
}
}
}
The short answer is yes but I'm not sure you would want too. I'm not sure you're going to find a way to do this efficiently and you might be better off finding a way to generate the objects such that this step is not necessary.
I can't tell from your question how the objects are actually stored so I'm going to provide some sample code that just deals with a simple array of Rectangles. You should be able to adapt it to your specifics.
I tried to make it slightly more efficient by not checking both t1 == t and t == t1.
const int Bounds = 1000;
static bool RemovedOverlappingRects(Rectangle[] rects)
{
for (int pos = 0; pos < rects.Length; ++pos)
{
for (int check = pos +1; check < rects.Length; ++check)
{
var r1 = rects[pos];
var r2 = rects[check];
if (r1.IntersectsWith(r2))
{
r2.Y = Rng.Next(1, Bounds);
rects[check] = r2;
Console.WriteLine($"{pos} overlaps with {check}");
return true;
}
}
}
return false;
}
Once we've randomly generated a new rectangle we have to start over. Which means invoking the above method in a loop.
var rects = GetRandomeRects(20).ToArray();
while (RemovedOverlappingRects(rects))
;
Because of the random movement I'm not certain you can guarantee this will always end. If you can deal with the non-random look of the results changing it to stack the overlaps would I believe always finish. That would be this:
r2.Y = r1.Y + r1.Height + 1;
in place of
r2.Y = Rng.Next(1, Bounds);
But even then you're still dealing with a very unpredictable run time due to the random input.
Maybe someone else can show us a more efficient way...
I'm looking to optimize a program that is basing a lot of its calculations on the rotation of a lot of 2D Points. I've search around to see if it's possible to do these calculations using SIMD in C#.
I found a c++ answer here that seems to do what I want, but I can't seem to translate this into C# using the System.Numerics.Vectors package.
Optimising 2D rotation
Can anyone point me in the right direction for how this can be done?
The below code shows the regular method without SIMD. Where Point is a struct with doubles X and Y.
public static Point[] RotatePoints(Point[] points, double cosAngle, double sinAngle)
{
var pointsLength = points.Length;
var results = new Point[pointsLength];
for (var i = 0; i < pointsLength; i++)
{
results[i].X = (points[i].X * cosAngle) - (points[i].Y * sinAngle);
results[i].Y = (points[i].X * sinAngle) + (points[i].Y * cosAngle);
}
return results;
}
Edit:
I've managed to get an implementation working using two Vector< float> but from benchmarking this, this seems to be a lot slower than the previous implementation.
private static void RotatePoints(float[] x, float[] y, float cosAngle, float sinAngle)
{
var chunkSize = Vector<float>.Count;
var resultX = new float[x.Length];
var resultY = new float[x.Length];
Vector<float> vectorChunk1;
Vector<float> vectorChunk2;
for (var i = 0; i < x.Length; i += chunkSize)
{
vectorChunk1 = new Vector<float>(x, i);
vectorChunk2 = new Vector<float>(y, i);
Vector.Subtract(Vector.Multiply(vectorChunk1, cosAngle), Vector.Multiply(vectorChunk2, sinAngle)).CopyTo(resultX, i);
Vector.Add(Vector.Multiply(vectorChunk1, sinAngle), Vector.Multiply(vectorChunk2, cosAngle)).CopyTo(resultY, i);
}
}
The code added in the edit is a good start, however the codegen for Vector.Multiply(Vector<float>, float) is extremely bad so this function should be avoided. It's an easy change to avoid it though, just broadcast outside the loop and multiply by a vector. I also added a more proper loop bound and "scalar epilog" in case the vector size does not neatly divide the size of the input arrays.
private static void RotatePoints(float[] x, float[] y, float cosAngle, float sinAngle)
{
var chunkSize = Vector<float>.Count;
var resultX = new float[x.Length];
var resultY = new float[x.Length];
Vector<float> vectorChunk1;
Vector<float> vectorChunk2;
Vector<float> vcosAngle = new Vector<float>(cosAngle);
Vector<float> vsinAngle = new Vector<float>(sinAngle);
int i;
for (i = 0; i + chunkSize - 1 < x.Length; i += chunkSize)
{
vectorChunk1 = new Vector<float>(x, i);
vectorChunk2 = new Vector<float>(y, i);
Vector.Subtract(Vector.Multiply(vectorChunk1, vcosAngle), Vector.Multiply(vectorChunk2, vsinAngle)).CopyTo(resultX, i);
Vector.Add(Vector.Multiply(vectorChunk1, vsinAngle), Vector.Multiply(vectorChunk2, vcosAngle)).CopyTo(resultY, i);
}
for (; i < x.Length; i++)
{
resultX[i] = x[i] * cosAngle - y[i] * sinAngle;
resultY[i] = x[i] * sinAngle + y[i] * cosAngle;
}
}
I am trying to develop a console application in C# which uses a WAV-file for input. The application should do a couple of things all in order, as shown below. First of all, the complete code:
class Program
{
static List<double> points = new List<double>();
static double maxValue = 0;
static double minValue = 1;
static int num = 0;
static int num2 = 0;
static List<double> values = new List<double>();
private static object akima;
static void Main(string[] args)
{
string[] fileLines = File.ReadAllLines(args[0]);
int count = 0;
foreach (string fileLine in fileLines)
{
if (!fileLine.Contains(";"))
{
string processLine = fileLine.Trim();
processLine = Regex.Replace(processLine, #"\s+", " ");
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
processLine = processLine.Replace(".", ",");
}
string[] dataParts = processLine.Split(Char.Parse(" "));
points.Add(double.Parse(dataParts[0]));
double value = Math.Pow(double.Parse(dataParts[1]), 2);
if (value > maxValue)
{
maxValue = value;
num = count;
}
values.Add(value);
}
count++;
}
for (int i = num; i < values.Count; i++)
{
if (values[i] < minValue)
{
minValue = values[i];
num2 = i;
}
}
Console.WriteLine(num + " " + num2);
int between = num2 - num;
points = points.GetRange(num, between);
values = values.GetRange(num, between);
List<double> defVal = new List<double>();
List<double> defValPoints = new List<double>();
alglib.spline1dinterpolant c;
alglib.spline1dbuildakima(points.ToArray(), values.ToArray(), out c);
double baseInt = alglib.spline1dintegrate(c, points[points.Count - 1]);
List<double> defETC = new List<double>();
for (int i = 0; i < points.Count; i += 10)
{
double toVal = points[i];
defETC.Add(10 * Math.Log10(values[i]));
defVal.Add(10 * Math.Log10((baseInt - alglib.spline1dintegrate(c, toVal)) / baseInt));
defValPoints.Add(points[i]);
}
WriteDoubleArrayToFile(defValPoints.ToArray(), defVal.ToArray(), "test.dat");
WriteDoubleArrayToFile(defValPoints.ToArray(), defETC.ToArray(), "etc.dat");
int end = 0;
for (int i = 0; i < points.Count; i++)
{
if (defVal[i] < -10)
{
end = i;
break;
}
}
//Console.WriteLine(num + " " + end);
int beginEDT = num;
int endEDT = num + end;
double timeBetween = (defValPoints[endEDT] - defValPoints[beginEDT]) * 6;
Console.WriteLine(timeBetween);
for (int i = 0; i < points.Count; i++)
{
}
Console.ReadLine();
}
static void WriteDoubleArrayToFile(double[] points, double[] values, string filename)
{
string[] defStr = new string[values.Length];
for (int i = 0; i < values.Length; i++)
{
defStr[i] = String.Format("{0,10}{1,25}", points[i], values[i]);
}
File.WriteAllLines(filename, defStr);
}
}
Extract the decimal/float/double value from the WAV-file
Create an array from extracted data
Create an Energy Time Curve that displays the decay of the noise/sound in a decibel-like way
Create an Decay Curve from the ETC created in step 3
Calculate things as Early Decay Time (EDT), T15/T20 and RT60 from this Decay Curve.
Display these Reverb Times in stdout.
At the moment I am sort of like half way through the process. I´ll explain what I did:
I used Sox to convert the audio file into a .dat file with numbers
I create an array using C# by simply splitting each line in the file above and putting the times in a TimesArray and the values at those points in a ValuesArray.
I am displaying a graph via GNUPlot, using the data processed with this function: 10 * Math.Log10(values[i]); (where i is an iterative integer in a for-loop iterating over all the items in the ValuesArray)
This is where I'm starting to get stuck. I mean, in this step I am using an Akima Spline function from Alglib to be able to integrate a line. I am doing that with a Schroeder integration (reversed), via this mathematical calculation: 10 * Math.Log10((baseInt - alglib.spline1dintegrate(c, toVal)) / baseInt); (where baseInt is a value calculated as a base integral for the complete curve, so I have a calculated bottom part of the reversed Schroeder integration. The c is a spline1dinterpolant made available when using the function alglib.spline1dbuildakima, which takes the timeArray as x values, valueArray as the y values, and c as an outwards spline1dinterpolant. toval is an x-value from the points array. The specific value is selected using a for-loop.) From these newly saved values I want to create an interpolated line and calculate the RT60 from that line, but I do not know how to do that.
Tried, did not really work out.
Same as above, I have no real values to show.
I'm quite stuck now, as I'm not sure if this is the right way to do it. If anyone can tell me how I can calculate the reverberation times in a fast and responsive way in C#, I'd be pleased to hear. The way of doing it might be completely different from what I have now, that's OK, just let me know!
Maybe you need to approach this differently:
start with the underlying maths. find out the mathematical formulas for these functions.
Use a simple mathematical function and calculate by hand (in excel or matlab) what the values should be (of all these things ETC, DC, EDC, T15, T20, RT60)
(A function such as a sine wave of just the minimum number of points you need )
then write a separate procedure to evaluate each of these in C# and verify your results for consistency with excel/matlab.
in C#, maybe store your data in a class that you pass around to each of the methods for its calculation.
your main function should end up something like this:
main(){
data = new Data();
//1, 2:
extract_data(data, filename);
//3:
energy_time_curve(data)
//...4, 5
//6:
display_results(data);
}
I'm tasked with building a .NET client app to detect silence in a WAV files.
Is this possible with the built-in Windows APIs? Or alternately, any good libraries out there to help with this?
Audio analysis is a difficult thing requiring a lot of complex math (think Fourier Transforms). The question you have to ask is "what is silence". If the audio that you are trying to edit is captured from an analog source, the chances are that there isn't any silence... they will only be areas of soft noise (line hum, ambient background noise, etc).
All that said, an algorithm that should work would be to determine a minimum volume (amplitude) threshold and duration (say, <10dbA for more than 2 seconds) and then simply do a volume analysis of the waveform looking for areas that meet this criteria (with perhaps some filters for millisecond spikes). I've never written this in C#, but this CodeProject article looks interesting; it describes C# code to draw a waveform... that is the same kind of code which could be used to do other amplitude analysis.
If you want to efficiently calculate the average power over a sliding window: square each sample, then add it to a running total. Subtract the squared value from N samples previous. Then move to the next step. This is the simplest form of a CIC Filter. Parseval's Theorem tells us that this power calculation is applicable to both time and frequency domains.
Also you may want to add Hysteresis to the system to avoid switching on&off rapidly when power level is dancing about the threshold level.
http://www.codeproject.com/Articles/19590/WAVE-File-Processor-in-C
This has all the code necessary to strip silence, and mix wave files.
Enjoy.
I'm using NAudio, and I wanted to detect the silence in audio files so I can either report or truncate.
After a lot of research, I came up with this basic implementation. So, I wrote an extension method for the AudioFileReader class which returns the silence duration at the start/end of the file, or starting from a specific position.
Here:
static class AudioFileReaderExt
{
public enum SilenceLocation { Start, End }
private static bool IsSilence(float amplitude, sbyte threshold)
{
double dB = 20 * Math.Log10(Math.Abs(amplitude));
return dB < threshold;
}
public static TimeSpan GetSilenceDuration(this AudioFileReader reader,
SilenceLocation location,
sbyte silenceThreshold = -40)
{
int counter = 0;
bool volumeFound = false;
bool eof = false;
long oldPosition = reader.Position;
var buffer = new float[reader.WaveFormat.SampleRate * 4];
while (!volumeFound && !eof)
{
int samplesRead = reader.Read(buffer, 0, buffer.Length);
if (samplesRead == 0)
eof = true;
for (int n = 0; n < samplesRead; n++)
{
if (IsSilence(buffer[n], silenceThreshold))
{
counter++;
}
else
{
if (location == SilenceLocation.Start)
{
volumeFound = true;
break;
}
else if (location == SilenceLocation.End)
{
counter = 0;
}
}
}
}
// reset position
reader.Position = oldPosition;
double silenceSamples = (double)counter / reader.WaveFormat.Channels;
double silenceDuration = (silenceSamples / reader.WaveFormat.SampleRate) * 1000;
return TimeSpan.FromMilliseconds(silenceDuration);
}
}
This will accept almost any audio file format not just WAV.
Usage:
using (AudioFileReader reader = new AudioFileReader(filePath))
{
TimeSpan duration = reader.GetSilenceDuration(AudioFileReaderExt.SilenceLocation.Start);
Console.WriteLine(duration.TotalMilliseconds);
}
References:
How audio dB levels are calculated.
Floating-point samples range.
More about amplitude.
Here a nice variant to detect threshold alternatings:
static class AudioFileReaderExt
{
private static bool IsSilence(float amplitude, sbyte threshold)
{
double dB = 20 * Math.Log10(Math.Abs(amplitude));
return dB < threshold;
}
private static bool IsBeep(float amplitude, sbyte threshold)
{
double dB = 20 * Math.Log10(Math.Abs(amplitude));
return dB > threshold;
}
public static double GetBeepDuration(this AudioFileReader reader,
double StartPosition, sbyte silenceThreshold = -40)
{
int counter = 0;
bool eof = false;
int initial = (int)(StartPosition * reader.WaveFormat.Channels * reader.WaveFormat.SampleRate / 1000);
if (initial > reader.Length) return -1;
reader.Position = initial;
var buffer = new float[reader.WaveFormat.SampleRate * 4];
while (!eof)
{
int samplesRead = reader.Read(buffer, 0, buffer.Length);
if (samplesRead == 0)
eof = true;
for (int n = initial; n < samplesRead; n++)
{
if (IsBeep(buffer[n], silenceThreshold))
{
counter++;
}
else
{
eof=true; break;
}
}
}
double silenceSamples = (double)counter / reader.WaveFormat.Channels;
double silenceDuration = (silenceSamples / reader.WaveFormat.SampleRate) * 1000;
return TimeSpan.FromMilliseconds(silenceDuration).TotalMilliseconds;
}
public static double GetSilenceDuration(this AudioFileReader reader,
double StartPosition, sbyte silenceThreshold = -40)
{
int counter = 0;
bool eof = false;
int initial = (int)(StartPosition * reader.WaveFormat.Channels * reader.WaveFormat.SampleRate / 1000);
if (initial > reader.Length) return -1;
reader.Position = initial;
var buffer = new float[reader.WaveFormat.SampleRate * 4];
while (!eof)
{
int samplesRead = reader.Read(buffer, 0, buffer.Length);
if (samplesRead == 0)
eof=true;
for (int n = initial; n < samplesRead; n++)
{
if (IsSilence(buffer[n], silenceThreshold))
{
counter++;
}
else
{
eof=true; break;
}
}
}
double silenceSamples = (double)counter / reader.WaveFormat.Channels;
double silenceDuration = (silenceSamples / reader.WaveFormat.SampleRate) * 1000;
return TimeSpan.FromMilliseconds(silenceDuration).TotalMilliseconds;
}
}
Main usage:
using (AudioFileReader reader = new AudioFileReader("test.wav"))
{
double duratioff = 1;
double duration = 1;
double position = 1;
while (duratioff >-1 && duration >-1)
{
duration = reader.GetBeepDuration(position);
Console.WriteLine(duration);
position = position + duration;
duratioff = reader.GetSilenceDuration(position);
Console.WriteLine(-duratioff);
position = position + duratioff;
}
}
I don't think you'll find any built-in APIs for detection of silence. But you can always use good ol' math/discreete signal processing to find out loudness.
Here's a small example: http://msdn.microsoft.com/en-us/magazine/cc163341.aspx
Use Sox. It can remove leading and trailing silences, but you'll have to call it as an exe from your app.
See code below from Detecting audio silence in WAV files using C#
private static void SkipSilent(string fileName, short silentLevel)
{
WaveReader wr = new WaveReader(File.OpenRead(fileName));
IntPtr format = wr.ReadFormat();
WaveWriter ww = new WaveWriter(File.Create(fileName + ".wav"),
AudioCompressionManager.FormatBytes(format));
int i = 0;
while (true)
{
byte[] data = wr.ReadData(i, 1);
if (data.Length == 0)
{
break;
}
if (!AudioCompressionManager.CheckSilent(format, data, silentLevel))
{
ww.WriteData(data);
}
}
ww.Close();
wr.Close();
}