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: