Home | History | Annotate | Download | only in sonic
      1 /* Sonic library
      2    Copyright 2010, 2011
      3    Bill Cox
      4    This file is part of the Sonic Library.
      5 
      6    This file is licensed under the Apache 2.0 license.
      7 */
      8 
      9 package sonic;
     10 
     11 public class Sonic {
     12 
     13 	private static final int SONIC_MIN_PITCH = 65;
     14 	private static final int SONIC_MAX_PITCH = 400;
     15 	/* This is used to down-sample some inputs to improve speed */
     16 	private static final int SONIC_AMDF_FREQ = 4000;
     17 
     18     private short inputBuffer[];
     19     private short outputBuffer[];
     20     private short pitchBuffer[];
     21     private short downSampleBuffer[];
     22     private float speed;
     23     private float volume;
     24     private float pitch;
     25     private float rate;
     26     private int oldRatePosition;
     27     private int newRatePosition;
     28     private boolean useChordPitch;
     29     private int quality;
     30     private int numChannels;
     31     private int inputBufferSize;
     32     private int pitchBufferSize;
     33     private int outputBufferSize;
     34     private int numInputSamples;
     35     private int numOutputSamples;
     36     private int numPitchSamples;
     37     private int minPeriod;
     38     private int maxPeriod;
     39     private int maxRequired;
     40     private int remainingInputToCopy;
     41     private int sampleRate;
     42     private int prevPeriod;
     43     private int prevMinDiff;
     44 
     45     // Resize the array.
     46     private short[] resize(
     47     	short[] oldArray,
     48     	int newLength)
     49     {
     50     	newLength *= numChannels;
     51         short[]	newArray = new short[newLength];
     52         int length = oldArray.length <= newLength? oldArray.length : newLength;
     53 
     54 
     55         for(int x = 0; x < length; x++) {
     56             newArray[x] = oldArray[x];
     57         }
     58         return newArray;
     59     }
     60 
     61     // Move samples from one array to another.  May move samples down within an array, but not up.
     62     private void move(
     63     	short dest[],
     64     	int destPos,
     65     	short source[],
     66     	int sourcePos,
     67     	int numSamples)
     68     {
     69     	for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
     70     	    dest[destPos*numChannels + xSample] = source[sourcePos*numChannels + xSample];
     71     	}
     72     }
     73 
     74     // Scale the samples by the factor.
     75     private void scaleSamples(
     76         short samples[],
     77         int position,
     78         int numSamples,
     79         float volume)
     80     {
     81         int fixedPointVolume = (int)(volume*4096.0f);
     82         int start = position*numChannels;
     83         int stop = start + numSamples*numChannels;
     84 
     85         for(int xSample = start; xSample < stop; xSample++) {
     86             int value = (samples[xSample]*fixedPointVolume) >> 12;
     87             if(value > 32767) {
     88                 value = 32767;
     89             } else if(value < -32767) {
     90                 value = -32767;
     91             }
     92             samples[xSample] = (short)value;
     93         }
     94     }
     95 
     96     // Get the speed of the stream.
     97     public float getSpeed()
     98     {
     99         return speed;
    100     }
    101 
    102     // Set the speed of the stream.
    103     public void setSpeed(
    104         float speed)
    105     {
    106         this.speed = speed;
    107     }
    108 
    109     // Get the pitch of the stream.
    110     public float getPitch()
    111     {
    112         return pitch;
    113     }
    114 
    115     // Set the pitch of the stream.
    116     public void setPitch(
    117         float pitch)
    118     {
    119         this.pitch = pitch;
    120     }
    121 
    122     // Get the rate of the stream.
    123     public float getRate()
    124     {
    125         return rate;
    126     }
    127 
    128     // Set the playback rate of the stream. This scales pitch and speed at the same time.
    129     public void setRate(
    130         float rate)
    131     {
    132         this.rate = rate;
    133         this.oldRatePosition = 0;
    134         this.newRatePosition = 0;
    135     }
    136 
    137     // Get the vocal chord pitch setting.
    138     public boolean getChordPitch()
    139     {
    140         return useChordPitch;
    141     }
    142 
    143     // Set the vocal chord mode for pitch computation.  Default is off.
    144     public void setChordPitch(
    145         boolean useChordPitch)
    146     {
    147         this.useChordPitch = useChordPitch;
    148     }
    149 
    150     // Get the quality setting.
    151     public int getQuality()
    152     {
    153         return quality;
    154     }
    155 
    156     // Set the "quality".  Default 0 is virtually as good as 1, but very much faster.
    157     public void setQuality(
    158         int quality)
    159     {
    160         this.quality = quality;
    161     }
    162 
    163     // Get the scaling factor of the stream.
    164     public float getVolume()
    165     {
    166         return volume;
    167     }
    168 
    169     // Set the scaling factor of the stream.
    170     public void setVolume(
    171         float volume)
    172     {
    173         this.volume = volume;
    174     }
    175 
    176     // Allocate stream buffers.
    177     private void allocateStreamBuffers(
    178         int sampleRate,
    179         int numChannels)
    180     {
    181         minPeriod = sampleRate/SONIC_MAX_PITCH;
    182         maxPeriod = sampleRate/SONIC_MIN_PITCH;
    183         maxRequired = 2*maxPeriod;
    184         inputBufferSize = maxRequired;
    185         inputBuffer = new short[maxRequired*numChannels];
    186         outputBufferSize = maxRequired;
    187         outputBuffer = new short[maxRequired*numChannels];
    188         pitchBufferSize = maxRequired;
    189         pitchBuffer = new short[maxRequired*numChannels];
    190         downSampleBuffer = new short[maxRequired];
    191         this.sampleRate = sampleRate;
    192         this.numChannels = numChannels;
    193         oldRatePosition = 0;
    194         newRatePosition = 0;
    195         prevPeriod = 0;
    196     }
    197 
    198     // Create a sonic stream.
    199     public Sonic(
    200         int sampleRate,
    201         int numChannels)
    202     {
    203         allocateStreamBuffers(sampleRate, numChannels);
    204         speed = 1.0f;
    205         pitch = 1.0f;
    206         volume = 1.0f;
    207         rate = 1.0f;
    208         oldRatePosition = 0;
    209         newRatePosition = 0;
    210         useChordPitch = false;
    211         quality = 0;
    212     }
    213 
    214     // Get the sample rate of the stream.
    215     public int getSampleRate()
    216     {
    217         return sampleRate;
    218     }
    219 
    220     // Set the sample rate of the stream.  This will cause samples buffered in the stream to be lost.
    221     public void setSampleRate(
    222         int sampleRate)
    223     {
    224         allocateStreamBuffers(sampleRate, numChannels);
    225     }
    226 
    227     // Get the number of channels.
    228     public int getNumChannels()
    229     {
    230         return numChannels;
    231     }
    232 
    233     // Set the num channels of the stream.  This will cause samples buffered in the stream to be lost.
    234     public void setNumChannels(
    235         int numChannels)
    236     {
    237         allocateStreamBuffers(sampleRate, numChannels);
    238     }
    239 
    240     // Enlarge the output buffer if needed.
    241     private void enlargeOutputBufferIfNeeded(
    242         int numSamples)
    243     {
    244         if(numOutputSamples + numSamples > outputBufferSize) {
    245             outputBufferSize += (outputBufferSize >> 1) + numSamples;
    246             outputBuffer = resize(outputBuffer, outputBufferSize);
    247         }
    248     }
    249 
    250     // Enlarge the input buffer if needed.
    251     private void enlargeInputBufferIfNeeded(
    252         int numSamples)
    253     {
    254         if(numInputSamples + numSamples > inputBufferSize) {
    255             inputBufferSize += (inputBufferSize >> 1) + numSamples;
    256             inputBuffer = resize(inputBuffer, inputBufferSize);
    257         }
    258     }
    259 
    260     // Add the input samples to the input buffer.
    261     private void addFloatSamplesToInputBuffer(
    262         float samples[],
    263         int numSamples)
    264     {
    265         if(numSamples == 0) {
    266             return;
    267         }
    268         enlargeInputBufferIfNeeded(numSamples);
    269         int xBuffer = numInputSamples*numChannels;
    270         for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
    271             inputBuffer[xBuffer++] = (short)(samples[xSample]*32767.0f);
    272         }
    273         numInputSamples += numSamples;
    274     }
    275 
    276     // Add the input samples to the input buffer.
    277     private void addShortSamplesToInputBuffer(
    278         short samples[],
    279         int numSamples)
    280     {
    281         if(numSamples == 0) {
    282             return;
    283         }
    284         enlargeInputBufferIfNeeded(numSamples);
    285         move(inputBuffer, numInputSamples, samples, 0, numSamples);
    286         numInputSamples += numSamples;
    287     }
    288 
    289     // Add the input samples to the input buffer.
    290     private void addUnsignedByteSamplesToInputBuffer(
    291         byte samples[],
    292         int numSamples)
    293     {
    294         short sample;
    295 
    296         enlargeInputBufferIfNeeded(numSamples);
    297         int xBuffer = numInputSamples*numChannels;
    298         for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
    299         	sample = (short)((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed
    300             inputBuffer[xBuffer++] = (short) (sample << 8);
    301         }
    302         numInputSamples += numSamples;
    303     }
    304 
    305     // Add the input samples to the input buffer.  They must be 16-bit little-endian encoded in a byte array.
    306     private void addBytesToInputBuffer(
    307         byte inBuffer[],
    308         int numBytes)
    309     {
    310     	int numSamples = numBytes/(2*numChannels);
    311         short sample;
    312 
    313         enlargeInputBufferIfNeeded(numSamples);
    314         int xBuffer = numInputSamples*numChannels;
    315         for(int xByte = 0; xByte + 1 < numBytes; xByte += 2) {
    316         	sample = (short)((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8));
    317             inputBuffer[xBuffer++] = sample;
    318         }
    319         numInputSamples += numSamples;
    320     }
    321 
    322     // Remove input samples that we have already processed.
    323     private void removeInputSamples(
    324         int position)
    325     {
    326         int remainingSamples = numInputSamples - position;
    327 
    328         move(inputBuffer, 0, inputBuffer, position, remainingSamples);
    329         numInputSamples = remainingSamples;
    330     }
    331 
    332     // Just copy from the array to the output buffer
    333     private void copyToOutput(
    334         short samples[],
    335         int position,
    336         int numSamples)
    337     {
    338         enlargeOutputBufferIfNeeded(numSamples);
    339         move(outputBuffer, numOutputSamples, samples, position, numSamples);
    340         numOutputSamples += numSamples;
    341     }
    342 
    343     // Just copy from the input buffer to the output buffer.  Return num samples copied.
    344     private int copyInputToOutput(
    345         int position)
    346     {
    347         int numSamples = remainingInputToCopy;
    348 
    349         if(numSamples > maxRequired) {
    350             numSamples = maxRequired;
    351         }
    352         copyToOutput(inputBuffer, position, numSamples);
    353         remainingInputToCopy -= numSamples;
    354         return numSamples;
    355     }
    356 
    357     // Read data out of the stream.  Sometimes no data will be available, and zero
    358     // is returned, which is not an error condition.
    359     public int readFloatFromStream(
    360         float samples[],
    361         int maxSamples)
    362     {
    363         int numSamples = numOutputSamples;
    364         int remainingSamples = 0;
    365 
    366         if(numSamples == 0) {
    367             return 0;
    368         }
    369         if(numSamples > maxSamples) {
    370             remainingSamples = numSamples - maxSamples;
    371             numSamples = maxSamples;
    372         }
    373         for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
    374             samples[xSample++] = (outputBuffer[xSample])/32767.0f;
    375         }
    376         move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
    377         numOutputSamples = remainingSamples;
    378         return numSamples;
    379     }
    380 
    381     // Read short data out of the stream.  Sometimes no data will be available, and zero
    382     // is returned, which is not an error condition.
    383     public int readShortFromStream(
    384         short samples[],
    385         int maxSamples)
    386     {
    387         int numSamples = numOutputSamples;
    388         int remainingSamples = 0;
    389 
    390         if(numSamples == 0) {
    391             return 0;
    392         }
    393         if(numSamples > maxSamples) {
    394             remainingSamples = numSamples - maxSamples;
    395             numSamples = maxSamples;
    396         }
    397         move(samples, 0, outputBuffer, 0, numSamples);
    398         move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
    399         numOutputSamples = remainingSamples;
    400         return numSamples;
    401     }
    402 
    403     // Read unsigned byte data out of the stream.  Sometimes no data will be available, and zero
    404     // is returned, which is not an error condition.
    405     public int readUnsignedByteFromStream(
    406         byte samples[],
    407         int maxSamples)
    408     {
    409         int numSamples = numOutputSamples;
    410         int remainingSamples = 0;
    411 
    412         if(numSamples == 0) {
    413             return 0;
    414         }
    415         if(numSamples > maxSamples) {
    416             remainingSamples = numSamples - maxSamples;
    417             numSamples = maxSamples;
    418         }
    419         for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
    420         	samples[xSample] = (byte)((outputBuffer[xSample] >> 8) + 128);
    421         }
    422         move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
    423         numOutputSamples = remainingSamples;
    424         return numSamples;
    425     }
    426 
    427     // Read unsigned byte data out of the stream.  Sometimes no data will be available, and zero
    428     // is returned, which is not an error condition.
    429     public int readBytesFromStream(
    430         byte outBuffer[],
    431         int maxBytes)
    432     {
    433     	int maxSamples = maxBytes/(2*numChannels);
    434         int numSamples = numOutputSamples;
    435         int remainingSamples = 0;
    436 
    437         if(numSamples == 0 || maxSamples == 0) {
    438             return 0;
    439         }
    440         if(numSamples > maxSamples) {
    441             remainingSamples = numSamples - maxSamples;
    442             numSamples = maxSamples;
    443         }
    444         for(int xSample = 0; xSample < numSamples*numChannels; xSample++) {
    445         	short sample = outputBuffer[xSample];
    446         	outBuffer[xSample << 1] = (byte)(sample & 0xff);
    447         	outBuffer[(xSample << 1) + 1] = (byte)(sample >> 8);
    448         }
    449         move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
    450         numOutputSamples = remainingSamples;
    451         return 2*numSamples*numChannels;
    452     }
    453 
    454     // Force the sonic stream to generate output using whatever data it currently
    455     // has.  No extra delay will be added to the output, but flushing in the middle of
    456     // words could introduce distortion.
    457     public void flushStream()
    458     {
    459         int remainingSamples = numInputSamples;
    460         float s = speed/pitch;
    461         float r = rate*pitch;
    462         int expectedOutputSamples = numOutputSamples + (int)((remainingSamples/s + numPitchSamples)/r + 0.5f);
    463 
    464         // Add enough silence to flush both input and pitch buffers.
    465         enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired);
    466         for(int xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) {
    467             inputBuffer[remainingSamples*numChannels + xSample] = 0;
    468         }
    469         numInputSamples += 2*maxRequired;
    470         writeShortToStream(null, 0);
    471         // Throw away any extra samples we generated due to the silence we added.
    472         if(numOutputSamples > expectedOutputSamples) {
    473             numOutputSamples = expectedOutputSamples;
    474         }
    475         // Empty input and pitch buffers.
    476         numInputSamples = 0;
    477         remainingInputToCopy = 0;
    478         numPitchSamples = 0;
    479     }
    480 
    481     // Return the number of samples in the output buffer
    482     public int samplesAvailable()
    483     {
    484         return numOutputSamples;
    485     }
    486 
    487     // If skip is greater than one, average skip samples together and write them to
    488     // the down-sample buffer.  If numChannels is greater than one, mix the channels
    489     // together as we down sample.
    490     private void downSampleInput(
    491         short samples[],
    492         int position,
    493         int skip)
    494     {
    495         int numSamples = maxRequired/skip;
    496         int samplesPerValue = numChannels*skip;
    497         int value;
    498 
    499         position *= numChannels;
    500         for(int i = 0; i < numSamples; i++) {
    501             value = 0;
    502             for(int j = 0; j < samplesPerValue; j++) {
    503                 value += samples[position + i*samplesPerValue + j];
    504             }
    505             value /= samplesPerValue;
    506             downSampleBuffer[i] = (short)value;
    507         }
    508     }
    509 
    510     // Find the best frequency match in the range, and given a sample skip multiple.
    511     // For now, just find the pitch of the first channel.  Note that retMinDiff and
    512     // retMaxDiff are Int objects, which the caller will need to create with new.
    513     private int findPitchPeriodInRange(
    514         short samples[],
    515         int position,
    516         int minPeriod,
    517         int maxPeriod,
    518         Integer retMinDiff,
    519         Integer retMaxDiff)
    520     {
    521         int bestPeriod = 0, worstPeriod = 255;
    522         int minDiff = 1, maxDiff = 0;
    523 
    524         position *= numChannels;
    525         for(int period = minPeriod; period <= maxPeriod; period++) {
    526             int diff = 0;
    527             for(int i = 0; i < period; i++) {
    528                 short sVal = samples[position + i];
    529                 short pVal = samples[position + period + i];
    530                 diff += sVal >= pVal? sVal - pVal : pVal - sVal;
    531             }
    532             /* Note that the highest number of samples we add into diff will be less
    533                than 256, since we skip samples.  Thus, diff is a 24 bit number, and
    534                we can safely multiply by numSamples without overflow */
    535             if(diff*bestPeriod < minDiff*period) {
    536                 minDiff = diff;
    537                 bestPeriod = period;
    538             }
    539             if(diff*worstPeriod > maxDiff*period) {
    540                 maxDiff = diff;
    541                 worstPeriod = period;
    542             }
    543         }
    544         retMinDiff = minDiff/bestPeriod;
    545         retMaxDiff = maxDiff/worstPeriod;
    546         return bestPeriod;
    547     }
    548 
    549     // At abrupt ends of voiced words, we can have pitch periods that are better
    550     // approximated by the previous pitch period estimate.  Try to detect this case.
    551     private boolean prevPeriodBetter(
    552         int period,
    553         int minDiff,
    554         int maxDiff,
    555         boolean preferNewPeriod)
    556     {
    557         if(minDiff == 0 || prevPeriod == 0) {
    558             return false;
    559         }
    560         if(preferNewPeriod) {
    561             if(maxDiff > minDiff*3) {
    562                 // Got a reasonable match this period
    563                 return false;
    564             }
    565             if(minDiff*2 <= prevMinDiff*3) {
    566                 // Mismatch is not that much greater this period
    567                 return false;
    568             }
    569         } else {
    570             if(minDiff <= prevMinDiff) {
    571                 return false;
    572             }
    573         }
    574         return true;
    575     }
    576 
    577     // Find the pitch period.  This is a critical step, and we may have to try
    578     // multiple ways to get a good answer.  This version uses AMDF.  To improve
    579     // speed, we down sample by an integer factor get in the 11KHz range, and then
    580     // do it again with a narrower frequency range without down sampling
    581     private int findPitchPeriod(
    582         short samples[],
    583         int position,
    584         boolean preferNewPeriod)
    585     {
    586         Integer minDiff = new Integer(0);
    587         Integer maxDiff = new Integer(0);
    588         int period, retPeriod;
    589         int skip = 1;
    590 
    591         if(sampleRate > SONIC_AMDF_FREQ && quality == 0) {
    592             skip = sampleRate/SONIC_AMDF_FREQ;
    593         }
    594         if(numChannels == 1 && skip == 1) {
    595             period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod, minDiff, maxDiff);
    596         } else {
    597             downSampleInput(samples, position, skip);
    598             period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod/skip,
    599                 maxPeriod/skip, minDiff, maxDiff);
    600             if(skip != 1) {
    601                 period *= skip;
    602                 int minP = period - (skip << 2);
    603                 int maxP = period + (skip << 2);
    604                 if(minP < minPeriod) {
    605                     minP = minPeriod;
    606                 }
    607                 if(maxP > maxPeriod) {
    608                     maxP = maxPeriod;
    609                 }
    610                 if(numChannels == 1) {
    611                     period = findPitchPeriodInRange(samples, position, minP, maxP, minDiff, maxDiff);
    612                 } else {
    613                     downSampleInput(samples, position, 1);
    614                     period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP, minDiff, maxDiff);
    615                 }
    616             }
    617         }
    618         if(prevPeriodBetter(period, minDiff, maxDiff, preferNewPeriod)) {
    619             retPeriod = prevPeriod;
    620         } else {
    621             retPeriod = period;
    622         }
    623         prevMinDiff = minDiff;
    624         prevPeriod = period;
    625         return retPeriod;
    626     }
    627 
    628     // Overlap two sound segments, ramp the volume of one down, while ramping the
    629     // other one from zero up, and add them, storing the result at the output.
    630     private void overlapAdd(
    631         int numSamples,
    632         int numChannels,
    633         short out[],
    634         int outPos,
    635         short rampDown[],
    636         int rampDownPos,
    637         short rampUp[],
    638         int rampUpPos)
    639     {
    640          for(int i = 0; i < numChannels; i++) {
    641             int o = outPos*numChannels + i;
    642             int u = rampUpPos*numChannels + i;
    643             int d = rampDownPos*numChannels + i;
    644             for(int t = 0; t < numSamples; t++) {
    645                 out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples);
    646                 o += numChannels;
    647                 d += numChannels;
    648                 u += numChannels;
    649             }
    650         }
    651     }
    652 
    653     // Overlap two sound segments, ramp the volume of one down, while ramping the
    654     // other one from zero up, and add them, storing the result at the output.
    655     private void overlapAddWithSeparation(
    656         int numSamples,
    657         int numChannels,
    658         int separation,
    659         short out[],
    660         int outPos,
    661         short rampDown[],
    662         int rampDownPos,
    663         short rampUp[],
    664         int rampUpPos)
    665     {
    666         for(int i = 0; i < numChannels; i++) {
    667             int o = outPos*numChannels + i;
    668             int u = rampUpPos*numChannels + i;
    669             int d = rampDownPos*numChannels + i;
    670             for(int t = 0; t < numSamples + separation; t++) {
    671                 if(t < separation) {
    672                     out[o] = (short)(rampDown[d]*(numSamples - t)/numSamples);
    673                     d += numChannels;
    674                 } else if(t < numSamples) {
    675                     out[o] = (short)((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples);
    676                     d += numChannels;
    677                     u += numChannels;
    678                 } else {
    679                     out[o] = (short)(rampUp[u]*(t - separation)/numSamples);
    680                     u += numChannels;
    681                 }
    682                 o += numChannels;
    683             }
    684         }
    685     }
    686 
    687     // Just move the new samples in the output buffer to the pitch buffer
    688     private void moveNewSamplesToPitchBuffer(
    689         int originalNumOutputSamples)
    690     {
    691         int numSamples = numOutputSamples - originalNumOutputSamples;
    692 
    693         if(numPitchSamples + numSamples > pitchBufferSize) {
    694             pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
    695             pitchBuffer = resize(pitchBuffer, pitchBufferSize);
    696         }
    697         move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples);
    698         numOutputSamples = originalNumOutputSamples;
    699         numPitchSamples += numSamples;
    700     }
    701 
    702     // Remove processed samples from the pitch buffer.
    703     private void removePitchSamples(
    704         int numSamples)
    705     {
    706         if(numSamples == 0) {
    707             return;
    708         }
    709         move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples);
    710         numPitchSamples -= numSamples;
    711     }
    712 
    713     // Change the pitch.  The latency this introduces could be reduced by looking at
    714     // past samples to determine pitch, rather than future.
    715     private void adjustPitch(
    716         int originalNumOutputSamples)
    717     {
    718         int period, newPeriod, separation;
    719         int position = 0;
    720 
    721         if(numOutputSamples == originalNumOutputSamples) {
    722             return;
    723         }
    724         moveNewSamplesToPitchBuffer(originalNumOutputSamples);
    725         while(numPitchSamples - position >= maxRequired) {
    726             period = findPitchPeriod(pitchBuffer, position, false);
    727             newPeriod = (int)(period/pitch);
    728             enlargeOutputBufferIfNeeded(newPeriod);
    729             if(pitch >= 1.0f) {
    730                 overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer,
    731                 	position, pitchBuffer, position + period - newPeriod);
    732             } else {
    733                 separation = newPeriod - period;
    734                 overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
    735                 	pitchBuffer, position, pitchBuffer, position);
    736             }
    737             numOutputSamples += newPeriod;
    738             position += period;
    739         }
    740         removePitchSamples(position);
    741     }
    742 
    743     // Interpolate the new output sample.
    744     private short interpolate(
    745         short in[],
    746         int inPos,
    747         int oldSampleRate,
    748         int newSampleRate)
    749     {
    750         short left = in[inPos*numChannels];
    751         short right = in[inPos*numChannels + numChannels];
    752         int position = newRatePosition*oldSampleRate;
    753         int leftPosition = oldRatePosition*newSampleRate;
    754         int rightPosition = (oldRatePosition + 1)*newSampleRate;
    755         int ratio = rightPosition - position;
    756         int width = rightPosition - leftPosition;
    757 
    758         return (short)((ratio*left + (width - ratio)*right)/width);
    759     }
    760 
    761     // Change the rate.
    762     private void adjustRate(
    763         float rate,
    764         int originalNumOutputSamples)
    765     {
    766         int newSampleRate = (int)(sampleRate/rate);
    767         int oldSampleRate = sampleRate;
    768         int position;
    769 
    770         // Set these values to help with the integer math
    771         while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
    772             newSampleRate >>= 1;
    773             oldSampleRate >>= 1;
    774         }
    775         if(numOutputSamples == originalNumOutputSamples) {
    776             return;
    777         }
    778         moveNewSamplesToPitchBuffer(originalNumOutputSamples);
    779         // Leave at least one pitch sample in the buffer
    780         for(position = 0; position < numPitchSamples - 1; position++) {
    781             while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) {
    782                 enlargeOutputBufferIfNeeded(1);
    783                 for(int i = 0; i < numChannels; i++) {
    784                     outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer, position + i,
    785                     	oldSampleRate, newSampleRate);
    786                 }
    787                 newRatePosition++;
    788                 numOutputSamples++;
    789             }
    790             oldRatePosition++;
    791             if(oldRatePosition == oldSampleRate) {
    792                 oldRatePosition = 0;
    793                 if(newRatePosition != newSampleRate) {
    794                     System.out.printf("Assertion failed: newRatePosition != newSampleRate\n");
    795                     assert false;
    796                 }
    797                 newRatePosition = 0;
    798             }
    799         }
    800         removePitchSamples(position);
    801     }
    802 
    803 
    804     // Skip over a pitch period, and copy period/speed samples to the output
    805     private int skipPitchPeriod(
    806         short samples[],
    807         int position,
    808         float speed,
    809         int period)
    810     {
    811         int newSamples;
    812 
    813         if(speed >= 2.0f) {
    814             newSamples = (int)(period/(speed - 1.0f));
    815         } else {
    816             newSamples = period;
    817             remainingInputToCopy = (int)(period*(2.0f - speed)/(speed - 1.0f));
    818         }
    819         enlargeOutputBufferIfNeeded(newSamples);
    820         overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position,
    821         	samples, position + period);
    822         numOutputSamples += newSamples;
    823         return newSamples;
    824     }
    825 
    826     // Insert a pitch period, and determine how much input to copy directly.
    827     private int insertPitchPeriod(
    828         short samples[],
    829         int position,
    830         float speed,
    831         int period)
    832     {
    833         int newSamples;
    834 
    835         if(speed < 0.5f) {
    836             newSamples = (int)(period*speed/(1.0f - speed));
    837         } else {
    838             newSamples = period;
    839             remainingInputToCopy = (int)(period*(2.0f*speed - 1.0f)/(1.0f - speed));
    840         }
    841         enlargeOutputBufferIfNeeded(period + newSamples);
    842         move(outputBuffer, numOutputSamples, samples, position, period);
    843         overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
    844         	position + period, samples, position);
    845         numOutputSamples += period + newSamples;
    846         return newSamples;
    847     }
    848 
    849     // Resample as many pitch periods as we have buffered on the input.  Return 0 if
    850     // we fail to resize an input or output buffer.  Also scale the output by the volume.
    851     private void changeSpeed(
    852         float speed)
    853     {
    854         int numSamples = numInputSamples;
    855         int position = 0, period, newSamples;
    856 
    857         if(numInputSamples < maxRequired) {
    858             return;
    859         }
    860         do {
    861             if(remainingInputToCopy > 0) {
    862                 newSamples = copyInputToOutput(position);
    863                 position += newSamples;
    864             } else {
    865                 period = findPitchPeriod(inputBuffer, position, true);
    866                 if(speed > 1.0) {
    867                     newSamples = skipPitchPeriod(inputBuffer, position, speed, period);
    868                     position += period + newSamples;
    869                 } else {
    870                     newSamples = insertPitchPeriod(inputBuffer, position, speed, period);
    871                     position += newSamples;
    872                 }
    873             }
    874         } while(position + maxRequired <= numSamples);
    875         removeInputSamples(position);
    876     }
    877 
    878     // Resample as many pitch periods as we have buffered on the input.  Scale the output by the volume.
    879     private void processStreamInput()
    880     {
    881         int originalNumOutputSamples = numOutputSamples;
    882         float s = speed/pitch;
    883         float r = rate;
    884 
    885         if(!useChordPitch) {
    886             r *= pitch;
    887         }
    888         if(s > 1.00001 || s < 0.99999) {
    889             changeSpeed(s);
    890         } else {
    891             copyToOutput(inputBuffer, 0, numInputSamples);
    892             numInputSamples = 0;
    893         }
    894         if(useChordPitch) {
    895             if(pitch != 1.0f) {
    896                 adjustPitch(originalNumOutputSamples);
    897             }
    898         } else if(r != 1.0f) {
    899             adjustRate(r, originalNumOutputSamples);
    900         }
    901         if(volume != 1.0f) {
    902             // Adjust output volume.
    903             scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples,
    904                 volume);
    905         }
    906     }
    907 
    908     // Write floating point data to the input buffer and process it.
    909     public void writeFloatToStream(
    910         float samples[],
    911         int numSamples)
    912     {
    913         addFloatSamplesToInputBuffer(samples, numSamples);
    914         processStreamInput();
    915     }
    916 
    917     // Write the data to the input stream, and process it.
    918     public void writeShortToStream(
    919         short samples[],
    920         int numSamples)
    921     {
    922         addShortSamplesToInputBuffer(samples, numSamples);
    923         processStreamInput();
    924     }
    925 
    926     // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short
    927     // conversion for you.
    928     public void writeUnsignedByteToStream(
    929         byte samples[],
    930         int numSamples)
    931     {
    932         addUnsignedByteSamplesToInputBuffer(samples, numSamples);
    933         processStreamInput();
    934     }
    935 
    936     // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion.
    937     public void writeBytesToStream(
    938         byte inBuffer[],
    939         int numBytes)
    940     {
    941         addBytesToInputBuffer(inBuffer, numBytes);
    942         processStreamInput();
    943     }
    944 
    945     // This is a non-stream oriented interface to just change the speed of a sound sample
    946     public static int changeFloatSpeed(
    947         float samples[],
    948         int numSamples,
    949         float speed,
    950         float pitch,
    951         float rate,
    952         float volume,
    953         boolean useChordPitch,
    954         int sampleRate,
    955         int numChannels)
    956     {
    957         Sonic stream = new Sonic(sampleRate, numChannels);
    958 
    959         stream.setSpeed(speed);
    960         stream.setPitch(pitch);
    961         stream.setRate(rate);
    962         stream.setVolume(volume);
    963         stream.setChordPitch(useChordPitch);
    964         stream.writeFloatToStream(samples, numSamples);
    965         stream.flushStream();
    966         numSamples = stream.samplesAvailable();
    967         stream.readFloatFromStream(samples, numSamples);
    968         return numSamples;
    969     }
    970 
    971     /* This is a non-stream oriented interface to just change the speed of a sound sample */
    972     public int sonicChangeShortSpeed(
    973         short samples[],
    974         int numSamples,
    975         float speed,
    976         float pitch,
    977         float rate,
    978         float volume,
    979         boolean useChordPitch,
    980         int sampleRate,
    981         int numChannels)
    982     {
    983         Sonic stream = new Sonic(sampleRate, numChannels);
    984 
    985         stream.setSpeed(speed);
    986         stream.setPitch(pitch);
    987         stream.setRate(rate);
    988         stream.setVolume(volume);
    989         stream.setChordPitch(useChordPitch);
    990         stream.writeShortToStream(samples, numSamples);
    991         stream.flushStream();
    992         numSamples = stream.samplesAvailable();
    993         stream.readShortFromStream(samples, numSamples);
    994         return numSamples;
    995     }
    996 }
    997