You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
997 lines
32 KiB
997 lines
32 KiB
/* 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;
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
|
|
for(int x = 0; x < length; x++) {
|
|
newArray[x] = oldArray[x];
|
|
}
|
|
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)
|
|
{
|
|
for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
|
|
dest[destPos*numChannels + xSample] = source[sourcePos*numChannels + xSample];
|
|
}
|
|
}
|
|
|
|
// Scale the samples by the factor.
|
|
private void scaleSamples(
|
|
short samples[],
|
|
int position,
|
|
int numSamples,
|
|
float volume)
|
|
{
|
|
int fixedPointVolume = (int)(volume*4096.0f);
|
|
int start = position*numChannels;
|
|
int stop = start + numSamples*numChannels;
|
|
|
|
for(int xSample = start; xSample < stop; xSample++) {
|
|
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. Note that retMinDiff and
|
|
// retMaxDiff are Int objects, which the caller will need to create with new.
|
|
private int findPitchPeriodInRange(
|
|
short samples[],
|
|
int position,
|
|
int minPeriod,
|
|
int maxPeriod,
|
|
Integer retMinDiff,
|
|
Integer retMaxDiff)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
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.
|
|
private boolean prevPeriodBetter(
|
|
int period,
|
|
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)
|
|
{
|
|
Integer minDiff = new Integer(0);
|
|
Integer maxDiff = new Integer(0);
|
|
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, minDiff, maxDiff);
|
|
} else {
|
|
downSampleInput(samples, position, skip);
|
|
period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod/skip,
|
|
maxPeriod/skip, minDiff, maxDiff);
|
|
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, minDiff, maxDiff);
|
|
} else {
|
|
downSampleInput(samples, position, 1);
|
|
period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP, minDiff, maxDiff);
|
|
}
|
|
}
|
|
}
|
|
if(prevPeriodBetter(period, 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);
|
|
}
|
|
|
|
// Interpolate the new output sample.
|
|
private short interpolate(
|
|
short in[],
|
|
int inPos,
|
|
int oldSampleRate,
|
|
int newSampleRate)
|
|
{
|
|
short left = in[inPos*numChannels];
|
|
short right = in[inPos*numChannels + numChannels];
|
|
int position = newRatePosition*oldSampleRate;
|
|
int leftPosition = oldRatePosition*newSampleRate;
|
|
int rightPosition = (oldRatePosition + 1)*newSampleRate;
|
|
int ratio = rightPosition - position;
|
|
int width = rightPosition - leftPosition;
|
|
|
|
return (short)((ratio*left + (width - ratio)*right)/width);
|
|
}
|
|
|
|
// Change the rate.
|
|
private void adjustRate(
|
|
float rate,
|
|
int originalNumOutputSamples)
|
|
{
|
|
int newSampleRate = (int)(sampleRate/rate);
|
|
int oldSampleRate = sampleRate;
|
|
int position;
|
|
|
|
// 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 one pitch sample in the buffer
|
|
for(position = 0; position < numPitchSamples - 1; position++) {
|
|
while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) {
|
|
enlargeOutputBufferIfNeeded(1);
|
|
for(int i = 0; i < numChannels; i++) {
|
|
outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer, position + 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;
|
|
}
|
|
}
|