Copyright 2010
Bill Cox
This file is part of the Sonic Library.
This file is licensed under the Apache 2.0 license.
*/
#include "sonic.h"
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
The following code was used to generate the following sinc lookup table.
#include <limits.h>
#include <math.h>
#include <stdio.h>
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 SINC_FILTER_POINTS \
12
#define SINC_TABLE_SIZE 601
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};
struct sonicStreamStruct {
#ifdef SONIC_SPECTROGRAM
sonicSpectrogram spectrogram;
#endif
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 avePower;
float remainingSamplesForSkip;
float remainingSamplesForInsert;
};
#ifdef SONIC_SPECTROGRAM
void sonicComputeSpectrogram(sonicStream stream) {
stream->spectrogram = sonicCreateSpectrogram(stream->sampleRate);
sonicSetSpeed(stream, 2.0);
}
sonicSpectrogram sonicGetSpectrogram(sonicStream stream) {
return stream->spectrogram;
}
#endif
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;
}
}
float sonicGetSpeed(sonicStream stream) { return stream->speed; }
void sonicSetSpeed(sonicStream stream, float speed) { stream->speed = speed; }
float sonicGetPitch(sonicStream stream) { return stream->pitch; }
void sonicSetPitch(sonicStream stream, float pitch) { stream->pitch = pitch; }
float sonicGetRate(sonicStream stream) { return stream->rate; }
time. */
void sonicSetRate(sonicStream stream, float rate) {
stream->rate = rate;
stream->oldRatePosition = 0;
stream->newRatePosition = 0;
}
int sonicGetChordPitch(sonicStream stream) { return stream->useChordPitch; }
void sonicSetChordPitch(sonicStream stream, int useChordPitch) {
stream->useChordPitch = useChordPitch;
}
int sonicGetQuality(sonicStream stream) { return stream->quality; }
faster. */
void sonicSetQuality(sonicStream stream, int quality) {
stream->quality = quality;
}
float sonicGetVolume(sonicStream stream) { return stream->volume; }
void sonicSetVolume(sonicStream stream, float volume) {
stream->volume = volume;
}
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);
}
}
void sonicDestroyStream(sonicStream stream) {
#ifdef SONIC_SPECTROGRAM
if (stream->spectrogram != NULL) {
sonicDestroySpectrogram(stream->spectrogram);
}
#endif
freeStreamBuffers(stream);
free(stream);
}
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;
}
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->avePower = 50.0f;
stream->remainingSamplesForSkip = 0.0f;
stream->remainingSamplesForInsert = 0.0f;
return stream;
}
int sonicGetSampleRate(sonicStream stream) { return stream->sampleRate; }
stream to be lost. */
void sonicSetSampleRate(sonicStream stream, int sampleRate) {
freeStreamBuffers(stream);
allocateStreamBuffers(stream, sampleRate, stream->numChannels);
}
int sonicGetNumChannels(sonicStream stream) { return stream->numChannels; }
stream to be lost. */
void sonicSetNumChannels(sonicStream stream, int numChannels) {
freeStreamBuffers(stream);
allocateStreamBuffers(stream, stream->sampleRate, numChannels);
}
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 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 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;
}
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;
}
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;
}
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 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;
}
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;
}
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;
}
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;
}
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;
}
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);
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;
}
if (stream->numOutputSamples > expectedOutputSamples) {
stream->numOutputSamples = expectedOutputSamples;
}
stream->numInputSamples = 0;
stream->remainingInputToCopy = 0;
stream->numPitchSamples = 0;
return 1;
}
int sonicSamplesAvailable(sonicStream stream) {
return stream->numOutputSamples;
}
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;
}
}
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;
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);
}
than 256, since we skip samples. Thus, diff is a 24 bit number, and
we can safely multiply by numSamples without overflow */
minDiff*period) || diff*bestPeriod < (minDiff >> 1)*period) {*/
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;
}
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) {
return 0;
}
if (minDiff * 2 <= stream->prevMinDiff * 3) {
return 0;
}
} else {
if (minDiff <= stream->prevMinDiff) {
return 0;
}
}
return 1;
}
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 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, minDiff, maxDiff, preferNewPeriod)) {
retPeriod = stream->prevPeriod;
} else {
retPeriod = period;
}
stream->prevMinDiff = minDiff;
stream->prevPeriod = period;
return retPeriod;
}
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;
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;
#else
*o = (*d * (numSamples - t) + *u * t) / numSamples;
#endif
o += numChannels;
d += numChannels;
u += numChannels;
}
}
}
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;
}
}
}
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 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;
}
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;
short* rampDown;
short* 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;
}
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;
}
static int getSign(int value) { return value >= 0 ? 1 : -1; }
static short interpolate(sonicStream stream, short* in, int oldSampleRate,
int newSampleRate) {
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) {
overflowCount += oldSign;
}
}
if (overflowCount > 0) {
return SHRT_MAX;
} else if (overflowCount < 0) {
return SHRT_MIN;
}
return total >> 16;
}
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;
while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
newSampleRate >>= 1;
oldSampleRate >>= 1;
}
if (stream->numOutputSamples == originalNumOutputSamples) {
return 1;
}
if (!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) {
return 0;
}
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;
if (stream->newRatePosition != newSampleRate) {
fprintf(stderr,
"Assertion failed: stream->newRatePosition != newSampleRate\n");
exit(1);
}
stream->newRatePosition = 0;
}
}
removePitchSamples(stream, position);
return 1;
}
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;
}
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;
}
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 {
if (stream->remainingInputToCopy > 0) {
newSamples = copyInputToOutput(stream, position);
position += newSamples;
} else {
samples = stream->inputBuffer + position * stream->numChannels;
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
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;
}
} while (position + maxRequired <= numSamples);
removeInputSamples(stream, position);
return 1;
}
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);
} 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) {
scaleSamples(
stream->outputBuffer + originalNumOutputSamples * stream->numChannels,
(stream->numOutputSamples - originalNumOutputSamples) *
stream->numChannels,
stream->volume);
}
return 1;
}
int sonicWriteFloatToStream(sonicStream stream, float* samples,
int numSamples) {
if (!addFloatSamplesToInputBuffer(stream, samples, numSamples)) {
return 0;
}
return processStreamInput(stream);
}
conversion for you. */
int sonicWriteShortToStream(sonicStream stream, short* samples,
int numSamples) {
if (!addShortSamplesToInputBuffer(stream, samples, numSamples)) {
return 0;
}
return processStreamInput(stream);
}
float conversion for you. */
int sonicWriteUnsignedCharToStream(sonicStream stream, unsigned char* samples,
int numSamples) {
if (!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) {
return 0;
}
return processStreamInput(stream);
}
* 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;
}
* 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;
}