package de.maramuse.soundcomp.effect;
/*
* Copyright 2012 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
*
*/
/**
* A delay and equidistant comb filter effect suitable for enhancing the "livelyness" of otherwise
* static sounds. Overlaying the original with a short-delayed copy with variable delay time.
*
* Note that this element has a MAXDELAY input. Whilst the all other inputs are evaluated on the fly
* and changes on the other inputs during playing have influence on the effect, the MAXDELAY input
* is being read on initialization of the element only, and later changes to not have any effect.
* The MAXDELAY input is used for determining the memory reserved for the delay loop, which is never
* resized thereafter. It thus makes no sense to connect the MAXDELAY input to anything but a ConstStream.
* If unconnected, defaults to 1s.
*
* There are inputs for FREQUENCY and DEPTH of the modulation. If however the MODULATION input is connected,
* the internal LFO is disregarded, to allow connecting more versatile LFOs for modulation. It makes no
* sense to connect any sources to FREQUENCY and MODULATION at the same time, and MODULATION has precedence.
* DEPTH multiplies with either the MODULATION input or internal LFO output, then adds to the DELAY input to
* build the actual delay time. The DELAY input usually is meant to connect to a constant average delay that
* the LFO oder MODULATION input add to (but may be exploited for a second modulation source). Take care
* that the sum of MODULATION/LFO and DELAY is hard limited to 0<=sum<=MAXDELAY.
* As always, all time parameters are in seconds, frequency parameters in Hz, although MAXDELAY, DELAY and DEPTH
* will usually be in the 3-30ms range, and FREQUENCY something around 0.3Hz..3Hz.
*
* TODO anti alias filtering implementation missing (skipping and/or inserting samples by modulating delay
* is a type of pitch shift/TDHS and would require anti aliasing filtering).
* Feedback?
*/
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.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 Flanger implements ProcessElement {
private String abstractName, instanceName;
private long nativeSpace=-1;
private boolean initialized=false;
private double[] samples;
private double out1=0.0, out2=0.0, _out1, _out2, lfophase=0.0, samplerate=0.0;
int index;
private SourceStore maxDelay=null, delay=null, frequency=null, in=null, depth=null,
modulation=null, intensity=null;
private static final ReadOnlyMapImpl<Integer, ValueType> srcMap=new ReadOnlyMapImpl<Integer, ValueType>();
private static final ReadOnlyMapImpl<Integer, ValueType> destMap=new ReadOnlyMapImpl<Integer, ValueType>();
private final ReadOnlyMapImpl<Integer, SourceStore> sourceMap=new ReadOnlyMapImpl<Integer, SourceStore>();
private static ParameterMap inputsMap=new ParameterMap();
private static ParameterMap outputsMap=new ParameterMap();
static{
inputsMap.put(StandardParameters.FREQUENCY); // the LFO frequency
inputsMap.put(StandardParameters.DEPTH); // the LFO amplitude, in resulting delay seconds
inputsMap.put(StandardParameters.INTENSITY); // the amount of delayed signal in the mix
// if this input is connected, the internal LFO is disconnected, to allow for versatile modulation.
// Expect an amplitude 1 signal here, -1 means no delay, +1 means MAXDELAY
inputsMap.put(StandardParameters.DELAY);
inputsMap.put(StandardParameters.MAXDELAY);
inputsMap.put(StandardParameters.MODULATION);
inputsMap.put(StandardParameters.IN);
destMap.put(StandardParameters.FREQUENCY.i, ValueType.STREAM);
destMap.put(StandardParameters.DEPTH.i, ValueType.STREAM);
destMap.put(StandardParameters.INTENSITY.i, ValueType.STREAM);
destMap.put(StandardParameters.DELAY.i, ValueType.STREAM);
destMap.put(StandardParameters.MODULATION.i, ValueType.STREAM);
destMap.put(StandardParameters.MAXDELAY.i, ValueType.DOUBLE);
destMap.put(StandardParameters.IN.i, ValueType.STREAM);
outputsMap.put(StandardParameters.OUT);
outputsMap.put(StandardParameters.OUT_IMAG);
srcMap.put(StandardParameters.OUT.i, ValueType.STREAM);
srcMap.put(StandardParameters.OUT_IMAG.i, ValueType.STREAM);
}
public Flanger() {
NativeObjects.registerNativeObject(this);
}
@Override
public ReadOnlyMap<Integer, ValueType> getSourceTypes() {
return srcMap;
}
@Override
public double getValue(int _index) {
if(_index==StandardParameters.OUT.i)
return out1;
else if(_index==StandardParameters.OUT_IMAG.i)
return out2;
else
throw new IllegalArgumentException();
}
@Override
public ReadOnlyMap<String, Parameter> outputsByName() {
return outputsMap;
}
@Override
public void advanceState() {
double currval=in.getValue();
if(!initialized){
double memorySize=samplerate=GlobalParameters.get().getSampleRate();
if(maxDelay!=null)
memorySize*=maxDelay.getValue();
samples=new double[(int)memorySize];
initialized=true;
}
double _modulation=Double.NaN;
if(modulation!=null)
_modulation=modulation.getValue();
else{
lfophase+=frequency.getValue()/samplerate/2/Math.PI;
lfophase%=1.0;
_modulation=Math.cos(lfophase);
}
_modulation*=depth.getValue();
double _delay=(delay.getValue()+_modulation)*samplerate;
if(_delay<0)
_delay=0;
if(_delay>=samples.length)
_delay=samples.length;
double _intensity=1.0;
if(intensity!=null)
_intensity=intensity.getValue();
_out1=currval+_intensity*samples[(int)((index+samples.length-_delay)%samples.length)];
_out2=currval-_intensity*samples[(int)((index+samples.length-_delay)%samples.length)];
index=index+1;
if(index==samples.length)
index=0;
samples[index]=currval;
}
@Override
public void advanceOutput() {
out1=_out1;
out2=_out2;
}
@Override
public void setAbstractName(String abstractName) {
this.abstractName=abstractName;
}
@Override
public String getAbstractName() {
return abstractName;
}
@Override
public void setInstanceName(String instanceName) {
this.instanceName=instanceName;
}
@Override
public String getInstanceName() {
return instanceName;
}
@Override
public long getNativeSpace() {
return nativeSpace;
}
@Override
public ReadOnlyMap<Integer, ValueType> getDestinationTypes() {
return destMap;
}
@Override
public void setSource(int connectionIndex, NamedSource source, int sourceIndex)
throws UnknownConnectionException, TypeMismatchException {
if(connectionIndex==StandardParameters.MAXDELAY.i){
// TODO check whether it's a constant?
maxDelay=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.INTENSITY.i){
intensity=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.FREQUENCY.i){
frequency=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.DELAY.i){
delay=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.DEPTH.i){
depth=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.MODULATION.i){
modulation=new SourceStore(source, sourceIndex);
}else if(connectionIndex==StandardParameters.IN.i){
in=new SourceStore(source, sourceIndex);
}
}
@Override
public ReadOnlyMap<Integer, SourceStore> getSourceMap() {
return sourceMap;
}
@Override
public ReadOnlyMap<String, Parameter> inputsByName() {
return inputsMap;
}
/**
* @see de.maramuse.soundcomp.process.ProcessElement#clone()
*/
@Override
public Flanger clone() {
Flanger c=new Flanger();
c.abstractName=abstractName;
return c;
}
}