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