view src/uk/ac/qmul/eecs/depic/daw/referencesound/BeadsSequenceMapping.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.referencesound;

import net.beadsproject.beads.core.AudioContext;
import net.beadsproject.beads.core.Bead;
import net.beadsproject.beads.ugens.Gain;
import uk.ac.qmul.eecs.depic.patterns.MathUtils;
import uk.ac.qmul.eecs.depic.patterns.Range;
import uk.ac.qmul.eecs.depic.patterns.Sequence;
import uk.ac.qmul.eecs.depic.patterns.Sequence.Value;
import uk.ac.qmul.eecs.depic.patterns.SequenceMapping;

/**
 * Implements {@code uk.ac.qmul.eecs.depic.patterns.SequenceMapping} using the Beads sound engine
 * It's a slightly modified version of {@code uk.ac.qmul.eecs.depic.daw.beads.BeadsSequenceMapping}
 * that includes the one ref and multiple ref sonification, used in GridSound.  
 * 
 *
 */
abstract class BeadsSequenceMapping implements SequenceMapping {
	public static final float DEFAULT_VALUE_LEN_MS = 50.0f; 
	public static final float DEFAULT_VALUE_REF_LEN_MS = 50.0f; 
	public static final float DEFAULT_VALUE_AMP = .5f; 
	public static boolean verboseMode = true;
	
	protected static final float RATE_STEP = 0.1f; 
	
	protected AudioContext ac;
	/* the master gain */
	protected Gain masterGain;
	
	private PitchedUGen renderCurveAtUGen;
	private Gain renderCurveGain;
	private float renderValueLen;
	private float renderValueRefLen;
	private float renderValueAmp;
	
	private Bead lastTrail;
	
	
	/**
	 * 
	 * @param ac an audio context. The audio context must be started outside this class 
	 */
	protected BeadsSequenceMapping(AudioContext ac){
		this.ac = ac;
		masterGain = new Gain(ac, 1, 1.0f);
		ac.out.addInput(masterGain);
		renderValueLen = DEFAULT_VALUE_LEN_MS;
		renderValueRefLen = DEFAULT_VALUE_REF_LEN_MS;
		
		renderValueAmp = DEFAULT_VALUE_AMP;
		
	}
	

	/**
	 * 
	 * returns the ugen used to render a sequence value 
	 * 
	 * @return
	 */
	protected abstract PitchedUGen createRenderValueUGen();
	
	
	/**
	 * returns a 
	 * @return
	 */
	protected abstract PitchedUGen createRenderCurveUGen();
	
	protected abstract Range<Float> getRenderCurvePitchSpan();
	
	protected abstract Range<Float> getRenderValuePitchSpan();
	
	public void setRenderValueRefLen(float renderValueRefLen) {
		this.renderValueRefLen = renderValueRefLen;
	}


	public float getRenderValueRefLen() {
		return renderValueRefLen;
	}


	public void setRenderValueLen(float milliseconds){
		renderValueLen = milliseconds;
	}
	
	public float getRenderValueLen(){
		return renderValueLen;
	}
	
	public float getRenderValueAmp() {
		return renderValueAmp;
	}

	public void setRenderValueAmp(float renderValueAmp) {
		this.renderValueAmp = renderValueAmp;
	}

	
	@Override
	public void renderValue(Value val) {
		
		if(lastTrail != null){
			lastTrail.kill();
			lastTrail = null;
		}
		
		float v = val.getValue();
		
		Sequence sequence = val.getSequence();
		Range<Float> range = sequence.getRange();
		
		/* Map the value to the pitch: */ 
		MathUtils.Scale scale =  new MathUtils.Scale(
				range, 
				getRenderValuePitchSpan()
				);
		
		float pitch = scale.exponential(v);
		
		PitchedUGen valuePlayer = createRenderValueUGen(); 
		valuePlayer.setLen(renderValueAmp, renderValueLen);
		valuePlayer.setPitch(pitch);  
		
		masterGain.addInput(valuePlayer);
	}
	
	public void renderValueOneRef(Value val){
		if(lastTrail != null){
			lastTrail.kill();
			lastTrail = null;
		}
		
		Range<Float> r = val.getSequence().getRange();
		float v = r.getStart() + (r.getEnd() - r.getStart())/2 ;
		
		Sequence sequence = val.getSequence();
		Range<Float> range = sequence.getRange();
		
		/* Map the value to the pitch: */ 
		MathUtils.Scale scale =  new MathUtils.Scale(
				range, 
				getRenderValuePitchSpan()
				);
		
		float pitch = scale.exponential(v);
		
		PitchedUGen valuePlayer = createRenderValueUGen(); 
		valuePlayer.setLen(renderValueAmp, renderValueLen);
		valuePlayer.setPitch(pitch);  
		
		masterGain.addInput(valuePlayer);
	}
	
