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 9package sonic; 10 11public 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