init
This commit is contained in:
commit
e58a138bea
176 files changed
+6194
No files matched your search
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 499e69e03e0bbf94eadfd1407e650d06
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CSCore.DSP;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// BasicSpectrumProvider
|
||||
/// </summary>
|
||||
public class BasicSpectrumProvider : FftProvider, ISpectrumProvider
|
||||
{
|
||||
private readonly int _sampleRate;
|
||||
private readonly List<object> _contexts = new List<object>();
|
||||
|
||||
public BasicSpectrumProvider(int channels, int sampleRate, FftSize fftSize)
|
||||
: base(channels, fftSize)
|
||||
{
|
||||
if (sampleRate <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sampleRate));
|
||||
}
|
||||
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public int GetFftBandIndex(float frequency)
|
||||
{
|
||||
int fftSize = (int)FftSize;
|
||||
double f = _sampleRate / 2.0;
|
||||
// ReSharper disable once PossibleLossOfFraction
|
||||
return (int)((frequency / f) * (fftSize / 2));
|
||||
}
|
||||
|
||||
public bool GetFftData(float[] fftResultBuffer, object context)
|
||||
{
|
||||
if (_contexts.Contains(context))
|
||||
return false;
|
||||
|
||||
_contexts.Add(context);
|
||||
GetFftData(fftResultBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Add(float[] samples, int count)
|
||||
{
|
||||
base.Add(samples, count);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
_contexts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Add(float left, float right)
|
||||
{
|
||||
base.Add(left, right);
|
||||
_contexts.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ac978f06ccd8f943ad7c7d03b6229bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using CSCore;
|
||||
using CSCore.DSP;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public class BiQuadFilterSource : SampleAggregatorBase
|
||||
{
|
||||
private readonly object _lockObject = new object();
|
||||
private BiQuad _biQuad;
|
||||
|
||||
public BiQuad Filter
|
||||
{
|
||||
get => _biQuad;
|
||||
set
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_biQuad = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BiQuadFilterSource(ISampleSource source) : base(source)
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(float[] buffer, int offset, int count)
|
||||
{
|
||||
var read = base.Read(buffer, offset, count);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (Filter != null)
|
||||
{
|
||||
for (int i = 0; i < read; i++)
|
||||
{
|
||||
buffer[i + offset] = Filter.Process(buffer[i + offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce8b1509cfd7d26459241de2dc788b5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
internal interface ISpectrumProvider
|
||||
{
|
||||
bool GetFftData(float[] fftBuffer, object context);
|
||||
int GetFftBandIndex(float frequency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b3d8384de325694c8a3a05f5c22b115
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CSCore.DSP;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
internal class LineSpectrum : SpectrumBase
|
||||
{
|
||||
public int BarCount
|
||||
{
|
||||
get => SpectrumResolution;
|
||||
set => SpectrumResolution = value;
|
||||
}
|
||||
|
||||
public LineSpectrum(FftSize fftSize, int minFrequency, int maxFrequency)
|
||||
: base(minFrequency, maxFrequency)
|
||||
{
|
||||
FftSize = fftSize;
|
||||
}
|
||||
|
||||
public float[] GetSpectrumData(double maxValue)
|
||||
{
|
||||
// Get spectrum data internal
|
||||
var fftBuffer = new float[(int)FftSize];
|
||||
|
||||
UpdateFrequencyMapping();
|
||||
|
||||
if (SpectrumProvider.GetFftData(fftBuffer, this))
|
||||
{
|
||||
SpectrumPointData[] spectrumPoints = CalculateSpectrumPoints(maxValue, fftBuffer);
|
||||
|
||||
// Convert to float[]
|
||||
List<float> spectrumData = new List<float>();
|
||||
spectrumPoints.ToList().ForEach(point => spectrumData.Add((float)point.Value));
|
||||
return spectrumData.ToArray();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2567baf275265a478107376fdd44c99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public enum ScalingStrategy
|
||||
{
|
||||
Decibel,
|
||||
Linear,
|
||||
Sqrt
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a5498d82d260dc48a788da725b7d4c7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using CSCore;
|
||||
using CSCore.DSP;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
internal abstract class SpectrumBase
|
||||
{
|
||||
private const int ScaleFactorLinear = 9;
|
||||
protected const int ScaleFactorSqr = 2;
|
||||
protected const double MinDbValue = -90;
|
||||
protected const double MaxDbValue = 0;
|
||||
protected const double DbScale = (MaxDbValue - MinDbValue);
|
||||
|
||||
private int _fftSize;
|
||||
private bool _isXLogScale;
|
||||
private int _maxFftIndex;
|
||||
private int _minFrequency;
|
||||
private int _minimumFrequencyIndex;
|
||||
private int _maxFrequency;
|
||||
private int _maximumFrequencyIndex;
|
||||
private int[] _spectrumIndexMax;
|
||||
private int[] _spectrumLogScaleIndexMax;
|
||||
private ISpectrumProvider _spectrumProvider;
|
||||
|
||||
protected int SpectrumResolution;
|
||||
private bool _useAverage;
|
||||
|
||||
[Browsable(false)]
|
||||
public ISpectrumProvider SpectrumProvider
|
||||
{
|
||||
get => _spectrumProvider;
|
||||
set => _spectrumProvider = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
public bool IsXLogScale
|
||||
{
|
||||
get => _isXLogScale;
|
||||
set
|
||||
{
|
||||
_isXLogScale = value;
|
||||
UpdateFrequencyMapping();
|
||||
}
|
||||
}
|
||||
|
||||
public ScalingStrategy ScalingStrategy { get; set; }
|
||||
|
||||
public bool UseAverage
|
||||
{
|
||||
get => _useAverage;
|
||||
set => _useAverage = value;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public FftSize FftSize
|
||||
{
|
||||
get => (FftSize) _fftSize;
|
||||
protected set
|
||||
{
|
||||
if ((int) Math.Log((int) value, 2) % 1 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
_fftSize = (int) value;
|
||||
_maxFftIndex = _fftSize / 2 - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public SpectrumBase(int minFrequency, int maxFrequency)
|
||||
{
|
||||
_minFrequency = minFrequency;
|
||||
_maxFrequency = maxFrequency;
|
||||
}
|
||||
|
||||
protected virtual void UpdateFrequencyMapping()
|
||||
{
|
||||
_minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(_minFrequency), _maxFftIndex);
|
||||
_maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(_maxFrequency) + 1, _maxFftIndex);
|
||||
|
||||
var actualResolution = SpectrumResolution;
|
||||
|
||||
var indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex;
|
||||
var linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3);
|
||||
|
||||
_spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true);
|
||||
_spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true);
|
||||
|
||||
var maxLog = Math.Log(actualResolution, actualResolution);
|
||||
|
||||
for (int i = 1; i < actualResolution; i++)
|
||||
{
|
||||
var logIndex =
|
||||
(int) ((maxLog - Math.Log((actualResolution + 1) - i, (actualResolution + 1))) * indexCount) +
|
||||
_minimumFrequencyIndex;
|
||||
|
||||
_spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize);
|
||||
_spectrumLogScaleIndexMax[i - 1] = logIndex;
|
||||
}
|
||||
|
||||
if (actualResolution > 0)
|
||||
{
|
||||
_spectrumIndexMax[_spectrumIndexMax.Length - 1] =
|
||||
_spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer)
|
||||
{
|
||||
var dataPoints = new List<SpectrumPointData>();
|
||||
|
||||
double value0 = 0, value = 0;
|
||||
double lastValue = 0;
|
||||
double actualMaxValue = maxValue;
|
||||
var spectrumPointIndex = 0;
|
||||
|
||||
for (int i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++)
|
||||
{
|
||||
switch (ScalingStrategy)
|
||||
{
|
||||
case ScalingStrategy.Decibel:
|
||||
value0 = (((20 * Math.Log10(fftBuffer[i])) - MinDbValue) / DbScale) * actualMaxValue;
|
||||
break;
|
||||
case ScalingStrategy.Linear:
|
||||
value0 = (fftBuffer[i] * ScaleFactorLinear) * actualMaxValue;
|
||||
break;
|
||||
case ScalingStrategy.Sqrt:
|
||||
value0 = ((Math.Sqrt(fftBuffer[i])) * ScaleFactorSqr) * actualMaxValue;
|
||||
break;
|
||||
}
|
||||
|
||||
var recalc = true;
|
||||
|
||||
value = Math.Max(0, Math.Max(value0, value));
|
||||
|
||||
while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 &&
|
||||
i ==
|
||||
(IsXLogScale
|
||||
? _spectrumLogScaleIndexMax[spectrumPointIndex]
|
||||
: _spectrumIndexMax[spectrumPointIndex]))
|
||||
{
|
||||
if (!recalc)
|
||||
{
|
||||
value = lastValue;
|
||||
}
|
||||
|
||||
if (value > maxValue)
|
||||
{
|
||||
value = maxValue;
|
||||
}
|
||||
|
||||
if (_useAverage && spectrumPointIndex > 0)
|
||||
{
|
||||
value = (lastValue + value) / 2.0;
|
||||
}
|
||||
|
||||
dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value});
|
||||
|
||||
lastValue = value;
|
||||
value = 0.0;
|
||||
spectrumPointIndex++;
|
||||
recalc = false;
|
||||
}
|
||||
}
|
||||
|
||||
return dataPoints.ToArray();
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{" + nameof(Value) + "}")]
|
||||
protected struct SpectrumPointData
|
||||
{
|
||||
public int SpectrumPointIndex;
|
||||
public double Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fbdbf5c48dee7b4da3f946503b29f80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public class SpectrumSmoother
|
||||
{
|
||||
private long _iteration;
|
||||
|
||||
private readonly int _spectrumSize;
|
||||
private readonly float[] _smoothedSpectrum;
|
||||
private readonly List<float[]> _spectrumHistory = new List<float[]>();
|
||||
|
||||
public byte SmoothingIterations { get; }
|
||||
|
||||
public SpectrumSmoother(int spectrumSize, byte smoothingIterations)
|
||||
{
|
||||
_spectrumSize = spectrumSize;
|
||||
|
||||
SmoothingIterations = smoothingIterations;
|
||||
|
||||
_smoothedSpectrum = new float[_spectrumSize];
|
||||
|
||||
for (int i = 0; i < _spectrumSize; i++)
|
||||
{
|
||||
_spectrumHistory.Add(new float[SmoothingIterations]);
|
||||
}
|
||||
}
|
||||
|
||||
public float[] GetSpectrumData(float[] spectrum)
|
||||
{
|
||||
_iteration++;
|
||||
|
||||
// Record and average last N frames
|
||||
for (var i = 0; i < _spectrumSize; i++)
|
||||
{
|
||||
var historyIndex = _iteration % SmoothingIterations;
|
||||
|
||||
var audioData = spectrum[i];
|
||||
_spectrumHistory[i][historyIndex] = audioData;
|
||||
|
||||
_smoothedSpectrum[i] = _spectrumHistory[i].Average();
|
||||
}
|
||||
|
||||
return _smoothedSpectrum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dd5954917d385d458671783cb3579ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using Assets.WasapiAudio.Scripts.Unity;
|
||||
using CSCore;
|
||||
using CSCore.CoreAudioAPI;
|
||||
using CSCore.DSP;
|
||||
using CSCore.SoundIn;
|
||||
using CSCore.Streams;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public class WasapiAudio
|
||||
{
|
||||
private const FftSize CFftSize = FftSize.Fft4096;
|
||||
private const float MaxAudioValue = 1.0f;
|
||||
|
||||
private readonly WasapiCaptureType _captureType;
|
||||
private readonly int _spectrumSize;
|
||||
private readonly ScalingStrategy _scalingStrategy;
|
||||
private readonly int _minFrequency;
|
||||
private readonly int _maxFrequency;
|
||||
|
||||
private WasapiCapture _wasapiCapture;
|
||||
private SoundInSource _soundInSource;
|
||||
private IWaveSource _realtimeSource;
|
||||
private BasicSpectrumProvider _basicSpectrumProvider;
|
||||
private LineSpectrum _lineSpectrum;
|
||||
private SingleBlockNotificationStream _singleBlockNotificationStream;
|
||||
private WasapiAudioFilter[] _filters;
|
||||
private Action<float[]> _receiveAudio;
|
||||
|
||||
public WasapiAudio(WasapiCaptureType captureType, int spectrumSize, ScalingStrategy scalingStrategy, int minFrequency, int maxFrequency, WasapiAudioFilter[] filters, Action<float[]> receiveAudio)
|
||||
{
|
||||
_captureType = captureType;
|
||||
_spectrumSize = spectrumSize;
|
||||
_scalingStrategy = scalingStrategy;
|
||||
_minFrequency = minFrequency;
|
||||
_maxFrequency = maxFrequency;
|
||||
_filters = filters;
|
||||
_receiveAudio = receiveAudio;
|
||||
}
|
||||
|
||||
public void StartListen()
|
||||
{
|
||||
switch (_captureType)
|
||||
{
|
||||
case WasapiCaptureType.Loopback:
|
||||
_wasapiCapture = new WasapiLoopbackCapture();
|
||||
break;
|
||||
case WasapiCaptureType.Microphone:
|
||||
MMDevice defaultMicrophone;
|
||||
using (var deviceEnumerator = new MMDeviceEnumerator())
|
||||
{
|
||||
defaultMicrophone = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications);
|
||||
}
|
||||
_wasapiCapture = new WasapiCapture();
|
||||
_wasapiCapture.Device = defaultMicrophone;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unhandled WasapiCaptureType");
|
||||
}
|
||||
|
||||
_wasapiCapture.Initialize();
|
||||
|
||||
_soundInSource = new SoundInSource(_wasapiCapture);
|
||||
|
||||
_basicSpectrumProvider = new BasicSpectrumProvider(_soundInSource.WaveFormat.Channels, _soundInSource.WaveFormat.SampleRate, CFftSize);
|
||||
|
||||
_lineSpectrum = new LineSpectrum(CFftSize, _minFrequency, _maxFrequency)
|
||||
{
|
||||
SpectrumProvider = _basicSpectrumProvider,
|
||||
BarCount = _spectrumSize,
|
||||
UseAverage = true,
|
||||
IsXLogScale = true,
|
||||
ScalingStrategy = _scalingStrategy
|
||||
};
|
||||
|
||||
_wasapiCapture.Start();
|
||||
|
||||
var sampleSource = _soundInSource.ToSampleSource();
|
||||
|
||||
if (_filters != null && _filters.Length > 0)
|
||||
{
|
||||
foreach (var filter in _filters)
|
||||
{
|
||||
sampleSource = sampleSource.AppendSource(x => new BiQuadFilterSource(x));
|
||||
var biQuadSource = (BiQuadFilterSource) sampleSource;
|
||||
|
||||
switch (filter.Type)
|
||||
{
|
||||
case WasapiAudioFilterType.LowPass:
|
||||
biQuadSource.Filter = new LowpassFilter(_soundInSource.WaveFormat.SampleRate, filter.Frequency);
|
||||
break;
|
||||
case WasapiAudioFilterType.HighPass:
|
||||
biQuadSource.Filter = new HighpassFilter(_soundInSource.WaveFormat.SampleRate, filter.Frequency);
|
||||
break;
|
||||
case WasapiAudioFilterType.BandPass:
|
||||
biQuadSource.Filter = new BandpassFilter(_soundInSource.WaveFormat.SampleRate, filter.Frequency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_singleBlockNotificationStream = new SingleBlockNotificationStream(sampleSource);
|
||||
_realtimeSource = _singleBlockNotificationStream.ToWaveSource();
|
||||
|
||||
var buffer = new byte[_realtimeSource.WaveFormat.BytesPerSecond / 2];
|
||||
|
||||
_soundInSource.DataAvailable += (s, ea) =>
|
||||
{
|
||||
while (_realtimeSource.Read(buffer, 0, buffer.Length) > 0)
|
||||
{
|
||||
var spectrumData = _lineSpectrum.GetSpectrumData(MaxAudioValue);
|
||||
|
||||
if (spectrumData != null)
|
||||
{
|
||||
_receiveAudio?.Invoke(spectrumData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_singleBlockNotificationStream.SingleBlockRead += SingleBlockNotificationStream_SingleBlockRead;
|
||||
}
|
||||
|
||||
public void StopListen()
|
||||
{
|
||||
_singleBlockNotificationStream.SingleBlockRead -= SingleBlockNotificationStream_SingleBlockRead;
|
||||
|
||||
_soundInSource.Dispose();
|
||||
_realtimeSource.Dispose();
|
||||
_receiveAudio = null;
|
||||
_wasapiCapture.Stop();
|
||||
_wasapiCapture.Dispose();
|
||||
}
|
||||
|
||||
private void SingleBlockNotificationStream_SingleBlockRead(object sender, SingleBlockReadEventArgs e)
|
||||
{
|
||||
_basicSpectrumProvider.Add(e.Left, e.Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3c269a428d97534b89ae32d45f9658a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public enum WasapiAudioFilterType
|
||||
{
|
||||
LowPass,
|
||||
HighPass,
|
||||
BandPass,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WasapiAudioFilter
|
||||
{
|
||||
public WasapiAudioFilterType Type;
|
||||
public int Frequency;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 512a31747c43f524b9317c43013b7f98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Assets.WasapiAudio.Scripts.Core
|
||||
{
|
||||
public enum WasapiCaptureType
|
||||
{
|
||||
Loopback,
|
||||
Microphone
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 503e87bd4ebdf5b498b2683bb0bba127
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7d31d84c01ffe24eb0e36653e03832a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
public abstract class AudioVisualizationEffect : MonoBehaviour
|
||||
{
|
||||
// Inspector Properties
|
||||
public WasapiAudioSource WasapiAudioSource;
|
||||
|
||||
[SerializeReference]
|
||||
[SerializeReferenceButton]
|
||||
public List<SpectrumTransformer> Transformers = new List<SpectrumTransformer>();
|
||||
|
||||
[SpectrumDataPreview]
|
||||
public SpectrumData Preview;
|
||||
|
||||
protected int SpectrumSize { get; private set; }
|
||||
|
||||
public virtual void Awake()
|
||||
{
|
||||
if (WasapiAudioSource == null)
|
||||
{
|
||||
Debug.Log("You must set a WasapiAudioSource");
|
||||
return;
|
||||
}
|
||||
|
||||
SpectrumSize = WasapiAudioSource.SpectrumSize;
|
||||
Preview = new SpectrumData();
|
||||
}
|
||||
|
||||
public float[] GetSpectrumData()
|
||||
{
|
||||
if (WasapiAudioSource == null)
|
||||
{
|
||||
Debug.Log("You must set a WasapiAudioSource");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get raw / unmodified spectrum data
|
||||
var spectrumData = WasapiAudioSource.GetSpectrumData();
|
||||
|
||||
// Run spectrum data through all configured transformers
|
||||
if (Transformers != null && Transformers.Count > 0)
|
||||
{
|
||||
foreach (var transformer in Transformers)
|
||||
{
|
||||
spectrumData = transformer.Transform(spectrumData);
|
||||
}
|
||||
}
|
||||
|
||||
Preview.Values = spectrumData;
|
||||
|
||||
return spectrumData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba1b44ffd6d8856448e4ae29b67ca69d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
//using UnityEngine;
|
||||
|
||||
//namespace Assets.WasapiAudio.Scripts.Unity
|
||||
//{
|
||||
// [CreateAssetMenu(fileName = "Audio Visualization Profile", menuName = "Audio/Audio Visualization Profile", order = 1)]
|
||||
// public class AudioVisualizationProfile : ScriptableObject
|
||||
// {
|
||||
// // Inspector Fields
|
||||
// public AnimationCurve ScaleCurve;
|
||||
// [Range(1, 50)]
|
||||
// public int AudioSmoothingIterations = 5;
|
||||
// [Range(1, 2)]
|
||||
// public float MinMaxThreshold = 1.2f;
|
||||
// public float MinScale = 0.0f;
|
||||
// public float MaxScale = 1.0f;
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ceabdd331bee1a8459e8b85d942a772f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
public abstract class AudioVisualizationSpectrumBandEffect : AudioVisualizationEffect
|
||||
{
|
||||
// Inspector properties
|
||||
public int SpectrumBand;
|
||||
|
||||
protected float GetSpectrumBandData()
|
||||
{
|
||||
return GetSpectrumData()[SpectrumBand];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b411fd36109dfe746be0478532cc7bc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
[Serializable]
|
||||
public class SpectrumData
|
||||
{
|
||||
public float[] Values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4ddc80a206711b4b8c119fec4626751
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class SpectrumDataPreviewAttribute : PropertyAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87b43e36cb5d1a64bb463e84f5e104a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class SpectrumTransformer
|
||||
{
|
||||
private float[] _input;
|
||||
private float[] _output;
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
protected float InputMin { get; private set; }
|
||||
protected float InputMax { get; private set; }
|
||||
protected float InputAverage { get; private set; }
|
||||
protected float InputRange { get; private set; }
|
||||
|
||||
public float[] Transform(float[] input)
|
||||
{
|
||||
_input = input;
|
||||
|
||||
if (_output == null || _output.Length != _input.Length)
|
||||
{
|
||||
_output = new float[_input.Length];
|
||||
}
|
||||
|
||||
InputMin = _input.Min();
|
||||
InputMax = _input.Max();
|
||||
InputAverage = _input.Average();
|
||||
InputRange = InputMax - InputMin;
|
||||
|
||||
if (Enabled)
|
||||
{
|
||||
PerformTransform(_input, ref _output);
|
||||
}
|
||||
|
||||
return Enabled ? _output : _input;
|
||||
}
|
||||
|
||||
protected abstract void PerformTransform(float[] input, ref float[] output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bf2ffb4b98288345ac7353d5ca83108
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 723fc4f6d47c6234fbe8232c91accc22
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity.Transformers
|
||||
{
|
||||
[Serializable]
|
||||
public class CurveScaledSpectrumTransformer : SpectrumTransformer
|
||||
{
|
||||
public AnimationCurve ScaleCurve;
|
||||
|
||||
protected override void PerformTransform(float[] input, ref float[] output)
|
||||
{
|
||||
if (ScaleCurve == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scaleStep = 1.0f / input.Length;
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
var scaledValue = ScaleCurve.Evaluate(scaleStep * i) * input[i];
|
||||
|
||||
output[i] = scaledValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 565fed8317271174792674bb2bc0e5af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity.Transformers
|
||||
{
|
||||
[Serializable]
|
||||
public class PercentileBoostSpectrumTransformer : SpectrumTransformer
|
||||
{
|
||||
private float[] _sortedPercentileArray;
|
||||
|
||||
[Range(0.0f, 1.0f)]
|
||||
public float Percentile = 0.5f;
|
||||
public float BelowPercentileMultiplier = 0.5f;
|
||||
public float AbovePercentileMultiplier = 2.0f;
|
||||
|
||||
protected override void PerformTransform(float[] input, ref float[] output)
|
||||
{
|
||||
if (_sortedPercentileArray == null || _sortedPercentileArray.Length != input.Length)
|
||||
{
|
||||
_sortedPercentileArray = new float[input.Length];
|
||||
}
|
||||
|
||||
input.CopyTo(_sortedPercentileArray, 0);
|
||||
|
||||
var percentile = GetPercentile(_sortedPercentileArray, Percentile);
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] < percentile)
|
||||
{
|
||||
output[i] = input[i] * BelowPercentileMultiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = input[i] * AbovePercentileMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetPercentile(float[] sequence, float percentile)
|
||||
{
|
||||
Array.Sort(sequence);
|
||||
|
||||
var length = sequence.Length;
|
||||
var n = (length - 1) * percentile + 1;
|
||||
|
||||
// Another method: double n = (N + 1) * excelPercentile;
|
||||
if (n == 1.0f)
|
||||
{
|
||||
return sequence[0];
|
||||
}
|
||||
|
||||
if (n == length)
|
||||
{
|
||||
return sequence[length - 1];
|
||||
}
|
||||
|
||||
int k = (int)n;
|
||||
float d = n - k;
|
||||
|
||||
return sequence[k - 1] + d * (sequence[k] - sequence[k - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac3cd62c87d1ef54f90e71e71fe2000b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Assets.WasapiAudio.Scripts.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity.Transformers
|
||||
{
|
||||
[Serializable]
|
||||
public class SmoothSpectrumTransformer : SpectrumTransformer
|
||||
{
|
||||
private SpectrumSmoother _smoother;
|
||||
|
||||
[Range(1, 50)]
|
||||
public byte Iterations = 1;
|
||||
|
||||
protected override void PerformTransform(float[] input, ref float[] output)
|
||||
{
|
||||
if (_smoother == null || _smoother.SmoothingIterations != Iterations)
|
||||
{
|
||||
_smoother = new SpectrumSmoother(input.Length, Iterations);
|
||||
}
|
||||
|
||||
output = _smoother.GetSpectrumData(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a86e967af4ca4f4ab907c1a6d5a77aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
using UnityEngine.VFX.Utility;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
[AddComponentMenu("VFX/Property Binders/VFX Audio Spectrum Data Binder")]
|
||||
[VFXBinder("Audio/Audio Spectrum Data to AttributeMap")]
|
||||
public class VfxAudioSpectrumDataBinder : VfxAudioVisualizationEffect
|
||||
{
|
||||
[VFXPropertyBinding("System.UInt32"), SerializeField]
|
||||
protected ExposedProperty SpectrumSizeParameter = "SpectrumSize";
|
||||
|
||||
[VFXPropertyBinding("UnityEngine.Texture2D"), SerializeField]
|
||||
protected ExposedProperty SpectrumDataTextureParameter = "SpectrumDataTexture";
|
||||
|
||||
private Texture2D _texture;
|
||||
private Color[] _colorCache;
|
||||
|
||||
private void UpdateTexture()
|
||||
{
|
||||
if (_texture == null)
|
||||
{
|
||||
_texture = new Texture2D(SpectrumSize, 1, TextureFormat.RFloat, false);
|
||||
_texture.name = $"AudioSpectrumData{SpectrumSize}";
|
||||
_colorCache = new Color[SpectrumSize];
|
||||
}
|
||||
|
||||
var spectrumData = GetSpectrumData();
|
||||
|
||||
for (int i = 0; i < SpectrumSize; i++)
|
||||
{
|
||||
_colorCache[i] = new Color(spectrumData[i], 0, 0, 0);
|
||||
}
|
||||
|
||||
_texture.SetPixels(_colorCache);
|
||||
_texture.Apply();
|
||||
}
|
||||
|
||||
public override void UpdateBinding(VisualEffect component)
|
||||
{
|
||||
base.UpdateBinding(component);
|
||||
UpdateTexture();
|
||||
component.SetUInt(SpectrumSizeParameter, (uint) SpectrumSize);
|
||||
component.SetTexture(SpectrumDataTextureParameter, _texture);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Audio Spectrum Data";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d899048990fdc6246a5f17ff39f840bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
using UnityEngine.VFX.Utility;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
public abstract class VfxAudioVisualizationEffect : VFXBinderBase
|
||||
{
|
||||
// Inspector Properties
|
||||
public WasapiAudioSource WasapiAudioSource;
|
||||
|
||||
[SerializeReference]
|
||||
[SerializeReferenceButton]
|
||||
public List<SpectrumTransformer> Transformers = new List<SpectrumTransformer>();
|
||||
|
||||
[SpectrumDataPreview]
|
||||
public SpectrumData Preview;
|
||||
|
||||
protected int SpectrumSize { get; private set; }
|
||||
|
||||
public override bool IsValid(VisualEffect component)
|
||||
{
|
||||
return WasapiAudioSource != null;
|
||||
}
|
||||
|
||||
public override void UpdateBinding(VisualEffect component)
|
||||
{
|
||||
SpectrumSize = WasapiAudioSource.SpectrumSize;
|
||||
Preview = new SpectrumData();
|
||||
}
|
||||
|
||||
protected float[] GetSpectrumData()
|
||||
{
|
||||
// Get raw / unmodified spectrum data
|
||||
var spectrumData = WasapiAudioSource.GetSpectrumData();
|
||||
|
||||
// Run spectrum data through all configured transformers
|
||||
if (Transformers != null && Transformers.Count > 0)
|
||||
{
|
||||
foreach (var transformer in Transformers)
|
||||
{
|
||||
spectrumData = transformer.Transform(spectrumData);
|
||||
}
|
||||
}
|
||||
|
||||
Preview.Values = spectrumData;
|
||||
|
||||
return spectrumData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dc0fbf002e63e148b601b689dcc5ca7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using Assets.WasapiAudio.Scripts.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.WasapiAudio.Scripts.Unity
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
public class WasapiAudioSource : MonoBehaviour
|
||||
{
|
||||
private Core.WasapiAudio _wasapiAudio;
|
||||
private float[] _spectrumData;
|
||||
|
||||
// Inspector Properties
|
||||
public WasapiCaptureType CaptureType = WasapiCaptureType.Loopback;
|
||||
public int SpectrumSize = 32;
|
||||
public ScalingStrategy ScalingStrategy = ScalingStrategy.Sqrt;
|
||||
public int MinFrequency = 100;
|
||||
public int MaxFrequency = 20000;
|
||||
public WasapiAudioFilter[] Filters;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
// Setup loopback audio and start listening
|
||||
_wasapiAudio = new Core.WasapiAudio(CaptureType, SpectrumSize, ScalingStrategy, MinFrequency, MaxFrequency, Filters, spectrumData =>
|
||||
{
|
||||
_spectrumData = spectrumData;
|
||||
});
|
||||
|
||||
_wasapiAudio.StartListen();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float[] GetSpectrumData()
|
||||
{
|
||||
return _spectrumData;
|
||||
}
|
||||
|
||||
public void OnApplicationQuit()
|
||||
{
|
||||
_wasapiAudio?.StopListen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 120ca4c4f9442e14db8c72acac5da136
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user