Channels ▼
RSS

JVM Languages

Music Components in Java: Effects


In this final article of a four-part series discussing how computer-based music synthesis works and presenting a basic platform for experimentation, I'll discuss effects. An effect is a general term for a class of component that manipulates samples towards some particular end. Example effects include tremelo, phaser, flanger, reverb, delay, equalizer, pitch shifter, chorus, compressor, expander, limiter, etc. This article will describe how to implement a delay and a phaser effect using the architecture developed over the course of this series.

In general, effects provide controls that are manipulated to achieve a desired sound. With a hardware effect like a guitar pedal, real physical knobs and switches are used for adjustments. A graphical user interface (GUI) is provided in a virtual environment for the same purpose. All effects (physical and virtual) provide a way to bypass their operation, effectively removing them from the signal path. The delay and phaser effects we will be discussing are no exceptions. Figures 1 and 2 show the GUI's for the delay and phaser effects within PSynth, the iPhone/iPod Touch electronic music synthesizer presented in this series.

Figure 1: Delay effect GUI.

Figure 2: Phaser effect GUI.

Effects are inserted into a signal path/chain between those components that produce samples (like our BasicOscillator) and those components that consume samples (like our SamplePlayer). To function in this environment, effects must be able to request samples from an upstream sample provider, process these samples in real time, and then provide the modified samples to the downstream component. To fit within our architecture, all effects must implement the SampleProviderIntfc. It should be noted that effects can be connected serially to further increase the complexity of the resultant sound. Keep in mind that the order in which effects are connected can have a drastic effect on the sounds produced.

Delay Effect

Delay effects have two primary uses. A small amount of delay (50 milliseconds or less) can be used to fatten up a sound (make it sound fuller), while large amounts of delay result in discrete echos. Repeating echos like the kind you hear when you yell in a canyon can be created using feedback to loopback some fraction of the delayed output back to the input of the delay effect. Sound example seven illustrates the delay effect in operation (the code and Javadocs for the complete series can be downloaded here). Execute the following command line in a shell to hear this example:
java -jar softsynth.jar 7.

Here, the delay is set to 250 milliseconds with 30% feedback and a 50% dry/wet mix. As you listen, you can hear the discrete echo of the plucked sound along with repeating echos in the background.

You can consult the DelayEffect class Javadocs to see the details of the provided API; but basically, you have the ability to bypass the delay effect, to control the dry/wet mix, and to set the amount of delay and the feedback level.

Delay is one of the easiest effects to implement and the basic code can be used as a template for other effects you might want to develop. As implemented, this effect can produce delays that range from one millisecond to two seconds, although these limits are easily adjusted. Before describing how the delay effect works, lets show how to couple the delay effect into a typical signal chain. The code snippet that follows shows how.

// Create a delay effect
DelayEffect de = new DelayEffect();
		
// Set the sample provider	 for the delay	
de.setSampleProvider(osc);
de.setDelayInMs(250);
de.setFeedbackPercent(30);

// The next component in the chain sets the delay as its sample provider 

Delay is accomplished by writing samples into a buffer and extracting them at some later point in time. A circular buffer with separate read and write pointers is used in this implementation. A read-follows-write approach is used for buffer management; meaning the read pointer is always behind the write pointer in time. New samples are added to the buffer at the write pointer index, while delayed samples are extracted from the buffer at the read pointer index. Both indices are incremented at the sample rate and both must be forced to stay within the buffer's bounds using the mod operator in conjunction with the buffer-size specification. In other words, as these indices pass the end of the circular buffer, they wrap around to the beginning of the buffer (and this happens continuously).

The core of the delay effect code is shown in Listing One. The getSamples method gets a buffer full of bytes from the sample provide, then extracts short integer sample values to pass to the processSample method. The processed sample returned from this method is converted back to a pair of byte values and stored in the buffer.

Listing One: The guts of the delay effect from the DelayEffect class,


	/**
	 * Process a single sample through the delay effect.
	 * 
	 * @param inputSample The input sample to process
	 * 
	 * @return Processed sample including wet and dry signal
	 */
	private short processSample(short inputSample) {
		
		if (bypassed) {
			return inputSample;
		}
		// Read a delayed sample		
		short delayedSample = delayBuffer[readIndex++];

		double dryLevel = ((100.0 - dryWetMixPercent) * inputSample) / 100.0;
		double wetLevel = (dryWetMixPercent * delayedSample) / 100.0;
		short outputSample = (short) (dryLevel + wetLevel);

		inputSample += (delayedSample * feedbackPercent) / 100.0;
		
		// Write an input sample		
		delayBuffer[writeIndex++] = inputSample;
				
		// Update indices
		readIndex  %= DELAY_BUFFER_SIZE;
		writeIndex %= DELAY_BUFFER_SIZE;
		
		return outputSample;		
	}

	/**
	 * 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);
			
			// Process the sample
			s = processSample(s);
			
			// Store the processed sample
			buffer[index++] = (byte)(s >> 8);
			buffer[index++] = (byte)(s & 0xFF);			
		}
		return SamplePlayer.BUFFER_SIZE;
	}

The processSample method is where all of the action is. As shown in Listing One, if the effect is bypassed, the input sample is returned as the output sample with no processing performed. If not bypassed, a delayed sample is read from the circular delay buffer, the dry and wet components are calculated and summed to form the output sample, feedback of the delayed sample augments the input sample's value, which is then stored in the circular buffer at the write index. Finally, both the read and write indices are checked for overflow and the output sample is returned.

It should be noted that as the amount of delay is changed via the API — the separation in samples between the read and the write pointer indices also changes, but that once set, the separation distance remains constant.

All in all, a delay is a fairly simple process that does not use digital signal processing (DSP) in its operation. The phaser effect, however, is not so straightforward.


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