1 /* Sonic library
2 Copyright 2010
3 Bill Cox
4 This file is part of the Sonic Library.
5
6 This file is licensed under the Apache 2.0 license.
7 */
8
9 #include "sonic.h"
10
11 #include <limits.h>
12 #include <math.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 /*
17 The following code was used to generate the following sinc lookup table.
18
19 #include <limits.h>
20 #include <math.h>
21 #include <stdio.h>
22
23 double findHannWeight(int N, double x) {
24 return 0.5*(1.0 - cos(2*M_PI*x/N));
25 }
26
27 double findSincCoefficient(int N, double x) {
28 double hannWindowWeight = findHannWeight(N, x);
29 double sincWeight;
30
31 x -= N/2.0;
32 if (x > 1e-9 || x < -1e-9) {
33 sincWeight = sin(M_PI*x)/(M_PI*x);
34 } else {
35 sincWeight = 1.0;
36 }
37 return hannWindowWeight*sincWeight;
38 }
39
40 int main() {
41 double x;
42 int i;
43 int N = 12;
44
45 for (i = 0, x = 0.0; x <= N; x += 0.02, i++) {
46 printf("%u %d\n", i, (int)(SHRT_MAX*findSincCoefficient(N, x)));
47 }
48 return 0;
49 }
50 */
51
52 /* The number of points to use in the sinc FIR filter for resampling. */
53 #define SINC_FILTER_POINTS \
54 12 /* I am not able to hear improvement with higher N. */
55 #define SINC_TABLE_SIZE 601
56
57 /* Lookup table for windowed sinc function of SINC_FILTER_POINTS points. */
58 static short sincTable[SINC_TABLE_SIZE] = {
59 0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2,
60 -3, -4, -6, -7, -9, -10, -12, -14, -17, -19, -21,
61 -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48,
62 -50, -51, -52, -53, -53, -53, -52, -50, -48, -46, -43,
63 -39, -34, -29, -22, -16, -8, 0, 9, 19, 29, 41,
64 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200,
65 215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348,
66 357, 363, 369, 372, 374, 375, 373, 369, 363, 355, 345,
67 332, 318, 300, 281, 259, 234, 208, 178, 147, 113, 77,
68 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426,
69 -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951,
70 -989, -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151,
71 -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728,
72 -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342,
73 462, 584, 708, 833, 958, 1084, 1209, 1333, 1455, 1575, 1693,
74 1807, 1916, 2022, 2122, 2216, 2304, 2384, 2457, 2522, 2579, 2625,
75 2663, 2689, 2706, 2711, 2705, 2687, 2657, 2614, 2559, 2491, 2411,
76 2317, 2211, 2092, 1960, 1815, 1658, 1489, 1308, 1115, 912, 698,
77 474, 241, 0, -249, -506, -769, -1037, -1310, -1586, -1864, -2144,
78 -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291, -4529, -4757, -4972,
79 -5174, -5360, -5531, -5685, -5819, -5935, -6029, -6101, -6150, -6175, -6175,
80 -6149, -6096, -6015, -5905, -5767, -5599, -5401, -5172, -4912, -4621, -4298,
81 -3944, -3558, -3141, -2693, -2214, -1705, -1166, -597, 0, 625, 1277,
82 1955, 2658, 3386, 4135, 4906, 5697, 6506, 7332, 8173, 9027, 9893,
83 10769, 11654, 12544, 13439, 14335, 15232, 16128, 17019, 17904, 18782, 19649,
84 20504, 21345, 22170, 22977, 23763, 24527, 25268, 25982, 26669, 27327, 27953,
85 28547, 29107, 29632, 30119, 30569, 30979, 31349, 31678, 31964, 32208, 32408,
86 32565, 32677, 32744, 32767, 32744, 32677, 32565, 32408, 32208, 31964, 31678,
87 31349, 30979, 30569, 30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982,
88 25268, 24527, 23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019,
89 16128, 15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332,
90 6506, 5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597,
91 -1166, -1705, -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172,
92 -5401, -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101,
93 -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529, -4291,
94 -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864, -1586, -1310,
95 -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308,
96 1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614,
97 2657, 2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457,
98 2384, 2304, 2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333,
99 1209, 1084, 958, 833, 708, 584, 462, 342, 225, 111, 0,
100 -107, -210, -309, -403, -492, -576, -655, -728, -796, -857, -913,
101 -963, -1007, -1046, -1078, -1105, -1125, -1141, -1151, -1155, -1154, -1149,
102 -1138, -1123, -1104, -1080, -1053, -1023, -989, -951, -912, -870, -825,
103 -779, -731, -682, -632, -581, -530, -478, -426, -375, -324, -274,
104 -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178,
105 208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369,
106 373, 375, 374, 372, 369, 363, 357, 348, 339, 328, 317,
107 304, 291, 276, 262, 247, 231, 215, 200, 184, 168, 152,
108 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9,
109 0, -8, -16, -22, -29, -34, -39, -43, -46, -48, -50,
110 -52, -53, -53, -53, -52, -51, -50, -48, -47, -44, -42,
111 -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14,
112 -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1,
113 0, 0, 0, 0, 0, 0, 0};
114
115 /* These functions allocate out of a static array rather than calling
116 calloc/realloc/free if the NO_MALLOC flag is defined. Otherwise, call
117 calloc/realloc/free as usual. This is useful for running on small
118 microcontrollers. */
119 #ifndef SONIC_NO_MALLOC
120
121 /* Just call calloc. */
sonicCalloc(int num,int size)122 static void *sonicCalloc(int num, int size) {
123 return calloc(num, size);
124 }
125
126 /* Just call realloc */
sonicRealloc(void * p,int oldNum,int newNum,int size)127 static void *sonicRealloc(void *p, int oldNum, int newNum, int size) {
128 return realloc(p, newNum * size);
129 }
130
131 /* Just call free. */
sonicFree(void * p)132 static void sonicFree(void *p) {
133 free(p);
134 }
135
136 #else
137
138 #ifndef SONIC_MAX_MEMORY
139 /* Large enough for speedup/slowdown at 8KHz, 16-bit mono samples/second. */
140 #define SONIC_MAX_MEMORY (16 * 1024)
141 #endif
142
143 /* This static buffer is used to hold data allocated for the sonicStream struct
144 and its buffers. There should never be more than one sonicStream in use at a
145 time when using SONIC_NO_MALLOC mode. Calls to realloc move the data to the
146 end of memoryBuffer. Calls to free reset the memory buffer to empty. */
147 static void*
148 memoryBufferAligned[(SONIC_MAX_MEMORY + sizeof(void) - 1) / sizeof(void*)];
149 static unsigned char* memoryBuffer = (unsigned char*)memoryBufferAligned;
150 static int memoryBufferPos = 0;
151
152 /* Allocate elements from a static memory buffer. */
sonicCalloc(int num,int size)153 static void *sonicCalloc(int num, int size) {
154 int len = num * size;
155
156 if (memoryBufferPos + len > SONIC_MAX_MEMORY) {
157 return 0;
158 }
159 unsigned char *p = memoryBuffer + memoryBufferPos;
160 memoryBufferPos += len;
161 memset(p, 0, len);
162 return p;
163 }
164
165 /* Preferably, SONIC_MAX_MEMORY has been set large enough that this is never
166 * called. */
sonicRealloc(void * p,int oldNum,int newNum,int size)167 static void *sonicRealloc(void *p, int oldNum, int newNum, int size) {
168 if (newNum <= oldNum) {
169 return p;
170 }
171 void *newBuffer = sonicCalloc(newNum, size);
172 if (newBuffer == NULL) {
173 return NULL;
174 }
175 memcpy(newBuffer, p, oldNum * size);
176 return newBuffer;
177 }
178
179 /* Reset memoryBufferPos to 0. We asssume all data is freed at the same time. */
sonicFree(void * p)180 static void sonicFree(void *p) {
181 memoryBufferPos = 0;
182 }
183
184 #endif
185
186 struct sonicStreamStruct {
187 #ifdef SONIC_SPECTROGRAM
188 sonicSpectrogram spectrogram;
189 #endif /* SONIC_SPECTROGRAM */
190 short* inputBuffer;
191 short* outputBuffer;
192 short* pitchBuffer;
193 short* downSampleBuffer;
194 void* userData;
195 float speed;
196 float volume;
197 float pitch;
198 float rate;
199 /* The point of the following 3 new variables is to gracefully handle rapidly
200 changing input speed.
201
202 samplePeriod is just 1.0/sampleRate. It is used in accumulating
203 inputPlayTime, which is how long we expect the total time should be to play
204 the current input samples in the input buffer. timeError keeps track of
205 the error in play time created when playing < 2.0X speed, where we either
206 insert or delete a whole pitch period. This can cause the output generated
207 from the input to be off in play time by up to a pitch period. timeError
208 replaces PICOLA's concept of the number of samples to play unmodified after
209 a pitch period insertion or deletion. If speeding up, and the error is >=
210 0.0, then remove a pitch period, and play samples unmodified until
211 timeError is >= 0 again. If slowing down, and the error is <= 0.0,
212 then add a pitch period, and play samples unmodified until timeError is <=
213 0 again. */
214 float samplePeriod; /* How long each output sample takes to play. */
215 /* How long we expect the entire input buffer to take to play. */
216 float inputPlayTime;
217 /* The difference in when the latest output sample was played vs when we wanted. */
218 float timeError;
219 int oldRatePosition;
220 int newRatePosition;
221 int quality;
222 int numChannels;
223 int inputBufferSize;
224 int pitchBufferSize;
225 int outputBufferSize;
226 int numInputSamples;
227 int numOutputSamples;
228 int numPitchSamples;
229 int minPeriod;
230 int maxPeriod;
231 int maxRequired;
232 int remainingInputToCopy;
233 int sampleRate;
234 int prevPeriod;
235 int prevMinDiff;
236 };
237
238 /* Attach user data to the stream. */
sonicSetUserData(sonicStream stream,void * userData)239 void sonicSetUserData(sonicStream stream, void *userData) {
240 stream->userData = userData;
241 }
242
243 /* Retrieve user data attached to the stream. */
sonicGetUserData(sonicStream stream)244 void *sonicGetUserData(sonicStream stream) {
245 return stream->userData;
246 }
247
248 #ifdef SONIC_SPECTROGRAM
249
250 /* Compute a spectrogram on the fly. */
sonicComputeSpectrogram(sonicStream stream)251 void sonicComputeSpectrogram(sonicStream stream) {
252 stream->spectrogram = sonicCreateSpectrogram(stream->sampleRate);
253 /* Force changeSpeed to be called to compute the spectrogram. */
254 sonicSetSpeed(stream, 2.0);
255 }
256
257 /* Get the spectrogram. */
sonicGetSpectrogram(sonicStream stream)258 sonicSpectrogram sonicGetSpectrogram(sonicStream stream) {
259 return stream->spectrogram;
260 }
261
262 #endif
263
264 /* Scale the samples by the factor. */
scaleSamples(short * samples,int numSamples,float volume)265 static void scaleSamples(short* samples, int numSamples, float volume) {
266 /* This is 24-bit integer and 8-bit fraction fixed-point representation. */
267 int fixedPointVolume = volume * 256.0f;
268 int value;
269
270 while (numSamples--) {
271 value = (*samples * fixedPointVolume) >> 8;
272 if (value > 32767) {
273 value = 32767;
274 } else if (value < -32767) {
275 value = -32767;
276 }
277 *samples++ = value;
278 }
279 }
280
281 /* Get the speed of the stream. */
sonicGetSpeed(sonicStream stream)282 float sonicGetSpeed(sonicStream stream) { return stream->speed; }
283
284 /* Set the speed of the stream. */
sonicSetSpeed(sonicStream stream,float speed)285 void sonicSetSpeed(sonicStream stream, float speed) { stream->speed = speed; }
286
287 /* Get the pitch of the stream. */
sonicGetPitch(sonicStream stream)288 float sonicGetPitch(sonicStream stream) { return stream->pitch; }
289
290 /* Set the pitch of the stream. */
sonicSetPitch(sonicStream stream,float pitch)291 void sonicSetPitch(sonicStream stream, float pitch) { stream->pitch = pitch; }
292
293 /* Get the rate of the stream. */
sonicGetRate(sonicStream stream)294 float sonicGetRate(sonicStream stream) { return stream->rate; }
295
296 /* Set the playback rate of the stream. This scales pitch and speed at the same
297 time. */
sonicSetRate(sonicStream stream,float rate)298 void sonicSetRate(sonicStream stream, float rate) {
299 stream->rate = rate;
300
301 stream->oldRatePosition = 0;
302 stream->newRatePosition = 0;
303 }
304
305 /* DEPRECATED. Get the vocal chord pitch setting. */
sonicGetChordPitch(sonicStream stream)306 int sonicGetChordPitch(sonicStream stream) {
307 return 0;
308 }
309
310 /* DEPRECATED. Set the vocal chord mode for pitch computation. Default is off. */
sonicSetChordPitch(sonicStream stream,int useChordPitch)311 void sonicSetChordPitch(sonicStream stream, int useChordPitch) {
312 }
313
314 /* Get the quality setting. */
sonicGetQuality(sonicStream stream)315 int sonicGetQuality(sonicStream stream) { return stream->quality; }
316
317 /* Set the "quality". Default 0 is virtually as good as 1, but very much
318 faster. */
sonicSetQuality(sonicStream stream,int quality)319 void sonicSetQuality(sonicStream stream, int quality) {
320 stream->quality = quality;
321 }
322
323 /* Get the scaling factor of the stream. */
sonicGetVolume(sonicStream stream)324 float sonicGetVolume(sonicStream stream) { return stream->volume; }
325
326 /* Set the scaling factor of the stream. */
sonicSetVolume(sonicStream stream,float volume)327 void sonicSetVolume(sonicStream stream, float volume) {
328 stream->volume = volume;
329 }
330
331 /* Free stream buffers. */
freeStreamBuffers(sonicStream stream)332 static void freeStreamBuffers(sonicStream stream) {
333 if (stream->inputBuffer != NULL) {
334 sonicFree(stream->inputBuffer);
335 }
336 if (stream->outputBuffer != NULL) {
337 sonicFree(stream->outputBuffer);
338 }
339 if (stream->pitchBuffer != NULL) {
340 sonicFree(stream->pitchBuffer);
341 }
342 if (stream->downSampleBuffer != NULL) {
343 sonicFree(stream->downSampleBuffer);
344 }
345 }
346
347 /* Destroy the sonic stream. */
sonicDestroyStream(sonicStream stream)348 void sonicDestroyStream(sonicStream stream) {
349 #ifdef SONIC_SPECTROGRAM
350 if (stream->spectrogram != NULL) {
351 sonicDestroySpectrogram(stream->spectrogram);
352 }
353 #endif /* SONIC_SPECTROGRAM */
354 freeStreamBuffers(stream);
355 sonicFree(stream);
356 }
357
358 /* Compute the number of samples to skip to down-sample the input. */
computeSkip(sonicStream stream)359 static int computeSkip(sonicStream stream) {
360 int skip = 1;
361 if (stream->sampleRate > SONIC_AMDF_FREQ && stream->quality == 0) {
362 skip = stream->sampleRate / SONIC_AMDF_FREQ;
363 }
364 return skip;
365 }
366
367 /* Allocate stream buffers. */
allocateStreamBuffers(sonicStream stream,int sampleRate,int numChannels)368 static int allocateStreamBuffers(sonicStream stream, int sampleRate,
369 int numChannels) {
370 int minPeriod = sampleRate / SONIC_MAX_PITCH;
371 int maxPeriod = sampleRate / SONIC_MIN_PITCH;
372 int maxRequired = 2 * maxPeriod;
373 int skip = computeSkip(stream);
374
375 /* Allocate 25% more than needed so we hopefully won't grow. */
376 stream->inputBufferSize = maxRequired + (maxRequired >> 2);;
377 stream->inputBuffer =
378 (short*)sonicCalloc(stream->inputBufferSize, sizeof(short) * numChannels);
379 if (stream->inputBuffer == NULL) {
380 sonicDestroyStream(stream);
381 return 0;
382 }
383 /* Allocate 25% more than needed so we hopefully won't grow. */
384 stream->outputBufferSize = maxRequired + (maxRequired >> 2);
385 stream->outputBuffer =
386 (short*)sonicCalloc(stream->outputBufferSize, sizeof(short) * numChannels);
387 if (stream->outputBuffer == NULL) {
388 sonicDestroyStream(stream);
389 return 0;
390 }
391 /* Allocate 25% more than needed so we hopefully won't grow. */
392 stream->pitchBufferSize = maxRequired + (maxRequired >> 2);
393 stream->pitchBuffer =
394 (short*)sonicCalloc(maxRequired, sizeof(short) * numChannels);
395 if (stream->pitchBuffer == NULL) {
396 sonicDestroyStream(stream);
397 return 0;
398 }
399 int downSampleBufferSize = (maxRequired + skip - 1)/ skip;
400 stream->downSampleBuffer = (short*)sonicCalloc(downSampleBufferSize, sizeof(short));
401 if (stream->downSampleBuffer == NULL) {
402 sonicDestroyStream(stream);
403 return 0;
404 }
405 stream->sampleRate = sampleRate;
406 stream->samplePeriod = 1.0 / sampleRate;
407 stream->numChannels = numChannels;
408 stream->oldRatePosition = 0;
409 stream->newRatePosition = 0;
410 stream->minPeriod = minPeriod;
411 stream->maxPeriod = maxPeriod;
412 stream->maxRequired = maxRequired;
413 stream->prevPeriod = 0;
414 return 1;
415 }
416
417 /* Create a sonic stream. Return NULL only if we are out of memory and cannot
418 allocate the stream. */
sonicCreateStream(int sampleRate,int numChannels)419 sonicStream sonicCreateStream(int sampleRate, int numChannels) {
420 sonicStream stream = (sonicStream)sonicCalloc(
421 1, sizeof(struct sonicStreamStruct));
422
423 if (stream == NULL) {
424 return NULL;
425 }
426 if (!allocateStreamBuffers(stream, sampleRate, numChannels)) {
427 return NULL;
428 }
429 stream->speed = 1.0f;
430 stream->pitch = 1.0f;
431 stream->volume = 1.0f;
432 stream->rate = 1.0f;
433 stream->oldRatePosition = 0;
434 stream->newRatePosition = 0;
435 stream->quality = 0;
436 return stream;
437 }
438
439 /* Get the sample rate of the stream. */
sonicGetSampleRate(sonicStream stream)440 int sonicGetSampleRate(sonicStream stream) { return stream->sampleRate; }
441
442 /* Set the sample rate of the stream. This will cause samples buffered in the
443 stream to be lost. */
sonicSetSampleRate(sonicStream stream,int sampleRate)444 void sonicSetSampleRate(sonicStream stream, int sampleRate) {
445 freeStreamBuffers(stream);
446 allocateStreamBuffers(stream, sampleRate, stream->numChannels);
447 }
448
449 /* Get the number of channels. */
sonicGetNumChannels(sonicStream stream)450 int sonicGetNumChannels(sonicStream stream) { return stream->numChannels; }
451
452 /* Set the num channels of the stream. This will cause samples buffered in the
453 stream to be lost. */
sonicSetNumChannels(sonicStream stream,int numChannels)454 void sonicSetNumChannels(sonicStream stream, int numChannels) {
455 freeStreamBuffers(stream);
456 allocateStreamBuffers(stream, stream->sampleRate, numChannels);
457 }
458
459 /* Enlarge the output buffer if needed. */
enlargeOutputBufferIfNeeded(sonicStream stream,int numSamples)460 static int enlargeOutputBufferIfNeeded(sonicStream stream, int numSamples) {
461 int outputBufferSize = stream->outputBufferSize;
462
463 if (stream->numOutputSamples + numSamples > outputBufferSize) {
464 stream->outputBufferSize += (outputBufferSize >> 1) + numSamples;
465 stream->outputBuffer = (short*)sonicRealloc(
466 stream->outputBuffer,
467 outputBufferSize,
468 stream->outputBufferSize,
469 sizeof(short) * stream->numChannels);
470 if (stream->outputBuffer == NULL) {
471 return 0;
472 }
473 }
474 return 1;
475 }
476
477 /* Enlarge the input buffer if needed. */
enlargeInputBufferIfNeeded(sonicStream stream,int numSamples)478 static int enlargeInputBufferIfNeeded(sonicStream stream, int numSamples) {
479 int inputBufferSize = stream->inputBufferSize;
480
481 if (stream->numInputSamples + numSamples > inputBufferSize) {
482 stream->inputBufferSize += (inputBufferSize >> 1) + numSamples;
483 stream->inputBuffer = (short*)sonicRealloc(
484 stream->inputBuffer,
485 inputBufferSize,
486 stream->inputBufferSize,
487 sizeof(short) * stream->numChannels);
488 if (stream->inputBuffer == NULL) {
489 return 0;
490 }
491 }
492 return 1;
493 }
494
495 /* Update stream->numInputSamples, and update stream->inputPlayTime. Call this
496 whenever adding samples to the input buffer, to keep track of total expected
497 input play time accounting. */
updateNumInputSamples(sonicStream stream,int numSamples)498 static void updateNumInputSamples(sonicStream stream, int numSamples) {
499 float speed = stream->speed / stream->pitch;
500
501 stream->numInputSamples += numSamples;
502 stream->inputPlayTime += numSamples * stream->samplePeriod / speed;
503 }
504
505 /* Add the input samples to the input buffer. */
addFloatSamplesToInputBuffer(sonicStream stream,const float * samples,int numSamples)506 static int addFloatSamplesToInputBuffer(sonicStream stream, const float* samples,
507 int numSamples) {
508 short* buffer;
509 int count = numSamples * stream->numChannels;
510
511 if (numSamples == 0) {
512 return 1;
513 }
514 if (!enlargeInputBufferIfNeeded(stream, numSamples)) {
515 return 0;
516 }
517 buffer = stream->inputBuffer + stream->numInputSamples * stream->numChannels;
518 while (count--) {
519 *buffer++ = (*samples++) * 32767.0f;
520 }
521 updateNumInputSamples(stream, numSamples);
522 return 1;
523 }
524
525 /* Add the input samples to the input buffer. */
addShortSamplesToInputBuffer(sonicStream stream,const short * samples,int numSamples)526 static int addShortSamplesToInputBuffer(sonicStream stream, const short* samples,
527 int numSamples) {
528 if (numSamples == 0) {
529 return 1;
530 }
531 if (!enlargeInputBufferIfNeeded(stream, numSamples)) {
532 return 0;
533 }
534 memcpy(stream->inputBuffer + stream->numInputSamples * stream->numChannels,
535 samples, numSamples * sizeof(short) * stream->numChannels);
536 updateNumInputSamples(stream, numSamples);
537 return 1;
538 }
539
540 /* Add the input samples to the input buffer. */
addUnsignedCharSamplesToInputBuffer(sonicStream stream,const unsigned char * samples,int numSamples)541 static int addUnsignedCharSamplesToInputBuffer(sonicStream stream,
542 const unsigned char* samples,
543 int numSamples) {
544 short* buffer;
545 int count = numSamples * stream->numChannels;
546
547 if (numSamples == 0) {
548 return 1;
549 }
550 if (!enlargeInputBufferIfNeeded(stream, numSamples)) {
551 return 0;
552 }
553 buffer = stream->inputBuffer + stream->numInputSamples * stream->numChannels;
554 while (count--) {
555 *buffer++ = (*samples++ - 128) << 8;
556 }
557 updateNumInputSamples(stream, numSamples);
558 return 1;
559 }
560
561 /* Remove input samples that we have already processed. */
removeInputSamples(sonicStream stream,int position)562 static void removeInputSamples(sonicStream stream, int position) {
563 int remainingSamples = stream->numInputSamples - position;
564
565 if (remainingSamples > 0) {
566 memmove(stream->inputBuffer,
567 stream->inputBuffer + position * stream->numChannels,
568 remainingSamples * sizeof(short) * stream->numChannels);
569 }
570 /* If we play 3/4ths of the samples, then the expected play time of the
571 remaining samples is 1/4th of the original expected play time. */
572 stream->inputPlayTime =
573 (stream->inputPlayTime * remainingSamples) / stream->numInputSamples;
574 stream->numInputSamples = remainingSamples;
575 }
576
577 /* Copy from the input buffer to the output buffer, and remove the samples from
578 the input buffer. */
copyInputToOutput(sonicStream stream,int numSamples)579 static int copyInputToOutput(sonicStream stream, int numSamples) {
580 if (!enlargeOutputBufferIfNeeded(stream, numSamples)) {
581 return 0;
582 }
583 memcpy(stream->outputBuffer + stream->numOutputSamples * stream->numChannels,
584 stream->inputBuffer, numSamples * sizeof(short) * stream->numChannels);
585 stream->numOutputSamples += numSamples;
586 removeInputSamples(stream, numSamples);
587 return 1;
588 }
589
590 /* Copy from samples to the output buffer */
copyToOutput(sonicStream stream,short * samples,int numSamples)591 static int copyToOutput(sonicStream stream, short* samples, int numSamples) {
592 if (!enlargeOutputBufferIfNeeded(stream, numSamples)) {
593 return 0;
594 }
595 memcpy(stream->outputBuffer + stream->numOutputSamples * stream->numChannels,
596 samples, numSamples * sizeof(short) * stream->numChannels);
597 stream->numOutputSamples += numSamples;
598 return 1;
599 }
600
601 /* Read data out of the stream. Sometimes no data will be available, and zero
602 is returned, which is not an error condition. */
sonicReadFloatFromStream(sonicStream stream,float * samples,int maxSamples)603 int sonicReadFloatFromStream(sonicStream stream, float* samples,
604 int maxSamples) {
605 int numSamples = stream->numOutputSamples;
606 int remainingSamples = 0;
607 short* buffer;
608 int count;
609
610 if (numSamples == 0) {
611 return 0;
612 }
613 if (numSamples > maxSamples) {
614 remainingSamples = numSamples - maxSamples;
615 numSamples = maxSamples;
616 }
617 buffer = stream->outputBuffer;
618 count = numSamples * stream->numChannels;
619 while (count--) {
620 *samples++ = (*buffer++) / 32767.0f;
621 }
622 if (remainingSamples > 0) {
623 memmove(stream->outputBuffer,
624 stream->outputBuffer + numSamples * stream->numChannels,
625 remainingSamples * sizeof(short) * stream->numChannels);
626 }
627 stream->numOutputSamples = remainingSamples;
628 return numSamples;
629 }
630
631 /* Read short data out of the stream. Sometimes no data will be available, and
632 zero is returned, which is not an error condition. */
sonicReadShortFromStream(sonicStream stream,short * samples,int maxSamples)633 int sonicReadShortFromStream(sonicStream stream, short* samples,
634 int maxSamples) {
635 int numSamples = stream->numOutputSamples;
636 int remainingSamples = 0;
637
638 if (numSamples == 0) {
639 return 0;
640 }
641 if (numSamples > maxSamples) {
642 remainingSamples = numSamples - maxSamples;
643 numSamples = maxSamples;
644 }
645 memcpy(samples, stream->outputBuffer,
646 numSamples * sizeof(short) * stream->numChannels);
647 if (remainingSamples > 0) {
648 memmove(stream->outputBuffer,
649 stream->outputBuffer + numSamples * stream->numChannels,
650 remainingSamples * sizeof(short) * stream->numChannels);
651 }
652 stream->numOutputSamples = remainingSamples;
653 return numSamples;
654 }
655
656 /* Read unsigned char data out of the stream. Sometimes no data will be
657 available, and zero is returned, which is not an error condition. */
sonicReadUnsignedCharFromStream(sonicStream stream,unsigned char * samples,int maxSamples)658 int sonicReadUnsignedCharFromStream(sonicStream stream, unsigned char* samples,
659 int maxSamples) {
660 int numSamples = stream->numOutputSamples;
661 int remainingSamples = 0;
662 short* buffer;
663 int count;
664
665 if (numSamples == 0) {
666 return 0;
667 }
668 if (numSamples > maxSamples) {
669 remainingSamples = numSamples - maxSamples;
670 numSamples = maxSamples;
671 }
672 buffer = stream->outputBuffer;
673 count = numSamples * stream->numChannels;
674 while (count--) {
675 *samples++ = (char)((*buffer++) >> 8) + 128;
676 }
677 if (remainingSamples > 0) {
678 memmove(stream->outputBuffer,
679 stream->outputBuffer + numSamples * stream->numChannels,
680 remainingSamples * sizeof(short) * stream->numChannels);
681 }
682 stream->numOutputSamples = remainingSamples;
683 return numSamples;
684 }
685
686 /* Force the sonic stream to generate output using whatever data it currently
687 has. No extra delay will be added to the output, but flushing in the middle
688 of words could introduce distortion. */
sonicFlushStream(sonicStream stream)689 int sonicFlushStream(sonicStream stream) {
690 int maxRequired = stream->maxRequired;
691 int remainingSamples = stream->numInputSamples;
692 float speed = stream->speed / stream->pitch;
693 float rate = stream->rate * stream->pitch;
694 int expectedOutputSamples =
695 stream->numOutputSamples +
696 (int)((remainingSamples / speed + stream->numPitchSamples) / rate + 0.5f);
697
698 /* Add enough silence to flush both input and pitch buffers. */
699 if (!enlargeInputBufferIfNeeded(stream, remainingSamples + 2 * maxRequired)) {
700 return 0;
701 }
702 memset(stream->inputBuffer + remainingSamples * stream->numChannels, 0,
703 2 * maxRequired * sizeof(short) * stream->numChannels);
704 stream->numInputSamples += 2 * maxRequired;
705 if (!sonicWriteShortToStream(stream, NULL, 0)) {
706 return 0;
707 }
708 /* Throw away any extra samples we generated due to the silence we added */
709 if (stream->numOutputSamples > expectedOutputSamples) {
710 stream->numOutputSamples = expectedOutputSamples;
711 }
712 /* Empty input and pitch buffers */
713 stream->numInputSamples = 0;
714 stream->inputPlayTime = 0.0f;
715 stream->timeError = 0.0f;
716 stream->numPitchSamples = 0;
717 return 1;
718 }
719
720 /* Return the number of samples in the output buffer */
sonicSamplesAvailable(sonicStream stream)721 int sonicSamplesAvailable(sonicStream stream) {
722 return stream->numOutputSamples;
723 }
724
725 /* If skip is greater than one, average skip samples together and write them to
726 the down-sample buffer. If numChannels is greater than one, mix the channels
727 together as we down sample. */
downSampleInput(sonicStream stream,short * samples,int skip)728 static void downSampleInput(sonicStream stream, short* samples, int skip) {
729 int numSamples = stream->maxRequired / skip;
730 int samplesPerValue = stream->numChannels * skip;
731 int i, j;
732 int value;
733 short* downSamples = stream->downSampleBuffer;
734
735 for (i = 0; i < numSamples; i++) {
736 value = 0;
737 for (j = 0; j < samplesPerValue; j++) {
738 value += *samples++;
739 }
740 value /= samplesPerValue;
741 *downSamples++ = value;
742 }
743 }
744
745 /* Find the best frequency match in the range, and given a sample skip multiple.
746 For now, just find the pitch of the first channel. */
findPitchPeriodInRange(short * samples,int minPeriod,int maxPeriod,int * retMinDiff,int * retMaxDiff)747 static int findPitchPeriodInRange(short* samples, int minPeriod, int maxPeriod,
748 int* retMinDiff, int* retMaxDiff) {
749 int period, bestPeriod = 0, worstPeriod = 255;
750 short* s;
751 short* p;
752 short sVal, pVal;
753 unsigned long diff, minDiff = 1, maxDiff = 0;
754 int i;
755
756 for (period = minPeriod; period <= maxPeriod; period++) {
757 diff = 0;
758 s = samples;
759 p = samples + period;
760 for (i = 0; i < period; i++) {
761 sVal = *s++;
762 pVal = *p++;
763 diff += sVal >= pVal ? (unsigned short)(sVal - pVal)
764 : (unsigned short)(pVal - sVal);
765 }
766 /* Note that the highest number of samples we add into diff will be less
767 than 256, since we skip samples. Thus, diff is a 24 bit number, and
768 we can safely multiply by numSamples without overflow */
769 if (bestPeriod == 0 || diff * bestPeriod < minDiff * period) {
770 minDiff = diff;
771 bestPeriod = period;
772 }
773 if (diff * worstPeriod > maxDiff * period) {
774 maxDiff = diff;
775 worstPeriod = period;
776 }
777 }
778 *retMinDiff = minDiff / bestPeriod;
779 *retMaxDiff = maxDiff / worstPeriod;
780 return bestPeriod;
781 }
782
783 /* At abrupt ends of voiced words, we can have pitch periods that are better
784 approximated by the previous pitch period estimate. Try to detect this case.
785 */
prevPeriodBetter(sonicStream stream,int minDiff,int maxDiff,int preferNewPeriod)786 static int prevPeriodBetter(sonicStream stream, int minDiff,
787 int maxDiff, int preferNewPeriod) {
788 if (minDiff == 0 || stream->prevPeriod == 0) {
789 return 0;
790 }
791 if (preferNewPeriod) {
792 if (maxDiff > minDiff * 3) {
793 /* Got a reasonable match this period */
794 return 0;
795 }
796 if (minDiff * 2 <= stream->prevMinDiff * 3) {
797 /* Mismatch is not that much greater this period */
798 return 0;
799 }
800 } else {
801 if (minDiff <= stream->prevMinDiff) {
802 return 0;
803 }
804 }
805 return 1;
806 }
807
808 /* Find the pitch period. This is a critical step, and we may have to try
809 multiple ways to get a good answer. This version uses Average Magnitude
810 Difference Function (AMDF). To improve speed, we down sample by an integer
811 factor get in the 11KHz range, and then do it again with a narrower
812 frequency range without down sampling */
findPitchPeriod(sonicStream stream,short * samples,int preferNewPeriod)813 static int findPitchPeriod(sonicStream stream, short* samples,
814 int preferNewPeriod) {
815 int minPeriod = stream->minPeriod;
816 int maxPeriod = stream->maxPeriod;
817 int minDiff, maxDiff, retPeriod;
818 int skip = computeSkip(stream);
819 int period;
820
821 if (stream->numChannels == 1 && skip == 1) {
822 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff,
823 &maxDiff);
824 } else {
825 downSampleInput(stream, samples, skip);
826 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod / skip,
827 maxPeriod / skip, &minDiff, &maxDiff);
828 if (skip != 1) {
829 period *= skip;
830 minPeriod = period - (skip << 2);
831 maxPeriod = period + (skip << 2);
832 if (minPeriod < stream->minPeriod) {
833 minPeriod = stream->minPeriod;
834 }
835 if (maxPeriod > stream->maxPeriod) {
836 maxPeriod = stream->maxPeriod;
837 }
838 if (stream->numChannels == 1) {
839 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff,
840 &maxDiff);
841 } else {
842 downSampleInput(stream, samples, 1);
843 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod,
844 maxPeriod, &minDiff, &maxDiff);
845 }
846 }
847 }
848 if (prevPeriodBetter(stream, minDiff, maxDiff, preferNewPeriod)) {
849 retPeriod = stream->prevPeriod;
850 } else {
851 retPeriod = period;
852 }
853 stream->prevMinDiff = minDiff;
854 stream->prevPeriod = period;
855 return retPeriod;
856 }
857
858 /* Overlap two sound segments, ramp the volume of one down, while ramping the
859 other one from zero up, and add them, storing the result at the output. */
overlapAdd(int numSamples,int numChannels,short * out,short * rampDown,short * rampUp)860 static void overlapAdd(int numSamples, int numChannels, short* out,
861 short* rampDown, short* rampUp) {
862 short* o;
863 short* u;
864 short* d;
865 int i, t;
866
867 for (i = 0; i < numChannels; i++) {
868 o = out + i;
869 u = rampUp + i;
870 d = rampDown + i;
871 for (t = 0; t < numSamples; t++) {
872 #ifdef SONIC_USE_SIN
873 float ratio = sin(t * M_PI / (2 * numSamples));
874 *o = *d * (1.0f - ratio) + *u * ratio;
875 #else
876 *o = (*d * (numSamples - t) + *u * t) / numSamples;
877 #endif
878 o += numChannels;
879 d += numChannels;
880 u += numChannels;
881 }
882 }
883 }
884
885 /* Just move the new samples in the output buffer to the pitch buffer */
moveNewSamplesToPitchBuffer(sonicStream stream,int originalNumOutputSamples)886 static int moveNewSamplesToPitchBuffer(sonicStream stream,
887 int originalNumOutputSamples) {
888 int numSamples = stream->numOutputSamples - originalNumOutputSamples;
889 int numChannels = stream->numChannels;
890
891 if (stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
892 int pitchBufferSize = stream->pitchBufferSize;
893 stream->pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
894 stream->pitchBuffer = (short*)sonicRealloc(
895 stream->pitchBuffer,
896 pitchBufferSize,
897 stream->pitchBufferSize,
898 sizeof(short) * numChannels);
899 }
900 memcpy(stream->pitchBuffer + stream->numPitchSamples * numChannels,
901 stream->outputBuffer + originalNumOutputSamples * numChannels,
902 numSamples * sizeof(short) * numChannels);
903 stream->numOutputSamples = originalNumOutputSamples;
904 stream->numPitchSamples += numSamples;
905 return 1;
906 }
907
908 /* Remove processed samples from the pitch buffer. */
removePitchSamples(sonicStream stream,int numSamples)909 static void removePitchSamples(sonicStream stream, int numSamples) {
910 int numChannels = stream->numChannels;
911 short* source = stream->pitchBuffer + numSamples * numChannels;
912
913 if (numSamples == 0) {
914 return;
915 }
916 if (numSamples != stream->numPitchSamples) {
917 memmove(
918 stream->pitchBuffer, source,
919 (stream->numPitchSamples - numSamples) * sizeof(short) * numChannels);
920 }
921 stream->numPitchSamples -= numSamples;
922 }
923
924 /* Approximate the sinc function times a Hann window from the sinc table. */
findSincCoefficient(int i,int ratio,int width)925 static int findSincCoefficient(int i, int ratio, int width) {
926 int lobePoints = (SINC_TABLE_SIZE - 1) / SINC_FILTER_POINTS;
927 int left = i * lobePoints + (ratio * lobePoints) / width;
928 int right = left + 1;
929 int position = i * lobePoints * width + ratio * lobePoints - left * width;
930 int leftVal = sincTable[left];
931 int rightVal = sincTable[right];
932
933 return ((leftVal * (width - position) + rightVal * position) << 1) / width;
934 }
935
936 /* Return 1 if value >= 0, else -1. This represents the sign of value. */
getSign(int value)937 static int getSign(int value) { return value >= 0 ? 1 : -1; }
938
939 /* Interpolate the new output sample. */
interpolate(sonicStream stream,short * in,int oldSampleRate,int newSampleRate)940 static short interpolate(sonicStream stream, short* in, int oldSampleRate,
941 int newSampleRate) {
942 /* Compute N-point sinc FIR-filter here. Clip rather than overflow. */
943 int i;
944 int total = 0;
945 int position = stream->newRatePosition * oldSampleRate;
946 int leftPosition = stream->oldRatePosition * newSampleRate;
947 int rightPosition = (stream->oldRatePosition + 1) * newSampleRate;
948 int ratio = rightPosition - position - 1;
949 int width = rightPosition - leftPosition;
950 int weight, value;
951 int oldSign;
952 int overflowCount = 0;
953
954 for (i = 0; i < SINC_FILTER_POINTS; i++) {
955 weight = findSincCoefficient(i, ratio, width);
956 value = in[i * stream->numChannels] * weight;
957 oldSign = getSign(total);
958 total += value;
959 if (oldSign != getSign(total) && getSign(value) == oldSign) {
960 /* We must have overflowed. This can happen with a sinc filter. */
961 overflowCount += oldSign;
962 }
963 }
964 /* It is better to clip than to wrap if there was a overflow. */
965 if (overflowCount > 0) {
966 return SHRT_MAX;
967 } else if (overflowCount < 0) {
968 return SHRT_MIN;
969 }
970 return total >> 16;
971 }
972
973 /* Change the rate. Interpolate with a sinc FIR filter using a Hann window. */
adjustRate(sonicStream stream,float rate,int originalNumOutputSamples)974 static int adjustRate(sonicStream stream, float rate,
975 int originalNumOutputSamples) {
976 int newSampleRate = stream->sampleRate / rate;
977 int oldSampleRate = stream->sampleRate;
978 int numChannels = stream->numChannels;
979 int position;
980 short *in, *out;
981 int i;
982 int N = SINC_FILTER_POINTS;
983
984 /* Set these values to help with the integer math */
985 while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
986 newSampleRate >>= 1;
987 oldSampleRate >>= 1;
988 }
989 if (stream->numOutputSamples == originalNumOutputSamples) {
990 return 1;
991 }
992 if (!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
993 return 0;
994 }
995 /* Leave at least N pitch sample in the buffer */
996 for (position = 0; position < stream->numPitchSamples - N; position++) {
997 while ((stream->oldRatePosition + 1) * newSampleRate >
998 stream->newRatePosition * oldSampleRate) {
999 if (!enlargeOutputBufferIfNeeded(stream, 1)) {
1000 return 0;
1001 }
1002 out = stream->outputBuffer + stream->numOutputSamples * numChannels;
1003 in = stream->pitchBuffer + position * numChannels;
1004 for (i = 0; i < numChannels; i++) {
1005 *out++ = interpolate(stream, in, oldSampleRate, newSampleRate);
1006 in++;
1007 }
1008 stream->newRatePosition++;
1009 stream->numOutputSamples++;
1010 }
1011 stream->oldRatePosition++;
1012 if (stream->oldRatePosition == oldSampleRate) {
1013 stream->oldRatePosition = 0;
1014 stream->newRatePosition = 0;
1015 }
1016 }
1017 removePitchSamples(stream, position);
1018 return 1;
1019 }
1020
1021 /* Skip over a pitch period. Return the number of output samples. */
skipPitchPeriod(sonicStream stream,short * samples,float speed,int period)1022 static int skipPitchPeriod(sonicStream stream, short* samples, float speed,
1023 int period) {
1024 long newSamples;
1025 int numChannels = stream->numChannels;
1026
1027 if (speed >= 2.0f) {
1028 /* For speeds >= 2.0, we skip over a portion of each pitch period rather
1029 than dropping whole pitch periods. */
1030 newSamples = period / (speed - 1.0f);
1031 } else {
1032 newSamples = period;
1033 }
1034 if (!enlargeOutputBufferIfNeeded(stream, newSamples)) {
1035 return 0;
1036 }
1037 overlapAdd(newSamples, numChannels,
1038 stream->outputBuffer + stream->numOutputSamples * numChannels,
1039 samples, samples + period * numChannels);
1040 stream->numOutputSamples += newSamples;
1041 return newSamples;
1042 }
1043
1044 /* Insert a pitch period, and determine how much input to copy directly. */
insertPitchPeriod(sonicStream stream,short * samples,float speed,int period)1045 static int insertPitchPeriod(sonicStream stream, short* samples, float speed,
1046 int period) {
1047 long newSamples;
1048 short* out;
1049 int numChannels = stream->numChannels;
1050
1051 if (speed <= 0.5f) {
1052 newSamples = period * speed / (1.0f - speed);
1053 } else {
1054 newSamples = period;
1055 }
1056 if (!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
1057 return 0;
1058 }
1059 out = stream->outputBuffer + stream->numOutputSamples * numChannels;
1060 memcpy(out, samples, period * sizeof(short) * numChannels);
1061 out =
1062 stream->outputBuffer + (stream->numOutputSamples + period) * numChannels;
1063 overlapAdd(newSamples, numChannels, out, samples + period * numChannels,
1064 samples);
1065 stream->numOutputSamples += period + newSamples;
1066 return newSamples;
1067 }
1068
1069 /* PICOLA copies input to output until the total output samples == consumed
1070 input samples * speed. */
copyUnmodifiedSamples(sonicStream stream,short * samples,float speed,int position,int * newSamples)1071 static int copyUnmodifiedSamples(sonicStream stream, short* samples,
1072 float speed, int position, int* newSamples) {
1073 int availableSamples = stream->numInputSamples - position;
1074 float inputToCopyFloat =
1075 1 - stream->timeError * speed / (stream->samplePeriod * (speed - 1.0));
1076
1077 *newSamples = inputToCopyFloat > availableSamples ? availableSamples
1078 : (int)inputToCopyFloat;
1079 if (!copyToOutput(stream, samples, *newSamples)) {
1080 return 0;
1081 }
1082 stream->timeError +=
1083 *newSamples * stream->samplePeriod * (speed - 1.0) / speed;
1084 return 1;
1085 }
1086
1087 /* Resample as many pitch periods as we have buffered on the input. Return 0 if
1088 we fail to resize an input or output buffer. */
changeSpeed(sonicStream stream,float speed)1089 static int changeSpeed(sonicStream stream, float speed) {
1090 short* samples;
1091 int numSamples = stream->numInputSamples;
1092 int position = 0, period, newSamples;
1093 int maxRequired = stream->maxRequired;
1094
1095 if (stream->numInputSamples < maxRequired) {
1096 return 1;
1097 }
1098 do {
1099 samples = stream->inputBuffer + position * stream->numChannels;
1100 if ((speed > 1.0f && speed < 2.0f && stream->timeError < 0.0f) ||
1101 (speed < 1.0f && speed > 0.5f && stream->timeError > 0.0f)) {
1102 /* Deal with the case where PICOLA is still copying input samples to
1103 output unmodified, */
1104 if (!copyUnmodifiedSamples(stream, samples, speed, position,
1105 &newSamples)) {
1106 return 0;
1107 }
1108 position += newSamples;
1109 } else {
1110 /* We are in the remaining cases, either inserting/removing a pitch period
1111 for speed < 2.0X, or a portion of one for speed >= 2.0X. */
1112 period = findPitchPeriod(stream, samples, 1);
1113 #ifdef SONIC_SPECTROGRAM
1114 if (stream->spectrogram != NULL) {
1115 sonicAddPitchPeriodToSpectrogram(stream->spectrogram, samples, period,
1116 stream->numChannels);
1117 newSamples = period;
1118 position += period;
1119 } else
1120 #endif /* SONIC_SPECTROGRAM */
1121 if (speed > 1.0) {
1122 newSamples = skipPitchPeriod(stream, samples, speed, period);
1123 position += period + newSamples;
1124 if (speed < 2.0) {
1125 stream->timeError += newSamples * stream->samplePeriod -
1126 (period + newSamples) * stream->inputPlayTime /
1127 stream->numInputSamples;
1128 }
1129 } else {
1130 newSamples = insertPitchPeriod(stream, samples, speed, period);
1131 position += newSamples;
1132 if (speed > 0.5) {
1133 stream->timeError +=
1134 (period + newSamples) * stream->samplePeriod -
1135 newSamples * stream->inputPlayTime / stream->numInputSamples;
1136 }
1137 }
1138 if (newSamples == 0) {
1139 return 0; /* Failed to resize output buffer */
1140 }
1141 }
1142 } while (position + maxRequired <= numSamples);
1143 removeInputSamples(stream, position);
1144 return 1;
1145 }
1146
1147 /* Resample as many pitch periods as we have buffered on the input. Return 0 if
1148 we fail to resize an input or output buffer. Also scale the output by the
1149 volume. */
processStreamInput(sonicStream stream)1150 static int processStreamInput(sonicStream stream) {
1151 int originalNumOutputSamples = stream->numOutputSamples;
1152 float rate = stream->rate * stream->pitch;
1153 float localSpeed;
1154
1155 if (stream->numInputSamples == 0) {
1156 return 1;
1157 }
1158 localSpeed =
1159 stream->numInputSamples * stream->samplePeriod / stream->inputPlayTime;
1160 if (localSpeed > 1.00001 || localSpeed < 0.99999) {
1161 changeSpeed(stream, localSpeed);
1162 } else {
1163 if (!copyInputToOutput(stream, stream->numInputSamples)) {
1164 return 0;
1165 }
1166 }
1167 if (rate != 1.0f) {
1168 if (!adjustRate(stream, rate, originalNumOutputSamples)) {
1169 return 0;
1170 }
1171 }
1172 if (stream->volume != 1.0f) {
1173 /* Adjust output volume. */
1174 scaleSamples(
1175 stream->outputBuffer + originalNumOutputSamples * stream->numChannels,
1176 (stream->numOutputSamples - originalNumOutputSamples) *
1177 stream->numChannels,
1178 stream->volume);
1179 }
1180 return 1;
1181 }
1182
1183 /* Write floating point data to the input buffer and process it. */
sonicWriteFloatToStream(sonicStream stream,const float * samples,int numSamples)1184 int sonicWriteFloatToStream(sonicStream stream, const float* samples,
1185 int numSamples) {
1186 if (!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
1187 return 0;
1188 }
1189 return processStreamInput(stream);
1190 }
1191
1192 /* Simple wrapper around sonicWriteFloatToStream that does the short to float
1193 conversion for you. */
sonicWriteShortToStream(sonicStream stream,const short * samples,int numSamples)1194 int sonicWriteShortToStream(sonicStream stream, const short* samples,
1195 int numSamples) {
1196 if (!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
1197 return 0;
1198 }
1199 return processStreamInput(stream);
1200 }
1201
1202 /* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to
1203 float conversion for you. */
sonicWriteUnsignedCharToStream(sonicStream stream,const unsigned char * samples,int numSamples)1204 int sonicWriteUnsignedCharToStream(sonicStream stream, const unsigned char* samples,
1205 int numSamples) {
1206 if (!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
1207 return 0;
1208 }
1209 return processStreamInput(stream);
1210 }
1211
1212 /* This is a non-stream oriented interface to just change the speed of a sound
1213 * sample */
sonicChangeFloatSpeed(float * samples,int numSamples,float speed,float pitch,float rate,float volume,int useChordPitch,int sampleRate,int numChannels)1214 int sonicChangeFloatSpeed(float* samples, int numSamples, float speed,
1215 float pitch, float rate, float volume,
1216 int useChordPitch, int sampleRate, int numChannels) {
1217 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
1218
1219 sonicSetSpeed(stream, speed);
1220 sonicSetPitch(stream, pitch);
1221 sonicSetRate(stream, rate);
1222 sonicSetVolume(stream, volume);
1223 sonicWriteFloatToStream(stream, samples, numSamples);
1224 sonicFlushStream(stream);
1225 numSamples = sonicSamplesAvailable(stream);
1226 sonicReadFloatFromStream(stream, samples, numSamples);
1227 sonicDestroyStream(stream);
1228 return numSamples;
1229 }
1230
1231 /* This is a non-stream oriented interface to just change the speed of a sound
1232 * sample */
sonicChangeShortSpeed(short * samples,int numSamples,float speed,float pitch,float rate,float volume,int useChordPitch,int sampleRate,int numChannels)1233 int sonicChangeShortSpeed(short* samples, int numSamples, float speed,
1234 float pitch, float rate, float volume,
1235 int useChordPitch, int sampleRate, int numChannels) {
1236 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
1237
1238 sonicSetSpeed(stream, speed);
1239 sonicSetPitch(stream, pitch);
1240 sonicSetRate(stream, rate);
1241 sonicSetVolume(stream, volume);
1242 sonicWriteShortToStream(stream, samples, numSamples);
1243 sonicFlushStream(stream);
1244 numSamples = sonicSamplesAvailable(stream);
1245 sonicReadShortFromStream(stream, samples, numSamples);
1246 sonicDestroyStream(stream);
1247 return numSamples;
1248 }
1249