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