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 <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdarg.h>
13 #ifdef SONIC_USE_SIN
14 #include <math.h>
15 #ifndef M_PI
16 #define M_PI 3.14159265358979323846
17 #endif
18 #endif
19 #include "sonic.h"
20
21 struct sonicStreamStruct {
22 short *inputBuffer;
23 short *outputBuffer;
24 short *pitchBuffer;
25 short *downSampleBuffer;
26 float speed;
27 float volume;
28 float pitch;
29 float rate;
30 int oldRatePosition;
31 int newRatePosition;
32 int useChordPitch;
33 int quality;
34 int numChannels;
35 int inputBufferSize;
36 int pitchBufferSize;
37 int outputBufferSize;
38 int numInputSamples;
39 int numOutputSamples;
40 int numPitchSamples;
41 int minPeriod;
42 int maxPeriod;
43 int maxRequired;
44 int remainingInputToCopy;
45 int sampleRate;
46 int prevPeriod;
47 int prevMinDiff;
48 };
49
50 static float remainingSamplesForSkip = 0.0f;
51 static float remainingSamplesForInsert = 0.0f;
52
53 /* Just used for debugging */
54 /*
55 void sonicMSG(char *format, ...)
56 {
57 char buffer[4096];
58 va_list ap;
59 FILE *file;
60
61 va_start(ap, format);
62 vsprintf((char *)buffer, (char *)format, ap);
63 va_end(ap);
64 file=fopen("/tmp/sonic.log", "a");
65 fprintf(file, "%s", buffer);
66 fclose(file);
67 }
68 */
69
70 /* Scale the samples by the factor. */
scaleSamples(short * samples,int numSamples,float volume)71 static void scaleSamples(
72 short *samples,
73 int numSamples,
74 float volume)
75 {
76 int fixedPointVolume = volume*4096.0f;
77 int value;
78
79 while(numSamples--) {
80 value = (*samples*fixedPointVolume) >> 12;
81 if(value > 32767) {
82 value = 32767;
83 } else if(value < -32767) {
84 value = -32767;
85 }
86 *samples++ = value;
87 }
88 }
89
90 /* Get the speed of the stream. */
sonicGetSpeed(sonicStream stream)91 float sonicGetSpeed(
92 sonicStream stream)
93 {
94 return stream->speed;
95 }
96
97 /* Set the speed of the stream. */
sonicSetSpeed(sonicStream stream,float speed)98 void sonicSetSpeed(
99 sonicStream stream,
100 float speed)
101 {
102 stream->speed = speed;
103 }
104
105 /* Get the pitch of the stream. */
sonicGetPitch(sonicStream stream)106 float sonicGetPitch(
107 sonicStream stream)
108 {
109 return stream->pitch;
110 }
111
112 /* Set the pitch of the stream. */
sonicSetPitch(sonicStream stream,float pitch)113 void sonicSetPitch(
114 sonicStream stream,
115 float pitch)
116 {
117 stream->pitch = pitch;
118 }
119
120 /* Get the rate of the stream. */
sonicGetRate(sonicStream stream)121 float sonicGetRate(
122 sonicStream stream)
123 {
124 return stream->rate;
125 }
126
127 /* Set the playback rate of the stream. This scales pitch and speed at the same time. */
sonicSetRate(sonicStream stream,float rate)128 void sonicSetRate(
129 sonicStream stream,
130 float rate)
131 {
132 stream->rate = rate;
133
134 stream->oldRatePosition = 0;
135 stream->newRatePosition = 0;
136 }
137
138 /* Get the vocal chord pitch setting. */
sonicGetChordPitch(sonicStream stream)139 int sonicGetChordPitch(
140 sonicStream stream)
141 {
142 return stream->useChordPitch;
143 }
144
145 /* Set the vocal chord mode for pitch computation. Default is off. */
sonicSetChordPitch(sonicStream stream,int useChordPitch)146 void sonicSetChordPitch(
147 sonicStream stream,
148 int useChordPitch)
149 {
150 stream->useChordPitch = useChordPitch;
151 }
152
153 /* Get the quality setting. */
sonicGetQuality(sonicStream stream)154 int sonicGetQuality(
155 sonicStream stream)
156 {
157 return stream->quality;
158 }
159
160 /* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */
sonicSetQuality(sonicStream stream,int quality)161 void sonicSetQuality(
162 sonicStream stream,
163 int quality)
164 {
165 stream->quality = quality;
166 }
167
168 /* Get the scaling factor of the stream. */
sonicGetVolume(sonicStream stream)169 float sonicGetVolume(
170 sonicStream stream)
171 {
172 return stream->volume;
173 }
174
175 /* Set the scaling factor of the stream. */
sonicSetVolume(sonicStream stream,float volume)176 void sonicSetVolume(
177 sonicStream stream,
178 float volume)
179 {
180 stream->volume = volume;
181 }
182
183 /* Free stream buffers. */
freeStreamBuffers(sonicStream stream)184 static void freeStreamBuffers(
185 sonicStream stream)
186 {
187 if(stream->inputBuffer != NULL) {
188 free(stream->inputBuffer);
189 }
190 if(stream->outputBuffer != NULL) {
191 free(stream->outputBuffer);
192 }
193 if(stream->pitchBuffer != NULL) {
194 free(stream->pitchBuffer);
195 }
196 if(stream->downSampleBuffer != NULL) {
197 free(stream->downSampleBuffer);
198 }
199 }
200
201 /* Destroy the sonic stream. */
sonicDestroyStream(sonicStream stream)202 void sonicDestroyStream(
203 sonicStream stream)
204 {
205 freeStreamBuffers(stream);
206 free(stream);
207 }
208
209 /* Allocate stream buffers. */
allocateStreamBuffers(sonicStream stream,int sampleRate,int numChannels)210 static int allocateStreamBuffers(
211 sonicStream stream,
212 int sampleRate,
213 int numChannels)
214 {
215 int minPeriod = sampleRate/SONIC_MAX_PITCH;
216 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
217 int maxRequired = 2*maxPeriod;
218
219 stream->inputBufferSize = maxRequired;
220 stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
221 if(stream->inputBuffer == NULL) {
222 sonicDestroyStream(stream);
223 return 0;
224 }
225 stream->outputBufferSize = maxRequired;
226 stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
227 if(stream->outputBuffer == NULL) {
228 sonicDestroyStream(stream);
229 return 0;
230 }
231 stream->pitchBufferSize = maxRequired;
232 stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
233 if(stream->pitchBuffer == NULL) {
234 sonicDestroyStream(stream);
235 return 0;
236 }
237 stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
238 if(stream->downSampleBuffer == NULL) {
239 sonicDestroyStream(stream);
240 return 0;
241 }
242 stream->sampleRate = sampleRate;
243 stream->numChannels = numChannels;
244 stream->oldRatePosition = 0;
245 stream->newRatePosition = 0;
246 stream->minPeriod = minPeriod;
247 stream->maxPeriod = maxPeriod;
248 stream->maxRequired = maxRequired;
249 stream->prevPeriod = 0;
250 return 1;
251 }
252
253 /* Create a sonic stream. Return NULL only if we are out of memory and cannot
254 allocate the stream. */
sonicCreateStream(int sampleRate,int numChannels)255 sonicStream sonicCreateStream(
256 int sampleRate,
257 int numChannels)
258 {
259 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
260
261 if(stream == NULL) {
262 return NULL;
263 }
264 if(!allocateStreamBuffers(stream, sampleRate, numChannels)) {
265 return NULL;
266 }
267 stream->speed = 1.0f;
268 stream->pitch = 1.0f;
269 stream->volume = 1.0f;
270 stream->rate = 1.0f;
271 stream->oldRatePosition = 0;
272 stream->newRatePosition = 0;
273 stream->useChordPitch = 0;
274 stream->quality = 0;
275 return stream;
276 }
277
278 /* Get the sample rate of the stream. */
sonicGetSampleRate(sonicStream stream)279 int sonicGetSampleRate(
280 sonicStream stream)
281 {
282 return stream->sampleRate;
283 }
284
285 /* Set the sample rate of the stream. This will cause samples buffered in the stream to
286 be lost. */
sonicSetSampleRate(sonicStream stream,int sampleRate)287 void sonicSetSampleRate(
288 sonicStream stream,
289 int sampleRate)
290 {
291 freeStreamBuffers(stream);
292 allocateStreamBuffers(stream, sampleRate, stream->numChannels);
293 }
294
295 /* Get the number of channels. */
sonicGetNumChannels(sonicStream stream)296 int sonicGetNumChannels(
297 sonicStream stream)
298 {
299 return stream->numChannels;
300 }
301
302 /* Set the num channels of the stream. This will cause samples buffered in the stream to
303 be lost. */
sonicSetNumChannels(sonicStream stream,int numChannels)304 void sonicSetNumChannels(
305 sonicStream stream,
306 int numChannels)
307 {
308 freeStreamBuffers(stream);
309 allocateStreamBuffers(stream, stream->sampleRate, numChannels);
310 }
311
312 /* Enlarge the output buffer if needed. */
enlargeOutputBufferIfNeeded(sonicStream stream,int numSamples)313 static int enlargeOutputBufferIfNeeded(
314 sonicStream stream,
315 int numSamples)
316 {
317 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
318 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
319 stream->outputBuffer = (short *)realloc(stream->outputBuffer,
320 stream->outputBufferSize*sizeof(short)*stream->numChannels);
321 if(stream->outputBuffer == NULL) {
322 return 0;
323 }
324 }
325 return 1;
326 }
327
328 /* Enlarge the input buffer if needed. */
enlargeInputBufferIfNeeded(sonicStream stream,int numSamples)329 static int enlargeInputBufferIfNeeded(
330 sonicStream stream,
331 int numSamples)
332 {
333 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
334 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
335 stream->inputBuffer = (short *)realloc(stream->inputBuffer,
336 stream->inputBufferSize*sizeof(short)*stream->numChannels);
337 if(stream->inputBuffer == NULL) {
338 return 0;
339 }
340 }
341 return 1;
342 }
343
344 /* Add the input samples to the input buffer. */
addFloatSamplesToInputBuffer(sonicStream stream,float * samples,int numSamples)345 static int addFloatSamplesToInputBuffer(
346 sonicStream stream,
347 float *samples,
348 int numSamples)
349 {
350 short *buffer;
351 int count = numSamples*stream->numChannels;
352
353 if(numSamples == 0) {
354 return 1;
355 }
356 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
357 return 0;
358 }
359 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
360 while(count--) {
361 *buffer++ = (*samples++)*32767.0f;
362 }
363 stream->numInputSamples += numSamples;
364 return 1;
365 }
366
367 /* Add the input samples to the input buffer. */
addShortSamplesToInputBuffer(sonicStream stream,short * samples,int numSamples)368 static int addShortSamplesToInputBuffer(
369 sonicStream stream,
370 short *samples,
371 int numSamples)
372 {
373 if(numSamples == 0) {
374 return 1;
375 }
376 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
377 return 0;
378 }
379 memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
380 numSamples*sizeof(short)*stream->numChannels);
381 stream->numInputSamples += numSamples;
382 return 1;
383 }
384
385 /* Add the input samples to the input buffer. */
addUnsignedCharSamplesToInputBuffer(sonicStream stream,unsigned char * samples,int numSamples)386 static int addUnsignedCharSamplesToInputBuffer(
387 sonicStream stream,
388 unsigned char *samples,
389 int numSamples)
390 {
391 short *buffer;
392 int count = numSamples*stream->numChannels;
393
394 if(numSamples == 0) {
395 return 1;
396 }
397 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
398 return 0;
399 }
400 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
401 while(count--) {
402 *buffer++ = (*samples++ - 128) << 8;
403 }
404 stream->numInputSamples += numSamples;
405 return 1;
406 }
407
408 /* Remove input samples that we have already processed. */
removeInputSamples(sonicStream stream,int position)409 static void removeInputSamples(
410 sonicStream stream,
411 int position)
412 {
413 int remainingSamples = stream->numInputSamples - position;
414
415 if(remainingSamples > 0) {
416 memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
417 remainingSamples*sizeof(short)*stream->numChannels);
418 }
419 stream->numInputSamples = remainingSamples;
420 }
421
422 /* Just copy from the array to the output buffer */
copyToOutput(sonicStream stream,short * samples,int numSamples)423 static int copyToOutput(
424 sonicStream stream,
425 short *samples,
426 int numSamples)
427 {
428 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
429 return 0;
430 }
431 memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
432 samples, numSamples*sizeof(short)*stream->numChannels);
433 stream->numOutputSamples += numSamples;
434 return 1;
435 }
436
437 /* Just copy from the input buffer to the output buffer. Return 0 if we fail to
438 resize the output buffer. Otherwise, return numSamples */
copyInputToOutput(sonicStream stream,int position)439 static int copyInputToOutput(
440 sonicStream stream,
441 int position)
442 {
443 int numSamples = stream->remainingInputToCopy;
444
445 if(numSamples > stream->maxRequired) {
446 numSamples = stream->maxRequired;
447 }
448 if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels,
449 numSamples)) {
450 return 0;
451 }
452 stream->remainingInputToCopy -= numSamples;
453 return numSamples;
454 }
455
456 /* Read data out of the stream. Sometimes no data will be available, and zero
457 is returned, which is not an error condition. */
sonicReadFloatFromStream(sonicStream stream,float * samples,int maxSamples)458 int sonicReadFloatFromStream(
459 sonicStream stream,
460 float *samples,
461 int maxSamples)
462 {
463 int numSamples = stream->numOutputSamples;
464 int remainingSamples = 0;
465 short *buffer;
466 int count;
467
468 if(numSamples == 0) {
469 return 0;
470 }
471 if(numSamples > maxSamples) {
472 remainingSamples = numSamples - maxSamples;
473 numSamples = maxSamples;
474 }
475 buffer = stream->outputBuffer;
476 count = numSamples*stream->numChannels;
477 while(count--) {
478 *samples++ = (*buffer++)/32767.0f;
479 }
480 if(remainingSamples > 0) {
481 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
482 remainingSamples*sizeof(short)*stream->numChannels);
483 }
484 stream->numOutputSamples = remainingSamples;
485 return numSamples;
486 }
487
488 /* Read short data out of the stream. Sometimes no data will be available, and zero
489 is returned, which is not an error condition. */
sonicReadShortFromStream(sonicStream stream,short * samples,int maxSamples)490 int sonicReadShortFromStream(
491 sonicStream stream,
492 short *samples,
493 int maxSamples)
494 {
495 int numSamples = stream->numOutputSamples;
496 int remainingSamples = 0;
497
498 if(numSamples == 0) {
499 return 0;
500 }
501 if(numSamples > maxSamples) {
502 remainingSamples = numSamples - maxSamples;
503 numSamples = maxSamples;
504 }
505 memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
506 if(remainingSamples > 0) {
507 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
508 remainingSamples*sizeof(short)*stream->numChannels);
509 }
510 stream->numOutputSamples = remainingSamples;
511 return numSamples;
512 }
513
514 /* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
515 is returned, which is not an error condition. */
sonicReadUnsignedCharFromStream(sonicStream stream,unsigned char * samples,int maxSamples)516 int sonicReadUnsignedCharFromStream(
517 sonicStream stream,
518 unsigned char *samples,
519 int maxSamples)
520 {
521 int numSamples = stream->numOutputSamples;
522 int remainingSamples = 0;
523 short *buffer;
524 int count;
525
526 if(numSamples == 0) {
527 return 0;
528 }
529 if(numSamples > maxSamples) {
530 remainingSamples = numSamples - maxSamples;
531 numSamples = maxSamples;
532 }
533 buffer = stream->outputBuffer;
534 count = numSamples*stream->numChannels;
535 while(count--) {
536 *samples++ = (char)((*buffer++) >> 8) + 128;
537 }
538 if(remainingSamples > 0) {
539 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
540 remainingSamples*sizeof(short)*stream->numChannels);
541 }
542 stream->numOutputSamples = remainingSamples;
543 return numSamples;
544 }
545
546 /* Force the sonic stream to generate output using whatever data it currently
547 has. No extra delay will be added to the output, but flushing in the middle of
548 words could introduce distortion. */
sonicFlushStream(sonicStream stream)549 int sonicFlushStream(
550 sonicStream stream)
551 {
552 int maxRequired = stream->maxRequired;
553 int remainingSamples = stream->numInputSamples;
554 float speed = stream->speed/stream->pitch;
555 float rate = stream->rate*stream->pitch;
556 int expectedOutputSamples = stream->numOutputSamples +
557 (int)((remainingSamples/speed + stream->numPitchSamples)/rate + 0.5f);
558
559 /* Add enough silence to flush both input and pitch buffers. */
560 if(!enlargeInputBufferIfNeeded(stream, remainingSamples + 2*maxRequired)) {
561 return 0;
562 }
563 memset(stream->inputBuffer + remainingSamples*stream->numChannels, 0,
564 2*maxRequired*sizeof(short)*stream->numChannels);
565 stream->numInputSamples += 2*maxRequired;
566 if(!sonicWriteShortToStream(stream, NULL, 0)) {
567 return 0;
568 }
569 /* Throw away any extra samples we generated due to the silence we added */
570 if(stream->numOutputSamples > expectedOutputSamples) {
571 stream->numOutputSamples = expectedOutputSamples;
572 }
573 /* Empty input and pitch buffers */
574 stream->numInputSamples = 0;
575 stream->remainingInputToCopy = 0;
576 stream->numPitchSamples = 0;
577 return 1;
578 }
579
580 /* Return the number of samples in the output buffer */
sonicSamplesAvailable(sonicStream stream)581 int sonicSamplesAvailable(
582 sonicStream stream)
583 {
584 return stream->numOutputSamples;
585 }
586
587 /* If skip is greater than one, average skip samples together and write them to
588 the down-sample buffer. If numChannels is greater than one, mix the channels
589 together as we down sample. */
downSampleInput(sonicStream stream,short * samples,int skip)590 static void downSampleInput(
591 sonicStream stream,
592 short *samples,
593 int skip)
594 {
595 int numSamples = stream->maxRequired/skip;
596 int samplesPerValue = stream->numChannels*skip;
597 int i, j;
598 int value;
599 short *downSamples = stream->downSampleBuffer;
600
601 for(i = 0; i < numSamples; i++) {
602 value = 0;
603 for(j = 0; j < samplesPerValue; j++) {
604 value += *samples++;
605 }
606 value /= samplesPerValue;
607 *downSamples++ = value;
608 }
609 }
610
611 /* Find the best frequency match in the range, and given a sample skip multiple.
612 For now, just find the pitch of the first channel. */
findPitchPeriodInRange(short * samples,int minPeriod,int maxPeriod,int * retMinDiff,int * retMaxDiff)613 static int findPitchPeriodInRange(
614 short *samples,
615 int minPeriod,
616 int maxPeriod,
617 int *retMinDiff,
618 int *retMaxDiff)
619 {
620 int period, bestPeriod = 0, worstPeriod = 255;
621 short *s, *p, sVal, pVal;
622 unsigned long diff, minDiff = 1, maxDiff = 0;
623 int i;
624
625 for(period = minPeriod; period <= maxPeriod; period++) {
626 diff = 0;
627 s = samples;
628 p = samples + period;
629 for(i = 0; i < period; i++) {
630 sVal = *s++;
631 pVal = *p++;
632 diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
633 (unsigned short)(pVal - sVal);
634 }
635 /* Note that the highest number of samples we add into diff will be less
636 than 256, since we skip samples. Thus, diff is a 24 bit number, and
637 we can safely multiply by numSamples without overflow */
638 if(diff*bestPeriod < minDiff*period) {
639 minDiff = diff;
640 bestPeriod = period;
641 }
642 if(diff*worstPeriod > maxDiff*period) {
643 maxDiff = diff;
644 worstPeriod = period;
645 }
646 }
647 *retMinDiff = minDiff/bestPeriod;
648 *retMaxDiff = maxDiff/worstPeriod;
649 return bestPeriod;
650 }
651
652 /* At abrupt ends of voiced words, we can have pitch periods that are better
653 approximated by the previous pitch period estimate. Try to detect this case. */
prevPeriodBetter(sonicStream stream,int period,int minDiff,int maxDiff,int preferNewPeriod)654 static int prevPeriodBetter(
655 sonicStream stream,
656 int period,
657 int minDiff,
658 int maxDiff,
659 int preferNewPeriod)
660 {
661 if(minDiff == 0 || stream->prevPeriod == 0) {
662 return 0;
663 }
664 if(preferNewPeriod) {
665 if(maxDiff > minDiff*3) {
666 /* Got a reasonable match this period */
667 return 0;
668 }
669 if(minDiff*2 <= stream->prevMinDiff*3) {
670 /* Mismatch is not that much greater this period */
671 return 0;
672 }
673 } else {
674 if(minDiff <= stream->prevMinDiff) {
675 return 0;
676 }
677 }
678 return 1;
679 }
680
681 /* Find the pitch period. This is a critical step, and we may have to try
682 multiple ways to get a good answer. This version uses AMDF. To improve
683 speed, we down sample by an integer factor get in the 11KHz range, and then
684 do it again with a narrower frequency range without down sampling */
findPitchPeriod(sonicStream stream,short * samples,int preferNewPeriod)685 static int findPitchPeriod(
686 sonicStream stream,
687 short *samples,
688 int preferNewPeriod)
689 {
690 int minPeriod = stream->minPeriod;
691 int maxPeriod = stream->maxPeriod;
692 int sampleRate = stream->sampleRate;
693 int minDiff, maxDiff, retPeriod;
694 int skip = 1;
695 int period;
696
697 if(sampleRate > SONIC_AMDF_FREQ && stream->quality == 0) {
698 skip = sampleRate/SONIC_AMDF_FREQ;
699 }
700 if(stream->numChannels == 1 && skip == 1) {
701 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff, &maxDiff);
702 } else {
703 downSampleInput(stream, samples, skip);
704 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip,
705 maxPeriod/skip, &minDiff, &maxDiff);
706 if(skip != 1) {
707 period *= skip;
708 minPeriod = period - (skip << 2);
709 maxPeriod = period + (skip << 2);
710 if(minPeriod < stream->minPeriod) {
711 minPeriod = stream->minPeriod;
712 }
713 if(maxPeriod > stream->maxPeriod) {
714 maxPeriod = stream->maxPeriod;
715 }
716 if(stream->numChannels == 1) {
717 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod,
718 &minDiff, &maxDiff);
719 } else {
720 downSampleInput(stream, samples, 1);
721 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod,
722 maxPeriod, &minDiff, &maxDiff);
723 }
724 }
725 }
726 if(prevPeriodBetter(stream, period, minDiff, maxDiff, preferNewPeriod)) {
727 retPeriod = stream->prevPeriod;
728 } else {
729 retPeriod = period;
730 }
731 stream->prevMinDiff = minDiff;
732 stream->prevPeriod = period;
733 return retPeriod;
734 }
735
736 /* Overlap two sound segments, ramp the volume of one down, while ramping the
737 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)738 static void overlapAdd(
739 int numSamples,
740 int numChannels,
741 short *out,
742 short *rampDown,
743 short *rampUp)
744 {
745 short *o, *u, *d;
746 int i, t;
747
748 for(i = 0; i < numChannels; i++) {
749 o = out + i;
750 u = rampUp + i;
751 d = rampDown + i;
752 for(t = 0; t < numSamples; t++) {
753 #ifdef SONIC_USE_SIN
754 float ratio = sin(t*M_PI/(2*numSamples));
755 *o = *d*(1.0f - ratio) + *u*ratio;
756 #else
757 *o = (*d*(numSamples - t) + *u*t)/numSamples;
758 #endif
759 o += numChannels;
760 d += numChannels;
761 u += numChannels;
762 }
763 }
764 }
765
766 /* Overlap two sound segments, ramp the volume of one down, while ramping the
767 other one from zero up, and add them, storing the result at the output. */
overlapAddWithSeparation(int numSamples,int numChannels,int separation,short * out,short * rampDown,short * rampUp)768 static void overlapAddWithSeparation(
769 int numSamples,
770 int numChannels,
771 int separation,
772 short *out,
773 short *rampDown,
774 short *rampUp)
775 {
776 short *o, *u, *d;
777 int i, t;
778
779 for(i = 0; i < numChannels; i++) {
780 o = out + i;
781 u = rampUp + i;
782 d = rampDown + i;
783 for(t = 0; t < numSamples + separation; t++) {
784 if(t < separation) {
785 *o = *d*(numSamples - t)/numSamples;
786 d += numChannels;
787 } else if(t < numSamples) {
788 *o = (*d*(numSamples - t) + *u*(t - separation))/numSamples;
789 d += numChannels;
790 u += numChannels;
791 } else {
792 *o = *u*(t - separation)/numSamples;
793 u += numChannels;
794 }
795 o += numChannels;
796 }
797 }
798 }
799
800 /* Just move the new samples in the output buffer to the pitch buffer */
moveNewSamplesToPitchBuffer(sonicStream stream,int originalNumOutputSamples)801 static int moveNewSamplesToPitchBuffer(
802 sonicStream stream,
803 int originalNumOutputSamples)
804 {
805 int numSamples = stream->numOutputSamples - originalNumOutputSamples;
806 int numChannels = stream->numChannels;
807
808 if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
809 stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples;
810 stream->pitchBuffer = (short *)realloc(stream->pitchBuffer,
811 stream->pitchBufferSize*sizeof(short)*numChannels);
812 if(stream->pitchBuffer == NULL) {
813 return 0;
814 }
815 }
816 memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels,
817 stream->outputBuffer + originalNumOutputSamples*numChannels,
818 numSamples*sizeof(short)*numChannels);
819 stream->numOutputSamples = originalNumOutputSamples;
820 stream->numPitchSamples += numSamples;
821 return 1;
822 }
823
824 /* Remove processed samples from the pitch buffer. */
removePitchSamples(sonicStream stream,int numSamples)825 static void removePitchSamples(
826 sonicStream stream,
827 int numSamples)
828 {
829 int numChannels = stream->numChannels;
830 short *source = stream->pitchBuffer + numSamples*numChannels;
831
832 if(numSamples == 0) {
833 return;
834 }
835 if(numSamples != stream->numPitchSamples) {
836 memmove(stream->pitchBuffer, source, (stream->numPitchSamples -
837 numSamples)*sizeof(short)*numChannels);
838 }
839 stream->numPitchSamples -= numSamples;
840 }
841
842 /* Change the pitch. The latency this introduces could be reduced by looking at
843 past samples to determine pitch, rather than future. */
adjustPitch(sonicStream stream,int originalNumOutputSamples)844 static int adjustPitch(
845 sonicStream stream,
846 int originalNumOutputSamples)
847 {
848 float pitch = stream->pitch;
849 int numChannels = stream->numChannels;
850 int period, newPeriod, separation;
851 int position = 0;
852 short *out, *rampDown, *rampUp;
853
854 if(stream->numOutputSamples == originalNumOutputSamples) {
855 return 1;
856 }
857 if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
858 return 0;
859 }
860 while(stream->numPitchSamples - position >= stream->maxRequired) {
861 period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels, 0);
862 newPeriod = period/pitch;
863 if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) {
864 return 0;
865 }
866 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
867 if(pitch >= 1.0f) {
868 rampDown = stream->pitchBuffer + position*numChannels;
869 rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels;
870 overlapAdd(newPeriod, numChannels, out, rampDown, rampUp);
871 } else {
872 rampDown = stream->pitchBuffer + position*numChannels;
873 rampUp = stream->pitchBuffer + position*numChannels;
874 separation = newPeriod - period;
875 overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp);
876 }
877 stream->numOutputSamples += newPeriod;
878 position += period;
879 }
880 removePitchSamples(stream, position);
881 return 1;
882 }
883
884 /* Interpolate the new output sample. */
interpolate(sonicStream stream,short * in,int oldSampleRate,int newSampleRate)885 static short interpolate(
886 sonicStream stream,
887 short *in,
888 int oldSampleRate,
889 int newSampleRate)
890 {
891 short left = *in;
892 short right = in[stream->numChannels];
893 int position = stream->newRatePosition*oldSampleRate;
894 int leftPosition = stream->oldRatePosition*newSampleRate;
895 int rightPosition = (stream->oldRatePosition + 1)*newSampleRate;
896 int ratio = rightPosition - position;
897 int width = rightPosition - leftPosition;
898
899 return (ratio*left + (width - ratio)*right)/width;
900 }
901
902 /* Change the rate. */
adjustRate(sonicStream stream,float rate,int originalNumOutputSamples)903 static int adjustRate(
904 sonicStream stream,
905 float rate,
906 int originalNumOutputSamples)
907 {
908 int newSampleRate = stream->sampleRate/rate;
909 int oldSampleRate = stream->sampleRate;
910 int numChannels = stream->numChannels;
911 int position = 0;
912 short *in, *out;
913 int i;
914
915 /* Set these values to help with the integer math */
916 while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
917 newSampleRate >>= 1;
918 oldSampleRate >>= 1;
919 }
920 if(stream->numOutputSamples == originalNumOutputSamples) {
921 return 1;
922 }
923 if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
924 return 0;
925 }
926 /* Leave at least one pitch sample in the buffer */
927 for(position = 0; position < stream->numPitchSamples - 1; position++) {
928 while((stream->oldRatePosition + 1)*newSampleRate >
929 stream->newRatePosition*oldSampleRate) {
930 if(!enlargeOutputBufferIfNeeded(stream, 1)) {
931 return 0;
932 }
933 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
934 in = stream->pitchBuffer + position;
935 for(i = 0; i < numChannels; i++) {
936 *out++ = interpolate(stream, in, oldSampleRate, newSampleRate);
937 in++;
938 }
939 stream->newRatePosition++;
940 stream->numOutputSamples++;
941 }
942 stream->oldRatePosition++;
943 if(stream->oldRatePosition == oldSampleRate) {
944 stream->oldRatePosition = 0;
945 if(stream->newRatePosition != newSampleRate) {
946 fprintf(stderr,
947 "Assertion failed: stream->newRatePosition != newSampleRate\n");
948 exit(1);
949 }
950 stream->newRatePosition = 0;
951 }
952 }
953 removePitchSamples(stream, position);
954 return 1;
955 }
956
957
958 /* Skip over a pitch period, and copy period/speed samples to the output */
skipPitchPeriod(sonicStream stream,short * samples,float speed,int period)959 static int skipPitchPeriod(
960 sonicStream stream,
961 short *samples,
962 float speed,
963 int period)
964 {
965 long newSamples;
966 int numChannels = stream->numChannels;
967
968 if(speed >= 2.0f) {
969 int upNewSamples = ceil((float)period / (speed - 1.0f));
970 int downNewSamples = floor((float)period / (speed - 1.0f));
971 if (remainingSamplesForSkip < 1) {
972 newSamples = downNewSamples;
973 remainingSamplesForSkip += (float)period / (speed - 1.0f) - downNewSamples;
974 } else {
975 newSamples = upNewSamples;
976 remainingSamplesForSkip += (float)period / (speed - 1.0f) - upNewSamples;
977 }
978 } else {
979 newSamples = period;
980 stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
981 }
982 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
983 return 0;
984 }
985 overlapAdd(newSamples, numChannels, stream->outputBuffer +
986 stream->numOutputSamples*numChannels, samples, samples + period*numChannels);
987 stream->numOutputSamples += newSamples;
988 return newSamples;
989 }
990
991 /* Insert a pitch period, and determine how much input to copy directly. */
insertPitchPeriod(sonicStream stream,short * samples,float speed,int period)992 static int insertPitchPeriod(
993 sonicStream stream,
994 short *samples,
995 float speed,
996 int period)
997 {
998 long newSamples;
999 short *out;
1000 int numChannels = stream->numChannels;
1001
1002 if(speed < 0.5f) {
1003 int upNewSamples = ceil((float)period * speed / (1.0f - speed));
1004 int downNewSamples = floor((float)period * speed / (1.0f - speed));
1005 if (remainingSamplesForInsert < 1) {
1006 newSamples = downNewSamples;
1007 remainingSamplesForInsert += (float)period * speed / (1.0f - speed) - downNewSamples;
1008 } else {
1009 newSamples = upNewSamples;
1010 remainingSamplesForInsert += (float)period * speed / (1.0f - speed) - upNewSamples;
1011 }
1012 } else {
1013 newSamples = period;
1014 stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
1015 }
1016 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
1017 return 0;
1018 }
1019 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
1020 memcpy(out, samples, period*sizeof(short)*numChannels);
1021 out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels;
1022 overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples);
1023 stream->numOutputSamples += period + newSamples;
1024 return newSamples;
1025 }
1026
1027 /* Resample as many pitch periods as we have buffered on the input. Return 0 if
1028 we fail to resize an input or output buffer. Also scale the output by the volume. */
changeSpeed(sonicStream stream,float speed)1029 static int changeSpeed(
1030 sonicStream stream,
1031 float speed)
1032 {
1033 short *samples;
1034 int numSamples = stream->numInputSamples;
1035 int position = 0, period, newSamples;
1036 int maxRequired = stream->maxRequired;
1037
1038 if(stream->numInputSamples < maxRequired) {
1039 return 1;
1040 }
1041 do {
1042 if(stream->remainingInputToCopy > 0) {
1043 newSamples = copyInputToOutput(stream, position);
1044 position += newSamples;
1045 } else {
1046 samples = stream->inputBuffer + position*stream->numChannels;
1047 period = findPitchPeriod(stream, samples, 1);
1048 if(speed > 1.0) {
1049 newSamples = skipPitchPeriod(stream, samples, speed, period);
1050 position += period + newSamples;
1051 } else {
1052 newSamples = insertPitchPeriod(stream, samples, speed, period);
1053 position += newSamples;
1054 }
1055 }
1056 if(newSamples == 0) {
1057 return 0; /* Failed to resize output buffer */
1058 }
1059 } while(position + maxRequired <= numSamples);
1060 removeInputSamples(stream, position);
1061 return 1;
1062 }
1063
1064 /* Resample as many pitch periods as we have buffered on the input. Return 0 if
1065 we fail to resize an input or output buffer. Also scale the output by the volume. */
processStreamInput(sonicStream stream)1066 static int processStreamInput(
1067 sonicStream stream)
1068 {
1069 int originalNumOutputSamples = stream->numOutputSamples;
1070 float speed = stream->speed/stream->pitch;
1071 float rate = stream->rate;
1072
1073 if(!stream->useChordPitch) {
1074 rate *= stream->pitch;
1075 }
1076 if(speed > 1.00001 || speed < 0.99999) {
1077 changeSpeed(stream, speed);
1078 } else {
1079 if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
1080 return 0;
1081 }
1082 stream->numInputSamples = 0;
1083 }
1084 if(stream->useChordPitch) {
1085 if(stream->pitch != 1.0f) {
1086 if(!adjustPitch(stream, originalNumOutputSamples)) {
1087 return 0;
1088 }
1089 }
1090 } else if(rate != 1.0f) {
1091 if(!adjustRate(stream, rate, originalNumOutputSamples)) {
1092 return 0;
1093 }
1094 }
1095 if(stream->volume != 1.0f) {
1096 /* Adjust output volume. */
1097 scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
1098 (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
1099 stream->volume);
1100 }
1101 return 1;
1102 }
1103
1104 /* Write floating point data to the input buffer and process it. */
sonicWriteFloatToStream(sonicStream stream,float * samples,int numSamples)1105 int sonicWriteFloatToStream(
1106 sonicStream stream,
1107 float *samples,
1108 int numSamples)
1109 {
1110 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
1111 return 0;
1112 }
1113 return processStreamInput(stream);
1114 }
1115
1116 /* Simple wrapper around sonicWriteFloatToStream that does the short to float
1117 conversion for you. */
sonicWriteShortToStream(sonicStream stream,short * samples,int numSamples)1118 int sonicWriteShortToStream(
1119 sonicStream stream,
1120 short *samples,
1121 int numSamples)
1122 {
1123 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
1124 return 0;
1125 }
1126 return processStreamInput(stream);
1127 }
1128
1129 /* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
1130 conversion for you. */
sonicWriteUnsignedCharToStream(sonicStream stream,unsigned char * samples,int numSamples)1131 int sonicWriteUnsignedCharToStream(
1132 sonicStream stream,
1133 unsigned char *samples,
1134 int numSamples)
1135 {
1136 if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
1137 return 0;
1138 }
1139 return processStreamInput(stream);
1140 }
1141
1142 /* This is a non-stream oriented interface to just change the speed of a sound sample */
sonicChangeFloatSpeed(float * samples,int numSamples,float speed,float pitch,float rate,float volume,int useChordPitch,int sampleRate,int numChannels)1143 int sonicChangeFloatSpeed(
1144 float *samples,
1145 int numSamples,
1146 float speed,
1147 float pitch,
1148 float rate,
1149 float volume,
1150 int useChordPitch,
1151 int sampleRate,
1152 int numChannels)
1153 {
1154 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
1155
1156 sonicSetSpeed(stream, speed);
1157 sonicSetPitch(stream, pitch);
1158 sonicSetRate(stream, rate);
1159 sonicSetVolume(stream, volume);
1160 sonicSetChordPitch(stream, useChordPitch);
1161 sonicWriteFloatToStream(stream, samples, numSamples);
1162 sonicFlushStream(stream);
1163 numSamples = sonicSamplesAvailable(stream);
1164 sonicReadFloatFromStream(stream, samples, numSamples);
1165 sonicDestroyStream(stream);
1166 return numSamples;
1167 }
1168
1169 /* This is a non-stream oriented interface to just change the speed of a sound sample */
sonicChangeShortSpeed(short * samples,int numSamples,float speed,float pitch,float rate,float volume,int useChordPitch,int sampleRate,int numChannels)1170 int sonicChangeShortSpeed(
1171 short *samples,
1172 int numSamples,
1173 float speed,
1174 float pitch,
1175 float rate,
1176 float volume,
1177 int useChordPitch,
1178 int sampleRate,
1179 int numChannels)
1180 {
1181 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
1182
1183 sonicSetSpeed(stream, speed);
1184 sonicSetPitch(stream, pitch);
1185 sonicSetRate(stream, rate);
1186 sonicSetVolume(stream, volume);
1187 sonicSetChordPitch(stream, useChordPitch);
1188 sonicWriteShortToStream(stream, samples, numSamples);
1189 sonicFlushStream(stream);
1190 numSamples = sonicSamplesAvailable(stream);
1191 sonicReadShortFromStream(stream, samples, numSamples);
1192 sonicDestroyStream(stream);
1193 return numSamples;
1194 }
1195