annotate src/uk/ac/qmul/eecs/depic/daw/beads/SamplePlayer.java @ 4:473da40f3d39 tip

added html formatting to Daw/package-info.java
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 25 Feb 2016 17:50:09 +0000
parents c0412c81d274
children
rev   line source
f@0 1 /*
f@0 2 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.
f@0 3
f@0 4 Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19 package uk.ac.qmul.eecs.depic.daw.beads;
f@0 20
f@0 21 /*
f@0 22 * This file is part of Beads. See http://www.beadsproject.net for all information.
f@0 23 */
f@0 24
f@0 25 import java.util.Collections;
f@0 26 import java.util.List;
f@0 27
f@0 28 import net.beadsproject.beads.core.AudioContext;
f@0 29 import net.beadsproject.beads.core.Bead;
f@0 30 import net.beadsproject.beads.core.UGen;
f@0 31 import net.beadsproject.beads.ugens.Static;
f@0 32 import uk.ac.qmul.eecs.depic.daw.Clip;
f@0 33 import uk.ac.qmul.eecs.depic.daw.ClipList;
f@0 34 import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent;
f@0 35 import uk.ac.qmul.eecs.depic.daw.SoundWaveListener;
f@0 36 import uk.ac.qmul.eecs.depic.patterns.Range;
f@0 37
f@0 38 /**
f@2 39 * This is actually responsible for play the audio file in a track when the play button is pressed.
f@2 40 * It implements a {@code SoundWaveListener} because it updates its lenght when the sound wave changes
f@2 41 *
f@2 42 *
f@0 43 */
f@0 44 public class SamplePlayer extends UGen implements SoundWaveListener {
f@0 45
f@0 46 public static final int NUM_CHANNELS = 2;
f@0 47 public static final float ADAPTIVE_INTERP_LOW_THRESH = 0.5f;
f@0 48 public static final float ADAPTIVE_INTERP_HIGH_THRESH = 2.5f;
f@0 49
f@0 50
f@0 51 /**
f@0 52 * Used to determine which kind of loop the sample player will use.
f@0 53 */
f@0 54 public static enum LoopType {
f@0 55 /** Play forwards without looping. */
f@0 56 NO_LOOP,
f@0 57 /** Play forwards with loop. */
f@0 58 LOOP,
f@0 59 };
f@0 60
f@0 61
f@0 62 /** The position in milliseconds. */
f@0 63 protected double position;
f@0 64
f@0 65 /** The rate envelope. */
f@0 66 protected UGen rateEnvelope;
f@0 67
f@0 68 /** The millisecond position increment per sample. Calculated from the ratio of the {@link AudioContext}'s sample rate and the Sample's sample rate. */
f@0 69 protected double positionIncrement;
f@0 70
f@0 71
f@0 72 /** The loop type. */
f@0 73 protected LoopType loopType;
f@0 74
f@0 75 /** Flag to determine whether playback starts at the beginning of the sample or at the beginning of the loop. */
f@0 76 protected boolean startLoop;
f@0 77
f@0 78
f@0 79 /** The rate. Calculated and used internally from the rate envelope. */
f@0 80 protected float rate;
f@0 81
f@0 82 /** The loop start. Calculated and used internally from the loop start envelope. */
f@0 83 protected float loopStart;
f@0 84
f@0 85 /** The loop end. Calculated and used internally from the loop end envelope. */
f@0 86 protected float loopEnd;
f@0 87
f@0 88 /** Bead responding to sample at end (only applies when not in loop mode). */
f@0 89 private Bead endListener;
f@0 90
f@0 91 /* lenght of the sample player */
f@0 92 private float length;
f@0 93
f@0 94 private ClipList clips;
f@0 95
f@0 96 private List<Clip> clipsAtPosition;
f@0 97 private boolean loopEnabled;
f@0 98
f@0 99
f@0 100 /**
f@0 101 * Instantiates a new SamplePlayer with given number of outputs.
f@0 102 *
f@0 103 * @param context the AudioContext.
f@0 104 * @param outs the number of outputs.
f@0 105 */
f@0 106 public SamplePlayer(AudioContext context, ClipList clips) {
f@0 107 super(context, NUM_CHANNELS);
f@0 108 this.clips = clips;
f@0 109 clipsAtPosition = Collections.emptyList();
f@0 110 clips.addSoundWaveListener(this);
f@0 111 rateEnvelope = new Static(context, 1.0f);
f@0 112 loopType = LoopType.NO_LOOP;
f@0 113 loopStart = 0.0f;
f@0 114 loopEnd = clips.getLength();
f@0 115 length = 0.0f;
f@0 116 positionIncrement = context.samplesToMs(1);
f@0 117 }
f@0 118
f@0 119
f@0 120 /**
f@0 121 * Sets the playback position to the end of the Sample.
f@0 122 */
f@0 123 public void setToEnd() {
f@0 124 position = length;
f@0 125 }
f@0 126
f@0 127 /**
f@0 128 * Determines whether the playback position is within the loop points.
f@0 129 *
f@0 130 * @return true if the playback position is within the loop points.
f@0 131 */
f@0 132 public boolean inLoop() {
f@0 133 return position < Math.max(loopStart, loopEnd) && position > Math.min(loopStart, loopEnd);
f@0 134 }
f@0 135
f@0 136 /**
f@0 137 * Sets the playback position to the loop start point.
f@0 138 */
f@0 139 public void setToLoopStart() {
f@0 140 position = Math.min(loopStart, loopEnd);
f@0 141 }
f@0 142
f@0 143 /**
f@0 144 * Starts the sample at the given position.
f@0 145 *
f@0 146 * @param msPosition the position in milliseconds.
f@0 147 */
f@0 148 public void start(float msPosition) {
f@0 149 position = msPosition;
f@0 150 start();
f@0 151 }
f@0 152
f@0 153 /**
f@0 154 * Resets the position to the start of the Sample.
f@0 155 */
f@0 156 public void reset() {
f@0 157 position = 0f;
f@0 158 }
f@0 159
f@0 160 /**
f@0 161 * Gets the playback position.
f@0 162 *
f@0 163 * @return the position in milliseconds.
f@0 164 */
f@0 165 public double getPosition() {
f@0 166 return position;
f@0 167 }
f@0 168
f@0 169 /**
f@0 170 * Sets the playback position. This will not work if the position envelope is not null.
f@0 171 *
f@0 172 * @param position the new position in milliseconds.
f@0 173 */
f@0 174 public void setPosition(double position) {
f@0 175 this.position = position;
f@0 176 }
f@0 177
f@0 178
f@0 179 /**
f@0 180 * Gets the rate UGen.
f@0 181 *
f@0 182 * @return the rate UGen.
f@0 183 */
f@0 184 public UGen getRateUGen() {
f@0 185 return rateEnvelope;
f@0 186 }
f@0 187
f@0 188
f@0 189 /**
f@0 190 * Sets the rate to a UGen.
f@0 191 *
f@0 192 * @param rateUGen the new rate UGen.
f@0 193 */
f@0 194 public void setRate(UGen rateUGen) {
f@0 195 this.rateEnvelope = rateUGen;
f@0 196 }
f@0 197
f@0 198 /**
f@0 199 * Gets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can
f@0 200 * be used interchangeably).
f@0 201 *
f@0 202 * @return the rate envelope.
f@0 203 */
f@0 204 public UGen getPitchUGen() {
f@0 205 return rateEnvelope;
f@0 206 }
f@0 207
f@0 208
f@0 209 /**
f@0 210 * Sets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can
f@0 211 * be used interchangeably).
f@0 212 *
f@0 213 * @param rateUGen the new rate UGen.
f@0 214 */
f@0 215 public void setPitch(UGen rateUGen) {
f@0 216 this.rateEnvelope = rateUGen;
f@0 217 }
f@0 218
f@0 219
f@0 220
f@0 221 /**
f@0 222 * Sets both loop points to static values as fractions of the Sample length,
f@0 223 * overriding any UGens that were controlling the loop points.
f@0 224 *
f@0 225 * @param start the start value, as fraction of the Sample length.
f@0 226 * @param end the end value, as fraction of the Sample length.
f@0 227 */
f@0 228 public void setLoopPointsFraction(float start, float end) {
f@0 229 float l = getLenght();
f@0 230 loopStart = start * l;
f@0 231 loopEnd = end * l;
f@0 232 }
f@0 233
f@0 234 public Range<Float> getLoop(){
f@0 235 return new Range<Float>(loopStart,loopEnd);
f@0 236 }
f@0 237
f@0 238 /**
f@0 239 * Gets the loop type.
f@0 240 *
f@0 241 * @return the loop type.
f@0 242 */
f@0 243 public LoopType getLoopType() {
f@0 244 return loopType;
f@0 245 }
f@0 246
f@0 247 /**
f@0 248 * Sets the loop type.
f@0 249 *
f@0 250 * @param loopType the new loop type.
f@0 251 */
f@0 252 public void setLoopType(LoopType loopType) {
f@0 253 if(loopEnabled){
f@0 254 this.loopType = loopType;
f@0 255 }
f@0 256 }
f@0 257
f@0 258
f@0 259 public void setLoopEnabled(boolean enabled){
f@0 260 loopEnabled = enabled;
f@0 261 this.loopType = loopEnabled ? LoopType.LOOP : LoopType.NO_LOOP;
f@0 262 }
f@0 263
f@0 264 /**
f@0 265 * Gets the sample rate.
f@0 266 *
f@0 267 * @return the sample rate, in samples per second.
f@0 268 */
f@0 269 public float getSampleRate() {
f@0 270 return rate;
f@0 271 }
f@0 272
f@0 273
f@0 274 @Override
f@0 275 public void calculateBuffer(){
f@0 276 //major speed up possible here if these envelopes are all either null or paused
f@0 277 //(can we pause Envelope when it is not doing anything?).
f@0 278 //if this holds true we can tell buffer to just grab the whole frame at the given rate
f@0 279 //and then update the position all at once.
f@0 280 rateEnvelope.update();
f@0 281
f@0 282 for (int i = 0; i < bufferSize; i++) {
f@0 283 /* calculate the samples */
f@0 284
f@0 285 /* initialize to silence */
f@0 286 for (int j = 0; j < NUM_CHANNELS; j++) {
f@0 287 bufOut[j][i] = 0.0f;
f@0 288 }
f@0 289
f@0 290 if(!clipsAtPosition.isEmpty()){ /* if no current sample, then output silence */
f@0 291 for(Clip s : clipsAtPosition){
f@0 292 float [] frame = new float[s.getSample().getNumChannels()];
f@0 293 /* Always use adaptive Interpolation */
f@0 294 if(rate > ADAPTIVE_INTERP_HIGH_THRESH) {
f@0 295 s.getSample().getFrameNoInterp(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame);
f@0 296 } else if(rate > ADAPTIVE_INTERP_LOW_THRESH) {
f@0 297 s.getSample().getFrameLinear(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame);
f@0 298 } else {
f@0 299 s.getSample().getFrameCubic(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame);
f@0 300 }
f@0 301
f@0 302 /* if the sample is mono, fill the two channels with the same frame */
f@0 303 for (int j = 0; j < NUM_CHANNELS; j++) {
f@0 304 bufOut[j][i] += frame[j % frame.length];
f@0 305 /* clip the sound */
f@0 306 if(bufOut[j][i] > 1.0f )
f@0 307 bufOut[j][i] = 1.0f;
f@0 308 else if(bufOut[j][i] < -1.0f){
f@0 309 bufOut[j][i] = -1.0f;
f@0 310 }
f@0 311 }
f@0 312 }
f@0 313 }
f@0 314
f@0 315 //update the position, loop state, direction
f@0 316 calculateNextPosition(i);
f@0 317 }
f@0 318 }
f@0 319
f@0 320 public float getLenght(){
f@0 321 return Math.max(length,clips.getLengthMs());
f@0 322 }
f@0 323
f@0 324 public void setLength(float length){
f@0 325 this.length = length;
f@0 326 }
f@0 327
f@0 328 /**
f@0 329 * Used at each sample in the perform routine to determine the next playback position.
f@0 330 *
f@0 331 * @param i the index within the buffer loop.
f@0 332 */
f@0 333 protected void calculateNextPosition(int i) {
f@0 334 rate = rateEnvelope.getValue(0, i);
f@0 335 switch(loopType) {
f@0 336 case NO_LOOP:
f@0 337 position += positionIncrement * rate;
f@0 338 if(position > clips.getLengthMs() || position < 0.0f)
f@0 339 atEnd();
f@0 340 break;
f@0 341 case LOOP:
f@0 342 position += positionIncrement * rate;
f@0 343 if(rate > 0 && position > Math.max(loopStart, loopEnd)) {
f@0 344 position = Math.min(loopStart, loopEnd);
f@0 345 } else if(rate < 0 && position < Math.min(loopStart, loopEnd)) {
f@0 346 position = Math.max(loopStart, loopEnd);
f@0 347 }
f@0 348 break;
f@0 349 }
f@0 350 clipsAtPosition = clips.getClipsAtTime((float)position);
f@0 351 }
f@0 352
f@0 353 /**
f@0 354 * Called when at the end of the Sample, assuming the loop mode is non-looping, or beginning, if the SamplePlayer is playing backwards..
f@0 355 */
f@0 356 private void atEnd() {
f@0 357 if(endListener != null) {
f@0 358 endListener.message(this);
f@0 359 }
f@0 360 reTrigger();
f@0 361 }
f@0 362
f@0 363 /**
f@0 364 * Sets a {@link Bead} that will be triggered when this SamplePlayer gets to the end. This occurs when the SamplePlayer's
f@0 365 * position reaches then end when playing forwards in a non-looping mode, or reaches the the beginning when playing backwards in a
f@0 366 * non-looping mode. It is never triggered in a looping mode. As an alternative, you can use the method {@link Bead.#setKillListener(Bead)}
f@0 367 * as long as {@link #setKillOnEnd(boolean)} is set to true. In other words, you set this SamplePlayer to kill itself when it
f@0 368 * reaches the end of the sample, and then use the functionality of {@link Bead}, which allows you to create a trigger
f@0 369 * whenever a Bead is killed. Set to null to remove the current listener.
f@0 370 *
f@0 371 * @param endListener the {@link Bead} that responds to this SamplePlayer reaching its end.
f@0 372 */
f@0 373 public void setEndListener(Bead endListener) {
f@0 374 this.endListener = endListener;
f@0 375 }
f@0 376
f@0 377 /**
f@0 378 * Gets the current endListener.
f@0 379 * @see {#setEndListener(Bead)}.
f@0 380 * @return the current endListener.
f@0 381 */
f@0 382 public Bead getEndListener() {
f@0 383 return endListener;
f@0 384 }
f@0 385
f@0 386 /**
f@0 387 * Re trigger the SamplePlayer from the beginning.
f@0 388 */
f@0 389 public void reTrigger() {
f@0 390 reset();
f@0 391 this.pause(false);
f@0 392 }
f@0 393
f@0 394 /**
f@0 395 * Updates the lenght when the sound wave changes
f@0 396 */
f@0 397 @Override
f@0 398 public void update(SoundWaveEvent evt) {
f@0 399 length = clips.getLengthMs();
f@0 400 }
f@0 401
f@0 402 }
f@0 403