blob: 95f43ff346a077e2fe97e75241cb095d55bfa842 [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. */
19#include <stdlib.h>
20#include <string.h>
21#include "sonic.h"
22
23struct sonicStreamStruct {
24 double speed;
25 float *inputBuffer;
26 float *outputBuffer;
27 int inputBufferSize;
28 int outputBufferSize;
29 int numInputSamples;
30 int numOutputSamples;
31 int minPeriod;
32 int maxPeriod;
33 int maxRequired;
34 int remainingInputToCopy;
35 int sampleRate;
36};
37
38/* Destroy the sonic stream. */
39void sonicDestroyStream(
40 sonicStream stream)
41{
42 if(stream->inputBuffer != NULL) {
43 free(stream->inputBuffer);
44 }
45 if(stream->outputBuffer != NULL) {
46 free(stream->outputBuffer);
47 }
48 free(stream);
49}
50
51/* Create a sonic stream. Return NULL only if we are out of memory and cannot
52 allocate the stream. */
53sonicStream sonicCreateStream(
54 double speed,
55 int sampleRate)
56{
57 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
58 int minPeriod = sampleRate/SONIC_MAX_PITCH;
59 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
60 int maxRequired = 2*maxPeriod;
61
62 if(stream == NULL) {
63 return NULL;
64 }
65 stream->inputBuffer = (float *)calloc(maxRequired, sizeof(float));
66 if(stream->inputBuffer == NULL) {
67 sonicDestroyStream(stream);
68 return NULL;
69 }
70 stream->outputBuffer = (float *)calloc(maxRequired, sizeof(float));
71 if(stream->outputBuffer == NULL) {
72 sonicDestroyStream(stream);
73 return NULL;
74 }
75 stream->speed = speed;
76 stream->sampleRate = sampleRate;
77 stream->minPeriod = minPeriod;
78 stream->maxPeriod = maxPeriod;
79 stream->maxRequired = maxRequired;
80 stream->inputBufferSize = maxRequired;
81 stream->outputBufferSize = maxRequired;
82 return stream;
83}
84
85/* Find the pitch period. This is a critical step, and we may have to try
86 multiple ways to get a good answer. This version uses AMDF. */
87static int findPitchPeriod(
88 sonicStream stream,
89 float *samples)
90{
91 int minPeriod = stream->minPeriod;
92 int maxPeriod = stream->maxPeriod;
93 int period, bestPeriod = 0;
94 double minDiff = 0.0;
95 double diff, value;
96 int xSample;
97
98 for(period = minPeriod; period <= maxPeriod; period++) {
99 diff = 0.0;
100 for(xSample = 0; xSample < period; xSample++) {
101 value = samples[xSample] - samples[xSample + period];
102 diff += value >= 0.0? value : -value;
103 }
104 diff /= period;
105 if(bestPeriod == 0 || diff < minDiff) {
106 minDiff = diff;
107 bestPeriod = period;
108 }
109 }
110 return bestPeriod;
111}
112
113/* Enlarge the output buffer if needed. */
114static int enlargeOutputBufferIfNeeded(
115 sonicStream stream,
116 int numSamples)
117{
118 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
119 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
120 stream->outputBuffer = (float *)realloc(stream->outputBuffer,
121 stream->outputBufferSize*sizeof(float));
122 if(stream->outputBuffer == NULL) {
123 sonicDestroyStream(stream);
124 return 0;
125 }
126 }
127 return 1;
128}
129
130/* Do the pitch based resampling for one pitch period of the input. */
131static int resamplePitchPeriod(
132 sonicStream stream,
133 float *samples,
134 double speed,
135 int period)
136{
137 int newSamples = period/(speed - 1.0); /* Note that speed is >= 2.0 */
138 double scale = 1.0/newSamples;
139 float *out;
140 int t;
141
142 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
143 return 0;
144 }
145 out = stream->outputBuffer + stream->numOutputSamples;
146 for(t = 0; t < newSamples; t++) {
147 out[t] = scale*(samples[t]*(newSamples - t) + samples[t + period]*t);
148 }
149 stream->numOutputSamples += newSamples;
150 return newSamples;
151}
152
153/* Enlarge the input buffer if needed. */
154static int enlargeInputBufferIfNeeded(
155 sonicStream stream,
156 int numSamples)
157{
158 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
159 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
160 stream->inputBuffer = (float *)realloc(stream->inputBuffer,
161 stream->inputBufferSize*sizeof(float));
162 if(stream->inputBuffer == NULL) {
163 sonicDestroyStream(stream);
164 return 0;
165 }
166 }
167 return 1;
168}
169
170/* Add the input samples to the input buffer. */
171static int addSamplesToInputBuffer(
172 sonicStream stream,
173 float *samples,
174 int numSamples)
175{
176 if(numSamples == 0) {
177 return 1;
178 }
179 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
180 return 0;
181 }
182 memcpy(stream->inputBuffer + stream->numInputSamples, samples, numSamples*sizeof(float));
Bill Cox14efa442010-11-02 15:43:58 -0400183 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400184 return 1;
185}
186
187/* Remove input samples that we have already processed. */
188static void removeInputSamples(
189 sonicStream stream,
190 int position)
191{
192 int remainingSamples = stream->numInputSamples - position;
193
194 if(remainingSamples > 0) {
195 memmove(stream->inputBuffer, stream->inputBuffer + position,
196 remainingSamples*sizeof(float));
197 }
198 stream->numInputSamples = remainingSamples;
199}
200
201/* Resample as many pitch periods as we have buffered on the input. Return 0 if
202 we fail to resize an input or output buffer */
203int sonicWriteToStream(
204 sonicStream stream,
205 float *samples,
206 int numSamples)
207{
208 double speed = stream->speed;
209 int position = 0, period, newSamples;
210 int maxRequired = stream->maxRequired;
211
212 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
213 return 0;
214 }
215 if(stream->numInputSamples < maxRequired) {
216 return 1;
217 }
218 samples = stream->inputBuffer;
219 numSamples = stream->numInputSamples;
220 do {
221 period = findPitchPeriod(stream, samples + position);
222 newSamples = resamplePitchPeriod(stream, samples + position, speed, period);
223 if(newSamples == 0) {
224 return 0; /* Failed to resize output buffer */
225 }
226 position += period + newSamples;
227 } while(position + maxRequired <= numSamples);
228 removeInputSamples(stream, position);
229 return 1;
230}
231
232/* Read data out of the stream. Sometimes no data will be available, and zero
233 is returned, which is not an error condition. */
234int sonicReadFromStream(
235 sonicStream stream,
236 float *samples,
237 int maxSamples)
238{
239 int numSamples = stream->numOutputSamples;
240 int remainingSamples = 0;
241
242 if(numSamples == 0) {
243 return 0;
244 }
245 if(numSamples > maxSamples) {
246 numSamples = maxSamples;
247 remainingSamples = numSamples - maxSamples;
248 }
249 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
250 if(remainingSamples > 0) {
251 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
252 remainingSamples*sizeof(float));
253 }
254 stream->numOutputSamples = remainingSamples;
255 return numSamples;
256}
257
258/* Force the sonic stream to generate output using whatever data it currently
259 has. Zeros will be appended to the input data if there is not enough data
260 in the stream's input buffer. Use this, followed by a final read from the
261 stream before destroying the stream. */
262int sonicFlushStream(
263 sonicStream stream)
264{
265 int maxRequired = stream->maxRequired;
266 int numSamples = stream->numInputSamples;
267 int remainingSpace;
268
269 if(numSamples == 0) {
270 return 1;
271 }
272 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
273 return 0;
274 }
275 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
276 remainingSpace = maxRequired - numSamples;
277 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
278 stream->numInputSamples = maxRequired;
279 return sonicWriteToStream(stream, NULL, 0);
280}
281
282/* Return the number of samples in the output buffer */
283int sonicSamplesAvailale(
284 sonicStream stream)
285{
286 return stream->numOutputSamples;
287}