blob: 200599da914fed06648b1d6e83a2cd689bb6f283 [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));
183 return 1;
184}
185
186/* Remove input samples that we have already processed. */
187static void removeInputSamples(
188 sonicStream stream,
189 int position)
190{
191 int remainingSamples = stream->numInputSamples - position;
192
193 if(remainingSamples > 0) {
194 memmove(stream->inputBuffer, stream->inputBuffer + position,
195 remainingSamples*sizeof(float));
196 }
197 stream->numInputSamples = remainingSamples;
198}
199
200/* Resample as many pitch periods as we have buffered on the input. Return 0 if
201 we fail to resize an input or output buffer */
202int sonicWriteToStream(
203 sonicStream stream,
204 float *samples,
205 int numSamples)
206{
207 double speed = stream->speed;
208 int position = 0, period, newSamples;
209 int maxRequired = stream->maxRequired;
210
211 if(!addSamplesToInputBuffer(stream, samples, numSamples)) {
212 return 0;
213 }
214 if(stream->numInputSamples < maxRequired) {
215 return 1;
216 }
217 samples = stream->inputBuffer;
218 numSamples = stream->numInputSamples;
219 do {
220 period = findPitchPeriod(stream, samples + position);
221 newSamples = resamplePitchPeriod(stream, samples + position, speed, period);
222 if(newSamples == 0) {
223 return 0; /* Failed to resize output buffer */
224 }
225 position += period + newSamples;
226 } while(position + maxRequired <= numSamples);
227 removeInputSamples(stream, position);
228 return 1;
229}
230
231/* Read data out of the stream. Sometimes no data will be available, and zero
232 is returned, which is not an error condition. */
233int sonicReadFromStream(
234 sonicStream stream,
235 float *samples,
236 int maxSamples)
237{
238 int numSamples = stream->numOutputSamples;
239 int remainingSamples = 0;
240
241 if(numSamples == 0) {
242 return 0;
243 }
244 if(numSamples > maxSamples) {
245 numSamples = maxSamples;
246 remainingSamples = numSamples - maxSamples;
247 }
248 memcpy(samples, stream->outputBuffer, numSamples*sizeof(float));
249 if(remainingSamples > 0) {
250 memmove(stream->outputBuffer, stream->outputBuffer + numSamples,
251 remainingSamples*sizeof(float));
252 }
253 stream->numOutputSamples = remainingSamples;
254 return numSamples;
255}
256
257/* Force the sonic stream to generate output using whatever data it currently
258 has. Zeros will be appended to the input data if there is not enough data
259 in the stream's input buffer. Use this, followed by a final read from the
260 stream before destroying the stream. */
261int sonicFlushStream(
262 sonicStream stream)
263{
264 int maxRequired = stream->maxRequired;
265 int numSamples = stream->numInputSamples;
266 int remainingSpace;
267
268 if(numSamples == 0) {
269 return 1;
270 }
271 if(numSamples >= maxRequired && !sonicWriteToStream(stream, NULL, 0)) {
272 return 0;
273 }
274 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
275 remainingSpace = maxRequired - numSamples;
276 memset(stream->inputBuffer + numSamples, 0, remainingSpace*sizeof(float));
277 stream->numInputSamples = maxRequired;
278 return sonicWriteToStream(stream, NULL, 0);
279}
280
281/* Return the number of samples in the output buffer */
282int sonicSamplesAvailale(
283 sonicStream stream)
284{
285 return stream->numOutputSamples;
286}