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