blob: cbf4f3523979203aacd406bd94d7cef39972bab0 [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
86/* Find the pitch period. This is a critical step, and we may have to try
87 multiple ways to get a good answer. This version uses AMDF. */
88static int findPitchPeriod(
89 sonicStream stream,
90 float *samples)
91{
92 int minPeriod = stream->minPeriod;
93 int maxPeriod = stream->maxPeriod;
94 int period, bestPeriod = 0;
95 double minDiff = 0.0;
96 double diff, value;
97 int xSample;
98
99 for(period = minPeriod; period <= maxPeriod; period++) {
100 diff = 0.0;
101 for(xSample = 0; xSample < period; xSample++) {
102 value = samples[xSample] - samples[xSample + period];
103 diff += value >= 0.0? value : -value;
104 }
105 diff /= period;
106 if(bestPeriod == 0 || diff < minDiff) {
107 minDiff = diff;
108 bestPeriod = period;
109 }
110 }
111 return bestPeriod;
112}
113
114/* Enlarge the output buffer if needed. */
115static int enlargeOutputBufferIfNeeded(
116 sonicStream stream,
117 int numSamples)
118{
119 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
120 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
121 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
122 stream->outputBufferSize*sizeof(float));
123 if(stream->outputBuffer == NULL) {
124 sonicDestroyStream(stream);
125 return 0;
126 }
127 }
128 return 1;
129}
130
131/* Do the pitch based resampling for one pitch period of the input. */
132static int resamplePitchPeriod(
133 sonicStream stream,
134 float *samples,
135 double speed,
136 int period)
137{
Bill Cox39adaff2010-11-03 03:36:05 -0400138 int t, newSamples;
Bill Cox882fb1d2010-11-02 16:27:20 -0400139 double scale;
Bill Coxca02d872010-11-02 15:10:52 -0400140 float *out;
Bill Coxca02d872010-11-02 15:10:52 -0400141
Bill Cox882fb1d2010-11-02 16:27:20 -0400142 if(speed >= 2.0) {
143 newSamples = period/(speed - 1.0);
Bill Cox882fb1d2010-11-02 16:27:20 -0400144 } else if(speed > 1.0) {
145 newSamples = period;
146 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
147 } else {
148 fprintf(stderr, "Speed currently must be > 1\n");
149 exit(1);
150 }
151 scale = 1.0/newSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400152 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
153 return 0;
154 }
155 out = stream->outputBuffer + stream->numOutputSamples;
156 for(t = 0; t < newSamples; t++) {
157 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
158 }
159 stream->numOutputSamples += newSamples;
160 return newSamples;
161}
162
163/* Enlarge the input buffer if needed. */
164static int enlargeInputBufferIfNeeded(
165 sonicStream stream,
166 int numSamples)
167{
168 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
169 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
170 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
171 stream->inputBufferSize*sizeof(float));
172 if(stream->inputBuffer == NULL) {
173 sonicDestroyStream(stream);
174 return 0;
175 }
176 }
177 return 1;
178}
179
180/* Add the input samples to the input buffer. */
181static int addSamplesToInputBuffer(
182 sonicStream stream,
183 float *samples,
184 int numSamples)
185{
186 if(numSamples == 0) {
187 return 1;
188 }
189 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
190 return 0;
191 }
192 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400193 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400194 return 1;
195}
196
197/* Remove input samples that we have already processed. */
198static void removeInputSamples(
199 sonicStream stream,
200 int position)
201{
202 int remainingSamples = stream->numInputSamples - position;
203
204 if(remainingSamples > 0) {
205 memmove(stream->inputBuffer, stream->inputBuffer + position,
206 remainingSamples*sizeof(float));
207 }
208 stream->numInputSamples = remainingSamples;
209}
210
Bill Cox882fb1d2010-11-02 16:27:20 -0400211/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
212 resize the output buffer. Otherwise, return numSamples */
213static int copyInputToOutput(
214 sonicStream stream,
215 int position)
216{
217 int numSamples = stream->remainingInputToCopy;
218
219 if(numSamples > stream->maxRequired) {
220 numSamples = stream->maxRequired;
221 }
222 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
223 return 0;
224 }
225 memcpy(stream->outputBuffer + stream->numOutputSamples, stream->inputBuffer + position,
226 numSamples*sizeof(float));
227 stream->numOutputSamples += numSamples;
228 stream->remainingInputToCopy -= numSamples;
229 return numSamples;
230}
231
Bill Coxca02d872010-11-02 15:10:52 -0400232/* Resample as many pitch periods as we have buffered on the input. Return 0 if
233 we fail to resize an input or output buffer */
234int sonicWriteToStream(
235 sonicStream stream,
236 float *samples,
237 int numSamples)
238{
239 double speed = stream->speed;
240 int position = 0, period, newSamples;
241 int maxRequired = stream->maxRequired;
242
243 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
244 return 0;
245 }
246 if(stream->numInputSamples < maxRequired) {
247 return 1;
248 }
249 samples = stream->inputBuffer;
250 numSamples = stream->numInputSamples;
251 do {
Bill Cox882fb1d2010-11-02 16:27:20 -0400252 if(stream->remainingInputToCopy > 0) {
253 period = 0;
254 newSamples = copyInputToOutput(stream, position);
255 } else {
256 period = findPitchPeriod(stream, samples + position);
257 newSamples = resamplePitchPeriod(stream, samples + position, speed, period);
258 }
Bill Coxca02d872010-11-02 15:10:52 -0400259 if(newSamples == 0) {
260 return 0; /* Failed to resize output buffer */
261 }
262 position += period + newSamples;
263 } while(position + maxRequired <= numSamples);
264 removeInputSamples(stream, position);
265 return 1;
266}
267
268/* Read data out of the stream. Sometimes no data will be available, and zero
269 is returned, which is not an error condition. */
270int sonicReadFromStream(
271 sonicStream stream,
272 float *samples,
273 int maxSamples)
274{
275 int numSamples = stream->numOutputSamples;
276 int remainingSamples = 0;
277
278 if(numSamples == 0) {
279 return 0;
280 }
281 if(numSamples > maxSamples) {
282 numSamples = maxSamples;
283 remainingSamples = numSamples - maxSamples;
284 }
285 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
286 if(remainingSamples > 0) {
287 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
288 remainingSamples*sizeof(float));
289 }
290 stream->numOutputSamples = remainingSamples;
291 return numSamples;
292}
293
294/* Force the sonic stream to generate output using whatever data it currently
295 has. Zeros will be appended to the input data if there is not enough data
296 in the stream's input buffer. Use this, followed by a final read from the
297 stream before destroying the stream. */
298int sonicFlushStream(
299 sonicStream stream)
300{
301 int maxRequired = stream->maxRequired;
302 int numSamples = stream->numInputSamples;
303 int remainingSpace;
304
305 if(numSamples == 0) {
306 return 1;
307 }
308 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
309 return 0;
310 }
311 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
312 remainingSpace = maxRequired - numSamples;
313 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
314 stream->numInputSamples = maxRequired;
315 return sonicWriteToStream(stream, NULL, 0);
316}
317
318/* Return the number of samples in the output buffer */
319int sonicSamplesAvailale(
320 sonicStream stream)
321{
322 return stream->numOutputSamples;
323}