f@0: /* f@0: Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. f@0: f@0: Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: package uk.ac.qmul.eecs.depic.daw; f@0: f@0: import java.util.ArrayList; f@0: import java.util.List; f@0: f@0: import uk.ac.qmul.eecs.depic.patterns.MathUtils; f@0: import uk.ac.qmul.eecs.depic.patterns.Range; f@0: import uk.ac.qmul.eecs.depic.patterns.Sequence; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceListener; f@0: f@1: /** f@1: * f@1: * A Wave that displays chucks in DB scale f@1: * f@1: */ f@0: public class DbWave implements Wave { f@0: private Wave soundWave ; f@0: private Sequence peakLevelSequence; f@0: f@0: public DbWave(Wave soundWave){ f@0: this.soundWave = soundWave; f@0: } f@0: f@0: @Override f@0: public int getChunkNum() { f@0: return soundWave.getChunkNum(); f@0: } f@0: f@0: @Override f@0: public Chunk getChunkAt(int i) { f@0: return new DBChunk(soundWave.getChunkAt(i)); f@0: } f@0: f@0: @Override f@0: public void setScaleFactor(int scaleFactor) { f@0: soundWave.setScaleFactor(scaleFactor); f@0: } f@0: f@0: @Override f@0: public int getScaleFactor() { f@0: return soundWave.getScaleFactor(); f@0: } f@0: f@0: @Override f@0: public int getMaxScaleFactor() { f@0: return soundWave.getMaxScaleFactor(); f@0: } f@0: f@0: @Override f@0: public float getWaveTime() { f@0: return soundWave.getWaveTime(); f@0: } f@0: f@0: @Override f@0: public float getMillisecPerChunk() { f@0: return soundWave.getMillisecPerChunk(); f@0: } f@0: f@0: /** f@0: * Returns a sequence representing the peak meter line, if any f@0: * f@0: * @return the peak level sequence or {@code null} if this wave doesn't have a sequence f@0: */ f@0: @Override f@0: public Sequence getSequence(){ f@0: return peakLevelSequence; f@0: } f@0: f@0: public void createNewSequence(){ f@0: peakLevelSequence = new PeakMeterSequence(this); f@0: } f@0: f@0: public void deleteSequence(){ f@0: peakLevelSequence = null; f@0: } f@0: f@0: @Override f@0: public boolean hasSequence(){ f@0: return (peakLevelSequence != null); f@0: } f@0: f@0: f@0: private static class PeakMeterSequence implements Sequence { f@0: private static final float TAN_THETA = 1/5000.0f; f@0: float previousPeak; f@0: float previousPeakTime; f@0: Range range; f@0: List values; f@0: private float len; f@0: f@0: PeakMeterSequence(Wave w){ f@0: previousPeak = 0.0f; f@0: previousPeakTime = 0.0f; f@0: values = new ArrayList<>(w.getChunkNum()/5); f@0: range = new Range(0.0f,1.0f); f@0: len = w.getChunkNum()*w.getMillisecPerChunk(); f@0: f@0: for(int i=0; i 0, otherwise would not be in this block */ f@0: float timeLeftToZero = decayTime - time; f@0: /* returns the y value of the peak line at timePoistion */ f@0: peakLineDecayPoint = timeLeftToZero * TAN_THETA; f@0: } f@0: f@0: if(c.getNormEnd() > peakLineDecayPoint) { f@0: final int index = values.size(); f@0: final float value = c.getNormEnd(); f@0: f@0: /* this chunk was higher than the peak line at time * f@0: * position therefore it becomes the new peak value */ f@0: previousPeak = value; f@0: previousPeakTime = time; f@0: f@0: values.add(new Sequence.Value() { f@0: @Override f@0: public int index() { f@0: return index; f@0: } f@0: f@0: @Override f@0: public float getValue() { f@0: return value; f@0: } f@0: f@0: @Override f@0: public float getTimePosition() { f@0: return time; f@0: } f@0: f@0: @Override f@0: public Sequence getSequence() { f@0: return PeakMeterSequence.this; f@0: } f@0: f@0: @Override f@0: public String toString(){ f@0: return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition(); f@0: } f@0: }); f@0: }else if( MathUtils.equal(time, decayTime, w.getMillisecPerChunk())){ f@0: previousPeak = 0.0f; f@0: previousPeakTime = time; f@0: final int index = values.size(); f@0: values.add(new Sequence.Value() { f@0: @Override f@0: public int index() { f@0: return index; f@0: } f@0: f@0: @Override f@0: public float getValue() { f@0: return 0.0f; f@0: } f@0: f@0: @Override f@0: public float getTimePosition() { f@0: return time; f@0: } f@0: f@0: @Override f@0: public Sequence getSequence() { f@0: return PeakMeterSequence.this; f@0: } f@0: f@0: @Override f@0: public String toString(){ f@0: return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition(); f@0: } f@0: }); f@0: } f@0: } f@0: } f@0: f@0: /** f@0: * Calculates the point on the time line (x-axis) where the peak curve will be completely decayed f@0: * to zero. f@0: * f@0: * @param time the time of the decay point f@0: * @param previousPeak the previous peak value. that is where the line has started decaying f@0: * @param previousPeakTime the time of the previous peak value f@0: * f@0: * @return f@0: */ f@0: float calculateDecayLineAtTime(float time,float previousPeak, float previousPeakTime){ f@0: /* decayTime is the time where the previous peak was plus the time * f@0: * that the peak line will take to decay to zero */ f@0: float decayTime = previousPeakTime +(previousPeak / TAN_THETA); f@0: f@0: /* the peak line has already decayed to zero in the past */ f@0: if(time > decayTime){ f@0: return 0.0f; f@0: } f@0: f@0: /* always > 0, otherwise would have returned already */ f@0: float timeLeftToZero = decayTime - time; f@0: /* returns the y value of the peak line at timePoistion */ f@0: return timeLeftToZero * TAN_THETA; f@0: } f@0: f@0: @Override f@0: public float getBegin() { f@0: return 0.0f; f@0: } f@0: f@0: @Override f@0: public float getEnd() { f@0: return values.isEmpty() ? getBegin() : values.get(values.size()-1).getValue(); f@0: } f@0: f@0: @Override f@0: public int getValuesNum() { f@0: return values.size(); f@0: } f@0: f@0: @Override f@0: public Value getValueAt(int index) { f@0: return values.get(index); f@0: } f@0: f@0: @Override f@0: public Range getRange() { f@0: return range; f@0: } f@0: f@0: @Override f@0: public void addSequenceListener(SequenceListener l) { f@0: // this sequence is immutable, therefore listeners would never be notfied f@0: } f@0: f@0: @Override f@0: public void removeSequenceListener(SequenceListener l) { f@0: // this sequence is immutable, therefore listeners would never be notfied f@0: } f@0: f@0: @Override f@0: public float getLen() { f@0: return len; f@0: } f@0: f@0: } f@0: f@0: } f@0: f@0: class DBChunk extends Chunk { f@0: float normStart; f@0: float normEnd; f@0: f@0: DBChunk(Chunk c) { f@0: super((short)0,(short)0); f@0: start = MathUtils.toDb(Math.abs(c.getStart())); f@0: if(Float.isInfinite(start)){ f@0: start = 0.0f; f@0: } f@0: end = MathUtils.toDb(Math.abs(c.getEnd())); f@0: if(Float.isInfinite(end)){ f@0: end = 0.0f; f@0: } f@0: f@0: normStart = 0.0f; f@0: f@0: float max = Math.max(Math.abs(c.getStart()),Math.abs(c.getEnd())); f@0: float db = MathUtils.toDb(max); f@0: if(Float.isInfinite(db)){ f@0: normEnd = 0; f@0: }else if(db < -60){ f@0: normEnd = 0; f@0: }else{ f@0: normEnd = new MathUtils.Scale(-60.0f, 0.0f, 0.0f, 1.0f).linear(db); f@0: } f@0: } f@0: f@0: @Override f@0: public float getNormStart() { f@0: return normStart; f@0: } f@0: f@0: @Override f@0: public float getNormEnd() { f@0: return normEnd; f@0: } f@0: f@0: } f@0: