plectrum

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

OptimizedGranulator.java (9939B)


      1 package be.tarsos.dsp.granulator;
      2 
      3 
      4 import java.util.ArrayList;
      5 import java.util.Arrays;
      6 
      7 import be.tarsos.dsp.AudioEvent;
      8 import be.tarsos.dsp.AudioProcessor;
      9 
     10 /**
     11  * Granulator plays back samples using granular synthesis. 
     12  * Methods can be used to control playback rate, pitch, grain size,
     13  *  grain interval and grain randomness and position (this last case assumes that the playback rate is zero). 
     14  * 
     15  * 
     16  * 
     17  * @author ollie
     18  * @author Joren
     19  */
     20 public class OptimizedGranulator implements AudioProcessor  {
     21 
     22 	public static final float ADAPTIVE_INTERP_LOW_THRESH = 0.5f;
     23 	public static final float ADAPTIVE_INTERP_HIGH_THRESH = 2.5f;
     24 	
     25 	/** The position in milliseconds. */
     26 	protected double position;
     27 	
     28 	/**
     29 	 * The millisecond position increment per sample. Calculated from the ratio
     30 	 * of the sample rate
     31 	 */
     32 	private double audioSampleLength;
     33 	
     34 	
     35 	private float grainInterval;
     36 	private float grainSize;
     37 	private float grainRandomness;
     38 	
     39 	/** The time in milliseconds since the last grain was activated. */
     40 	private float timeSinceLastGrain;
     41 
     42 
     43 	/** The pitch, bound to the pitch envelope. */
     44 	private float pitchFactor;
     45 	
     46 	/** The pitch, bound to the pitch envelope. */
     47 	private float timeStretchFactor;
     48 
     49 	/** The list of current grains. */
     50 	private Grain[] grains;
     51 
     52 	/** The interpolation type. */
     53 	//protected InterpolationType interpolationType;
     54 
     55 	/** The window used by grains. */
     56 	private final float[] window;
     57 	
     58 		
     59 	private final float[] audioBuffer;
     60 	private int audioBufferWatermark;
     61 	
     62 	private final float[] outputBuffer;
     63 	
     64 
     65 	/**
     66 	 * Instantiates a new GranularSamplePlayer.
     67 	 * 
     68 	 * @param sampleRate the sample rate.
     69 	 * @param bufferSize the size of an output buffer.
     70 	 */
     71 	public OptimizedGranulator(float sampleRate,int bufferSize) {
     72 		grains = new Grain[50];
     73 		for(int i = 0 ; i < grains.length ; i++){
     74 			grains[i] = new Grain();
     75 		}
     76 		
     77 		audioBuffer = new float[4800*2];//max 0.2s 
     78 		audioBufferWatermark = 0;
     79 		
     80 		pitchFactor = 1.0f;
     81 		
     82 		grainInterval = 40.0f;
     83 		grainSize = 100.0f;
     84 		grainRandomness = 0.1f;
     85 		
     86 		window = new be.tarsos.dsp.util.fft.CosineWindow().generateCurve(512);
     87 		outputBuffer = new float[bufferSize];
     88 			
     89 		audioSampleLength = 1000.0f/sampleRate;
     90 	}
     91 
     92 		
     93 	public void start() {
     94 		timeSinceLastGrain = 0;
     95 	}
     96 	
     97 	
     98 	/** Flag to indicate special case for the first grain. */
     99 	private boolean firstGrain = true;
    100 
    101 	/** Special case method for playing first grain. */
    102 	private void firstGrain() {
    103 		if(firstGrain) {
    104 			Grain g = grains[0];
    105 			g.position = position;
    106 			g.age = grainSize / 4f;
    107 			g.grainSize = grainSize;
    108 			
    109 			firstGrain = false;
    110 			timeSinceLastGrain = grainInterval / 2f;
    111 		}
    112 	}
    113 
    114 	@Override
    115 	public boolean process(AudioEvent audioEvent) {
    116 		
    117 		int bufferSize = audioEvent.getBufferSize();
    118 		for (int i = 0; i < bufferSize; i++) {
    119 			audioBuffer[audioBufferWatermark] = audioEvent.getFloatBuffer()[i];
    120 			audioBufferWatermark++;
    121 			if(audioBufferWatermark==audioBuffer.length){
    122 				audioBufferWatermark=0;
    123 				
    124 			}
    125 		}
    126 		
    127 		System.out.println("Buffer water mark:" + audioBufferWatermark);
    128 
    129 		// grains.clear();
    130 		// position = audioEvent.getTimeStamp()*1000 - 5000;
    131 
    132 		// reset output
    133 		Arrays.fill(outputBuffer, 0);
    134 
    135 		firstGrain();
    136 		
    137 		int activeGrains = 0;
    138 		for(int j = 0 ; j < grains.length ; j++){
    139 			if(grains[j].active){
    140 				activeGrains++;
    141 			}
    142 		}	
    143 		System.out.println("Active grains = " + activeGrains);
    144 
    145 		// now loop through the buffer
    146 		for (int i = 0; i < bufferSize; i++) {
    147 			// determine if we need a new grain
    148 			if (timeSinceLastGrain > grainInterval) {
    149 				Grain firstInactiveGrain = null;
    150 				for(int j = 0 ; j < grains.length ; j++){
    151 					if(!grains[j].active){
    152 						firstInactiveGrain = grains[j];
    153 						firstInactiveGrain.reset(grainSize, grainRandomness, position,timeStretchFactor,pitchFactor);
    154 						timeSinceLastGrain = 0f;
    155 						break;
    156 					}
    157 				}	
    158 				//System.out.println(grains.size());
    159 			}
    160 
    161 			// gather the output from each grain
    162 			for (int gi = 0; gi < grains.length; gi++) {
    163 				Grain g = grains[gi];
    164 				if(g.active){
    165 					// calculate value of grain window
    166 					float windowScale = getValueFraction((float) (g.age / g.grainSize));
    167 					// get position in sample for this grain
    168 					// get the frame for this grain
    169 	
    170 					double sampleValue;
    171 					//if (pitchFactor > ADAPTIVE_INTERP_HIGH_THRESH) {
    172 						sampleValue = getFrameNoInterp(g.position);
    173 					//} else if (pitchFactor > ADAPTIVE_INTERP_LOW_THRESH) {
    174 					//	sampleValue = getFrameLinear(g.position);
    175 					//} else {
    176 					//	sampleValue = getFrameCubic(g.position);
    177 					//}
    178 					sampleValue = sampleValue * windowScale;
    179 					outputBuffer[i] += (float) sampleValue;
    180 				}
    181 			}
    182 			// increment time
    183 			position += audioSampleLength * timeStretchFactor;
    184 
    185 			for (int gi = 0; gi < grains.length; gi++) {
    186 				Grain g = grains[gi];
    187 				if(g.active){
    188 					calculateNextGrainPosition(g);
    189 					
    190 					if (g.age > g.grainSize) {
    191 						g.active = false;
    192 					}
    193 				}
    194 			}
    195 			
    196 			timeSinceLastGrain += audioSampleLength;	
    197 		}
    198 		
    199 		for (int i = 0; i < bufferSize; i++) {
    200 			outputBuffer[i] = outputBuffer[i]/(float) 5.0f;
    201 		}
    202 		
    203 		audioEvent.setFloatBuffer(outputBuffer);
    204 
    205 		return true;
    206 	}
    207 	
    208 
    209 	/**
    210 	 * Retrieves a frame of audio using linear interpolation. If the frame is
    211 	 * not in the sample range then zeros are returned.
    212 	 * 
    213 	 * @param posInMS
    214 	 *            The frame to read -- can be fractional (e.g., 4.4).
    215 	 * @param result
    216 	 *            The framedata to fill.
    217 	 */
    218 	public double getFrameLinear(double posInMS) {
    219 		double result = 0.0;
    220 		double sampleNumber = msToSamples(posInMS);
    221 		int sampleNumberFloor = (int) Math.floor(sampleNumber);
    222 		if (sampleNumberFloor > 0 && sampleNumberFloor < audioBufferWatermark) {
    223 			double sampleNumberFraction = sampleNumber - sampleNumberFloor;
    224 			if (sampleNumberFloor == audioBufferWatermark - 1) {
    225 				result = audioBuffer[sampleNumberFloor];
    226 			} else {
    227 				// linear interpolation
    228 				double current = audioBuffer[sampleNumberFloor];
    229 				double next = audioBuffer[sampleNumberFloor];
    230 				result = (float) ((1 - sampleNumberFraction) * current + sampleNumberFraction * next);
    231 			} 
    232 		}
    233 		return result;
    234 	}
    235 	
    236 	/**
    237 	 * Retrieves a frame of audio using no interpolation. If the frame is not in
    238 	 * the sample range then zeros are returned.
    239 	 * 
    240 	 * @param posInMS
    241 	 *            The frame to read -- will take the last frame before this one.
    242 	 *
    243 	 */
    244 	public float getFrameNoInterp(double posInMS) {
    245 		double frame = msToSamples(posInMS);
    246 		
    247 		int frame_floor = (int) Math.floor(frame);
    248 		
    249 		//int diff = audioBufferWatermark - frame_floor; 
    250 		//if( diff < 4800 || diff > )
    251 
    252 		
    253 		return audioBuffer[frame_floor];
    254 	}
    255 	
    256 	/**
    257 	 * Retrieves a frame of audio using cubic interpolation. If the frame is not
    258 	 * in the sample range then zeros are returned.
    259 	 * 
    260 	 * @param posInMS
    261 	 *            The frame to read -- can be fractional (e.g., 4.4).
    262 	 */
    263 	public float getFrameCubic(double posInMS) {
    264 		float frame = (float) msToSamples(posInMS);
    265 		float result = 0.0f;
    266 		float a0, a1, a2, a3, mu2;
    267 		float ym1, y0, y1, y2;
    268 	
    269 		int realCurrentSample = (int) Math.floor(frame);
    270 		float fractionOffset = (float) (frame - realCurrentSample);
    271 
    272 		if (realCurrentSample >= 0 && realCurrentSample < (audioBufferWatermark - 1)) {
    273 			realCurrentSample--;
    274 			if (realCurrentSample < 0) {
    275 				ym1 = audioBuffer[0];
    276 				realCurrentSample = 0;
    277 			} else {
    278 				ym1 = audioBuffer[realCurrentSample++];
    279 			}
    280 			y0 = audioBuffer[realCurrentSample++];
    281 			if (realCurrentSample >= audioBufferWatermark) {
    282 				y1 = audioBuffer[audioBufferWatermark-1]; // ??
    283 			} else {
    284 				y1 = audioBuffer[realCurrentSample++];
    285 			}
    286 			if (realCurrentSample >= audioBufferWatermark) {
    287 				y2 = audioBuffer[audioBufferWatermark-1];
    288 			} else {
    289 				y2 = audioBuffer[realCurrentSample++];
    290 			}
    291 			mu2 = fractionOffset * fractionOffset;
    292 			a0 = y2 - y1 - ym1 + y0;
    293 			a1 = ym1 - y0 - a0;
    294 			a2 = y1 - ym1;
    295 			a3 = y0;
    296 			result = a0 * fractionOffset * mu2 + a1 * mu2 + a2 * fractionOffset + a3;
    297 		}
    298 		return result;
    299 	}
    300 	
    301 	
    302 	private double msToSamples(double posInMs){
    303 		double positionInSamples = posInMs / audioSampleLength;
    304 		if(positionInSamples < 0){
    305 			positionInSamples = 0;
    306 		}else{
    307 			int bufferNumber = (int) (positionInSamples/audioBuffer.length);
    308 			positionInSamples = positionInSamples - bufferNumber * audioBuffer.length;
    309 		}
    310 		return positionInSamples;
    311 	}
    312 
    313 	@Override
    314 	public void processingFinished() {
    315 		
    316 	}
    317 	
    318 	/**
    319 	 * Returns the value of the buffer at the given fraction along its length (0 = start, 1 = end). Uses linear interpolation.
    320 	 * 
    321 	 * @param fraction the point along the buffer to inspect. 
    322 	 * 
    323 	 * @return the value at that point.
    324 	 */
    325 	public float getValueFraction(float fraction) {
    326 		float posInBuf = fraction * window.length;
    327 		if(fraction >= 1.0f){
    328 			posInBuf -= 1.0f;
    329 		}
    330 		int lowerIndex = (int) posInBuf;
    331 		float offset = posInBuf - lowerIndex;
    332 		int upperIndex = (lowerIndex + 1) % window.length;
    333 		return (1 - offset) * window[lowerIndex] + offset * window[upperIndex];
    334 	}
    335 
    336 	/**
    337 	 * Calculate next position for the given Grain.
    338 	 * 
    339 	 * @param g the Grain.
    340 	 */
    341 	private void calculateNextGrainPosition(Grain g) {
    342 		int direction = timeStretchFactor >= 0 ? 1 : -1;	//this is a bit odd in the case when controlling grain from positionEnvelope
    343 		g.age += audioSampleLength;
    344 		g.position += direction * audioSampleLength * pitchFactor;	
    345 	}
    346 
    347 	public void setTimestretchFactor(float currentFactor) {
    348 		timeStretchFactor = currentFactor;
    349 	}
    350 
    351 	public void setPitchShiftFactor(float currentFactor) {
    352 		pitchFactor = currentFactor;
    353 	}
    354 
    355 
    356 
    357 	public void setGrainInterval(int grainInterval) {
    358 		this.grainInterval = grainInterval;
    359 	}
    360 
    361 
    362 
    363 	public void setGrainSize(int grainSize) {
    364 		this.grainSize = grainSize;
    365 		
    366 	}
    367 
    368 	public void setGrainRandomness(float grainRandomness) {
    369 		this.grainRandomness = grainRandomness;
    370 	}
    371 
    372 
    373 
    374 	/**
    375 	 * @param position in seconds
    376 	 */
    377 	public void setPosition(float position) {
    378 		this.position = position * 1000;
    379 	}
    380 }
    381