blob: 9a9c11c11cf6b10637df47897cf85b73a816cbc4 [file] [log] [blame]
Bill Coxca02d872010-11-02 15:10:52 -04001/* Sonic library
2 Copyright 2010
3 Bill Cox
4 This file is part of the Sonic Library.
5
Bill Coxa9999872010-11-11 14:36:59 -05006 The Sonic Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
Bill Coxca02d872010-11-02 15:10:52 -040010
Bill Coxa9999872010-11-11 14:36:59 -050011 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
Bill Coxca02d872010-11-02 15:10:52 -040015
Bill Coxa9999872010-11-11 14:36:59 -050016 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
Bill Cox882fb1d2010-11-02 16:27:20 -040020#include <stdio.h>
Bill Coxca02d872010-11-02 15:10:52 -040021#include <stdlib.h>
22#include <string.h>
23#include "sonic.h"
24
25struct sonicStreamStruct {
26 double speed;
27 float *inputBuffer;
28 float *outputBuffer;
29 int inputBufferSize;
30 int outputBufferSize;
31 int numInputSamples;
32 int numOutputSamples;
33 int minPeriod;
34 int maxPeriod;
35 int maxRequired;
36 int remainingInputToCopy;
37 int sampleRate;
38};
39
Bill Coxaf9a6242010-11-08 09:32:27 -050040/* Get the speed of the stream. */
41double sonicGetSpeed(
42 sonicStream stream)
43{
44 return stream->speed;
45}
46
47/* Get the sample rate of the stream. */
48int sonicGetSampleRate(
49 sonicStream stream)
50{
51 return stream->sampleRate;
52}
53
Bill Coxca02d872010-11-02 15:10:52 -040054/* Destroy the sonic stream. */
55void sonicDestroyStream(
56 sonicStream stream)
57{
58 if(stream->inputBuffer != NULL) {
59 free(stream->inputBuffer);
60 }
61 if(stream->outputBuffer != NULL) {
62 free(stream->outputBuffer);
63 }
64 free(stream);
65}
66
67/* Create a sonic stream. Return NULL only if we are out of memory and cannot
68 allocate the stream. */
69sonicStream sonicCreateStream(
70 double speed,
71 int sampleRate)
72{
73 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
74 int minPeriod = sampleRate/SONIC_MAX_PITCH;
75 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
76 int maxRequired = 2*maxPeriod;
77
78 if(stream == NULL) {
79 return NULL;
80 }
81 stream->inputBuffer = (float *)calloc(maxRequired, sizeof(float));
82 if(stream->inputBuffer == NULL) {
83 sonicDestroyStream(stream);
84 return NULL;
85 }
86 stream->outputBuffer = (float *)calloc(maxRequired, sizeof(float));
87 if(stream->outputBuffer == NULL) {
88 sonicDestroyStream(stream);
89 return NULL;
90 }
91 stream->speed = speed;
92 stream->sampleRate = sampleRate;
93 stream->minPeriod = minPeriod;
94 stream->maxPeriod = maxPeriod;
95 stream->maxRequired = maxRequired;
96 stream->inputBufferSize = maxRequired;
97 stream->outputBufferSize = maxRequired;
98 return stream;
99}
100
Bill Coxca02d872010-11-02 15:10:52 -0400101/* Enlarge the output buffer if needed. */
102static int enlargeOutputBufferIfNeeded(
103 sonicStream stream,
104 int numSamples)
105{
106 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
107 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
108 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
109 stream->outputBufferSize*sizeof(float));
110 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400111 return 0;
112 }
113 }
114 return 1;
115}
116
Bill Coxca02d872010-11-02 15:10:52 -0400117/* Enlarge the input buffer if needed. */
118static int enlargeInputBufferIfNeeded(
119 sonicStream stream,
120 int numSamples)
121{
122 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
123 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
124 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
125 stream->inputBufferSize*sizeof(float));
126 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400127 return 0;
128 }
129 }
130 return 1;
131}
132
133/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500134static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400135 sonicStream stream,
136 float *samples,
137 int numSamples)
138{
139 if(numSamples == 0) {
140 return 1;
141 }
142 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
143 return 0;
144 }
145 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400146 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400147 return 1;
148}
149
Bill Cox0c4c0602010-11-08 11:46:30 -0500150/* Add the input samples to the input buffer. */
151static int addShortSamplesToInputBuffer(
152 sonicStream stream,
153 short *samples,
154 int numSamples)
155{
156 float *buffer;
157 int count = numSamples;
158
159 if(numSamples == 0) {
160 return 1;
161 }
162 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
163 return 0;
164 }
165 buffer = stream->inputBuffer + stream->numInputSamples;
166 while(count--) {
167 *buffer++ = *samples++;
168 }
169 stream->numInputSamples += numSamples;
170 return 1;
171}
172
Bill Coxca02d872010-11-02 15:10:52 -0400173/* Remove input samples that we have already processed. */
174static void removeInputSamples(
175 sonicStream stream,
176 int position)
177{
178 int remainingSamples = stream->numInputSamples - position;
179
180 if(remainingSamples > 0) {
181 memmove(stream->inputBuffer, stream->inputBuffer + position,
182 remainingSamples*sizeof(float));
183 }
184 stream->numInputSamples = remainingSamples;
185}
186
Bill Cox59e65122010-11-03 10:06:29 -0400187/* Just copy from the array to the output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500188static int copyFloatToOutput(
Bill Cox59e65122010-11-03 10:06:29 -0400189 sonicStream stream,
190 float *samples,
191 int numSamples)
192{
193 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
194 return 0;
195 }
196 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
197 stream->numOutputSamples += numSamples;
198 return numSamples;
199}
200
Bill Cox0c4c0602010-11-08 11:46:30 -0500201/* Just copy from the array to the output buffer */
202static int copyShortToOutput(
203 sonicStream stream,
204 short *samples,
205 int numSamples)
206{
207 float *buffer;
208 int count = numSamples;
209
210 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
211 return 0;
212 }
213 buffer = stream->outputBuffer + stream->numOutputSamples;
214 while(count--) {
215 *buffer++ = *samples++;
216 }
217 stream->numOutputSamples += numSamples;
218 return numSamples;
219}
220
Bill Cox882fb1d2010-11-02 16:27:20 -0400221/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
222 resize the output buffer. Otherwise, return numSamples */
223static int copyInputToOutput(
224 sonicStream stream,
225 int position)
226{
227 int numSamples = stream->remainingInputToCopy;
228
229 if(numSamples > stream->maxRequired) {
230 numSamples = stream->maxRequired;
231 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500232 if(!copyFloatToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400233 return 0;
234 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400235 stream->remainingInputToCopy -= numSamples;
236 return numSamples;
237}
238
Bill Coxca02d872010-11-02 15:10:52 -0400239/* Read data out of the stream. Sometimes no data will be available, and zero
240 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500241int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400242 sonicStream stream,
243 float *samples,
244 int maxSamples)
245{
246 int numSamples = stream->numOutputSamples;
247 int remainingSamples = 0;
248
249 if(numSamples == 0) {
250 return 0;
251 }
252 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400253 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400254 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400255 }
256 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
257 if(remainingSamples > 0) {
258 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
259 remainingSamples*sizeof(float));
260 }
261 stream->numOutputSamples = remainingSamples;
262 return numSamples;
263}
264
Bill Cox0c4c0602010-11-08 11:46:30 -0500265/* Read short data out of the stream. Sometimes no data will be available, and zero
266 is returned, which is not an error condition. */
267int sonicReadShortFromStream(
268 sonicStream stream,
269 short *samples,
270 int maxSamples)
271{
272 int numSamples = stream->numOutputSamples;
273 int remainingSamples = 0;
274 float *buffer;
275 int i;
276
277 if(numSamples == 0) {
278 return 0;
279 }
280 if(numSamples > maxSamples) {
281 remainingSamples = numSamples - maxSamples;
282 numSamples = maxSamples;
283 }
284 buffer = stream->outputBuffer;
285 for(i = 0; i < numSamples; i++) {
286 *samples++ = *buffer++;
287 }
288 if(remainingSamples > 0) {
289 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
290 remainingSamples*sizeof(float));
291 }
292 stream->numOutputSamples = remainingSamples;
293 return numSamples;
294}
295
Bill Coxca02d872010-11-02 15:10:52 -0400296/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500297 has. No extra delay will be added to the output, but flushing in the middle of
298 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400299int sonicFlushStream(
300 sonicStream stream)
301{
302 int maxRequired = stream->maxRequired;
303 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500304 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400305
306 if(numSamples == 0) {
307 return 1;
308 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500309 if(numSamples >= maxRequired && !sonicWriteFloatToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400310 return 0;
311 }
312 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500313 if(numSamples == 0) {
314 return 1;
315 }
Bill Coxca02d872010-11-02 15:10:52 -0400316 remainingSpace = maxRequired - numSamples;
317 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
318 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500319 numOutputSamples = stream->numOutputSamples;
320 if(!sonicWriteFloatToStream(stream, NULL, 0)) {
321 return 0;
322 }
323 /* Throw away any extra samples we generated due to the silence we added */
324 expectedSamples = (int)(numSamples*stream->speed + 0.5);
325 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
326 stream->numOutputSamples = numOutputSamples + expectedSamples;
327 }
328 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400329}
330
331/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400332int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400333 sonicStream stream)
334{
335 return stream->numOutputSamples;
336}
Bill Cox9bf11b52010-11-03 05:33:09 -0400337
Bill Cox0cd49c82010-11-03 10:46:22 -0400338/* Find the best frequency match in the range, and given a sample skip multiple. */
339static int findPitchPeriodInRange(
340 sonicStream stream,
341 float *samples,
342 int minPeriod,
343 int maxPeriod,
344 int skip)
345{
346 int period, bestPeriod = 0;
347 double minDiff = 0.0;
348 double diff;
349 float value, *s, *p;
350 int xSample;
351
352 for(period = minPeriod; period <= maxPeriod; period += skip) {
353 diff = 0.0;
354 s = samples;
355 p = samples + period;
356 for(xSample = 0; xSample < period; xSample += skip) {
357 value = *s - *p;
358 s += skip;
359 p += skip;
360 diff += value >= 0.0? value : -value;
361 }
362 if(bestPeriod == 0 || diff < minDiff*period) {
363 diff /= period;
364 minDiff = diff;
365 bestPeriod = period;
366 }
367 }
368 return bestPeriod;
369}
370
Bill Cox9bf11b52010-11-03 05:33:09 -0400371/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400372 multiple ways to get a good answer. This version uses AMDF. To improve
373 speed, we down sample by an integer factor get in the 11KHz range, and then
374 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400375static int findPitchPeriod(
376 sonicStream stream,
377 float *samples)
378{
379 int minPeriod = stream->minPeriod;
380 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400381 int sampleRate = stream->sampleRate;
382 int skip = 1;
383 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400384
Bill Cox0cd49c82010-11-03 10:46:22 -0400385 if(sampleRate > SONIC_AMDF_FREQ) {
386 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400387 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400388 period = findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, skip);
389 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
390 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
391 if(minPeriod < stream->minPeriod) {
392 minPeriod = stream->minPeriod;
393 }
394 if(maxPeriod > stream->maxPeriod) {
395 maxPeriod = stream->maxPeriod;
396 }
397 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400398}
399
Bill Cox59e65122010-11-03 10:06:29 -0400400/* Skip over a pitch period, and copy period/speed samples to the output */
401static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400402 sonicStream stream,
403 float *samples,
404 double speed,
405 int period)
406{
407 int t, newSamples;
408 double scale;
409 float *out;
410
411 if(speed >= 2.0) {
412 newSamples = period/(speed - 1.0);
413 } else if(speed > 1.0) {
414 newSamples = period;
415 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400416 }
417 scale = 1.0/newSamples;
418 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
419 return 0;
420 }
421 out = stream->outputBuffer + stream->numOutputSamples;
422 for(t = 0; t < newSamples; t++) {
423 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
424 }
425 stream->numOutputSamples += newSamples;
426 return newSamples;
427}
428
Bill Cox59e65122010-11-03 10:06:29 -0400429/* Insert a pitch period, and determine how much input to copy directly. */
430static int insertPitchPeriod(
431 sonicStream stream,
432 float *samples,
433 double speed,
434 int period)
435{
436 int t, newSamples;
437 double scale;
438 float *out;
439
440 if(speed < 0.5) {
441 newSamples = period*speed/(1.0 - speed);
442 } else {
443 newSamples = period;
444 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
445 }
446 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
447 return 0;
448 }
449 out = stream->outputBuffer + stream->numOutputSamples;
450 memcpy(out, samples, period*sizeof(float));
451 out += period;
452 scale = 1.0/newSamples;
453 for(t = 0; t < newSamples; t++) {
454 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
455 }
456 stream->numOutputSamples += period + newSamples;
457 return newSamples;
458}
459
Bill Cox9bf11b52010-11-03 05:33:09 -0400460/* Resample as many pitch periods as we have buffered on the input. Return 0 if
461 we fail to resize an input or output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500462static int processStreamInput(
463 sonicStream stream)
Bill Cox9bf11b52010-11-03 05:33:09 -0400464{
Bill Cox0c4c0602010-11-08 11:46:30 -0500465 float *samples = stream->inputBuffer;
466 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400467 double speed = stream->speed;
468 int position = 0, period, newSamples;
469 int maxRequired = stream->maxRequired;
470
Bill Cox9bf11b52010-11-03 05:33:09 -0400471 if(stream->numInputSamples < maxRequired) {
472 return 1;
473 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400474 do {
475 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400476 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400477 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400478 } else {
479 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400480 if(speed > 1.0) {
481 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
482 position += period + newSamples;
483 } else {
484 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
485 position += newSamples;
486 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400487 }
488 if(newSamples == 0) {
489 return 0; /* Failed to resize output buffer */
490 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400491 } while(position + maxRequired <= numSamples);
492 removeInputSamples(stream, position);
493 return 1;
494}
Bill Cox0c4c0602010-11-08 11:46:30 -0500495
496/* Write floating point data to the input buffer and process it. */
497int sonicWriteFloatToStream(
498 sonicStream stream,
499 float *samples,
500 int numSamples)
501{
502 double speed = stream->speed;
503
504 if(speed > 0.999999 && speed < 1.000001) {
505 /* No speed change - just copy to the output */
506 return copyFloatToOutput(stream, samples, numSamples);
507 }
508 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
509 return 0;
510 }
511 return processStreamInput(stream);
512}
513
514/* Simple wrapper around sonicWriteFloatToStream that does the short to float
515 conversion for you. */
516int sonicWriteShortToStream(
517 sonicStream stream,
518 short *samples,
519 int numSamples)
520{
521 double speed = stream->speed;
522
523 if(speed > 0.999999 && speed < 1.000001) {
524 /* No speed change - just copy to the output */
525 return copyShortToOutput(stream, samples, numSamples);
526 }
527 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
528 return 0;
529 }
530 return processStreamInput(stream);
531}
532
Bill Cox036d7322010-11-09 09:29:24 -0500533/* This is a non-stream oriented interface to just change the speed of a sound sample */
534int sonicChangeFloatSpeed(
535 float *samples,
536 int numSamples,
537 double speed,
538 int sampleRate)
539{
540 sonicStream stream = sonicCreateStream(speed, sampleRate);
541
542 sonicWriteFloatToStream(stream, samples, numSamples);
543 sonicFlushStream(stream);
544 numSamples = sonicSamplesAvailable(stream);
545 sonicReadFloatFromStream(stream, samples, numSamples);
546 sonicDestroyStream(stream);
547 return numSamples;
548}
549
Bill Coxa9999872010-11-11 14:36:59 -0500550#include <stdarg.h>
551void MSG(char *format, ...)
552{
553 char buffer[4096];
554 va_list ap;
555 FILE *file;
556
557 va_start(ap, format);
558 vsprintf((char *)buffer, (char *)format, ap);
559 va_end(ap);
560 file=fopen("/tmp/sonic.log", "a");
561 fprintf(file, "%s", buffer);
562 fclose(file);
563}
564
Bill Cox036d7322010-11-09 09:29:24 -0500565/* This is a non-stream oriented interface to just change the speed of a sound sample */
566int sonicChangeShortSpeed(
567 short *samples,
568 int numSamples,
569 double speed,
570 int sampleRate)
571{
572 sonicStream stream = sonicCreateStream(speed, sampleRate);
573
574 sonicWriteShortToStream(stream, samples, numSamples);
575 sonicFlushStream(stream);
576 numSamples = sonicSamplesAvailable(stream);
577 sonicReadShortFromStream(stream, samples, numSamples);
578 sonicDestroyStream(stream);
579 return numSamples;
580}
581