4
This file is part of the Sonic Library.
6
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.
11
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.
16
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
29
struct sonicStreamStruct {
33
short *downSampleBuffer;
47
int remainingInputToCopy;
54
/* Just used for debugging */
55
void sonicMSG(char *format, ...)
62
vsprintf((char *)buffer, (char *)format, ap);
64
file=fopen("/tmp/sonic.log", "a");
65
fprintf(file, "%s", buffer);
69
/* Scale the samples by the factor. */
70
static void scaleSamples(
75
int fixedPointVolume = volume*4096.0f;
79
value = (*samples*fixedPointVolume) >> 12;
82
} else if(value < -32767) {
89
/* Get the speed of the stream. */
96
/* Set the speed of the stream. */
101
stream->speed = speed;
104
/* Get the pitch of the stream. */
108
return stream->pitch;
111
/* Set the pitch of the stream. */
116
stream->pitch = pitch;
119
/* Get the scaling factor of the stream. */
120
float sonicGetVolume(
123
return stream->volume;
126
/* Set the scaling factor of the stream. */
131
stream->volume = volume;
134
/* Get the sample rate of the stream. */
135
int sonicGetSampleRate(
138
return stream->sampleRate;
141
/* Get the number of channels. */
142
int sonicGetNumChannels(
145
return stream->numChannels;
148
/* Destroy the sonic stream. */
149
void sonicDestroyStream(
152
if(stream->inputBuffer != NULL) {
153
free(stream->inputBuffer);
155
if(stream->outputBuffer != NULL) {
156
free(stream->outputBuffer);
158
if(stream->pitchBuffer != NULL) {
159
free(stream->pitchBuffer);
161
if(stream->downSampleBuffer != NULL) {
162
free(stream->downSampleBuffer);
167
/* Create a sonic stream. Return NULL only if we are out of memory and cannot
168
allocate the stream. */
169
sonicStream sonicCreateStream(
173
sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct));
174
int minPeriod = sampleRate/SONIC_MAX_PITCH;
175
int maxPeriod = sampleRate/SONIC_MIN_PITCH;
176
int maxRequired = 2*maxPeriod;
181
stream->inputBufferSize = maxRequired;
182
stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
183
if(stream->inputBuffer == NULL) {
184
sonicDestroyStream(stream);
187
stream->outputBufferSize = maxRequired;
188
stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
189
if(stream->outputBuffer == NULL) {
190
sonicDestroyStream(stream);
193
stream->pitchBufferSize = maxRequired;
194
stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels);
195
if(stream->pitchBuffer == NULL) {
196
sonicDestroyStream(stream);
199
stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short));
200
stream->speed = 1.0f;
201
stream->pitch = 1.0f;
202
stream->volume = 1.0f;
203
stream->sampleRate = sampleRate;
204
stream->numChannels = numChannels;
205
stream->minPeriod = minPeriod;
206
stream->maxPeriod = maxPeriod;
207
stream->maxRequired = maxRequired;
211
/* Enlarge the output buffer if needed. */
212
static int enlargeOutputBufferIfNeeded(
216
if(stream->numOutputSamples + numSamples > stream->outputBufferSize) {
217
stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples;
218
stream->outputBuffer = (short *)realloc(stream->outputBuffer,
219
stream->outputBufferSize*sizeof(short)*stream->numChannels);
220
if(stream->outputBuffer == NULL) {
227
/* Enlarge the input buffer if needed. */
228
static int enlargeInputBufferIfNeeded(
232
if(stream->numInputSamples + numSamples > stream->inputBufferSize) {
233
stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples;
234
stream->inputBuffer = (short *)realloc(stream->inputBuffer,
235
stream->inputBufferSize*sizeof(short)*stream->numChannels);
236
if(stream->inputBuffer == NULL) {
243
/* Add the input samples to the input buffer. */
244
static int addFloatSamplesToInputBuffer(
250
int count = numSamples*stream->numChannels;
252
if(numSamples == 0) {
255
if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
258
buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
260
*buffer++ = (*samples++)*32767.0f;
262
stream->numInputSamples += numSamples;
266
/* Add the input samples to the input buffer. */
267
static int addShortSamplesToInputBuffer(
272
if(numSamples == 0) {
275
if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
278
memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples,
279
numSamples*sizeof(short)*stream->numChannels);
280
stream->numInputSamples += numSamples;
284
/* Add the input samples to the input buffer. */
285
static int addUnsignedCharSamplesToInputBuffer(
287
unsigned char *samples,
291
int count = numSamples*stream->numChannels;
293
if(numSamples == 0) {
296
if(!enlargeInputBufferIfNeeded(stream, numSamples)) {
299
buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels;
301
*buffer++ = (*samples++ - 128) << 8;
303
stream->numInputSamples += numSamples;
307
/* Remove input samples that we have already processed. */
308
static void removeInputSamples(
312
int remainingSamples = stream->numInputSamples - position;
314
if(remainingSamples > 0) {
315
memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels,
316
remainingSamples*sizeof(short)*stream->numChannels);
318
stream->numInputSamples = remainingSamples;
321
/* Just copy from the array to the output buffer */
322
static int copyToOutput(
327
if(!enlargeOutputBufferIfNeeded(stream, numSamples)) {
330
memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels,
331
samples, numSamples*sizeof(short)*stream->numChannels);
332
stream->numOutputSamples += numSamples;
336
/* Just copy from the input buffer to the output buffer. Return 0 if we fail to
337
resize the output buffer. Otherwise, return numSamples */
338
static int copyInputToOutput(
342
int numSamples = stream->remainingInputToCopy;
344
if(numSamples > stream->maxRequired) {
345
numSamples = stream->maxRequired;
347
if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels,
351
stream->remainingInputToCopy -= numSamples;
355
/* Read data out of the stream. Sometimes no data will be available, and zero
356
is returned, which is not an error condition. */
357
int sonicReadFloatFromStream(
362
int numSamples = stream->numOutputSamples;
363
int remainingSamples = 0;
367
if(numSamples == 0) {
370
if(numSamples > maxSamples) {
371
remainingSamples = numSamples - maxSamples;
372
numSamples = maxSamples;
374
buffer = stream->outputBuffer;
375
count = numSamples*stream->numChannels;
377
*samples++ = (*buffer++)/32767.0f;
379
if(remainingSamples > 0) {
380
memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
381
remainingSamples*sizeof(short)*stream->numChannels);
383
stream->numOutputSamples = remainingSamples;
387
/* Read short data out of the stream. Sometimes no data will be available, and zero
388
is returned, which is not an error condition. */
389
int sonicReadShortFromStream(
394
int numSamples = stream->numOutputSamples;
395
int remainingSamples = 0;
397
if(numSamples == 0) {
400
if(numSamples > maxSamples) {
401
remainingSamples = numSamples - maxSamples;
402
numSamples = maxSamples;
404
memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels);
405
if(remainingSamples > 0) {
406
memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
407
remainingSamples*sizeof(short)*stream->numChannels);
409
stream->numOutputSamples = remainingSamples;
413
/* Read unsigned char data out of the stream. Sometimes no data will be available, and zero
414
is returned, which is not an error condition. */
415
int sonicReadUnsignedCharFromStream(
417
unsigned char *samples,
420
int numSamples = stream->numOutputSamples;
421
int remainingSamples = 0;
425
if(numSamples == 0) {
428
if(numSamples > maxSamples) {
429
remainingSamples = numSamples - maxSamples;
430
numSamples = maxSamples;
432
buffer = stream->outputBuffer;
433
count = numSamples*stream->numChannels;
435
*samples++ = (char)((*buffer++) >> 8) + 128;
437
if(remainingSamples > 0) {
438
memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels,
439
remainingSamples*sizeof(short)*stream->numChannels);
441
stream->numOutputSamples = remainingSamples;
445
/* Force the sonic stream to generate output using whatever data it currently
446
has. No extra delay will be added to the output, but flushing in the middle of
447
words could introduce distortion. */
448
int sonicFlushStream(
451
int maxRequired = stream->maxRequired;
452
int numSamples = stream->numInputSamples;
453
int remainingSpace, numOutputSamples, expectedSamples;
455
if(numSamples == 0) {
458
if(numSamples >= maxRequired && !sonicWriteShortToStream(stream, NULL, 0)) {
461
numSamples = stream->numInputSamples; /* Now numSamples < maxRequired */
462
if(numSamples == 0) {
465
remainingSpace = maxRequired - numSamples;
466
memset(stream->inputBuffer + numSamples*stream->numChannels, 0,
467
remainingSpace*sizeof(short)*stream->numChannels);
468
stream->numInputSamples = maxRequired;
469
numOutputSamples = stream->numOutputSamples;
470
if(!sonicWriteShortToStream(stream, NULL, 0)) {
473
/* Throw away any extra samples we generated due to the silence we added */
474
expectedSamples = (int)(numSamples*stream->speed + 0.5);
475
if(stream->numOutputSamples > numOutputSamples + expectedSamples) {
476
stream->numOutputSamples = numOutputSamples + expectedSamples;
481
/* Return the number of samples in the output buffer */
482
int sonicSamplesAvailable(
485
return stream->numOutputSamples;
488
/* If skip is greater than one, average skip samples togther and write them to
489
the down-sample buffer. If numChannels is greater than one, mix the channels
490
together as we down sample. */
491
static void downSampleInput(
496
int numSamples = stream->maxRequired/skip;
497
int samplesPerValue = stream->numChannels*skip;
500
short *downSamples = stream->downSampleBuffer;
502
for(i = 0; i < numSamples; i++) {
504
for(j = 0; j < samplesPerValue; j++) {
507
value /= samplesPerValue;
508
*downSamples++ = value;
512
/* Find the best frequency match in the range, and given a sample skip multiple.
513
For now, just find the pitch of the first channel. */
514
static int findPitchPeriodInRange(
521
int period, bestPeriod = 0;
522
short *s, *p, sVal, pVal;
523
unsigned long diff, minDiff = 1, maxDiff = 0;
526
for(period = minPeriod; period <= maxPeriod; period++) {
529
p = samples + period;
530
for(i = 0; i < period; i++) {
533
diff += sVal >= pVal? (unsigned short)(sVal - pVal) :
534
(unsigned short)(pVal - sVal);
536
/* Note that the highest number of samples we add into diff will be less
537
than 256, since we skip samples. Thus, diff is a 24 bit number, and
538
we can safely multiply by numSamples without overflow */
539
if(diff*bestPeriod < minDiff*period) {
543
if(diff*bestPeriod > maxDiff*period) {
547
*retMinDiff = minDiff;
548
*retMaxDiff = maxDiff;
552
/* At abrupt ends of voiced words, we can have pitch periods that are better
553
aproximated by the previous pitch period estimate. Try to detect this case. */
554
static int prevPeriodBetter(
560
if(maxDiff*3/2 < stream->prevMaxDiff && (maxDiff*3.0f)*stream->prevMinDiff <
561
(float)stream->prevMaxDiff*minDiff*2) {
567
/* Find the pitch period. This is a critical step, and we may have to try
568
multiple ways to get a good answer. This version uses AMDF. To improve
569
speed, we down sample by an integer factor get in the 11KHz range, and then
570
do it again with a narrower frequency range without down sampling */
571
static int findPitchPeriod(
575
int minPeriod = stream->minPeriod;
576
int maxPeriod = stream->maxPeriod;
577
int sampleRate = stream->sampleRate;
578
int minDiff, maxDiff, retPeriod;
582
if(sampleRate > SONIC_AMDF_FREQ) {
583
skip = sampleRate/SONIC_AMDF_FREQ;
585
if(stream->numChannels == 1 && skip == 1) {
586
period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff, &maxDiff);
588
downSampleInput(stream, samples, skip);
589
period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip,
590
maxPeriod/skip, &minDiff, &maxDiff);
593
minPeriod = period - (skip << 2);
594
maxPeriod = period + (skip << 2);
595
if(minPeriod < stream->minPeriod) {
596
minPeriod = stream->minPeriod;
598
if(maxPeriod > stream->maxPeriod) {
599
maxPeriod = stream->maxPeriod;
601
if(stream->numChannels == 1) {
602
period = findPitchPeriodInRange(samples, minPeriod, maxPeriod,
605
downSampleInput(stream, samples, 1);
606
period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod,
607
maxPeriod, &minDiff, &maxDiff);
611
if(prevPeriodBetter(stream, period, minDiff, maxDiff)) {
612
retPeriod = stream->prevPeriod;
616
stream->prevMinDiff = minDiff;
617
stream->prevMaxDiff = maxDiff;
618
stream->prevPeriod = period;
622
/* Overlap two sound segments, ramp the volume of one down, while ramping the
623
other one from zero up, and add them, storing the result at the output. */
624
static void overlapAdd(
634
for(i = 0; i < numChannels; i++) {
638
for(t = 0; t < numSamples; t++) {
639
*o = (*d*(numSamples - t) + *u*t)/numSamples;
647
/* Overlap two sound segments, ramp the volume of one down, while ramping the
648
other one from zero up, and add them, storing the result at the output. */
649
static void overlapAddWithSeparation(
660
for(i = 0; i < numChannels; i++) {
664
for(t = 0; t < numSamples + separation; t++) {
666
*o = *d*(numSamples - t)/numSamples;
668
} else if(t < numSamples) {
669
*o = (*d*(numSamples - t) + *u*(t - separation))/numSamples;
673
*o = *u*(t - separation)/numSamples;
681
/* Just move the new samples in the output buffer to the pitch bufer */
682
static int moveNewSamplesToPitchBuffer(
684
int originalNumOutputSamples)
686
int numSamples = stream->numOutputSamples - originalNumOutputSamples;
687
int numChannels = stream->numChannels;
689
if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) {
690
stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples;
691
stream->pitchBuffer = (short *)realloc(stream->pitchBuffer,
692
stream->pitchBufferSize*sizeof(short)*numChannels);
693
if(stream->pitchBuffer == NULL) {
697
memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels,
698
stream->outputBuffer + originalNumOutputSamples*numChannels,
699
numSamples*sizeof(short)*numChannels);
700
stream->numOutputSamples = originalNumOutputSamples;
701
stream->numPitchSamples += numSamples;
705
/* Remove processed samples from the pitch buffer. */
706
static void removePitchSamples(
710
int numChannels = stream->numChannels;
711
short *source = stream->pitchBuffer + numSamples*numChannels;
713
if(numSamples == 0) {
716
if(numSamples != stream->numPitchSamples) {
717
memmove(stream->pitchBuffer, source, (stream->numPitchSamples -
718
numSamples)*sizeof(short)*numChannels);
720
stream->numPitchSamples -= numSamples;
723
/* Change the pitch. The latency this introduces could be reduced by looking at
724
past samples to determine pitch, rather than future. */
725
static int adjustPitch(
727
int originalNumOutputSamples)
729
float pitch = stream->pitch;
730
int numChannels = stream->numChannels;
731
int period, newPeriod, separation;
733
short *out, *rampDown, *rampUp;
735
if(stream->numOutputSamples == originalNumOutputSamples) {
738
if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
741
while(stream->numPitchSamples - position >= stream->maxRequired) {
742
period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels);
743
newPeriod = period/pitch;
744
if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) {
747
out = stream->outputBuffer + stream->numOutputSamples*numChannels;
749
rampDown = stream->pitchBuffer + position*numChannels;
750
rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels;
751
overlapAdd(newPeriod, numChannels, out, rampDown, rampUp);
753
rampDown = stream->pitchBuffer + position*numChannels;
754
rampUp = stream->pitchBuffer + position*numChannels;
755
separation = newPeriod - period;
756
overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp);
758
stream->numOutputSamples += newPeriod;
761
removePitchSamples(stream, position);
765
/* Skip over a pitch period, and copy period/speed samples to the output */
766
static int skipPitchPeriod(
773
int numChannels = stream->numChannels;
776
newSamples = period/(speed - 1.0f);
777
} else if(speed > 1.0f) {
779
stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f);
781
if(!enlargeOutputBufferIfNeeded(stream, newSamples)) {
784
overlapAdd(newSamples, numChannels, stream->outputBuffer +
785
stream->numOutputSamples*numChannels, samples, samples + period*numChannels);
786
stream->numOutputSamples += newSamples;
790
/* Insert a pitch period, and determine how much input to copy directly. */
791
static int insertPitchPeriod(
799
int numChannels = stream->numChannels;
802
newSamples = period*speed/(1.0f - speed);
805
stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed);
807
if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) {
810
out = stream->outputBuffer + stream->numOutputSamples*numChannels;
811
memcpy(out, samples, period*sizeof(short)*numChannels);
812
out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels;
813
overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples);
814
stream->numOutputSamples += period + newSamples;
818
/* Resample as many pitch periods as we have buffered on the input. Return 0 if
819
we fail to resize an input or output buffer. Also scale the output by the volume. */
820
static int changeSpeed(
825
int numSamples = stream->numInputSamples;
826
int position = 0, period, newSamples;
827
int maxRequired = stream->maxRequired;
829
if(stream->numInputSamples < maxRequired) {
833
if(stream->remainingInputToCopy > 0) {
834
newSamples = copyInputToOutput(stream, position);
835
position += newSamples;
837
samples = stream->inputBuffer + position*stream->numChannels;
838
period = findPitchPeriod(stream, samples);
840
newSamples = skipPitchPeriod(stream, samples, speed, period);
841
position += period + newSamples;
843
newSamples = insertPitchPeriod(stream, samples, speed, period);
844
position += newSamples;
847
if(newSamples == 0) {
848
return 0; /* Failed to resize output buffer */
850
} while(position + maxRequired <= numSamples);
851
removeInputSamples(stream, position);
855
/* Resample as many pitch periods as we have buffered on the input. Return 0 if
856
we fail to resize an input or output buffer. Also scale the output by the volume. */
857
static int processStreamInput(
860
int originalNumOutputSamples = stream->numOutputSamples;
861
float speed = stream->speed/stream->pitch;
863
if(speed > 1.00001 || speed < 0.99999) {
864
changeSpeed(stream, speed);
866
if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) {
869
stream->numInputSamples = 0;
871
if(stream->pitch != 1.0f) {
872
if(!adjustPitch(stream, originalNumOutputSamples)) {
876
if(stream->volume != 1.0f) {
877
/* Adjust output volume. */
878
scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels,
879
(stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels,
885
/* Write floating point data to the input buffer and process it. */
886
int sonicWriteFloatToStream(
891
if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
894
return processStreamInput(stream);
897
/* Simple wrapper around sonicWriteFloatToStream that does the short to float
898
conversion for you. */
899
int sonicWriteShortToStream(
904
if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
907
return processStreamInput(stream);
910
/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float
911
conversion for you. */
912
int sonicWriteUnsignedCharToStream(
914
unsigned char *samples,
917
if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
920
return processStreamInput(stream);
923
/* This is a non-stream oriented interface to just change the speed of a sound sample */
924
int sonicChangeFloatSpeed(
933
sonicStream stream = sonicCreateStream(sampleRate, numChannels);
935
sonicSetSpeed(stream, speed);
936
sonicSetPitch(stream, pitch);
937
sonicSetVolume(stream, volume);
938
sonicWriteFloatToStream(stream, samples, numSamples);
939
sonicFlushStream(stream);
940
numSamples = sonicSamplesAvailable(stream);
941
sonicReadFloatFromStream(stream, samples, numSamples);
942
sonicDestroyStream(stream);
946
/* This is a non-stream oriented interface to just change the speed of a sound sample */
947
int sonicChangeShortSpeed(
956
sonicStream stream = sonicCreateStream(sampleRate, numChannels);
958
sonicSetSpeed(stream, speed);
959
sonicSetPitch(stream, pitch);
960
sonicSetVolume(stream, volume);
961
sonicWriteShortToStream(stream, samples, numSamples);
962
sonicFlushStream(stream);
963
numSamples = sonicSamplesAvailable(stream);
964
sonicReadShortFromStream(stream, samples, numSamples);
965
sonicDestroyStream(stream);