blob: 065a59bcdf4de6f7ac20e59d153b5ea6a102b578 [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>
Bill Coxa33e3bd2010-11-16 19:57:43 -050023#include <stdarg.h>
Bill Coxca02d872010-11-02 15:10:52 -040024#include "sonic.h"
25
26struct sonicStreamStruct {
27 double speed;
28 float *inputBuffer;
29 float *outputBuffer;
30 int inputBufferSize;
31 int outputBufferSize;
32 int numInputSamples;
33 int numOutputSamples;
34 int minPeriod;
35 int maxPeriod;
36 int maxRequired;
37 int remainingInputToCopy;
38 int sampleRate;
39};
40
Bill Coxa33e3bd2010-11-16 19:57:43 -050041void sonicMSG(char *format, ...)
42{
43 char buffer[4096];
44 va_list ap;
45 FILE *file;
46
47 va_start(ap, format);
48 vsprintf((char *)buffer, (char *)format, ap);
49 va_end(ap);
50 file=fopen("/tmp/sonic.log", "a");
51 fprintf(file, "%s", buffer);
52 fclose(file);
53}
54
Bill Coxaf9a6242010-11-08 09:32:27 -050055/* Get the speed of the stream. */
56double sonicGetSpeed(
57 sonicStream stream)
58{
59 return stream->speed;
60}
61
62/* Get the sample rate of the stream. */
63int sonicGetSampleRate(
64 sonicStream stream)
65{
66 return stream->sampleRate;
67}
68
Bill Coxca02d872010-11-02 15:10:52 -040069/* Destroy the sonic stream. */
70void sonicDestroyStream(
71 sonicStream stream)
72{
73 if(stream->inputBuffer != NULL) {
74 free(stream->inputBuffer);
75 }
76 if(stream->outputBuffer != NULL) {
77 free(stream->outputBuffer);
78 }
79 free(stream);
80}
81
82/* Create a sonic stream. Return NULL only if we are out of memory and cannot
83 allocate the stream. */
84sonicStream sonicCreateStream(
85 double speed,
86 int sampleRate)
87{
88 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
89 int minPeriod = sampleRate/SONIC_MAX_PITCH;
90 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
91 int maxRequired = 2*maxPeriod;
92
93 if(stream == NULL) {
94 return NULL;
95 }
96 stream->inputBuffer = (float *)calloc(maxRequired, sizeof(float));
97 if(stream->inputBuffer == NULL) {
98 sonicDestroyStream(stream);
99 return NULL;
100 }
101 stream->outputBuffer = (float *)calloc(maxRequired, sizeof(float));
102 if(stream->outputBuffer == NULL) {
103 sonicDestroyStream(stream);
104 return NULL;
105 }
106 stream->speed = speed;
107 stream->sampleRate = sampleRate;
108 stream->minPeriod = minPeriod;
109 stream->maxPeriod = maxPeriod;
110 stream->maxRequired = maxRequired;
111 stream->inputBufferSize = maxRequired;
112 stream->outputBufferSize = maxRequired;
113 return stream;
114}
115
Bill Coxca02d872010-11-02 15:10:52 -0400116/* Enlarge the output buffer if needed. */
117static int enlargeOutputBufferIfNeeded(
118 sonicStream stream,
119 int numSamples)
120{
121 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
122 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
123 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
124 stream->outputBufferSize*sizeof(float));
125 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400126 return 0;
127 }
128 }
129 return 1;
130}
131
Bill Coxca02d872010-11-02 15:10:52 -0400132/* Enlarge the input buffer if needed. */
133static int enlargeInputBufferIfNeeded(
134 sonicStream stream,
135 int numSamples)
136{
137 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
138 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
139 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
140 stream->inputBufferSize*sizeof(float));
141 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400142 return 0;
143 }
144 }
145 return 1;
146}
147
148/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500149static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400150 sonicStream stream,
151 float *samples,
152 int numSamples)
153{
154 if(numSamples == 0) {
155 return 1;
156 }
157 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
158 return 0;
159 }
160 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400161 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400162 return 1;
163}
164
Bill Cox0c4c0602010-11-08 11:46:30 -0500165/* Add the input samples to the input buffer. */
166static int addShortSamplesToInputBuffer(
167 sonicStream stream,
168 short *samples,
169 int numSamples)
170{
171 float *buffer;
172 int count = numSamples;
173
174 if(numSamples == 0) {
175 return 1;
176 }
177 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
178 return 0;
179 }
180 buffer = stream->inputBuffer + stream->numInputSamples;
181 while(count--) {
182 *buffer++ = *samples++;
183 }
184 stream->numInputSamples += numSamples;
185 return 1;
186}
187
Bill Coxca02d872010-11-02 15:10:52 -0400188/* Remove input samples that we have already processed. */
189static void removeInputSamples(
190 sonicStream stream,
191 int position)
192{
193 int remainingSamples = stream->numInputSamples - position;
194
195 if(remainingSamples > 0) {
196 memmove(stream->inputBuffer, stream->inputBuffer + position,
197 remainingSamples*sizeof(float));
198 }
199 stream->numInputSamples = remainingSamples;
200}
201
Bill Cox59e65122010-11-03 10:06:29 -0400202/* Just copy from the array to the output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500203static int copyFloatToOutput(
Bill Cox59e65122010-11-03 10:06:29 -0400204 sonicStream stream,
205 float *samples,
206 int numSamples)
207{
208 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
209 return 0;
210 }
211 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
212 stream->numOutputSamples += numSamples;
213 return numSamples;
214}
215
Bill Cox0c4c0602010-11-08 11:46:30 -0500216/* Just copy from the array to the output buffer */
217static int copyShortToOutput(
218 sonicStream stream,
219 short *samples,
220 int numSamples)
221{
222 float *buffer;
223 int count = numSamples;
224
225 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
226 return 0;
227 }
228 buffer = stream->outputBuffer + stream->numOutputSamples;
229 while(count--) {
230 *buffer++ = *samples++;
231 }
232 stream->numOutputSamples += numSamples;
233 return numSamples;
234}
235
Bill Cox882fb1d2010-11-02 16:27:20 -0400236/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
237 resize the output buffer. Otherwise, return numSamples */
238static int copyInputToOutput(
239 sonicStream stream,
240 int position)
241{
242 int numSamples = stream->remainingInputToCopy;
243
244 if(numSamples > stream->maxRequired) {
245 numSamples = stream->maxRequired;
246 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500247 if(!copyFloatToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400248 return 0;
249 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400250 stream->remainingInputToCopy -= numSamples;
251 return numSamples;
252}
253
Bill Coxca02d872010-11-02 15:10:52 -0400254/* Read data out of the stream. Sometimes no data will be available, and zero
255 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500256int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400257 sonicStream stream,
258 float *samples,
259 int maxSamples)
260{
261 int numSamples = stream->numOutputSamples;
262 int remainingSamples = 0;
263
264 if(numSamples == 0) {
265 return 0;
266 }
267 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400268 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400269 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400270 }
271 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
272 if(remainingSamples > 0) {
273 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
274 remainingSamples*sizeof(float));
275 }
276 stream->numOutputSamples = remainingSamples;
277 return numSamples;
278}
279
Bill Cox0c4c0602010-11-08 11:46:30 -0500280/* Read short data out of the stream. Sometimes no data will be available, and zero
281 is returned, which is not an error condition. */
282int sonicReadShortFromStream(
283 sonicStream stream,
284 short *samples,
285 int maxSamples)
286{
287 int numSamples = stream->numOutputSamples;
288 int remainingSamples = 0;
289 float *buffer;
290 int i;
291
292 if(numSamples == 0) {
293 return 0;
294 }
295 if(numSamples > maxSamples) {
296 remainingSamples = numSamples - maxSamples;
297 numSamples = maxSamples;
298 }
299 buffer = stream->outputBuffer;
300 for(i = 0; i < numSamples; i++) {
301 *samples++ = *buffer++;
302 }
303 if(remainingSamples > 0) {
304 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
305 remainingSamples*sizeof(float));
306 }
307 stream->numOutputSamples = remainingSamples;
308 return numSamples;
309}
310
Bill Coxca02d872010-11-02 15:10:52 -0400311/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500312 has. No extra delay will be added to the output, but flushing in the middle of
313 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400314int sonicFlushStream(
315 sonicStream stream)
316{
317 int maxRequired = stream->maxRequired;
318 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500319 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400320
321 if(numSamples == 0) {
322 return 1;
323 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500324 if(numSamples >= maxRequired && !sonicWriteFloatToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400325 return 0;
326 }
327 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500328 if(numSamples == 0) {
329 return 1;
330 }
Bill Coxca02d872010-11-02 15:10:52 -0400331 remainingSpace = maxRequired - numSamples;
332 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
333 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500334 numOutputSamples = stream->numOutputSamples;
335 if(!sonicWriteFloatToStream(stream, NULL, 0)) {
336 return 0;
337 }
338 /* Throw away any extra samples we generated due to the silence we added */
339 expectedSamples = (int)(numSamples*stream->speed + 0.5);
340 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
341 stream->numOutputSamples = numOutputSamples + expectedSamples;
342 }
343 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400344}
345
346/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400347int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400348 sonicStream stream)
349{
350 return stream->numOutputSamples;
351}
Bill Cox9bf11b52010-11-03 05:33:09 -0400352
Bill Cox0cd49c82010-11-03 10:46:22 -0400353/* Find the best frequency match in the range, and given a sample skip multiple. */
354static int findPitchPeriodInRange(
Bill Cox0cd49c82010-11-03 10:46:22 -0400355 float *samples,
356 int minPeriod,
357 int maxPeriod,
358 int skip)
359{
360 int period, bestPeriod = 0;
361 double minDiff = 0.0;
362 double diff;
363 float value, *s, *p;
364 int xSample;
365
366 for(period = minPeriod; period <= maxPeriod; period += skip) {
367 diff = 0.0;
368 s = samples;
369 p = samples + period;
370 for(xSample = 0; xSample < period; xSample += skip) {
371 value = *s - *p;
372 s += skip;
373 p += skip;
374 diff += value >= 0.0? value : -value;
375 }
376 if(bestPeriod == 0 || diff < minDiff*period) {
377 diff /= period;
378 minDiff = diff;
379 bestPeriod = period;
380 }
381 }
382 return bestPeriod;
383}
384
Bill Cox9bf11b52010-11-03 05:33:09 -0400385/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400386 multiple ways to get a good answer. This version uses AMDF. To improve
387 speed, we down sample by an integer factor get in the 11KHz range, and then
388 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400389static int findPitchPeriod(
390 sonicStream stream,
391 float *samples)
392{
393 int minPeriod = stream->minPeriod;
394 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400395 int sampleRate = stream->sampleRate;
396 int skip = 1;
397 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400398
Bill Cox0cd49c82010-11-03 10:46:22 -0400399 if(sampleRate > SONIC_AMDF_FREQ) {
400 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400401 }
Bill Coxa33e3bd2010-11-16 19:57:43 -0500402 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, skip);
Bill Cox0cd49c82010-11-03 10:46:22 -0400403 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
404 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
405 if(minPeriod < stream->minPeriod) {
406 minPeriod = stream->minPeriod;
407 }
408 if(maxPeriod > stream->maxPeriod) {
409 maxPeriod = stream->maxPeriod;
410 }
411 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400412}
413
Bill Cox59e65122010-11-03 10:06:29 -0400414/* Skip over a pitch period, and copy period/speed samples to the output */
415static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400416 sonicStream stream,
417 float *samples,
418 double speed,
419 int period)
420{
421 int t, newSamples;
422 double scale;
423 float *out;
424
425 if(speed >= 2.0) {
426 newSamples = period/(speed - 1.0);
427 } else if(speed > 1.0) {
428 newSamples = period;
429 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400430 }
431 scale = 1.0/newSamples;
432 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
433 return 0;
434 }
435 out = stream->outputBuffer + stream->numOutputSamples;
436 for(t = 0; t < newSamples; t++) {
437 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
438 }
439 stream->numOutputSamples += newSamples;
440 return newSamples;
441}
442
Bill Cox59e65122010-11-03 10:06:29 -0400443/* Insert a pitch period, and determine how much input to copy directly. */
444static int insertPitchPeriod(
445 sonicStream stream,
446 float *samples,
447 double speed,
448 int period)
449{
450 int t, newSamples;
451 double scale;
452 float *out;
453
454 if(speed < 0.5) {
455 newSamples = period*speed/(1.0 - speed);
456 } else {
457 newSamples = period;
458 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
459 }
460 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
461 return 0;
462 }
463 out = stream->outputBuffer + stream->numOutputSamples;
464 memcpy(out, samples, period*sizeof(float));
465 out += period;
466 scale = 1.0/newSamples;
467 for(t = 0; t < newSamples; t++) {
468 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
469 }
470 stream->numOutputSamples += period + newSamples;
471 return newSamples;
472}
473
Bill Cox9bf11b52010-11-03 05:33:09 -0400474/* Resample as many pitch periods as we have buffered on the input. Return 0 if
475 we fail to resize an input or output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500476static int processStreamInput(
477 sonicStream stream)
Bill Cox9bf11b52010-11-03 05:33:09 -0400478{
Bill Cox0c4c0602010-11-08 11:46:30 -0500479 float *samples = stream->inputBuffer;
480 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400481 double speed = stream->speed;
482 int position = 0, period, newSamples;
483 int maxRequired = stream->maxRequired;
484
Bill Cox9bf11b52010-11-03 05:33:09 -0400485 if(stream->numInputSamples < maxRequired) {
486 return 1;
487 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400488 do {
489 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400490 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400491 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400492 } else {
493 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400494 if(speed > 1.0) {
495 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
496 position += period + newSamples;
497 } else {
498 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
499 position += newSamples;
500 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400501 }
502 if(newSamples == 0) {
503 return 0; /* Failed to resize output buffer */
504 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400505 } while(position + maxRequired <= numSamples);
506 removeInputSamples(stream, position);
507 return 1;
508}
Bill Cox0c4c0602010-11-08 11:46:30 -0500509
510/* Write floating point data to the input buffer and process it. */
511int sonicWriteFloatToStream(
512 sonicStream stream,
513 float *samples,
514 int numSamples)
515{
516 double speed = stream->speed;
517
518 if(speed > 0.999999 && speed < 1.000001) {
519 /* No speed change - just copy to the output */
520 return copyFloatToOutput(stream, samples, numSamples);
521 }
522 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
523 return 0;
524 }
525 return processStreamInput(stream);
526}
527
528/* Simple wrapper around sonicWriteFloatToStream that does the short to float
529 conversion for you. */
530int sonicWriteShortToStream(
531 sonicStream stream,
532 short *samples,
533 int numSamples)
534{
535 double speed = stream->speed;
536
537 if(speed > 0.999999 && speed < 1.000001) {
538 /* No speed change - just copy to the output */
539 return copyShortToOutput(stream, samples, numSamples);
540 }
541 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
542 return 0;
543 }
544 return processStreamInput(stream);
545}
546
Bill Cox036d7322010-11-09 09:29:24 -0500547/* This is a non-stream oriented interface to just change the speed of a sound sample */
548int sonicChangeFloatSpeed(
549 float *samples,
550 int numSamples,
551 double speed,
552 int sampleRate)
553{
554 sonicStream stream = sonicCreateStream(speed, sampleRate);
555
556 sonicWriteFloatToStream(stream, samples, numSamples);
557 sonicFlushStream(stream);
558 numSamples = sonicSamplesAvailable(stream);
559 sonicReadFloatFromStream(stream, samples, numSamples);
560 sonicDestroyStream(stream);
561 return numSamples;
562}
563
564/* This is a non-stream oriented interface to just change the speed of a sound sample */
565int sonicChangeShortSpeed(
566 short *samples,
567 int numSamples,
568 double speed,
569 int sampleRate)
570{
571 sonicStream stream = sonicCreateStream(speed, sampleRate);
572
573 sonicWriteShortToStream(stream, samples, numSamples);
574 sonicFlushStream(stream);
575 numSamples = sonicSamplesAvailable(stream);
576 sonicReadShortFromStream(stream, samples, numSamples);
577 sonicDestroyStream(stream);
578 return numSamples;
579}
580