| /* Sonic library |
| Copyright 2010 |
| Bill Cox |
| This file is part of the Sonic Library. |
| |
| The Sonic Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; if not, write to the Free |
| Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #ifdef SONIC_USE_SIN |
| #include <math.h> |
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
| #endif |
| #include "sonic.h" |
| |
| struct sonicStreamStruct { |
| short *inputBuffer; |
| short *outputBuffer; |
| short *pitchBuffer; |
| short *downSampleBuffer; |
| float speed; |
| float volume; |
| float pitch; |
| int quality; |
| int numChannels; |
| int inputBufferSize; |
| int pitchBufferSize; |
| int outputBufferSize; |
| int numInputSamples; |
| int numOutputSamples; |
| int numPitchSamples; |
| int minPeriod; |
| int maxPeriod; |
| int maxRequired; |
| int remainingInputToCopy; |
| int sampleRate; |
| int prevPeriod; |
| int prevMaxDiff; |
| int prevMinDiff; |
| }; |
| |
| /* Just used for debugging */ |
| /* |
| void sonicMSG(char *format, ...) |
| { |
| char buffer[4096]; |
| va_list ap; |
| FILE *file; |
| |
| va_start(ap, format); |
| vsprintf((char *)buffer, (char *)format, ap); |
| va_end(ap); |
| file=fopen("/tmp/sonic.log", "a"); |
| fprintf(file, "%s", buffer); |
| fclose(file); |
| } |
| */ |
| |
| /* Scale the samples by the factor. */ |
| static void scaleSamples( |
| short *samples, |
| int numSamples, |
| float volume) |
| { |
| int fixedPointVolume = volume*4096.0f; |
| int value; |
| |
| while(numSamples--) { |
| value = (*samples*fixedPointVolume) >> 12; |
| if(value > 32767) { |
| value = 32767; |
| } else if(value < -32767) { |
| value = -32767; |
| } |
| *samples++ = value; |
| } |
| } |
| |
| /* Get the speed of the stream. */ |
| float sonicGetSpeed( |
| sonicStream stream) |
| { |
| return stream->speed; |
| } |
| |
| /* Set the speed of the stream. */ |
| void sonicSetSpeed( |
| sonicStream stream, |
| float speed) |
| { |
| stream->speed = speed; |
| } |
| |
| /* Get the pitch of the stream. */ |
| float sonicGetPitch( |
| sonicStream stream) |
| { |
| return stream->pitch; |
| } |
| |
| /* Set the pitch of the stream. */ |
| void sonicSetPitch( |
| sonicStream stream, |
| float pitch) |
| { |
| stream->pitch = pitch; |
| } |
| |
| /* Get the quality setting. */ |
| int sonicGetQuality( |
| sonicStream stream) |
| { |
| return stream->quality; |
| } |
| |
| /* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */ |
| void sonicSetQuality( |
| sonicStream stream, |
| int quality) |
| { |
| stream->quality = quality; |
| } |
| |
| /* Get the scaling factor of the stream. */ |
| float sonicGetVolume( |
| sonicStream stream) |
| { |
| return stream->volume; |
| } |
| |
| /* Set the scaling factor of the stream. */ |
| void sonicSetVolume( |
| sonicStream stream, |
| float volume) |
| { |
| stream->volume = volume; |
| } |
| |
| /* Get the sample rate of the stream. */ |
| int sonicGetSampleRate( |
| sonicStream stream) |
| { |
| return stream->sampleRate; |
| } |
| |
| /* Get the number of channels. */ |
| int sonicGetNumChannels( |
| sonicStream stream) |
| { |
| return stream->numChannels; |
| } |
| |
| /* Destroy the sonic stream. */ |
| void sonicDestroyStream( |
| sonicStream stream) |
| { |
| if(stream->inputBuffer != NULL) { |
| free(stream->inputBuffer); |
| } |
| if(stream->outputBuffer != NULL) { |
| free(stream->outputBuffer); |
| } |
| if(stream->pitchBuffer != NULL) { |
| free(stream->pitchBuffer); |
| } |
| if(stream->downSampleBuffer != NULL) { |
| free(stream->downSampleBuffer); |
| } |
| free(stream); |
| } |
| |
| /* Create a sonic stream. Return NULL only if we are out of memory and cannot |
| allocate the stream. */ |
| sonicStream sonicCreateStream( |
| int sampleRate, |
| int numChannels) |
| { |
| sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct)); |
| int minPeriod = sampleRate/SONIC_MAX_PITCH; |
| int maxPeriod = sampleRate/SONIC_MIN_PITCH; |
| int maxRequired = 2*maxPeriod; |
| |
| if(stream == NULL) { |
| return NULL; |
| } |
| stream->inputBufferSize = maxRequired; |
| stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); |
| if(stream->inputBuffer == NULL) { |
| sonicDestroyStream(stream); |
| return NULL; |
| } |
| stream->outputBufferSize = maxRequired; |
| stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); |
| if(stream->outputBuffer == NULL) { |
| sonicDestroyStream(stream); |
| return NULL; |
| } |
| stream->pitchBufferSize = maxRequired; |
| stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); |
| if(stream->pitchBuffer == NULL) { |
| sonicDestroyStream(stream); |
| return NULL; |
| } |
| stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short)); |
| stream->speed = 1.0f; |
| stream->pitch = 1.0f; |
| stream->volume = 1.0f; |
| stream->quality = 0; |
| stream->sampleRate = sampleRate; |
| stream->numChannels = numChannels; |
| stream->minPeriod = minPeriod; |
| stream->maxPeriod = maxPeriod; |
| stream->maxRequired = maxRequired; |
| return stream; |
| } |
| |
| /* Enlarge the output buffer if needed. */ |
| static int enlargeOutputBufferIfNeeded( |
| sonicStream stream, |
| int numSamples) |
| { |
| if(stream->numOutputSamples + numSamples > stream->outputBufferSize) { |
| stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples; |
| stream->outputBuffer = (short *)realloc(stream->outputBuffer, |
| stream->outputBufferSize*sizeof(short)*stream->numChannels); |
| if(stream->outputBuffer == NULL) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| /* Enlarge the input buffer if needed. */ |
| static int enlargeInputBufferIfNeeded( |
| sonicStream stream, |
| int numSamples) |
| { |
| if(stream->numInputSamples + numSamples > stream->inputBufferSize) { |
| stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples; |
| stream->inputBuffer = (short *)realloc(stream->inputBuffer, |
| stream->inputBufferSize*sizeof(short)*stream->numChannels); |
| if(stream->inputBuffer == NULL) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| /* Add the input samples to the input buffer. */ |
| static int addFloatSamplesToInputBuffer( |
| sonicStream stream, |
| float *samples, |
| int numSamples) |
| { |
| short *buffer; |
| int count = numSamples*stream->numChannels; |
| |
| if(numSamples == 0) { |
| return 1; |
| } |
| if(!enlargeInputBufferIfNeeded(stream, numSamples)) { |
| return 0; |
| } |
| buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels; |
| while(count--) { |
| *buffer++ = (*samples++)*32767.0f; |
| } |
| stream->numInputSamples += numSamples; |
| return 1; |
| } |
| |
| /* Add the input samples to the input buffer. */ |
| static int addShortSamplesToInputBuffer( |
| sonicStream stream, |
| short *samples, |
| int numSamples) |
| { |
| if(numSamples == 0) { |
| return 1; |
| } |
| if(!enlargeInputBufferIfNeeded(stream, numSamples)) { |
| return 0; |
| } |
| memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples, |
| numSamples*sizeof(short)*stream->numChannels); |
| stream->numInputSamples += numSamples; |
| return 1; |
| } |
| |
| /* Add the input samples to the input buffer. */ |
| static int addUnsignedCharSamplesToInputBuffer( |
| sonicStream stream, |
| unsigned char *samples, |
| int numSamples) |
| { |
| short *buffer; |
| int count = numSamples*stream->numChannels; |
| |
| if(numSamples == 0) { |
| return 1; |
| } |
| if(!enlargeInputBufferIfNeeded(stream, numSamples)) { |
| return 0; |
| } |
| buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels; |
| while(count--) { |
| *buffer++ = (*samples++ - 128) << 8; |
| } |
| stream->numInputSamples += numSamples; |
| return 1; |
| } |
| |
| /* Remove input samples that we have already processed. */ |
| static void removeInputSamples( |
| sonicStream stream, |
| int position) |
| { |
| int remainingSamples = stream->numInputSamples - position; |
| |
| if(remainingSamples > 0) { |
| memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels, |
| remainingSamples*sizeof(short)*stream->numChannels); |
| } |
| stream->numInputSamples = remainingSamples; |
| } |
| |
| /* Just copy from the array to the output buffer */ |
| static int copyToOutput( |
| sonicStream stream, |
| short *samples, |
| int numSamples) |
| { |
| if(!enlargeOutputBufferIfNeeded(stream, numSamples)) { |
| return 0; |
| } |
| memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels, |
| samples, numSamples*sizeof(short)*stream->numChannels); |
| stream->numOutputSamples += numSamples; |
| return numSamples; |
| } |
| |
| /* Just copy from the input buffer to the output buffer. Return 0 if we fail to |
| resize the output buffer. Otherwise, return numSamples */ |
| static int copyInputToOutput( |
| sonicStream stream, |
| int position) |
| { |
| int numSamples = stream->remainingInputToCopy; |
| |
| if(numSamples > stream->maxRequired) { |
| numSamples = stream->maxRequired; |
| } |
| if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels, |
| numSamples)) { |
| return 0; |
| } |
| stream->remainingInputToCopy -= numSamples; |
| return numSamples; |
| } |
| |
| /* Read data out of the stream. Sometimes no data will be available, and zero |
| is returned, which is not an error condition. */ |
| int sonicReadFloatFromStream( |
| sonicStream stream, |
| float *samples, |
| int maxSamples) |
| { |
| int numSamples = stream->numOutputSamples; |
| int remainingSamples = 0; |
| short *buffer; |
| int count; |
| |
| if(numSamples == 0) { |
| return 0; |
| } |
| if(numSamples > maxSamples) { |
| remainingSamples = numSamples - maxSamples; |
| numSamples = maxSamples; |
| } |
| buffer = stream->outputBuffer; |
| count = numSamples*stream->numChannels; |
| while(count--) { |
| *samples++ = (*buffer++)/32767.0f; |
| } |
| if(remainingSamples > 0) { |
| memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, |
| remainingSamples*sizeof(short)*stream->numChannels); |
| } |
| stream->numOutputSamples = remainingSamples; |
| return numSamples; |
| } |
| |
| /* Read short data out of the stream. Sometimes no data will be available, and zero |
| is returned, which is not an error condition. */ |
| int sonicReadShortFromStream( |
| sonicStream stream, |
| short *samples, |
| int maxSamples) |
| { |
| int numSamples = stream->numOutputSamples; |
| int remainingSamples = 0; |
| |
| if(numSamples == 0) { |
| return 0; |
| } |
| if(numSamples > maxSamples) { |
| remainingSamples = numSamples - maxSamples; |
| numSamples = maxSamples; |
| } |
| memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels); |
| if(remainingSamples > 0) { |
| memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, |
| remainingSamples*sizeof(short)*stream->numChannels); |
| } |
| stream->numOutputSamples = remainingSamples; |
| return numSamples; |
| } |
| |
| /* Read unsigned char data out of the stream. Sometimes no data will be available, and zero |
| is returned, which is not an error condition. */ |
| int sonicReadUnsignedCharFromStream( |
| sonicStream stream, |
| unsigned char *samples, |
| int maxSamples) |
| { |
| int numSamples = stream->numOutputSamples; |
| int remainingSamples = 0; |
| short *buffer; |
| int count; |
| |
| if(numSamples == 0) { |
| return 0; |
| } |
| if(numSamples > maxSamples) { |
| remainingSamples = numSamples - maxSamples; |
| numSamples = maxSamples; |
| } |
| buffer = stream->outputBuffer; |
| count = numSamples*stream->numChannels; |
| while(count--) { |
| *samples++ = (char)((*buffer++) >> 8) + 128; |
| } |
| if(remainingSamples > 0) { |
| memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, |
| remainingSamples*sizeof(short)*stream->numChannels); |
| } |
| stream->numOutputSamples = remainingSamples; |
| return numSamples; |
| } |
| |
| /* Force the sonic stream to generate output using whatever data it currently |
| has. No extra delay will be added to the output, but flushing in the middle of |
| words could introduce distortion. */ |
| int sonicFlushStream( |
| sonicStream stream) |
| { |
| int maxRequired = stream->maxRequired; |
| int remainingSamples = stream->numInputSamples; |
| float speed = stream->speed/stream->pitch; |
| int expectedOutputSamples = stream->numOutputSamples + |
| (int)(remainingSamples/speed + stream->numPitchSamples/stream->pitch + 0.5f); |
| |
| /* Add enough silence to flush both input and pitch buffers. */ |
| if(!enlargeInputBufferIfNeeded(stream, remainingSamples + 2*maxRequired)) { |
| return 0; |
| } |
| memset(stream->inputBuffer + remainingSamples*stream->numChannels, 0, |
| 2*maxRequired*sizeof(short)*stream->numChannels); |
| stream->numInputSamples += 2*maxRequired; |
| if(!sonicWriteShortToStream(stream, NULL, 0)) { |
| return 0; |
| } |
| /* Throw away any extra samples we generated due to the silence we added */ |
| if(stream->numOutputSamples > expectedOutputSamples) { |
| stream->numOutputSamples = expectedOutputSamples; |
| } |
| /* Empty input and pitch buffers */ |
| stream->numInputSamples = 0; |
| stream->remainingInputToCopy = 0; |
| stream->numPitchSamples = 0; |
| return 1; |
| } |
| |
| /* Return the number of samples in the output buffer */ |
| int sonicSamplesAvailable( |
| sonicStream stream) |
| { |
| return stream->numOutputSamples; |
| } |
| |
| /* If skip is greater than one, average skip samples togther and write them to |
| the down-sample buffer. If numChannels is greater than one, mix the channels |
| together as we down sample. */ |
| static void downSampleInput( |
| sonicStream stream, |
| short *samples, |
| int skip) |
| { |
| int numSamples = stream->maxRequired/skip; |
| int samplesPerValue = stream->numChannels*skip; |
| int i, j; |
| int value; |
| short *downSamples = stream->downSampleBuffer; |
| |
| for(i = 0; i < numSamples; i++) { |
| value = 0; |
| for(j = 0; j < samplesPerValue; j++) { |
| value += *samples++; |
| } |
| value /= samplesPerValue; |
| *downSamples++ = value; |
| } |
| } |
| |
| /* Find the best frequency match in the range, and given a sample skip multiple. |
| For now, just find the pitch of the first channel. */ |
| static int findPitchPeriodInRange( |
| short *samples, |
| int minPeriod, |
| int maxPeriod, |
| int *retMinDiff, |
| int *retMaxDiff) |
| { |
| int period, bestPeriod = 0, worstPeriod = 255; |
| short *s, *p, sVal, pVal; |
| unsigned long diff, minDiff = 1, maxDiff = 0; |
| int i; |
| |
| for(period = minPeriod; period <= maxPeriod; period++) { |
| diff = 0; |
| s = samples; |
| p = samples + period; |
| for(i = 0; i < period; i++) { |
| sVal = *s++; |
| pVal = *p++; |
| diff += sVal >= pVal? (unsigned short)(sVal - pVal) : |
| (unsigned short)(pVal - sVal); |
| } |
| /* Note that the highest number of samples we add into diff will be less |
| than 256, since we skip samples. Thus, diff is a 24 bit number, and |
| we can safely multiply by numSamples without overflow */ |
| if(diff*bestPeriod < minDiff*period) { |
| minDiff = diff; |
| bestPeriod = period; |
| } |
| if(diff*worstPeriod > maxDiff*period) { |
| maxDiff = diff; |
| worstPeriod = period; |
| } |
| } |
| *retMinDiff = minDiff/bestPeriod; |
| *retMaxDiff = maxDiff/worstPeriod; |
| return bestPeriod; |
| } |
| |
| /* At abrupt ends of voiced words, we can have pitch periods that are better |
| aproximated by the previous pitch period estimate. Try to detect this case. */ |
| static int prevPeriodBetter( |
| sonicStream stream, |
| int period, |
| int minDiff, |
| int maxDiff, |
| int preferNewPeriod) |
| { |
| if(minDiff == 0) { |
| return 0; |
| } |
| if(preferNewPeriod) { |
| if(maxDiff > minDiff*3) { |
| /* Got a reasonable match this period */ |
| return 0; |
| } |
| if(minDiff*2 <= stream->prevMinDiff*3) { |
| /* Mismatch is not that much greater this period */ |
| return 0; |
| } |
| } else { |
| if(minDiff <= stream->prevMinDiff) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| /* Find the pitch period. This is a critical step, and we may have to try |
| multiple ways to get a good answer. This version uses AMDF. To improve |
| speed, we down sample by an integer factor get in the 11KHz range, and then |
| do it again with a narrower frequency range without down sampling */ |
| static int findPitchPeriod( |
| sonicStream stream, |
| short *samples, |
| int preferNewPeriod) |
| { |
| int minPeriod = stream->minPeriod; |
| int maxPeriod = stream->maxPeriod; |
| int sampleRate = stream->sampleRate; |
| int minDiff, maxDiff, retPeriod; |
| int skip = 1; |
| int period; |
| |
| if(sampleRate > SONIC_AMDF_FREQ && stream->quality == 0) { |
| skip = sampleRate/SONIC_AMDF_FREQ; |
| } |
| if(stream->numChannels == 1 && skip == 1) { |
| period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff, &maxDiff); |
| } else { |
| downSampleInput(stream, samples, skip); |
| period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip, |
| maxPeriod/skip, &minDiff, &maxDiff); |
| if(skip != 1) { |
| period *= skip; |
| minPeriod = period - (skip << 2); |
| maxPeriod = period + (skip << 2); |
| if(minPeriod < stream->minPeriod) { |
| minPeriod = stream->minPeriod; |
| } |
| if(maxPeriod > stream->maxPeriod) { |
| maxPeriod = stream->maxPeriod; |
| } |
| if(stream->numChannels == 1) { |
| period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, |
| &minDiff, &maxDiff); |
| } else { |
| downSampleInput(stream, samples, 1); |
| period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod, |
| maxPeriod, &minDiff, &maxDiff); |
| } |
| } |
| } |
| if(prevPeriodBetter(stream, period, minDiff, maxDiff, preferNewPeriod)) { |
| retPeriod = stream->prevPeriod; |
| } else { |
| retPeriod = period; |
| } |
| stream->prevMinDiff = minDiff; |
| stream->prevMaxDiff = maxDiff; |
| stream->prevPeriod = period; |
| return retPeriod; |
| } |
| |
| /* Overlap two sound segments, ramp the volume of one down, while ramping the |
| other one from zero up, and add them, storing the result at the output. */ |
| static void overlapAdd( |
| int numSamples, |
| int numChannels, |
| short *out, |
| short *rampDown, |
| short *rampUp) |
| { |
| short *o, *u, *d; |
| int i, t; |
| |
| for(i = 0; i < numChannels; i++) { |
| o = out + i; |
| u = rampUp + i; |
| d = rampDown + i; |
| for(t = 0; t < numSamples; t++) { |
| #ifdef SONIC_USE_SIN |
| float ratio = sin(t*M_PI/(2*numSamples)); |
| *o = *d*(1.0f - ratio) + *u*ratio; |
| #else |
| *o = (*d*(numSamples - t) + *u*t)/numSamples; |
| #endif |
| o += numChannels; |
| d += numChannels; |
| u += numChannels; |
| } |
| } |
| } |
| |
| /* Overlap two sound segments, ramp the volume of one down, while ramping the |
| other one from zero up, and add them, storing the result at the output. */ |
| static void overlapAddWithSeparation( |
| int numSamples, |
| int numChannels, |
| int separation, |
| short *out, |
| short *rampDown, |
| short *rampUp) |
| { |
| short *o, *u, *d; |
| int i, t; |
| |
| for(i = 0; i < numChannels; i++) { |
| o = out + i; |
| u = rampUp + i; |
| d = rampDown + i; |
| for(t = 0; t < numSamples + separation; t++) { |
| if(t < separation) { |
| *o = *d*(numSamples - t)/numSamples; |
| d += numChannels; |
| } else if(t < numSamples) { |
| *o = (*d*(numSamples - t) + *u*(t - separation))/numSamples; |
| d += numChannels; |
| u += numChannels; |
| } else { |
| *o = *u*(t - separation)/numSamples; |
| u += numChannels; |
| } |
| o += numChannels; |
| } |
| } |
| } |
| |
| /* Just move the new samples in the output buffer to the pitch bufer */ |
| static int moveNewSamplesToPitchBuffer( |
| sonicStream stream, |
| int originalNumOutputSamples) |
| { |
| int numSamples = stream->numOutputSamples - originalNumOutputSamples; |
| int numChannels = stream->numChannels; |
| |
| if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) { |
| stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples; |
| stream->pitchBuffer = (short *)realloc(stream->pitchBuffer, |
| stream->pitchBufferSize*sizeof(short)*numChannels); |
| if(stream->pitchBuffer == NULL) { |
| return 0; |
| } |
| } |
| memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels, |
| stream->outputBuffer + originalNumOutputSamples*numChannels, |
| numSamples*sizeof(short)*numChannels); |
| stream->numOutputSamples = originalNumOutputSamples; |
| stream->numPitchSamples += numSamples; |
| return 1; |
| } |
| |
| /* Remove processed samples from the pitch buffer. */ |
| static void removePitchSamples( |
| sonicStream stream, |
| int numSamples) |
| { |
| int numChannels = stream->numChannels; |
| short *source = stream->pitchBuffer + numSamples*numChannels; |
| |
| if(numSamples == 0) { |
| return; |
| } |
| if(numSamples != stream->numPitchSamples) { |
| memmove(stream->pitchBuffer, source, (stream->numPitchSamples - |
| numSamples)*sizeof(short)*numChannels); |
| } |
| stream->numPitchSamples -= numSamples; |
| } |
| |
| /* Change the pitch. The latency this introduces could be reduced by looking at |
| past samples to determine pitch, rather than future. */ |
| static int adjustPitch( |
| sonicStream stream, |
| int originalNumOutputSamples) |
| { |
| float pitch = stream->pitch; |
| int numChannels = stream->numChannels; |
| int period, newPeriod, separation; |
| int position = 0; |
| short *out, *rampDown, *rampUp; |
| |
| if(stream->numOutputSamples == originalNumOutputSamples) { |
| return 1; |
| } |
| if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) { |
| return 0; |
| } |
| while(stream->numPitchSamples - position >= stream->maxRequired) { |
| period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels, 0); |
| newPeriod = period/pitch; |
| if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) { |
| return 0; |
| } |
| out = stream->outputBuffer + stream->numOutputSamples*numChannels; |
| if(pitch >= 1.0f) { |
| rampDown = stream->pitchBuffer + position*numChannels; |
| rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels; |
| overlapAdd(newPeriod, numChannels, out, rampDown, rampUp); |
| } else { |
| rampDown = stream->pitchBuffer + position*numChannels; |
| rampUp = stream->pitchBuffer + position*numChannels; |
| separation = newPeriod - period; |
| overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp); |
| } |
| stream->numOutputSamples += newPeriod; |
| position += period; |
| } |
| removePitchSamples(stream, position); |
| return 1; |
| } |
| |
| /* Skip over a pitch period, and copy period/speed samples to the output */ |
| static int skipPitchPeriod( |
| sonicStream stream, |
| short *samples, |
| float speed, |
| int period) |
| { |
| long newSamples; |
| int numChannels = stream->numChannels; |
| |
| if(speed >= 2.0f) { |
| newSamples = period/(speed - 1.0f); |
| } else if(speed > 1.0f) { |
| newSamples = period; |
| stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f); |
| } |
| if(!enlargeOutputBufferIfNeeded(stream, newSamples)) { |
| return 0; |
| } |
| overlapAdd(newSamples, numChannels, stream->outputBuffer + |
| stream->numOutputSamples*numChannels, samples, samples + period*numChannels); |
| stream->numOutputSamples += newSamples; |
| return newSamples; |
| } |
| |
| /* Insert a pitch period, and determine how much input to copy directly. */ |
| static int insertPitchPeriod( |
| sonicStream stream, |
| short *samples, |
| float speed, |
| int period) |
| { |
| long newSamples; |
| short *out; |
| int numChannels = stream->numChannels; |
| |
| if(speed < 0.5f) { |
| newSamples = period*speed/(1.0f - speed); |
| } else { |
| newSamples = period; |
| stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed); |
| } |
| if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) { |
| return 0; |
| } |
| out = stream->outputBuffer + stream->numOutputSamples*numChannels; |
| memcpy(out, samples, period*sizeof(short)*numChannels); |
| out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels; |
| overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples); |
| stream->numOutputSamples += period + newSamples; |
| return newSamples; |
| } |
| |
| /* Resample as many pitch periods as we have buffered on the input. Return 0 if |
| we fail to resize an input or output buffer. Also scale the output by the volume. */ |
| static int changeSpeed( |
| sonicStream stream, |
| float speed) |
| { |
| short *samples; |
| int numSamples = stream->numInputSamples; |
| int position = 0, period, newSamples; |
| int maxRequired = stream->maxRequired; |
| |
| if(stream->numInputSamples < maxRequired) { |
| return 1; |
| } |
| do { |
| if(stream->remainingInputToCopy > 0) { |
| newSamples = copyInputToOutput(stream, position); |
| position += newSamples; |
| } else { |
| samples = stream->inputBuffer + position*stream->numChannels; |
| period = findPitchPeriod(stream, samples, 1); |
| if(speed > 1.0) { |
| newSamples = skipPitchPeriod(stream, samples, speed, period); |
| position += period + newSamples; |
| } else { |
| newSamples = insertPitchPeriod(stream, samples, speed, period); |
| position += newSamples; |
| } |
| } |
| if(newSamples == 0) { |
| return 0; /* Failed to resize output buffer */ |
| } |
| } while(position + maxRequired <= numSamples); |
| removeInputSamples(stream, position); |
| return 1; |
| } |
| |
| /* Resample as many pitch periods as we have buffered on the input. Return 0 if |
| we fail to resize an input or output buffer. Also scale the output by the volume. */ |
| static int processStreamInput( |
| sonicStream stream) |
| { |
| int originalNumOutputSamples = stream->numOutputSamples; |
| float speed = stream->speed/stream->pitch; |
| |
| if(speed > 1.00001 || speed < 0.99999) { |
| changeSpeed(stream, speed); |
| } else { |
| if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) { |
| return 0; |
| } |
| stream->numInputSamples = 0; |
| } |
| if(stream->pitch != 1.0f) { |
| if(!adjustPitch(stream, originalNumOutputSamples)) { |
| return 0; |
| } |
| } |
| if(stream->volume != 1.0f) { |
| /* Adjust output volume. */ |
| scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels, |
| (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels, |
| stream->volume); |
| } |
| return 1; |
| } |
| |
| /* Write floating point data to the input buffer and process it. */ |
| int sonicWriteFloatToStream( |
| sonicStream stream, |
| float *samples, |
| int numSamples) |
| { |
| if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) { |
| return 0; |
| } |
| return processStreamInput(stream); |
| } |
| |
| /* Simple wrapper around sonicWriteFloatToStream that does the short to float |
| conversion for you. */ |
| int sonicWriteShortToStream( |
| sonicStream stream, |
| short *samples, |
| int numSamples) |
| { |
| if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) { |
| return 0; |
| } |
| return processStreamInput(stream); |
| } |
| |
| /* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float |
| conversion for you. */ |
| int sonicWriteUnsignedCharToStream( |
| sonicStream stream, |
| unsigned char *samples, |
| int numSamples) |
| { |
| if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) { |
| return 0; |
| } |
| return processStreamInput(stream); |
| } |
| |
| /* This is a non-stream oriented interface to just change the speed of a sound sample */ |
| int sonicChangeFloatSpeed( |
| float *samples, |
| int numSamples, |
| float speed, |
| float pitch, |
| float volume, |
| int sampleRate, |
| int numChannels) |
| { |
| sonicStream stream = sonicCreateStream(sampleRate, numChannels); |
| |
| sonicSetSpeed(stream, speed); |
| sonicSetPitch(stream, pitch); |
| sonicSetVolume(stream, volume); |
| sonicWriteFloatToStream(stream, samples, numSamples); |
| sonicFlushStream(stream); |
| numSamples = sonicSamplesAvailable(stream); |
| sonicReadFloatFromStream(stream, samples, numSamples); |
| sonicDestroyStream(stream); |
| return numSamples; |
| } |
| |
| /* This is a non-stream oriented interface to just change the speed of a sound sample */ |
| int sonicChangeShortSpeed( |
| short *samples, |
| int numSamples, |
| float speed, |
| float pitch, |
| float volume, |
| int sampleRate, |
| int numChannels) |
| { |
| sonicStream stream = sonicCreateStream(sampleRate, numChannels); |
| |
| sonicSetSpeed(stream, speed); |
| sonicSetPitch(stream, pitch); |
| sonicSetVolume(stream, volume); |
| sonicWriteShortToStream(stream, samples, numSamples); |
| sonicFlushStream(stream); |
| numSamples = sonicSamplesAvailable(stream); |
| sonicReadShortFromStream(stream, samples, numSamples); |
| sonicDestroyStream(stream); |
| return numSamples; |
| } |