blob: 3843a93f7e6de995eff3cb288a4a8d24eea7daac [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. */
133static int addSamplesToInputBuffer(
134 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
149/* Remove input samples that we have already processed. */
150static void removeInputSamples(
151 sonicStream stream,
152 int position)
153{
154 int remainingSamples = stream->numInputSamples - position;
155
156 if(remainingSamples > 0) {
157 memmove(stream->inputBuffer, stream->inputBuffer + position,
158 remainingSamples*sizeof(float));
159 }
160 stream->numInputSamples = remainingSamples;
161}
162
Bill Cox59e65122010-11-03 10:06:29 -0400163/* Just copy from the array to the output buffer */
164static int copyToOutput(
165 sonicStream stream,
166 float *samples,
167 int numSamples)
168{
169 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
170 return 0;
171 }
172 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
173 stream->numOutputSamples += numSamples;
174 return numSamples;
175}
176
Bill Cox882fb1d2010-11-02 16:27:20 -0400177/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
178 resize the output buffer. Otherwise, return numSamples */
179static int copyInputToOutput(
180 sonicStream stream,
181 int position)
182{
183 int numSamples = stream->remainingInputToCopy;
184
185 if(numSamples > stream->maxRequired) {
186 numSamples = stream->maxRequired;
187 }
Bill Cox59e65122010-11-03 10:06:29 -0400188 if(!copyToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400189 return 0;
190 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400191 stream->remainingInputToCopy -= numSamples;
192 return numSamples;
193}
194
Bill Coxca02d872010-11-02 15:10:52 -0400195/* Read data out of the stream. Sometimes no data will be available, and zero
196 is returned, which is not an error condition. */
197int sonicReadFromStream(
198 sonicStream stream,
199 float *samples,
200 int maxSamples)
201{
202 int numSamples = stream->numOutputSamples;
203 int remainingSamples = 0;
204
205 if(numSamples == 0) {
206 return 0;
207 }
208 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400209 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400210 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400211 }
212 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
213 if(remainingSamples > 0) {
214 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
215 remainingSamples*sizeof(float));
216 }
217 stream->numOutputSamples = remainingSamples;
218 return numSamples;
219}
220
221/* Force the sonic stream to generate output using whatever data it currently
222 has. Zeros will be appended to the input data if there is not enough data
223 in the stream's input buffer. Use this, followed by a final read from the
224 stream before destroying the stream. */
225int sonicFlushStream(
226 sonicStream stream)
227{
228 int maxRequired = stream->maxRequired;
229 int numSamples = stream->numInputSamples;
230 int remainingSpace;
231
232 if(numSamples == 0) {
233 return 1;
234 }
235 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
236 return 0;
237 }
238 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
239 remainingSpace = maxRequired - numSamples;
240 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
241 stream->numInputSamples = maxRequired;
242 return sonicWriteToStream(stream, NULL, 0);
243}
244
245/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400246int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400247 sonicStream stream)
248{
249 return stream->numOutputSamples;
250}
Bill Cox9bf11b52010-11-03 05:33:09 -0400251
Bill Cox0cd49c82010-11-03 10:46:22 -0400252/* Find the best frequency match in the range, and given a sample skip multiple. */
253static int findPitchPeriodInRange(
254 sonicStream stream,
255 float *samples,
256 int minPeriod,
257 int maxPeriod,
258 int skip)
259{
260 int period, bestPeriod = 0;
261 double minDiff = 0.0;
262 double diff;
263 float value, *s, *p;
264 int xSample;
265
266 for(period = minPeriod; period <= maxPeriod; period += skip) {
267 diff = 0.0;
268 s = samples;
269 p = samples + period;
270 for(xSample = 0; xSample < period; xSample += skip) {
271 value = *s - *p;
272 s += skip;
273 p += skip;
274 diff += value >= 0.0? value : -value;
275 }
276 if(bestPeriod == 0 || diff < minDiff*period) {
277 diff /= period;
278 minDiff = diff;
279 bestPeriod = period;
280 }
281 }
282 return bestPeriod;
283}
284
Bill Cox9bf11b52010-11-03 05:33:09 -0400285/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400286 multiple ways to get a good answer. This version uses AMDF. To improve
287 speed, we down sample by an integer factor get in the 11KHz range, and then
288 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400289static int findPitchPeriod(
290 sonicStream stream,
291 float *samples)
292{
293 int minPeriod = stream->minPeriod;
294 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400295 int sampleRate = stream->sampleRate;
296 int skip = 1;
297 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400298
Bill Cox0cd49c82010-11-03 10:46:22 -0400299 if(sampleRate > SONIC_AMDF_FREQ) {
300 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400301 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400302 period = findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, skip);
303 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
304 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
305 if(minPeriod < stream->minPeriod) {
306 minPeriod = stream->minPeriod;
307 }
308 if(maxPeriod > stream->maxPeriod) {
309 maxPeriod = stream->maxPeriod;
310 }
311 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400312}
313
Bill Cox59e65122010-11-03 10:06:29 -0400314/* Skip over a pitch period, and copy period/speed samples to the output */
315static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400316 sonicStream stream,
317 float *samples,
318 double speed,
319 int period)
320{
321 int t, newSamples;
322 double scale;
323 float *out;
324
325 if(speed >= 2.0) {
326 newSamples = period/(speed - 1.0);
327 } else if(speed > 1.0) {
328 newSamples = period;
329 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400330 }
331 scale = 1.0/newSamples;
332 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
333 return 0;
334 }
335 out = stream->outputBuffer + stream->numOutputSamples;
336 for(t = 0; t < newSamples; t++) {
337 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
338 }
339 stream->numOutputSamples += newSamples;
340 return newSamples;
341}
342
Bill Cox59e65122010-11-03 10:06:29 -0400343/* Insert a pitch period, and determine how much input to copy directly. */
344static int insertPitchPeriod(
345 sonicStream stream,
346 float *samples,
347 double speed,
348 int period)
349{
350 int t, newSamples;
351 double scale;
352 float *out;
353
354 if(speed < 0.5) {
355 newSamples = period*speed/(1.0 - speed);
356 } else {
357 newSamples = period;
358 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
359 }
360 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
361 return 0;
362 }
363 out = stream->outputBuffer + stream->numOutputSamples;
364 memcpy(out, samples, period*sizeof(float));
365 out += period;
366 scale = 1.0/newSamples;
367 for(t = 0; t < newSamples; t++) {
368 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
369 }
370 stream->numOutputSamples += period + newSamples;
371 return newSamples;
372}
373
Bill Cox9bf11b52010-11-03 05:33:09 -0400374/* Resample as many pitch periods as we have buffered on the input. Return 0 if
375 we fail to resize an input or output buffer */
376int sonicWriteToStream(
377 sonicStream stream,
378 float *samples,
379 int numSamples)
380{
381 double speed = stream->speed;
382 int position = 0, period, newSamples;
383 int maxRequired = stream->maxRequired;
384
Bill Cox59e65122010-11-03 10:06:29 -0400385 if(speed > 0.999999 && speed < 1.000001) {
386 /* No speed change - just copy to the output */
387 return copyToOutput(stream, samples, numSamples);
388 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400389 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
390 return 0;
391 }
392 if(stream->numInputSamples < maxRequired) {
393 return 1;
394 }
395 samples = stream->inputBuffer;
396 numSamples = stream->numInputSamples;
397 do {
398 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400399 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400400 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400401 } else {
402 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400403 if(speed > 1.0) {
404 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
405 position += period + newSamples;
406 } else {
407 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
408 position += newSamples;
409 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400410 }
411 if(newSamples == 0) {
412 return 0; /* Failed to resize output buffer */
413 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400414 } while(position + maxRequired <= numSamples);
415 removeInputSamples(stream, position);
416 return 1;
417}