blob: 6543495917e31e7ad0b8cdc4f95111742e76b9dd [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 Cox882fb1d2010-11-02 16:27:20 -0400151/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
152 resize the output buffer. Otherwise, return numSamples */
153static int copyInputToOutput(
154 sonicStream stream,
155 int position)
156{
157 int numSamples = stream->remainingInputToCopy;
158
159 if(numSamples > stream->maxRequired) {
160 numSamples = stream->maxRequired;
161 }
162 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
163 return 0;
164 }
165 memcpy(stream->outputBuffer + stream->numOutputSamples, stream->inputBuffer + position,
166 numSamples*sizeof(float));
167 stream->numOutputSamples += numSamples;
168 stream->remainingInputToCopy -= numSamples;
169 return numSamples;
170}
171
Bill Coxca02d872010-11-02 15:10:52 -0400172/* Read data out of the stream. Sometimes no data will be available, and zero
173 is returned, which is not an error condition. */
174int sonicReadFromStream(
175 sonicStream stream,
176 float *samples,
177 int maxSamples)
178{
179 int numSamples = stream->numOutputSamples;
180 int remainingSamples = 0;
181
182 if(numSamples == 0) {
183 return 0;
184 }
185 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400186 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400187 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400188 }
189 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
190 if(remainingSamples > 0) {
191 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
192 remainingSamples*sizeof(float));
193 }
194 stream->numOutputSamples = remainingSamples;
195 return numSamples;
196}
197
198/* Force the sonic stream to generate output using whatever data it currently
199 has. Zeros will be appended to the input data if there is not enough data
200 in the stream's input buffer. Use this, followed by a final read from the
201 stream before destroying the stream. */
202int sonicFlushStream(
203 sonicStream stream)
204{
205 int maxRequired = stream->maxRequired;
206 int numSamples = stream->numInputSamples;
207 int remainingSpace;
208
209 if(numSamples == 0) {
210 return 1;
211 }
212 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
213 return 0;
214 }
215 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
216 remainingSpace = maxRequired - numSamples;
217 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
218 stream->numInputSamples = maxRequired;
219 return sonicWriteToStream(stream, NULL, 0);
220}
221
222/* Return the number of samples in the output buffer */
223int sonicSamplesAvailale(
224 sonicStream stream)
225{
226 return stream->numOutputSamples;
227}
Bill Cox9bf11b52010-11-03 05:33:09 -0400228
229/* Find the pitch period. This is a critical step, and we may have to try
230 multiple ways to get a good answer. This version uses AMDF. */
231static int findPitchPeriod(
232 sonicStream stream,
233 float *samples)
234{
235 int minPeriod = stream->minPeriod;
236 int maxPeriod = stream->maxPeriod;
237 int period, bestPeriod = 0;
238 double minDiff = 0.0;
239 double diff, value;
240 int xSample;
241
242 for(period = minPeriod; period <= maxPeriod; period++) {
243 diff = 0.0;
244 for(xSample = 0; xSample < period; xSample++) {
245 value = samples[xSample] - samples[xSample + period];
246 diff += value >= 0.0? value : -value;
247 }
248 diff /= period;
249 if(bestPeriod == 0 || diff < minDiff) {
250 minDiff = diff;
251 bestPeriod = period;
252 }
253 }
254 return bestPeriod;
255}
256
257/* Do the pitch based resampling for one pitch period of the input. */
258static int resamplePitchPeriod(
259 sonicStream stream,
260 float *samples,
261 double speed,
262 int period)
263{
264 int t, newSamples;
265 double scale;
266 float *out;
267
268 if(speed >= 2.0) {
269 newSamples = period/(speed - 1.0);
270 } else if(speed > 1.0) {
271 newSamples = period;
272 stream->remainingInputToCopy = period*(2.0 - speed)/(speed - 1.0);
273 } else {
274 fprintf(stderr, "Speed currently must be > 1\n");
275 exit(1);
276 }
277 scale = 1.0/newSamples;
278 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
279 return 0;
280 }
281 out = stream->outputBuffer + stream->numOutputSamples;
282 for(t = 0; t < newSamples; t++) {
283 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
284 }
285 stream->numOutputSamples += newSamples;
286 return newSamples;
287}
288
289/* Resample as many pitch periods as we have buffered on the input. Return 0 if
290 we fail to resize an input or output buffer */
291int sonicWriteToStream(
292 sonicStream stream,
293 float *samples,
294 int numSamples)
295{
296 double speed = stream->speed;
297 int position = 0, period, newSamples;
298 int maxRequired = stream->maxRequired;
299
300 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
301 return 0;
302 }
303 if(stream->numInputSamples < maxRequired) {
304 return 1;
305 }
306 samples = stream->inputBuffer;
307 numSamples = stream->numInputSamples;
308 do {
309 if(stream->remainingInputToCopy > 0) {
310 period = 0;
311 newSamples = copyInputToOutput(stream, position);
312 } else {
313 period = findPitchPeriod(stream, samples + position);
314 newSamples = resamplePitchPeriod(stream, samples + position, speed, period);
315 }
316 if(newSamples == 0) {
317 return 0; /* Failed to resize output buffer */
318 }
319 position += period + newSamples;
320 } while(position + maxRequired <= numSamples);
321 removeInputSamples(stream, position);
322 return 1;
323}