diff --git a/.gitignore b/.gitignore
index 6db9ba9..8a19a86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -334,3 +334,4 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
+Properties/launchSettings.json
diff --git a/Measurer.cs b/Measurer.cs
new file mode 100644
index 0000000..1c8537d
--- /dev/null
+++ b/Measurer.cs
@@ -0,0 +1,122 @@
+using NAudio.Dsp;
+using NAudio.Wave;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace SoundThermo
+{
+ ///
+ /// Generates sine sample in the form
+ /// amp * sin(x / freq)
+ ///
+ class ThermoSampleProvider : ISampleProvider
+ {
+ public const int SAMPLE_RATE = 44100;
+ WaveFormat format;
+ float freq;
+ float amp;
+ int alreadyGeneratedSamples;
+
+ public ThermoSampleProvider(float freq, float amp)
+ {
+ format = WaveFormat.CreateIeeeFloatWaveFormat(SAMPLE_RATE, 1);
+ this.freq = freq;
+ this.amp = amp;
+ alreadyGeneratedSamples = 0;
+ }
+
+ public WaveFormat WaveFormat
+ {
+ get
+ {
+ return format;
+ }
+ }
+
+ public int Read(float[] buffer, int offset, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ int sample = alreadyGeneratedSamples++;
+ buffer[offset + i] = amp * MathF.Sin(2 * MathF.PI * sample * freq / SAMPLE_RATE);
+ }
+ return count;
+ }
+ }
+
+ ///
+ /// Returns ratio of output signal to input signal
+ ///
+ class Measurer
+ {
+ public enum MeasureChannel
+ {
+ Left,
+ Right
+ }
+
+ const int THERMO_FREQ = 6000;
+ const float THERMO_AMP = 0.1f;
+
+ WaveInEvent inputDevice;
+ WaveOutEvent outputDevice;
+ MeasureChannel channel;
+
+ public Measurer(WaveInEvent inputDevice, WaveOutEvent outputDevice, MeasureChannel ch)
+ {
+ this.inputDevice = inputDevice;
+ this.outputDevice = outputDevice;
+ this.channel = ch;
+ }
+
+ public async Task Measure(float duration)
+ {
+ var recorder = new Recorder(inputDevice);
+ var provider = new ThermoSampleProvider(THERMO_FREQ, THERMO_AMP);
+ outputDevice.Init(provider.ToStereo(channel == MeasureChannel.Left ? 1 : 0, channel == MeasureChannel.Right ? 1 : 0));
+ outputDevice.Play();
+ await recorder.Record(duration);
+ outputDevice.Stop();
+ var newSamples = new List();
+ int leftPos = 0;
+ while (leftPos < recorder.samples.Count && MathF.Abs(recorder.samples[leftPos]) < 1e-4) leftPos++;
+ int rightPos = recorder.samples.Count - 1;
+ while (rightPos > 0 && MathF.Abs(recorder.samples[rightPos]) < 1e-4) rightPos--;
+ for (int i = leftPos; i <= rightPos; i++) newSamples.Add(recorder.samples[i]);
+ int pow = 1;
+ int m = 0;
+ while (2 * pow <= newSamples.Count)
+ {
+ pow *= 2;
+ m++;
+ }
+ var data = new Complex[pow];
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i].X = newSamples[i];
+ data[i].Y = 0;
+ }
+ FastFourierTransform.FFT(true, m, data);
+ float max = -1;
+ int argmax = 0;
+ for (int i = 0; i < data.Length; i++)
+ {
+ var amp = MathF.Sqrt(data[i].X * data[i].X + data[i].Y * data[i].Y);
+ if (amp > max)
+ {
+ max = amp;
+ argmax = i;
+ }
+ }
+ float desired = THERMO_FREQ * pow / ThermoSampleProvider.SAMPLE_RATE;
+ if (MathF.Abs(desired - argmax) < 1e-3)
+ {
+ // max - amplitude on desired frequency
+ return max / THERMO_AMP;
+ }
+ return float.NaN;
+
+ }
+ }
+}
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..5ab5ac9
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,124 @@
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using NAudio.Wave;
+
+namespace SoundThermo
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ CultureInfo.CurrentCulture = new CultureInfo("en-US");
+ var rootCommand = new RootCommand();
+ var listCommand = new Command("list", "List all available devices");
+ listCommand.Handler = CommandHandler.Create(ListDevices);
+ var recordCommand = new Command("record")
+ {
+ new Option("--input", "Input device to record from (GUID)"),
+ new Option("--duration", "How many seconds of audio you want to record"),
+ };
+ recordCommand.Handler = CommandHandler.Create(RecordSamples);
+ var measureCommand = new Command("measure")
+ {
+ new Option("--input", "Input device to record from (GUID)"),
+ new Option("--output", "Output device to play sound to (GUID)"),
+ new Option("--duration", "How many seconds of audio you want to record (uses twice this time because of recording on two channels)"),
+ };
+ measureCommand.Handler = CommandHandler.Create(Measure);
+ rootCommand.AddCommand(listCommand);
+ rootCommand.AddCommand(measureCommand);
+ rootCommand.AddCommand(recordCommand);
+ rootCommand.InvokeAsync(args).Wait();
+ }
+
+ private async static Task Measure(string input, string output, float duration)
+ {
+ var leftMea = new Measurer(GetInputDevice(input), GetOutputDevice(output), Measurer.MeasureChannel.Left);
+ var leftResult = await leftMea.Measure(duration);
+ var rightMea = new Measurer(GetInputDevice(input), GetOutputDevice(output), Measurer.MeasureChannel.Right);
+ var rightResult = await rightMea.Measure(duration);
+ float k = 2 * rightResult;
+ float v0v = leftResult / k;
+ // assume r2 = 10K
+ float r1 = 10000 / v0v - 10000;
+ float t = 1.0f / (MathF.Log(r1 / 10000) / 4300.0f + 1.0f / 298.0f) - 273.0f;
+ Console.WriteLine(t.ToString());
+ }
+
+ private static WaveOutEvent GetOutputDevice(string guid)
+ {
+ WaveOutEvent device = null;
+ for (int n = -1; n < WaveOut.DeviceCount; n++)
+ {
+ var caps = WaveOut.GetCapabilities(n);
+ if (caps.ProductGuid.ToString() == guid)
+ {
+ device = new WaveOutEvent();
+ device.DeviceNumber = n;
+ break;
+ }
+ }
+ if (device == null)
+ {
+ throw new Exception("Unable to find device " + guid);
+ }
+ return device;
+ }
+
+ private static WaveInEvent GetInputDevice(string guid)
+ {
+ WaveInEvent device = null;
+ for (int n = -1; n < WaveIn.DeviceCount; n++)
+ {
+ var caps = WaveIn.GetCapabilities(n);
+ if (caps.ProductGuid.ToString() == guid)
+ {
+ device = new WaveInEvent();
+ device.DeviceNumber = n;
+ break;
+ }
+ }
+ if (device == null)
+ {
+ throw new Exception("Unable to find device " + guid);
+ }
+ return device;
+ }
+
+ private async static Task RecordSamples(string input, float duration)
+ {
+ WaveInEvent device = GetInputDevice(input);
+ var rec = new Recorder(device);
+ await rec.Record(duration);
+ using (FileStream fs = File.OpenWrite("recorded.txt"))
+ {
+ for (int i = 0; i < rec.samples.Count; i++)
+ {
+ fs.Write(Encoding.UTF8.GetBytes(rec.samples[i].ToString() + "\n"));
+ }
+ }
+ }
+
+ private static void ListDevices()
+ {
+ Console.WriteLine("List of audio devices in your system");
+ Console.WriteLine("Output devices:");
+ for (int n = -1; n < WaveOut.DeviceCount; n++)
+ {
+ var caps = WaveOut.GetCapabilities(n);
+ Console.WriteLine($"{n}: {caps.ProductName} {caps.ProductGuid}");
+ }
+ Console.WriteLine("Input devices:");
+ for (int n = -1; n < WaveIn.DeviceCount; n++)
+ {
+ var caps = WaveIn.GetCapabilities(n);
+ Console.WriteLine($"{n}: {caps.ProductName} {caps.ProductGuid}");
+ }
+ }
+ }
+}
diff --git a/Recorder.cs b/Recorder.cs
new file mode 100644
index 0000000..e26207f
--- /dev/null
+++ b/Recorder.cs
@@ -0,0 +1,37 @@
+using NAudio.Wave;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace SoundThermo
+{
+ class Recorder
+ {
+ const int RECORDING_RATE = 44100;
+ private WaveInEvent device;
+ public List samples;
+
+ public Recorder(WaveInEvent device)
+ {
+ this.device = device;
+ }
+
+ public async Task Record(float duration)
+ {
+ device.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(RECORDING_RATE, 1);
+ samples = new List();
+ device.DataAvailable += OnDataAvailable;
+ device.StartRecording();
+ await Task.Delay((int)(duration * 1000));
+ device.StopRecording();
+ }
+
+ private void OnDataAvailable(object sender, WaveInEventArgs e)
+ {
+ var buffer = new WaveBuffer(e.Buffer);
+ for (int i = 0; i < e.BytesRecorded / 4; i++)
+ {
+ samples.Add(buffer.FloatBuffer[i]);
+ }
+ }
+ }
+}
diff --git a/SoundThermo.csproj b/SoundThermo.csproj
new file mode 100644
index 0000000..16e254b
--- /dev/null
+++ b/SoundThermo.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
diff --git a/SoundThermo.sln b/SoundThermo.sln
new file mode 100644
index 0000000..df3cad1
--- /dev/null
+++ b/SoundThermo.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29209.62
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoundThermo", "SoundThermo.csproj", "{95349FF5-57FB-44EC-BDEB-E7902C04CDDC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {95349FF5-57FB-44EC-BDEB-E7902C04CDDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {95349FF5-57FB-44EC-BDEB-E7902C04CDDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {95349FF5-57FB-44EC-BDEB-E7902C04CDDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {95349FF5-57FB-44EC-BDEB-E7902C04CDDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A08CF9A2-F3A7-486E-BC48-B0D0BACF659B}
+ EndGlobalSection
+EndGlobal