From abcd177e8a940f5fe89288cd720abc7744e9416a Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 27 Mar 2017 04:39:54 -0700 Subject: [PATCH] Remove some unused Sonic functionality. Also move it closer to the ExoPlayer code style. Note: This change is intended to be purely cosmetic. Issue: #26 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151307575 --- .../android/exoplayer2/audio/Sonic.java | 690 ++++++------------ 1 file changed, 214 insertions(+), 476 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index 40c52f13c2f..47acccd967a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -16,329 +16,129 @@ */ package com.google.android.exoplayer2.audio; +import com.google.android.exoplayer2.util.Assertions; +import java.util.Arrays; + /** - * Sonic audio time/pitch stretching library. Based on https://github.com/waywardgeek/sonic. + * Sonic audio stream processor for time/pitch stretching. + *

+ * Based on https://github.com/waywardgeek/sonic. */ /* package */ final 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 static final boolean USE_CHORD_PITCH = false; + private static final int MINIMUM_PITCH = 65; + private static final int MAXIMUM_PITCH = 400; + private static final int AMDF_FREQUENCY = 4000; + + private final int sampleRate; + private final int numChannels; + private final int minPeriod; + private final int maxPeriod; + private final int maxRequired; + private final short[] downSampleBuffer; + private int inputBufferSize; private short[] inputBuffer; + private int outputBufferSize; private short[] outputBuffer; + private int pitchBufferSize; 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 float speed; + private float pitch; 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 = Math.min(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) { - 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; + /** + * Creates a new Sonic audio stream processor. + * + * @param sampleRate The sample rate of input audio. + * @param numChannels The number of channels in the input audio. + */ + public Sonic(int sampleRate, int numChannels) { + this.sampleRate = sampleRate; + this.numChannels = numChannels; + minPeriod = sampleRate / MAXIMUM_PITCH; + maxPeriod = sampleRate / MINIMUM_PITCH; maxRequired = 2 * maxPeriod; + downSampleBuffer = new short[maxRequired]; 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); - } + /** + * Sets the output speed. + */ + public void setSpeed(float speed) { + this.speed = speed; } - // 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; + /** + * Gets the output speed. + */ + public float getSpeed() { + return speed; } - // 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; + /** + * Sets the output pitch. + */ + public void setPitch(float pitch) { + this.pitch = pitch; } - // 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; + /** + * Gets the output pitch. + */ + public float getPitch() { + return pitch; } - // 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) { + /** + * Writes {@code numBytes} from {@code buffer} as input. + * + * @param buffer A buffer containing input data. + * @param numBytes The number of bytes of input data to read from {@code buffer}. + */ + public void writeBytesToStream(byte[] buffer, 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)); + sample = (short) ((buffer[xByte] & 0xff) | (buffer[xByte + 1] << 8)); inputBuffer[xBuffer++] = sample; } numInputSamples += numSamples; + processStreamInput(); } - // 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 readBytesFromStream(byte[] outBuffer, int maxBytes) { + /** + * Reads up to {@code maxBytes} of output into {@code buffer}. + * + * @param buffer The buffer into which output will be written. + * @param maxBytes The maximum number of bytes to write. + * @return The number of bytes read from the stream. + */ + public int readBytesFromStream(byte[] buffer, int maxBytes) { int maxSamples = maxBytes / (2 * numChannels); int numSamples = numOutputSamples; int remainingSamples = 0; @@ -352,23 +152,24 @@ public int readBytesFromStream(byte[] outBuffer, int maxBytes) { } 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); + buffer[xSample << 1] = (byte) (sample & 0xff); + buffer[(xSample << 1) + 1] = (byte) (sample >> 8); } - move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + System.arraycopy(outputBuffer, numSamples * numChannels, outputBuffer, 0, + remainingSamples * numChannels); 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. + /** + * Forces generating output using whatever data has been queued already. 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); + numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / pitch + 0.5f); // Add enough silence to flush both input and pitch buffers. enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); @@ -376,7 +177,7 @@ public void flushStream() { inputBuffer[remainingSamples * numChannels + xSample] = 0; } numInputSamples += 2 * maxRequired; - writeShortToStream(null, 0); + processStreamInput(); // Throw away any extra samples we generated due to the silence we added. if (numOutputSamples > expectedOutputSamples) { numOutputSamples = expectedOutputSamples; @@ -387,22 +188,59 @@ public void flushStream() { numPitchSamples = 0; } - // Return the number of samples in the output buffer + /** + * Returns the number of output samples that can be read with + * {@link #readBytesFromStream(byte[], int)}. + */ 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. + // Internal methods. + + private void enlargeOutputBufferIfNeeded(int numSamples) { + if (numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize / 2) + numSamples; + outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * numChannels); + } + } + + private void enlargeInputBufferIfNeeded(int numSamples) { + if (numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize / 2) + numSamples; + inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSize * numChannels); + } + } + + private void removeProcessedInputSamples(int position) { + int remainingSamples = numInputSamples - position; + System.arraycopy(inputBuffer, position * numChannels, inputBuffer, 0, + remainingSamples * numChannels); + numInputSamples = remainingSamples; + } + + private void copyToOutput(short[] samples, int position, int numSamples) { + enlargeOutputBufferIfNeeded(numSamples); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + numSamples * numChannels); + numOutputSamples += numSamples; + } + + private int copyInputToOutput(int position) { + int numSamples = Math.min(maxRequired, remainingInputToCopy); + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + private void downSampleInput(short[] samples, int position, int skip) { + // 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. int numSamples = maxRequired / skip; int samplesPerValue = numChannels * skip; - int value; - position *= numChannels; for (int i = 0; i < numSamples; i++) { - value = 0; + int value = 0; for (int j = 0; j < samplesPerValue; j++) { value += samples[position + i * samplesPerValue + j]; } @@ -411,14 +249,13 @@ private void downSampleInput(short[] samples, int position, int skip) { } } - // 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) { + // Find the best frequency match in the range, and given a sample skip multiple. For now, just + // find the pitch of the first channel. int bestPeriod = 0; int worstPeriod = 255; int minDiff = 1; int maxDiff = 0; - position *= numChannels; for (int period = minPeriod; period <= maxPeriod; period++) { int diff = 0; @@ -428,7 +265,7 @@ private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, 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 + // 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; @@ -441,13 +278,14 @@ private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, } 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) { + /** + * Returns whether the previous pitch period estimate is a better approximation, which can occur + * at the abrupt end of voiced words. + */ + private boolean previousPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { if (minDiff == 0 || prevPeriod == 0) { return false; } @@ -468,18 +306,14 @@ private boolean prevPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeri 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) { + // 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 11 kHz range, and then do it again with a narrower frequency range without down + // sampling. int period; int retPeriod; - int skip = 1; - - if (sampleRate > SONIC_AMDF_FREQ && quality == 0) { - skip = sampleRate / SONIC_AMDF_FREQ; - } + int skip = sampleRate > AMDF_FREQUENCY ? sampleRate / AMDF_FREQUENCY : 1; if (numChannels == 1 && skip == 1) { period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); } else { @@ -487,8 +321,8 @@ private int findPitchPeriod(short[] samples, int position, boolean preferNewPeri period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, maxPeriod / skip); if (skip != 1) { period *= skip; - int minP = period - (skip << 2); - int maxP = period + (skip << 2); + int minP = period - (skip * 4); + int maxP = period + (skip * 4); if (minP < minPeriod) { minP = minPeriod; } @@ -503,7 +337,7 @@ private int findPitchPeriod(short[] samples, int position, boolean preferNewPeri } } } - if (prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + if (previousPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { retPeriod = prevPeriod; } else { retPeriod = period; @@ -513,93 +347,44 @@ private int findPitchPeriod(short[] samples, int position, boolean preferNewPeri 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 static 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 static 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); + pitchBufferSize += (pitchBufferSize / 2) + numSamples; + pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * numChannels); } - move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); + System.arraycopy(outputBuffer, originalNumOutputSamples * numChannels, pitchBuffer, + numPitchSamples * numChannels, numSamples * numChannels); 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); + System.arraycopy(pitchBuffer, numSamples * numChannels, pitchBuffer, 0, + (numPitchSamples - numSamples) * numChannels); 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; - int newPeriod; - int separation; - int position = 0; - + // Latency due to pitch changes could be reduced by looking at past samples to determine pitch, + // rather than future. if (numOutputSamples == originalNumOutputSamples) { return; } moveNewSamplesToPitchBuffer(originalNumOutputSamples); + int position = 0; while (numPitchSamples - position >= maxRequired) { - period = findPitchPeriod(pitchBuffer, position, false); - newPeriod = (int) (period / pitch); + int period = findPitchPeriod(pitchBuffer, position, false); + int 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; + int separation = newPeriod - period; overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, pitchBuffer, position, pitchBuffer, position); } @@ -609,7 +394,6 @@ private void adjustPitch(int originalNumOutputSamples) { 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]; @@ -618,27 +402,23 @@ private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampl 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) { + if (numOutputSamples == originalNumOutputSamples) { + return; + } int newSampleRate = (int) (sampleRate / rate); int oldSampleRate = sampleRate; - int position; - - // Set these values to help with the integer math + // Set these values to help with the integer math. while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { - newSampleRate >>= 1; - oldSampleRate >>= 1; - } - if (numOutputSamples == originalNumOutputSamples) { - return; + newSampleRate /= 2; + oldSampleRate /= 2; } moveNewSamplesToPitchBuffer(originalNumOutputSamples); - // Leave at least one pitch sample in the buffer - for (position = 0; position < numPitchSamples - 1; position++) { + // Leave at least one pitch sample in the buffer. + for (int position = 0; position < numPitchSamples - 1; position++) { while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { enlargeOutputBufferIfNeeded(1); for (int i = 0; i < numChannels; i++) { @@ -651,20 +431,16 @@ private void adjustRate(float rate, int originalNumOutputSamples) { oldRatePosition++; if (oldRatePosition == oldSampleRate) { oldRatePosition = 0; - if (newRatePosition != newSampleRate) { - System.out.printf("Assertion failed: newRatePosition != newSampleRate\n"); - assert false; - } + Assertions.checkState(newRatePosition == newSampleRate); newRatePosition = 0; } } - removePitchSamples(position); + removePitchSamples(numPitchSamples - 1); } - // Skip over a pitch period, and copy period/speed samples to the output private int skipPitchPeriod(short[] samples, int position, float speed, int period) { + // Skip over a pitch period, and copy period/speed samples to the output. int newSamples; - if (speed >= 2.0f) { newSamples = (int) (period / (speed - 1.0f)); } else { @@ -678,10 +454,9 @@ private int skipPitchPeriod(short[] samples, int position, float speed, int peri 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) { + // Insert a pitch period, and determine how much input to copy directly. int newSamples; - if (speed < 0.5f) { newSamples = (int) (period * speed / (1.0f - speed)); } else { @@ -689,129 +464,92 @@ private int insertPitchPeriod(short[] samples, int position, float speed, int pe remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); } enlargeOutputBufferIfNeeded(period + newSamples); - move(outputBuffer, numOutputSamples, samples, position, period); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + period * numChannels); 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; - int period; - int newSamples; - if (numInputSamples < maxRequired) { return; } + int numSamples = numInputSamples; + int position = 0; do { if (remainingInputToCopy > 0) { - newSamples = copyInputToOutput(position); - position += newSamples; + position += copyInputToOutput(position); } else { - period = findPitchPeriod(inputBuffer, position, true); + int period = findPitchPeriod(inputBuffer, position, true); if (speed > 1.0) { - newSamples = skipPitchPeriod(inputBuffer, position, speed, period); - position += period + newSamples; + position += period + skipPitchPeriod(inputBuffer, position, speed, period); } else { - newSamples = insertPitchPeriod(inputBuffer, position, speed, period); - position += newSamples; + position += insertPitchPeriod(inputBuffer, position, speed, period); } } } while (position + maxRequired <= numSamples); - removeInputSamples(position); + removeProcessedInputSamples(position); } - // Resample as many pitch periods as we have buffered on the input. Scale the output by the - // volume. private void processStreamInput() { + // Resample as many pitch periods as we have buffered on the input. 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 (USE_CHORD_PITCH) { 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); + } else if (!USE_CHORD_PITCH && pitch != 1.0f) { + adjustRate(pitch, originalNumOutputSamples); } } - // 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; + private static 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; + } + } } - /* 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; + private static 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; + } + } } }