blob: a107ee61e1015bcc1f1ec498f6eb256b6a59a883 [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
6This program is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2 of the License, or
9(at your option) any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */
Bill Cox882fb1d2010-11-02 16:27:20 -040019#include <stdio.h>
Bill Coxca02d872010-11-02 15:10:52 -040020#include <stdlib.h>
21#include <string.h>
22#include "sonic.h"
23
24struct sonicStreamStruct {
25 double speed;
26 float *inputBuffer;
27 float *outputBuffer;
28 int inputBufferSize;
29 int outputBufferSize;
30 int numInputSamples;
31 int numOutputSamples;
32 int minPeriod;
33 int maxPeriod;
34 int maxRequired;
35 int remainingInputToCopy;
36 int sampleRate;
37};
38
Bill Coxaf9a6242010-11-08 09:32:27 -050039/* Get the speed of the stream. */
40double sonicGetSpeed(
41 sonicStream stream)
42{
43 return stream->speed;
44}
45
46/* Get the sample rate of the stream. */
47int sonicGetSampleRate(
48 sonicStream stream)
49{
50 return stream->sampleRate;
51}
52
Bill Coxca02d872010-11-02 15:10:52 -040053/* Destroy the sonic stream. */
54void sonicDestroyStream(
55 sonicStream stream)
56{
57 if(stream->inputBuffer != NULL) {
58 free(stream->inputBuffer);
59 }
60 if(stream->outputBuffer != NULL) {
61 free(stream->outputBuffer);
62 }
63 free(stream);
64}
65
66/* Create a sonic stream. Return NULL only if we are out of memory and cannot
67 allocate the stream. */
68sonicStream sonicCreateStream(
69 double speed,
70 int sampleRate)
71{
72 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
73 int minPeriod = sampleRate/SONIC_MAX_PITCH;
74 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
75 int maxRequired = 2*maxPeriod;
76
77 if(stream == NULL) {
78 return NULL;
79 }
80 stream->inputBuffer = (float *)calloc(maxRequired, sizeof(float));
81 if(stream->inputBuffer == NULL) {
82 sonicDestroyStream(stream);
83 return NULL;
84 }
85 stream->outputBuffer = (float *)calloc(maxRequired, sizeof(float));
86 if(stream->outputBuffer == NULL) {
87 sonicDestroyStream(stream);
88 return NULL;
89 }
90 stream->speed = speed;
91 stream->sampleRate = sampleRate;
92 stream->minPeriod = minPeriod;
93 stream->maxPeriod = maxPeriod;
94 stream->maxRequired = maxRequired;
95 stream->inputBufferSize = maxRequired;
96 stream->outputBufferSize = maxRequired;
97 return stream;
98}
99
Bill Coxca02d872010-11-02 15:10:52 -0400100/* Enlarge the output buffer if needed. */
101static int enlargeOutputBufferIfNeeded(
102 sonicStream stream,
103 int numSamples)
104{
105 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
106 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
107 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
108 stream->outputBufferSize*sizeof(float));
109 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400110 return 0;
111 }
112 }
113 return 1;
114}
115
Bill Coxca02d872010-11-02 15:10:52 -0400116/* Enlarge the input buffer if needed. */
117static int enlargeInputBufferIfNeeded(
118 sonicStream stream,
119 int numSamples)
120{
121 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
122 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
123 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
124 stream->inputBufferSize*sizeof(float));
125 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400126 return 0;
127 }
128 }
129 return 1;
130}
131
132/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500133static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400134 sonicStream stream,
135 float *samples,
136 int numSamples)
137{
138 if(numSamples == 0) {
139 return 1;
140 }
141 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
142 return 0;
143 }
144 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400145 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400146 return 1;
147}
148
Bill Cox0c4c0602010-11-08 11:46:30 -0500149/* Add the input samples to the input buffer. */
150static int addShortSamplesToInputBuffer(
151 sonicStream stream,
152 short *samples,
153 int numSamples)
154{
155 float *buffer;
156 int count = numSamples;
157
158 if(numSamples == 0) {
159 return 1;
160 }
161 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
162 return 0;
163 }
164 buffer = stream->inputBuffer + stream->numInputSamples;
165 while(count--) {
166 *buffer++ = *samples++;
167 }
168 stream->numInputSamples += numSamples;
169 return 1;
170}
171
Bill Coxca02d872010-11-02 15:10:52 -0400172/* Remove input samples that we have already processed. */
173static void removeInputSamples(
174 sonicStream stream,
175 int position)
176{
177 int remainingSamples = stream->numInputSamples - position;
178
179 if(remainingSamples > 0) {
180 memmove(stream->inputBuffer, stream->inputBuffer + position,
181 remainingSamples*sizeof(float));
182 }
183 stream->numInputSamples = remainingSamples;
184}
185
Bill Cox59e65122010-11-03 10:06:29 -0400186/* Just copy from the array to the output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500187static int copyFloatToOutput(
Bill Cox59e65122010-11-03 10:06:29 -0400188 sonicStream stream,
189 float *samples,
190 int numSamples)
191{
192 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
193 return 0;
194 }
195 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
196 stream->numOutputSamples += numSamples;
197 return numSamples;
198}
199
Bill Cox0c4c0602010-11-08 11:46:30 -0500200/* Just copy from the array to the output buffer */
201static int copyShortToOutput(
202 sonicStream stream,
203 short *samples,
204 int numSamples)
205{
206 float *buffer;
207 int count = numSamples;
208
209 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
210 return 0;
211 }
212 buffer = stream->outputBuffer + stream->numOutputSamples;
213 while(count--) {
214 *buffer++ = *samples++;
215 }
216 stream->numOutputSamples += numSamples;
217 return numSamples;
218}
219
Bill Cox882fb1d2010-11-02 16:27:20 -0400220/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
221 resize the output buffer. Otherwise, return numSamples */
222static int copyInputToOutput(
223 sonicStream stream,
224 int position)
225{
226 int numSamples = stream->remainingInputToCopy;
227
228 if(numSamples > stream->maxRequired) {
229 numSamples = stream->maxRequired;
230 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500231 if(!copyFloatToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400232 return 0;
233 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400234 stream->remainingInputToCopy -= numSamples;
235 return numSamples;
236}
237
Bill Coxca02d872010-11-02 15:10:52 -0400238/* Read data out of the stream. Sometimes no data will be available, and zero
239 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500240int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400241 sonicStream stream,
242 float *samples,
243 int maxSamples)
244{
245 int numSamples = stream->numOutputSamples;
246 int remainingSamples = 0;
247
248 if(numSamples == 0) {
249 return 0;
250 }
251 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400252 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400253 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400254 }
255 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
256 if(remainingSamples > 0) {
257 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
258 remainingSamples*sizeof(float));
259 }
260 stream->numOutputSamples = remainingSamples;
261 return numSamples;
262}
263
Bill Cox0c4c0602010-11-08 11:46:30 -0500264/* Read short data out of the stream. Sometimes no data will be available, and zero
265 is returned, which is not an error condition. */
266int sonicReadShortFromStream(
267 sonicStream stream,
268 short *samples,
269 int maxSamples)
270{
271 int numSamples = stream->numOutputSamples;
272 int remainingSamples = 0;
273 float *buffer;
274 int i;
275
276 if(numSamples == 0) {
277 return 0;
278 }
279 if(numSamples > maxSamples) {
280 remainingSamples = numSamples - maxSamples;
281 numSamples = maxSamples;
282 }
283 buffer = stream->outputBuffer;
284 for(i = 0; i < numSamples; i++) {
285 *samples++ = *buffer++;
286 }
287 if(remainingSamples > 0) {
288 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
289 remainingSamples*sizeof(float));
290 }
291 stream->numOutputSamples = remainingSamples;
292 return numSamples;
293}
294
Bill Coxca02d872010-11-02 15:10:52 -0400295/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500296 has. No extra delay will be added to the output, but flushing in the middle of
297 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400298int sonicFlushStream(
299 sonicStream stream)
300{
301 int maxRequired = stream->maxRequired;
302 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500303 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400304
305 if(numSamples == 0) {
306 return 1;
307 }
Bill Cox0c4c0602010-11-08 11:46:30 -0500308 if(numSamples >= maxRequired && !sonicWriteFloatToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400309 return 0;
310 }
311 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500312 if(numSamples == 0) {
313 return 1;
314 }
Bill Coxca02d872010-11-02 15:10:52 -0400315 remainingSpace = maxRequired - numSamples;
316 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
317 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500318 numOutputSamples = stream->numOutputSamples;
319 if(!sonicWriteFloatToStream(stream, NULL, 0)) {
320 return 0;
321 }
322 /* Throw away any extra samples we generated due to the silence we added */
323 expectedSamples = (int)(numSamples*stream->speed + 0.5);
324 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
325 stream->numOutputSamples = numOutputSamples + expectedSamples;
326 }
327 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400328}
329
330/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400331int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400332 sonicStream stream)
333{
334 return stream->numOutputSamples;
335}
Bill Cox9bf11b52010-11-03 05:33:09 -0400336
Bill Cox0cd49c82010-11-03 10:46:22 -0400337/* Find the best frequency match in the range, and given a sample skip multiple. */
338static int findPitchPeriodInRange(
339 sonicStream stream,
340 float *samples,
341 int minPeriod,
342 int maxPeriod,
343 int skip)
344{
345 int period, bestPeriod = 0;
346 double minDiff = 0.0;
347 double diff;
348 float value, *s, *p;
349 int xSample;
350
351 for(period = minPeriod; period <= maxPeriod; period += skip) {
352 diff = 0.0;
353 s = samples;
354 p = samples + period;
355 for(xSample = 0; xSample < period; xSample += skip) {
356 value = *s - *p;
357 s += skip;
358 p += skip;
359 diff += value >= 0.0? value : -value;
360 }
361 if(bestPeriod == 0 || diff < minDiff*period) {
362 diff /= period;
363 minDiff = diff;
364 bestPeriod = period;
365 }
366 }
367 return bestPeriod;
368}
369
Bill Cox9bf11b52010-11-03 05:33:09 -0400370/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400371 multiple ways to get a good answer. This version uses AMDF. To improve
372 speed, we down sample by an integer factor get in the 11KHz range, and then
373 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400374static int findPitchPeriod(
375 sonicStream stream,
376 float *samples)
377{
378 int minPeriod = stream->minPeriod;
379 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400380 int sampleRate = stream->sampleRate;
381 int skip = 1;
382 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400383
Bill Cox0cd49c82010-11-03 10:46:22 -0400384 if(sampleRate > SONIC_AMDF_FREQ) {
385 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400386 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400387 period = findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, skip);
388 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
389 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
390 if(minPeriod < stream->minPeriod) {
391 minPeriod = stream->minPeriod;
392 }
393 if(maxPeriod > stream->maxPeriod) {
394 maxPeriod = stream->maxPeriod;
395 }
396 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400397}
398
Bill Cox59e65122010-11-03 10:06:29 -0400399/* Skip over a pitch period, and copy period/speed samples to the output */
400static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400401 sonicStream stream,
402 float *samples,
403 double speed,
404 int period)
405{
406 int t, newSamples;
407 double scale;
408 float *out;
409
410 if(speed >= 2.0) {
411 newSamples = period/(speed - 1.0);
412 } else if(speed > 1.0) {
413 newSamples = period;
414 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400415 }
416 scale = 1.0/newSamples;
417 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
418 return 0;
419 }
420 out = stream->outputBuffer + stream->numOutputSamples;
421 for(t = 0; t < newSamples; t++) {
422 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
423 }
424 stream->numOutputSamples += newSamples;
425 return newSamples;
426}
427
Bill Cox59e65122010-11-03 10:06:29 -0400428/* Insert a pitch period, and determine how much input to copy directly. */
429static int insertPitchPeriod(
430 sonicStream stream,
431 float *samples,
432 double speed,
433 int period)
434{
435 int t, newSamples;
436 double scale;
437 float *out;
438
439 if(speed < 0.5) {
440 newSamples = period*speed/(1.0 - speed);
441 } else {
442 newSamples = period;
443 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
444 }
445 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
446 return 0;
447 }
448 out = stream->outputBuffer + stream->numOutputSamples;
449 memcpy(out, samples, period*sizeof(float));
450 out += period;
451 scale = 1.0/newSamples;
452 for(t = 0; t < newSamples; t++) {
453 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
454 }
455 stream->numOutputSamples += period + newSamples;
456 return newSamples;
457}
458
Bill Cox9bf11b52010-11-03 05:33:09 -0400459/* Resample as many pitch periods as we have buffered on the input. Return 0 if
460 we fail to resize an input or output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500461static int processStreamInput(
462 sonicStream stream)
Bill Cox9bf11b52010-11-03 05:33:09 -0400463{
Bill Cox0c4c0602010-11-08 11:46:30 -0500464 float *samples = stream->inputBuffer;
465 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400466 double speed = stream->speed;
467 int position = 0, period, newSamples;
468 int maxRequired = stream->maxRequired;
469
Bill Cox9bf11b52010-11-03 05:33:09 -0400470 if(stream->numInputSamples < maxRequired) {
471 return 1;
472 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400473 do {
474 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400475 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400476 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400477 } else {
478 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400479 if(speed > 1.0) {
480 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
481 position += period + newSamples;
482 } else {
483 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
484 position += newSamples;
485 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400486 }
487 if(newSamples == 0) {
488 return 0; /* Failed to resize output buffer */
489 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400490 } while(position + maxRequired <= numSamples);
491 removeInputSamples(stream, position);
492 return 1;
493}
Bill Cox0c4c0602010-11-08 11:46:30 -0500494
495/* Write floating point data to the input buffer and process it. */
496int sonicWriteFloatToStream(
497 sonicStream stream,
498 float *samples,
499 int numSamples)
500{
501 double speed = stream->speed;
502
503 if(speed > 0.999999 && speed < 1.000001) {
504 /* No speed change - just copy to the output */
505 return copyFloatToOutput(stream, samples, numSamples);
506 }
507 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
508 return 0;
509 }
510 return processStreamInput(stream);
511}
512
513/* Simple wrapper around sonicWriteFloatToStream that does the short to float
514 conversion for you. */
515int sonicWriteShortToStream(
516 sonicStream stream,
517 short *samples,
518 int numSamples)
519{
520 double speed = stream->speed;
521
522 if(speed > 0.999999 && speed < 1.000001) {
523 /* No speed change - just copy to the output */
524 return copyShortToOutput(stream, samples, numSamples);
525 }
526 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
527 return 0;
528 }
529 return processStreamInput(stream);
530}
531