From b535c30b4b9c5c6134c0153bb0a8b43219d32658 Mon Sep 17 00:00:00 2001 From: VoidX Date: Wed, 31 Jul 2024 22:42:37 +0200 Subject: [PATCH] Microphone disconnection detection --- Cavern.QuickEQ/Utilities/GraphUtils.cs | 32 +++++++++---------- .../Input/InputDeviceBlockReader.cs | 10 ++++++ Tests/Test.Cavern.QuickEQ/Consts/TestUtils.cs | 15 +++++++-- .../SyntheticBiquadCrossover_Tests.cs | 2 +- .../MovingAverage_Tests.cs | 6 ++-- Tests/Test.Cavern.QuickEQ/Windowing_Tests.cs | 8 ++--- 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/Cavern.QuickEQ/Utilities/GraphUtils.cs b/Cavern.QuickEQ/Utilities/GraphUtils.cs index 887e50d5..423406bf 100644 --- a/Cavern.QuickEQ/Utilities/GraphUtils.cs +++ b/Cavern.QuickEQ/Utilities/GraphUtils.cs @@ -22,7 +22,7 @@ public static class GraphUtils { /// Performed action public static void ForEachLin(T[] source, double startFreq, double endFreq, FrequencyFunction action) { double step = (endFreq - startFreq) / (source.Length - 1); - for (int entry = 0; entry < source.Length; ++entry) { + for (int entry = 0; entry < source.Length; entry++) { action(startFreq + step * entry, ref source[entry]); } } @@ -36,7 +36,7 @@ public static void ForEachLin(T[] source, double startFreq, double endFreq, F /// Performed action public static void ForEachLog(T[] source, double startFreq, double endFreq, FrequencyFunction action) { double mul = Math.Pow(10, (Math.Log10(endFreq) - Math.Log10(startFreq)) / (source.Length - 1)); - for (int i = 0; i < source.Length; ++i) { + for (int i = 0; i < source.Length; i++) { action(startFreq, ref source[i]); startFreq *= mul; } @@ -48,7 +48,7 @@ public static void ForEachLog(T[] source, double startFreq, double endFreq, F [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AddSlope(this float[] curve, float slope) { float mul = 1f / curve.Length; - for (int i = 0; i < curve.Length; ++i) { + for (int i = 0; i < curve.Length; i++) { curve[i] += slope * mul; } } @@ -58,7 +58,7 @@ public static void AddSlope(this float[] curve, float slope) { /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ConvertFromDecibels(float[] curve) { - for (int i = 0; i < curve.Length; ++i) { + for (int i = 0; i < curve.Length; i++) { curve[i] = (float)Math.Pow(10, curve[i] * .05f); } } @@ -75,7 +75,7 @@ public static void ConvertFromDecibels(float[] curve) { /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ConvertToDecibels(float[] curve, float minimum) { - for (int i = 0; i < curve.Length; ++i) { + for (int i = 0; i < curve.Length; i++) { curve[i] = 20 * (float)Math.Log10(curve[i]); if (curve[i] < minimum) { // this is also true if curve[i] == 0 curve[i] = minimum; @@ -95,7 +95,7 @@ public static float[] ConvertToGraph(Complex[] response, double startFreq, doubl float[] graph = new float[resultSize]; double step = Math.Pow(10, (Math.Log10(endFreq) - Math.Log10(startFreq)) / (graph.Length - 1)), positioner = response.Length / (double)sampleRate; - for (int i = 0; i < graph.Length; ++i) { + for (int i = 0; i < graph.Length; i++) { graph[i] = response[(int)(startFreq * positioner)].Magnitude; startFreq *= step; } @@ -116,7 +116,7 @@ public static float[] ConvertToGraph(float[] response, double startFreq, double float[] graph = new float[resultSize]; double step = Math.Pow(10, (Math.Log10(endFreq) - Math.Log10(startFreq)) / graph.Length), positioner = response.Length * 2 / (double)sampleRate; - for (int i = 0; i < graph.Length; ++i) { + for (int i = 0; i < graph.Length; i++) { graph[i] = response[(int)(startFreq * positioner)]; startFreq *= step; } @@ -151,7 +151,7 @@ public static float Correlation(float[] x, float[] y, int start, int end) { public static (float min, float max) GetLimits(float[] values) { float min = values[0]; float max = values[0]; - for (int i = 1; i < values.Length; ++i) { + for (int i = 1; i < values.Length; i++) { if (max < values[i]) { max = values[i]; } @@ -182,7 +182,7 @@ public static float Max(this float[] graph) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Normalize(float[] graph) { float avg = graph.Average(); - for (int i = 0; i < graph.Length; ++i) { + for (int i = 0; i < graph.Length; i++) { graph[i] -= avg; } } @@ -193,7 +193,7 @@ public static void Normalize(float[] graph) { public static float[] Scale(float[] source, int newLength) { float[] result = new float[newLength]; float scale = source.Length / (float)newLength--; - for (int pos = 0; pos < newLength; ++pos) { + for (int pos = 0; pos < newLength; pos++) { result[pos] = WaveformUtils.GetPeakSigned(source, (int)(pos * scale), (int)((pos + 1) * scale)); } return result; @@ -205,7 +205,7 @@ public static float[] Scale(float[] source, int newLength) { public static float[] Scale(float[] source, int newLength, int sourceStart, int sourceEnd) { float[] result = new float[newLength]; float scale = (sourceEnd - sourceStart) / (float)newLength--; - for (int pos = 0; pos < newLength; ++pos) { + for (int pos = 0; pos < newLength; pos++) { result[pos] = WaveformUtils.GetPeakSigned(source, sourceStart + (int)(pos * scale), sourceStart + (int)((pos + 1) * scale)); } @@ -220,18 +220,18 @@ public static float[] SmoothUniform(float[] source, int windowSize) { lastBlock = length - windowSize; float[] smoothed = new float[length--]; float average = 0; - for (int sample = 0; sample < windowSize; ++sample) { + for (int sample = 0; sample < windowSize; sample++) { average += source[sample]; } - for (int sample = 0; sample < windowSize; ++sample) { + for (int sample = 0; sample < windowSize; sample++) { smoothed[sample] = average / (sample + windowSize); average += source[sample + windowSize]; } - for (int sample = windowSize; sample < lastBlock; ++sample) { + for (int sample = windowSize; sample < lastBlock; sample++) { average += source[sample + windowSize] - source[sample - windowSize]; smoothed[sample] = average / (windowSize * 2); } - for (int sample = lastBlock; sample <= length; ++sample) { + for (int sample = lastBlock; sample <= length; sample++) { average -= source[sample - windowSize]; smoothed[sample] = average / (length - sample + windowSize); } @@ -268,7 +268,7 @@ public static float[] SmoothGraph(float[] samples, float startFreq, float endFre endGraph = SmoothGraph(samples, startFreq, endFreq, endOctave), output = new float[samples.Length]; float positioner = 1f / samples.Length; - for (int i = 0; i < samples.Length; ++i) { + for (int i = 0; i < samples.Length; i++) { output[i] = QMath.Lerp(startGraph[i], endGraph[i], i * positioner); } return output; diff --git a/CavernUnity DLL/Input/InputDeviceBlockReader.cs b/CavernUnity DLL/Input/InputDeviceBlockReader.cs index 6aab6998..b3521a8b 100644 --- a/CavernUnity DLL/Input/InputDeviceBlockReader.cs +++ b/CavernUnity DLL/Input/InputDeviceBlockReader.cs @@ -19,6 +19,11 @@ public class InputDeviceBlockReader : MonoBehaviour { /// public event AudioBlockDelegate Callback; + /// + /// Called when the microphone is not recording anymore for any reason. + /// + public event Action OnMicrophoneDisconnected; + /// /// Recording has successfully started. /// @@ -133,6 +138,11 @@ void Update() { if (buffer == null) { return; } + if (!MultiplatformMicrophone.IsRecording(activeDevice)) { + buffer = null; + OnMicrophoneDisconnected?.Invoke(); + return; + } int pos = MultiplatformMicrophone.GetPosition(activeDevice), interval = blockSize - overlap; diff --git a/Tests/Test.Cavern.QuickEQ/Consts/TestUtils.cs b/Tests/Test.Cavern.QuickEQ/Consts/TestUtils.cs index 9cfcb554..267790b1 100644 --- a/Tests/Test.Cavern.QuickEQ/Consts/TestUtils.cs +++ b/Tests/Test.Cavern.QuickEQ/Consts/TestUtils.cs @@ -5,14 +5,23 @@ internal static class TestUtils { /// /// Test if an between the given limits is strictly monotonously decreasing, - /// but allowing an error margin of . + /// but allowing an error margin of . /// - public static void AssertDecrease(float[] array, int from, int to, float epsilon) { + public static void AssertDecrease(float[] array, int from, int to, float delta) { for (int i = from + 1; i < to; i++) { - if (array[i - 1] + epsilon <= array[i]) { + if (array[i - 1] + delta <= array[i]) { Assert.Fail(); } } } + + /// + /// Test if all values of both arrays are in the expected margin of error () from each other. + /// + public static void AssertArrayEquals(float[] expected, float[] actual, float delta) { + for (int i = 0; i < expected.Length; i++) { + Assert.AreEqual(expected[i], actual[i], delta); + } + } } } \ No newline at end of file diff --git a/Tests/Test.Cavern.QuickEQ/Crossover/SyntheticBiquadCrossover_Tests.cs b/Tests/Test.Cavern.QuickEQ/Crossover/SyntheticBiquadCrossover_Tests.cs index 0636a2d8..a28b703d 100644 --- a/Tests/Test.Cavern.QuickEQ/Crossover/SyntheticBiquadCrossover_Tests.cs +++ b/Tests/Test.Cavern.QuickEQ/Crossover/SyntheticBiquadCrossover_Tests.cs @@ -10,6 +10,6 @@ public class SyntheticBiquadCrossover_Tests { /// Tests if generates correct impulse responses. /// [TestMethod, Timeout(1000)] - public void ImpulseResponse() => Utils.ImpulseResponse(new SyntheticBiquadCrossover(null, null), 0.47490564f, 0.5231629f); + public void ImpulseResponse() => Utils.ImpulseResponse(new SyntheticBiquadCrossover(null, null), .474857f, .52310514f); } } \ No newline at end of file diff --git a/Tests/Test.Cavern.QuickEQ/MovingAverage_Tests.cs b/Tests/Test.Cavern.QuickEQ/MovingAverage_Tests.cs index 8b0c20d0..5090f2e6 100644 --- a/Tests/Test.Cavern.QuickEQ/MovingAverage_Tests.cs +++ b/Tests/Test.Cavern.QuickEQ/MovingAverage_Tests.cs @@ -10,13 +10,13 @@ public class MovingAverage_Tests { /// Tests if works as intended. /// [TestMethod, Timeout(1000)] - public void Test() { + public void MovingAverage() { MovingAverage average = new MovingAverage(3); float[] first = { 1, 0, 4 }; average.AddFrame(first); Equals(average.Average, first); - average.AddFrame(new float[] { 2, 0, 4 }); - average.AddFrame(new float[] { 3, 3, 4 }); + average.AddFrame([2, 0, 4]); + average.AddFrame([3, 3, 4]); Equals(average.Average, new float[] { 2, 1, 4 }); } } diff --git a/Tests/Test.Cavern.QuickEQ/Windowing_Tests.cs b/Tests/Test.Cavern.QuickEQ/Windowing_Tests.cs index 4c43caed..60457deb 100644 --- a/Tests/Test.Cavern.QuickEQ/Windowing_Tests.cs +++ b/Tests/Test.Cavern.QuickEQ/Windowing_Tests.cs @@ -60,15 +60,15 @@ public void Symmetry() { /// /// A correct example of the Hann window's bounding area. /// - static readonly float[] hannResult = { + static readonly float[] hannResult = [ 0, 0.0954915f, 0.34549153f, 0.65450853f, 0.90450853f, 1, 0.9045085f, 0.65450853f, 0.34549144f, 0.09549138f - }; + ]; /// /// A correct example of the Tukey window's bounding area. /// - static readonly float[] tukeyResult = { + static readonly float[] tukeyResult = [ 0, 0.9045085f, 1, 1, 1, 1, 1, 1, 1, 0.904508233f - }; + ]; } } \ No newline at end of file