blob: 1fa2c25a5fecfede23aca895738520ea69038195 [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 Coxca02d872010-11-02 15:10:52 -0400135/* Destroy the sonic stream. */
136void sonicDestroyStream(
137 sonicStream stream)
138{
139 if(stream->inputBuffer != NULL) {
140 free(stream->inputBuffer);
141 }
142 if(stream->outputBuffer != NULL) {
143 free(stream->outputBuffer);
144 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500145 if(stream->pitchBuffer != NULL) {
146 free(stream->pitchBuffer);
147 }
148 if(stream->downSampleBuffer != NULL) {
149 free(stream->downSampleBuffer);
150 }
Bill Coxca02d872010-11-02 15:10:52 -0400151 free(stream);
152}
153
154/* Create a sonic stream. Return NULL only if we are out of memory and cannot
155 allocate the stream. */
156sonicStream sonicCreateStream(
Bill Cox1a299bb2010-11-19 15:07:17 -0500157 int sampleRate,
158 int numChannels)
Bill Coxca02d872010-11-02 15:10:52 -0400159{
160 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
161 int minPeriod = sampleRate/SONIC_MAX_PITCH;
162 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
163 int maxRequired = 2*maxPeriod;
164
165 if(stream == NULL) {
166 return NULL;
167 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500168 stream->inputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500169 stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400170 if(stream->inputBuffer == NULL) {
171 sonicDestroyStream(stream);
172 return NULL;
173 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500174 stream->outputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500175 stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400176 if(stream->outputBuffer == NULL) {
177 sonicDestroyStream(stream);
178 return NULL;
179 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500180 stream->pitchBufferSize = maxRequired;
181 stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
182 if(stream->pitchBuffer == NULL) {
183 sonicDestroyStream(stream);
184 return NULL;
185 }
186 stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
187 stream->speed = 1.0f;
188 stream->pitch = 1.0f;
189 stream->volume = 1.0f;
Bill Coxca02d872010-11-02 15:10:52 -0400190 stream->sampleRate = sampleRate;
Bill Cox1a299bb2010-11-19 15:07:17 -0500191 stream->numChannels = numChannels;
Bill Coxca02d872010-11-02 15:10:52 -0400192 stream->minPeriod = minPeriod;
193 stream->maxPeriod = maxPeriod;
194 stream->maxRequired = maxRequired;
Bill Coxca02d872010-11-02 15:10:52 -0400195 return stream;
196}
197
Bill Coxca02d872010-11-02 15:10:52 -0400198/* Enlarge the output buffer if needed. */
199static int enlargeOutputBufferIfNeeded(
200 sonicStream stream,
201 int numSamples)
202{
203 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
204 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500205 stream->outputBuffer = (short *)realloc(stream->outputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500206 stream->outputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400207 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400208 return 0;
209 }
210 }
211 return 1;
212}
213
Bill Coxca02d872010-11-02 15:10:52 -0400214/* Enlarge the input buffer if needed. */
215static int enlargeInputBufferIfNeeded(
216 sonicStream stream,
217 int numSamples)
218{
219 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
220 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500221 stream->inputBuffer = (short *)realloc(stream->inputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500222 stream->inputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400223 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400224 return 0;
225 }
226 }
227 return 1;
228}
229
230/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500231static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400232 sonicStream stream,
233 float *samples,
234 int numSamples)
235{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500236 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500237 int count = numSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500238
Bill Coxca02d872010-11-02 15:10:52 -0400239 if(numSamples == 0) {
240 return 1;
241 }
242 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
243 return 0;
244 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500245 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500246 while(count--) {
247 *buffer++ = (*samples++)*32767.0f;
248 }
Bill Cox14efa442010-11-02 15:43:58 -0400249 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400250 return 1;
251}
252
Bill Cox0c4c0602010-11-08 11:46:30 -0500253/* Add the input samples to the input buffer. */
254static int addShortSamplesToInputBuffer(
255 sonicStream stream,
256 short *samples,
257 int numSamples)
258{
Bill Cox0c4c0602010-11-08 11:46:30 -0500259 if(numSamples == 0) {
260 return 1;
261 }
262 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
263 return 0;
264 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500265 memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
266 numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500267 stream->numInputSamples += numSamples;
268 return 1;
269}
270
Bill Cox8a23d2f2010-11-16 18:49:36 -0500271/* Add the input samples to the input buffer. */
272static int addUnsignedCharSamplesToInputBuffer(
273 sonicStream stream,
274 unsigned char *samples,
275 int numSamples)
276{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500277 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500278 int count = numSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500279
280 if(numSamples == 0) {
281 return 1;
282 }
283 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
284 return 0;
285 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500286 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500287 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500288 *buffer++ = (*samples++ - 128) << 8;
Bill Coxca02d872010-11-02 15:10:52 -0400289 }
290 stream->numInputSamples += numSamples;
291 return 1;
292}
293
294/* Remove input samples that we have already processed. */
295static void removeInputSamples(
296 sonicStream stream,
297 int position)
298{
299 int remainingSamples = stream->numInputSamples - position;
300
301 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500302 memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
303 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400304 }
305 stream->numInputSamples = remainingSamples;
306}
307
Bill Cox59e65122010-11-03 10:06:29 -0400308/* Just copy from the array to the output buffer */
Bill Cox0c4c0602010-11-08 11:46:30 -0500309static int copyShortToOutput(
310 sonicStream stream,
311 short *samples,
312 int numSamples)
313{
Bill Cox0c4c0602010-11-08 11:46:30 -0500314 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
315 return 0;
316 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500317 memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
318 samples, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500319 stream->numOutputSamples += numSamples;
320 return numSamples;
321}
322
Bill Cox882fb1d2010-11-02 16:27:20 -0400323/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
324 resize the output buffer. Otherwise, return numSamples */
325static int copyInputToOutput(
326 sonicStream stream,
327 int position)
328{
329 int numSamples = stream->remainingInputToCopy;
330
331 if(numSamples > stream->maxRequired) {
332 numSamples = stream->maxRequired;
333 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500334 if(!copyShortToOutput(stream, stream->inputBuffer + position*stream->numChannels,
335 numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400336 return 0;
337 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400338 stream->remainingInputToCopy -= numSamples;
339 return numSamples;
340}
341
Bill Coxca02d872010-11-02 15:10:52 -0400342/* Read data out of the stream. Sometimes no data will be available, and zero
343 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500344int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400345 sonicStream stream,
346 float *samples,
347 int maxSamples)
348{
349 int numSamples = stream->numOutputSamples;
350 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500351 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500352 int count;
Bill Coxca02d872010-11-02 15:10:52 -0400353
354 if(numSamples == 0) {
355 return 0;
356 }
357 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400358 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400359 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400360 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500361 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500362 count = numSamples*stream->numChannels;
363 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500364 *samples++ = (*buffer++)/32767.0f;
365 }
Bill Coxca02d872010-11-02 15:10:52 -0400366 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500367 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
368 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400369 }
370 stream->numOutputSamples = remainingSamples;
371 return numSamples;
372}
373
Bill Cox0c4c0602010-11-08 11:46:30 -0500374/* Read short data out of the stream. Sometimes no data will be available, and zero
375 is returned, which is not an error condition. */
376int sonicReadShortFromStream(
377 sonicStream stream,
378 short *samples,
379 int maxSamples)
380{
381 int numSamples = stream->numOutputSamples;
382 int remainingSamples = 0;
Bill Cox0c4c0602010-11-08 11:46:30 -0500383
384 if(numSamples == 0) {
385 return 0;
386 }
387 if(numSamples > maxSamples) {
388 remainingSamples = numSamples - maxSamples;
389 numSamples = maxSamples;
390 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500391 memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500392 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500393 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
394 remainingSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500395 }
396 stream->numOutputSamples = remainingSamples;
397 return numSamples;
398}
399
Bill Cox8a23d2f2010-11-16 18:49:36 -0500400/* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
401 is returned, which is not an error condition. */
402int sonicReadUnsignedCharFromStream(
403 sonicStream stream,
404 unsigned char *samples,
405 int maxSamples)
406{
407 int numSamples = stream->numOutputSamples;
408 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500409 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500410 int count;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500411
412 if(numSamples == 0) {
413 return 0;
414 }
415 if(numSamples > maxSamples) {
416 remainingSamples = numSamples - maxSamples;
417 numSamples = maxSamples;
418 }
419 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500420 count = numSamples*stream->numChannels;
421 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500422 *samples++ = (char)((*buffer++) >> 8) + 128;
Bill Coxca02d872010-11-02 15:10:52 -0400423 }
424 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500425 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
426 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400427 }
428 stream->numOutputSamples = remainingSamples;
429 return numSamples;
430}
431
432/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500433 has. No extra delay will be added to the output, but flushing in the middle of
434 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400435int sonicFlushStream(
436 sonicStream stream)
437{
438 int maxRequired = stream->maxRequired;
439 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500440 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400441
442 if(numSamples == 0) {
443 return 1;
444 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500445 if(numSamples >= maxRequired && !sonicWriteShortToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400446 return 0;
447 }
448 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500449 if(numSamples == 0) {
450 return 1;
451 }
Bill Coxca02d872010-11-02 15:10:52 -0400452 remainingSpace = maxRequired - numSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500453 memset(stream->inputBuffer + numSamples*stream->numChannels, 0,
454 remainingSpace*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400455 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500456 numOutputSamples = stream->numOutputSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500457 if(!sonicWriteShortToStream(stream, NULL, 0)) {
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500458 return 0;
459 }
460 /* Throw away any extra samples we generated due to the silence we added */
461 expectedSamples = (int)(numSamples*stream->speed + 0.5);
462 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
463 stream->numOutputSamples = numOutputSamples + expectedSamples;
464 }
465 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400466}
467
468/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400469int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400470 sonicStream stream)
471{
472 return stream->numOutputSamples;
473}
Bill Cox9bf11b52010-11-03 05:33:09 -0400474
Bill Coxd544fdb2010-11-23 14:13:46 -0500475/* If skip is greater than one, average skip samples togther and write them to
476 the down-sample buffer. If numChannels is greater than one, mix the channels
477 together as we down sample. */
478static void downSampleInput(
479 sonicStream stream,
480 short *samples,
481 int skip)
482{
483 int numSamples = stream->maxRequired/skip;
484 int samplesPerValue = stream->numChannels*skip;
485 int i, j;
486 int value;
487 short *downSamples = stream->downSampleBuffer;
488
489 for(i = 0; i < numSamples; i++) {
490 value = 0;
491 for(j = 0; j < samplesPerValue; j++) {
492 value += *samples++;
493 }
494 value /= samplesPerValue;
495 *downSamples++ = value;
496 }
497}
498
Bill Cox1a299bb2010-11-19 15:07:17 -0500499/* Find the best frequency match in the range, and given a sample skip multiple.
500 For now, just find the pitch of the first channel. */
Bill Cox0cd49c82010-11-03 10:46:22 -0400501static int findPitchPeriodInRange(
Bill Cox6a1bbb12010-11-19 11:14:28 -0500502 short *samples,
Bill Cox0cd49c82010-11-03 10:46:22 -0400503 int minPeriod,
Bill Coxd544fdb2010-11-23 14:13:46 -0500504 int maxPeriod)
Bill Cox0cd49c82010-11-03 10:46:22 -0400505{
506 int period, bestPeriod = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500507 short *s, *p, sVal, pVal;
508 unsigned long diff, minDiff = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500509 int i;
Bill Cox0cd49c82010-11-03 10:46:22 -0400510
Bill Coxd544fdb2010-11-23 14:13:46 -0500511 for(period = minPeriod; period <= maxPeriod; period++) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500512 diff = 0;
Bill Cox0cd49c82010-11-03 10:46:22 -0400513 s = samples;
Bill Coxd544fdb2010-11-23 14:13:46 -0500514 p = samples + period;
515 for(i = 0; i < period; i++) {
516 sVal = *s++;
517 pVal = *p++;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500518 diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
519 (unsigned short)(pVal - sVal);
Bill Cox0cd49c82010-11-03 10:46:22 -0400520 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500521 /* Note that the highest number of samples we add into diff will be less
522 than 256, since we skip samples. Thus, diff is a 24 bit number, and
523 we can safely multiply by numSamples without overflow */
Bill Coxd544fdb2010-11-23 14:13:46 -0500524 if(bestPeriod == 0 || diff*bestPeriod < minDiff*period) {
Bill Cox0cd49c82010-11-03 10:46:22 -0400525 minDiff = diff;
526 bestPeriod = period;
527 }
528 }
529 return bestPeriod;
530}
531
Bill Cox9bf11b52010-11-03 05:33:09 -0400532/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400533 multiple ways to get a good answer. This version uses AMDF. To improve
534 speed, we down sample by an integer factor get in the 11KHz range, and then
535 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400536static int findPitchPeriod(
537 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500538 short *samples)
Bill Cox9bf11b52010-11-03 05:33:09 -0400539{
540 int minPeriod = stream->minPeriod;
541 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400542 int sampleRate = stream->sampleRate;
543 int skip = 1;
544 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400545
Bill Cox0cd49c82010-11-03 10:46:22 -0400546 if(sampleRate > SONIC_AMDF_FREQ) {
547 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400548 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500549 if(stream->numChannels == 1 && skip == 1) {
550 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
551 }
552 downSampleInput(stream, samples, skip);
553 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip, maxPeriod/skip);
Bill Cox6a1bbb12010-11-19 11:14:28 -0500554 if(skip == 1) {
555 return period;
556 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500557 period *= skip;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500558 minPeriod = period - (skip << 2);
559 maxPeriod = period + (skip << 2);
Bill Cox0cd49c82010-11-03 10:46:22 -0400560 if(minPeriod < stream->minPeriod) {
561 minPeriod = stream->minPeriod;
562 }
563 if(maxPeriod > stream->maxPeriod) {
564 maxPeriod = stream->maxPeriod;
565 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500566 if(stream->numChannels == 1) {
567 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
568 }
569 downSampleInput(stream, samples, 1);
570 return findPitchPeriodInRange(stream->downSampleBuffer, minPeriod, maxPeriod);
571}
572
573/* Change the pitch. Increasing pitch just requires an overlap and add between
574 pitch periods to reduce their length. Decreasing pitch is done by inserting
575 a sound segment made with overlap-add. Return the new perid. */
576static void adjustPitch(
577 sonicStream stream,
578 int originalNumOutputSamples)
579{
580 /* TODO: write this */
Bill Cox9bf11b52010-11-03 05:33:09 -0400581}
582
Bill Cox59e65122010-11-03 10:06:29 -0400583/* Skip over a pitch period, and copy period/speed samples to the output */
584static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400585 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500586 short *samples,
587 float speed,
Bill Cox9bf11b52010-11-03 05:33:09 -0400588 int period)
589{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500590 long t, newSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500591 short *out, *prevPeriodSamples, *nextPeriodSamples;
592 int numChannels = stream->numChannels;
593 int i;
Bill Cox9bf11b52010-11-03 05:33:09 -0400594
Bill Cox6a1bbb12010-11-19 11:14:28 -0500595 if(speed >= 2.0f) {
596 newSamples = period/(speed - 1.0f);
597 } else if(speed > 1.0f) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400598 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500599 stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
Bill Cox9bf11b52010-11-03 05:33:09 -0400600 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400601 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
602 return 0;
603 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500604 for(i = 0; i < numChannels; i++) {
605 out = stream->outputBuffer + stream->numOutputSamples*numChannels + i;
606 prevPeriodSamples = samples + i;
607 nextPeriodSamples = samples + i + period*numChannels;
608 for(t = 0; t < newSamples; t++) {
609 *out = (*prevPeriodSamples*(newSamples - t) + *nextPeriodSamples*t)/newSamples;
610 out += numChannels;
611 prevPeriodSamples += numChannels;
612 nextPeriodSamples += numChannels;
613 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400614 }
615 stream->numOutputSamples += newSamples;
616 return newSamples;
617}
618
Bill Cox59e65122010-11-03 10:06:29 -0400619/* Insert a pitch period, and determine how much input to copy directly. */
620static int insertPitchPeriod(
621 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500622 short *samples,
623 float speed,
Bill Cox59e65122010-11-03 10:06:29 -0400624 int period)
625{
Bill Cox1dcc64a2010-11-21 04:50:24 -0500626 long t, newSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500627 short *out, *prevPeriodSamples, *nextPeriodSamples;
628 int numChannels = stream->numChannels;
629 int i;
Bill Cox59e65122010-11-03 10:06:29 -0400630
Bill Cox6a1bbb12010-11-19 11:14:28 -0500631 if(speed < 0.5f) {
632 newSamples = period*speed/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400633 } else {
634 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500635 stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400636 }
637 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
638 return 0;
639 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500640 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
641 memcpy(out, samples, period*sizeof(short)*numChannels);
642 for(i = 0; i < numChannels; i++) {
643 out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels + i;
644 prevPeriodSamples = samples + i;
645 nextPeriodSamples = samples + i + period*numChannels;
646 for(t = 0; t < newSamples; t++) {
647 *out = (*prevPeriodSamples*t + *nextPeriodSamples*(newSamples - t))/newSamples;
648 out += numChannels;
649 prevPeriodSamples += numChannels;
650 nextPeriodSamples += numChannels;
651 }
Bill Cox59e65122010-11-03 10:06:29 -0400652 }
653 stream->numOutputSamples += period + newSamples;
654 return newSamples;
655}
656
Bill Cox9bf11b52010-11-03 05:33:09 -0400657/* Resample as many pitch periods as we have buffered on the input. Return 0 if
Bill Coxd544fdb2010-11-23 14:13:46 -0500658 we fail to resize an input or output buffer. Also scale the output by the volume. */
659static int changeSpeed(
660 sonicStream stream,
661 float speed)
Bill Cox9bf11b52010-11-03 05:33:09 -0400662{
Bill Cox1a299bb2010-11-19 15:07:17 -0500663 short *samples;
Bill Cox0c4c0602010-11-08 11:46:30 -0500664 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400665 int position = 0, period, newSamples;
666 int maxRequired = stream->maxRequired;
667
Bill Cox9bf11b52010-11-03 05:33:09 -0400668 if(stream->numInputSamples < maxRequired) {
669 return 1;
670 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400671 do {
672 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400673 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400674 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400675 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500676 samples = stream->inputBuffer + position*stream->numChannels;
677 period = findPitchPeriod(stream, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400678 if(speed > 1.0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500679 newSamples = skipPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400680 position += period + newSamples;
681 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500682 newSamples = insertPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400683 position += newSamples;
684 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400685 }
686 if(newSamples == 0) {
687 return 0; /* Failed to resize output buffer */
688 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400689 } while(position + maxRequired <= numSamples);
690 removeInputSamples(stream, position);
691 return 1;
692}
Bill Cox0c4c0602010-11-08 11:46:30 -0500693
Bill Coxd544fdb2010-11-23 14:13:46 -0500694/* Resample as many pitch periods as we have buffered on the input. Return 0 if
695 we fail to resize an input or output buffer. Also scale the output by the volume. */
696static int processStreamInput(
697 sonicStream stream)
698{
699 int originalNumOutputSamples = stream->numOutputSamples;
700 float speed = stream->speed/stream->pitch;
701
702 if(speed > 1.00001 || speed < 0.99999) {
703 changeSpeed(stream, speed);
704 } else {
705 if(!copyShortToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
706 return 0;
707 }
708 stream->numInputSamples = 0;
709 }
710 if(stream->pitch != 1.0f) {
711 adjustPitch(stream, originalNumOutputSamples);
712 }
713 if(stream->volume != 1.0f) {
714 /* Adjust output volume. */
715 scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
716 (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
717 stream->volume);
718 }
719 return 1;
720}
721
Bill Cox0c4c0602010-11-08 11:46:30 -0500722/* Write floating point data to the input buffer and process it. */
723int sonicWriteFloatToStream(
724 sonicStream stream,
725 float *samples,
726 int numSamples)
727{
Bill Cox0c4c0602010-11-08 11:46:30 -0500728 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
729 return 0;
730 }
731 return processStreamInput(stream);
732}
733
734/* Simple wrapper around sonicWriteFloatToStream that does the short to float
735 conversion for you. */
736int sonicWriteShortToStream(
737 sonicStream stream,
738 short *samples,
739 int numSamples)
740{
Bill Cox0c4c0602010-11-08 11:46:30 -0500741 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
742 return 0;
743 }
744 return processStreamInput(stream);
745}
746
Bill Cox8a23d2f2010-11-16 18:49:36 -0500747/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
748 conversion for you. */
749int sonicWriteUnsignedCharToStream(
750 sonicStream stream,
751 unsigned char *samples,
752 int numSamples)
753{
Bill Cox8a23d2f2010-11-16 18:49:36 -0500754 if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
755 return 0;
756 }
757 return processStreamInput(stream);
758}
759
Bill Cox036d7322010-11-09 09:29:24 -0500760/* This is a non-stream oriented interface to just change the speed of a sound sample */
761int sonicChangeFloatSpeed(
762 float *samples,
763 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500764 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500765 float pitch,
766 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500767 int sampleRate,
768 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500769{
Bill Coxd544fdb2010-11-23 14:13:46 -0500770 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500771
Bill Coxd544fdb2010-11-23 14:13:46 -0500772 sonicSetSpeed(stream, speed);
773 sonicSetPitch(stream, pitch);
774 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500775 sonicWriteFloatToStream(stream, samples, numSamples);
776 sonicFlushStream(stream);
777 numSamples = sonicSamplesAvailable(stream);
778 sonicReadFloatFromStream(stream, samples, numSamples);
779 sonicDestroyStream(stream);
780 return numSamples;
781}
782
783/* This is a non-stream oriented interface to just change the speed of a sound sample */
784int sonicChangeShortSpeed(
785 short *samples,
786 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500787 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500788 float pitch,
789 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500790 int sampleRate,
791 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500792{
Bill Coxd544fdb2010-11-23 14:13:46 -0500793 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500794
Bill Coxd544fdb2010-11-23 14:13:46 -0500795 sonicSetSpeed(stream, speed);
796 sonicSetPitch(stream, pitch);
797 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500798 sonicWriteShortToStream(stream, samples, numSamples);
799 sonicFlushStream(stream);
800 numSamples = sonicSamplesAvailable(stream);
801 sonicReadShortFromStream(stream, samples, numSamples);
802 sonicDestroyStream(stream);
803 return numSamples;
804}