blob: a77add7d5f1a76e29af6ebb5cc4292e3b327c98d [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) {
96 sonicDestroyStream(stream);
97 return 0;
98 }
99 }
100 return 1;
101}
102
Bill Coxca02d872010-11-02 15:10:52 -0400103/* Enlarge the input buffer if needed. */
104static int enlargeInputBufferIfNeeded(
105 sonicStream stream,
106 int numSamples)
107{
108 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
109 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
110 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
111 stream->inputBufferSize*sizeof(float));
112 if(stream->inputBuffer == NULL) {
113 sonicDestroyStream(stream);
114 return 0;
115 }
116 }
117 return 1;
118}
119
120/* Add the input samples to the input buffer. */
121static int addSamplesToInputBuffer(
122 sonicStream stream,
123 float *samples,
124 int numSamples)
125{
126 if(numSamples == 0) {
127 return 1;
128 }
129 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
130 return 0;
131 }
132 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400133 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400134 return 1;
135}
136
137/* Remove input samples that we have already processed. */
138static void removeInputSamples(
139 sonicStream stream,
140 int position)
141{
142 int remainingSamples = stream->numInputSamples - position;
143
144 if(remainingSamples > 0) {
145 memmove(stream->inputBuffer, stream->inputBuffer + position,
146 remainingSamples*sizeof(float));
147 }
148 stream->numInputSamples = remainingSamples;
149}
150
Bill Cox59e65122010-11-03 10:06:29 -0400151/* Just copy from the array to the output buffer */
152static int copyToOutput(
153 sonicStream stream,
154 float *samples,
155 int numSamples)
156{
157 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
158 return 0;
159 }
160 memcpy(stream->outputBuffer + stream->numOutputSamples, samples, numSamples*sizeof(float));
161 stream->numOutputSamples += numSamples;
162 return numSamples;
163}
164
Bill Cox882fb1d2010-11-02 16:27:20 -0400165/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
166 resize the output buffer. Otherwise, return numSamples */
167static int copyInputToOutput(
168 sonicStream stream,
169 int position)
170{
171 int numSamples = stream->remainingInputToCopy;
172
173 if(numSamples > stream->maxRequired) {
174 numSamples = stream->maxRequired;
175 }
Bill Cox59e65122010-11-03 10:06:29 -0400176 if(!copyToOutput(stream, stream->inputBuffer + position, numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400177 return 0;
178 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400179 stream->remainingInputToCopy -= numSamples;
180 return numSamples;
181}
182
Bill Coxca02d872010-11-02 15:10:52 -0400183/* Read data out of the stream. Sometimes no data will be available, and zero
184 is returned, which is not an error condition. */
185int sonicReadFromStream(
186 sonicStream stream,
187 float *samples,
188 int maxSamples)
189{
190 int numSamples = stream->numOutputSamples;
191 int remainingSamples = 0;
192
193 if(numSamples == 0) {
194 return 0;
195 }
196 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400197 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400198 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400199 }
200 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
201 if(remainingSamples > 0) {
202 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
203 remainingSamples*sizeof(float));
204 }
205 stream->numOutputSamples = remainingSamples;
206 return numSamples;
207}
208
209/* Force the sonic stream to generate output using whatever data it currently
210 has. Zeros will be appended to the input data if there is not enough data
211 in the stream's input buffer. Use this, followed by a final read from the
212 stream before destroying the stream. */
213int sonicFlushStream(
214 sonicStream stream)
215{
216 int maxRequired = stream->maxRequired;
217 int numSamples = stream->numInputSamples;
218 int remainingSpace;
219
220 if(numSamples == 0) {
221 return 1;
222 }
223 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
224 return 0;
225 }
226 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
227 remainingSpace = maxRequired - numSamples;
228 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
229 stream->numInputSamples = maxRequired;
230 return sonicWriteToStream(stream, NULL, 0);
231}
232
233/* Return the number of samples in the output buffer */
234int sonicSamplesAvailale(
235 sonicStream stream)
236{
237 return stream->numOutputSamples;
238}
Bill Cox9bf11b52010-11-03 05:33:09 -0400239
240/* Find the pitch period. This is a critical step, and we may have to try
241 multiple ways to get a good answer. This version uses AMDF. */
242static int findPitchPeriod(
243 sonicStream stream,
244 float *samples)
245{
246 int minPeriod = stream->minPeriod;
247 int maxPeriod = stream->maxPeriod;
248 int period, bestPeriod = 0;
249 double minDiff = 0.0;
250 double diff, value;
251 int xSample;
252
253 for(period = minPeriod; period <= maxPeriod; period++) {
254 diff = 0.0;
255 for(xSample = 0; xSample < period; xSample++) {
256 value = samples[xSample] - samples[xSample + period];
257 diff += value >= 0.0? value : -value;
258 }
259 diff /= period;
260 if(bestPeriod == 0 || diff < minDiff) {
261 minDiff = diff;
262 bestPeriod = period;
263 }
264 }
265 return bestPeriod;
266}
267
Bill Cox59e65122010-11-03 10:06:29 -0400268/* Skip over a pitch period, and copy period/speed samples to the output */
269static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400270 sonicStream stream,
271 float *samples,
272 double speed,
273 int period)
274{
275 int t, newSamples;
276 double scale;
277 float *out;
278
279 if(speed >= 2.0) {
280 newSamples = period/(speed - 1.0);
281 } else if(speed > 1.0) {
282 newSamples = period;
283 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
Bill Cox9bf11b52010-11-03 05:33:09 -0400284 }
285 scale = 1.0/newSamples;
286 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
287 return 0;
288 }
289 out = stream->outputBuffer + stream->numOutputSamples;
290 for(t = 0; t < newSamples; t++) {
291 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
292 }
293 stream->numOutputSamples += newSamples;
294 return newSamples;
295}
296
Bill Cox59e65122010-11-03 10:06:29 -0400297/* Insert a pitch period, and determine how much input to copy directly. */
298static int insertPitchPeriod(
299 sonicStream stream,
300 float *samples,
301 double speed,
302 int period)
303{
304 int t, newSamples;
305 double scale;
306 float *out;
307
308 if(speed < 0.5) {
309 newSamples = period*speed/(1.0 - speed);
310 } else {
311 newSamples = period;
312 stream->remainingInputToCopy = period*(2.0*speed - 1.0)/(1.0 - speed);
313 }
314 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
315 return 0;
316 }
317 out = stream->outputBuffer + stream->numOutputSamples;
318 memcpy(out, samples, period*sizeof(float));
319 out += period;
320 scale = 1.0/newSamples;
321 for(t = 0; t < newSamples; t++) {
322 out[t] = scale*(samples[t]*t + samples[t + period]*(newSamples - t));
323 }
324 stream->numOutputSamples += period + newSamples;
325 return newSamples;
326}
327
Bill Cox9bf11b52010-11-03 05:33:09 -0400328/* Resample as many pitch periods as we have buffered on the input. Return 0 if
329 we fail to resize an input or output buffer */
330int sonicWriteToStream(
331 sonicStream stream,
332 float *samples,
333 int numSamples)
334{
335 double speed = stream->speed;
336 int position = 0, period, newSamples;
337 int maxRequired = stream->maxRequired;
338
Bill Cox59e65122010-11-03 10:06:29 -0400339 if(speed > 0.999999 && speed < 1.000001) {
340 /* No speed change - just copy to the output */
341 return copyToOutput(stream, samples, numSamples);
342 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400343 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
344 return 0;
345 }
346 if(stream->numInputSamples < maxRequired) {
347 return 1;
348 }
349 samples = stream->inputBuffer;
350 numSamples = stream->numInputSamples;
351 do {
352 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400353 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400354 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400355 } else {
356 period = findPitchPeriod(stream, samples + position);
Bill Cox59e65122010-11-03 10:06:29 -0400357 if(speed > 1.0) {
358 newSamples = skipPitchPeriod(stream, samples + position, speed, period);
359 position += period + newSamples;
360 } else {
361 newSamples = insertPitchPeriod(stream, samples + position, speed, period);
362 position += newSamples;
363 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400364 }
365 if(newSamples == 0) {
366 return 0; /* Failed to resize output buffer */
367 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400368 } while(position + maxRequired <= numSamples);
369 removeInputSamples(stream, position);
370 return 1;
371}