blob: 6e66dff0f61b03190d1f8ee177ffb30e28f530d0 [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
Bill Coxa9999872010-11-11 14:36:59 -05006 The Sonic Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
Bill Coxca02d872010-11-02 15:10:52 -040010
Bill Coxa9999872010-11-11 14:36:59 -050011 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
Bill Coxca02d872010-11-02 15:10:52 -040015
Bill Coxa9999872010-11-11 14:36:59 -050016 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
Bill Cox882fb1d2010-11-02 16:27:20 -040020#include <stdio.h>
Bill Coxca02d872010-11-02 15:10:52 -040021#include <stdlib.h>
22#include <string.h>
Bill Coxa33e3bd2010-11-16 19:57:43 -050023#include <stdarg.h>
Bill Coxca02d872010-11-02 15:10:52 -040024#include "sonic.h"
25
26struct sonicStreamStruct {
Bill Cox6a1bbb12010-11-19 11:14:28 -050027 short *inputBuffer;
28 short *outputBuffer;
Bill Coxd544fdb2010-11-23 14:13:46 -050029 short *pitchBuffer;
30 short *downSampleBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -050031 float speed;
Bill Coxd544fdb2010-11-23 14:13:46 -050032 float volume;
33 float pitch;
Bill Cox1a299bb2010-11-19 15:07:17 -050034 int numChannels;
Bill Coxca02d872010-11-02 15:10:52 -040035 int inputBufferSize;
Bill Coxd544fdb2010-11-23 14:13:46 -050036 int pitchBufferSize;
Bill Coxca02d872010-11-02 15:10:52 -040037 int outputBufferSize;
38 int numInputSamples;
39 int numOutputSamples;
Bill Coxd544fdb2010-11-23 14:13:46 -050040 int numPitchSamples;
Bill Coxca02d872010-11-02 15:10:52 -040041 int minPeriod;
42 int maxPeriod;
43 int maxRequired;
44 int remainingInputToCopy;
45 int sampleRate;
Bill Coxc17208e2010-11-26 11:09:15 -050046 int prevPeriod;
47 int prevMaxDiff;
48 int prevMinDiff;
Bill Coxca02d872010-11-02 15:10:52 -040049};
50
Bill Cox1a299bb2010-11-19 15:07:17 -050051/* Just used for debugging */
Bill Coxa33e3bd2010-11-16 19:57:43 -050052void sonicMSG(char *format, ...)
53{
54 char buffer[4096];
55 va_list ap;
56 FILE *file;
57
58 va_start(ap, format);
59 vsprintf((char *)buffer, (char *)format, ap);
60 va_end(ap);
61 file=fopen("/tmp/sonic.log", "a");
62 fprintf(file, "%s", buffer);
63 fclose(file);
64}
65
Bill Coxd544fdb2010-11-23 14:13:46 -050066/* Scale the samples by the factor. */
67static void scaleSamples(
68 short *samples,
69 int numSamples,
70 float volume)
71{
72 int fixedPointVolume = volume*4096.0f;
73 int value;
74
75 while(numSamples--) {
76 value = (*samples*fixedPointVolume) >> 12;
77 if(value > 32767) {
78 value = 32767;
79 } else if(value < -32767) {
80 value = -32767;
81 }
82 *samples++ = value;
83 }
84}
85
Bill Coxaf9a6242010-11-08 09:32:27 -050086/* Get the speed of the stream. */
Bill Cox6a1bbb12010-11-19 11:14:28 -050087float sonicGetSpeed(
Bill Coxaf9a6242010-11-08 09:32:27 -050088 sonicStream stream)
89{
90 return stream->speed;
91}
92
Bill Coxd544fdb2010-11-23 14:13:46 -050093/* Set the speed of the stream. */
94void sonicSetSpeed(
95 sonicStream stream,
96 float speed)
97{
98 stream->speed = speed;
99}
100
101/* Get the pitch of the stream. */
102float sonicGetPitch(
103 sonicStream stream)
104{
105 return stream->pitch;
106}
107
108/* Set the pitch of the stream. */
109void sonicSetPitch(
110 sonicStream stream,
111 float pitch)
112{
113 stream->pitch = pitch;
114}
115
116/* Get the scaling factor of the stream. */
117float sonicGetVolume(
118 sonicStream stream)
119{
120 return stream->volume;
121}
122
123/* Set the scaling factor of the stream. */
124void sonicSetVolume(
125 sonicStream stream,
126 float volume)
127{
128 stream->volume = volume;
129}
130
Bill Coxaf9a6242010-11-08 09:32:27 -0500131/* Get the sample rate of the stream. */
132int sonicGetSampleRate(
133 sonicStream stream)
134{
135 return stream->sampleRate;
136}
137
Bill Cox527b4e82010-11-24 17:42:58 -0500138/* Get the number of channels. */
139int sonicGetNumChannels(
140 sonicStream stream)
141{
142 return stream->numChannels;
143}
144
Bill Coxca02d872010-11-02 15:10:52 -0400145/* Destroy the sonic stream. */
146void sonicDestroyStream(
147 sonicStream stream)
148{
149 if(stream->inputBuffer != NULL) {
150 free(stream->inputBuffer);
151 }
152 if(stream->outputBuffer != NULL) {
153 free(stream->outputBuffer);
154 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500155 if(stream->pitchBuffer != NULL) {
156 free(stream->pitchBuffer);
157 }
158 if(stream->downSampleBuffer != NULL) {
159 free(stream->downSampleBuffer);
160 }
Bill Coxca02d872010-11-02 15:10:52 -0400161 free(stream);
162}
163
164/* Create a sonic stream. Return NULL only if we are out of memory and cannot
165 allocate the stream. */
166sonicStream sonicCreateStream(
Bill Cox1a299bb2010-11-19 15:07:17 -0500167 int sampleRate,
168 int numChannels)
Bill Coxca02d872010-11-02 15:10:52 -0400169{
170 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
171 int minPeriod = sampleRate/SONIC_MAX_PITCH;
172 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
173 int maxRequired = 2*maxPeriod;
174
175 if(stream == NULL) {
176 return NULL;
177 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500178 stream->inputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500179 stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400180 if(stream->inputBuffer == NULL) {
181 sonicDestroyStream(stream);
182 return NULL;
183 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500184 stream->outputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500185 stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400186 if(stream->outputBuffer == NULL) {
187 sonicDestroyStream(stream);
188 return NULL;
189 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500190 stream->pitchBufferSize = maxRequired;
191 stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
192 if(stream->pitchBuffer == NULL) {
193 sonicDestroyStream(stream);
194 return NULL;
195 }
196 stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
197 stream->speed = 1.0f;
198 stream->pitch = 1.0f;
199 stream->volume = 1.0f;
Bill Coxca02d872010-11-02 15:10:52 -0400200 stream->sampleRate = sampleRate;
Bill Cox1a299bb2010-11-19 15:07:17 -0500201 stream->numChannels = numChannels;
Bill Coxca02d872010-11-02 15:10:52 -0400202 stream->minPeriod = minPeriod;
203 stream->maxPeriod = maxPeriod;
204 stream->maxRequired = maxRequired;
Bill Coxca02d872010-11-02 15:10:52 -0400205 return stream;
206}
207
Bill Coxca02d872010-11-02 15:10:52 -0400208/* Enlarge the output buffer if needed. */
209static int enlargeOutputBufferIfNeeded(
210 sonicStream stream,
211 int numSamples)
212{
213 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
214 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500215 stream->outputBuffer = (short *)realloc(stream->outputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500216 stream->outputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400217 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400218 return 0;
219 }
220 }
221 return 1;
222}
223
Bill Coxca02d872010-11-02 15:10:52 -0400224/* Enlarge the input buffer if needed. */
225static int enlargeInputBufferIfNeeded(
226 sonicStream stream,
227 int numSamples)
228{
229 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
230 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500231 stream->inputBuffer = (short *)realloc(stream->inputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500232 stream->inputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400233 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400234 return 0;
235 }
236 }
237 return 1;
238}
239
240/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500241static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400242 sonicStream stream,
243 float *samples,
244 int numSamples)
245{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500246 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500247 int count = numSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500248
Bill Coxca02d872010-11-02 15:10:52 -0400249 if(numSamples == 0) {
250 return 1;
251 }
252 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
253 return 0;
254 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500255 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500256 while(count--) {
257 *buffer++ = (*samples++)*32767.0f;
258 }
Bill Cox14efa442010-11-02 15:43:58 -0400259 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400260 return 1;
261}
262
Bill Cox0c4c0602010-11-08 11:46:30 -0500263/* Add the input samples to the input buffer. */
264static int addShortSamplesToInputBuffer(
265 sonicStream stream,
266 short *samples,
267 int numSamples)
268{
Bill Cox0c4c0602010-11-08 11:46:30 -0500269 if(numSamples == 0) {
270 return 1;
271 }
272 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
273 return 0;
274 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500275 memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
276 numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500277 stream->numInputSamples += numSamples;
278 return 1;
279}
280
Bill Cox8a23d2f2010-11-16 18:49:36 -0500281/* Add the input samples to the input buffer. */
282static int addUnsignedCharSamplesToInputBuffer(
283 sonicStream stream,
284 unsigned char *samples,
285 int numSamples)
286{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500287 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500288 int count = numSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500289
290 if(numSamples == 0) {
291 return 1;
292 }
293 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
294 return 0;
295 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500296 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500297 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500298 *buffer++ = (*samples++ - 128) << 8;
Bill Coxca02d872010-11-02 15:10:52 -0400299 }
300 stream->numInputSamples += numSamples;
301 return 1;
302}
303
304/* Remove input samples that we have already processed. */
305static void removeInputSamples(
306 sonicStream stream,
307 int position)
308{
309 int remainingSamples = stream->numInputSamples - position;
310
311 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500312 memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
313 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400314 }
315 stream->numInputSamples = remainingSamples;
316}
317
Bill Cox59e65122010-11-03 10:06:29 -0400318/* Just copy from the array to the output buffer */
Bill Cox68e2aee2010-11-23 19:24:41 -0500319static int copyToOutput(
Bill Cox0c4c0602010-11-08 11:46:30 -0500320 sonicStream stream,
321 short *samples,
322 int numSamples)
323{
Bill Cox0c4c0602010-11-08 11:46:30 -0500324 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
325 return 0;
326 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500327 memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
328 samples, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500329 stream->numOutputSamples += numSamples;
330 return numSamples;
331}
332
Bill Cox882fb1d2010-11-02 16:27:20 -0400333/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
334 resize the output buffer. Otherwise, return numSamples */
335static int copyInputToOutput(
336 sonicStream stream,
337 int position)
338{
339 int numSamples = stream->remainingInputToCopy;
340
341 if(numSamples > stream->maxRequired) {
342 numSamples = stream->maxRequired;
343 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500344 if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels,
Bill Cox1a299bb2010-11-19 15:07:17 -0500345 numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400346 return 0;
347 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400348 stream->remainingInputToCopy -= numSamples;
349 return numSamples;
350}
351
Bill Coxca02d872010-11-02 15:10:52 -0400352/* Read data out of the stream. Sometimes no data will be available, and zero
353 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500354int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400355 sonicStream stream,
356 float *samples,
357 int maxSamples)
358{
359 int numSamples = stream->numOutputSamples;
360 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500361 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500362 int count;
Bill Coxca02d872010-11-02 15:10:52 -0400363
364 if(numSamples == 0) {
365 return 0;
366 }
367 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400368 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400369 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400370 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500371 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500372 count = numSamples*stream->numChannels;
373 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500374 *samples++ = (*buffer++)/32767.0f;
375 }
Bill Coxca02d872010-11-02 15:10:52 -0400376 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500377 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
378 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400379 }
380 stream->numOutputSamples = remainingSamples;
381 return numSamples;
382}
383
Bill Cox0c4c0602010-11-08 11:46:30 -0500384/* Read short data out of the stream. Sometimes no data will be available, and zero
385 is returned, which is not an error condition. */
386int sonicReadShortFromStream(
387 sonicStream stream,
388 short *samples,
389 int maxSamples)
390{
391 int numSamples = stream->numOutputSamples;
392 int remainingSamples = 0;
Bill Cox0c4c0602010-11-08 11:46:30 -0500393
394 if(numSamples == 0) {
395 return 0;
396 }
397 if(numSamples > maxSamples) {
398 remainingSamples = numSamples - maxSamples;
399 numSamples = maxSamples;
400 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500401 memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500402 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500403 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
404 remainingSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500405 }
406 stream->numOutputSamples = remainingSamples;
407 return numSamples;
408}
409
Bill Cox8a23d2f2010-11-16 18:49:36 -0500410/* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
411 is returned, which is not an error condition. */
412int sonicReadUnsignedCharFromStream(
413 sonicStream stream,
414 unsigned char *samples,
415 int maxSamples)
416{
417 int numSamples = stream->numOutputSamples;
418 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500419 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500420 int count;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500421
422 if(numSamples == 0) {
423 return 0;
424 }
425 if(numSamples > maxSamples) {
426 remainingSamples = numSamples - maxSamples;
427 numSamples = maxSamples;
428 }
429 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500430 count = numSamples*stream->numChannels;
431 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500432 *samples++ = (char)((*buffer++) >> 8) + 128;
Bill Coxca02d872010-11-02 15:10:52 -0400433 }
434 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500435 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
436 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400437 }
438 stream->numOutputSamples = remainingSamples;
439 return numSamples;
440}
441
442/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500443 has. No extra delay will be added to the output, but flushing in the middle of
444 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400445int sonicFlushStream(
446 sonicStream stream)
447{
448 int maxRequired = stream->maxRequired;
449 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500450 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400451
452 if(numSamples == 0) {
453 return 1;
454 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500455 if(numSamples >= maxRequired && !sonicWriteShortToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400456 return 0;
457 }
458 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500459 if(numSamples == 0) {
460 return 1;
461 }
Bill Coxca02d872010-11-02 15:10:52 -0400462 remainingSpace = maxRequired - numSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500463 memset(stream->inputBuffer + numSamples*stream->numChannels, 0,
464 remainingSpace*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400465 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500466 numOutputSamples = stream->numOutputSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500467 if(!sonicWriteShortToStream(stream, NULL, 0)) {
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500468 return 0;
469 }
470 /* Throw away any extra samples we generated due to the silence we added */
471 expectedSamples = (int)(numSamples*stream->speed + 0.5);
472 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
473 stream->numOutputSamples = numOutputSamples + expectedSamples;
474 }
475 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400476}
477
478/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400479int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400480 sonicStream stream)
481{
482 return stream->numOutputSamples;
483}
Bill Cox9bf11b52010-11-03 05:33:09 -0400484
Bill Coxd544fdb2010-11-23 14:13:46 -0500485/* If skip is greater than one, average skip samples togther and write them to
486 the down-sample buffer. If numChannels is greater than one, mix the channels
487 together as we down sample. */
488static void downSampleInput(
489 sonicStream stream,
490 short *samples,
491 int skip)
492{
493 int numSamples = stream->maxRequired/skip;
494 int samplesPerValue = stream->numChannels*skip;
495 int i, j;
496 int value;
497 short *downSamples = stream->downSampleBuffer;
498
499 for(i = 0; i < numSamples; i++) {
500 value = 0;
501 for(j = 0; j < samplesPerValue; j++) {
502 value += *samples++;
503 }
504 value /= samplesPerValue;
505 *downSamples++ = value;
506 }
507}
508
Bill Cox1a299bb2010-11-19 15:07:17 -0500509/* Find the best frequency match in the range, and given a sample skip multiple.
510 For now, just find the pitch of the first channel. */
Bill Cox0cd49c82010-11-03 10:46:22 -0400511static int findPitchPeriodInRange(
Bill Cox6a1bbb12010-11-19 11:14:28 -0500512 short *samples,
Bill Cox0cd49c82010-11-03 10:46:22 -0400513 int minPeriod,
Bill Coxc17208e2010-11-26 11:09:15 -0500514 int maxPeriod,
515 int *retMinDiff,
516 int *retMaxDiff)
Bill Cox0cd49c82010-11-03 10:46:22 -0400517{
518 int period, bestPeriod = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500519 short *s, *p, sVal, pVal;
Bill Coxc17208e2010-11-26 11:09:15 -0500520 unsigned long diff, minDiff = 1, maxDiff = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500521 int i;
Bill Cox0cd49c82010-11-03 10:46:22 -0400522
Bill Coxd544fdb2010-11-23 14:13:46 -0500523 for(period = minPeriod; period <= maxPeriod; period++) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500524 diff = 0;
Bill Cox0cd49c82010-11-03 10:46:22 -0400525 s = samples;
Bill Coxd544fdb2010-11-23 14:13:46 -0500526 p = samples + period;
527 for(i = 0; i < period; i++) {
528 sVal = *s++;
529 pVal = *p++;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500530 diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
531 (unsigned short)(pVal - sVal);
Bill Cox0cd49c82010-11-03 10:46:22 -0400532 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500533 /* Note that the highest number of samples we add into diff will be less
534 than 256, since we skip samples. Thus, diff is a 24 bit number, and
535 we can safely multiply by numSamples without overflow */
Bill Coxc17208e2010-11-26 11:09:15 -0500536 if(diff*bestPeriod < minDiff*period) {
Bill Cox0cd49c82010-11-03 10:46:22 -0400537 minDiff = diff;
538 bestPeriod = period;
539 }
Bill Coxc17208e2010-11-26 11:09:15 -0500540 if(diff*bestPeriod > maxDiff*period) {
541 maxDiff = diff;
542 }
Bill Cox0cd49c82010-11-03 10:46:22 -0400543 }
Bill Coxc17208e2010-11-26 11:09:15 -0500544 *retMinDiff = minDiff;
545 *retMaxDiff = maxDiff;
Bill Cox0cd49c82010-11-03 10:46:22 -0400546 return bestPeriod;
547}
548
Bill Coxc17208e2010-11-26 11:09:15 -0500549/* At abrupt ends of voiced words, we can have pitch periods that are better
550 aproximated by the previous pitch period estimate. Try to detect this case. */
551static int prevPeriodBetter(
552 sonicStream stream,
553 int period,
554 int minDiff,
555 int maxDiff)
556{
557 if(maxDiff*3/2 < stream->prevMaxDiff && (maxDiff*3.0f)*stream->prevMinDiff <
558 (float)stream->prevMaxDiff*minDiff*2) {
559 return 1;
560 }
561 return 0;
562}
563
Bill Cox9bf11b52010-11-03 05:33:09 -0400564/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400565 multiple ways to get a good answer. This version uses AMDF. To improve
566 speed, we down sample by an integer factor get in the 11KHz range, and then
567 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400568static int findPitchPeriod(
569 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500570 short *samples)
Bill Cox9bf11b52010-11-03 05:33:09 -0400571{
572 int minPeriod = stream->minPeriod;
573 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400574 int sampleRate = stream->sampleRate;
Bill Coxc17208e2010-11-26 11:09:15 -0500575 int minDiff, maxDiff, retPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400576 int skip = 1;
577 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400578
Bill Cox0cd49c82010-11-03 10:46:22 -0400579 if(sampleRate > SONIC_AMDF_FREQ) {
580 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400581 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500582 if(stream->numChannels == 1 && skip == 1) {
Bill Coxc17208e2010-11-26 11:09:15 -0500583 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff, &maxDiff);
584 } else {
585 downSampleInput(stream, samples, skip);
586 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip,
587 maxPeriod/skip, &minDiff, &maxDiff);
588 if(skip != 1) {
589 period *= skip;
590 minPeriod = period - (skip << 2);
591 maxPeriod = period + (skip << 2);
592 if(minPeriod < stream->minPeriod) {
593 minPeriod = stream->minPeriod;
594 }
595 if(maxPeriod > stream->maxPeriod) {
596 maxPeriod = stream->maxPeriod;
597 }
598 if(stream->numChannels == 1) {
599 period = findPitchPeriodInRange(samples, minPeriod, maxPeriod,
600 &minDiff, &maxDiff);
601 } else {
602 downSampleInput(stream, samples, 1);
603 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod,
604 maxPeriod, &minDiff, &maxDiff);
605 }
606 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500607 }
Bill Coxc17208e2010-11-26 11:09:15 -0500608 if(prevPeriodBetter(stream, period, minDiff, maxDiff)) {
609 retPeriod = stream->prevPeriod;
610 } else {
611 retPeriod = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500612 }
Bill Coxc17208e2010-11-26 11:09:15 -0500613 stream->prevMinDiff = minDiff;
614 stream->prevMaxDiff = maxDiff;
615 stream->prevPeriod = period;
616 return retPeriod;
Bill Coxd544fdb2010-11-23 14:13:46 -0500617}
618
Bill Cox68e2aee2010-11-23 19:24:41 -0500619/* Overlap two sound segments, ramp the volume of one down, while ramping the
620 other one from zero up, and add them, storing the result at the output. */
621static void overlapAdd(
622 int numSamples,
623 int numChannels,
624 short *out,
625 short *rampDown,
626 short *rampUp)
627{
Bill Coxd76d2222010-11-24 11:42:29 -0500628 short *o, *u, *d;
Bill Cox68e2aee2010-11-23 19:24:41 -0500629 int i, t;
630
631 for(i = 0; i < numChannels; i++) {
632 o = out + i;
Bill Coxd76d2222010-11-24 11:42:29 -0500633 u = rampUp + i;
634 d = rampDown + i;
Bill Cox68e2aee2010-11-23 19:24:41 -0500635 for(t = 0; t < numSamples; t++) {
Bill Coxd76d2222010-11-24 11:42:29 -0500636 *o = (*d*(numSamples - t) + *u*t)/numSamples;
Bill Cox68e2aee2010-11-23 19:24:41 -0500637 o += numChannels;
Bill Coxd76d2222010-11-24 11:42:29 -0500638 d += numChannels;
639 u += numChannels;
640 }
641 }
642}
643
644/* Overlap two sound segments, ramp the volume of one down, while ramping the
645 other one from zero up, and add them, storing the result at the output. */
646static void overlapAddWithSeparation(
647 int numSamples,
648 int numChannels,
649 int separation,
650 short *out,
651 short *rampDown,
652 short *rampUp)
653{
654 short *o, *u, *d;
655 int i, t;
656
657 for(i = 0; i < numChannels; i++) {
658 o = out + i;
659 u = rampUp + i;
660 d = rampDown + i;
661 for(t = 0; t < numSamples + separation; t++) {
662 if(t < separation) {
663 *o = *d*(numSamples - t)/numSamples;
664 d += numChannels;
665 } else if(t < numSamples) {
666 *o = (*d*(numSamples - t) + *u*(t - separation))/numSamples;
667 d += numChannels;
668 u += numChannels;
669 } else {
670 *o = *u*(t - separation)/numSamples;
671 u += numChannels;
672 }
673 o += numChannels;
Bill Cox68e2aee2010-11-23 19:24:41 -0500674 }
675 }
676}
677
678/* Just move the new samples in the output buffer to the pitch bufer */
679static int moveNewSamplesToPitchBuffer(
Bill Coxd544fdb2010-11-23 14:13:46 -0500680 sonicStream stream,
681 int originalNumOutputSamples)
682{
Bill Cox68e2aee2010-11-23 19:24:41 -0500683 int numSamples = stream->numOutputSamples - originalNumOutputSamples;
684 int numChannels = stream->numChannels;
685
686 if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
687 stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples;
688 stream->pitchBuffer = (short *)realloc(stream->pitchBuffer,
689 stream->pitchBufferSize*sizeof(short)*numChannels);
690 if(stream->pitchBuffer == NULL) {
691 return 0;
692 }
693 }
694 memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels,
695 stream->outputBuffer + originalNumOutputSamples*numChannels,
696 numSamples*sizeof(short)*numChannels);
697 stream->numOutputSamples = originalNumOutputSamples;
698 stream->numPitchSamples += numSamples;
699 return 1;
700}
701
702/* Remove processed samples from the pitch buffer. */
703static void removePitchSamples(
704 sonicStream stream,
705 int numSamples)
706{
707 int numChannels = stream->numChannels;
708 short *source = stream->pitchBuffer + numSamples*numChannels;
709
710 if(numSamples == 0) {
711 return;
712 }
713 if(numSamples != stream->numPitchSamples) {
714 memmove(stream->pitchBuffer, source, (stream->numPitchSamples -
715 numSamples)*sizeof(short)*numChannels);
716 }
717 stream->numPitchSamples -= numSamples;
718}
719
720/* Change the pitch. The latency this introduces could be reduced by looking at
721 past samples to determine pitch, rather than future. */
722static int adjustPitch(
723 sonicStream stream,
724 int originalNumOutputSamples)
725{
726 float pitch = stream->pitch;
727 int numChannels = stream->numChannels;
Bill Coxd76d2222010-11-24 11:42:29 -0500728 int period, newPeriod, separation;
Bill Cox68e2aee2010-11-23 19:24:41 -0500729 int position = 0;
730 short *out, *rampDown, *rampUp;
731
732 if(stream->numOutputSamples == originalNumOutputSamples) {
733 return 1;
734 }
735 if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
736 return 0;
737 }
738 while(stream->numPitchSamples - position >= stream->maxRequired) {
739 period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels);
740 newPeriod = period/pitch;
Bill Coxd76d2222010-11-24 11:42:29 -0500741 if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) {
Bill Cox68e2aee2010-11-23 19:24:41 -0500742 return 0;
743 }
Bill Coxd76d2222010-11-24 11:42:29 -0500744 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
745 if(pitch >= 1.0f) {
746 rampDown = stream->pitchBuffer + position*numChannels;
747 rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels;
748 overlapAdd(newPeriod, numChannels, out, rampDown, rampUp);
749 } else {
750 rampDown = stream->pitchBuffer + position*numChannels;
751 rampUp = stream->pitchBuffer + position*numChannels;
752 separation = newPeriod - period;
753 overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp);
Bill Cox68e2aee2010-11-23 19:24:41 -0500754 }
Bill Coxd76d2222010-11-24 11:42:29 -0500755 stream->numOutputSamples += newPeriod;
Bill Cox68e2aee2010-11-23 19:24:41 -0500756 position += period;
757 }
758 removePitchSamples(stream, position);
759 return 1;
Bill Cox9bf11b52010-11-03 05:33:09 -0400760}
761
Bill Cox59e65122010-11-03 10:06:29 -0400762/* Skip over a pitch period, and copy period/speed samples to the output */
763static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400764 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500765 short *samples,
766 float speed,
Bill Cox9bf11b52010-11-03 05:33:09 -0400767 int period)
768{
Bill Cox68e2aee2010-11-23 19:24:41 -0500769 long newSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500770 int numChannels = stream->numChannels;
Bill Cox9bf11b52010-11-03 05:33:09 -0400771
Bill Cox6a1bbb12010-11-19 11:14:28 -0500772 if(speed >= 2.0f) {
773 newSamples = period/(speed - 1.0f);
774 } else if(speed > 1.0f) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400775 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500776 stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
Bill Cox9bf11b52010-11-03 05:33:09 -0400777 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400778 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
779 return 0;
780 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500781 overlapAdd(newSamples, numChannels, stream->outputBuffer +
782 stream->numOutputSamples*numChannels, samples, samples + period*numChannels);
Bill Cox9bf11b52010-11-03 05:33:09 -0400783 stream->numOutputSamples += newSamples;
784 return newSamples;
785}
786
Bill Cox59e65122010-11-03 10:06:29 -0400787/* Insert a pitch period, and determine how much input to copy directly. */
788static int insertPitchPeriod(
789 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500790 short *samples,
791 float speed,
Bill Cox59e65122010-11-03 10:06:29 -0400792 int period)
793{
Bill Cox68e2aee2010-11-23 19:24:41 -0500794 long newSamples;
795 short *out;
Bill Cox1a299bb2010-11-19 15:07:17 -0500796 int numChannels = stream->numChannels;
Bill Cox59e65122010-11-03 10:06:29 -0400797
Bill Cox6a1bbb12010-11-19 11:14:28 -0500798 if(speed < 0.5f) {
799 newSamples = period*speed/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400800 } else {
801 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500802 stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400803 }
804 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
805 return 0;
806 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500807 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
808 memcpy(out, samples, period*sizeof(short)*numChannels);
Bill Cox68e2aee2010-11-23 19:24:41 -0500809 out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels;
810 overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400811 stream->numOutputSamples += period + newSamples;
812 return newSamples;
813}
814
Bill Cox9bf11b52010-11-03 05:33:09 -0400815/* Resample as many pitch periods as we have buffered on the input. Return 0 if
Bill Coxd544fdb2010-11-23 14:13:46 -0500816 we fail to resize an input or output buffer. Also scale the output by the volume. */
817static int changeSpeed(
818 sonicStream stream,
819 float speed)
Bill Cox9bf11b52010-11-03 05:33:09 -0400820{
Bill Cox1a299bb2010-11-19 15:07:17 -0500821 short *samples;
Bill Cox0c4c0602010-11-08 11:46:30 -0500822 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400823 int position = 0, period, newSamples;
824 int maxRequired = stream->maxRequired;
825
Bill Cox9bf11b52010-11-03 05:33:09 -0400826 if(stream->numInputSamples < maxRequired) {
827 return 1;
828 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400829 do {
830 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400831 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400832 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400833 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500834 samples = stream->inputBuffer + position*stream->numChannels;
835 period = findPitchPeriod(stream, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400836 if(speed > 1.0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500837 newSamples = skipPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400838 position += period + newSamples;
839 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500840 newSamples = insertPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400841 position += newSamples;
842 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400843 }
844 if(newSamples == 0) {
845 return 0; /* Failed to resize output buffer */
846 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400847 } while(position + maxRequired <= numSamples);
848 removeInputSamples(stream, position);
849 return 1;
850}
Bill Cox0c4c0602010-11-08 11:46:30 -0500851
Bill Coxd544fdb2010-11-23 14:13:46 -0500852/* Resample as many pitch periods as we have buffered on the input. Return 0 if
853 we fail to resize an input or output buffer. Also scale the output by the volume. */
854static int processStreamInput(
855 sonicStream stream)
856{
857 int originalNumOutputSamples = stream->numOutputSamples;
858 float speed = stream->speed/stream->pitch;
859
860 if(speed > 1.00001 || speed < 0.99999) {
861 changeSpeed(stream, speed);
862 } else {
Bill Cox68e2aee2010-11-23 19:24:41 -0500863 if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
Bill Coxd544fdb2010-11-23 14:13:46 -0500864 return 0;
865 }
866 stream->numInputSamples = 0;
867 }
868 if(stream->pitch != 1.0f) {
Bill Cox68e2aee2010-11-23 19:24:41 -0500869 if(!adjustPitch(stream, originalNumOutputSamples)) {
870 return 0;
871 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500872 }
873 if(stream->volume != 1.0f) {
874 /* Adjust output volume. */
875 scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
876 (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
877 stream->volume);
878 }
879 return 1;
880}
881
Bill Cox0c4c0602010-11-08 11:46:30 -0500882/* Write floating point data to the input buffer and process it. */
883int sonicWriteFloatToStream(
884 sonicStream stream,
885 float *samples,
886 int numSamples)
887{
Bill Cox0c4c0602010-11-08 11:46:30 -0500888 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
889 return 0;
890 }
891 return processStreamInput(stream);
892}
893
894/* Simple wrapper around sonicWriteFloatToStream that does the short to float
895 conversion for you. */
896int sonicWriteShortToStream(
897 sonicStream stream,
898 short *samples,
899 int numSamples)
900{
Bill Cox0c4c0602010-11-08 11:46:30 -0500901 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
902 return 0;
903 }
904 return processStreamInput(stream);
905}
906
Bill Cox8a23d2f2010-11-16 18:49:36 -0500907/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
908 conversion for you. */
909int sonicWriteUnsignedCharToStream(
910 sonicStream stream,
911 unsigned char *samples,
912 int numSamples)
913{
Bill Cox8a23d2f2010-11-16 18:49:36 -0500914 if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
915 return 0;
916 }
917 return processStreamInput(stream);
918}
919
Bill Cox036d7322010-11-09 09:29:24 -0500920/* This is a non-stream oriented interface to just change the speed of a sound sample */
921int sonicChangeFloatSpeed(
922 float *samples,
923 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500924 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500925 float pitch,
926 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500927 int sampleRate,
928 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500929{
Bill Coxd544fdb2010-11-23 14:13:46 -0500930 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500931
Bill Coxd544fdb2010-11-23 14:13:46 -0500932 sonicSetSpeed(stream, speed);
933 sonicSetPitch(stream, pitch);
934 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500935 sonicWriteFloatToStream(stream, samples, numSamples);
936 sonicFlushStream(stream);
937 numSamples = sonicSamplesAvailable(stream);
938 sonicReadFloatFromStream(stream, samples, numSamples);
939 sonicDestroyStream(stream);
940 return numSamples;
941}
942
943/* This is a non-stream oriented interface to just change the speed of a sound sample */
944int sonicChangeShortSpeed(
945 short *samples,
946 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500947 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500948 float pitch,
949 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500950 int sampleRate,
951 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500952{
Bill Coxd544fdb2010-11-23 14:13:46 -0500953 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500954
Bill Coxd544fdb2010-11-23 14:13:46 -0500955 sonicSetSpeed(stream, speed);
956 sonicSetPitch(stream, pitch);
957 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500958 sonicWriteShortToStream(stream, samples, numSamples);
959 sonicFlushStream(stream);
960 numSamples = sonicSamplesAvailable(stream);
961 sonicReadShortFromStream(stream, samples, numSamples);
962 sonicDestroyStream(stream);
963 return numSamples;
964}