Mercurial > hg > cmdp
view src/uk/ac/qmul/eecs/depic/daw/beads/SamplePlayer.java @ 2:c0412c81d274
Added documentation
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Thu, 18 Feb 2016 18:35:26 +0000 |
parents | 3074a84ef81e |
children |
line wrap: on
line source
/* Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.depic.daw.beads; /* * This file is part of Beads. See http://www.beadsproject.net for all information. */ import java.util.Collections; import java.util.List; import net.beadsproject.beads.core.AudioContext; import net.beadsproject.beads.core.Bead; import net.beadsproject.beads.core.UGen; import net.beadsproject.beads.ugens.Static; import uk.ac.qmul.eecs.depic.daw.Clip; import uk.ac.qmul.eecs.depic.daw.ClipList; import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; import uk.ac.qmul.eecs.depic.patterns.Range; /** * This is actually responsible for play the audio file in a track when the play button is pressed. * It implements a {@code SoundWaveListener} because it updates its lenght when the sound wave changes * * */ public class SamplePlayer extends UGen implements SoundWaveListener { public static final int NUM_CHANNELS = 2; public static final float ADAPTIVE_INTERP_LOW_THRESH = 0.5f; public static final float ADAPTIVE_INTERP_HIGH_THRESH = 2.5f; /** * Used to determine which kind of loop the sample player will use. */ public static enum LoopType { /** Play forwards without looping. */ NO_LOOP, /** Play forwards with loop. */ LOOP, }; /** The position in milliseconds. */ protected double position; /** The rate envelope. */ protected UGen rateEnvelope; /** The millisecond position increment per sample. Calculated from the ratio of the {@link AudioContext}'s sample rate and the Sample's sample rate. */ protected double positionIncrement; /** The loop type. */ protected LoopType loopType; /** Flag to determine whether playback starts at the beginning of the sample or at the beginning of the loop. */ protected boolean startLoop; /** The rate. Calculated and used internally from the rate envelope. */ protected float rate; /** The loop start. Calculated and used internally from the loop start envelope. */ protected float loopStart; /** The loop end. Calculated and used internally from the loop end envelope. */ protected float loopEnd; /** Bead responding to sample at end (only applies when not in loop mode). */ private Bead endListener; /* lenght of the sample player */ private float length; private ClipList clips; private List<Clip> clipsAtPosition; private boolean loopEnabled; /** * Instantiates a new SamplePlayer with given number of outputs. * * @param context the AudioContext. * @param outs the number of outputs. */ public SamplePlayer(AudioContext context, ClipList clips) { super(context, NUM_CHANNELS); this.clips = clips; clipsAtPosition = Collections.emptyList(); clips.addSoundWaveListener(this); rateEnvelope = new Static(context, 1.0f); loopType = LoopType.NO_LOOP; loopStart = 0.0f; loopEnd = clips.getLength(); length = 0.0f; positionIncrement = context.samplesToMs(1); } /** * Sets the playback position to the end of the Sample. */ public void setToEnd() { position = length; } /** * Determines whether the playback position is within the loop points. * * @return true if the playback position is within the loop points. */ public boolean inLoop() { return position < Math.max(loopStart, loopEnd) && position > Math.min(loopStart, loopEnd); } /** * Sets the playback position to the loop start point. */ public void setToLoopStart() { position = Math.min(loopStart, loopEnd); } /** * Starts the sample at the given position. * * @param msPosition the position in milliseconds. */ public void start(float msPosition) { position = msPosition; start(); } /** * Resets the position to the start of the Sample. */ public void reset() { position = 0f; } /** * Gets the playback position. * * @return the position in milliseconds. */ public double getPosition() { return position; } /** * Sets the playback position. This will not work if the position envelope is not null. * * @param position the new position in milliseconds. */ public void setPosition(double position) { this.position = position; } /** * Gets the rate UGen. * * @return the rate UGen. */ public UGen getRateUGen() { return rateEnvelope; } /** * Sets the rate to a UGen. * * @param rateUGen the new rate UGen. */ public void setRate(UGen rateUGen) { this.rateEnvelope = rateUGen; } /** * Gets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can * be used interchangeably). * * @return the rate envelope. */ public UGen getPitchUGen() { return rateEnvelope; } /** * Sets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can * be used interchangeably). * * @param rateUGen the new rate UGen. */ public void setPitch(UGen rateUGen) { this.rateEnvelope = rateUGen; } /** * Sets both loop points to static values as fractions of the Sample length, * overriding any UGens that were controlling the loop points. * * @param start the start value, as fraction of the Sample length. * @param end the end value, as fraction of the Sample length. */ public void setLoopPointsFraction(float start, float end) { float l = getLenght(); loopStart = start * l; loopEnd = end * l; } public Range<Float> getLoop(){ return new Range<Float>(loopStart,loopEnd); } /** * Gets the loop type. * * @return the loop type. */ public LoopType getLoopType() { return loopType; } /** * Sets the loop type. * * @param loopType the new loop type. */ public void setLoopType(LoopType loopType) { if(loopEnabled){ this.loopType = loopType; } } public void setLoopEnabled(boolean enabled){ loopEnabled = enabled; this.loopType = loopEnabled ? LoopType.LOOP : LoopType.NO_LOOP; } /** * Gets the sample rate. * * @return the sample rate, in samples per second. */ public float getSampleRate() { return rate; } @Override public void calculateBuffer(){ //major speed up possible here if these envelopes are all either null or paused //(can we pause Envelope when it is not doing anything?). //if this holds true we can tell buffer to just grab the whole frame at the given rate //and then update the position all at once. rateEnvelope.update(); for (int i = 0; i < bufferSize; i++) { /* calculate the samples */ /* initialize to silence */ for (int j = 0; j < NUM_CHANNELS; j++) { bufOut[j][i] = 0.0f; } if(!clipsAtPosition.isEmpty()){ /* if no current sample, then output silence */ for(Clip s : clipsAtPosition){ float [] frame = new float[s.getSample().getNumChannels()]; /* Always use adaptive Interpolation */ if(rate > ADAPTIVE_INTERP_HIGH_THRESH) { s.getSample().getFrameNoInterp(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); } else if(rate > ADAPTIVE_INTERP_LOW_THRESH) { s.getSample().getFrameLinear(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); } else { s.getSample().getFrameCubic(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); } /* if the sample is mono, fill the two channels with the same frame */ for (int j = 0; j < NUM_CHANNELS; j++) { bufOut[j][i] += frame[j % frame.length]; /* clip the sound */ if(bufOut[j][i] > 1.0f ) bufOut[j][i] = 1.0f; else if(bufOut[j][i] < -1.0f){ bufOut[j][i] = -1.0f; } } } } //update the position, loop state, direction calculateNextPosition(i); } } public float getLenght(){ return Math.max(length,clips.getLengthMs()); } public void setLength(float length){ this.length = length; } /** * Used at each sample in the perform routine to determine the next playback position. * * @param i the index within the buffer loop. */ protected void calculateNextPosition(int i) { rate = rateEnvelope.getValue(0, i); switch(loopType) { case NO_LOOP: position += positionIncrement * rate; if(position > clips.getLengthMs() || position < 0.0f) atEnd(); break; case LOOP: position += positionIncrement * rate; if(rate > 0 && position > Math.max(loopStart, loopEnd)) { position = Math.min(loopStart, loopEnd); } else if(rate < 0 && position < Math.min(loopStart, loopEnd)) { position = Math.max(loopStart, loopEnd); } break; } clipsAtPosition = clips.getClipsAtTime((float)position); } /** * Called when at the end of the Sample, assuming the loop mode is non-looping, or beginning, if the SamplePlayer is playing backwards.. */ private void atEnd() { if(endListener != null) { endListener.message(this); } reTrigger(); } /** * Sets a {@link Bead} that will be triggered when this SamplePlayer gets to the end. This occurs when the SamplePlayer's * position reaches then end when playing forwards in a non-looping mode, or reaches the the beginning when playing backwards in a * non-looping mode. It is never triggered in a looping mode. As an alternative, you can use the method {@link Bead.#setKillListener(Bead)} * as long as {@link #setKillOnEnd(boolean)} is set to true. In other words, you set this SamplePlayer to kill itself when it * reaches the end of the sample, and then use the functionality of {@link Bead}, which allows you to create a trigger * whenever a Bead is killed. Set to null to remove the current listener. * * @param endListener the {@link Bead} that responds to this SamplePlayer reaching its end. */ public void setEndListener(Bead endListener) { this.endListener = endListener; } /** * Gets the current endListener. * @see {#setEndListener(Bead)}. * @return the current endListener. */ public Bead getEndListener() { return endListener; } /** * Re trigger the SamplePlayer from the beginning. */ public void reTrigger() { reset(); this.pause(false); } /** * Updates the lenght when the sound wave changes */ @Override public void update(SoundWaveEvent evt) { length = clips.getLengthMs(); } }