blob: a5a554843334acd56527868f205613d60ffe3da3 [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{
Bill Cox0a651042010-11-24 08:01:06 -0500110 if(pitch < 0.66666f) {
111 fprintf(stderr, "Pitch change below 2/3 is not supported: using 2/3.\n");
112 pitch = 0.66666f;
113 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500114 stream->pitch = pitch;
115}
116
117/* Get the scaling factor of the stream. */
118float sonicGetVolume(
119 sonicStream stream)
120{
121 return stream->volume;
122}
123
124/* Set the scaling factor of the stream. */
125void sonicSetVolume(
126 sonicStream stream,
127 float volume)
128{
129 stream->volume = volume;
130}
131
Bill Coxaf9a6242010-11-08 09:32:27 -0500132/* Get the sample rate of the stream. */
133int sonicGetSampleRate(
134 sonicStream stream)
135{
136 return stream->sampleRate;
137}
138
Bill Coxca02d872010-11-02 15:10:52 -0400139/* Destroy the sonic stream. */
140void sonicDestroyStream(
141 sonicStream stream)
142{
143 if(stream->inputBuffer != NULL) {
144 free(stream->inputBuffer);
145 }
146 if(stream->outputBuffer != NULL) {
147 free(stream->outputBuffer);
148 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500149 if(stream->pitchBuffer != NULL) {
150 free(stream->pitchBuffer);
151 }
152 if(stream->downSampleBuffer != NULL) {
153 free(stream->downSampleBuffer);
154 }
Bill Coxca02d872010-11-02 15:10:52 -0400155 free(stream);
156}
157
158/* Create a sonic stream. Return NULL only if we are out of memory and cannot
159 allocate the stream. */
160sonicStream sonicCreateStream(
Bill Cox1a299bb2010-11-19 15:07:17 -0500161 int sampleRate,
162 int numChannels)
Bill Coxca02d872010-11-02 15:10:52 -0400163{
164 sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
165 int minPeriod = sampleRate/SONIC_MAX_PITCH;
166 int maxPeriod = sampleRate/SONIC_MIN_PITCH;
167 int maxRequired = 2*maxPeriod;
168
169 if(stream == NULL) {
170 return NULL;
171 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500172 stream->inputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500173 stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400174 if(stream->inputBuffer == NULL) {
175 sonicDestroyStream(stream);
176 return NULL;
177 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500178 stream->outputBufferSize = maxRequired;
Bill Cox1a299bb2010-11-19 15:07:17 -0500179 stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400180 if(stream->outputBuffer == NULL) {
181 sonicDestroyStream(stream);
182 return NULL;
183 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500184 stream->pitchBufferSize = maxRequired;
185 stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
186 if(stream->pitchBuffer == NULL) {
187 sonicDestroyStream(stream);
188 return NULL;
189 }
190 stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
191 stream->speed = 1.0f;
192 stream->pitch = 1.0f;
193 stream->volume = 1.0f;
Bill Coxca02d872010-11-02 15:10:52 -0400194 stream->sampleRate = sampleRate;
Bill Cox1a299bb2010-11-19 15:07:17 -0500195 stream->numChannels = numChannels;
Bill Coxca02d872010-11-02 15:10:52 -0400196 stream->minPeriod = minPeriod;
197 stream->maxPeriod = maxPeriod;
198 stream->maxRequired = maxRequired;
Bill Coxca02d872010-11-02 15:10:52 -0400199 return stream;
200}
201
Bill Coxca02d872010-11-02 15:10:52 -0400202/* Enlarge the output buffer if needed. */
203static int enlargeOutputBufferIfNeeded(
204 sonicStream stream,
205 int numSamples)
206{
207 if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
208 stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500209 stream->outputBuffer = (short *)realloc(stream->outputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500210 stream->outputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400211 if(stream->outputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400212 return 0;
213 }
214 }
215 return 1;
216}
217
Bill Coxca02d872010-11-02 15:10:52 -0400218/* Enlarge the input buffer if needed. */
219static int enlargeInputBufferIfNeeded(
220 sonicStream stream,
221 int numSamples)
222{
223 if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
224 stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500225 stream->inputBuffer = (short *)realloc(stream->inputBuffer,
Bill Cox1a299bb2010-11-19 15:07:17 -0500226 stream->inputBufferSize*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400227 if(stream->inputBuffer == NULL) {
Bill Coxca02d872010-11-02 15:10:52 -0400228 return 0;
229 }
230 }
231 return 1;
232}
233
234/* Add the input samples to the input buffer. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500235static int addFloatSamplesToInputBuffer(
Bill Coxca02d872010-11-02 15:10:52 -0400236 sonicStream stream,
237 float *samples,
238 int numSamples)
239{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500240 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500241 int count = numSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500242
Bill Coxca02d872010-11-02 15:10:52 -0400243 if(numSamples == 0) {
244 return 1;
245 }
246 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
247 return 0;
248 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500249 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500250 while(count--) {
251 *buffer++ = (*samples++)*32767.0f;
252 }
Bill Cox14efa442010-11-02 15:43:58 -0400253 stream->numInputSamples += numSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400254 return 1;
255}
256
Bill Cox0c4c0602010-11-08 11:46:30 -0500257/* Add the input samples to the input buffer. */
258static int addShortSamplesToInputBuffer(
259 sonicStream stream,
260 short *samples,
261 int numSamples)
262{
Bill Cox0c4c0602010-11-08 11:46:30 -0500263 if(numSamples == 0) {
264 return 1;
265 }
266 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
267 return 0;
268 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500269 memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
270 numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500271 stream->numInputSamples += numSamples;
272 return 1;
273}
274
Bill Cox8a23d2f2010-11-16 18:49:36 -0500275/* Add the input samples to the input buffer. */
276static int addUnsignedCharSamplesToInputBuffer(
277 sonicStream stream,
278 unsigned char *samples,
279 int numSamples)
280{
Bill Cox6a1bbb12010-11-19 11:14:28 -0500281 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500282 int count = numSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500283
284 if(numSamples == 0) {
285 return 1;
286 }
287 if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
288 return 0;
289 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500290 buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500291 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500292 *buffer++ = (*samples++ - 128) << 8;
Bill Coxca02d872010-11-02 15:10:52 -0400293 }
294 stream->numInputSamples += numSamples;
295 return 1;
296}
297
298/* Remove input samples that we have already processed. */
299static void removeInputSamples(
300 sonicStream stream,
301 int position)
302{
303 int remainingSamples = stream->numInputSamples - position;
304
305 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500306 memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
307 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400308 }
309 stream->numInputSamples = remainingSamples;
310}
311
Bill Cox59e65122010-11-03 10:06:29 -0400312/* Just copy from the array to the output buffer */
Bill Cox68e2aee2010-11-23 19:24:41 -0500313static int copyToOutput(
Bill Cox0c4c0602010-11-08 11:46:30 -0500314 sonicStream stream,
315 short *samples,
316 int numSamples)
317{
Bill Cox0c4c0602010-11-08 11:46:30 -0500318 if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
319 return 0;
320 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500321 memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
322 samples, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500323 stream->numOutputSamples += numSamples;
324 return numSamples;
325}
326
Bill Cox882fb1d2010-11-02 16:27:20 -0400327/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
328 resize the output buffer. Otherwise, return numSamples */
329static int copyInputToOutput(
330 sonicStream stream,
331 int position)
332{
333 int numSamples = stream->remainingInputToCopy;
334
335 if(numSamples > stream->maxRequired) {
336 numSamples = stream->maxRequired;
337 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500338 if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels,
Bill Cox1a299bb2010-11-19 15:07:17 -0500339 numSamples)) {
Bill Cox882fb1d2010-11-02 16:27:20 -0400340 return 0;
341 }
Bill Cox882fb1d2010-11-02 16:27:20 -0400342 stream->remainingInputToCopy -= numSamples;
343 return numSamples;
344}
345
Bill Coxca02d872010-11-02 15:10:52 -0400346/* Read data out of the stream. Sometimes no data will be available, and zero
347 is returned, which is not an error condition. */
Bill Cox0c4c0602010-11-08 11:46:30 -0500348int sonicReadFloatFromStream(
Bill Coxca02d872010-11-02 15:10:52 -0400349 sonicStream stream,
350 float *samples,
351 int maxSamples)
352{
353 int numSamples = stream->numOutputSamples;
354 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500355 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500356 int count;
Bill Coxca02d872010-11-02 15:10:52 -0400357
358 if(numSamples == 0) {
359 return 0;
360 }
361 if(numSamples > maxSamples) {
Bill Coxca02d872010-11-02 15:10:52 -0400362 remainingSamples = numSamples - maxSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400363 numSamples = maxSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400364 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500365 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500366 count = numSamples*stream->numChannels;
367 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500368 *samples++ = (*buffer++)/32767.0f;
369 }
Bill Coxca02d872010-11-02 15:10:52 -0400370 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500371 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
372 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400373 }
374 stream->numOutputSamples = remainingSamples;
375 return numSamples;
376}
377
Bill Cox0c4c0602010-11-08 11:46:30 -0500378/* Read short data out of the stream. Sometimes no data will be available, and zero
379 is returned, which is not an error condition. */
380int sonicReadShortFromStream(
381 sonicStream stream,
382 short *samples,
383 int maxSamples)
384{
385 int numSamples = stream->numOutputSamples;
386 int remainingSamples = 0;
Bill Cox0c4c0602010-11-08 11:46:30 -0500387
388 if(numSamples == 0) {
389 return 0;
390 }
391 if(numSamples > maxSamples) {
392 remainingSamples = numSamples - maxSamples;
393 numSamples = maxSamples;
394 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500395 memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500396 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500397 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
398 remainingSamples*sizeof(short)*stream->numChannels);
Bill Cox0c4c0602010-11-08 11:46:30 -0500399 }
400 stream->numOutputSamples = remainingSamples;
401 return numSamples;
402}
403
Bill Cox8a23d2f2010-11-16 18:49:36 -0500404/* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
405 is returned, which is not an error condition. */
406int sonicReadUnsignedCharFromStream(
407 sonicStream stream,
408 unsigned char *samples,
409 int maxSamples)
410{
411 int numSamples = stream->numOutputSamples;
412 int remainingSamples = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500413 short *buffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500414 int count;
Bill Cox8a23d2f2010-11-16 18:49:36 -0500415
416 if(numSamples == 0) {
417 return 0;
418 }
419 if(numSamples > maxSamples) {
420 remainingSamples = numSamples - maxSamples;
421 numSamples = maxSamples;
422 }
423 buffer = stream->outputBuffer;
Bill Cox1a299bb2010-11-19 15:07:17 -0500424 count = numSamples*stream->numChannels;
425 while(count--) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500426 *samples++ = (char)((*buffer++) >> 8) + 128;
Bill Coxca02d872010-11-02 15:10:52 -0400427 }
428 if(remainingSamples > 0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500429 memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
430 remainingSamples*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400431 }
432 stream->numOutputSamples = remainingSamples;
433 return numSamples;
434}
435
436/* Force the sonic stream to generate output using whatever data it currently
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500437 has. No extra delay will be added to the output, but flushing in the middle of
438 words could introduce distortion. */
Bill Coxca02d872010-11-02 15:10:52 -0400439int sonicFlushStream(
440 sonicStream stream)
441{
442 int maxRequired = stream->maxRequired;
443 int numSamples = stream->numInputSamples;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500444 int remainingSpace, numOutputSamples, expectedSamples;
Bill Coxca02d872010-11-02 15:10:52 -0400445
446 if(numSamples == 0) {
447 return 1;
448 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500449 if(numSamples >= maxRequired && !sonicWriteShortToStream(stream, NULL, 0)) {
Bill Coxca02d872010-11-02 15:10:52 -0400450 return 0;
451 }
452 numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500453 if(numSamples == 0) {
454 return 1;
455 }
Bill Coxca02d872010-11-02 15:10:52 -0400456 remainingSpace = maxRequired - numSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500457 memset(stream->inputBuffer + numSamples*stream->numChannels, 0,
458 remainingSpace*sizeof(short)*stream->numChannels);
Bill Coxca02d872010-11-02 15:10:52 -0400459 stream->numInputSamples = maxRequired;
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500460 numOutputSamples = stream->numOutputSamples;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500461 if(!sonicWriteShortToStream(stream, NULL, 0)) {
Bill Cox4bbbbcc2010-11-09 05:32:38 -0500462 return 0;
463 }
464 /* Throw away any extra samples we generated due to the silence we added */
465 expectedSamples = (int)(numSamples*stream->speed + 0.5);
466 if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
467 stream->numOutputSamples = numOutputSamples + expectedSamples;
468 }
469 return 1;
Bill Coxca02d872010-11-02 15:10:52 -0400470}
471
472/* Return the number of samples in the output buffer */
Bill Cox3a7abf92010-11-06 15:18:49 -0400473int sonicSamplesAvailable(
Bill Coxca02d872010-11-02 15:10:52 -0400474 sonicStream stream)
475{
476 return stream->numOutputSamples;
477}
Bill Cox9bf11b52010-11-03 05:33:09 -0400478
Bill Coxd544fdb2010-11-23 14:13:46 -0500479/* If skip is greater than one, average skip samples togther and write them to
480 the down-sample buffer. If numChannels is greater than one, mix the channels
481 together as we down sample. */
482static void downSampleInput(
483 sonicStream stream,
484 short *samples,
485 int skip)
486{
487 int numSamples = stream->maxRequired/skip;
488 int samplesPerValue = stream->numChannels*skip;
489 int i, j;
490 int value;
491 short *downSamples = stream->downSampleBuffer;
492
493 for(i = 0; i < numSamples; i++) {
494 value = 0;
495 for(j = 0; j < samplesPerValue; j++) {
496 value += *samples++;
497 }
498 value /= samplesPerValue;
499 *downSamples++ = value;
500 }
501}
502
Bill Cox1a299bb2010-11-19 15:07:17 -0500503/* Find the best frequency match in the range, and given a sample skip multiple.
504 For now, just find the pitch of the first channel. */
Bill Cox0cd49c82010-11-03 10:46:22 -0400505static int findPitchPeriodInRange(
Bill Cox6a1bbb12010-11-19 11:14:28 -0500506 short *samples,
Bill Cox0cd49c82010-11-03 10:46:22 -0400507 int minPeriod,
Bill Coxd544fdb2010-11-23 14:13:46 -0500508 int maxPeriod)
Bill Cox0cd49c82010-11-03 10:46:22 -0400509{
510 int period, bestPeriod = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500511 short *s, *p, sVal, pVal;
512 unsigned long diff, minDiff = 0;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500513 int i;
Bill Cox0cd49c82010-11-03 10:46:22 -0400514
Bill Coxd544fdb2010-11-23 14:13:46 -0500515 for(period = minPeriod; period <= maxPeriod; period++) {
Bill Cox6a1bbb12010-11-19 11:14:28 -0500516 diff = 0;
Bill Cox0cd49c82010-11-03 10:46:22 -0400517 s = samples;
Bill Coxd544fdb2010-11-23 14:13:46 -0500518 p = samples + period;
519 for(i = 0; i < period; i++) {
520 sVal = *s++;
521 pVal = *p++;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500522 diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
523 (unsigned short)(pVal - sVal);
Bill Cox0cd49c82010-11-03 10:46:22 -0400524 }
Bill Cox6a1bbb12010-11-19 11:14:28 -0500525 /* Note that the highest number of samples we add into diff will be less
526 than 256, since we skip samples. Thus, diff is a 24 bit number, and
527 we can safely multiply by numSamples without overflow */
Bill Coxd544fdb2010-11-23 14:13:46 -0500528 if(bestPeriod == 0 || diff*bestPeriod < minDiff*period) {
Bill Cox0cd49c82010-11-03 10:46:22 -0400529 minDiff = diff;
530 bestPeriod = period;
531 }
532 }
533 return bestPeriod;
534}
535
Bill Cox9bf11b52010-11-03 05:33:09 -0400536/* Find the pitch period. This is a critical step, and we may have to try
Bill Cox0cd49c82010-11-03 10:46:22 -0400537 multiple ways to get a good answer. This version uses AMDF. To improve
538 speed, we down sample by an integer factor get in the 11KHz range, and then
539 do it again with a narrower frequency range without down sampling */
Bill Cox9bf11b52010-11-03 05:33:09 -0400540static int findPitchPeriod(
541 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500542 short *samples)
Bill Cox9bf11b52010-11-03 05:33:09 -0400543{
544 int minPeriod = stream->minPeriod;
545 int maxPeriod = stream->maxPeriod;
Bill Cox0cd49c82010-11-03 10:46:22 -0400546 int sampleRate = stream->sampleRate;
547 int skip = 1;
548 int period;
Bill Cox9bf11b52010-11-03 05:33:09 -0400549
Bill Cox0cd49c82010-11-03 10:46:22 -0400550 if(sampleRate > SONIC_AMDF_FREQ) {
551 skip = sampleRate/SONIC_AMDF_FREQ;
Bill Cox9bf11b52010-11-03 05:33:09 -0400552 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500553 if(stream->numChannels == 1 && skip == 1) {
554 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
555 }
556 downSampleInput(stream, samples, skip);
557 period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip, maxPeriod/skip);
Bill Cox6a1bbb12010-11-19 11:14:28 -0500558 if(skip == 1) {
559 return period;
560 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500561 period *= skip;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500562 minPeriod = period - (skip << 2);
563 maxPeriod = period + (skip << 2);
Bill Cox0cd49c82010-11-03 10:46:22 -0400564 if(minPeriod < stream->minPeriod) {
565 minPeriod = stream->minPeriod;
566 }
567 if(maxPeriod > stream->maxPeriod) {
568 maxPeriod = stream->maxPeriod;
569 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500570 if(stream->numChannels == 1) {
571 return findPitchPeriodInRange(samples, minPeriod, maxPeriod);
572 }
573 downSampleInput(stream, samples, 1);
574 return findPitchPeriodInRange(stream->downSampleBuffer, minPeriod, maxPeriod);
575}
576
Bill Cox68e2aee2010-11-23 19:24:41 -0500577/* Overlap two sound segments, ramp the volume of one down, while ramping the
578 other one from zero up, and add them, storing the result at the output. */
579static void overlapAdd(
580 int numSamples,
581 int numChannels,
582 short *out,
583 short *rampDown,
584 short *rampUp)
585{
586 short *o;
587 int i, t;
588
589 for(i = 0; i < numChannels; i++) {
590 o = out + i;
591 for(t = 0; t < numSamples; t++) {
592 *o = (*rampDown*(numSamples - t) + *rampUp*t)/numSamples;
593 o += numChannels;
594 rampDown += numChannels;
595 rampUp += numChannels;
596 }
597 }
598}
599
600/* Just move the new samples in the output buffer to the pitch bufer */
601static int moveNewSamplesToPitchBuffer(
Bill Coxd544fdb2010-11-23 14:13:46 -0500602 sonicStream stream,
603 int originalNumOutputSamples)
604{
Bill Cox68e2aee2010-11-23 19:24:41 -0500605 int numSamples = stream->numOutputSamples - originalNumOutputSamples;
606 int numChannels = stream->numChannels;
607
608 if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
609 stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples;
610 stream->pitchBuffer = (short *)realloc(stream->pitchBuffer,
611 stream->pitchBufferSize*sizeof(short)*numChannels);
612 if(stream->pitchBuffer == NULL) {
613 return 0;
614 }
615 }
616 memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels,
617 stream->outputBuffer + originalNumOutputSamples*numChannels,
618 numSamples*sizeof(short)*numChannels);
619 stream->numOutputSamples = originalNumOutputSamples;
620 stream->numPitchSamples += numSamples;
621 return 1;
622}
623
624/* Remove processed samples from the pitch buffer. */
625static void removePitchSamples(
626 sonicStream stream,
627 int numSamples)
628{
629 int numChannels = stream->numChannels;
630 short *source = stream->pitchBuffer + numSamples*numChannels;
631
632 if(numSamples == 0) {
633 return;
634 }
635 if(numSamples != stream->numPitchSamples) {
636 memmove(stream->pitchBuffer, source, (stream->numPitchSamples -
637 numSamples)*sizeof(short)*numChannels);
638 }
639 stream->numPitchSamples -= numSamples;
640}
641
642/* Change the pitch. The latency this introduces could be reduced by looking at
643 past samples to determine pitch, rather than future. */
644static int adjustPitch(
645 sonicStream stream,
646 int originalNumOutputSamples)
647{
648 float pitch = stream->pitch;
649 int numChannels = stream->numChannels;
650 int period, newPeriod, overlappedSamples;
651 int position = 0;
652 short *out, *rampDown, *rampUp;
653
654 if(stream->numOutputSamples == originalNumOutputSamples) {
655 return 1;
656 }
657 if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
658 return 0;
659 }
660 while(stream->numPitchSamples - position >= stream->maxRequired) {
661 period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels);
662 newPeriod = period/pitch;
663 if(pitch >= 1.0f) {
664 overlappedSamples = newPeriod;
665 rampDown = stream->pitchBuffer + position*numChannels;
666 rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels;
667 } else {
668 overlappedSamples = (period << 1) - newPeriod;
669 if(!copyToOutput(stream, stream->pitchBuffer + position*numChannels,
670 period - overlappedSamples)) {
671 return 0;
672 }
673 rampDown = stream->pitchBuffer +
674 (position + (period - overlappedSamples))*numChannels;
675 rampUp = stream->pitchBuffer + position*numChannels;
676 }
677 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
678 if(!enlargeOutputBufferIfNeeded(stream, overlappedSamples)) {
679 return 0;
680 }
681 overlapAdd(overlappedSamples, numChannels, out, rampDown, rampUp);
682 stream->numOutputSamples += overlappedSamples;
683 if(pitch < 1.0f) {
684 if(!copyToOutput(stream, stream->pitchBuffer +
685 position + overlappedSamples*numChannels, period - overlappedSamples)) {
686 return 0;
687 }
688 }
689 position += period;
690 }
691 removePitchSamples(stream, position);
692 return 1;
Bill Cox9bf11b52010-11-03 05:33:09 -0400693}
694
Bill Cox59e65122010-11-03 10:06:29 -0400695/* Skip over a pitch period, and copy period/speed samples to the output */
696static int skipPitchPeriod(
Bill Cox9bf11b52010-11-03 05:33:09 -0400697 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500698 short *samples,
699 float speed,
Bill Cox9bf11b52010-11-03 05:33:09 -0400700 int period)
701{
Bill Cox68e2aee2010-11-23 19:24:41 -0500702 long newSamples;
Bill Cox1a299bb2010-11-19 15:07:17 -0500703 int numChannels = stream->numChannels;
Bill Cox9bf11b52010-11-03 05:33:09 -0400704
Bill Cox6a1bbb12010-11-19 11:14:28 -0500705 if(speed >= 2.0f) {
706 newSamples = period/(speed - 1.0f);
707 } else if(speed > 1.0f) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400708 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500709 stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
Bill Cox9bf11b52010-11-03 05:33:09 -0400710 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400711 if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
712 return 0;
713 }
Bill Cox68e2aee2010-11-23 19:24:41 -0500714 overlapAdd(newSamples, numChannels, stream->outputBuffer +
715 stream->numOutputSamples*numChannels, samples, samples + period*numChannels);
Bill Cox9bf11b52010-11-03 05:33:09 -0400716 stream->numOutputSamples += newSamples;
717 return newSamples;
718}
719
Bill Cox59e65122010-11-03 10:06:29 -0400720/* Insert a pitch period, and determine how much input to copy directly. */
721static int insertPitchPeriod(
722 sonicStream stream,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500723 short *samples,
724 float speed,
Bill Cox59e65122010-11-03 10:06:29 -0400725 int period)
726{
Bill Cox68e2aee2010-11-23 19:24:41 -0500727 long newSamples;
728 short *out;
Bill Cox1a299bb2010-11-19 15:07:17 -0500729 int numChannels = stream->numChannels;
Bill Cox59e65122010-11-03 10:06:29 -0400730
Bill Cox6a1bbb12010-11-19 11:14:28 -0500731 if(speed < 0.5f) {
732 newSamples = period*speed/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400733 } else {
734 newSamples = period;
Bill Cox6a1bbb12010-11-19 11:14:28 -0500735 stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
Bill Cox59e65122010-11-03 10:06:29 -0400736 }
737 if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
738 return 0;
739 }
Bill Cox1a299bb2010-11-19 15:07:17 -0500740 out = stream->outputBuffer + stream->numOutputSamples*numChannels;
741 memcpy(out, samples, period*sizeof(short)*numChannels);
Bill Cox68e2aee2010-11-23 19:24:41 -0500742 out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels;
743 overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400744 stream->numOutputSamples += period + newSamples;
745 return newSamples;
746}
747
Bill Cox9bf11b52010-11-03 05:33:09 -0400748/* Resample as many pitch periods as we have buffered on the input. Return 0 if
Bill Coxd544fdb2010-11-23 14:13:46 -0500749 we fail to resize an input or output buffer. Also scale the output by the volume. */
750static int changeSpeed(
751 sonicStream stream,
752 float speed)
Bill Cox9bf11b52010-11-03 05:33:09 -0400753{
Bill Cox1a299bb2010-11-19 15:07:17 -0500754 short *samples;
Bill Cox0c4c0602010-11-08 11:46:30 -0500755 int numSamples = stream->numInputSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400756 int position = 0, period, newSamples;
757 int maxRequired = stream->maxRequired;
758
Bill Cox9bf11b52010-11-03 05:33:09 -0400759 if(stream->numInputSamples < maxRequired) {
760 return 1;
761 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400762 do {
763 if(stream->remainingInputToCopy > 0) {
Bill Cox9bf11b52010-11-03 05:33:09 -0400764 newSamples = copyInputToOutput(stream, position);
Bill Cox59e65122010-11-03 10:06:29 -0400765 position += newSamples;
Bill Cox9bf11b52010-11-03 05:33:09 -0400766 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500767 samples = stream->inputBuffer + position*stream->numChannels;
768 period = findPitchPeriod(stream, samples);
Bill Cox59e65122010-11-03 10:06:29 -0400769 if(speed > 1.0) {
Bill Cox1a299bb2010-11-19 15:07:17 -0500770 newSamples = skipPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400771 position += period + newSamples;
772 } else {
Bill Cox1a299bb2010-11-19 15:07:17 -0500773 newSamples = insertPitchPeriod(stream, samples, speed, period);
Bill Cox59e65122010-11-03 10:06:29 -0400774 position += newSamples;
775 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400776 }
777 if(newSamples == 0) {
778 return 0; /* Failed to resize output buffer */
779 }
Bill Cox9bf11b52010-11-03 05:33:09 -0400780 } while(position + maxRequired <= numSamples);
781 removeInputSamples(stream, position);
782 return 1;
783}
Bill Cox0c4c0602010-11-08 11:46:30 -0500784
Bill Coxd544fdb2010-11-23 14:13:46 -0500785/* Resample as many pitch periods as we have buffered on the input. Return 0 if
786 we fail to resize an input or output buffer. Also scale the output by the volume. */
787static int processStreamInput(
788 sonicStream stream)
789{
790 int originalNumOutputSamples = stream->numOutputSamples;
791 float speed = stream->speed/stream->pitch;
792
793 if(speed > 1.00001 || speed < 0.99999) {
794 changeSpeed(stream, speed);
795 } else {
Bill Cox68e2aee2010-11-23 19:24:41 -0500796 if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
Bill Coxd544fdb2010-11-23 14:13:46 -0500797 return 0;
798 }
799 stream->numInputSamples = 0;
800 }
801 if(stream->pitch != 1.0f) {
Bill Cox68e2aee2010-11-23 19:24:41 -0500802 if(!adjustPitch(stream, originalNumOutputSamples)) {
803 return 0;
804 }
Bill Coxd544fdb2010-11-23 14:13:46 -0500805 }
806 if(stream->volume != 1.0f) {
807 /* Adjust output volume. */
808 scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
809 (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
810 stream->volume);
811 }
812 return 1;
813}
814
Bill Cox0c4c0602010-11-08 11:46:30 -0500815/* Write floating point data to the input buffer and process it. */
816int sonicWriteFloatToStream(
817 sonicStream stream,
818 float *samples,
819 int numSamples)
820{
Bill Cox0c4c0602010-11-08 11:46:30 -0500821 if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
822 return 0;
823 }
824 return processStreamInput(stream);
825}
826
827/* Simple wrapper around sonicWriteFloatToStream that does the short to float
828 conversion for you. */
829int sonicWriteShortToStream(
830 sonicStream stream,
831 short *samples,
832 int numSamples)
833{
Bill Cox0c4c0602010-11-08 11:46:30 -0500834 if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
835 return 0;
836 }
837 return processStreamInput(stream);
838}
839
Bill Cox8a23d2f2010-11-16 18:49:36 -0500840/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
841 conversion for you. */
842int sonicWriteUnsignedCharToStream(
843 sonicStream stream,
844 unsigned char *samples,
845 int numSamples)
846{
Bill Cox8a23d2f2010-11-16 18:49:36 -0500847 if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
848 return 0;
849 }
850 return processStreamInput(stream);
851}
852
Bill Cox036d7322010-11-09 09:29:24 -0500853/* This is a non-stream oriented interface to just change the speed of a sound sample */
854int sonicChangeFloatSpeed(
855 float *samples,
856 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500857 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500858 float pitch,
859 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500860 int sampleRate,
861 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500862{
Bill Coxd544fdb2010-11-23 14:13:46 -0500863 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500864
Bill Coxd544fdb2010-11-23 14:13:46 -0500865 sonicSetSpeed(stream, speed);
866 sonicSetPitch(stream, pitch);
867 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500868 sonicWriteFloatToStream(stream, samples, numSamples);
869 sonicFlushStream(stream);
870 numSamples = sonicSamplesAvailable(stream);
871 sonicReadFloatFromStream(stream, samples, numSamples);
872 sonicDestroyStream(stream);
873 return numSamples;
874}
875
876/* This is a non-stream oriented interface to just change the speed of a sound sample */
877int sonicChangeShortSpeed(
878 short *samples,
879 int numSamples,
Bill Cox6a1bbb12010-11-19 11:14:28 -0500880 float speed,
Bill Coxd544fdb2010-11-23 14:13:46 -0500881 float pitch,
882 float volume,
Bill Cox1a299bb2010-11-19 15:07:17 -0500883 int sampleRate,
884 int numChannels)
Bill Cox036d7322010-11-09 09:29:24 -0500885{
Bill Coxd544fdb2010-11-23 14:13:46 -0500886 sonicStream stream = sonicCreateStream(sampleRate, numChannels);
Bill Cox036d7322010-11-09 09:29:24 -0500887
Bill Coxd544fdb2010-11-23 14:13:46 -0500888 sonicSetSpeed(stream, speed);
889 sonicSetPitch(stream, pitch);
890 sonicSetVolume(stream, volume);
Bill Cox036d7322010-11-09 09:29:24 -0500891 sonicWriteShortToStream(stream, samples, numSamples);
892 sonicFlushStream(stream);
893 numSamples = sonicSamplesAvailable(stream);
894 sonicReadShortFromStream(stream, samples, numSamples);
895 sonicDestroyStream(stream);
896 return numSamples;
897}