-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
654 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,11 @@ | |
build*/ | ||
cmake-build*/ | ||
|
||
bin/ | ||
obj/ | ||
|
||
.DS_Store | ||
Thumbs.db | ||
|
||
*.mp3 | ||
*.wav |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Application xmlns="https://github.com/avaloniaui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
x:Class="Spectrality.App" | ||
xmlns:local="using:Spectrality" | ||
RequestedThemeVariant="Default"> | ||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> | ||
|
||
<Application.DataTemplates> | ||
<local:ViewLocator/> | ||
</Application.DataTemplates> | ||
|
||
<Application.Styles> | ||
<FluentTheme /> | ||
<StyleInclude Source="avares://OxyPlot.Avalonia/Themes/Default.axaml"/> | ||
</Application.Styles> | ||
</Application> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using Avalonia; | ||
using Avalonia.Controls.ApplicationLifetimes; | ||
using Avalonia.Markup.Xaml; | ||
using Spectrality.ViewModels; | ||
using Spectrality.Views; | ||
|
||
namespace Spectrality; | ||
|
||
public partial class App : Application | ||
{ | ||
public override void Initialize() | ||
{ | ||
AvaloniaXamlLoader.Load(this); | ||
} | ||
|
||
public override void OnFrameworkInitializationCompleted() | ||
{ | ||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) | ||
{ | ||
desktop.MainWindow = new MainWindow | ||
{ | ||
DataContext = new MainWindowViewModel(), | ||
}; | ||
} | ||
|
||
base.OnFrameworkInitializationCompleted(); | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Numerics; | ||
using Nito.Collections; | ||
|
||
class QDFT | ||
{ | ||
public double Samplerate { get; private set; } | ||
public (double, double) Bandwidth { get; private set; } | ||
public double Resolution { get; private set; } | ||
public double Quality { get; private set; } | ||
public double Latency { get; private set; } | ||
public (double, double) Window { get; private set; } | ||
|
||
public int Length { get; private set; } | ||
|
||
public double[] Frequencies { get; private set; } | ||
public double[] Qualities { get; private set; } | ||
public double[] Latencies { get; private set; } | ||
public int[] Periods { get; private set; } | ||
public int[] Offsets { get; private set; } | ||
public double[] Weights { get; private set; } | ||
|
||
public Complex[] Fiddles { get; private set; } | ||
public Complex[] Twiddles { get; private set; } | ||
|
||
private Deque<double> Inputs { get; set; } | ||
private Complex[] Outputs { get; set; } | ||
|
||
public QDFT(double samplerate, | ||
(double, double) bandwidth, | ||
double resolution = 24, | ||
double quality = 0, | ||
double latency = 0) | ||
{ | ||
Samplerate = samplerate; | ||
Bandwidth = bandwidth; | ||
Resolution = resolution; | ||
Quality = quality; | ||
Latency = latency; | ||
Window = (+0.5, -0.5); | ||
|
||
Length = (int)Math.Ceiling(Resolution * Math.Log2(Bandwidth.Item2 / Bandwidth.Item1)); | ||
|
||
Frequencies = new double[Length]; | ||
Qualities = new double[Length]; | ||
Latencies = new double[Length]; | ||
Periods = new int[Length]; | ||
Offsets = new int[Length]; | ||
Weights = new double[Length]; | ||
|
||
Fiddles = new Complex[Length * 3]; | ||
Twiddles = new Complex[Length * 3]; | ||
|
||
Bootstrap(); | ||
|
||
Inputs = new Deque<double>(new double[Periods.First() + 1]); | ||
Outputs = new Complex[Length * 3]; | ||
} | ||
|
||
private void Bootstrap() | ||
{ | ||
var alpha = Math.Pow(2.0, 1.0 / Resolution) - 1.0; | ||
var beta = (Quality < 0) ? (alpha * 24.7 / 0.108) : Quality; | ||
|
||
for (int i = 0; i < Length; i++) | ||
{ | ||
var frequency = Bandwidth.Item1 * Math.Pow(2.0, i / Resolution); | ||
|
||
Frequencies[i] = frequency; | ||
|
||
var quality = frequency / (alpha * frequency + beta); | ||
|
||
Qualities[i] = quality; | ||
|
||
var period = Math.Ceiling(quality * Samplerate / frequency); | ||
|
||
Periods[i] = (int)period; | ||
|
||
var offset = Math.Ceiling((Periods.First() - period) | ||
* Math.Clamp(Latency * 0.5 + 0.5, 0.0, 1.0)); | ||
|
||
Offsets[i] = (int)offset; | ||
|
||
var latency = (Periods.First() - offset) / Samplerate; | ||
|
||
Latencies[i] = latency; | ||
|
||
var weight = 1.0 / period; | ||
|
||
Weights[i] = weight; | ||
} | ||
|
||
foreach (int k in new[] { -1, 0, +1 }) | ||
{ | ||
for (int i = 0, j = 1; i < Length; ++i, j+=3) | ||
{ | ||
var fiddle = Complex.FromPolarCoordinates( | ||
1.0, -2.0 * Math.PI * (Qualities[i] + k)); | ||
|
||
Fiddles[j + k] = fiddle; | ||
|
||
var twiddle = Complex.FromPolarCoordinates( | ||
1.0, +2.0 * Math.PI * (Qualities[i] + k) / Periods[i]); | ||
|
||
Twiddles[j + k] = twiddle; | ||
} | ||
} | ||
} | ||
|
||
public void Reset() | ||
{ | ||
for (int i = 0; i < Inputs.Count; i++) | ||
{ | ||
Inputs[i] = 0; | ||
} | ||
|
||
for (int i = 0; i < Outputs.Length; i++) | ||
{ | ||
Outputs[i] = 0; | ||
} | ||
} | ||
|
||
public void Analyze(double sample, Span<Complex> dft) | ||
{ | ||
if (dft.Length != Length) | ||
{ | ||
throw new ArgumentException(); | ||
} | ||
|
||
var periods = Periods; | ||
var offsets = Offsets; | ||
var weights = Weights; | ||
|
||
var fiddles = Fiddles; | ||
var twiddles = Twiddles; | ||
|
||
var inputs = Inputs; | ||
var outputs = Outputs; | ||
|
||
var a = Window.Item1; | ||
var b = Window.Item2 / 2; | ||
|
||
inputs.RemoveFromFront(); | ||
inputs.AddToBack(sample); | ||
|
||
for (int i = 0, j = 1; i < Length; ++i, j+=3) | ||
{ | ||
var period = periods[i]; | ||
var offset = offsets[i]; | ||
var weight = weights[i]; | ||
|
||
var left = inputs[offset + period]; | ||
var right = inputs[offset]; | ||
|
||
var k1 = j - 1; | ||
var k2 = j; | ||
var k3 = j + 1; | ||
|
||
var delta1 = (fiddles[k1] * left - right) * weight; | ||
var delta2 = (fiddles[k2] * left - right) * weight; | ||
var delta3 = (fiddles[k3] * left - right) * weight; | ||
|
||
outputs[k1] = twiddles[k1] * (outputs[k1] + delta1); | ||
outputs[k2] = twiddles[k2] * (outputs[k2] + delta2); | ||
outputs[k3] = twiddles[k3] * (outputs[k3] + delta3); | ||
|
||
dft[i] = outputs[k2] * a + (outputs[k1] + outputs[k3]) * b; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
struct Spectrogram | ||
{ | ||
public double Samplerate; | ||
public double[] Timestamps; | ||
public double[] Frequencies; | ||
public float[,] Magnitudes; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Numerics; | ||
|
||
class SpectrumAnalyzer | ||
{ | ||
public double Samplerate { get; private set; } | ||
public double Hopsize { get; private set; } | ||
|
||
private readonly QDFT QDFT; | ||
|
||
public SpectrumAnalyzer(double samplerate, double hopsize) | ||
{ | ||
Samplerate = samplerate; | ||
Hopsize = hopsize; | ||
|
||
QDFT = new QDFT(samplerate, (100, 10000), 12 * 4); | ||
} | ||
|
||
public Spectrogram GetSpectrogram(Span<float> samples) | ||
{ | ||
var watch = Stopwatch.GetTimestamp(); | ||
|
||
var qdft = QDFT; | ||
|
||
var hop = (int)Math.Ceiling(1.0 * Hopsize * Samplerate); | ||
var hops = (int)Math.Ceiling(1.0 * samples.Length / hop); | ||
var bins = qdft.Length; | ||
|
||
var timestamps = Enumerable.Range(0, hops).Select(_ => _ * Hopsize).ToArray(); | ||
var frequencies = qdft.Frequencies; | ||
var magnitudes = new float[hops, bins]; | ||
var dft = new Complex[bins]; | ||
|
||
qdft.Reset(); | ||
|
||
for (var i = 0; i < samples.Length; i++) | ||
{ | ||
qdft.Analyze(samples[i], dft); | ||
|
||
if (i % hop != 0) | ||
continue; | ||
|
||
var t = i / hop; | ||
|
||
for (var j = 0; j < bins; j++) | ||
{ | ||
var magnitude = dft[j].Magnitude; | ||
|
||
magnitude = 20.0 * Math.Log10( | ||
magnitude + double.Epsilon); | ||
|
||
magnitudes[t, j] = (float)magnitude; | ||
} | ||
} | ||
|
||
System.Console.WriteLine($"{Stopwatch.GetElapsedTime(watch).Milliseconds}ms"); | ||
|
||
return new Spectrogram | ||
{ | ||
Samplerate = Samplerate, | ||
Timestamps = timestamps, | ||
Frequencies = frequencies, | ||
Magnitudes = magnitudes | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using OxyPlot; | ||
|
||
class SpectrogramImage | ||
{ | ||
public (double, double) Limit { get; private set; } | ||
public double Gamma { get; private set; } | ||
public double Saturation { get; private set; } | ||
|
||
public SpectrogramImage((double, double) limit, double gamma = 1, double saturation = 1) | ||
{ | ||
Limit = limit; | ||
Gamma = gamma; | ||
Saturation = saturation; | ||
} | ||
|
||
public OxyImage GetImage(Spectrogram spectrogram) | ||
{ | ||
var data = spectrogram.Magnitudes; | ||
|
||
var m = data.GetLength(0); | ||
var n = data.GetLength(1); | ||
|
||
var min = Limit.Item1; | ||
var max = Limit.Item2; | ||
|
||
var slope = 1.0 / (max - min); | ||
var intercept = min / (max - min); | ||
|
||
var colors = Enumerable.Range(0, n).Select(_ => 1.0 - (double)_ / n).ToArray(); | ||
var gamma = Math.Clamp(Gamma, 0.1, 10); | ||
var saturation = (Saturation >= 0) ? Math.Clamp(Saturation, 0.0, 1.0) : 1.0; | ||
var highlighting = (Saturation < 0) ? Math.Clamp(Saturation, -1.0, 0.0) : 0.0; | ||
|
||
var pixels = new OxyColor[m, n]; | ||
|
||
for (var i = 0; i < m; i++) | ||
{ | ||
for (var j = 0; j < n; j++) | ||
{ | ||
var h = colors[j]; | ||
var s = saturation; | ||
var v = (double)data[i, j]; | ||
|
||
v = Math.Clamp(v * slope - intercept, 0.0, 1.0); | ||
v = Math.Pow(v, gamma); | ||
|
||
s += v * highlighting; | ||
|
||
pixels[i, j] = OxyColor.FromHsv(h, s, v); | ||
} | ||
} | ||
|
||
return OxyImage.Create(pixels, ImageFormat.Bmp); | ||
} | ||
} |
Oops, something went wrong.