blob: 8059048a8f51d14c1d2263d0600a055d76d5c624 [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
296 has. Zeros will be appended to the input data if there is not enough data
297 in the stream's input buffer. Use this, followed by a final read from the
298 stream before destroying the stream. */
299int sonicFlushStream(
300 sonicStream stream)
301{
302 int maxRequired = stream->maxRequired;
303 int numSamples = stream->numInputSamples;
304 int remainingSpace;
305
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 */
313 remainingSpace = maxRequired - numSamples;
314 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
315 stream->numInputSamples = maxRequired;
Bill Cox0c4c0602010-11-08 11:46:30 -0500316 return sonicWriteFloatToStream(stream, NULL, 0);
Bill Coxca02d872010-11-02 15:10:52 -0400317}
318
319/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400320int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400321 sonicStream stream)
322{
323 return stream->numOutputSamples;
324}
Bill Cox9bf11b52010-11-03 05:33:09 -0400325
Bill Cox0cd49c82010-11-03 10:46:22 -0400326/* Find the best frequency match in the range, and given a sample skip multiple. */
327static int findPitchPeriodInRange(
328 sonicStream stream,
329 float *samples,
330 int minPeriod,
331 int maxPeriod,
332 int skip)
333{
334 int period, bestPeriod = 0;
335 double minDiff = 0.0;
336 double diff;
337 float value, *s, *p;
338 int xSample;
339
340 for(period = minPeriod; period <= maxPeriod; period += skip) {
341 diff = 0.0;
342 s = samples;
343 p = samples + period;
344 for(xSample = 0; xSample < period; xSample += skip) {
345 value = *s - *p;
346 s += skip;
347 p += skip;
348 diff += value >= 0.0? value : -value;
349 }
350 if(bestPeriod == 0 || diff < minDiff*period) {
351 diff /= period;
352 minDiff = diff;
353 bestPeriod = period;
354 }
355 }
356 return bestPeriod;
357}
358
Bill Cox9bf11b52010-11-03 05:33:09 -0400359/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400360 multiple ways to get a good answer. This version uses AMDF. To improve
361 speed, we down sample by an integer factor get in the 11KHz range, and then
362 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400363static int findPitchPeriod(
364 sonicStream stream,
365 float *samples)
366{
367 int minPeriod = stream->minPeriod;
368 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400369 int sampleRate = stream->sampleRate;
370 int skip = 1;
371 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400372
Bill Cox0cd49c82010-11-03 10:46:22 -0400373 if(sampleRate > SONIC_AMDF_FREQ) {
374 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400375 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400376 period = findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, skip);
377 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
378 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
379 if(minPeriod < stream->minPeriod) {
380 minPeriod = stream->minPeriod;
381 }
382 if(maxPeriod > stream->maxPeriod) {
383 maxPeriod = stream->maxPeriod;
384 }
385 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400386}
387
Bill Cox59e65122010-11-03 10:06:29 -0400388/* Skip over a pitch period, and copy period/speed samples to the output */
389static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400390 sonicStream stream,
391 float *samples,
392 double speed,
393 int period)
394{
395 int t, newSamples;
396 double scale;
397 float *out;
398
399 if(speed >= 2.0) {
400 newSamples = period/(speed - 1.0);
401 } else if(speed > 1.0) {
402 newSamples = period;
403 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400404 }
405 scale = 1.0/newSamples;
406 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
407 return 0;
408 }
409 out = stream->outputBuffer + stream->numOutputSamples;
410 for(t = 0; t < newSamples; t++) {
411 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
412 }
413 stream->numOutputSamples += newSamples;
414 return newSamples;
415}
416
Bill Cox59e65122010-11-03 10:06:29 -0400417/* Insert a pitch period, and determine how much input to copy directly. */
418static int insertPitchPeriod(
419 sonicStream stream,
420 float *samples,
421 double speed,
422 int period)
423{
424 int t, newSamples;
425 double scale;
426 float *out;
427
428 if(speed < 0.5) {
429 newSamples = period*speed/(1.0 - speed);
430 } else {
431 newSamples = period;
432 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
433 }
434 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
435 return 0;
436 }
437 out = stream->outputBuffer + stream->numOutputSamples;
438 memcpy(out, samples, period*sizeof(float));
439 out += period;
440 scale = 1.0/newSamples;
441 for(t = 0; t < newSamples; t++) {
442 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
443 }
444 stream->numOutputSamples += period + newSamples;
445 return newSamples;
446}
447
Bill Cox9bf11b52010-11-03 05:33:09 -0400448/* Resample as many pitch periods as we have buffered on the input. Return 0 if
449 we fail to resize an input or output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500450static int processStreamInput(
451 sonicStream stream)
Bill Cox9bf11b52010-11-03 05:33:09 -0400452{
Bill Cox0c4c0602010-11-08 11:46:30 -0500453 float *samples = stream->inputBuffer;
454 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400455 double speed = stream->speed;
456 int position = 0, period, newSamples;
457 int maxRequired = stream->maxRequired;
458
Bill Cox9bf11b52010-11-03 05:33:09 -0400459 if(stream->numInputSamples < maxRequired) {
460 return 1;
461 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400462 do {
463 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400464 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400465 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400466 } else {
467 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400468 if(speed > 1.0) {
469 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
470 position += period + newSamples;
471 } else {
472 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
473 position += newSamples;
474 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400475 }
476 if(newSamples == 0) {
477 return 0; /* Failed to resize output buffer */
478 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400479 } while(position + maxRequired <= numSamples);
480 removeInputSamples(stream, position);
481 return 1;
482}
Bill Cox0c4c0602010-11-08 11:46:30 -0500483
484/* Write floating point data to the input buffer and process it. */
485int sonicWriteFloatToStream(
486 sonicStream stream,
487 float *samples,
488 int numSamples)
489{
490 double speed = stream->speed;
491
492 if(speed > 0.999999 && speed < 1.000001) {
493 /* No speed change - just copy to the output */
494 return copyFloatToOutput(stream, samples, numSamples);
495 }
496 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
497 return 0;
498 }
499 return processStreamInput(stream);
500}
501
502/* Simple wrapper around sonicWriteFloatToStream that does the short to float
503 conversion for you. */
504int sonicWriteShortToStream(
505 sonicStream stream,
506 short *samples,
507 int numSamples)
508{
509 double speed = stream->speed;
510
511 if(speed > 0.999999 && speed < 1.000001) {
512 /* No speed change - just copy to the output */
513 return copyShortToOutput(stream, samples, numSamples);
514 }
515 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
516 return 0;
517 }
518 return processStreamInput(stream);
519}
520