/*
* Copyright 2010 Jan Schmidt-Reinisch
*
* SoundComp - a sound processing library
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; in version 2.1
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* This parser symbol represents a tempo change annotation
*/
package de.maramuse.soundcomp.parser;
import de.maramuse.soundcomp.events.AbruptTempoChangeEvent;
import de.maramuse.soundcomp.events.ExponentialTempoChangeEvent;
import de.maramuse.soundcomp.events.HarmonicTempoChangeEvent;
import de.maramuse.soundcomp.events.InverseTempoChangeEvent;
import de.maramuse.soundcomp.events.LinearTempoChangeEvent;
import de.maramuse.soundcomp.events.ReturnToGlobalTiming;
import de.maramuse.soundcomp.events.SeamlessTempoChangeEvent;
import de.maramuse.soundcomp.events.TempoChangeEvent;
public class Bpm extends TimingEvent {
public static final int ACCELMODE_EXPONENTIAL=0;
public static final int ACCELMODE_HARMONIC=1;
public static final int ACCELMODE_INVERSE=2;
public static final int ACCELMODE_SEAMLESS=3;
public static final int ACCELMODE_ABRUPT=4;
public static final int ACCELMODE_RETURN=5;
public static final int ACCELMODE_LINEAR=6;
double startSpeed=Double.NaN, endSpeed=Double.NaN;
boolean voicelocal=false;
boolean sweep=true;
double duration=Double.NaN;
double delay=Double.NaN;
private int accelMode=ACCELMODE_ABRUPT;
public Bpm(String s) {
super(SCParser.BPM, s);
}
public Bpm assignParams(SCParser.ParserVal list){
if(list.inner.size()==0)
throw new IllegalArgumentException("missing parameters for tempo change in "+getLocationText());
for(SCParser.ParserVal val: list.inner){
assignParam(val);
}
// now check for useful combinations
if(accelMode==Bpm.ACCELMODE_ABRUPT){
if(!Double.isNaN(startSpeed)&&!Double.isNaN(endSpeed)&&startSpeed!=endSpeed){
throw new IllegalArgumentException("start and end tempo cannot be different on abrupt tempo change in "+getLocationText());
}
if(Double.isNaN(startSpeed))startSpeed=endSpeed;
if(Double.isNaN(endSpeed))endSpeed=startSpeed;
}
// we need an end tempo, unless we have a "return to global tempo" command
if(Double.isNaN(endSpeed)&&(accelMode!=Bpm.ACCELMODE_ABRUPT||!voicelocal))
throw new IllegalArgumentException("end tempo must be given for tempo change on "+getLocationText());
// later on we need a valid delay - if it is not given, take the default zero.
if(Double.isNaN(delay))
delay=0D;
return this;
}
private void assignParam(SCParser.ParserVal val){
SCParser.ParserVal dval=null;
if(val.inner.size()>0)dval=val.get(0);
switch(val.getType()){
case SCParser.STARTBPM:
if(dval==null)
throw new IllegalArgumentException("start tempo is not set in "+getLocationText());
if(!dval.isConstant())
throw new IllegalArgumentException("start tempo is not constant in "+dval.getLocationText());
if(!Double.isNaN(startSpeed))
throw new IllegalArgumentException("duplicate start tempo given in "+dval.getLocationText());
setStartSpeed(dval.asDouble());
return;
case SCParser.END:
if(dval==null)
throw new IllegalArgumentException("end tempo is not set in "+getLocationText());
if(!dval.isConstant())
throw new IllegalArgumentException("end tempo is not constant in "+dval.getLocationText());
if(!Double.isNaN(endSpeed))
throw new IllegalArgumentException("duplicate end tempo given in "+dval.getLocationText());
setEndSpeed(dval.asDouble());
return;
case SCParser.DURATION:
if(dval==null)
throw new IllegalArgumentException("tempo change duration is not set in "+getLocationText());
if(!dval.isConstant())
throw new IllegalArgumentException("tempo change duration is not constant in "+dval.getLocationText());
if(!Double.isNaN(duration))
throw new IllegalArgumentException("duplicate tempo change duration given in "+dval.getLocationText());
setDuration(dval.asDouble());
return;
case SCParser.VOICE:
setVoicelocal(true);
return;
case SCParser.DELAY:
if(dval==null)
throw new IllegalArgumentException("delay is not set in "+getLocationText());
if(!dval.isConstant())
throw new IllegalArgumentException("delay is not constant in "+dval.getLocationText());
if(!Double.isNaN(delay))
throw new IllegalArgumentException("duplicate delay given in "+dval.getLocationText());
delay=dval.asDouble();
return;
case SCParser.MEXP:
case SCParser.HARMONIC:
case SCParser.INVERSE:
case SCParser.SEAMLESS:
case SCParser.LINEAR:
if(accelMode!=Bpm.ACCELMODE_ABRUPT)
throw new IllegalArgumentException("duplicate tempo change mode given for tempo change on "+getLocationText());
setAccelMode(((AccelMode)val).getAccelMode());
return;
}
}
/**
* set the new startSpeed
*
* @param startSpeed
* the new startSpeed
* @return this
*/
public Bpm setStartSpeed(double startSpeed) {
this.startSpeed=startSpeed;
return this;
}
/**
* Returns the set startSpeed
*
* @return the set startSpeed
*/
public double getStartSpeed() {
return startSpeed;
}
/**
* set the new startSpeed
*
* @param startSpeed
* the new startSpeed
* @return this
*/
public Bpm setEndSpeed(double endSpeed) {
this.endSpeed=endSpeed;
return this;
}
/**
* Returns the set startSpeed
*
* @return the set startSpeed
*/
public double getEndSpeed() {
return endSpeed;
}
/**
* set the voicelocal flag, indicating that the tempo change is valid for the containing voice only (otherwise global
* for all voices that don't have a local tempo set)
*
* @param voicelocal
* the new voicelocal flag
* @return this
*/
Bpm setVoicelocal(boolean voicelocal) {
this.voicelocal=voicelocal;
return this;
}
/**
* Returns the voicelocal flag, indicating that the tempo change is valid for the containing voice only (@see
* Bpm.setVoicelocal(boolean voicelocal))
*
* @return the voicelocal flag
*/
@Override
public boolean isVoicelocal() {
return voicelocal;
}
/**
* Returns the sweep flag, =true if this change is not abrupt
*
* @return the sweep flag, =true if this change is not abrupt
*/
public boolean isSweep() {
return sweep;
}
/**
* Returns the duration of the tempo change, in whole beats
*
* @return the duration of the tempo change, in whole beats
*/
public double getDuration() {
return duration;
}
/**
* Returns the delay of the tempo change, in whole beats
*
* @return the delay of the tempo change, in whole beats
*/
public double getDelay() {
return delay;
}
/**
* Sets the duration of the tempo change, in whole beats
*
* @param duration
* the duration of the tempo change, in whole beats
*/
public Bpm setDuration(double duration) {
this.duration=duration;
sweep=(duration>0&&!Double.isNaN(duration));
return this;
}
public Bpm setAccelMode(int accelMode){
switch(accelMode){
case ACCELMODE_ABRUPT:
case ACCELMODE_LINEAR:
case ACCELMODE_EXPONENTIAL:
case ACCELMODE_HARMONIC:
case ACCELMODE_INVERSE:
case ACCELMODE_SEAMLESS:
case ACCELMODE_RETURN:
this.accelMode=accelMode;
return this;
default: throw new IllegalArgumentException("Unknown tempo acceleration mode "+accelMode);
}
}
@Override
public de.maramuse.soundcomp.events.TempoChangeEvent toTempoEvent() {
TempoChangeEvent tce=null;
switch(accelMode){
case ACCELMODE_ABRUPT:
tce=new AbruptTempoChangeEvent(tempTimestamp);
break;
case ACCELMODE_EXPONENTIAL:
tce=new ExponentialTempoChangeEvent(tempTimestamp);
break;
case ACCELMODE_HARMONIC:
tce=new HarmonicTempoChangeEvent(tempTimestamp);
break;
case ACCELMODE_INVERSE:
tce=new InverseTempoChangeEvent(tempTimestamp);
break;
case ACCELMODE_SEAMLESS:
tce=new SeamlessTempoChangeEvent(tempTimestamp);
break;
case ACCELMODE_RETURN:
tce=new ReturnToGlobalTiming(tempTimestamp);
break;
case ACCELMODE_LINEAR:
tce=new LinearTempoChangeEvent(tempTimestamp);
break;
default:
return null;
}
tce.setEndBpm(endSpeed);
tce.setStartBpm(startSpeed);
tce.setBeats(duration);
tce.setVoicelocal(voicelocal);
tce.init();
// tce.setBeat(beat); since this potentially is to be used in another voice,
// there might be a beat offset necessary. We do not have the necessary info here.
// if necessary, calculate it in the relevant voice where the previous tempo change,
// timestamp and beat is known.
tce.setBeat(Double.NaN);
return tce;
}
}