/* Perform real-time pitch scaling of audio signals. Copyright (c) 1998-2006 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.actor.lib.javasound.test.pitchshift; import ptolemy.media.javasound.SoundCapture; import ptolemy.media.javasound.SoundPlayback; import ptolemy.util.StringUtilities; ////////////////////////////////////////////////////////////////////////// //// ProcessAudio /** Perform real-time pitch shifting of audio signals. This only works for audio signals that have either a unique pitch or no pitch at any given time (pitched or unpitched, voiced or unvoiced). Examples inlude human vocal sounds and sounds from musical instruments capable of playing only one note at a times (e.g., horns, flute). The pitch shifting algorithm is based on the algorithm proposed by Keith Lent in his paper: "An efficient Method for Pitch Shifting Digitally Sampled Sounds", published in the Computer Music Journal, Vol 13, No. 4, Winter 1989. The algorithm is presented with more mathematical rigore in the paper by Robert Bristow-Johnson: "A Detailed Analysis of a Time-Domain Formant-Corrected Pitch- Shifting Algorithm", in J. Audio Eng. Soc., Vol 43, No. 5, May 1995. <p> The pitch shifting algorithm uses a pitch-synchronous overlap-add (PSOLA) based algorithm, and therefore requires the pitch of the input signal. The pitch detector used in Keith Lent's algorithm consists of a bandpass filter followed by a simple negative-slop zero-crossing detector. I found such a simple pitch detector to be completely unusable for vocal and musical instrument sounds. I therefore decided to implement a more robust pitch detector. I am currently using a pitch detector that uses cepstrum analysis. The (real) cepstrum is computed, and then peak finding is performed on the high-time region of the cepstrum. This cepstral technique works well for vocal sounds but does not currently perform well for pitches above about 600 Hz. <p> Note: This application requires JDK 1.3. and at least a Pentium II 400 MHz class processor (for 22050 Hz sample rate). @author Brian K. Vogel @version $Id$ @since Ptolemy II 1.0 @Pt.ProposedRating Red (vogel) @Pt.AcceptedRating Red (vogel) */ public class ProcessAudio implements Runnable { String errStr; Thread thread; // Set the default sample rate. double sampleRate = 22050; // Default pitch scale factor(s). double pitchScaleIn1 = 1.0; public void start() { System.out.println("Sampling rate = " + sampleRate + " Hz."); errStr = null; thread = new Thread(this); thread.start(); } public void stop() { thread = null; } private void shutDown(String message) { if ((errStr = message) != null) { System.err.println(errStr); } if (thread != null) { thread = null; // Now exit. StringUtilities.exit(0); } } // Update the pitch scale factor. public void updatePitchScaleFactor(double pitchScaleIn) { this.pitchScaleIn1 = pitchScaleIn; } // Set the sampling rate. Valid sampling rates are 11025, 22050, 44100. // This method should be the first method called in this class. public void setSamplingRate(double sr) { this.sampleRate = sr; } public void run() { // Capture specific stuff: int sampleSizeInBits = 16; int channels = 1; int inBufferSize = 6000; // Internal buffer size for capture. int outBufferSize = 6000; // Internal buffer size for playback. // Amount of data to read or write from/to the internal buffer // at a time. This should be set smaller than the internal buffer // size! int getSamplesSize = 256; SoundCapture soundCapture = new SoundCapture((float) sampleRate, sampleSizeInBits, channels, inBufferSize, getSamplesSize); int putSamplesSize = getSamplesSize; // Construct a sound playback object that plays audio //through the computer's speaker. SoundPlayback soundPlayback = new SoundPlayback((float) sampleRate, sampleSizeInBits, channels, outBufferSize, putSamplesSize); // Initialize and begin real-time capture and playback. try { soundCapture.startCapture(); soundPlayback.startPlayback(); } catch (Exception ex) { System.err.println(ex); } double[][] capturedSamplesArray /* Avoid Dead Store: = new double[channels][getSamplesSize] */; // Initialize the pitch detector. int vectorSize = getSamplesSize * channels; PitchDetector pd = new PitchDetector(vectorSize, (int) sampleRate); // Initialize the pitch shifter. PitchShift ps = new PitchShift((float) sampleRate); double[] currPitchArray; while (thread != null) { try { // Read in some captured audio. capturedSamplesArray = soundCapture.getSamples(); /////////////////////////////////////////////////////////// ////// Do processing on audioInDoubleArray here ///// currPitchArray = pd.performPitchDetect(capturedSamplesArray[0]); capturedSamplesArray[0] = ps.performPitchShift( capturedSamplesArray[0], currPitchArray, pitchScaleIn1); // Play the processed audio samples. soundPlayback.putSamples(capturedSamplesArray); } catch (Exception e) { shutDown("Error during playback: " + e); break; } } // we reached the end of the stream. let the data play out, then // stop and close the line. shutDown(null); } }