Channels ▼
RSS

JVM Languages

Music Components in Java: The Synthesizer Core


Controlling Filter Dynamics

Whereas the amplitude of samples are manipulated in the VCA, the cutoff frequency of a Low Pass Filter (LPF) is manipulated by our VCF. An idealized LPF response graph is shown in Figure 3. What this figure indicates is that frequencies lower than the filter's cutoff frequency are passed unaltered whereas frequencies higher than the cutoff frequency are severely attenuated (reduced in level).

Figure 3: An idealized LPF response graph.

Let's take a minute to talk about what this means in the subtractive synthesis environment. With subtractive synthesis, you start with a harmonically rich sound like a square or a sawtooth wave. These waveforms have harmonics that are multiples of the waveform's fundamental frequency. If the cutoff frequency of a LPF in the signal chain is set high, most of the harmonics are passed through the filter with the result being little modification of the waveform's native sound. As the cutoff frequency of the filter is lowered, more and more of the waveform's harmonics are filtered out and the sound changes to become more pure. If the cutoff frequency of the LPF is set at or below the fundamental frequency of the waveform, the sound approximates that of a sine wave.

Sweeping a LPF with an EG causes a dynamic and dramatic change in the produced sound. If the sweep goes from high to low, you get the full sound of the waveform that morphs over time into a purer sound. If the sweep goes from low to high, you hear the pure sound first that then transitions to the native sound of the waveform.

As with our VCA, the VCF class extends EnvelopeGenerator and implements SampleProviderIntfc. This arrangement allows the EG's APIs to be directly available to the user of the VCF class. The noteOn and noteOff methods are again the control interface for the VCF.

The filter we are using is a sweepable, 24 db/octave, variable resonance, LPF (is that a mouthful or what?) whose code was taken from the Music DSP site. This filter is found in the comment section of the "Moog VCF" topic. The theory of how this filter works is beyond the scope of this article because it enters the realm of Digital Signal Processing (DSP) and requires knowledge of filter design to understand. Suffice it to say that this filter is a cascaded arrangement of four single pole filters providing a total of 24 db/octave cutoff slope.

As this filter's resonance is increased, it will begin to self oscillate just like the MiniMoog's filter. Oscillating filters have their own uses in electronic music including synthesizing the sound of bells under certain circumstances.

The depth control API method determines the range and direction of the cutoff frequency sweep as illustrated in Table 1:

Table 1.

See Listing Two for the code for the VCF and the embedded javadocs for the details of the VCF's API.

Listing Two: The code for the VCF.

public class VCF extends EnvelopeGenerator implements SampleProviderIntfc {
	
	public static final double MIN_CUTOFF = 20.0;
	public static final double MAX_CUTOFF = 8000.0;
	public static final double MIN_DEPTH = -2.0;
	public static final double MAX_DEPTH = 2.0;	
	
	/**
	 * Set the static cutoff frequency of the filter.
	 * Cutoff frequency must be between MIN_CUTOFF and MAX_CUTOFF.
	 * Envelope signal varies the cutoff frequency from this static value.
	 * @param cutoff Cutoff frequency in Hz
	 */
	public void setCutoffFrequencyInHz(double cutoff) {
		
		cutoff = (cutoff < MIN_CUTOFF) ? MIN_CUTOFF : cutoff;
		cutoff = (cutoff > MAX_CUTOFF) ? MAX_CUTOFF : cutoff;
		
		cutoffFrequencyInHz = cutoff;
		this.cutoff = cutoff;

		recalculate();
	}

	/**
	 * Set the resonance of the filter.
	 * Valid values are between 0.0 and 1.0 where<br>
	 * 0.0 is no resonance and 1.0 is full resonance or oscillation.
	 * @param resonance The resonance value to set
	 */
	public void setResonance(double resonance) {

		this.resonance = resonance;

		recalculate();
	}
		
	/**
	 * Set the depth of the filter effect.
	 * depth == 0 env gen has no affect on cutoff frequency<br>
	 * depth < 0 env gen drives cutoff sweep downward<br>
	 * depth == -1 sweep is 1 octave; depth = -2 sweep is 2 octaves<br>
	 * depth > 0 env gen drives cutoff sweep upward<br>
	 * depth == 1 sweep is 1 octave; depth = 2 sweep is 2 octaves<br>
	 * Depth value must be between MIN_DEPTH and MAX_DEPTH.
	 * @param depth The depth to set
	 */
	public void setDepth(double depth) {
		
		depth = (depth < MIN_DEPTH) ? MIN_DEPTH : depth;
		depth = (depth > MAX_DEPTH) ? MAX_DEPTH : depth;

		this.depth = depth;
	}

	/**
	 * Setup the provider of samples
	 * @param provider The provider of samples for the VCF.
	 */
	public void setSampleProvider(SampleProviderIntfc provider) {
		this.provider = provider;
	}

	/**
	 * Recalculate filter parameters on changes to cutoff or resonance
	 */
	private void recalculate() {
		
		double f = (cutoff + cutoff) / (double) SamplePlayer.SAMPLE_RATE;
		p = f * (1.8 - (0.8 * f));
		k = p + p - 1.0;

		double t = (1.0 - p) * 1.386249;
		double t2 = 12.0 + t * t;
		r = resonance * (t2 + 6.0 * t) / (t2 - 6.0 * t);
	}

	/**
	 * Process a single sample through the filter
	 * @param input The input sample to process
	 * @return Filtered sample
	 */
	private short processSample(short input) {
		// Process input
		x = ((double) input/Short.MAX_VALUE) - r*y4;
		
		// Four cascaded one pole filters (bilinear transform)
		y1 =  x*p +  oldx*p - k*y1;
		y2 = y1*p + oldy1*p - k*y2;
		y3 = y2*p + oldy2*p - k*y3;
		y4 = y3*p + oldy3*p - k*y4;
		
		// Clipper band limited sigmoid
		y4 -= (y4*y4*y4) / 6.0;
		
		oldx = x; oldy1 = y1; oldy2 = y2; oldy3 = y3;
		return (short) (y4 * Short.MAX_VALUE);
	}
		
	/**
	 * Process a buffer full of samples pulled from the sample provider
	 * @param buffer Buffer in which the samples are to be processed
	 * @return Count of number of bytes processed
	 */
	public int getSamples(byte [] buffer) {
		
		// Grab samples to manipulate from this modules sample provider
		provider.getSamples(buffer);
		
		int index = 0;
		for (int i = 0; i < SamplePlayer.SAMPLES_PER_BUFFER; i++) {
			// Get a sample to process
			byte b2 = buffer[index];
			byte b1 = buffer[index+1];

			// Convert bytes into short sample
			short s = (short)((((int) b2) << 8) + b1);
			
			// Get value from envelope generator in the range 0.0 .. 1.0
			double v = getValue();
			
			// Calculate actual cutoff freq given depth and env gen modifiers
			cutoff = cutoffFrequencyInHz * Math.pow(2.0, depth * v);
			recalculate();
			
			// Return processed sample from filter
			s = processSample(s);
			
			// Store the processed sample
			buffer[index++] = (byte)(s >> 8);
			buffer[index++] = (byte)(s & 0xFF);			
		}
		return SamplePlayer.BUFFER_SIZE;
	}
	// Instance data
	private double resonance, depth, cutoff, cutoffFrequencyInHz;
	private double x, r, p, k, y1, y2, y3, y4, oldx, oldy1, oldy2, oldy3;
	private SampleProviderIntfc provider;
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video