From 866cd3663a7c67934a57a592522c22a6542b7485 Mon Sep 17 00:00:00 2001 From: zhangwenting3652 Date: Fri, 25 Apr 2025 16:10:31 +0800 Subject: [PATCH] updat sonic library Signed-off-by: zhangwenting3652 --- sonic/sonic.c | 2109 +++++++++++++++++++++++++------------------------ sonic/sonic.h | 211 ++++- 2 files changed, 1265 insertions(+), 1055 deletions(-) diff --git a/sonic/sonic.c b/sonic/sonic.c index 8b04c647a..8e2f696b2 100644 --- a/sonic/sonic.c +++ b/sonic/sonic.c @@ -6,1190 +6,1243 @@ This file is licensed under the Apache 2.0 license. */ -#include +#include "sonic.h" + +#include +#include #include #include -#include -#ifdef SONIC_USE_SIN -#include -#ifndef M_PI -#define M_PI 3.14159265358979323846 + +/* + The following code was used to generate the following sinc lookup table. + + #include + #include + #include + + double findHannWeight(int N, double x) { + return 0.5*(1.0 - cos(2*M_PI*x/N)); + } + + double findSincCoefficient(int N, double x) { + double hannWindowWeight = findHannWeight(N, x); + double sincWeight; + + x -= N/2.0; + if (x > 1e-9 || x < -1e-9) { + sincWeight = sin(M_PI*x)/(M_PI*x); + } else { + sincWeight = 1.0; + } + return hannWindowWeight*sincWeight; + } + + int main() { + double x; + int i; + int N = 12; + + for (i = 0, x = 0.0; x <= N; x += 0.02, i++) { + printf("%u %d\n", i, (int)(SHRT_MAX*findSincCoefficient(N, x))); + } + return 0; + } +*/ + +#define CLAMP(val, min, max) \ + ((val) < (min) ? (min) : (val) > (max) ? (max) : (val)) + +/* The number of points to use in the sinc FIR filter for resampling. */ +#define SINC_FILTER_POINTS \ + 12 /* I am not able to hear improvement with higher N. */ +#define SINC_TABLE_SIZE 601 + +/* Lookup table for windowed sinc function of SINC_FILTER_POINTS points. */ +static short sincTable[SINC_TABLE_SIZE] = { + 0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2, + -3, -4, -6, -7, -9, -10, -12, -14, -17, -19, -21, + -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48, + -50, -51, -52, -53, -53, -53, -52, -50, -48, -46, -43, + -39, -34, -29, -22, -16, -8, 0, 9, 19, 29, 41, + 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200, + 215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348, + 357, 363, 369, 372, 374, 375, 373, 369, 363, 355, 345, + 332, 318, 300, 281, 259, 234, 208, 178, 147, 113, 77, + 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426, + -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951, + -989, -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151, + -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728, + -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342, + 462, 584, 708, 833, 958, 1084, 1209, 1333, 1455, 1575, 1693, + 1807, 1916, 2022, 2122, 2216, 2304, 2384, 2457, 2522, 2579, 2625, + 2663, 2689, 2706, 2711, 2705, 2687, 2657, 2614, 2559, 2491, 2411, + 2317, 2211, 2092, 1960, 1815, 1658, 1489, 1308, 1115, 912, 698, + 474, 241, 0, -249, -506, -769, -1037, -1310, -1586, -1864, -2144, + -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291, -4529, -4757, -4972, + -5174, -5360, -5531, -5685, -5819, -5935, -6029, -6101, -6150, -6175, -6175, + -6149, -6096, -6015, -5905, -5767, -5599, -5401, -5172, -4912, -4621, -4298, + -3944, -3558, -3141, -2693, -2214, -1705, -1166, -597, 0, 625, 1277, + 1955, 2658, 3386, 4135, 4906, 5697, 6506, 7332, 8173, 9027, 9893, + 10769, 11654, 12544, 13439, 14335, 15232, 16128, 17019, 17904, 18782, 19649, + 20504, 21345, 22170, 22977, 23763, 24527, 25268, 25982, 26669, 27327, 27953, + 28547, 29107, 29632, 30119, 30569, 30979, 31349, 31678, 31964, 32208, 32408, + 32565, 32677, 32744, 32767, 32744, 32677, 32565, 32408, 32208, 31964, 31678, + 31349, 30979, 30569, 30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982, + 25268, 24527, 23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019, + 16128, 15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332, + 6506, 5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597, + -1166, -1705, -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172, + -5401, -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101, + -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529, -4291, + -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864, -1586, -1310, + -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308, + 1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614, + 2657, 2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457, + 2384, 2304, 2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333, + 1209, 1084, 958, 833, 708, 584, 462, 342, 225, 111, 0, + -107, -210, -309, -403, -492, -576, -655, -728, -796, -857, -913, + -963, -1007, -1046, -1078, -1105, -1125, -1141, -1151, -1155, -1154, -1149, + -1138, -1123, -1104, -1080, -1053, -1023, -989, -951, -912, -870, -825, + -779, -731, -682, -632, -581, -530, -478, -426, -375, -324, -274, + -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178, + 208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369, + 373, 375, 374, 372, 369, 363, 357, 348, 339, 328, 317, + 304, 291, 276, 262, 247, 231, 215, 200, 184, 168, 152, + 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9, + 0, -8, -16, -22, -29, -34, -39, -43, -46, -48, -50, + -52, -53, -53, -53, -52, -51, -50, -48, -47, -44, -42, + -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14, + -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1, + 0, 0, 0, 0, 0, 0, 0}; + +/* These functions allocate out of a static array rather than calling + calloc/realloc/free if the NO_MALLOC flag is defined. Otherwise, call + calloc/realloc/free as usual. This is useful for running on small + microcontrollers. */ +#ifndef SONIC_NO_MALLOC + +/* Just call calloc. */ +static void* sonicCalloc(int num, int size) { return calloc(num, size); } + +/* Just call realloc */ +static void* sonicRealloc(void* p, int oldNum, int newNum, int size) { + return realloc(p, newNum * size); +} + +/* Just call free. */ +static void sonicFree(void* p) { free(p); } + +#else + +#ifndef SONIC_MAX_MEMORY +/* Large enough for speedup/slowdown at 8KHz, 16-bit mono samples/second. */ +#define SONIC_MAX_MEMORY (16 * 1024) #endif + +/* This static buffer is used to hold data allocated for the sonicStream struct + and its buffers. There should never be more than one sonicStream in use at a + time when using SONIC_NO_MALLOC mode. Calls to realloc move the data to the + end of memoryBuffer. Calls to free reset the memory buffer to empty. */ +static void* + memoryBufferAligned[(SONIC_MAX_MEMORY + sizeof(void) - 1) / sizeof(void*)]; +static unsigned char* memoryBuffer = (unsigned char*)memoryBufferAligned; +static int memoryBufferPos = 0; + +/* Allocate elements from a static memory buffer. */ +static void* sonicCalloc(int num, int size) { + int len = num * size; + + if (memoryBufferPos + len > SONIC_MAX_MEMORY) { + return 0; + } + unsigned char* p = memoryBuffer + memoryBufferPos; + memoryBufferPos += len; + memset(p, 0, len); + return p; +} + +/* Preferably, SONIC_MAX_MEMORY has been set large enough that this is never + * called. */ +static void* sonicRealloc(void* p, int oldNum, int newNum, int size) { + if (newNum <= oldNum) { + return p; + } + void* newBuffer = sonicCalloc(newNum, size); + if (newBuffer == NULL) { + return NULL; + } + memcpy(newBuffer, p, oldNum * size); + return newBuffer; +} + +/* Reset memoryBufferPos to 0. We asssume all data is freed at the same time. + */ +static void sonicFree(void* p) { memoryBufferPos = 0; } + #endif -#include "sonic.h" struct sonicStreamStruct { - short *inputBuffer; - short *outputBuffer; - short *pitchBuffer; - short *downSampleBuffer; - float speed; - float volume; - float pitch; - float rate; - int oldRatePosition; - int newRatePosition; - int useChordPitch; - 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 prevMinDiff; - float remainingSamplesForSkip; - float remainingSamplesForInsert; +#ifdef SONIC_SPECTROGRAM + sonicSpectrogram spectrogram; +#endif /* SONIC_SPECTROGRAM */ + short* inputBuffer; + short* outputBuffer; + short* pitchBuffer; + short* downSampleBuffer; + void* userData; + float speed; + float volume; + float pitch; + float rate; + /* The point of the following 3 new variables is to gracefully handle rapidly + changing input speed. + + samplePeriod is just 1.0/sampleRate. It is used in accumulating + inputPlayTime, which is how long we expect the total time should be to play + the current input samples in the input buffer. timeError keeps track of + the error in play time created when playing < 2.0X speed, where we either + insert or delete a whole pitch period. This can cause the output generated + from the input to be off in play time by up to a pitch period. timeError + replaces PICOLA's concept of the number of samples to play unmodified after + a pitch period insertion or deletion. If speeding up, and the error is >= + 0.0, then remove a pitch period, and play samples unmodified until + timeError is >= 0 again. If slowing down, and the error is <= 0.0, + then add a pitch period, and play samples unmodified until timeError is <= + 0 again. */ + float samplePeriod; /* How long each output sample takes to play. */ + /* How long we expect the entire input buffer to take to play. */ + float inputPlayTime; + /* The difference in when the latest output sample was played vs when we + * wanted. */ + float timeError; + int oldRatePosition; + int newRatePosition; + 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 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); +/* Attach user data to the stream. */ +void sonicSetUserData(sonicStream stream, void* userData) { + stream->userData = userData; +} + +/* Retrieve user data attached to the stream. */ +void* sonicGetUserData(sonicStream stream) { return stream->userData; } + +#ifdef SONIC_SPECTROGRAM + +/* Compute a spectrogram on the fly. */ +void sonicComputeSpectrogram(sonicStream stream) { + stream->spectrogram = sonicCreateSpectrogram(stream->sampleRate); + /* Force changeSpeed to be called to compute the spectrogram. */ + sonicSetSpeed(stream, 2.0); } -*/ + +/* Get the spectrogram. */ +sonicSpectrogram sonicGetSpectrogram(sonicStream stream) { + return stream->spectrogram; +} + +#endif /* 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; - } +static void scaleSamples(short* samples, int numSamples, float volume) { + /* This is 24-bit integer and 8-bit fraction fixed-point representation. */ + int fixedPointVolume = volume * 256.0f; + int value; + + while (numSamples--) { + value = (*samples * fixedPointVolume) >> 8; + 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; -} +float sonicGetSpeed(sonicStream stream) { return stream->speed; } /* Set the speed of the stream. */ -void sonicSetSpeed( - sonicStream stream, - float speed) -{ - stream->speed = speed; +void sonicSetSpeed(sonicStream stream, float speed) { + stream->speed = CLAMP(speed, SONIC_MIN_SPEED, SONIC_MAX_SPEED); } /* Get the pitch of the stream. */ -float sonicGetPitch( - sonicStream stream) -{ - return stream->pitch; -} +float sonicGetPitch(sonicStream stream) { return stream->pitch; } /* Set the pitch of the stream. */ -void sonicSetPitch( - sonicStream stream, - float pitch) -{ - stream->pitch = pitch; +void sonicSetPitch(sonicStream stream, float pitch) { + stream->pitch = CLAMP(pitch, SONIC_MIN_PITCH_SETTING, SONIC_MAX_PITCH_SETTING); } /* Get the rate of the stream. */ -float sonicGetRate( - sonicStream stream) -{ - return stream->rate; -} +float sonicGetRate(sonicStream stream) { return stream->rate; } -/* Set the playback rate of the stream. This scales pitch and speed at the same time. */ -void sonicSetRate( - sonicStream stream, - float rate) -{ - stream->rate = rate; +/* Set the playback rate of the stream. This scales pitch and speed at the same + time. */ +void sonicSetRate(sonicStream stream, float rate) { + stream->rate = CLAMP(rate, SONIC_MIN_RATE, SONIC_MAX_RATE); - stream->oldRatePosition = 0; - stream->newRatePosition = 0; + stream->oldRatePosition = 0; + stream->newRatePosition = 0; } -/* Get the vocal chord pitch setting. */ -int sonicGetChordPitch( - sonicStream stream) -{ - return stream->useChordPitch; -} +/* DEPRECATED. Get the vocal chord pitch setting. */ +int sonicGetChordPitch(sonicStream stream) { return 0; } -/* Set the vocal chord mode for pitch computation. Default is off. */ -void sonicSetChordPitch( - sonicStream stream, - int useChordPitch) -{ - stream->useChordPitch = useChordPitch; -} +/* DEPRECATED. Set the vocal chord mode for pitch computation. Default is off. + */ +void sonicSetChordPitch(sonicStream stream, int useChordPitch) {} /* Get the quality setting. */ -int sonicGetQuality( - sonicStream stream) -{ - return stream->quality; -} +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; +/* Set the "quality". Default 0 is virtually as good as 1, but very much + faster. */ +void sonicSetQuality(sonicStream stream, int quality) { + stream->quality = quality != 0? 1 : 0; } /* Get the scaling factor of the stream. */ -float sonicGetVolume( - sonicStream stream) -{ - return stream->volume; -} +float sonicGetVolume(sonicStream stream) { return stream->volume; } /* Set the scaling factor of the stream. */ -void sonicSetVolume( - sonicStream stream, - float volume) -{ - stream->volume = volume; +void sonicSetVolume(sonicStream stream, float volume) { + stream->volume = CLAMP(volume, SONIC_MIN_VOLUME, SONIC_MAX_VOLUME); } /* Free stream buffers. */ -static void freeStreamBuffers( - 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); - } +static void freeStreamBuffers(sonicStream stream) { + if (stream->inputBuffer != NULL) { + sonicFree(stream->inputBuffer); + } + if (stream->outputBuffer != NULL) { + sonicFree(stream->outputBuffer); + } + if (stream->pitchBuffer != NULL) { + sonicFree(stream->pitchBuffer); + } + if (stream->downSampleBuffer != NULL) { + sonicFree(stream->downSampleBuffer); + } } /* Destroy the sonic stream. */ -void sonicDestroyStream( - sonicStream stream) -{ - freeStreamBuffers(stream); - free(stream); +void sonicDestroyStream(sonicStream stream) { +#ifdef SONIC_SPECTROGRAM + if (stream->spectrogram != NULL) { + sonicDestroySpectrogram(stream->spectrogram); + } +#endif /* SONIC_SPECTROGRAM */ + freeStreamBuffers(stream); + sonicFree(stream); +} + +/* Compute the number of samples to skip to down-sample the input. */ +static int computeSkip(sonicStream stream, int sampleRate) { + int skip = 1; + if (sampleRate > SONIC_AMDF_FREQ && stream->quality == 0) { + skip = sampleRate / SONIC_AMDF_FREQ; + } + return skip; } /* Allocate stream buffers. */ -static int allocateStreamBuffers( - sonicStream stream, - int sampleRate, - int numChannels) -{ - int minPeriod = sampleRate/SONIC_MAX_PITCH; - int maxPeriod = sampleRate/SONIC_MIN_PITCH; - int maxRequired = 2*maxPeriod; - - stream->inputBufferSize = maxRequired; - stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); - if(stream->inputBuffer == NULL) { - sonicDestroyStream(stream); - return 0; - } - stream->outputBufferSize = maxRequired; - stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); - if(stream->outputBuffer == NULL) { - sonicDestroyStream(stream); - return 0; - } - stream->pitchBufferSize = maxRequired; - stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); - if(stream->pitchBuffer == NULL) { - sonicDestroyStream(stream); - return 0; - } - stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short)); - if(stream->downSampleBuffer == NULL) { - sonicDestroyStream(stream); - return 0; - } - stream->sampleRate = sampleRate; - stream->numChannels = numChannels; - stream->oldRatePosition = 0; - stream->newRatePosition = 0; - stream->minPeriod = minPeriod; - stream->maxPeriod = maxPeriod; - stream->maxRequired = maxRequired; - stream->prevPeriod = 0; - return 1; +static int allocateStreamBuffers(sonicStream stream, int sampleRate, + int numChannels) { + int minPeriod = sampleRate / SONIC_MAX_PITCH; + int maxPeriod = sampleRate / SONIC_MIN_PITCH; + int maxRequired = 2 * maxPeriod; + int skip = computeSkip(stream, sampleRate); + + /* Allocate 25% more than needed so we hopefully won't grow. */ + stream->inputBufferSize = maxRequired + (maxRequired >> 2); + + stream->inputBuffer = + (short*)sonicCalloc(stream->inputBufferSize, sizeof(short) * numChannels); + if (stream->inputBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + /* Allocate 25% more than needed so we hopefully won't grow. */ + stream->outputBufferSize = maxRequired + (maxRequired >> 2); + stream->outputBuffer = (short*)sonicCalloc(stream->outputBufferSize, + sizeof(short) * numChannels); + if (stream->outputBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + /* Allocate 25% more than needed so we hopefully won't grow. */ + stream->pitchBufferSize = maxRequired + (maxRequired >> 2); + stream->pitchBuffer = + (short*)sonicCalloc(stream->pitchBufferSize, sizeof(short) * numChannels); + if (stream->pitchBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + int downSampleBufferSize = (maxRequired + skip - 1) / skip; + stream->downSampleBuffer = + (short*)sonicCalloc(downSampleBufferSize, sizeof(short)); + if (stream->downSampleBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + stream->sampleRate = sampleRate; + stream->samplePeriod = 1.0 / sampleRate; + stream->numChannels = numChannels; + stream->oldRatePosition = 0; + stream->newRatePosition = 0; + stream->minPeriod = minPeriod; + stream->maxPeriod = maxPeriod; + stream->maxRequired = maxRequired; + stream->prevPeriod = 0; + return 1; } /* 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)); - - if(stream == NULL) { - return NULL; - } - if(!allocateStreamBuffers(stream, sampleRate, numChannels)) { - return NULL; - } - stream->speed = 1.0f; - stream->pitch = 1.0f; - stream->volume = 1.0f; - stream->rate = 1.0f; - stream->oldRatePosition = 0; - stream->newRatePosition = 0; - stream->useChordPitch = 0; - stream->quality = 0; - stream->remainingSamplesForSkip = 0.0f; - stream->remainingSamplesForInsert = 0.0f; - return stream; +sonicStream sonicCreateStream(int sampleRate, int numChannels) { + sonicStream stream = + (sonicStream)sonicCalloc(1, sizeof(struct sonicStreamStruct)); + + sampleRate = CLAMP(sampleRate, SONIC_MIN_SAMPLE_RATE, SONIC_MAX_SAMPLE_RATE); + numChannels = CLAMP(numChannels, SONIC_MIN_CHANNELS, SONIC_MAX_CHANNELS); + if (stream == NULL) { + return NULL; + } + if (!allocateStreamBuffers(stream, sampleRate, numChannels)) { + return NULL; + } + stream->speed = 1.0f; + stream->pitch = 1.0f; + stream->volume = 1.0f; + stream->rate = 1.0f; + stream->oldRatePosition = 0; + stream->newRatePosition = 0; + stream->quality = 0; + return stream; } /* Get the sample rate of the stream. */ -int sonicGetSampleRate( - sonicStream stream) -{ - return stream->sampleRate; -} - -/* Set the sample rate of the stream. This will cause samples buffered in the stream to - be lost. */ -void sonicSetSampleRate( - sonicStream stream, - int sampleRate) -{ - freeStreamBuffers(stream); - allocateStreamBuffers(stream, sampleRate, stream->numChannels); +int sonicGetSampleRate(sonicStream stream) { return stream->sampleRate; } + +/* Set the sample rate of the stream. This will cause samples buffered in the + stream to be lost. */ +void sonicSetSampleRate(sonicStream stream, int sampleRate) { + sampleRate = CLAMP(sampleRate, SONIC_MIN_SAMPLE_RATE, SONIC_MAX_SAMPLE_RATE); + freeStreamBuffers(stream); + allocateStreamBuffers(stream, sampleRate, stream->numChannels); } /* Get the number of channels. */ -int sonicGetNumChannels( - sonicStream stream) -{ - return stream->numChannels; -} - -/* Set the num channels of the stream. This will cause samples buffered in the stream to - be lost. */ -void sonicSetNumChannels( - sonicStream stream, - int numChannels) -{ - freeStreamBuffers(stream); - allocateStreamBuffers(stream, stream->sampleRate, numChannels); +int sonicGetNumChannels(sonicStream stream) { return stream->numChannels; } + +/* Set the num channels of the stream. This will cause samples buffered in the + stream to be lost. */ +void sonicSetNumChannels(sonicStream stream, int numChannels) { + numChannels = CLAMP(numChannels, SONIC_MIN_CHANNELS, SONIC_MAX_CHANNELS); + freeStreamBuffers(stream); + allocateStreamBuffers(stream, stream->sampleRate, numChannels); } /* 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; +static int enlargeOutputBufferIfNeeded(sonicStream stream, int numSamples) { + int outputBufferSize = stream->outputBufferSize; + + if (stream->numOutputSamples + numSamples > outputBufferSize) { + stream->outputBufferSize += (outputBufferSize >> 1) + numSamples; + stream->outputBuffer = (short*)sonicRealloc( + stream->outputBuffer, outputBufferSize, 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; +static int enlargeInputBufferIfNeeded(sonicStream stream, int numSamples) { + int inputBufferSize = stream->inputBufferSize; + + if (stream->numInputSamples + numSamples > inputBufferSize) { + stream->inputBufferSize += (inputBufferSize >> 1) + numSamples; + stream->inputBuffer = (short*)sonicRealloc( + stream->inputBuffer, inputBufferSize, stream->inputBufferSize, + sizeof(short) * stream->numChannels); + if (stream->inputBuffer == NULL) { + return 0; + } + } + return 1; +} + +/* Update stream->numInputSamples, and update stream->inputPlayTime. Call this + whenever adding samples to the input buffer, to keep track of total expected + input play time accounting. */ +static void updateNumInputSamples(sonicStream stream, int numSamples) { + float speed = stream->speed / stream->pitch; + + stream->numInputSamples += numSamples; + stream->inputPlayTime += numSamples * stream->samplePeriod / speed; } /* 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; +static int addFloatSamplesToInputBuffer(sonicStream stream, + const 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; + } + updateNumInputSamples(stream, 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; +static int addShortSamplesToInputBuffer(sonicStream stream, + const 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); + updateNumInputSamples(stream, 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; +static int addUnsignedCharSamplesToInputBuffer(sonicStream stream, + const 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; + } + updateNumInputSamples(stream, 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; +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); + } + /* If we play 3/4ths of the samples, then the expected play time of the + remaining samples is 1/4th of the original expected play time. */ + stream->inputPlayTime = + (stream->inputPlayTime * remainingSamples) / stream->numInputSamples; + 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 1; +/* Copy from the input buffer to the output buffer, and remove the samples from + the input buffer. */ +static int copyInputToOutput(sonicStream stream, int numSamples) { + if (!enlargeOutputBufferIfNeeded(stream, numSamples)) { + return 0; + } + memcpy(stream->outputBuffer + stream->numOutputSamples * stream->numChannels, + stream->inputBuffer, numSamples * sizeof(short) * stream->numChannels); + stream->numOutputSamples += numSamples; + removeInputSamples(stream, numSamples); + return 1; } -/* 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; +/* Copy from samples 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 1; } /* 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; +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 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; +/* 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; - float rate = stream->rate*stream->pitch; - int expectedOutputSamples = stream->numOutputSamples + - (int)((remainingSamples/speed + stream->numPitchSamples)/rate + 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; + 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; + float rate = stream->rate * stream->pitch; + int expectedOutputSamples = + stream->numOutputSamples + + (int)((remainingSamples / speed + stream->numPitchSamples) / rate + 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->inputPlayTime = 0.0f; + stream->timeError = 0.0f; + stream->numPitchSamples = 0; + return 1; } /* Return the number of samples in the output buffer */ -int sonicSamplesAvailable( - sonicStream stream) -{ - return stream->numOutputSamples; +int sonicSamplesAvailable(sonicStream stream) { + return stream->numOutputSamples; } /* If skip is greater than one, average skip samples together 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; - } +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; +static int findPitchPeriodInRange(short* samples, int minPeriod, int maxPeriod, + int* retMinDiff, int* retMaxDiff) { + int period, bestPeriod = 0, worstPeriod = 255; + short* s; + short* p; + short 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 (bestPeriod == 0 || 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 - approximated 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 || stream->prevPeriod == 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; + approximated by the previous pitch period estimate. Try to detect this case. + */ +static int prevPeriodBetter(sonicStream stream, int minDiff, int maxDiff, + int preferNewPeriod) { + if (minDiff == 0 || stream->prevPeriod == 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->prevPeriod = period; - return retPeriod; + multiple ways to get a good answer. This version uses Average Magnitude + Difference Function (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 minDiff, maxDiff, retPeriod; + int skip = computeSkip(stream, stream->sampleRate); + int period; + + 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, minDiff, maxDiff, preferNewPeriod)) { + retPeriod = stream->prevPeriod; + } else { + retPeriod = period; + } + stream->prevMinDiff = minDiff; + 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++) { +static void overlapAdd(int numSamples, int numChannels, short* out, + short* rampDown, short* rampUp) { + short* o; + short* u; + short* 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; + float ratio = sin(t * M_PI / (2 * numSamples)); + *o = *d * (1.0f - ratio) + *u * ratio; #else - *o = (*d*(numSamples - t) + *u*t)/numSamples; + *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; - } + o += numChannels; + d += numChannels; + u += numChannels; } + } } /* Just move the new samples in the output buffer to the pitch buffer */ -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; +static int moveNewSamplesToPitchBuffer(sonicStream stream, + int originalNumOutputSamples) { + int numSamples = stream->numOutputSamples - originalNumOutputSamples; + int numChannels = stream->numChannels; + int pitchBufferSize = stream->pitchBufferSize; + + if (stream->numPitchSamples + numSamples > pitchBufferSize) { + stream->pitchBufferSize += (pitchBufferSize >> 1) + numSamples; + stream->pitchBuffer = (short*)sonicRealloc( + stream->pitchBuffer, pitchBufferSize, stream->pitchBufferSize, + sizeof(short) * numChannels); + } + 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; +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; +/* Approximate the sinc function times a Hann window from the sinc table. */ +static int findSincCoefficient(int i, int ratio, int width) { + int lobePoints = (SINC_TABLE_SIZE - 1) / SINC_FILTER_POINTS; + int left = i * lobePoints + (ratio * lobePoints) / width; + int right = left + 1; + int position = i * lobePoints * width + ratio * lobePoints - left * width; + int leftVal = sincTable[left]; + int rightVal = sincTable[right]; + + return ((leftVal * (width - position) + rightVal * position) << 1) / width; } +/* Return 1 if value >= 0, else -1. This represents the sign of value. */ +static int getSign(int value) { return value >= 0 ? 1 : -1; } + /* Interpolate the new output sample. */ -static short interpolate( - sonicStream stream, - short *in, - int oldSampleRate, - int newSampleRate) -{ - short left = *in; - short right = in[stream->numChannels]; - int position = stream->newRatePosition*oldSampleRate; - int leftPosition = stream->oldRatePosition*newSampleRate; - int rightPosition = (stream->oldRatePosition + 1)*newSampleRate; - int ratio = rightPosition - position; - int width = rightPosition - leftPosition; - - return (ratio*left + (width - ratio)*right)/width; +static short interpolate(sonicStream stream, short* in, int oldSampleRate, + int newSampleRate) { + /* Compute N-point sinc FIR-filter here. Clip rather than overflow. */ + int i; + int total = 0; + int position = stream->newRatePosition * oldSampleRate; + int leftPosition = stream->oldRatePosition * newSampleRate; + int rightPosition = (stream->oldRatePosition + 1) * newSampleRate; + int ratio = rightPosition - position - 1; + int width = rightPosition - leftPosition; + int weight, value; + int oldSign; + int overflowCount = 0; + + for (i = 0; i < SINC_FILTER_POINTS; i++) { + weight = findSincCoefficient(i, ratio, width); + value = in[i * stream->numChannels] * weight; + oldSign = getSign(total); + total += value; + if (oldSign != getSign(total) && getSign(value) == oldSign) { + /* We must have overflowed. This can happen with a sinc filter. */ + overflowCount += oldSign; + } + } + /* It is better to clip than to wrap if there was a overflow. */ + if (overflowCount > 0) { + return SHRT_MAX; + } else if (overflowCount < 0) { + return SHRT_MIN; + } + return total >> 16; } -/* Change the rate. */ -static int adjustRate( - sonicStream stream, - float rate, - int originalNumOutputSamples) -{ - int newSampleRate = stream->sampleRate/rate; - int oldSampleRate = stream->sampleRate; - int numChannels = stream->numChannels; - int position = 0; - short *in, *out; - int i; - - /* Set these values to help with the integer math */ - while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { - newSampleRate >>= 1; - oldSampleRate >>= 1; - } - if(stream->numOutputSamples == originalNumOutputSamples) { - return 1; - } - if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) { - return 0; - } - /* Leave at least one pitch sample in the buffer */ - for(position = 0; position < stream->numPitchSamples - 1; position++) { - while((stream->oldRatePosition + 1)*newSampleRate > - stream->newRatePosition*oldSampleRate) { - if(!enlargeOutputBufferIfNeeded(stream, 1)) { - return 0; - } - out = stream->outputBuffer + stream->numOutputSamples*numChannels; - in = stream->pitchBuffer + position; - for(i = 0; i < numChannels; i++) { - *out++ = interpolate(stream, in, oldSampleRate, newSampleRate); - in++; - } - stream->newRatePosition++; - stream->numOutputSamples++; - } - stream->oldRatePosition++; - if(stream->oldRatePosition == oldSampleRate) { - stream->oldRatePosition = 0; - if(stream->newRatePosition != newSampleRate) { - fprintf(stderr, - "Assertion failed: stream->newRatePosition != newSampleRate\n"); - exit(1); - } - stream->newRatePosition = 0; - } - } - removePitchSamples(stream, position); +/* Change the rate. Interpolate with a sinc FIR filter using a Hann window. */ +static int adjustRate(sonicStream stream, float rate, + int originalNumOutputSamples) { + int newSampleRate = stream->sampleRate / rate; + int oldSampleRate = stream->sampleRate; + int numChannels = stream->numChannels; + int position; + short *in, *out; + int i; + int N = SINC_FILTER_POINTS; + + /* Set these values to help with the integer math */ + while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate >>= 1; + oldSampleRate >>= 1; + } + if (stream->numOutputSamples == originalNumOutputSamples) { return 1; + } + if (!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) { + return 0; + } + /* Leave at least N pitch sample in the buffer */ + for (position = 0; position < stream->numPitchSamples - N; position++) { + while ((stream->oldRatePosition + 1) * newSampleRate > + stream->newRatePosition * oldSampleRate) { + if (!enlargeOutputBufferIfNeeded(stream, 1)) { + return 0; + } + out = stream->outputBuffer + stream->numOutputSamples * numChannels; + in = stream->pitchBuffer + position * numChannels; + for (i = 0; i < numChannels; i++) { + *out++ = interpolate(stream, in, oldSampleRate, newSampleRate); + in++; + } + stream->newRatePosition++; + stream->numOutputSamples++; + } + stream->oldRatePosition++; + if (stream->oldRatePosition == oldSampleRate) { + stream->oldRatePosition = 0; + stream->newRatePosition = 0; + } + } + 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) { - int upNewSamples = ceil((float)period / (speed - 1.0f)); - int downNewSamples = floor((float)period / (speed - 1.0f)); - if (stream->remainingSamplesForSkip < 1) { - newSamples = downNewSamples; - stream->remainingSamplesForSkip += (float)period / (speed - 1.0f) - downNewSamples; - } else { - newSamples = upNewSamples; - stream->remainingSamplesForSkip += (float)period / (speed - 1.0f) - upNewSamples; - } - } else { - 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; +/* Skip over a pitch period. Return the number of output samples. */ +static int skipPitchPeriod(sonicStream stream, short* samples, float speed, + int period) { + long newSamples; + int numChannels = stream->numChannels; + + if (speed >= 2.0f) { + /* For speeds >= 2.0, we skip over a portion of each pitch period rather + than dropping whole pitch periods. */ + newSamples = period / (speed - 1.0f); + } else { + newSamples = period; + } + 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) { - int upNewSamples = ceil((float)period * speed / (1.0f - speed)); - int downNewSamples = floor((float)period * speed / (1.0f - speed)); - if (stream->remainingSamplesForInsert < 1) { - newSamples = downNewSamples; - stream->remainingSamplesForInsert += (float)period * speed / (1.0f - speed) - downNewSamples; - } else { - newSamples = upNewSamples; - stream->remainingSamplesForInsert += (float)period * speed / (1.0f - speed) - upNewSamples; - } - } 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; +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; + } + 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; +/* PICOLA copies input to output until the total output samples == consumed + input samples * speed. */ +static int copyUnmodifiedSamples(sonicStream stream, short* samples, + float speed, int position, int* newSamples) { + int availableSamples = stream->numInputSamples - position; + float inputToCopyFloat = + 1 - stream->timeError * speed / (stream->samplePeriod * (speed - 1.0)); + + *newSamples = inputToCopyFloat > availableSamples ? availableSamples + : (int)inputToCopyFloat; + if (!copyToOutput(stream, samples, *newSamples)) { + return 0; + } + stream->timeError += + *newSamples * stream->samplePeriod * (speed - 1.0) / speed; + 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; - float rate = stream->rate; - - if(!stream->useChordPitch) { - rate *= stream->pitch; - } - if(speed > 1.00001 || speed < 0.99999) { - changeSpeed(stream, speed); + we fail to resize an input or output buffer. */ +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 { + samples = stream->inputBuffer + position * stream->numChannels; + if ((speed > 1.0f && speed < 2.0f && stream->timeError < 0.0f) || + (speed < 1.0f && speed > 0.5f && stream->timeError > 0.0f)) { + /* Deal with the case where PICOLA is still copying input samples to + output unmodified, */ + if (!copyUnmodifiedSamples(stream, samples, speed, position, + &newSamples)) { + return 0; + } + position += newSamples; } else { - if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) { - return 0; - } - stream->numInputSamples = 0; - } - if(stream->useChordPitch) { - if(stream->pitch != 1.0f) { - if(!adjustPitch(stream, originalNumOutputSamples)) { - return 0; - } - } - } else if(rate != 1.0f) { - if(!adjustRate(stream, rate, originalNumOutputSamples)) { - return 0; - } - } - if(stream->volume != 1.0f) { - /* Adjust output volume. */ - scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels, - (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels, - stream->volume); + /* We are in the remaining cases, either inserting/removing a pitch period + for speed < 2.0X, or a portion of one for speed >= 2.0X. */ + period = findPitchPeriod(stream, samples, 1); +#ifdef SONIC_SPECTROGRAM + if (stream->spectrogram != NULL) { + sonicAddPitchPeriodToSpectrogram(stream->spectrogram, samples, period, + stream->numChannels); + newSamples = period; + position += period; + } else +#endif /* SONIC_SPECTROGRAM */ + if (speed > 1.0) { + newSamples = skipPitchPeriod(stream, samples, speed, period); + position += period + newSamples; + if (speed < 2.0) { + stream->timeError += newSamples * stream->samplePeriod - + (period + newSamples) * stream->inputPlayTime / + stream->numInputSamples; + } + } else { + newSamples = insertPitchPeriod(stream, samples, speed, period); + position += newSamples; + if (speed > 0.5) { + stream->timeError += + (period + newSamples) * stream->samplePeriod - + newSamples * stream->inputPlayTime / stream->numInputSamples; + } + } + 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 rate = stream->rate * stream->pitch; + float localSpeed; + + if (stream->numInputSamples == 0) { return 1; + } + localSpeed = + stream->numInputSamples * stream->samplePeriod / stream->inputPlayTime; + if (localSpeed > 1.00001 || localSpeed < 0.99999) { + changeSpeed(stream, localSpeed); + } else { + if (!copyInputToOutput(stream, stream->numInputSamples)) { + return 0; + } + } + if (rate != 1.0f) { + if (!adjustRate(stream, rate, 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); +int sonicWriteFloatToStream(sonicStream stream, const 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); +int sonicWriteShortToStream(sonicStream stream, const 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); +/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to + float conversion for you. */ +int sonicWriteUnsignedCharToStream(sonicStream stream, + const 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 rate, - float volume, - int useChordPitch, - int sampleRate, - int numChannels) -{ - sonicStream stream = sonicCreateStream(sampleRate, numChannels); - - sonicSetSpeed(stream, speed); - sonicSetPitch(stream, pitch); - sonicSetRate(stream, rate); - sonicSetVolume(stream, volume); - sonicSetChordPitch(stream, useChordPitch); - 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 sonicChangeFloatSpeed(float* samples, int numSamples, float speed, + float pitch, float rate, float volume, + int useChordPitch, int sampleRate, int numChannels) { + sonicStream stream = sonicCreateStream(sampleRate, numChannels); + + sonicSetSpeed(stream, speed); + sonicSetPitch(stream, pitch); + sonicSetRate(stream, rate); + 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 rate, - float volume, - int useChordPitch, - int sampleRate, - int numChannels) -{ - sonicStream stream = sonicCreateStream(sampleRate, numChannels); - - sonicSetSpeed(stream, speed); - sonicSetPitch(stream, pitch); - sonicSetRate(stream, rate); - sonicSetVolume(stream, volume); - sonicSetChordPitch(stream, useChordPitch); - sonicWriteShortToStream(stream, samples, numSamples); - sonicFlushStream(stream); - numSamples = sonicSamplesAvailable(stream); - sonicReadShortFromStream(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 rate, float volume, + int useChordPitch, int sampleRate, int numChannels) { + sonicStream stream = sonicCreateStream(sampleRate, numChannels); + + sonicSetSpeed(stream, speed); + sonicSetPitch(stream, pitch); + sonicSetRate(stream, rate); + sonicSetVolume(stream, volume); + sonicWriteShortToStream(stream, samples, numSamples); + sonicFlushStream(stream); + numSamples = sonicSamplesAvailable(stream); + sonicReadShortFromStream(stream, samples, numSamples); + sonicDestroyStream(stream); + return numSamples; } diff --git a/sonic/sonic.h b/sonic/sonic.h index 9b44e6809..a8b5fb5f1 100644 --- a/sonic/sonic.h +++ b/sonic/sonic.h @@ -1,3 +1,6 @@ +#ifndef SONIC_H_ +#define SONIC_H_ + /* Sonic library Copyright 2010 Bill Cox @@ -30,7 +33,7 @@ where t = 0 to newSamples - 1. For speed factors < 2X, the PICOLA algorithm is used. The above algorithm is first used to double the speed of one pitch period. Then, enough input is directly copied from the input to the output to achieve the desired -speed up facter, where 1.0 < speed < 2.0. The amount of data copied is derived: +speed up factor, where 1.0 < speed < 2.0. The amount of data copied is derived: speed = (2*period + length)/(period + length) speed*length + speed*period = 2*period + length @@ -47,24 +50,92 @@ For slow down factors below 0.5, no data is copied, and an algorithm similar to high speed factors is used. */ -#ifdef __cplusplus -extern "C" { -#endif - /* Uncomment this to use sin-wav based overlap add which in theory can improve sound quality slightly, at the expense of lots of floating point math. */ /* #define SONIC_USE_SIN */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef SONIC_INTERNAL +/* The following #define's are used to change the names of the routines defined + * here so that a new library (i.e. speedy) can reuse these names, and then call + * the original names. We do this for two reasons: 1) we don't want to change + * the original API, and 2) we want to add a shim, using the original names and + * still call these routines. + * + * Original users of this API and the libsonic library need to do nothing. The + * original behavior remains. + * + * A new user that add some additional functionality above this library (a shim) + * should #define SONIC_INTERNAL before including this file, undefine all these + * symbols and call the sonicIntXXX functions directly. + */ +#define sonicCreateStream sonicIntCreateStream +#define sonicDestroyStream sonicIntDestroyStream +#define sonicWriteFloatToStream sonicIntWriteFloatToStream +#define sonicWriteShortToStream sonicIntWriteShortToStream +#define sonicWriteUnsignedCharToStream sonicIntWriteUnsignedCharToStream +#define sonicReadFloatFromStream sonicIntReadFloatFromStream +#define sonicReadShortFromStream sonicIntReadShortFromStream +#define sonicReadUnsignedCharFromStream sonicIntReadUnsignedCharFromStream +#define sonicFlushStream sonicIntFlushStream +#define sonicSamplesAvailable sonicIntSamplesAvailable +#define sonicGetSpeed sonicIntGetSpeed +#define sonicSetSpeed sonicIntSetSpeed +#define sonicGetPitch sonicIntGetPitch +#define sonicSetPitch sonicIntSetPitch +#define sonicGetRate sonicIntGetRate +#define sonicSetRate sonicIntSetRate +#define sonicGetVolume sonicIntGetVolume +#define sonicSetVolume sonicIntSetVolume +#define sonicGetQuality sonicIntGetQuality +#define sonicSetQuality sonicIntSetQuality +#define sonicGetSampleRate sonicIntGetSampleRate +#define sonicSetSampleRate sonicIntSetSampleRate +#define sonicGetNumChannels sonicIntGetNumChannels +#define sonicGetUserData sonicIntGetUserData +#define sonicSetUserData sonicIntSetUserData +#define sonicSetNumChannels sonicIntSetNumChannels +#define sonicChangeFloatSpeed sonicIntChangeFloatSpeed +#define sonicChangeShortSpeed sonicIntChangeShortSpeed +#define sonicEnableNonlinearSpeedup sonicIntEnableNonlinearSpeedup +#define sonicSetDurationFeedbackStrength sonicIntSetDurationFeedbackStrength +#define sonicComputeSpectrogram sonicIntComputeSpectrogram +#define sonicGetSpectrogram sonicIntGetSpectrogram + +#endif /* SONIC_INTERNAL */ + /* This specifies the range of voice pitches we try to match. Note that if we go lower than 65, we could overflow in findPitchInRange */ +#ifndef SONIC_MIN_PITCH #define SONIC_MIN_PITCH 65 +#endif /* SONIC_MIN_PITCH */ +#ifndef SONIC_MAX_PITCH #define SONIC_MAX_PITCH 400 +#endif /* SONIC_MAX_PITCH */ + +/* The following values are used to clamp inputs such as speed to sane values. + */ +#define SONIC_MIN_VOLUME 0.01f +#define SONIC_MAX_VOLUME 100.0f +#define SONIC_MIN_SPEED 0.05f +#define SONIC_MAX_SPEED 20.0f +#define SONIC_MIN_PITCH_SETTING 0.05f +#define SONIC_MAX_PITCH_SETTING 20.0f +#define SONIC_MIN_RATE 0.05f +#define SONIC_MAX_RATE 20.0f +#define SONIC_MIN_SAMPLE_RATE 1000 +#define SONIC_MAX_SAMPLE_RATE 500000 +#define SONIC_MIN_CHANNELS 1 +#define SONIC_MAX_CHANNELS 32 /* These are used to down-sample some inputs to improve speed */ #define SONIC_AMDF_FREQ 4000 struct sonicStreamStruct; -typedef struct sonicStreamStruct *sonicStream; +typedef struct sonicStreamStruct* sonicStream; /* For all of the following functions, numChannels is multiplied by numSamples to determine the actual number of values read or returned. */ @@ -74,27 +145,36 @@ typedef struct sonicStreamStruct *sonicStream; sonicStream sonicCreateStream(int sampleRate, int numChannels); /* Destroy the sonic stream. */ void sonicDestroyStream(sonicStream stream); +/* Attach user data to the stream. */ +void sonicSetUserData(sonicStream stream, void *userData); +/* Retrieve user data attached to the stream. */ +void *sonicGetUserData(sonicStream stream); /* Use this to write floating point data to be speed up or down into the stream. - Values must be between -1 and 1. Return 0 if memory realloc failed, otherwise 1 */ -int sonicWriteFloatToStream(sonicStream stream, float *samples, int numSamples); + Values must be between -1 and 1. Return 0 if memory realloc failed, + otherwise 1 */ +int sonicWriteFloatToStream(sonicStream stream, const float* samples, int numSamples); /* Use this to write 16-bit data to be speed up or down into the stream. Return 0 if memory realloc failed, otherwise 1 */ -int sonicWriteShortToStream(sonicStream stream, short *samples, int numSamples); +int sonicWriteShortToStream(sonicStream stream, const short* samples, int numSamples); /* Use this to write 8-bit unsigned data to be speed up or down into the stream. Return 0 if memory realloc failed, otherwise 1 */ -int sonicWriteUnsignedCharToStream(sonicStream stream, unsigned char *samples, int numSamples); +int sonicWriteUnsignedCharToStream(sonicStream stream, const unsigned char* samples, + int numSamples); /* Use this to read floating point 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 sonicReadFloatFromStream(sonicStream stream, float* samples, + int maxSamples); /* Use this to read 16-bit 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); -/* Use this to read 8-bit unsigned 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 sonicReadShortFromStream(sonicStream stream, short* samples, + int maxSamples); +/* Use this to read 8-bit unsigned 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); /* 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. */ + has. No extra delay will be added to the output, but flushing in the middle + of words could introduce distortion. */ int sonicFlushStream(sonicStream stream); /* Return the number of samples in the output buffer */ int sonicSamplesAvailable(sonicStream stream); @@ -114,6 +194,8 @@ void sonicSetRate(sonicStream stream, float rate); float sonicGetVolume(sonicStream stream); /* Set the scaling factor of the stream. */ void sonicSetVolume(sonicStream stream, float volume); +/* Chord pitch is DEPRECATED. AFAIK, it was never used by anyone. These + functions still exist to avoid breaking existing code. */ /* Get the chord pitch setting. */ int sonicGetChordPitch(sonicStream stream); /* Set chord pitch mode on or off. Default is off. See the documentation @@ -121,27 +203,102 @@ int sonicGetChordPitch(sonicStream stream); void sonicSetChordPitch(sonicStream stream, int useChordPitch); /* Get the quality setting. */ int sonicGetQuality(sonicStream stream); -/* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */ +/* Set the "quality". Default 0 is virtually as good as 1, but very much + * faster. */ void sonicSetQuality(sonicStream stream, int quality); /* Get the sample rate of the stream. */ int sonicGetSampleRate(sonicStream stream); -/* Set the sample rate of the stream. This will drop any samples that have not been read. */ +/* Set the sample rate of the stream. This will drop any samples that have not + * been read. */ void sonicSetSampleRate(sonicStream stream, int sampleRate); /* Get the number of channels. */ int sonicGetNumChannels(sonicStream stream); -/* Set the number of channels. This will drop any samples that have not been read. */ +/* Set the number of channels. This will drop any samples that have not been + * read. */ void sonicSetNumChannels(sonicStream stream, int numChannels); /* This is a non-stream oriented interface to just change the speed of a sound sample. It works in-place on the sample array, so there must be at least - speed*numSamples available space in the array. Returns the new number of samples. */ -int sonicChangeFloatSpeed(float *samples, int numSamples, float speed, float pitch, - float rate, float volume, int useChordPitch, int sampleRate, int numChannels); + speed*numSamples available space in the array. Returns the new number of + samples. */ +int sonicChangeFloatSpeed(float* samples, int numSamples, float speed, + float pitch, float rate, float volume, + int useChordPitch, int sampleRate, int numChannels); /* This is a non-stream oriented interface to just change the speed of a sound sample. It works in-place on the sample array, so there must be at least - speed*numSamples available space in the array. Returns the new number of samples. */ -int sonicChangeShortSpeed(short *samples, int numSamples, float speed, float pitch, - float rate, float volume, int useChordPitch, int sampleRate, int numChannels); + speed*numSamples available space in the array. Returns the new number of + samples. */ +int sonicChangeShortSpeed(short* samples, int numSamples, float speed, + float pitch, float rate, float volume, + int useChordPitch, int sampleRate, int numChannels); + +#ifdef SONIC_SPECTROGRAM +/* +This code generates high quality spectrograms from sound samples, using +Time-Aliased-FFTs as described at: + + https://github.com/waywardgeek/spectrogram -#ifdef __cplusplus +Basically, two adjacent pitch periods are overlap-added to create a sound +sample that accurately represents the speech sound at that moment in time. +This set of samples is converted to a spetral line using an FFT, and the result +is saved as a single spectral line at that moment in time. The resulting +spectral lines vary in resolution (it is equal to the number of samples in the +pitch period), and the spacing of spectral lines also varies (proportional to +the numver of samples in the pitch period). + +To generate a bitmap, linear interpolation is used to render the grayscale +value at any particular point in time and frequency. +*/ + +#define SONIC_MAX_SPECTRUM_FREQ 5000 + +struct sonicSpectrogramStruct; +struct sonicBitmapStruct; +typedef struct sonicSpectrogramStruct* sonicSpectrogram; +typedef struct sonicBitmapStruct* sonicBitmap; + +/* sonicBitmap objects represent spectrograms as grayscale bitmaps where each + pixel is from 0 (black) to 255 (white). Bitmaps are rows*cols in size. + Rows are indexed top to bottom and columns are indexed left to right */ +struct sonicBitmapStruct { + unsigned char* data; + int numRows; + int numCols; +}; + +/* Enable coomputation of a spectrogram on the fly. */ +void sonicComputeSpectrogram(sonicStream stream); + +/* Get the spectrogram. */ +sonicSpectrogram sonicGetSpectrogram(sonicStream stream); + +/* Create an empty spectrogram. Called automatically if sonicComputeSpectrogram + has been called. */ +sonicSpectrogram sonicCreateSpectrogram(int sampleRate); + +/* Destroy the spectrotram. This is called automatically when calling + sonicDestroyStream. */ +void sonicDestroySpectrogram(sonicSpectrogram spectrogram); + +/* Convert the spectrogram to a bitmap. Caller must destroy bitmap when done. */ +sonicBitmap sonicConvertSpectrogramToBitmap(sonicSpectrogram spectrogram, + int numRows, int numCols); + +/* Destroy a bitmap returned by sonicConvertSpectrogramToBitmap. */ +void sonicDestroyBitmap(sonicBitmap bitmap); + +int sonicWritePGM(sonicBitmap bitmap, char* fileName); + +/* Add two pitch periods worth of samples to the spectrogram. There must be + 2*period samples. Time should advance one pitch period for each call to + this function. */ +void sonicAddPitchPeriodToSpectrogram(sonicSpectrogram spectrogram, + short* samples, int numSamples, + int numChannels); +#endif /* SONIC_SPECTROGRAM */ + +#ifdef __cplusplus } #endif + +#endif /* SONIC_H_ */ -- Gitee