-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDebugResampleTest.cs
185 lines (154 loc) · 6.33 KB
/
DebugResampleTest.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
using Godot;
using System;
using WaveLoader;
using PhaseEngine;
public class DebugResampleTest : Control
{
public WaveFile wf;
public float[] data = new float[0];
public float[][] lod = new float[8][];
AudioStreamGenerator stream;
AudioStreamGeneratorPlayback buf;
AudioStreamPlayer player;
Vector2[] bufferPool;
public delegate float DownsampleTechniqueDelegate (int sample);
public DownsampleTechniqueDelegate Downsampler;
enum ResampleTechnique{NONE, LOD, LOD_LERP}
double STRENGTH = 1.5;
int currentLod = 0;
double head = 0;
float PlaybackPosition {get => data.Length==0? 0: (float)(head/(double)data.Length);}
double stride = 1;
float multiplier = 1;
float currentSpeed = 1;
public DebugResampleTest(){lod[0] = data; Downsampler = RawSampleOf;}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
player = GetNode<AudioStreamPlayer>("Player");
stream = (AudioStreamGenerator) player.Stream;
buf = (AudioStreamGeneratorPlayback) player.GetStreamPlayback();
bufferPool = new Vector2[(int)Math.Max(stream.MixRate * stream.BufferLength +2, stream.MixRate)];
stream.MixRate = Global.MixRate;
player.Play();
}
public void LoadWave(string path)
{
wf = WaveFile.Load(path);
data = wf.GetDataFloat();
head=0;
stride = wf.SampleRate / stream.MixRate;
// GD.Print(wf.SampleRate, ", stride ", stride);
GetNode<Slider>("Freq").Value = wf.SampleRate;
//Load LODs
lod[0] = data;
for (int i=1; i<lod.Length; i++)
{
float[] array;
array = Tools.Butterworth(data, wf.SampleRate, wf.SampleRate / (1<<i));
lod[i] = array;
}
RecalcDisplay();
}
public void RecalcDisplay() => RecalcDisplay(lod[currentLod]);
public void RecalcDisplay(float[] dataSrc)
{
var rect = GetNode<ColorRect>("Display");
var display = new WaveformDisplay<float>(dataSrc);
float[] mins = new float[(int)rect.RectSize.x*1], maxes = new float[(int)rect.RectSize.x*1];
int samplesPerPx = Math.Max(1, (int)Math.Round(dataSrc.Length/rect.RectSize.x));
display.GetDisplayData(0, samplesPerPx, ref mins, ref maxes, (int)rect.RectSize.x*1);
var optimalSamples = Math.Sqrt(dataSrc.Length/OS.GetScreenSize().x);
GD.Print("Optimal samples per LUT: ", optimalSamples, ", ", Tools.Pow2Ceil((int)optimalSamples));
GD.Print("Predicted LUT size: ", 131072.0 / Tools.Pow2Ceil((int)optimalSamples));
//Convert peaks and valleys such that any bits from the current data are connected to the previous.
const float epsilon = 1.0f/(float)ushort.MaxValue;
for (int i=1; i < mins.Length; i++)
{
if (mins[i] > maxes[i - 1])
mins[i] = maxes[i - 1] + epsilon;
if (maxes[i] < mins[i - 1])
maxes[i] = mins[i - 1] - epsilon;
}
rect.Set("mins", mins);
rect.Set("maxes", maxes);
rect.Update();
}
public float RawSampleOf(int sample) => data[sample];
public float LodOf(int sample)
{
var mult = (int)Math.Clamp(Math.Abs(multiplier), 0, 7); //Clamp to the number of LODs available
return lod[mult][sample];
}
public float LerpLodOf(int sample)
{
var mult = (int)Math.Clamp(Math.Abs(multiplier), 0, 7); //Clamp to the number of LODs available
var mult2 = mult+1; if (mult2>7) mult2=7;
var percent = multiplier - Math.Truncate(multiplier);
return Tools.Lerp(lod[mult][sample], lod[mult2][sample], (float)percent);
}
//TODO: Consider the following: Build a log2 table of value multipliers up to 8 octaves above current.
//Using a base note on a MIDI note value we can get the closest floating log2 approximates and lerp between them.
//Lerping the multiplier approximates may not be necessary but will only give 12 levels of fidelity between LODs.
//Test to see if the transition is audible on pitch bends.
//This is to speed up lookups of LOD mixing percentages based on current pitch relative to normal in the sampler core.
//Also consider exposing ability to set a sample as "LODable" as well as a strength multiplier to affect how strong the lowpass effect is as pitch rises.
public void SetSpeed(float multiplier)
{
currentSpeed = multiplier;
stride = wf.SampleRate / stream.MixRate * multiplier;
this.multiplier = (float)Tools.Log2(Math.Abs(multiplier)*STRENGTH) * Math.Sign(multiplier);
if(double.IsNaN(this.multiplier))
this.multiplier=0;
}
public void SetStrength(float strength) { STRENGTH = strength; SetSpeed(currentSpeed); }
public void SetResampleTechnique(int tech)
{
switch((ResampleTechnique)tech)
{
case ResampleTechnique.NONE:
Downsampler = RawSampleOf;
break;
case ResampleTechnique.LOD:
Downsampler = LodOf;
break;
case ResampleTechnique.LOD_LERP:
Downsampler = LerpLodOf;
break;
default:
Downsampler = RawSampleOf;
break;
}
}
public override void _Process(float delta)
{
base._Process(delta);
if (buf.GetSkips() > 0)
{
var frames= buf.GetFramesAvailable();
// var output = new Vector2[frames];
var segment = new ArraySegment<Vector2>(bufferPool, 0, frames);
var output = segment.Array;
for (int i=0; i<frames; i++)
{
if(data.Length==0)
{
output[i].x = 0;
output[i].y = 0;
} else {
// output[i].x = lod[currentLod][(int)head];
output[i].x = Downsampler((int)head);
output[i].y = output[i].x;
head+=stride;
// head%=data.Length;
head = Tools.Mod(head, data.Length);
}
}
buf.PushBuffer(segment.ToArray());
}
}
public override void _PhysicsProcess(float delta)
{
base._PhysicsProcess(delta);
}
}