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. resize( short[] oldArray, int newLength)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. move( short dest[], int destPos, short source[], int sourcePos, int numSamples)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. scaleSamples( short samples[], int position, int numSamples, float volume)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. getSpeed()97 public float getSpeed() 98 { 99 return speed; 100 } 101 102 // Set the speed of the stream. setSpeed( float speed)103 public void setSpeed( 104 float speed) 105 { 106 this.speed = speed; 107 } 108 109 // Get the pitch of the stream. getPitch()110 public float getPitch() 111 { 112 return pitch; 113 } 114 115 // Set the pitch of the stream. setPitch( float pitch)116 public void setPitch( 117 float pitch) 118 { 119 this.pitch = pitch; 120 } 121 122 // Get the rate of the stream. getRate()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. setRate( float rate)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. getChordPitch()138 public boolean getChordPitch() 139 { 140 return useChordPitch; 141 } 142 143 // Set the vocal chord mode for pitch computation. Default is off. setChordPitch( boolean useChordPitch)144 public void setChordPitch( 145 boolean useChordPitch) 146 { 147 this.useChordPitch = useChordPitch; 148 } 149 150 // Get the quality setting. getQuality()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. setQuality( int quality)157 public void setQuality( 158 int quality) 159 { 160 this.quality = quality; 161 } 162 163 // Get the scaling factor of the stream. getVolume()164 public float getVolume() 165 { 166 return volume; 167 } 168 169 // Set the scaling factor of the stream. setVolume( float volume)170 public void setVolume( 171 float volume) 172 { 173 this.volume = volume; 174 } 175 176 // Allocate stream buffers. allocateStreamBuffers( int sampleRate, int numChannels)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. Sonic( int sampleRate, int numChannels)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. getSampleRate()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. setSampleRate( int sampleRate)221 public void setSampleRate( 222 int sampleRate) 223 { 224 allocateStreamBuffers(sampleRate, numChannels); 225 } 226 227 // Get the number of channels. getNumChannels()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. setNumChannels( int numChannels)234 public void setNumChannels( 235 int numChannels) 236 { 237 allocateStreamBuffers(sampleRate, numChannels); 238 } 239 240 // Enlarge the output buffer if needed. enlargeOutputBufferIfNeeded( int numSamples)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. enlargeInputBufferIfNeeded( int numSamples)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. addFloatSamplesToInputBuffer( float samples[], int numSamples)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. addShortSamplesToInputBuffer( short samples[], int numSamples)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. addUnsignedByteSamplesToInputBuffer( byte samples[], int numSamples)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. addBytesToInputBuffer( byte inBuffer[], int numBytes)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. removeInputSamples( int position)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 copyToOutput( short samples[], int position, int numSamples)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. copyInputToOutput( int position)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. readFloatFromStream( float samples[], int maxSamples)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. readShortFromStream( short samples[], int maxSamples)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. readUnsignedByteFromStream( byte samples[], int maxSamples)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. readBytesFromStream( byte outBuffer[], int maxBytes)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. flushStream()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 samplesAvailable()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. downSampleInput( short samples[], int position, int skip)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. findPitchPeriodInRange( short samples[], int position, int minPeriod, int maxPeriod, Integer retMinDiff, Integer retMaxDiff)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. prevPeriodBetter( int period, int minDiff, int maxDiff, boolean preferNewPeriod)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 findPitchPeriod( short samples[], int position, boolean preferNewPeriod)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. overlapAdd( int numSamples, int numChannels, short out[], int outPos, short rampDown[], int rampDownPos, short rampUp[], int rampUpPos)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. overlapAddWithSeparation( int numSamples, int numChannels, int separation, short out[], int outPos, short rampDown[], int rampDownPos, short rampUp[], int rampUpPos)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 moveNewSamplesToPitchBuffer( int originalNumOutputSamples)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. removePitchSamples( int numSamples)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. adjustPitch( int originalNumOutputSamples)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. interpolate( short in[], int inPos, int oldSampleRate, int newSampleRate)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. adjustRate( float rate, int originalNumOutputSamples)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 skipPitchPeriod( short samples[], int position, float speed, int period)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. insertPitchPeriod( short samples[], int position, float speed, int period)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. changeSpeed( float speed)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. processStreamInput()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. writeFloatToStream( float samples[], int numSamples)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. writeShortToStream( short samples[], int numSamples)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. writeUnsignedByteToStream( byte samples[], int numSamples)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. writeBytesToStream( byte inBuffer[], int numBytes)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 changeFloatSpeed( float samples[], int numSamples, float speed, float pitch, float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels)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 */ sonicChangeShortSpeed( short samples[], int numSamples, float speed, float pitch, float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels)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