blob: e0c4375d5f719ec79456403cb4d12c4ce9f07646 [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;
46};
47
Bill Cox1a299bb2010-11-19 15:07:17 -050048/* Just used for debugging */
Bill Coxa33e3bd2010-11-16 19:57:43 -050049void sonicMSG(char *format, ...)
50{
51 char buffer[4096];
52 va_list ap;
53 FILE *file;
54
55 va_start(ap, format);
56 vsprintf((char *)buffer, (char *)format, ap);
57 va_end(ap);
58 file=fopen("/tmp/sonic.log", "a");
59 fprintf(file, "%s", buffer);
60 fclose(file);
61}
62
Bill Coxd544fdb2010-11-23 14:13:46 -050063/* Scale the samples by the factor. */
64static void scaleSamples(
65 short *samples,
66 int numSamples,
67 float volume)
68{
69 int fixedPointVolume = volume*4096.0f;
70 int value;
71
72 while(numSamples--) {
73 value = (*samples*fixedPointVolume) >> 12;
74 if(value > 32767) {
75 value = 32767;
76 } else if(value < -32767) {
77 value = -32767;
78 }
79 *samples++ = value;
80 }
81}
82
Bill Coxaf9a6242010-11-08 09:32:27 -050083/* Get the speed of the stream. */
Bill Cox6a1bbb12010-11-19 11:14:28 -050084float sonicGetSpeed(
Bill Coxaf9a6242010-11-08 09:32:27 -050085 sonicStream stream)
86{
87 return stream->speed;
88}
89
Bill Coxd544fdb2010-11-23 14:13:46 -050090/* Set the speed of the stream. */
91void sonicSetSpeed(
92 sonicStream stream,
93 float speed)
94{
95 stream->speed = speed;
96}
97
98/* Get the pitch of the stream. */
99float sonicGetPitch(
100 sonicStream stream)
101{
102 return stream->pitch;
103}
104
105/* Set the pitch of the stream. */
106void sonicSetPitch(
107 sonicStream stream,
108 float pitch)
109{
110 stream->pitch = pitch;
111}
112
113/* Get the scaling factor of the stream. */
114float sonicGetVolume(
115 sonicStream stream)
116{
117 return stream->volume;
118}
119
120/* Set the scaling factor of the stream. */
121void sonicSetVolume(
122 sonicStream stream,
123 float volume)
124{
125 stream->volume = volume;
126}
127
Bill Coxaf9a6242010-11-08 09:32:27 -0500128/* Get the sample rate of the stream. */
129int sonicGetSampleRate(
130 sonicStream stream)
131{
132 return stream->sampleRate;
133}
134
Bill Cox527b4e82010-11-24 17:42:58 -0500135/* Get the number of channels. */
136int sonicGetNumChannels(
137 sonicStream stream)
138{
139 return stream->numChannels;
140}
141
Bill Coxca02d872010-11-02 15:10:52 -0400142/* Destroy the sonic stream. */
143void sonicDestroyStream(
144 sonicStream stream)
145{
146 if(stream->inputBuffer != NULL) {
147 free(stream->inputBuffer);
148 }
149 if(stream->outputBuffer != NULL) {
150 free(stream->outputBuffer);
151 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500152 if(stream->pitchBuffer != NULL) {
153 free(stream->pitchBuffer);
154 }
155 if(stream->downSampleBuffer != NULL) {
156 free(stream->downSampleBuffer);
157 }
Bill Coxca02d872010-11-02 15:10:52 -0400158 free(stream);
159}
160
161/* Create a sonic stream. Return NULL only if we are out of memory and cannot
162 allocate the stream. */
163sonicStream sonicCreateStream(
Bill Cox1a299bb2010-11-19 15:07:17 -0500164 int sampleRate,
165 int numChannels)
Bill Coxca02d872010-11-02 15:10:52 -0400166{
167 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
168 int minPeriod = sampleRate/SONIC_MAX_PITCH;
169 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
170 int maxRequired = 2*maxPeriod;
171
172 if(stream == NULL) {
173 return NULL;
174 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500175 stream->inputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500176 stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400177 if(stream->inputBuffer == NULL) {
178 sonicDestroyStream(stream);
179 return NULL;
180 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500181 stream->outputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500182 stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400183 if(stream->outputBuffer == NULL) {
184 sonicDestroyStream(stream);
185 return NULL;
186 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500187 stream->pitchBufferSize = maxRequired;
188 stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
189 if(stream->pitchBuffer == NULL) {
190 sonicDestroyStream(stream);
191 return NULL;
192 }
193 stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
194 stream->speed = 1.0f;
195 stream->pitch = 1.0f;
196 stream->volume = 1.0f;
Bill Coxca02d872010-11-02 15:10:52 -0400197 stream->sampleRate = sampleRate;
Bill Cox1a299bb2010-11-19 15:07:17 -0500198 stream->numChannels = numChannels;
Bill Coxca02d872010-11-02 15:10:52 -0400199 stream->minPeriod = minPeriod;
200 stream->maxPeriod = maxPeriod;
201 stream->maxRequired = maxRequired;
Bill Coxca02d872010-11-02 15:10:52 -0400202 return stream;
203}
204
Bill Coxca02d872010-11-02 15:10:52 -0400205/* Enlarge the output buffer if needed. */
206static int enlargeOutputBufferIfNeeded(
207 sonicStream stream,
208 int numSamples)
209{
210 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
211 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500212 stream->outputBuffer = (short *)realloc(stream->outputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500213 stream->outputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400214 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400215 return 0;
216 }
217 }
218 return 1;
219}
220
Bill Coxca02d872010-11-02 15:10:52 -0400221/* Enlarge the input buffer if needed. */
222static int enlargeInputBufferIfNeeded(
223 sonicStream stream,
224 int numSamples)
225{
226 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
227 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500228 stream->inputBuffer = (short *)realloc(stream->inputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500229 stream->inputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400230 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400231 return 0;
232 }
233 }
234 return 1;
235}
236
237/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500238static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400239 sonicStream stream,
240 float *samples,
241 int numSamples)
242{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500243 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500244 int count = numSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500245
Bill Coxca02d872010-11-02 15:10:52 -0400246 if(numSamples == 0) {
247 return 1;
248 }
249 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
250 return 0;
251 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500252 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500253 while(count--) {
254 *buffer++ = (*samples++)*32767.0f;
255 }
Bill Cox14efa442010-11-02 15:43:58 -0400256 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400257 return 1;
258}
259
Bill Cox0c4c0602010-11-08 11:46:30 -0500260/* Add the input samples to the input buffer. */
261static int addShortSamplesToInputBuffer(
262 sonicStream stream,
263 short *samples,
264 int numSamples)
265{
Bill Cox0c4c0602010-11-08 11:46:30 -0500266 if(numSamples == 0) {
267 return 1;
268 }
269 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
270 return 0;
271 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500272 memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
273 numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500274 stream->numInputSamples += numSamples;
275 return 1;
276}
277
Bill Cox8a23d2f2010-11-16 18:49:36 -0500278/* Add the input samples to the input buffer. */
279static int addUnsignedCharSamplesToInputBuffer(
280 sonicStream stream,
281 unsigned char *samples,
282 int numSamples)
283{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500284 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500285 int count = numSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500286
287 if(numSamples == 0) {
288 return 1;
289 }
290 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
291 return 0;
292 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500293 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500294 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500295 *buffer++ = (*samples++ - 128) << 8;
Bill Coxca02d872010-11-02 15:10:52 -0400296 }
297 stream->numInputSamples += numSamples;
298 return 1;
299}
300
301/* Remove input samples that we have already processed. */
302static void removeInputSamples(
303 sonicStream stream,
304 int position)
305{
306 int remainingSamples = stream->numInputSamples - position;
307
308 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500309 memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
310 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400311 }
312 stream->numInputSamples = remainingSamples;
313}
314
Bill Cox59e65122010-11-03 10:06:29 -0400315/* Just copy from the array to the output buffer */
Bill Cox68e2aee2010-11-23 19:24:41 -0500316static int copyToOutput(
Bill Cox0c4c0602010-11-08 11:46:30 -0500317 sonicStream stream,
318 short *samples,
319 int numSamples)
320{
Bill Cox0c4c0602010-11-08 11:46:30 -0500321 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
322 return 0;
323 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500324 memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
325 samples, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500326 stream->numOutputSamples += numSamples;
327 return numSamples;
328}
329
Bill Cox882fb1d2010-11-02 16:27:20 -0400330/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
331 resize the output buffer. Otherwise, return numSamples */
332static int copyInputToOutput(
333 sonicStream stream,
334 int position)
335{
336 int numSamples = stream->remainingInputToCopy;
337
338 if(numSamples > stream->maxRequired) {
339 numSamples = stream->maxRequired;
340 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500341 if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels,
Bill Cox1a299bb2010-11-19 15:07:17 -0500342 numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400343 return 0;
344 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400345 stream->remainingInputToCopy -= numSamples;
346 return numSamples;
347}
348
Bill Coxca02d872010-11-02 15:10:52 -0400349/* Read data out of the stream. Sometimes no data will be available, and zero
350 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500351int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400352 sonicStream stream,
353 float *samples,
354 int maxSamples)
355{
356 int numSamples = stream->numOutputSamples;
357 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500358 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500359 int count;
Bill Coxca02d872010-11-02 15:10:52 -0400360
361 if(numSamples == 0) {
362 return 0;
363 }
364 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400365 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400366 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400367 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500368 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500369 count = numSamples*stream->numChannels;
370 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500371 *samples++ = (*buffer++)/32767.0f;
372 }
Bill Coxca02d872010-11-02 15:10:52 -0400373 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500374 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
375 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400376 }
377 stream->numOutputSamples = remainingSamples;
378 return numSamples;
379}
380
Bill Cox0c4c0602010-11-08 11:46:30 -0500381/* Read short data out of the stream. Sometimes no data will be available, and zero
382 is returned, which is not an error condition. */
383int sonicReadShortFromStream(
384 sonicStream stream,
385 short *samples,
386 int maxSamples)
387{
388 int numSamples = stream->numOutputSamples;
389 int remainingSamples = 0;
Bill Cox0c4c0602010-11-08 11:46:30 -0500390
391 if(numSamples == 0) {
392 return 0;
393 }
394 if(numSamples > maxSamples) {
395 remainingSamples = numSamples - maxSamples;
396 numSamples = maxSamples;
397 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500398 memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500399 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500400 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
401 remainingSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500402 }
403 stream->numOutputSamples = remainingSamples;
404 return numSamples;
405}
406
Bill Cox8a23d2f2010-11-16 18:49:36 -0500407/* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
408 is returned, which is not an error condition. */
409int sonicReadUnsignedCharFromStream(
410 sonicStream stream,
411 unsigned char *samples,
412 int maxSamples)
413{
414 int numSamples = stream->numOutputSamples;
415 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500416 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500417 int count;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500418
419 if(numSamples == 0) {
420 return 0;
421 }
422 if(numSamples > maxSamples) {
423 remainingSamples = numSamples - maxSamples;
424 numSamples = maxSamples;
425 }
426 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500427 count = numSamples*stream->numChannels;
428 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500429 *samples++ = (char)((*buffer++) >> 8) + 128;
Bill Coxca02d872010-11-02 15:10:52 -0400430 }
431 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500432 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
433 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400434 }
435 stream->numOutputSamples = remainingSamples;
436 return numSamples;
437}
438
439/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500440 has. No extra delay will be added to the output, but flushing in the middle of
441 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400442int sonicFlushStream(
443 sonicStream stream)
444{
445 int maxRequired = stream->maxRequired;
446 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500447 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400448
449 if(numSamples == 0) {
450 return 1;
451 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500452 if(numSamples >= maxRequired && !sonicWriteShortToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400453 return 0;
454 }
455 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500456 if(numSamples == 0) {
457 return 1;
458 }
Bill Coxca02d872010-11-02 15:10:52 -0400459 remainingSpace = maxRequired - numSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500460 memset(stream->inputBuffer + numSamples*stream->numChannels, 0,
461 remainingSpace*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400462 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500463 numOutputSamples = stream->numOutputSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500464 if(!sonicWriteShortToStream(stream, NULL, 0)) {
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500465 return 0;
466 }
467 /* Throw away any extra samples we generated due to the silence we added */
468 expectedSamples = (int)(numSamples*stream->speed + 0.5);
469 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
470 stream->numOutputSamples = numOutputSamples + expectedSamples;
471 }
472 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400473}
474
475/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400476int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400477 sonicStream stream)
478{
479 return stream->numOutputSamples;
480}
Bill Cox9bf11b52010-11-03 05:33:09 -0400481
Bill Coxd544fdb2010-11-23 14:13:46 -0500482/* If skip is greater than one, average skip samples togther and write them to
483 the down-sample buffer. If numChannels is greater than one, mix the channels
484 together as we down sample. */
485static void downSampleInput(
486 sonicStream stream,
487 short *samples,
488 int skip)
489{
490 int numSamples = stream->maxRequired/skip;
491 int samplesPerValue = stream->numChannels*skip;
492 int i, j;
493 int value;
494 short *downSamples = stream->downSampleBuffer;
495
496 for(i = 0; i < numSamples; i++) {
497 value = 0;
498 for(j = 0; j < samplesPerValue; j++) {
499 value += *samples++;
500 }
501 value /= samplesPerValue;
502 *downSamples++ = value;
503 }
504}
505
Bill Cox1a299bb2010-11-19 15:07:17 -0500506/* Find the best frequency match in the range, and given a sample skip multiple.
507 For now, just find the pitch of the first channel. */
Bill Cox0cd49c82010-11-03 10:46:22 -0400508static int findPitchPeriodInRange(
Bill Cox6a1bbb12010-11-19 11:14:28 -0500509 short *samples,
Bill Cox0cd49c82010-11-03 10:46:22 -0400510 int minPeriod,
Bill Coxd544fdb2010-11-23 14:13:46 -0500511 int maxPeriod)
Bill Cox0cd49c82010-11-03 10:46:22 -0400512{
513 int period, bestPeriod = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500514 short *s, *p, sVal, pVal;
515 unsigned long diff, minDiff = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500516 int i;
Bill Cox0cd49c82010-11-03 10:46:22 -0400517
Bill Coxd544fdb2010-11-23 14:13:46 -0500518 for(period = minPeriod; period <= maxPeriod; period++) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500519 diff = 0;
Bill Cox0cd49c82010-11-03 10:46:22 -0400520 s = samples;
Bill Coxd544fdb2010-11-23 14:13:46 -0500521 p = samples + period;
522 for(i = 0; i < period; i++) {
523 sVal = *s++;
524 pVal = *p++;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500525 diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
526 (unsigned short)(pVal - sVal);
Bill Cox0cd49c82010-11-03 10:46:22 -0400527 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500528 /* Note that the highest number of samples we add into diff will be less
529 than 256, since we skip samples. Thus, diff is a 24 bit number, and
530 we can safely multiply by numSamples without overflow */
Bill Coxd544fdb2010-11-23 14:13:46 -0500531 if(bestPeriod == 0 || diff*bestPeriod < minDiff*period) {
Bill Cox0cd49c82010-11-03 10:46:22 -0400532 minDiff = diff;
533 bestPeriod = period;
534 }
535 }
536 return bestPeriod;
537}
538
Bill Cox9bf11b52010-11-03 05:33:09 -0400539/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400540 multiple ways to get a good answer. This version uses AMDF. To improve
541 speed, we down sample by an integer factor get in the 11KHz range, and then
542 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400543static int findPitchPeriod(
544 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500545 short *samples)
Bill Cox9bf11b52010-11-03 05:33:09 -0400546{
547 int minPeriod = stream->minPeriod;
548 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400549 int sampleRate = stream->sampleRate;
550 int skip = 1;
551 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400552
Bill Cox0cd49c82010-11-03 10:46:22 -0400553 if(sampleRate > SONIC_AMDF_FREQ) {
554 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400555 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500556 if(stream->numChannels == 1 && skip == 1) {
557 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
558 }
559 downSampleInput(stream, samples, skip);
560 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip, maxPeriod/skip);
Bill Cox6a1bbb12010-11-19 11:14:28 -0500561 if(skip == 1) {
562 return period;
563 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500564 period *= skip;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500565 minPeriod = period - (skip << 2);
566 maxPeriod = period + (skip << 2);
Bill Cox0cd49c82010-11-03 10:46:22 -0400567 if(minPeriod < stream->minPeriod) {
568 minPeriod = stream->minPeriod;
569 }
570 if(maxPeriod > stream->maxPeriod) {
571 maxPeriod = stream->maxPeriod;
572 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500573 if(stream->numChannels == 1) {
574 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
575 }
576 downSampleInput(stream, samples, 1);
577 return findPitchPeriodInRange(stream->downSampleBuffer, minPeriod, maxPeriod);
578}
579
Bill Cox68e2aee2010-11-23 19:24:41 -0500580/* Overlap two sound segments, ramp the volume of one down, while ramping the
581 other one from zero up, and add them, storing the result at the output. */
582static void overlapAdd(
583 int numSamples,
584 int numChannels,
585 short *out,
586 short *rampDown,
587 short *rampUp)
588{
Bill Coxd76d2222010-11-24 11:42:29 -0500589 short *o, *u, *d;
Bill Cox68e2aee2010-11-23 19:24:41 -0500590 int i, t;
591
592 for(i = 0; i < numChannels; i++) {
593 o = out + i;
Bill Coxd76d2222010-11-24 11:42:29 -0500594 u = rampUp + i;
595 d = rampDown + i;
Bill Cox68e2aee2010-11-23 19:24:41 -0500596 for(t = 0; t < numSamples; t++) {
Bill Coxd76d2222010-11-24 11:42:29 -0500597 *o = (*d*(numSamples - t) + *u*t)/numSamples;
Bill Cox68e2aee2010-11-23 19:24:41 -0500598 o += numChannels;
Bill Coxd76d2222010-11-24 11:42:29 -0500599 d += numChannels;
600 u += numChannels;
601 }
602 }
603}
604
605/* Overlap two sound segments, ramp the volume of one down, while ramping the
606 other one from zero up, and add them, storing the result at the output. */
607static void overlapAddWithSeparation(
608 int numSamples,
609 int numChannels,
610 int separation,
611 short *out,
612 short *rampDown,
613 short *rampUp)
614{
615 short *o, *u, *d;
616 int i, t;
617
618 for(i = 0; i < numChannels; i++) {
619 o = out + i;
620 u = rampUp + i;
621 d = rampDown + i;
622 for(t = 0; t < numSamples + separation; t++) {
623 if(t < separation) {
624 *o = *d*(numSamples - t)/numSamples;
625 d += numChannels;
626 } else if(t < numSamples) {
627 *o = (*d*(numSamples - t) + *u*(t - separation))/numSamples;
628 d += numChannels;
629 u += numChannels;
630 } else {
631 *o = *u*(t - separation)/numSamples;
632 u += numChannels;
633 }
634 o += numChannels;
Bill Cox68e2aee2010-11-23 19:24:41 -0500635 }
636 }
637}
638
639/* Just move the new samples in the output buffer to the pitch bufer */
640static int moveNewSamplesToPitchBuffer(
Bill Coxd544fdb2010-11-23 14:13:46 -0500641 sonicStream stream,
642 int originalNumOutputSamples)
643{
Bill Cox68e2aee2010-11-23 19:24:41 -0500644 int numSamples = stream->numOutputSamples - originalNumOutputSamples;
645 int numChannels = stream->numChannels;
646
647 if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
648 stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples;
649 stream->pitchBuffer = (short *)realloc(stream->pitchBuffer,
650 stream->pitchBufferSize*sizeof(short)*numChannels);
651 if(stream->pitchBuffer == NULL) {
652 return 0;
653 }
654 }
655 memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels,
656 stream->outputBuffer + originalNumOutputSamples*numChannels,
657 numSamples*sizeof(short)*numChannels);
658 stream->numOutputSamples = originalNumOutputSamples;
659 stream->numPitchSamples += numSamples;
660 return 1;
661}
662
663/* Remove processed samples from the pitch buffer. */
664static void removePitchSamples(
665 sonicStream stream,
666 int numSamples)
667{
668 int numChannels = stream->numChannels;
669 short *source = stream->pitchBuffer + numSamples*numChannels;
670
671 if(numSamples == 0) {
672 return;
673 }
674 if(numSamples != stream->numPitchSamples) {
675 memmove(stream->pitchBuffer, source, (stream->numPitchSamples -
676 numSamples)*sizeof(short)*numChannels);
677 }
678 stream->numPitchSamples -= numSamples;
679}
680
681/* Change the pitch. The latency this introduces could be reduced by looking at
682 past samples to determine pitch, rather than future. */
683static int adjustPitch(
684 sonicStream stream,
685 int originalNumOutputSamples)
686{
687 float pitch = stream->pitch;
688 int numChannels = stream->numChannels;
Bill Coxd76d2222010-11-24 11:42:29 -0500689 int period, newPeriod, separation;
Bill Cox68e2aee2010-11-23 19:24:41 -0500690 int position = 0;
691 short *out, *rampDown, *rampUp;
692
693 if(stream->numOutputSamples == originalNumOutputSamples) {
694 return 1;
695 }
696 if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
697 return 0;
698 }
699 while(stream->numPitchSamples - position >= stream->maxRequired) {
700 period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels);
701 newPeriod = period/pitch;
Bill Coxd76d2222010-11-24 11:42:29 -0500702 if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) {
Bill Cox68e2aee2010-11-23 19:24:41 -0500703 return 0;
704 }
Bill Coxd76d2222010-11-24 11:42:29 -0500705 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
706 if(pitch >= 1.0f) {
707 rampDown = stream->pitchBuffer + position*numChannels;
708 rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels;
709 overlapAdd(newPeriod, numChannels, out, rampDown, rampUp);
710 } else {
711 rampDown = stream->pitchBuffer + position*numChannels;
712 rampUp = stream->pitchBuffer + position*numChannels;
713 separation = newPeriod - period;
714 overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp);
Bill Cox68e2aee2010-11-23 19:24:41 -0500715 }
Bill Coxd76d2222010-11-24 11:42:29 -0500716 stream->numOutputSamples += newPeriod;
Bill Cox68e2aee2010-11-23 19:24:41 -0500717 position += period;
718 }
719 removePitchSamples(stream, position);
720 return 1;
Bill Cox9bf11b52010-11-03 05:33:09 -0400721}
722
Bill Cox59e65122010-11-03 10:06:29 -0400723/* Skip over a pitch period, and copy period/speed samples to the output */
724static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400725 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500726 short *samples,
727 float speed,
Bill Cox9bf11b52010-11-03 05:33:09 -0400728 int period)
729{
Bill Cox68e2aee2010-11-23 19:24:41 -0500730 long newSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500731 int numChannels = stream->numChannels;
Bill Cox9bf11b52010-11-03 05:33:09 -0400732
Bill Cox6a1bbb12010-11-19 11:14:28 -0500733 if(speed >= 2.0f) {
734 newSamples = period/(speed - 1.0f);
735 } else if(speed > 1.0f) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400736 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500737 stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
Bill Cox9bf11b52010-11-03 05:33:09 -0400738 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400739 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
740 return 0;
741 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500742 overlapAdd(newSamples, numChannels, stream->outputBuffer +
743 stream->numOutputSamples*numChannels, samples, samples + period*numChannels);
Bill Cox9bf11b52010-11-03 05:33:09 -0400744 stream->numOutputSamples += newSamples;
745 return newSamples;
746}
747
Bill Cox59e65122010-11-03 10:06:29 -0400748/* Insert a pitch period, and determine how much input to copy directly. */
749static int insertPitchPeriod(
750 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500751 short *samples,
752 float speed,
Bill Cox59e65122010-11-03 10:06:29 -0400753 int period)
754{
Bill Cox68e2aee2010-11-23 19:24:41 -0500755 long newSamples;
756 short *out;
Bill Cox1a299bb2010-11-19 15:07:17 -0500757 int numChannels = stream->numChannels;
Bill Cox59e65122010-11-03 10:06:29 -0400758
Bill Cox6a1bbb12010-11-19 11:14:28 -0500759 if(speed < 0.5f) {
760 newSamples = period*speed/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400761 } else {
762 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500763 stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400764 }
765 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
766 return 0;
767 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500768 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
769 memcpy(out, samples, period*sizeof(short)*numChannels);
Bill Cox68e2aee2010-11-23 19:24:41 -0500770 out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels;
771 overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400772 stream->numOutputSamples += period + newSamples;
773 return newSamples;
774}
775
Bill Cox9bf11b52010-11-03 05:33:09 -0400776/* Resample as many pitch periods as we have buffered on the input. Return 0 if
Bill Coxd544fdb2010-11-23 14:13:46 -0500777 we fail to resize an input or output buffer. Also scale the output by the volume. */
778static int changeSpeed(
779 sonicStream stream,
780 float speed)
Bill Cox9bf11b52010-11-03 05:33:09 -0400781{
Bill Cox1a299bb2010-11-19 15:07:17 -0500782 short *samples;
Bill Cox0c4c0602010-11-08 11:46:30 -0500783 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400784 int position = 0, period, newSamples;
785 int maxRequired = stream->maxRequired;
786
Bill Cox9bf11b52010-11-03 05:33:09 -0400787 if(stream->numInputSamples < maxRequired) {
788 return 1;
789 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400790 do {
791 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400792 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400793 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400794 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500795 samples = stream->inputBuffer + position*stream->numChannels;
796 period = findPitchPeriod(stream, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400797 if(speed > 1.0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500798 newSamples = skipPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400799 position += period + newSamples;
800 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500801 newSamples = insertPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400802 position += newSamples;
803 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400804 }
805 if(newSamples == 0) {
806 return 0; /* Failed to resize output buffer */
807 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400808 } while(position + maxRequired <= numSamples);
809 removeInputSamples(stream, position);
810 return 1;
811}
Bill Cox0c4c0602010-11-08 11:46:30 -0500812
Bill Coxd544fdb2010-11-23 14:13:46 -0500813/* Resample as many pitch periods as we have buffered on the input. Return 0 if
814 we fail to resize an input or output buffer. Also scale the output by the volume. */
815static int processStreamInput(
816 sonicStream stream)
817{
818 int originalNumOutputSamples = stream->numOutputSamples;
819 float speed = stream->speed/stream->pitch;
820
821 if(speed > 1.00001 || speed < 0.99999) {
822 changeSpeed(stream, speed);
823 } else {
Bill Cox68e2aee2010-11-23 19:24:41 -0500824 if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
Bill Coxd544fdb2010-11-23 14:13:46 -0500825 return 0;
826 }
827 stream->numInputSamples = 0;
828 }
829 if(stream->pitch != 1.0f) {
Bill Cox68e2aee2010-11-23 19:24:41 -0500830 if(!adjustPitch(stream, originalNumOutputSamples)) {
831 return 0;
832 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500833 }
834 if(stream->volume != 1.0f) {
835 /* Adjust output volume. */
836 scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
837 (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
838 stream->volume);
839 }
840 return 1;
841}
842
Bill Cox0c4c0602010-11-08 11:46:30 -0500843/* Write floating point data to the input buffer and process it. */
844int sonicWriteFloatToStream(
845 sonicStream stream,
846 float *samples,
847 int numSamples)
848{
Bill Cox0c4c0602010-11-08 11:46:30 -0500849 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
850 return 0;
851 }
852 return processStreamInput(stream);
853}
854
855/* Simple wrapper around sonicWriteFloatToStream that does the short to float
856 conversion for you. */
857int sonicWriteShortToStream(
858 sonicStream stream,
859 short *samples,
860 int numSamples)
861{
Bill Cox0c4c0602010-11-08 11:46:30 -0500862 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
863 return 0;
864 }
865 return processStreamInput(stream);
866}
867
Bill Cox8a23d2f2010-11-16 18:49:36 -0500868/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
869 conversion for you. */
870int sonicWriteUnsignedCharToStream(
871 sonicStream stream,
872 unsigned char *samples,
873 int numSamples)
874{
Bill Cox8a23d2f2010-11-16 18:49:36 -0500875 if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
876 return 0;
877 }
878 return processStreamInput(stream);
879}
880
Bill Cox036d7322010-11-09 09:29:24 -0500881/* This is a non-stream oriented interface to just change the speed of a sound sample */
882int sonicChangeFloatSpeed(
883 float *samples,
884 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500885 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500886 float pitch,
887 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500888 int sampleRate,
889 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500890{
Bill Coxd544fdb2010-11-23 14:13:46 -0500891 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500892
Bill Coxd544fdb2010-11-23 14:13:46 -0500893 sonicSetSpeed(stream, speed);
894 sonicSetPitch(stream, pitch);
895 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500896 sonicWriteFloatToStream(stream, samples, numSamples);
897 sonicFlushStream(stream);
898 numSamples = sonicSamplesAvailable(stream);
899 sonicReadFloatFromStream(stream, samples, numSamples);
900 sonicDestroyStream(stream);
901 return numSamples;
902}
903
904/* This is a non-stream oriented interface to just change the speed of a sound sample */
905int sonicChangeShortSpeed(
906 short *samples,
907 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500908 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500909 float pitch,
910 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500911 int sampleRate,
912 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500913{
Bill Coxd544fdb2010-11-23 14:13:46 -0500914 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500915
Bill Coxd544fdb2010-11-23 14:13:46 -0500916 sonicSetSpeed(stream, speed);
917 sonicSetPitch(stream, pitch);
918 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500919 sonicWriteShortToStream(stream, samples, numSamples);
920 sonicFlushStream(stream);
921 numSamples = sonicSamplesAvailable(stream);
922 sonicReadShortFromStream(stream, samples, numSamples);
923 sonicDestroyStream(stream);
924 return numSamples;
925}