package de.maramuse.soundcomp.generator;
/*
* 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
*/
/*
* todo improvement for all exponential curves that are determined by 2 points:
*
* y=exp(bx+c)
*
* b=(x1-x0)*ln(y0/y1)
* c=ln(y0)-bx0
*
*
*
* where (x1-x0) is the time to reach end point, y0 is start value
* and y1 is end value.
* x0 can be "offset" to 0 (this is the starting time of a new state), so:
* so:
* b=desttime*ln(nowval-destval)
* c=ln(nowval)
*
* this applies to attack, sustain, release...
*
* There should NEVER be a strong "jump" in the output value as this
* always leads to audible glitches - test this under all conditions
* (start and end triggers in all possible phases).
*/
/**
* An envelope is used for the time dependent volume of a single note
* It is a logarithmic 3-ramp generator
*
* @author jssr67
*
*/
import de.maramuse.soundcomp.process.NamedSource;
import de.maramuse.soundcomp.process.ParameterMap;
import de.maramuse.soundcomp.process.ProcessElement;
import de.maramuse.soundcomp.process.SourceStore;
import de.maramuse.soundcomp.process.StandardParameters;
import de.maramuse.soundcomp.process.Stateful;
import de.maramuse.soundcomp.process.TypeMismatchException;
import de.maramuse.soundcomp.process.UnknownConnectionException;
import de.maramuse.soundcomp.process.ValueType;
import de.maramuse.soundcomp.process.StandardParameters.Parameter;
import de.maramuse.soundcomp.util.GlobalParameters;
import de.maramuse.soundcomp.util.NativeObjects;
import de.maramuse.soundcomp.util.ReadOnlyMap;
import de.maramuse.soundcomp.util.ReadOnlyMapImpl;
public class Envelope implements ProcessElement, Stateful {
public long nativeSpace=-1;
private double sampleRate=GlobalParameters.get().getSampleRate();
public Envelope() {
NativeObjects.registerNativeObject(this);
}
Envelope(boolean s) {
}
private static final int WAIT=0;
private static final int ATTACK=1;
private static final int DECAY=2;
private static final int SUSTAIN=3;
private static final int RELEASE=4;
private static final double timeConstant=-12; // ln(2^-17), we want -102dB to be the smallest ampl
private static final double minVal=1d/131072d;
private static final ReadOnlyMapImpl<Integer, ValueType> destTypeMap=new ReadOnlyMapImpl<Integer, ValueType>();
static{
destTypeMap.put(StandardParameters.A.i, ValueType.STREAM);
destTypeMap.put(StandardParameters.D.i, ValueType.STREAM);
destTypeMap.put(StandardParameters.S.i, ValueType.STREAM);
destTypeMap.put(StandardParameters.R.i, ValueType.STREAM);
destTypeMap.put(StandardParameters.SYNC.i, ValueType.STREAM);
}
protected static final ReadOnlyMapImpl<Integer, ValueType> sourceTypeMap=new ReadOnlyMapImpl<Integer, ValueType>();
static{
sourceTypeMap.put(StandardParameters.OUT.i, ValueType.STREAM);
}
private static ParameterMap inputsMap=new ParameterMap();
private static ParameterMap outputsMap=new ParameterMap();
static{
inputsMap.put(StandardParameters.A);
inputsMap.put(StandardParameters.D);
inputsMap.put(StandardParameters.S);
inputsMap.put(StandardParameters.R);
inputsMap.put(StandardParameters.SYNC);
outputsMap.put(StandardParameters.OUT);
}
private int phase=WAIT;
private double countSincePhaseChange;
private double attack=0;
private double decay=0;
private double sustain=1d;
private double release=0d;
private double restRelease=0d;
private double decayStart=0d;
private double releaseStart=0d;
private double val=0d, _val=0d;
private boolean trig=false;
private boolean trigold=false;
private String abstractName, instanceName;
private ReadOnlyMapImpl<Integer, SourceStore> params=new ReadOnlyMapImpl<Integer, SourceStore>();
@Override
public ReadOnlyMap<Integer, ValueType> getDestinationTypes() {
return destTypeMap;
}
@Override
public void setSource(int connection, NamedSource source,
int sourceIndex) throws UnknownConnectionException,
TypeMismatchException {
params.put(connection, new SourceStore(source, sourceIndex));
}
@Override
public ReadOnlyMap<Integer, ValueType> getSourceTypes() {
return sourceTypeMap;
}
@Override
public double getValue(int index) {
return val;
}
@Override
public void advanceState() {
SourceStore s=params.get(StandardParameters.A.i);
if(phase!=ATTACK){
if(s!=null)
attack=s.getValue();
if(attack<0)
attack=0;
}
if(phase!=DECAY){
s=params.get(StandardParameters.D.i);
if(s!=null)
decay=s.getValue();
if(decay<0)
decay=0;
}
if(phase!=SUSTAIN){
s=params.get(StandardParameters.S.i);
if(s!=null)
sustain=s.getValue();
if(sustain<0)
sustain=0;
if(sustain>1)
sustain=1;
}
if(phase!=RELEASE){
s=params.get(StandardParameters.R.i);
if(s!=null)
release=s.getValue();
if(release<0)
release=0;
}
s=params.get(StandardParameters.SYNC.i);
trigold=trig;
trig=s!=null&&s.getValue()>0;
if(trig&&!trigold){
// attack starts
restRelease=_val;
_val=minVal+restRelease;// we do not expect more than 16 bits resolution in the end.
// starting with even one power below should be safe
// ~ 2^timeConstant
phase=ATTACK;
countSincePhaseChange=0;
}else if(trigold&&!trig){
// release starts
if(phase==ATTACK||phase==DECAY||phase==SUSTAIN){
phase=RELEASE;
countSincePhaseChange=0d;
releaseStart=val;
}
}else{
// no change by external event
}
switch(phase){
case ATTACK:
// double restTime=attack-countSincePhaseChange;
if(attack>0){
_val=Math.exp(timeConstant*(1-countSincePhaseChange/attack))+restRelease
*Math.exp(timeConstant*countSincePhaseChange/attack);
if(_val>=1){
_val=decayStart=1;
countSincePhaseChange=0d;
phase=DECAY;
}
}else
_val=1d;
break;
case RELEASE:
// restTime=release-countSincePhaseChange;
if(release>0){
_val=releaseStart*Math.exp(timeConstant*countSincePhaseChange/release);
if(_val<minVal){
countSincePhaseChange=0d;
phase=WAIT;
_val=0d;
}
}else
_val=0d;
break;
case SUSTAIN:
_val=sustain;
break;
case DECAY:
// restTime=decay-countSincePhaseChange;
if(decay>0){
_val=decayStart*Math.exp(timeConstant*countSincePhaseChange/decay);
if(_val<=sustain){
_val=sustain;
phase=SUSTAIN;
countSincePhaseChange=0d;
}
if(_val<minVal){
countSincePhaseChange=0d;
phase=WAIT;
_val=0d;
}
}else
_val=0d;
break;
}
countSincePhaseChange+=(1/sampleRate);
}
@Override
public void advanceOutput() {
val=_val;
}
@Override
public long getNativeSpace() {
return nativeSpace;
}
@Override
public String getAbstractName() {
return abstractName;
}
@Override
public String getInstanceName() {
return instanceName;
}
@Override
public void setAbstractName(String abstractName) {
this.abstractName=abstractName;
}
@Override
public void setInstanceName(String instanceName) {
this.instanceName=instanceName;
}
/**
* @see de.maramuse.soundcomp.process.ProcessElement#clone()
*/
@Override
public Envelope clone() {
Envelope c=new Envelope();
c.abstractName=abstractName;
return c;
}
@Override
public ReadOnlyMap<Integer, SourceStore> getSourceMap() {
return params;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#outputsByName()
*/
@Override
public ReadOnlyMap<String, Parameter> outputsByName() {
return outputsMap;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#inputsByName()
*/
@Override
public ReadOnlyMap<String, Parameter> inputsByName() {
return inputsMap;
}
}