package jass.patches;
import jass.engine.*;
import jass.generators.*;
/** Map HSB color to [pitch reson-width lowpass-cutoff]
Represent a color (h,s,b) by a noise source of maximum freq~b,
filtered through a reson bank with Shepard frequencies
(i.e. octaves apart covering the audible range) and some damping d = 1*freq/freq_lowest.
The hue h [0 1] will be mapped to an octave range in freq. (Note the dampings
are also scaled when freq. is scaled to preserve scale invariance of octaves.) The saturation
s [0 1] will be mapped to the "material" (i.e., the width of the resonances will be
multiplied by a factor depending on the saturation.
This is a patch using ColorSonificator (with b==1) and then filter through Butterworth filter.
So brightness is mapped to surface roughness if we think of the sound as being scrpe sounds.
@author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class LowpassColorSonificator extends InOut {
protected float srate=44100;
protected float upperFreq=4000;
protected float lowerFreq=50;
protected FilterContainer filterContainer;
protected Butter2LowFilter butter2LowFilter;
protected HS1ColorSonificator theHS1ColorSonificator; // ColorSonificator but Brightness==1
protected float minSlideVelocity = 0.01f;
protected float maxSlideVelocity = 2.5f; // sound drops out if >2.5 and using unsaturated sound (BUG)
/** As parent class but lock its brightness to 1 as this will now be mapped to
lowpasscutoff
*/
class HS1ColorSonificator extends ColorSonificator {
public HS1ColorSonificator(float srate,int bufferSize) {
super(srate,bufferSize);
}
/** Set hue, saturation (brightness ==1)
@param h hue in range 0-1
@param s saturation in range 0-1
*/
public void setHS(float h,float s) {
super.setHSB(h,s,1);
}
/** Set hue, saturation (brightness ==1), and slide velocity
@param h hue in range 0-1
@param s saturation in range 0-1
@param v velocity in range 0-1
*/
public void setHS_V(float h,float s,float v) {
super.setHSB_V(h,s,1,v);
}
}
/** Create and initialize.
@param srate sampling rate in Hertz.
@param bufferSize Buffer size used for real-time rendering.
*/
public LowpassColorSonificator(float srate,int bufferSize) {
super(bufferSize);
this.srate = srate;
createPatch();
}
/** Create. For derived classes.
@param bufferSize Buffer size used for real-time rendering.
*/
public LowpassColorSonificator(int bufferSize) {
super(bufferSize);
}
/** Set freq. limits of lowpass
@param lower freq for black
@param upper freq. for white
*/
public void setLowpassFrequencyRange(float lower,float upper) {
lowerFreq = lower;
upperFreq = upper;
}
private float[] freqLimits = new float[2];
/** Get freq. limits of lowpass
@return [lower upper] freq for black and white
*/
public float[] getLowpassFrequencyRange() {
freqLimits[0]=lowerFreq;
freqLimits[1]=upperFreq;
return freqLimits;
}
/** Create. For derived classes.
@param bufferSize Buffer size used for real-time rendering.
@param srate sampling rate in Hz
*/
public LowpassColorSonificator(int bufferSize,float srate) {
super(bufferSize);
this.srate = srate;
createPatch();
}
/**
Set power in scaling law for gains a = a*d^fudgePower. .5 for constant energy but lower
in practice because of critical band effect, to have d-independent loudness
@param p fudgePower
*/
public void setFudgePower(float p) {
theHS1ColorSonificator.setFudgePower(p);
}
/**
Get power in scaling law for gains a = a*d^fudgePower. .5 for constant energy but lower
in practice because of critical band effect, to have d-independent loudness
@return fudgePower
*/
public float getFudgePower(float p) {
return theHS1ColorSonificator.getFudgePower();
}
/** Set damping range corresponding to saturation
@param dmin damping for saturated color
@param dmax damping for unsaturated color
*/
public void setSaturationLimits(float dmin,float dmax) {
theHS1ColorSonificator.setSaturationLimits(dmin,dmax);
}
private void setLowpassCutoff(float f) {
butter2LowFilter.setCutoffFrequency(f);
}
private void setLowpassGain(float g) {
butter2LowFilter.setGain(g);
}
protected void createPatch() {
butter2LowFilter = new Butter2LowFilter(srate);
filterContainer = new FilterContainer(srate,bufferSize,butter2LowFilter);
theHS1ColorSonificator = new HS1ColorSonificator(srate,bufferSize);
try {
theHS1ColorSonificator.addSource(filterContainer);
} catch(SinkIsFullException e) {
System.out.println(this+" "+e);
}
// set model parameters to defaults (this is code for documentation really, can leave it out)
theHS1ColorSonificator.setMaximumLevelDifference(theHS1ColorSonificator.getMaximumLevelDifference());
float[] satLimits = theHS1ColorSonificator.getSaturationLimits();
theHS1ColorSonificator.setSaturationLimits(satLimits[0],satLimits[1]);
float[] freqLimits = this.getLowpassFrequencyRange();
this.setLowpassFrequencyRange(freqLimits[0],freqLimits[1]);
// synchronize in case patch is created at runtime
long t = getTime();
filterContainer.setTime(t);
theHS1ColorSonificator.setTime(t);
}
/** Add source to input of patch, the lowpass filter in this case. Allow 1 input only
@param s Source to add.
@return object representing Source in Sink (may be null).
*/
public Object addSource(Source s) throws SinkIsFullException {
if(getSources().length > 0) {
throw new SinkIsFullException();
} else {
filterContainer.addSource(s);
// add to Vector so that other stuff like getSources will work
return super.addSource(s);
}
}
// set brightness and velocity (0-1)
private void setB_V(float b,float v) {
if(v<minSlideVelocity) {
v = minSlideVelocity;
} else if(v>maxSlideVelocity) {
v = maxSlideVelocity;
}
double fcutoff = v*lowerFreq*Math.pow(upperFreq/lowerFreq,b);
if(fcutoff<lowerFreq) {
fcutoff = lowerFreq;
}
//System.out.println(fcutoff);
setLowpassCutoff((float)fcutoff);
}
/** Set hue, saturation and brightness
@param h hue in range 0-1
@param s saturation in range 0-1
@param b brightness in range 0-1
*/
public void setHSB(float h,float s,float b) {
float velocity = 1;
theHS1ColorSonificator.setHS_V(h,s,velocity);
this.setB_V(b,velocity);
}
/** Set hue, saturation, brightness and slide velocity (1 = max)
@param h hue in range 0-1
@param s saturation in range 0-1
@param b brightness in range 0-1
@param v velocity in range 0-1
*/
public void setHSB_V(float h,float s,float b,float v) {
if(v<minSlideVelocity) {
v = minSlideVelocity;
} else if(v>maxSlideVelocity) {
v = maxSlideVelocity;
}
theHS1ColorSonificator.setHS_V(h,s,v);
this.setB_V(b,v);
}
/** Compute the next buffer and store in member float[] buf.
*/
protected void computeBuffer() {
try {
// get buffer from internal patch output
buf = theHS1ColorSonificator.getBuffer(getTime());
} catch(BufferNotAvailableException e) {
System.out.println(this+" "+e);
}
}
}