blob: 3a2594009f45f9c432ab83bfb674cffb8f2f9d87 [file] [log] [blame]
/* Sonic library
Copyright 2010, 2011
Bill Cox
This file is part of the Sonic Library.
This file is licensed under the Apache 2.0 license.
*/
package sonic;
public class Sonic {
private static final int SONIC_MIN_PITCH = 65;
private static final int SONIC_MAX_PITCH = 400;
// This is used to down-sample some inputs to improve speed
private static final int SONIC_AMDF_FREQ = 4000;
// The number of points to use in the sinc FIR filter for resampling.
private static final int SINC_FILTER_POINTS = 12;
private static final int SINC_TABLE_SIZE = 601;
// Lookup table for windowed sinc function of SINC_FILTER_POINTS points.
// The code to generate this is in the header comment of sonic.c.
private static final short sincTable[] = {
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
};
private short inputBuffer[];
private short outputBuffer[];
private short pitchBuffer[];
private short downSampleBuffer[];
private float speed;
private float volume;
private float pitch;
private float rate;
private int oldRatePosition;
private int newRatePosition;
private boolean useChordPitch;
private int quality;
private int numChannels;
private int inputBufferSize;
private int pitchBufferSize;
private int outputBufferSize;
private int numInputSamples;
private int numOutputSamples;
private int numPitchSamples;
private int minPeriod;
private int maxPeriod;
private int maxRequired;
private int remainingInputToCopy;
private int sampleRate;
private int prevPeriod;
private int prevMinDiff;
private int minDiff;
private int maxDiff;
// Resize the array.
private short[] resize(
short[] oldArray,
int newLength)
{
newLength *= numChannels;
short[] newArray = new short[newLength];
int length = oldArray.length <= newLength? oldArray.length : newLength;
System.arraycopy(oldArray, 0, newArray, 0, length);
return newArray;
}
// Move samples from one array to another. May move samples down within an array, but not up.
private void move(
short dest[],
int destPos,
short source[],
int sourcePos,
int numSamples)
{
System.arraycopy(source, sourcePos*numChannels, dest, destPos*numChannels, numSamples*numChannels);
}
// Scale the samples by the factor.
private void scaleSamples(
short samples[],
int position,
int numSamples,
float volume)
{
// Convert volume to fixed-point, with a 12 bit fraction.
int fixedPointVolume = (int)(volume*4096.0f);
int start = position*numChannels;
int stop = start + numSamples*numChannels;
for(int xSample = start; xSample < stop; xSample++) {
// Convert back from fixed point to 16-bit integer.
int value = (samples[xSample]*fixedPointVolume) >> 12;
if(value > 32767) {
value = 32767;
} else if(value < -32767) {
value = -32767;
}
samples[xSample] = (short)value;
}
}
// Get the speed of the stream.
public float getSpeed()
{
return speed;
}
// Set the speed of the stream.
public void setSpeed(
float speed)
{
this.speed = speed;
}
// Get the pitch of the stream.
public float getPitch()
{
return pitch;
}
// Set the pitch of the stream.
public void setPitch(
float pitch)
{
this.pitch = pitch;
}
// Get the rate of the stream.
public float getRate()
{
return rate;
}
// Set the playback rate of the stream. This scales pitch and speed at the same time.
public void setRate(
float rate)
{
this.rate = rate;
this.oldRatePosition = 0;
this.newRatePosition = 0;
}
// Get the vocal chord pitch setting.
public boolean getChordPitch()
{
return useChordPitch;
}
// Set the vocal chord mode for pitch computation. Default is off.
public void setChordPitch(
boolean useChordPitch)
{
this.useChordPitch = useChordPitch;
}
// Get the quality setting.
public int getQuality()
{
return quality;
}
// Set the "quality". Default 0 is virtually as good as 1, but very much faster.
public void setQuality(
int quality)
{
this.quality = quality;
}
// Get the scaling factor of the stream.
public float getVolume()
{
return volume;
}
// Set the scaling factor of the stream.
public void setVolume(
float volume)
{
this.volume = volume;
}
// Allocate stream buffers.
private void allocateStreamBuffers(
int sampleRate,
int numChannels)
{
minPeriod = sampleRate/SONIC_MAX_PITCH;
maxPeriod = sampleRate/SONIC_MIN_PITCH;
maxRequired = 2*maxPeriod;
inputBufferSize = maxRequired;
inputBuffer = new short[maxRequired*numChannels];
outputBufferSize = maxRequired;
outputBuffer = new short[maxRequired*numChannels];
pitchBufferSize = maxRequired;
pitchBuffer = new short[maxRequired*numChannels];
downSampleBuffer = new short[maxRequired];
this.sampleRate = sampleRate;
this.numChannels = numChannels;
oldRatePosition = 0;
newRatePosition = 0;
prevPeriod = 0;
}
// Create a sonic stream.
public Sonic(
int sampleRate,
int numChannels)
{
allocateStreamBuffers(sampleRate, numChannels);
speed = 1.0f;
pitch = 1.0f;
volume = 1.0f;
rate = 1.0f;
oldRatePosition = 0;
newRatePosition = 0;
useChordPitch = false;
quality = 0;
}
// Get the sample rate of the stream.
public int getSampleRate()
{
return sampleRate;
}
// Set the sample rate of the stream. This will cause samples buffered in the stream to be lost.
public void setSampleRate(
int sampleRate)
{
allocateStreamBuffers(sampleRate, numChannels);
}
// Get the number of channels.
public int getNumChannels()
{
return numChannels;
}
// Set the num channels of the stream. This will cause samples buffered in the stream to be lost.
public void setNumChannels(
int numChannels)
{
allocateStreamBuffers(sampleRate, numChannels);
}
// Enlarge the output buffer if needed.
private void enlargeOutputBufferIfNeeded(
int numSamples)
{
if(numOutputSamples + numSamples > outputBufferSize) {
outputBufferSize += (outputBufferSize >> 1) + numSamples;
outputBuffer = resize(outputBuffer, outputBufferSize);
}
}
// Enlarge the input buffer if needed.
private void enlargeInputBufferIfNeeded(
int numSamples)
{
if(numInputSamples + numSamples > inputBufferSize) {
inputBufferSize += (inputBufferSize >> 1) + numSamples;
inputBuffer = resize(inputBuffer, inputBufferSize);
}
}
// Add the input samples to the input buffer.
private void addFloatSamplesToInputBuffer(
float samples[],
int numSamples)
{
if(numSamples == 0) {
return;
}
enlargeInputBufferIfNeeded(numSamples);
int xBuffer = numInputSamples*numChannels;
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
inputBuffer[xBuffer++] = (short)(samples[xSample]*32767.0f);
}
numInputSamples += numSamples;
}
// Add the input samples to the input buffer.
private void addShortSamplesToInputBuffer(
short samples[],
int numSamples)
{
if(numSamples == 0) {
return;
}
enlargeInputBufferIfNeeded(numSamples);
move(inputBuffer, numInputSamples, samples, 0, numSamples);
numInputSamples += numSamples;
}
// Add the input samples to the input buffer.
private void addUnsignedByteSamplesToInputBuffer(
byte samples[],
int numSamples)
{
short sample;
enlargeInputBufferIfNeeded(numSamples);
int xBuffer = numInputSamples*numChannels;
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
sample = (short)((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed
inputBuffer[xBuffer++] = (short) (sample << 8);
}
numInputSamples += numSamples;
}
// Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte array.
private void addBytesToInputBuffer(
byte inBuffer[],
int numBytes)
{
int numSamples = numBytes/(2*numChannels);
short sample;
enlargeInputBufferIfNeeded(numSamples);
int xBuffer = numInputSamples*numChannels;
for(int xByte = 0; xByte + 1 < numBytes; xByte += 2) {
sample = (short)((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8));
inputBuffer[xBuffer++] = sample;
}
numInputSamples += numSamples;
}
// Remove input samples that we have already processed.
private void removeInputSamples(
int position)
{
int remainingSamples = numInputSamples - position;
move(inputBuffer, 0, inputBuffer, position, remainingSamples);
numInputSamples = remainingSamples;
}
// Just copy from the array to the output buffer
private void copyToOutput(
short samples[],
int position,
int numSamples)
{
enlargeOutputBufferIfNeeded(numSamples);
move(outputBuffer, numOutputSamples, samples, position, numSamples);
numOutputSamples += numSamples;
}
// Just copy from the input buffer to the output buffer. Return num samples copied.
private int copyInputToOutput(
int position)
{
int numSamples = remainingInputToCopy;
if(numSamples > maxRequired) {
numSamples = maxRequired;
}
copyToOutput(inputBuffer, position, numSamples);
remainingInputToCopy -= numSamples;
return numSamples;
}
// Read data out of the stream. Sometimes no data will be available, and zero
// is returned, which is not an error condition.
public int readFloatFromStream(
float samples[],
int maxSamples)
{
int numSamples = numOutputSamples;
int remainingSamples = 0;
if(numSamples == 0) {
return 0;
}
if(numSamples > maxSamples) {
remainingSamples = numSamples - maxSamples;
numSamples = maxSamples;
}
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
samples[xSample] = (outputBuffer[xSample])/32767.0f;
}
move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
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.
public int readShortFromStream(
short samples[],
int maxSamples)
{
int numSamples = numOutputSamples;
int remainingSamples = 0;
if(numSamples == 0) {
return 0;
}
if(numSamples > maxSamples) {
remainingSamples = numSamples - maxSamples;
numSamples = maxSamples;
}
move(samples, 0, outputBuffer, 0, numSamples);
move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
numOutputSamples = remainingSamples;
return numSamples;
}
// Read unsigned byte data out of the stream. Sometimes no data will be available, and zero
// is returned, which is not an error condition.
public int readUnsignedByteFromStream(
byte samples[],
int maxSamples)
{
int numSamples = numOutputSamples;
int remainingSamples = 0;
if(numSamples == 0) {
return 0;
}
if(numSamples > maxSamples) {
remainingSamples = numSamples - maxSamples;
numSamples = maxSamples;
}
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
samples[xSample] = (byte)((outputBuffer[xSample] >> 8) + 128);
}
move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
numOutputSamples = remainingSamples;
return numSamples;
}
// Read unsigned byte data out of the stream. Sometimes no data will be available, and zero
// is returned, which is not an error condition.
public int readBytesFromStream(
byte outBuffer[],
int maxBytes)
{
int maxSamples = maxBytes/(2*numChannels);
int numSamples = numOutputSamples;
int remainingSamples = 0;
if(numSamples == 0 || maxSamples == 0) {
return 0;
}
if(numSamples > maxSamples) {
remainingSamples = numSamples - maxSamples;
numSamples = maxSamples;
}
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
short sample = outputBuffer[xSample];
outBuffer[xSample << 1] = (byte)(sample & 0xff);
outBuffer[(xSample << 1) + 1] = (byte)(sample >> 8);
}
move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
numOutputSamples = remainingSamples;
return 2*numSamples*numChannels;
}
// 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.
public void flushStream()
{
int remainingSamples = numInputSamples;
float s = speed/pitch;
float r = rate*pitch;
int expectedOutputSamples = numOutputSamples + (int)((remainingSamples/s + numPitchSamples)/r + 0.5f);
// Add enough silence to flush both input and pitch buffers.
enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired);
for(int xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) {
inputBuffer[remainingSamples*numChannels + xSample] = 0;
}
numInputSamples += 2*maxRequired;
writeShortToStream(null, 0);
// Throw away any extra samples we generated due to the silence we added.
if(numOutputSamples > expectedOutputSamples) {
numOutputSamples = expectedOutputSamples;
}
// Empty input and pitch buffers.
numInputSamples = 0;
remainingInputToCopy = 0;
numPitchSamples = 0;
}
// Return the number of samples in the output buffer
public int samplesAvailable()
{
return 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.
private void downSampleInput(
short samples[],
int position,
int skip)
{
int numSamples = maxRequired/skip;
int samplesPerValue = numChannels*skip;
int value;
position *= numChannels;
for(int i = 0; i < numSamples; i++) {
value = 0;
for(int j = 0; j < samplesPerValue; j++) {
value += samples[position + i*samplesPerValue + j];
}
value /= samplesPerValue;
downSampleBuffer[i] = (short)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.
private int findPitchPeriodInRange(
short samples[],
int position,
int minPeriod,
int maxPeriod)
{
int bestPeriod = 0, worstPeriod = 255;
int minDiff = 1, maxDiff = 0;
position *= numChannels;
for(int period = minPeriod; period <= maxPeriod; period++) {
int diff = 0;
for(int i = 0; i < period; i++) {
short sVal = samples[position + i];
short pVal = samples[position + period + i];
diff += sVal >= pVal? sVal - pVal : 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;
}
}
this.minDiff = minDiff/bestPeriod;
this.maxDiff = 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.
private boolean prevPeriodBetter(
int minDiff,
int maxDiff,
boolean preferNewPeriod)
{
if(minDiff == 0 || prevPeriod == 0) {
return false;
}
if(preferNewPeriod) {
if(maxDiff > minDiff*3) {
// Got a reasonable match this period
return false;
}
if(minDiff*2 <= prevMinDiff*3) {
// Mismatch is not that much greater this period
return false;
}
} else {
if(minDiff <= prevMinDiff) {
return false;
}
}
return true;
}
// 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
private int findPitchPeriod(
short samples[],
int position,
boolean preferNewPeriod)
{
int period, retPeriod;
int skip = 1;
if(sampleRate > SONIC_AMDF_FREQ && quality == 0) {
skip = sampleRate/SONIC_AMDF_FREQ;
}
if(numChannels == 1 && skip == 1) {
period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod);
} else {
downSampleInput(samples, position, skip);
period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod/skip,
maxPeriod/skip);
if(skip != 1) {
period *= skip;
int minP = period - (skip << 2);
int maxP = period + (skip << 2);
if(minP < minPeriod) {
minP = minPeriod;
}
if(maxP > maxPeriod) {
maxP = maxPeriod;
}
if(numChannels == 1) {
period = findPitchPeriodInRange(samples, position, minP, maxP);
} else {
downSampleInput(samples, position, 1);
period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP);
}
}
}
if(prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) {
retPeriod = prevPeriod;
} else {
retPeriod = period;
}
prevMinDiff = minDiff;
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.
private void overlapAdd(
int numSamples,
int numChannels,
short out[],
int outPos,
short rampDown[],
int rampDownPos,
short rampUp[],
int rampUpPos)
{
for(int i = 0; i < numChannels; i++) {
int o = outPos*numChannels + i;
int u = rampUpPos*numChannels + i;
int d = rampDownPos*numChannels + i;
for(int t = 0; t < numSamples; t++) {
out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples);
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.
private void overlapAddWithSeparation(
int numSamples,
int numChannels,
int separation,
short out[],
int outPos,
short rampDown[],
int rampDownPos,
short rampUp[],
int rampUpPos)
{
for(int i = 0; i < numChannels; i++) {
int o = outPos*numChannels + i;
int u = rampUpPos*numChannels + i;
int d = rampDownPos*numChannels + i;
for(int t = 0; t < numSamples + separation; t++) {
if(t < separation) {
out[o] = (short)(rampDown[d]*(numSamples - t)/numSamples);
d += numChannels;
} else if(t < numSamples) {
out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples);
d += numChannels;
u += numChannels;
} else {
out[o] = (short)(rampUp[u]*(t - separation)/numSamples);
u += numChannels;
}
o += numChannels;
}
}
}
// Just move the new samples in the output buffer to the pitch buffer
private void moveNewSamplesToPitchBuffer(
int originalNumOutputSamples)
{
int numSamples = numOutputSamples - originalNumOutputSamples;
if(numPitchSamples + numSamples > pitchBufferSize) {
pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
pitchBuffer = resize(pitchBuffer, pitchBufferSize);
}
move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples);
numOutputSamples = originalNumOutputSamples;
numPitchSamples += numSamples;
}
// Remove processed samples from the pitch buffer.
private void removePitchSamples(
int numSamples)
{
if(numSamples == 0) {
return;
}
move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples);
numPitchSamples -= numSamples;
}
// Change the pitch. The latency this introduces could be reduced by looking at
// past samples to determine pitch, rather than future.
private void adjustPitch(
int originalNumOutputSamples)
{
int period, newPeriod, separation;
int position = 0;
if(numOutputSamples == originalNumOutputSamples) {
return;
}
moveNewSamplesToPitchBuffer(originalNumOutputSamples);
while(numPitchSamples - position >= maxRequired) {
period = findPitchPeriod(pitchBuffer, position, false);
newPeriod = (int)(period/pitch);
enlargeOutputBufferIfNeeded(newPeriod);
if(pitch >= 1.0f) {
overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer,
position, pitchBuffer, position + period - newPeriod);
} else {
separation = newPeriod - period;
overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
pitchBuffer, position, pitchBuffer, position);
}
numOutputSamples += newPeriod;
position += period;
}
removePitchSamples(position);
}
// Approximate the sinc function times a Hann window from the sinc table.
private 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.
private int getSign(int value) {
return value >= 0? 1 : -1;
}
// Interpolate the new output sample.
private short interpolate(
short in[],
int inPos, // Index to first sample which already includes channel offset.
int oldSampleRate,
int newSampleRate)
{
// Compute N-point sinc FIR-filter here. Clip rather than overflow.
int i;
int total = 0;
int position = newRatePosition*oldSampleRate;
int leftPosition = oldRatePosition*newSampleRate;
int rightPosition = (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);
/* printf("%u %f\n", i, weight); */
value = in[inPos + i*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 Short.MAX_VALUE;
} else if (overflowCount < 0) {
return Short.MIN_VALUE;
}
return (short)(total >> 16);
}
// Change the rate.
private void adjustRate(
float rate,
int originalNumOutputSamples)
{
int newSampleRate = (int)(sampleRate/rate);
int oldSampleRate = sampleRate;
int position;
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(numOutputSamples == originalNumOutputSamples) {
return;
}
moveNewSamplesToPitchBuffer(originalNumOutputSamples);
// Leave at least N pitch samples in the buffer
for(position = 0; position < numPitchSamples - N; position++) {
while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) {
enlargeOutputBufferIfNeeded(1);
for(int i = 0; i < numChannels; i++) {
outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer,
position*numChannels + i, oldSampleRate, newSampleRate);
}
newRatePosition++;
numOutputSamples++;
}
oldRatePosition++;
if(oldRatePosition == oldSampleRate) {
oldRatePosition = 0;
if(newRatePosition != newSampleRate) {
System.out.printf("Assertion failed: newRatePosition != newSampleRate\n");
assert false;
}
newRatePosition = 0;
}
}
removePitchSamples(position);
}
// Skip over a pitch period, and copy period/speed samples to the output
private int skipPitchPeriod(
short samples[],
int position,
float speed,
int period)
{
int newSamples;
if(speed >= 2.0f) {
newSamples = (int)(period/(speed - 1.0f));
} else {
newSamples = period;
remainingInputToCopy = (int)(period*(2.0f - speed)/(speed - 1.0f));
}
enlargeOutputBufferIfNeeded(newSamples);
overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position,
samples, position + period);
numOutputSamples += newSamples;
return newSamples;
}
// Insert a pitch period, and determine how much input to copy directly.
private int insertPitchPeriod(
short samples[],
int position,
float speed,
int period)
{
int newSamples;
if(speed < 0.5f) {
newSamples = (int)(period*speed/(1.0f - speed));
} else {
newSamples = period;
remainingInputToCopy = (int)(period*(2.0f*speed - 1.0f)/(1.0f - speed));
}
enlargeOutputBufferIfNeeded(period + newSamples);
move(outputBuffer, numOutputSamples, samples, position, period);
overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
position + period, samples, position);
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.
private void changeSpeed(
float speed)
{
int numSamples = numInputSamples;
int position = 0, period, newSamples;
if(numInputSamples < maxRequired) {
return;
}
do {
if(remainingInputToCopy > 0) {
newSamples = copyInputToOutput(position);
position += newSamples;
} else {
period = findPitchPeriod(inputBuffer, position, true);
if(speed > 1.0) {
newSamples = skipPitchPeriod(inputBuffer, position, speed, period);
position += period + newSamples;
} else {
newSamples = insertPitchPeriod(inputBuffer, position, speed, period);
position += newSamples;
}
}
} while(position + maxRequired <= numSamples);
removeInputSamples(position);
}
// Resample as many pitch periods as we have buffered on the input. Scale the output by the volume.
private void processStreamInput()
{
int originalNumOutputSamples = numOutputSamples;
float s = speed/pitch;
float r = rate;
if(!useChordPitch) {
r *= pitch;
}
if(s > 1.00001 || s < 0.99999) {
changeSpeed(s);
} else {
copyToOutput(inputBuffer, 0, numInputSamples);
numInputSamples = 0;
}
if(useChordPitch) {
if(pitch != 1.0f) {
adjustPitch(originalNumOutputSamples);
}
} else if(r != 1.0f) {
adjustRate(r, originalNumOutputSamples);
}
if(volume != 1.0f) {
// Adjust output volume.
scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples,
volume);
}
}
// Write floating point data to the input buffer and process it.
public void writeFloatToStream(
float samples[],
int numSamples)
{
addFloatSamplesToInputBuffer(samples, numSamples);
processStreamInput();
}
// Write the data to the input stream, and process it.
public void writeShortToStream(
short samples[],
int numSamples)
{
addShortSamplesToInputBuffer(samples, numSamples);
processStreamInput();
}
// Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short
// conversion for you.
public void writeUnsignedByteToStream(
byte samples[],
int numSamples)
{
addUnsignedByteSamplesToInputBuffer(samples, numSamples);
processStreamInput();
}
// Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion.
public void writeBytesToStream(
byte inBuffer[],
int numBytes)
{
addBytesToInputBuffer(inBuffer, numBytes);
processStreamInput();
}
// This is a non-stream oriented interface to just change the speed of a sound sample
public static int changeFloatSpeed(
float samples[],
int numSamples,
float speed,
float pitch,
float rate,
float volume,
boolean useChordPitch,
int sampleRate,
int numChannels)
{
Sonic stream = new Sonic(sampleRate, numChannels);
stream.setSpeed(speed);
stream.setPitch(pitch);
stream.setRate(rate);
stream.setVolume(volume);
stream.setChordPitch(useChordPitch);
stream.writeFloatToStream(samples, numSamples);
stream.flushStream();
numSamples = stream.samplesAvailable();
stream.readFloatFromStream(samples, numSamples);
return numSamples;
}
/* This is a non-stream oriented interface to just change the speed of a sound sample */
public int sonicChangeShortSpeed(
short samples[],
int numSamples,
float speed,
float pitch,
float rate,
float volume,
boolean useChordPitch,
int sampleRate,
int numChannels)
{
Sonic stream = new Sonic(sampleRate, numChannels);
stream.setSpeed(speed);
stream.setPitch(pitch);
stream.setRate(rate);
stream.setVolume(volume);
stream.setChordPitch(useChordPitch);
stream.writeShortToStream(samples, numSamples);
stream.flushStream();
numSamples = stream.samplesAvailable();
stream.readShortFromStream(samples, numSamples);
return numSamples;
}
}