Initial commit
This commit is contained in:
@@ -334,3 +334,4 @@ ASALocalRun/
|
|||||||
# Local History for Visual Studio
|
# Local History for Visual Studio
|
||||||
.localhistory/
|
.localhistory/
|
||||||
|
|
||||||
|
Properties/launchSettings.json
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
using NAudio.Dsp;
|
||||||
|
using NAudio.Wave;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SoundThermo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates sine sample in the form
|
||||||
|
/// amp * sin(x / freq)
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns ratio of output signal to input signal
|
||||||
|
/// </summary>
|
||||||
|
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<float> 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<float>();
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+124
@@ -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<string>("--input", "Input device to record from (GUID)"),
|
||||||
|
new Option<float>("--duration", "How many seconds of audio you want to record"),
|
||||||
|
};
|
||||||
|
recordCommand.Handler = CommandHandler.Create<string, float>(RecordSamples);
|
||||||
|
var measureCommand = new Command("measure")
|
||||||
|
{
|
||||||
|
new Option<string>("--input", "Input device to record from (GUID)"),
|
||||||
|
new Option<string>("--output", "Output device to play sound to (GUID)"),
|
||||||
|
new Option<float>("--duration", "How many seconds of audio you want to record (uses twice this time because of recording on two channels)"),
|
||||||
|
};
|
||||||
|
measureCommand.Handler = CommandHandler.Create<string, string, float>(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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -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<float> samples;
|
||||||
|
|
||||||
|
public Recorder(WaveInEvent device)
|
||||||
|
{
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Record(float duration)
|
||||||
|
{
|
||||||
|
device.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(RECORDING_RATE, 1);
|
||||||
|
samples = new List<float>();
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="NAudio" Version="1.10.0" />
|
||||||
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20371.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user