-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSampleRateConverter.cs
More file actions
250 lines (232 loc) · 9.78 KB
/
SampleRateConverter.cs
File metadata and controls
250 lines (232 loc) · 9.78 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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// LibSampleRate.NET – managed wrapper for libsamplerate
// Copyright (c) 2011-2025 Mario Guggenberger
//
// This file is distributed under the BSD 2-Clause License.
// LibSampleRate.NET bundles the native libsamplerate library
// (Copyright (c) Eric de Castro Lopo), which is also released
// under the BSD 2-Clause license. See the LICENSE and COPYING
// files for full terms.
using System;
namespace LibSampleRate
{
/// <summary>
/// A sample rate converter backed by libsamplerate.
/// </summary>
public class SampleRateConverter : IDisposable
{
private IntPtr srcState = IntPtr.Zero;
private SRC_DATA srcData;
private int error;
private int channels;
private double ratio;
private double bufferedSamples;
/// <summary>
/// Creates a new resampler instance for the specified converter type (which defines the resampling quality)
/// and channel count.
/// </summary>
/// <param name="type">the type of the internal conversion algorithm (quality level)</param>
/// <param name="channels">the number of channels that will be provided to the processing method</param>
public SampleRateConverter(ConverterType type, int channels)
{
srcState = InteropWrapper.src_new(type, channels, out error);
ThrowExceptionForError(error);
srcData = new SRC_DATA();
SetRatio(1d);
this.channels = channels;
this.bufferedSamples = 0;
}
/// <summary>
/// Gets the number of bytes currently buffered by the converter. Buffering can occur because the converter
/// may consume more samples than it produces during a single <see cref="Process"/>
/// call.
///
/// The value is an estimation and can be off by a few samples due to the calculation approach. See the
/// private <c>Process</c> overload for details.
/// </summary>
public int BufferedBytes
{
get { return (int)(bufferedSamples * 4); }
}
/// <summary>
/// Resets the resampler, which essentially clears the internal buffer.
/// </summary>
public void Reset()
{
error = InteropWrapper.src_reset(srcState);
ThrowExceptionForError(error);
bufferedSamples = 0;
}
/// <summary>
/// Sets the resampling ratio through an instant change.
/// </summary>
/// <param name="ratio">the resampling ratio</param>
public void SetRatio(double ratio)
{
SetRatio(ratio, true);
}
/// <summary>
/// Sets the resampling ratio. Multiplying the input rate by the ratio factor yields the output rate.
/// </summary>
/// <param name="ratio">the resampling ratio</param>
/// <param name="step">True to change the ratio immediately; false to linearly interpolate to the new ratio during the next processing call.</param>
public void SetRatio(double ratio, bool step)
{
if (step)
{
// force the ratio for the next #Process call instead of linearly interpolating from the previous
// ratio to the current ratio
error = InteropWrapper.src_set_ratio(srcState, ratio);
ThrowExceptionForError(error);
}
this.ratio = ratio;
}
/// <summary>
/// Checks whether a given resampling ratio is valid.
/// </summary>
/// <param name="ratio">The ratio to validate.</param>
/// <returns>True if the ratio is supported; otherwise false.</returns>
public static bool CheckRatio(double ratio)
{
return InteropWrapper.src_is_valid_ratio(ratio) == 1;
}
/// <summary>
/// Processes a block of input samples by resampling it into a block of output samples. This overload
/// expects 32-bit floating-point samples stored in byte arrays. When the resampler is configured
/// for multiple channels, the samples must be interleaved. All byte counts refer to the totals across channels.
/// </summary>
/// <param name="input">The input sample block.</param>
/// <param name="inputOffset">The offset in the input block, in bytes.</param>
/// <param name="inputLength">The length of the input block data, in bytes.</param>
/// <param name="output">The output sample block.</param>
/// <param name="outputOffset">The offset in the output block, in bytes.</param>
/// <param name="outputLength">The available length in the output block, in bytes.</param>
/// <param name="endOfInput">True to flush buffered samples because no more input samples are available.</param>
/// <param name="inputLengthUsed">On return, contains the number of bytes read from the input block.</param>
/// <param name="outputLengthGenerated">On return, contains the number of bytes written to the output block.</param>
public void Process(
byte[] input,
int inputOffset,
int inputLength,
byte[] output,
int outputOffset,
int outputLength,
bool endOfInput,
out int inputLengthUsed,
out int outputLengthGenerated
)
{
unsafe
{
fixed (
byte* inputBytes = &input[inputOffset],
outputBytes = &output[outputOffset]
)
{
Process(
(float*)inputBytes,
inputLength / 4,
(float*)outputBytes,
outputLength / 4,
endOfInput,
out inputLengthUsed,
out outputLengthGenerated
);
inputLengthUsed *= 4;
outputLengthGenerated *= 4;
}
}
}
/// <summary>
/// Processes a block of input samples by resampling it into a block of output samples. This overload
/// expects 32-bit floating-point samples stored in <see cref="float"/> arrays. When the resampler is configured
/// for multiple channels, the samples must be interleaved. All sample counts refer to the totals across channels.
/// </summary>
/// <param name="input">The input sample block.</param>
/// <param name="inputOffset">The offset in the input block, in samples.</param>
/// <param name="inputLength">The length of the input block data, in samples.</param>
/// <param name="output">The output sample block.</param>
/// <param name="outputOffset">The offset in the output block, in samples.</param>
/// <param name="outputLength">The available length in the output block, in samples.</param>
/// <param name="endOfInput">True to flush buffered samples because no more input samples are available.</param>
/// <param name="inputLengthUsed">On return, contains the number of samples read from the input block.</param>
/// <param name="outputLengthGenerated">On return, contains the number of samples written to the output block.</param>
public void Process(
float[] input,
int inputOffset,
int inputLength,
float[] output,
int outputOffset,
int outputLength,
bool endOfInput,
out int inputLengthUsed,
out int outputLengthGenerated
)
{
unsafe
{
fixed (
float* inputFloats = &input[inputOffset],
outputFloats = &output[outputOffset]
)
{
Process(
inputFloats,
inputLength,
outputFloats,
outputLength,
endOfInput,
out inputLengthUsed,
out outputLengthGenerated
);
}
}
}
private unsafe void Process(
float* input,
int inputLength,
float* output,
int outputLength,
bool endOfInput,
out int inputLengthUsed,
out int outputLengthGenerated
)
{
srcData.data_in = input;
srcData.data_out = output;
srcData.end_of_input = endOfInput ? 1 : 0;
srcData.input_frames = inputLength / channels;
srcData.output_frames = outputLength / channels;
srcData.src_ratio = ratio;
error = InteropWrapper.src_process(srcState, ref srcData);
ThrowExceptionForError(error);
inputLengthUsed = srcData.input_frames_used * channels;
outputLengthGenerated = srcData.output_frames_gen * channels;
bufferedSamples += inputLengthUsed - (outputLengthGenerated / ratio);
}
private void ThrowExceptionForError(int error)
{
if (error != 0)
{
throw new Exception(InteropWrapper.src_strerror(error));
}
}
/// <summary>
/// Disposes this instance of the resampler, freeing its memory.
/// </summary>
public void Dispose()
{
if (srcState != IntPtr.Zero)
{
srcState = InteropWrapper.src_delete(srcState);
if (srcState != IntPtr.Zero)
{
throw new Exception("could not delete the sample rate converter");
}
}
}
~SampleRateConverter()
{
Dispose();
}
}
}