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

import java.util.ArrayList;
import java.util.List;

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.SequenceListener;

/**
 * 
 * A Wave that displays chucks in DB scale 
 *
 */
public class DbWave implements Wave {
	private Wave soundWave ;
	private Sequence peakLevelSequence;
	
	public DbWave(Wave soundWave){
		this.soundWave = soundWave;
	}
	
	@Override
	public int getChunkNum() {
		return soundWave.getChunkNum();
	}

	@Override
	public Chunk getChunkAt(int i) {
		return new DBChunk(soundWave.getChunkAt(i));
	}

	@Override
	public void setScaleFactor(int scaleFactor) {
		soundWave.setScaleFactor(scaleFactor);
	}

	@Override
	public int getScaleFactor() {
		return soundWave.getScaleFactor();
	}

	@Override
	public int getMaxScaleFactor() {
		return soundWave.getMaxScaleFactor();
	}

	@Override
	public float getWaveTime() {
		return soundWave.getWaveTime();
	}

	@Override
	public float getMillisecPerChunk() {
		return soundWave.getMillisecPerChunk();
	}
	
	/**
	 * Returns a sequence representing the peak meter line, if any 
	 * 
	 * @return the peak level sequence or {@code null} if this wave doesn't have a sequence 
	 */
	@Override
	public Sequence getSequence(){
		return peakLevelSequence;
	}
	
	public void createNewSequence(){
		peakLevelSequence = new PeakMeterSequence(this);
	}
	
	public void deleteSequence(){
		peakLevelSequence = null;
	}
	
	@Override 
	public boolean hasSequence(){
		return (peakLevelSequence != null);
	}


	private static class PeakMeterSequence implements Sequence {
		private static final float TAN_THETA = 1/5000.0f; 
		float previousPeak;
		float previousPeakTime;
		Range<Float> range;
		List<Sequence.Value> values;
		private float len;
		
		PeakMeterSequence(Wave w){
			previousPeak = 0.0f;
			previousPeakTime = 0.0f;
			values = new ArrayList<>(w.getChunkNum()/5);
			range = new Range<Float>(0.0f,1.0f);
			len =  w.getChunkNum()*w.getMillisecPerChunk();
			
			for(int i=0; i<w.getChunkNum();i++){
				Chunk c = w.getChunkAt(i);
				final float time = i*w.getMillisecPerChunk();
				
				/* decayTime is the time where the previous peak was, plus the time  *
				 * that the peak line will take, to decay to zero                    */
				float decayTime = previousPeakTime +(previousPeak / TAN_THETA);
				
				float peakLineDecayPoint = 0.0f;
				/* if the peak line has already decayed to zero in the past, * 
				 * peakLineDecayPoint will just be left to 0                 */
				if(time < decayTime || MathUtils.equal(time, decayTime,w.getMillisecPerChunk()/2)){
					/* timeLeftToZero is always > 0, otherwise would not be in this block  */
					float timeLeftToZero = decayTime - time;
					/* returns the y value of the peak line at timePoistion */
					peakLineDecayPoint = timeLeftToZero * TAN_THETA;
				}
				
				if(c.getNormEnd() > peakLineDecayPoint) {
					final int index = values.size();
					final float value = c.getNormEnd();
					
					/* this chunk was higher than the peak line at time * 
					 * position therefore it becomes the new peak value */
					previousPeak = value;
					previousPeakTime = time;
					
					values.add(new Sequence.Value() {
						@Override
						public int index() {
							return index;
						}
						
						@Override
						public float getValue() {
							return value;
						}
						
						@Override
						public float getTimePosition() {
							return time;
						}
						
						@Override
						public Sequence getSequence() {
							return PeakMeterSequence.this;
						}
						
						@Override
						public String toString(){
							return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition();
						}
					});
				}else if( MathUtils.equal(time, decayTime, w.getMillisecPerChunk())){
					previousPeak = 0.0f;
					previousPeakTime = time;
					final int index = values.size();
					values.add(new Sequence.Value() {
						@Override
						public int index() {
							return index;
						}
						
						@Override
						public float getValue() {
							return 0.0f;
						}
						
						@Override
						public float getTimePosition() {
							return time;
						}
						
						@Override
						public Sequence getSequence() {
							return PeakMeterSequence.this;
						}
						
						@Override
						public String toString(){
							return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition();
						}
					});
				}
			}
		}
		
		/**
		 * Calculates the point on the time line (x-axis) where the peak curve will be completely decayed 
		 * to zero. 
		 * 
		 * @param time the time of the decay point 
		 * @param previousPeak the previous peak value. that is where the line has started decaying 
		 * @param previousPeakTime the time of the previous peak value 
		 *  
		 * @return
		 */
		float calculateDecayLineAtTime(float time,float previousPeak, float previousPeakTime){
			/* decayTime is the time where the previous peak was plus the time  *
			 * that the peak line will take to decay to zero                    */
			float decayTime = previousPeakTime +(previousPeak / TAN_THETA);
			
			/* the peak line has already decayed to zero in the past */
			if(time > decayTime){
				return  0.0f;
			}
			
			/* always > 0, otherwise would have returned already */
			float timeLeftToZero = decayTime - time;
			/* returns the y value of the peak line at timePoistion */
			return timeLeftToZero * TAN_THETA;
		}
		
		@Override
		public float getBegin() {
			return 0.0f;
		}
	
		@Override
		public float getEnd() {
			return values.isEmpty() ? getBegin() : values.get(values.size()-1).getValue();
		}
	
		@Override
		public int getValuesNum() {
			return values.size();
		}
	
		@Override
		public Value getValueAt(int index) {
			return values.get(index);
		}
	
		@Override
		public Range<Float> getRange() {
			return range;
		}
	
		@Override
		public void addSequenceListener(SequenceListener l) {
			// this sequence is immutable, therefore listeners would never be notfied
		}
	
		@Override
		public void removeSequenceListener(SequenceListener l) {
			// this sequence is immutable, therefore listeners would never be notfied
		}
	
		@Override
		public float getLen() {
			return len;
		}
		
	}
	
}

class DBChunk extends Chunk {
	float normStart;
	float normEnd;
	
	DBChunk(Chunk c) {
		super((short)0,(short)0);
		start = MathUtils.toDb(Math.abs(c.getStart()));
		if(Float.isInfinite(start)){
			start = 0.0f;
		}
		end = MathUtils.toDb(Math.abs(c.getEnd()));
		if(Float.isInfinite(end)){
			end = 0.0f;
		}
		
		normStart = 0.0f;
		
		float max = Math.max(Math.abs(c.getStart()),Math.abs(c.getEnd()));
		float db = MathUtils.toDb(max);
		if(Float.isInfinite(db)){
			normEnd = 0;
		}else if(db < -60){
			normEnd = 0;
		}else{
			normEnd = new MathUtils.Scale(-60.0f, 0.0f, 0.0f, 1.0f).linear(db);
		}
	}

	@Override
	public float getNormStart() {
		return normStart;
	}

	@Override
	public float getNormEnd() {
		return normEnd;
	}
	
}