plectrum

Plectrum: instrument tuner for Android
Log | Files | Refs | README | LICENSE

PitchShifter.java (5009B)


      1 package be.tarsos.dsp;
      2 
      3 
      4 import be.tarsos.dsp.util.fft.FFT;
      5 
      6 /**
      7  * This is a translation of code by Stephan M. Bernsee. See the following explanation on this code:
      8  * <a href="http://www.dspdimension.com/admin/pitch-shifting-using-the-ft/">Pitch shifting using the STFT</a>.
      9  * 
     10  * @author Joren Six
     11  * @author Stephan M. Bernsee
     12  */
     13 public class PitchShifter implements AudioProcessor{
     14 	
     15 	private final FFT fft;
     16 	private final int size;
     17 	private final float[] currentMagnitudes;
     18 	private final float[] currentPhase;
     19 	private final float[] currentFrequencies;
     20 	private final float[] outputAccumulator;
     21 	private final float[] summedPhase;
     22 	
     23 	private  float[] previousPhase;
     24 	
     25 	private double pitchShiftRatio = 0;
     26 
     27 	private final double sampleRate;
     28 	
     29 	private long osamp;
     30 	
     31 	private double excpt;
     32 	
     33 	public PitchShifter(double factor, double sampleRate, int size, int overlap){
     34 
     35 		
     36 		pitchShiftRatio = factor;
     37 		this.size = size;
     38 		this.sampleRate = sampleRate;
     39 		//this.d = d;
     40 		
     41 		osamp=size/(size-overlap);
     42 		
     43 		this.excpt = 2.*Math.PI*(double)(size-overlap)/(double)size;
     44 		
     45 		fft = new FFT(size);
     46 		
     47 		currentMagnitudes = new float[size/2];
     48 		currentFrequencies = new float[size/2];
     49 		currentPhase = new float[size/2];
     50 		
     51 		previousPhase = new float[size/2];
     52 		summedPhase = new float[size/2];
     53 		outputAccumulator = new float[size*2];
     54 	}
     55 	
     56 	public void setPitchShiftFactor(float newPitchShiftFactor){
     57 		this.pitchShiftRatio = newPitchShiftFactor;
     58 	}
     59 
     60 	@Override
     61 	public boolean process(AudioEvent audioEvent) {
     62 		//see http://downloads.dspdimension.com/smbPitchShift.cpp
     63 			
     64 		/* ***************** ANALYSIS ******************* */		
     65 		float[] fftData = audioEvent.getFloatBuffer().clone();
     66 		
     67 		for(int i = 0 ; i<size ; i++){
     68 			float window = (float) (-.5*Math.cos(2.*Math.PI*(double)i/(double)size)+.5);
     69 			fftData[i] = window * fftData[i];
     70 		}
     71 		//Fourier transform the audio 
     72 		fft.forwardTransform(fftData);
     73 		//Calculate the magnitudes and phase information. 
     74 		fft.powerAndPhaseFromFFT(fftData, currentMagnitudes, currentPhase);
     75 		
     76 		float freqPerBin  = (float) (sampleRate/(float)size);	// distance in Hz between FFT bins
     77 		
     78 		for(int i = 0 ; i < size/2 ; i++){
     79 			
     80 			float phase = currentPhase[i];
     81 			
     82 			/* compute phase difference */
     83 			double tmp = phase - previousPhase[i];
     84 			previousPhase[i] = phase;
     85 
     86 			/* subtract expected phase difference */
     87 			tmp -= (double)i*excpt;
     88 
     89 			/* map delta phase into +/- Pi interval */
     90 			long qpd = (long) (tmp/Math.PI);
     91 			if (qpd >= 0) 
     92 				qpd += qpd&1;
     93 			else 
     94 				qpd -= qpd&1;
     95 			tmp -= Math.PI*(double)qpd;
     96 
     97 			/* get deviation from bin frequency from the +/- Pi interval */
     98 			tmp = osamp*tmp/(2.*Math.PI);
     99 
    100 			/* compute the k-th partials' true frequency */
    101 			tmp = (double)i*freqPerBin + tmp*freqPerBin;			
    102 
    103 			/* store magnitude and true frequency in analysis arrays */
    104 			currentFrequencies[i] = (float) tmp;
    105 		}
    106 		
    107 		/* ***************** PROCESSING ******************* */
    108 		/* this does the actual pitch shifting */
    109 		float[] newMagnitudes = new float[size/2];
    110 		float[] newFrequencies = new float[size/2];
    111 		
    112 		for(int i = 0 ; i < size/2 ; i++){
    113 			int index = (int)(i * pitchShiftRatio);
    114 			if(index < size/2){
    115 				newMagnitudes[index] += currentMagnitudes[i];
    116 				newFrequencies[index] = (float) (currentFrequencies[i]*pitchShiftRatio);
    117 			}
    118 		}
    119 		
    120 		///Synthesis****
    121 		float[] newFFTData = new float[size];
    122 		
    123 		for(int i =0 ; i < size/2 ; i++){
    124 			
    125 			float magn = newMagnitudes[i];
    126 			double tmp = newFrequencies[i];
    127 
    128 			/* subtract bin mid frequency */
    129 			tmp -= (double)i*freqPerBin;
    130 
    131 			/* get bin deviation from freq deviation */
    132 			tmp /= freqPerBin;
    133 
    134 			/* take osamp into account */
    135 			tmp = 2.*Math.PI*tmp/osamp;
    136 
    137 			/* add the overlap phase advance back in */
    138 			tmp += (double)i*excpt;
    139 
    140 			/* accumulate delta phase to get bin phase */
    141 			summedPhase[i] += tmp;
    142 			float phase = summedPhase[i];
    143 
    144 			/* get real and imag part and re-interleave */
    145 			newFFTData[2*i] = (float) (magn * Math.cos(phase));
    146 			newFFTData[2*i+1] = (float) (magn* Math.sin(phase));
    147 		}
    148 		
    149 		/* zero negative frequencies */
    150 		for (int i = size/2+2;  i < size; i++){ 
    151 			newFFTData[i] = 0.f;
    152 		}
    153 		
    154 		fft.backwardsTransform(newFFTData);
    155 		for(int i = 0 ; i < newFFTData.length ; i ++){
    156 			float window = (float) (-.5*Math.cos(2.*Math.PI*(double)i/(double)size)+.5);
    157 			//outputAccumulator[i] += 2000*window*newFFTData[i]/(float) (size*osamp);
    158 			outputAccumulator[i] += window*newFFTData[i]/(float) osamp;
    159 			if(outputAccumulator[i] > 1.0 ||  outputAccumulator[i] < -1.0 ){
    160 				System.err.println("Clipping!");
    161 			}
    162 		}
    163 		
    164 		int stepSize = (int) (size/osamp);
    165 		
    166 		
    167 		
    168 		//Arrays.fill(audioBuffer, 0);
    169 		System.arraycopy(outputAccumulator, stepSize, outputAccumulator, 0, size);
    170 		
    171 		float[] audioBuffer = new float[audioEvent.getFloatBuffer().length];
    172 		audioEvent.setFloatBuffer(audioBuffer);
    173 		System.arraycopy(outputAccumulator, 0, audioBuffer,size-stepSize, stepSize);
    174 		
    175 		return true;
    176 	}
    177 
    178 	@Override
    179 	public void processingFinished() {
    180 		
    181 	}
    182 }