diff --git a/AudioAnalysis.sln.DotSettings b/AudioAnalysis.sln.DotSettings index 2419a01e3..c214b65e1 100644 --- a/AudioAnalysis.sln.DotSettings +++ b/AudioAnalysis.sln.DotSettings @@ -280,8 +280,11 @@ True True True + True True True + True + True True True True diff --git a/src/Acoustics.Tools/Audio/SoxAudioUtility.cs b/src/Acoustics.Tools/Audio/SoxAudioUtility.cs index 8cb941214..4929b7f25 100644 --- a/src/Acoustics.Tools/Audio/SoxAudioUtility.cs +++ b/src/Acoustics.Tools/Audio/SoxAudioUtility.cs @@ -188,15 +188,16 @@ protected override string ConstructModifyArgs(FileInfo source, FileInfo output, var trim = string.Empty; if (request.OffsetStart.HasValue && !request.OffsetEnd.HasValue) { - trim = "trim " + request.OffsetStart.Value.TotalSeconds; + trim = "trim " + request.OffsetStart.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture); } else if (!request.OffsetStart.HasValue && request.OffsetEnd.HasValue) { - trim = "trim 0 " + request.OffsetEnd.Value.TotalSeconds; + trim = "trim 0 " + request.OffsetEnd.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture); } else if (request.OffsetStart.HasValue && request.OffsetEnd.HasValue) { - trim = "trim " + request.OffsetStart.Value.TotalSeconds + " " + (request.OffsetEnd.Value.TotalSeconds - request.OffsetStart.Value.TotalSeconds); + var delta = request.OffsetEnd.Value.TotalSeconds - request.OffsetStart.Value.TotalSeconds; + trim = FormattableString.Invariant($"trim {request.OffsetStart.Value.TotalSeconds} {delta}"); } var bandpass = string.Empty; @@ -205,12 +206,14 @@ protected override string ConstructModifyArgs(FileInfo source, FileInfo output, switch (request.BandPassType) { case BandPassType.Sinc: - bandpass += "sinc {0}k-{1}k".Format(request.BandpassLow.Value / 1000, request.BandpassHigh.Value / 1000); + bandpass += FormattableString.Invariant( + $"sinc {request.BandpassLow.Value / 1000}k-{request.BandpassHigh.Value / 1000}k"); break; case BandPassType.Bandpass: double width = request.BandpassHigh.Value - request.BandpassLow.Value; - var center = width / 2.0; - bandpass += "bandpass {0}k width{k}".Format(center / 1000, width / 1000); + var center = request.BandpassLow.Value + (width / 2.0); + bandpass += FormattableString.Invariant( + $"bandpass { center / 1000 }k { width / 1000 }k"); break; case BandPassType.None: default: @@ -218,7 +221,6 @@ protected override string ConstructModifyArgs(FileInfo source, FileInfo output, } } - // example // remix down to 1 channel, medium resample quality using steep filter with target sample rate of 11025hz // sox input.wav output.wav remix - rate -m -s 11025 diff --git a/tests/Acoustics.Test/Acoustics.Test.csproj b/tests/Acoustics.Test/Acoustics.Test.csproj index 853de4971..693232e9e 100644 --- a/tests/Acoustics.Test/Acoustics.Test.csproj +++ b/tests/Acoustics.Test/Acoustics.Test.csproj @@ -341,6 +341,8 @@ + + diff --git a/tests/Acoustics.Test/TestHelpers/LeakyFfmpegAudioUtility.cs b/tests/Acoustics.Test/TestHelpers/LeakyFfmpegAudioUtility.cs new file mode 100644 index 000000000..a693b6573 --- /dev/null +++ b/tests/Acoustics.Test/TestHelpers/LeakyFfmpegAudioUtility.cs @@ -0,0 +1,36 @@ +// +// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). +// + +namespace Acoustics.Test.TestHelpers +{ + using System.IO; + using Acoustics.Tools; + using Acoustics.Tools.Audio; + + /// + /// So named because we use it to break our abstractions, to make them leaky. + /// + public class LeakyFfmpegAudioUtility : FfmpegAudioUtility + { + public LeakyFfmpegAudioUtility(FileInfo ffmpegExe, FileInfo ffprobeExe) + : base(ffmpegExe, ffprobeExe) + { + } + + public LeakyFfmpegAudioUtility(FileInfo ffmpegExe, FileInfo ffprobeExe, DirectoryInfo temporaryFilesDirectory) + : base(ffmpegExe, ffprobeExe, temporaryFilesDirectory) + { + } + + public string GetConstructedModifyArguments(FileInfo source, FileInfo output, AudioUtilityRequest request) + { + return this.ConstructModifyArgs(source, output, request); + } + + public string GetConstructedInfoArguments(FileInfo source, FileInfo output, AudioUtilityRequest request) + { + return this.ConstructInfoArgs(source); + } + } +} \ No newline at end of file diff --git a/tests/Acoustics.Test/TestHelpers/LeakySoxAudioUtility.cs b/tests/Acoustics.Test/TestHelpers/LeakySoxAudioUtility.cs new file mode 100644 index 000000000..9319ab580 --- /dev/null +++ b/tests/Acoustics.Test/TestHelpers/LeakySoxAudioUtility.cs @@ -0,0 +1,42 @@ +// +// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). +// + +namespace Acoustics.Test.TestHelpers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Acoustics.Shared; + using Acoustics.Tools; + using Acoustics.Tools.Audio; + + /// + /// So named because we use it to break our abstractions, to make them leaky. + /// + public class LeakySoxAudioUtility : SoxAudioUtility + { + public LeakySoxAudioUtility(FileInfo soxExe) + : base(soxExe) + { + } + + public LeakySoxAudioUtility(FileInfo soxExe, DirectoryInfo temporaryFilesDirectory, bool enableShortNameHack = true) + : base(soxExe, temporaryFilesDirectory, enableShortNameHack) + { + } + + public string GetConstructedModifyArguments(FileInfo source, FileInfo output, AudioUtilityRequest request) + { + return this.ConstructModifyArgs(source, output, request); + } + + public string GetConstructedInfoArguments(FileInfo source) + { + return this.ConstructInfoArgs(source); + } + } +} diff --git a/tests/Acoustics.Test/Tools/MasterAudioUtilityTests.cs b/tests/Acoustics.Test/Tools/MasterAudioUtilityTests.cs index 184dbbadd..70e03ed37 100644 --- a/tests/Acoustics.Test/Tools/MasterAudioUtilityTests.cs +++ b/tests/Acoustics.Test/Tools/MasterAudioUtilityTests.cs @@ -315,17 +315,6 @@ public void SegmentsWvCorrectly() TimeSpan.FromMilliseconds(0)); } - - - /// - /// The test sox. - /// - [TestMethod] - public void TestSox() - { - TestHelper.GetAudioUtility().Info(PathHelper.GetTestAudioFile("TorresianCrow.wav")); - } - /// /// The validates non existing exe paths. /// diff --git a/tests/Acoustics.Test/Tools/SoxUtilityTests.cs b/tests/Acoustics.Test/Tools/SoxUtilityTests.cs index bb219861c..a0be4f1c8 100644 --- a/tests/Acoustics.Test/Tools/SoxUtilityTests.cs +++ b/tests/Acoustics.Test/Tools/SoxUtilityTests.cs @@ -9,7 +9,9 @@ namespace Acoustics.Test.Tools { using System; + using System.Globalization; using System.IO; + using System.Threading; using Acoustics.Shared; using Acoustics.Tools; using Acoustics.Tools.Audio; @@ -102,7 +104,6 @@ public void SoxResamplingShouldBeDeterministic() repeats[r] = reader.Samples; File.Delete(output.FullName); - } for (int i = 1; i < repeats.Length; i++) @@ -118,7 +119,64 @@ public void SoxResamplingShouldBeDeterministic() CollectionAssert.AreEqual(repeats[0], repeats[i], $"Repeat {i} was not identical to repeat 0. Total delta: {totalDifference}"); } + } + + [DataTestMethod] + [DataRow("en-AU", BandPassType.Bandpass)] + [DataRow("de-DE", BandPassType.Bandpass)] + [DataRow("it-it", BandPassType.Bandpass)] + [DataRow("es-AR", BandPassType.Bandpass)] + [DataRow("en-AU", BandPassType.Sinc)] + [DataRow("de-DE", BandPassType.Sinc)] + [DataRow("it-it", BandPassType.Sinc)] + [DataRow("es-AR", BandPassType.Sinc)] + public void SoxCanSegmentWithDifferentLocales(string culture, BandPassType bandPassType) + { + var originalCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + try + { + var util = new LeakySoxAudioUtility(PathHelper.GetExe(AppConfigHelper.SoxExe)); + + var source = TestHelper.GetAudioFile("CaneToad_Gympie_44100.wav"); + + // intentionally testing that commas aren't included in the + // constructed commands for numbers as a decimal separator + var constructedArguments = util.GetConstructedModifyArguments(source, TempFileHelper.NewTempFile(), new AudioUtilityRequest() + { + OffsetStart = 123.456.Seconds(), + OffsetEnd = 789.123.Seconds(), + BandpassHigh = 789.456, + BandpassLow = 123.789, + BandPassType = bandPassType, + }); + StringAssert.Contains(constructedArguments, "123.456"); + + // end is specified as a duration + StringAssert.Contains(constructedArguments, "665.667"); + + switch (bandPassType) + { + case BandPassType.Sinc: + StringAssert.Contains(constructedArguments, "0.123789"); + StringAssert.Contains(constructedArguments, "0.789456"); + break; + case BandPassType.Bandpass: + + // bandpass filter is centre + width hence numbers are different + StringAssert.Contains(constructedArguments, "0.4566225"); + StringAssert.Contains(constructedArguments, "0.665667"); + break; + case BandPassType.None: + default: + throw new ArgumentOutOfRangeException(nameof(bandPassType), bandPassType, null); + } + } + finally + { + Thread.CurrentThread.CurrentCulture = originalCulture; + } } } -} \ No newline at end of file +}