Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

complex FFT #23

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 40 additions & 10 deletions VL.Audio.vl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Document xmlns:p="property" xmlns:r="reflection" Id="HTX4axw4uH5NDghqAqpe1F" LanguageVersion="2023.5.2-0021-g1f5f468ca7" Version="0.128">
<NugetDependency Id="NrtwmMKDemaOAGHoKxrmNR" Location="VL.CoreLib" Version="2021.4.9" />
<Document xmlns:p="property" xmlns:r="reflection" Id="HTX4axw4uH5NDghqAqpe1F" LanguageVersion="2023.5.1" Version="0.128">
<NugetDependency Id="NrtwmMKDemaOAGHoKxrmNR" Location="VL.CoreLib" Version="2023.5.1" />
<Patch Id="Mj1seFpSbg5MtaCXVBGhJf">
<Canvas Id="ClqUpgLjNRdQbB5ajAF1ry" DefaultCategory="Audio" CanvasType="FullCategory">
<!--
Expand Down Expand Up @@ -6051,11 +6051,11 @@
<Pin Id="FFeAJsrRm9GQBg6JZqhgWM" Name="Force" Kind="InputPin" />
<Pin Id="LEfHkzAQUR1MkiFMHNl48p" Name="Dispose Cached Outputs" Kind="InputPin" />
<Pin Id="B8vRHwb4rzUPzYioesswxw" Name="Has Changed" Kind="OutputPin" />
<ControlPoint Id="Gn57CDdYlUmNcIZoKugcbT" Bounds="486,315" Alignment="Top" />
<ControlPoint Id="LflNmNaKoMTPx6uW3Nnzkv" Bounds="599,315" Alignment="Top" />
<ControlPoint Id="Fyp261j7FMXMLUDm6U6Wwb" Bounds="768,315" Alignment="Top" />
<ControlPoint Id="Si2WnTvbnAtPEmdYMhLNP9" Bounds="415,754" Alignment="Bottom" />
<ControlPoint Id="Ep2aANmPfKfP4A4WTjizCf" Bounds="404,315" Alignment="Top" />
<ControlPoint Id="Gn57CDdYlUmNcIZoKugcbT" Bounds="486,314" Alignment="Top" />
<ControlPoint Id="LflNmNaKoMTPx6uW3Nnzkv" Bounds="599,314" Alignment="Top" />
<ControlPoint Id="Fyp261j7FMXMLUDm6U6Wwb" Bounds="768,314" Alignment="Top" />
<ControlPoint Id="Si2WnTvbnAtPEmdYMhLNP9" Bounds="415,758" Alignment="Bottom" />
<ControlPoint Id="Ep2aANmPfKfP4A4WTjizCf" Bounds="404,314" Alignment="Top" />
<Patch Id="DaBhCPftxssPYou2fu2Sf0" ManuallySortedPins="true">
<Patch Id="AsbfpfuiCfVPqMrGKE1dLC" Name="Create" ManuallySortedPins="true" />
<Patch Id="DQdyCfrOvjtPVQqIUUueNS" Name="Then" ManuallySortedPins="true" />
Expand Down Expand Up @@ -6137,6 +6137,7 @@
<Pin Id="BIC0ZaQJPuzLI7jJN5TQ7I" Name="Output" Kind="StateOutputPin" />
</Node>
</Patch>
<ControlPoint Id="F6WCRhxsw4tP1zEKMZarGu" Bounds="676,758" Alignment="Bottom" />
</Node>
<ControlPoint Id="QsmIxUewDdSMcWdgnIZ1bb" Bounds="473,916" />
<ControlPoint Id="U0BR6jO0232QP1JN7zkqMJ" Bounds="514,785" />
Expand All @@ -6149,6 +6150,26 @@
<Pin Id="U2SaeSganFFP8ev3LOyX4X" Name="Value" Kind="InputPin" />
<Pin Id="KTA2XY09OxVMVSCbSlp4df" Name="Output" Kind="StateOutputPin" />
</Node>
<Node Bounds="703,896,85,26" Id="TsCOrFIte0FPlrYHnkytZH">
<p:NodeReference LastCategoryFullName="VL.Audio.FFTOutSignal" LastDependency="VL.Audio.dll">
<Choice Kind="NodeFlag" Name="Node" Fixed="true" />
<Choice Kind="OperationCallFlag" Name="FFTOutImagVal" />
</p:NodeReference>
<Pin Id="Dqi1TZVypsjMfULZ4rvhQF" Name="Input" Kind="StateInputPin" />
<Pin Id="LzsoPqdENUKLZZk7rh8Bmb" Name="Output" Kind="StateOutputPin" />
<Pin Id="CIysctfe8QTOy8aX93qUGU" Name="FFTOut Imag Val" Kind="OutputPin" />
</Node>
<Node Bounds="829,900,82,26" Id="Eb8xL4v2aJ3M3H0uByIAUV">
<p:NodeReference LastCategoryFullName="VL.Audio.FFTOutSignal" LastDependency="VL.Audio.dll">
<Choice Kind="NodeFlag" Name="Node" Fixed="true" />
<Choice Kind="OperationCallFlag" Name="FFTOutRealVal" />
</p:NodeReference>
<Pin Id="R8phkGfC50wQaU84QNPlIv" Name="Input" Kind="StateInputPin" />
<Pin Id="O3pMvRYEMEaPhrmdmUShcS" Name="Output" Kind="StateOutputPin" />
<Pin Id="UoS67x1glTiMEtLtdk0Cdd" Name="FFTOut Real Val" Kind="OutputPin" />
</Node>
<ControlPoint Id="VOkB5fk7UbqQSaJ6mdcwwq" Bounds="777,954" />
<ControlPoint Id="GksPxEYZdcPLpqVjwRDnF1" Bounds="903,949" />
</Canvas>
<ProcessDefinition Id="RkjxTwKuXCAP8l5Dal04mm">
<Fragment Id="AdUlmBj3ei3LBP7NFEsuTe" Patch="Lt6hp5z1A2LMaoIHRIuSQH" Enabled="true" />
Expand Down Expand Up @@ -6213,7 +6234,16 @@
<Choice Kind="TypeFlag" Name="IReadOnlyList" />
</p:TypeAnnotation>
</Pin>
<Pin Id="NrVkdhSFJIWPgR2xApPw0K" Name="FFTOut Imag Val" Kind="OutputPin" />
<Pin Id="G7mrTzYy18eNXcJ9efcOYx" Name="FFTOut Real Val" Kind="OutputPin" />
</Patch>
<Link Id="LesXWLPG16KLnci37Ft8K6" Ids="VOkB5fk7UbqQSaJ6mdcwwq,NrVkdhSFJIWPgR2xApPw0K" IsHidden="true" />
<Link Id="HJUFUbdENZ4PT2nlyvrlSf" Ids="CIysctfe8QTOy8aX93qUGU,VOkB5fk7UbqQSaJ6mdcwwq" />
<Link Id="CPESl54zFuPOZSf3cdrY00" Ids="GksPxEYZdcPLpqVjwRDnF1,G7mrTzYy18eNXcJ9efcOYx" IsHidden="true" />
<Link Id="FupPwgRjYWdN90WhBiVltl" Ids="UoS67x1glTiMEtLtdk0Cdd,GksPxEYZdcPLpqVjwRDnF1" />
<Link Id="UANkLdx7FHrOHjtUQQOspR" Ids="EmXaxemUI1YME0KvudtnML,F6WCRhxsw4tP1zEKMZarGu" />
<Link Id="RvszISPk4FlMXCqJn6hi4d" Ids="F6WCRhxsw4tP1zEKMZarGu,Dqi1TZVypsjMfULZ4rvhQF" />
<Link Id="KNfvY6K8Hp2NM3bEWKriMc" Ids="F6WCRhxsw4tP1zEKMZarGu,R8phkGfC50wQaU84QNPlIv" />
</Patch>
</Node>
</Canvas>
Expand Down Expand Up @@ -8904,7 +8934,7 @@
</Node>
</Canvas>
</Canvas>
<Pad Id="NoaHvu678xqPBpDjpGsGQv" Bounds="667,122,169,21" ShowValueBox="true" isIOBox="true" Value="Buffer and Sample region">
<Pad Id="NoaHvu678xqPBpDjpGsGQv" Bounds="667,122,159,19" ShowValueBox="true" isIOBox="true" Value="Buffer and Sample region">
<p:TypeAnnotation>
<Choice Kind="TypeFlag" Name="String" />
</p:TypeAnnotation>
Expand Down Expand Up @@ -16961,7 +16991,7 @@
</Patch>
</Patch>
</Node>
<Pad Id="BZEa0dGC2kGL3tvqKZ94XL" Bounds="537,612,156,40" ShowValueBox="true" isIOBox="true" Value="todo: &#xD;&#xA;- create a VST factory">
<Pad Id="BZEa0dGC2kGL3tvqKZ94XL" Bounds="537,612,139,37" ShowValueBox="true" isIOBox="true" Value="todo: &#xD;&#xA;- create a VST factory">
<p:TypeAnnotation>
<Choice Kind="TypeFlag" Name="String" />
</p:TypeAnnotation>
Expand All @@ -16970,7 +17000,7 @@
<p:stringtype p:Assembly="VL.Core" p:Type="VL.Core.StringType">Comment</p:stringtype>
</p:ValueBoxSettings>
</Pad>
<Pad Id="MU4LoZ56mz0LZ2Sv3XvZRt" Bounds="539,653,254,19" ShowValueBox="true" isIOBox="true" Value="https://github.com/obiwanjacobi/vst.net">
<Pad Id="MU4LoZ56mz0LZ2Sv3XvZRt" Bounds="539,653,140,19" ShowValueBox="true" isIOBox="true" Value="https://github.com/obiwanjacobi/vst.net">
<p:TypeAnnotation>
<Choice Kind="TypeFlag" Name="String" />
</p:TypeAnnotation>
Expand Down
2 changes: 2 additions & 0 deletions src/Nodes/NodeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public NodeFactory(string directory = default, string directoryToWatch = default
Directory = directory;
DirectoryToWatch = directoryToWatch;


var builder = ImmutableArray.CreateBuilder<IVLNodeDescription>();
//sources
builder.Add(new NodeDescription(this, typeof(ADSRSignal), "ADSR", "Source", "Generates an ADSR envelope in 0..1 range", "", "envelope"));
Expand All @@ -30,6 +31,7 @@ public NodeFactory(string directory = default, string directoryToWatch = default
builder.Add(new NodeDescription(this, typeof(ValueToAudioSignal), "V2A", "Source", "Converts a value into a static audio signal", "", ""));
builder.Add(new NodeDescription(this, typeof(GranulatorSignal), "Granulator2 (Internal)", "Source", "Reads grains from an audio file", "", "granular synthesis"));
builder.Add(new NodeDescription(this, typeof(IFFTSignal), "IFFT", "Source", "Creates an audio signal from spectrum data", "", "additive synthesis inverse"));
builder.Add(new NodeDescription(this, typeof(IFFTSignalDouble), "IFFTDouble", "Source", "Creates an audio signal from spectrum data", "", "additive synthesis inverse"));
builder.Add(new NodeDescription(this, typeof(ValueSequenceSignal), "ValueSequence2 (Internal)", "Source", "Generates a sequence of values which are played back as an audio signal", "", "sequencer clip loop"));

//filter
Expand Down
6 changes: 6 additions & 0 deletions src/Signals/Sinks/FFTOutSignal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public WindowFunction WindowFunc
double[] FFFTBuffer = new double[1];
Complex[] FFFTComplexBuffer = new Complex[1];
public float[] FFTOut = new float[2];
public double[] FFTOutRealVal = new double[2];
public double[] FFTOutImagVal = new double[2];
double[] FWindow = new double[1];
private float FdBRange;

Expand All @@ -87,6 +89,8 @@ protected override void FillBuffer(float[] buffer, int offset, int count)
FFFTBuffer = new double[fftSize];
FFFTComplexBuffer = new Complex[fftSize];
FFTOut = new float[fftSize/2];
FFTOutRealVal = new double[fftSize / 2];
FFTOutImagVal = new double[fftSize / 2];
FWindow = AudioUtils.CreateWindowDouble(fftSize, WindowFunc);
}

Expand All @@ -113,6 +117,8 @@ void CalcFFT(float[] ringbufferData)
var lastValue = FFTOut[n];
var newValue = (float)Decibels.LinearToDecibels(Math.Max(complex[n].MagnitudeSquared, FMindB)) / FdBRange + 1;
FFTOut[n] = newValue * (1 - Smoothing) + lastValue * Smoothing;
FFTOutRealVal[n] = complex[n].Real;
FFTOutImagVal[n] = complex[n].Imaginary;
}
}
}
Expand Down
167 changes: 167 additions & 0 deletions src/Signals/Sources/IFFTSignalDouble.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Created by SharpDevelop.
* User: TF
* Date: 02.05.2015
* Time: 12:09
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
using System;
using System.Collections.Generic;
using Lomont;
using VL.Lib.Collections;

