blob: a3394d71b4cc59bf11e8d1dda1d9d53e2fb985a1 [file] [log] [blame]
Bill Coxdfe6b372011-07-15 13:38:26 -04001/* Sonic library
2 Copyright 2010, 2011
3 Bill Cox
4 This file is part of the Sonic Library.
5
Bill Cox60eeb062015-02-27 10:17:45 -08006 This file is licensed under the Apache 2.0 license.
Bill Coxe7200652011-07-16 12:09:05 -04007*/
Bill Coxdfe6b372011-07-15 13:38:26 -04008
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 {
David R. Souto32251502013-09-10 13:33:34 +020050 newLength *= numChannels;
Bill Coxdfe6b372011-07-15 13:38:26 -040051 short[] newArray = new short[newLength];
52 int length = oldArray.length <= newLength? oldArray.length : newLength;
53
David R. Souto32251502013-09-10 13:33:34 +020054
Bill Coxdfe6b372011-07-15 13:38:26 -040055 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
Bill Cox2e48c222011-07-16 12:00:46 -0400305 // 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
Bill Coxdfe6b372011-07-15 13:38:26 -0400322 // 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
Bill Cox2e48c222011-07-16 12:00:46 -0400427 // 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
Bill Coxdfe6b372011-07-15 13:38:26 -0400454 // 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) {
Bill Cox2e48c222011-07-16 12:00:46 -0400889 changeSpeed(s);
Bill Coxdfe6b372011-07-15 13:38:26 -0400890 } else {
891 copyToOutput(inputBuffer, 0, numInputSamples);
892 numInputSamples = 0;
893 }
894 if(useChordPitch) {
895 if(pitch != 1.0f) {
896 adjustPitch(originalNumOutputSamples);
897 }
Bill Cox2e48c222011-07-16 12:00:46 -0400898 } else if(r != 1.0f) {
899 adjustRate(r, originalNumOutputSamples);
Bill Coxdfe6b372011-07-15 13:38:26 -0400900 }
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
Bill Cox2e48c222011-07-16 12:00:46 -0400936 // 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
Bill Coxdfe6b372011-07-15 13:38:26 -0400945 // 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 }
Bill Coxe7200652011-07-16 12:09:05 -0400996}