• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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