namespace VL.Audio
{
public class IFFTPullBufferDouble : CircularPullBuffer
{
public IFFTPullBufferDouble(SigParam<IReadOnlyList<double>> fftDataReal, SigParam<IReadOnlyList<double>> fftDataImag, int size, WindowFunction window)
: base(size)
{
FFFTDataReal = fftDataReal;
FFFTDataImag = fftDataImag;
RealImagData = new double[size];
TimeDomain = new float[size];
Window = AudioUtils.CreateWindowDouble(size, window);
WindowFunc = window;
WindowFunc = window;
PullCount = size;
}

readonly SigParam<IReadOnlyList<double>> FFFTDataReal;
readonly SigParam<IReadOnlyList<double>> FFFTDataImag;
readonly double[] RealImagData;
readonly double[] Window;
public readonly WindowFunction WindowFunc;
readonly float[] TimeDomain;
readonly LomontFFT FFFT = new LomontFFT();

public double Gain = 1;

public override void Pull(int count)
{
var real = FFFTDataReal.Value ?? Spread<double>.Empty;
var imag = FFFTDataImag.Value ?? Spread<double>.Empty;

var copyCount = Math.Min(count / 2, Math.Max(real.Count, imag.Count));
var j = 0;

if (real.Count > imag.Count)
{
if (imag.Count == 0)
{
for (int i = 0; i < copyCount; i++)
{
RealImagData[j++] = real[i];
RealImagData[j++] = 0.0;
}
}
else
{
for (int i = 0; i < copyCount; i++)
{
RealImagData[j++] = real[i];
RealImagData[j++] = imag[AudioUtils.Zmod(i, imag.Count)];
}
}
}
else if(real?.Count == imag?.Count)
{
for (int i = 0; i < copyCount; i++)
{
RealImagData[j++] = real[i];
RealImagData[j++] = imag[i];
}
}
else
{
if (real.Count == 0)
{
for (int i = 0; i < copyCount; i++)
{
RealImagData[j++] = 0.0;
RealImagData[j++] = imag[i];
}
}
else
{
for (int i = 0; i < copyCount; i++)
{
RealImagData[j++] = real[AudioUtils.Zmod(i, real.Count)];
RealImagData[j++] = imag[i];
}
}
}

if(copyCount < count/2)
{

for (int i = copyCount; i < count/2; i++)
{
RealImagData[j++] = 0;
RealImagData[j++] = 0;
}
}

FFFT.RealFFT(RealImagData, false);

//RealImagData[0] = RealImagData[1];

TimeDomain.WriteDoubleWindowed(RealImagData, Window, 0, count, Gain);

Write(TimeDomain, 0, count);
}
}

/// <summary>
/// Description of IFFTSignal.
/// </summary>
public class IFFTSignalDouble : AudioSignal
{
SigParam<IReadOnlyList<double>> FFTDataReal = new SigParam<IReadOnlyList<double>>("FFT Data Real");
SigParam<IReadOnlyList<double>> FFTDataImag = new SigParam<IReadOnlyList<double>>("FFT Data Imaginary");
SigParam<WindowFunction> FWindowFunc = new SigParam<WindowFunction>("Window Function");
SigParam<double> FGain = new SigParam<double>("Gain", 0.5);
SigParam<int> FBufferSize = new SigParam<int>("IFFT Buffer Size", true);

public IFFTSignalDouble()
{
}

IFFTPullBufferDouble FIFFTBuffer;

int NextPow2(int val)
{
var result = 2;
while(result < val)
{
result *= 2;
}

return result;
}

protected override void FillBuffer(float[] buffer, int offset, int count)
{
//recreate ring buffer?
var size = Math.Max(NextPow2(FFTDataReal.Value?.Count ?? 0), NextPow2(FFTDataImag.Value?.Count ?? 0)) * 2;

if (size < count)
{
FBufferSize.Value = 0;
return;
}

if(FIFFTBuffer == null || size != FIFFTBuffer.PullCount || FWindowFunc.Value != FIFFTBuffer.WindowFunc)
{
FIFFTBuffer = new IFFTPullBufferDouble(FFTDataReal, FFTDataImag, size, FWindowFunc.Value);
FBufferSize.Value = size;
}

FIFFTBuffer.Gain = FGain.Value;

//perform IFFT
FIFFTBuffer.Read(buffer, offset, count);
}
}
}