-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathWaveGenerator.cs
More file actions
218 lines (182 loc) · 7.79 KB
/
Copy pathWaveGenerator.cs
File metadata and controls
218 lines (182 loc) · 7.79 KB
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MusicalCSharp;
public static partial class WaveGenerator
{
public static int GenUInt16(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, float, float, ushort> sampleFunc,
Func<int, byte[]>? bufferProvider = null)
{
return GenInt(sampleRate, channels, seconds, sampleFunc, bufferProvider);
}
public static int GenInt32(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, float, float, int> sampleFunc,
Func<int, byte[]>? bufferProvider = null)
{
return GenInt(sampleRate, channels, seconds, sampleFunc, bufferProvider);
}
public static int GenFloat32(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, float, float, float> sampleFunc,
Func<int, byte[]>? bufferProvider = null)
{
return GenFloat(sampleRate, channels, seconds, sampleFunc, bufferProvider);
}
public static int GenFloat64(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, double, double, double> sampleFunc,
Func<int, byte[]>? bufferProvider = null)
{
return GenFloat(sampleRate, channels, seconds, sampleFunc, bufferProvider);
}
public static int GenFloat<T>(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, T, T, T> sampleFunc,
Func<int, byte[]>? bufferProvider = null)
where T : struct, IBinaryFloatingPointIeee754<T>
{
if (typeof(T) == typeof(Half))
{
throw new NotSupportedException("16-bit floating point is not a valid sample format.");
}
int sampleCount = CalculateSampleCount(sampleRate, channels, seconds);
using MemoryStream stream = CreateStreamAndWriteHeader<T>(sampleRate, channels, AudioFormat.Float, sampleCount, bufferProvider);
WriteFloatingPointSamples(stream, sampleCount, sampleRate, (int)channels, sampleFunc);
return (int)stream.Position;
}
public static int GenInt<T>(
uint sampleRate,
AudioChannels channels,
float seconds,
Func<int, int, float, float, T> sampleFunc,
Func<int, byte[]>? bufferProvider)
where T : struct, IBinaryInteger<T>, IMinMaxValue<T>
{
int sampleCount = CalculateSampleCount(sampleRate, channels, seconds);
using MemoryStream stream = CreateStreamAndWriteHeader<T>(sampleRate, channels, AudioFormat.PCM, sampleCount, bufferProvider);
WriteIntegerSamples(stream, sampleCount, sampleRate, (int)channels, sampleFunc);
return (int)stream.Position;
}
private static int CalculateSampleCount(uint sampleRate, AudioChannels channels, float seconds)
{
return (int)(sampleRate * (int)channels * seconds);
}
internal static MemoryStream CreateStreamAndWriteHeader<T>(
uint sampleRate,
AudioChannels channels,
AudioFormat format,
int sampleCount,
Func<int, byte[]>? bufferProvider)
{
// Create header.
WaveHeader header = CreateHeader<T>(sampleRate, format, channels, sampleCount);
int bufferSize = (int)header.Size + 8;
// Create stream.
byte[] buffer = bufferProvider?.Invoke(bufferSize) ?? new byte[bufferSize];
MemoryStream stream = new MemoryStream(buffer);
// Write header to stream.
Span<WaveHeader> headerSpan = MemoryMarshal.CreateSpan(ref header, 1);
Span<byte> headerBytes = MemoryMarshal.Cast<WaveHeader, byte>(headerSpan);
stream.Write(headerBytes);
return stream;
}
public static void Generate<T>(uint sampleRate, AudioChannels channels, float seconds, Func<int, int, T, T, T> sampleFunc)
where T : struct, IBinaryFloatingPointIeee754<T>
{
if (typeof(T) == typeof(Half))
{
throw new NotSupportedException("16-bit floating point is not a valid sample format.");
}
int sampleCount = (int)(sampleRate * (int)channels * seconds);
WaveHeader header = CreateHeader<T>(sampleRate, AudioFormat.Float, channels, sampleCount);
byte[] rentedBuffer = ArrayPool<byte>.Shared.Rent((int)header.Size + 8);
try
{
using MemoryStream ms = new MemoryStream(rentedBuffer);
Span<WaveHeader> headerSpan = MemoryMarshal.CreateSpan(ref header, 1);
Span<byte> headerBytes = MemoryMarshal.Cast<WaveHeader, byte>(headerSpan);
ms.Write(headerBytes);
// Sample data
WriteFloatingPointSamples(ms, sampleCount, sampleRate, (int)channels, sampleFunc);
}
finally
{
ArrayPool<byte>.Shared.Return(rentedBuffer);
}
}
private static WaveHeader CreateHeader<T>(uint sampleRate, AudioFormat format, AudioChannels channels, int sampleCount)
{
const int bitsPerByte = 8;
ushort bytesPerSample = (ushort)Unsafe.SizeOf<T>();
ushort bitsPerSample = (ushort)(bytesPerSample * bitsPerByte);
ushort bytesPerBlock = (ushort)((ushort)channels * bytesPerSample);
uint bytesPerSecond = sampleRate * bytesPerBlock;
uint samplesByteCount = (uint)(sampleCount * bytesPerSample);
uint overallFileSize = (uint)(Unsafe.SizeOf<WaveHeader>() + samplesByteCount);
return new WaveHeader
{
RiffMagicString = MemoryMarshal.Read<WaveHeaderMagicString>("RIFF"u8),
Size = overallFileSize - 8,
WaveMagicString = MemoryMarshal.Read<WaveHeaderMagicString>("WAVE"u8),
FormatMagicString = MemoryMarshal.Read<WaveHeaderMagicString>("fmt "u8),
ChunkSize = 16,
Format = format,
Channels = channels,
SampleRate = sampleRate,
BytesPerSecond = bytesPerSecond,
BytesPerBlock = bytesPerBlock,
BitsPerSample = bitsPerSample,
DataMagicString = MemoryMarshal.Read<WaveHeaderMagicString>("data"u8),
SamplesByteCount = samplesByteCount,
};
}
private static void WriteFloatingPointSamples<T>(MemoryStream ms, int sampleCount, uint sampleRate, int channels, Func<int, int, T, T, T> func)
where T : struct, IBinaryFloatingPointIeee754<T>
{
for (int i = 0; i < sampleCount; i++)
{
// The channel of the current sample.
int channel = i % channels;
// The index of the current sample.
int sample = i / channels;
// Create floating point variants of the sample and sample rate.
T sampleFloat = T.CreateChecked(sample);
T sampleRateFloat = T.CreateChecked(sampleRate);
// One hertz, relative to the current sample.
T hz = T.Tau * sampleFloat / sampleRateFloat;
// Current time in seconds.
T s = sampleFloat / sampleRateFloat;
ms.WriteAsBytes(func(channel, sample, hz, s));
}
}
private static void WriteIntegerSamples<T>(MemoryStream ms, int sampleCount, uint sampleRate, int channels, Func<int, int, float, float, T> func)
where T : struct, IBinaryInteger<T>, IMinMaxValue<T>
{
for (int i = 0; i < sampleCount; i++)
{
// The channel of the current sample.
int channel = i % channels;
// The index of the current sample.
int sample = i / channels;
// One hertz, relative to the current sample.
float hz = float.Tau * sample / sampleRate;
// Current time in seconds.
float s = (float)sample / sampleRate;
ms.WriteAsBytes(func(channel, sample, hz, s));
}
}
}