Skip to content

Commit 8e2eff5

Browse files
committed
WaveformDataParser: 32bit float audio support
1 parent 1ccd12c commit 8e2eff5

File tree

2 files changed

+50
-19
lines changed

2 files changed

+50
-19
lines changed

source/funkin/audio/waveform/WaveformData.hx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ class WaveformData
7272
return (channelData == null) ? buildChannelData()[index] : channelData[index];
7373
}
7474

75-
public function get(index:Int):Int
75+
public inline function get(index:Int):Int
7676
{
7777
return data[index] ?? 0;
7878
}
7979

80-
public function set(index:Int, value:Int)
80+
public inline function set(index:Int, value:Int)
8181
{
8282
data[index] = value;
8383
}
@@ -88,8 +88,9 @@ class WaveformData
8888
*/
8989
public function maxSampleValue():Int
9090
{
91+
// TODO: Should this still be cached?
9192
if (_maxSampleValue != 0) return _maxSampleValue;
92-
return _maxSampleValue = Std.int(Math.pow(2, bits));
93+
return _maxSampleValue = 1 << bits;
9394
}
9495

9596
/**

source/funkin/audio/waveform/WaveformDataParser.hx

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package funkin.audio.waveform;
22

33
import funkin.util.TimerUtil;
4+
import haxe.ds.Vector;
5+
import haxe.io.Bytes;
46

57
class WaveformDataParser
68
{
@@ -51,11 +53,31 @@ class WaveformDataParser
5153
var pointsPerSecond:Float = sampleRate / samplesPerPoint; // 172 samples per second for most songs is plenty precise while still being performant..
5254

5355
// TODO: Make this work better on HTML5.
54-
var soundData:lime.utils.Int16Array = cast soundBuffer.data;
56+
var soundData:Bytes = soundBuffer.data.toBytes();
57+
var fakeBitsPerSample:Int = bitsPerSample;
58+
var minSampleValue:Int;
59+
var maxSampleValue:Int;
60+
61+
switch (bitsPerSample)
62+
{
63+
case 8:
64+
minSampleValue = INT8_MIN;
65+
maxSampleValue = INT8_MAX;
66+
case 16:
67+
minSampleValue = INT16_MIN;
68+
maxSampleValue = INT16_MAX;
69+
case 32:
70+
// We'll cheat by scaling the values to fit in a 16-bit range.
71+
minSampleValue = INT16_MIN;
72+
maxSampleValue = INT16_MAX;
73+
fakeBitsPerSample = 16;
74+
default:
75+
throw 'Unsupported bits per sample: $bitsPerSample';
76+
}
5577

5678
var soundDataRawLength:Int = soundData.length;
57-
var soundDataSampleCount:Int = Std.int(Math.ceil(soundDataRawLength / channels / (bitsPerSample == 16 ? 2 : 1)));
58-
var outputPointCount:Int = Std.int(Math.ceil(soundDataSampleCount / samplesPerPoint));
79+
var soundDataSampleCount:Int = Math.ceil(soundDataRawLength / channels / (bitsPerSample / 8));
80+
var outputPointCount:Int = Math.ceil(soundDataSampleCount / samplesPerPoint);
5981

6082
// trace('Interpreting audio buffer:');
6183
// trace(' sampleRate: ${sampleRate}');
@@ -68,22 +90,19 @@ class WaveformDataParser
6890
// trace(' soundDataRawLength/4: ${soundDataRawLength / 4}');
6991
// trace(' outputPointCount: ${outputPointCount}');
7092

71-
var minSampleValue:Int = bitsPerSample == 16 ? INT16_MIN : INT8_MIN;
72-
var maxSampleValue:Int = bitsPerSample == 16 ? INT16_MAX : INT8_MAX;
73-
74-
var outputData:Array<Int> = [];
93+
var outputData:Vector<Int> = new Vector<Int>(outputPointCount * 2 * channels);
7594

7695
var perfStart:Float = TimerUtil.start();
7796

97+
// minChannel1, maxChannel1, minChannel2, maxChannel2, ...
98+
var values:Vector<Int> = new Vector<Int>(2 * channels);
99+
78100
for (pointIndex in 0...outputPointCount)
79101
{
80-
// minChannel1, maxChannel1, minChannel2, maxChannel2, ...
81-
var values:Array<Int> = [];
82-
83102
for (i in 0...channels)
84103
{
85-
values.push(bitsPerSample == 16 ? INT16_MAX : INT8_MAX);
86-
values.push(bitsPerSample == 16 ? INT16_MIN : INT8_MIN);
104+
values[i * 2] = maxSampleValue;
105+
values[i * 2 + 1] = minSampleValue;
87106
}
88107

89108
var rangeStart = pointIndex * samplesPerPoint;
@@ -95,20 +114,31 @@ class WaveformDataParser
95114
for (channelIndex in 0...channels)
96115
{
97116
var sampleIndex:Int = sampleIndex * channels + channelIndex;
98-
var sampleValue = soundData[sampleIndex];
117+
var sampleValue:Int = switch (bitsPerSample)
118+
{
119+
case 8:
120+
final byte = soundData.get(sampleIndex);
121+
(byte & 0x80) != 0 ? (byte | ~0xFF) : (byte & 0xFF);
122+
case 16:
123+
final word = soundData.getUInt16(sampleIndex * 2);
124+
(word & 0x8000) != 0 ? (word | ~0xFFFF) : (word & 0xFFFF);
125+
case 32:
126+
Std.int(soundData.getFloat(sampleIndex * 4) * INT16_MAX);
127+
default:
128+
0;
129+
}
99130

100131
if (sampleValue < values[channelIndex * 2]) values[(channelIndex * 2)] = sampleValue;
101132
if (sampleValue > values[channelIndex * 2 + 1]) values[(channelIndex * 2) + 1] = sampleValue;
102133
}
103134
}
104135

105136
// We now have the min and max values for the range.
106-
for (value in values)
107-
outputData.push(value);
137+
Vector.blit(values, 0, outputData, pointIndex * values.length, values.length);
108138
}
109139

110140
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
111-
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
141+
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, fakeBitsPerSample, outputPointCount, outputData.toArray());
112142

113143
trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.');
114144

0 commit comments

Comments
 (0)