	public void renderValueMulRef(Value val, float resolution) {
		
		if(lastTrail != null){
			lastTrail.kill();
			lastTrail = null;
		}
		
		Range<Float> r = val.getSequence().getRange();
		float reference = r.getStart() + (r.getEnd() - r.getStart())/2;
		
		float v = val.getValue();
		Sequence sequence = val.getSequence();
		Range<Float> range = sequence.getRange();

		if(!rangeContains(range,v) || !rangeContains(range,reference)){
			throw new IllegalArgumentException("One or more values not in range val:"+ v + " reference:" +reference);
		}
		
		if(Float.compare(resolution, 0) <= 0){
			throw new IllegalArgumentException("Resolution must be positive. resolution:"+resolution);
		}
		
		/* if the step is bigger than the start and end make the step = end-start */
		if(resolution > Math.abs(v-reference) ){
			resolution = Math.abs(v-reference);
		}
		
		/* Map the value to the pitch: */ 
		MathUtils.Scale scale =  new MathUtils.Scale(
				range, 
				getRenderValuePitchSpan()
			);
		
		final PitchedUGen valuePlayer = createRenderValueUGen();
		
		boolean onePitchOnly = true;
		if(v > reference){ // scale from v down to reference 
			/* goes all the way from v-resolution to the last value before reaching the 
			 * reference, each step is of resolution  */
			for(float i=v-resolution; i>=reference; i = i-resolution){
				final float nextPitch = scale.exponential(i);

				if(i == v-resolution){ // first run
					valuePlayer.setPitch(nextPitch);
				}else{
					onePitchOnly = false;
					valuePlayer.addLen(renderValueAmp, renderValueRefLen,new Bead(){
						@Override
						public void messageReceived(Bead bead){
							valuePlayer.setPitch(nextPitch);
						}
					});
				}
			}
			
			/* add the last bit of envelope that will kill the UGen */
			/* if the envelope has many pitches already used addLen which, if env is != null 
			 * will bring it to 0. Otherwise use setLen that brings the envelope to 0 */
			if(onePitchOnly){
				valuePlayer.setLen(renderValueAmp, renderValueRefLen);
			}else{
				valuePlayer.addLen(renderValueAmp, renderValueRefLen);				
			}
			
			lastTrail = valuePlayer;
			masterGain.addInput(valuePlayer);
		}else if(v < reference){ // scale from v up to reference
			
			for(float i=v+resolution; i<=reference; i = i+resolution){
				final float nextPitch = scale.exponential(i);

				if(i == v+resolution){ // first run
					valuePlayer.setPitch(nextPitch);
				}else{
					onePitchOnly = false;
					valuePlayer.addLen(renderValueAmp, renderValueRefLen,new Bead(){
						@Override
						public void messageReceived(Bead bead){
							valuePlayer.setPitch(nextPitch);
						}
					});
				}
			}
			
			/* add the last bit of envelope that will kill the UGen */
			/* if the envelope has many pitches already used addLen which, if env is != null 
			 * will bring it to 0. Otherwise use setLen that brings the envelope to 0 */
			if(onePitchOnly){
				valuePlayer.setLen(renderValueAmp, renderValueRefLen);
			}else{
				valuePlayer.addLen(renderValueAmp, renderValueRefLen);				
			}
			
			lastTrail = valuePlayer;
			masterGain.addInput(valuePlayer);
			
		}
	}
	
	private static boolean rangeContains(Range<Float> range, float f){
		return (Float.compare(f, range.getStart()) >= 0 && Float.compare(f, range.getEnd()) <= 0); 
	}
	
	@Override
	public void renderCurveAt(Sequence sequence, float time, float duration){
		/* negative value means stop now */
		if(duration < 0.0f ){
			if(renderCurveAtUGen != null)
				renderCurveAtUGen.kill();
			renderCurveAtUGen = null;
			return;
		}		
		
		/* look for the two sequence values, whose times contain time in between */
		float valueAtTime = new MathUtils.Interpolate(sequence).linear(time);
		float pitch = new MathUtils.Scale(sequence.getRange(),getRenderCurvePitchSpan()).linear(valueAtTime);
		
		/* infinite duration means play until renderCurveAt is called again with duration = -1 */
		if(Float.isInfinite(duration)){
			if(renderCurveAtUGen == null){
				renderCurveAtUGen = createRenderCurveUGen();
				renderCurveAtUGen.setPitch(pitch);
				masterGain.addInput(renderCurveAtUGen);
				renderCurveAtUGen.start();
			}else{
				renderCurveAtUGen.setPitch(pitch);
			}
		}else if(duration > 0.0f){
				if(renderCurveAtUGen == null){
					renderCurveAtUGen = createRenderCurveUGen();
					renderCurveAtUGen.setPitch(pitch);
					renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f);
					ac.out.addInput(renderCurveAtUGen);
					renderCurveAtUGen.start();
				}else{
					renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f);
				}
		}
	}
	
}