blob: 390e42fd138d245ae6825d820c46c9fb4a4027c0 [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
39/* Destroy the sonic stream. */
40void sonicDestroyStream(
41 sonicStream stream)
42{
43 if(stream->inputBuffer != NULL) {
44 free(stream->inputBuffer);
45 }
46 if(stream->outputBuffer != NULL) {
47 free(stream->outputBuffer);
48 }
49 free(stream);
50}
51
52/* Create a sonic stream. Return NULL only if we are out of memory and cannot
53 allocate the stream. */
54sonicStream sonicCreateStream(
55 double speed,
56 int sampleRate)
57{
58 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
59 int minPeriod = sampleRate/SONIC_MAX_PITCH;
60 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
61 int maxRequired = 2*maxPeriod;
62
63 if(stream == NULL) {
64 return NULL;
65 }
66 stream->inputBuffer = (float *)calloc(maxRequired, sizeof(float));
67 if(stream->inputBuffer == NULL) {
68 sonicDestroyStream(stream);
69 return NULL;
70 }
71 stream->outputBuffer = (float *)calloc(maxRequired, sizeof(float));
72 if(stream->outputBuffer == NULL) {
73 sonicDestroyStream(stream);
74 return NULL;
75 }
76 stream->speed = speed;
77 stream->sampleRate = sampleRate;
78 stream->minPeriod = minPeriod;
79 stream->maxPeriod = maxPeriod;
80 stream->maxRequired = maxRequired;
81 stream->inputBufferSize = maxRequired;
82 stream->outputBufferSize = maxRequired;
83 return stream;
84}
85
Bill Coxca02d872010-11-02 15:10:52 -040086/* Enlarge the output buffer if needed. */
87static int enlargeOutputBufferIfNeeded(
88 sonicStream stream,
89 int numSamples)
90{
91 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
92 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
93 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
94 stream->outputBufferSize*sizeof(float));
95 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -040096 return 0;
97 }
98 }
99 return 1;
100}
101
Bill Coxca02d872010-11-02 15:10:52 -0400102/* Enlarge the input buffer if needed. */
103static int enlargeInputBufferIfNeeded(
104 sonicStream stream,
105 int numSamples)
106{
107 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
108 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
109 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
110 stream->inputBufferSize*sizeof(float));
111 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400112 return 0;
113 }
114 }
115 return 1;
116}
117
118/* Add the input samples to the input buffer. */
119static int addSamplesToInputBuffer(
120 sonicStream stream,
121 float *samples,
122 int numSamples)
123{
124 if(numSamples == 0) {
125 return 1;
126 }
127 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
128 return 0;
129 }
130 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400131 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400132 return 1;
133}
134
135/* Remove input samples that we have already processed. */
136static void removeInputSamples(
137 sonicStream stream,
138 int position)
139{
140 int remainingSamples = stream->numInputSamples - position;
141
142 if(remainingSamples > 0) {
143 memmove(stream->inputBuffer, stream->inputBuffer + position,
144 remainingSamples*sizeof(float));
145 }
146 stream->numInputSamples = remainingSamples;
147}
148
Bill Cox59e65122010-11-03 10:06:29 -0400149/* Just copy from the array to the output buffer */
150static int copyToOutput(
151 sonicStream stream,
152 float *samples,
153 int numSamples)
154{
155 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
156 return 0;
157 }
158 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
159 stream->numOutputSamples += numSamples;
160 return numSamples;
161}
162
Bill Cox882fb1d2010-11-02 16:27:20 -0400163/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
164 resize the output buffer. Otherwise, return numSamples */
165static int copyInputToOutput(
166 sonicStream stream,
167 int position)
168{
169 int numSamples = stream->remainingInputToCopy;
170
171 if(numSamples > stream->maxRequired) {
172 numSamples = stream->maxRequired;
173 }
Bill Cox59e65122010-11-03 10:06:29 -0400174 if(!copyToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400175 return 0;
176 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400177 stream->remainingInputToCopy -= numSamples;
178 return numSamples;
179}
180
Bill Coxca02d872010-11-02 15:10:52 -0400181/* Read data out of the stream. Sometimes no data will be available, and zero
182 is returned, which is not an error condition. */
183int sonicReadFromStream(
184 sonicStream stream,
185 float *samples,
186 int maxSamples)
187{
188 int numSamples = stream->numOutputSamples;
189 int remainingSamples = 0;
190
191 if(numSamples == 0) {
192 return 0;
193 }
194 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400195 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400196 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400197 }
198 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
199 if(remainingSamples > 0) {
200 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
201 remainingSamples*sizeof(float));
202 }
203 stream->numOutputSamples = remainingSamples;
204 return numSamples;
205}
206
207/* Force the sonic stream to generate output using whatever data it currently
208 has. Zeros will be appended to the input data if there is not enough data
209 in the stream's input buffer. Use this, followed by a final read from the
210 stream before destroying the stream. */
211int sonicFlushStream(
212 sonicStream stream)
213{
214 int maxRequired = stream->maxRequired;
215 int numSamples = stream->numInputSamples;
216 int remainingSpace;
217
218 if(numSamples == 0) {
219 return 1;
220 }
221 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
222 return 0;
223 }
224 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
225 remainingSpace = maxRequired - numSamples;
226 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
227 stream->numInputSamples = maxRequired;
228 return sonicWriteToStream(stream, NULL, 0);
229}
230
231/* Return the number of samples in the output buffer */
232int sonicSamplesAvailale(
233 sonicStream stream)
234{
235 return stream->numOutputSamples;
236}
Bill Cox9bf11b52010-11-03 05:33:09 -0400237
Bill Cox0cd49c82010-11-03 10:46:22 -0400238/* Find the best frequency match in the range, and given a sample skip multiple. */
239static int findPitchPeriodInRange(
240 sonicStream stream,
241 float *samples,
242 int minPeriod,
243 int maxPeriod,
244 int skip)
245{
246 int period, bestPeriod = 0;
247 double minDiff = 0.0;
248 double diff;
249 float value, *s, *p;
250 int xSample;
251
252 for(period = minPeriod; period <= maxPeriod; period += skip) {
253 diff = 0.0;
254 s = samples;
255 p = samples + period;
256 for(xSample = 0; xSample < period; xSample += skip) {
257 value = *s - *p;
258 s += skip;
259 p += skip;
260 diff += value >= 0.0? value : -value;
261 }
262 if(bestPeriod == 0 || diff < minDiff*period) {
263 diff /= period;
264 minDiff = diff;
265 bestPeriod = period;
266 }
267 }
268 return bestPeriod;
269}
270
Bill Cox9bf11b52010-11-03 05:33:09 -0400271/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400272 multiple ways to get a good answer. This version uses AMDF. To improve
273 speed, we down sample by an integer factor get in the 11KHz range, and then
274 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400275static int findPitchPeriod(
276 sonicStream stream,
277 float *samples)
278{
279 int minPeriod = stream->minPeriod;
280 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400281 int sampleRate = stream->sampleRate;
282 int skip = 1;
283 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400284
Bill Cox0cd49c82010-11-03 10:46:22 -0400285 if(sampleRate > SONIC_AMDF_FREQ) {
286 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400287 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400288 period = findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, skip);
289 minPeriod = period*(1.0 - SONIC_AMDF_RANGE);
290 maxPeriod = period*(1.0 + SONIC_AMDF_RANGE);
291 if(minPeriod < stream->minPeriod) {
292 minPeriod = stream->minPeriod;
293 }
294 if(maxPeriod > stream->maxPeriod) {
295 maxPeriod = stream->maxPeriod;
296 }
297 return findPitchPeriodInRange(stream, samples, minPeriod, maxPeriod, 1);
Bill Cox9bf11b52010-11-03 05:33:09 -0400298}
299
Bill Cox59e65122010-11-03 10:06:29 -0400300/* Skip over a pitch period, and copy period/speed samples to the output */
301static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400302 sonicStream stream,
303 float *samples,
304 double speed,
305 int period)
306{
307 int t, newSamples;
308 double scale;
309 float *out;
310
311 if(speed >= 2.0) {
312 newSamples = period/(speed - 1.0);
313 } else if(speed > 1.0) {
314 newSamples = period;
315 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400316 }
317 scale = 1.0/newSamples;
318 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
319 return 0;
320 }
321 out = stream->outputBuffer + stream->numOutputSamples;
322 for(t = 0; t < newSamples; t++) {
323 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
324 }
325 stream->numOutputSamples += newSamples;
326 return newSamples;
327}
328
Bill Cox59e65122010-11-03 10:06:29 -0400329/* Insert a pitch period, and determine how much input to copy directly. */
330static int insertPitchPeriod(
331 sonicStream stream,
332 float *samples,
333 double speed,
334 int period)
335{
336 int t, newSamples;
337 double scale;
338 float *out;
339
340 if(speed < 0.5) {
341 newSamples = period*speed/(1.0 - speed);
342 } else {
343 newSamples = period;
344 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
345 }
346 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
347 return 0;
348 }
349 out = stream->outputBuffer + stream->numOutputSamples;
350 memcpy(out, samples, period*sizeof(float));
351 out += period;
352 scale = 1.0/newSamples;
353 for(t = 0; t < newSamples; t++) {
354 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
355 }
356 stream->numOutputSamples += period + newSamples;
357 return newSamples;
358}
359
Bill Cox9bf11b52010-11-03 05:33:09 -0400360/* Resample as many pitch periods as we have buffered on the input. Return 0 if
361 we fail to resize an input or output buffer */
362int sonicWriteToStream(
363 sonicStream stream,
364 float *samples,
365 int numSamples)
366{
367 double speed = stream->speed;
368 int position = 0, period, newSamples;
369 int maxRequired = stream->maxRequired;
370
Bill Cox59e65122010-11-03 10:06:29 -0400371 if(speed > 0.999999 && speed < 1.000001) {
372 /* No speed change - just copy to the output */
373 return copyToOutput(stream, samples, numSamples);
374 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400375 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
376 return 0;
377 }
378 if(stream->numInputSamples < maxRequired) {
379 return 1;
380 }
381 samples = stream->inputBuffer;
382 numSamples = stream->numInputSamples;
383 do {
384 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400385 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400386 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400387 } else {
388 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400389 if(speed > 1.0) {
390 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
391 position += period + newSamples;
392 } else {
393 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
394 position += newSamples;
395 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400396 }
397 if(newSamples == 0) {
398 return 0; /* Failed to resize output buffer */
399 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400400 } while(position + maxRequired <= numSamples);
401 removeInputSamples(stream, position);
402 return 1;
403}