blob: aa3b033fae0bd061c84dc82c8633bc4c7b0e07ac [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 Cox882fb1d2010-11-02 16:27:20 -0400138 int t, newSamples, extraLength;
139 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);
144 extraLength = 0;
145 } else if(speed > 1.0) {
146 newSamples = period;
147 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
148 } else {
149 fprintf(stderr, "Speed currently must be > 1\n");
150 exit(1);
151 }
152 scale = 1.0/newSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400153 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
154 return 0;
155 }
156 out = stream->outputBuffer + stream->numOutputSamples;
157 for(t = 0; t < newSamples; t++) {
158 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
159 }
160 stream->numOutputSamples += newSamples;
161 return newSamples;
162}
163
164/* Enlarge the input buffer if needed. */
165static int enlargeInputBufferIfNeeded(
166 sonicStream stream,
167 int numSamples)
168{
169 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
170 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
171 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
172 stream->inputBufferSize*sizeof(float));
173 if(stream->inputBuffer == NULL) {
174 sonicDestroyStream(stream);
175 return 0;
176 }
177 }
178 return 1;
179}
180
181/* Add the input samples to the input buffer. */
182static int addSamplesToInputBuffer(
183 sonicStream stream,
184 float *samples,
185 int numSamples)
186{
187 if(numSamples == 0) {
188 return 1;
189 }
190 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
191 return 0;
192 }
193 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400194 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400195 return 1;
196}
197
198/* Remove input samples that we have already processed. */
199static void removeInputSamples(
200 sonicStream stream,
201 int position)
202{
203 int remainingSamples = stream->numInputSamples - position;
204
205 if(remainingSamples > 0) {
206 memmove(stream->inputBuffer, stream->inputBuffer + position,
207 remainingSamples*sizeof(float));
208 }
209 stream->numInputSamples = remainingSamples;
210}
211
Bill Cox882fb1d2010-11-02 16:27:20 -0400212/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
213 resize the output buffer. Otherwise, return numSamples */
214static int copyInputToOutput(
215 sonicStream stream,
216 int position)
217{
218 int numSamples = stream->remainingInputToCopy;
219
220 if(numSamples > stream->maxRequired) {
221 numSamples = stream->maxRequired;
222 }
223 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
224 return 0;
225 }
226 memcpy(stream->outputBuffer + stream->numOutputSamples, stream->inputBuffer + position,
227 numSamples*sizeof(float));
228 stream->numOutputSamples += numSamples;
229 stream->remainingInputToCopy -= numSamples;
230 return numSamples;
231}
232
Bill Coxca02d872010-11-02 15:10:52 -0400233/* Resample as many pitch periods as we have buffered on the input. Return 0 if
234 we fail to resize an input or output buffer */
235int sonicWriteToStream(
236 sonicStream stream,
237 float *samples,
238 int numSamples)
239{
240 double speed = stream->speed;
241 int position = 0, period, newSamples;
242 int maxRequired = stream->maxRequired;
243
244 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
245 return 0;
246 }
247 if(stream->numInputSamples < maxRequired) {
248 return 1;
249 }
250 samples = stream->inputBuffer;
251 numSamples = stream->numInputSamples;
252 do {
Bill Cox882fb1d2010-11-02 16:27:20 -0400253 if(stream->remainingInputToCopy > 0) {
254 period = 0;
255 newSamples = copyInputToOutput(stream, position);
256 } else {
257 period = findPitchPeriod(stream, samples + position);
258 newSamples = resamplePitchPeriod(stream, samples + position, speed, period);
259 }
Bill Coxca02d872010-11-02 15:10:52 -0400260 if(newSamples == 0) {
261 return 0; /* Failed to resize output buffer */
262 }
263 position += period + newSamples;
264 } while(position + maxRequired <= numSamples);
265 removeInputSamples(stream, position);
266 return 1;
267}
268
269/* Read data out of the stream. Sometimes no data will be available, and zero
270 is returned, which is not an error condition. */
271int sonicReadFromStream(
272 sonicStream stream,
273 float *samples,
274 int maxSamples)
275{
276 int numSamples = stream->numOutputSamples;
277 int remainingSamples = 0;
278
279 if(numSamples == 0) {
280 return 0;
281 }
282 if(numSamples > maxSamples) {
283 numSamples = maxSamples;
284 remainingSamples = numSamples - maxSamples;
285 }
286 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
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
295/* 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 }
309 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
310 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;
316 return sonicWriteToStream(stream, NULL, 0);
317}
318
319/* Return the number of samples in the output buffer */
320int sonicSamplesAvailale(
321 sonicStream stream)
322{
323 return stream->numOutputSamples;
324}