jMusic Instrument Architecture

This tutorial reveals how a jMusic instrument class is structured.

jMusic instruments are used to render jMusic scores as audio files (or as real time audio output). Instruments are made up from audio objects (not unlike unit generators in Csound or audio objects in Max/MSP) arranged in a hierarchial structure called a chain. Typically the audio chain is a linear arrangement of audio objects, as in the class below, but more complex hierarchies of audio objects can be constructed to produce more complex timbres.

Hear the result of this tutorial's music below.

Gongs.mp3 [492K]

View / Download source - Instrument source code

View / Download source - Composition source code

Let's have a closer look. 
import jm.audio.io.*;
import jm.audio.synth.*;
import jm.music.data.Note;
import jm.audio.AudioObject;

 
/**
* A basic wavtebable synthesis instrument implementation
* which implements envelope and volume control
* @author Andrew Sorensen
*/

 
public final class SimpleSineInst extends jm.audio.Instrument{
//----------------------------------------------
// Attributes
//----------------------------------------------
/** the sample rate passed to the instrument */

private int sampleRate;

/** the sample rate passed to the instrument */

private int channels;

 
//----------------------------------------------
// Constructor
//----------------------------------------------
/**
* Basic default constructor to set an initial
* sampling rate.
* @param sampleRate
*/

public SimpleSineInst(int sampleRate){
this.sampleRate = sampleRate;
this.channels = 1;
}

 
//----------------------------------------------
// Methods
//----------------------------------------------

/**
* Initialisation method used to build the objects that
* this instrument will use.
* Declares the primary audio object array and the
* audio object(s) in that array. (One array element per channel)
*/

public void createChain(){
Oscillator wt = new Oscillator(this, Oscillator.SINE_WAVE,
sampleRate, channels);

Envelope env = new Envelope(wt, new EnvPoint[] {
new EnvPoint((float)0.0, (float)0.0),
new EnvPoint((float)0.02, (float)1.0),
new EnvPoint((float)0.15, (float)0.6),
new EnvPoint((float)0.9, (float)0.4),
new EnvPoint((float)1.0, (float)0.0)

});
SampleOut sout = new SampleOut( env, "jmusic.tmp"); } }

Instrument class overview

jMusic instruments, such as the SimpleSineInst above, extend the Instrument class.

public final class SimpleSineInst extends jm.audio.Instrument{

This provides them with significant functionality and means that the instrument classes you create need only pay attention to the timbral aspects of the instruments, leaving the details of communication between audio objects and jMusic scores to the Instrument super class.

Class variables for sample rate and number of channels can be decalred with constant values or assigned with values from constructors.

	private int sampleRate;
private int channels;

In the SimpleSineInst they are assigned from the constructor.

	public SimpleSineInst(int sampleRate){
this.sampleRate = sampleRate;
this.channels = 1;
}

jMusic instruments can be rendered at any sample rate. Having the rate passed in the constructor provides the chance to determine the quality and rendering speed at compile time. Note that to accomodate the Nyquist rule the sample rate must be at least twice the frequency of the highest note in the score. In practice using a sample rate of 8000 or above should cause few problems.

The createChain() method

The heart of the Instrument classes is the creatChain() method. All jMusic instrument classes must include this method. The createChain() method is where the synthesis process or signal processing process is defined.

	public void createChain(){
Oscillator wt = new Oscillator(this, this.sampleRate,
Oscillator.getSineWave(this.sampleRate), 1);
Envelope env = new Envelope(wt, new EnvPoint[] {
new EnvPoint((float)0.0, (float)0.0),
new EnvPoint((float)1.0, (float)0.1),
new EnvPoint((float)0.0, (float)1.0)
});
SampleOut sout = new SampleOut( env, "jmusic.tmp");
}

The createChain() method in the SimpleSineInst class (shown above) has a chain of three audio objects, Wavetable, Envelope, and SampleOut.

An audio object's position in the chain is indicated by the first constructor argument; the preceeding audio object is passed as the firt argument. For example, the SampleOut instance "sout" is passed the "env" object - this indicates that sout comes after env in the chain. The first object in the chain ( "wt" in this example) takes the instrument instance, "this", as an argument, as in; WaveTable wt = new WaveTable(this, . . .

Audio objects

There are many audio objects provided with jMusic and you are encouraged to write your own. Some of the audio objects are:

Oscillator, Envelope, Volume, Panner, SampleIn, SampleOut, Filter, Add, Splitter, and WhiteNoise.

For a complete list see the jMusic documentation, or look in the jm/audio/synth directory of the jMusic source tree.

All audio objects take one or more arguments, the first of which is by convention the preceeding audio object in the chain, the other arguments vary depending upon the funtion of theaudio object class - check the documentation for details.

Constructing new Audio Objects is beyond the scope of this tutorial and is covered elsewhere.

Sample stream

The hierarchial arrangment of objects is necessary because the Instrument is essentailly a class to generate and/or processes samples. The samples are passed from one object to another along the chain creating a sample stream, as indicated in the diagram below.

To visualise this imagine that the SimpleSineInst generates a raw sione wave in the wt object, then each sample amplitude is adjusted according the the envelope in the env object, and lastly each sample is written to disk by the sout object. This process is shown diagramatically below.

changes into then data is written to file

One important, but counter intuitive, aspect of the sample progression through the chain is that the last object in the chain is the first object to be executed. In this case the sout (SampleOut) object runs first, it requests samples from the env (Envelope) object, which in turn requests samples from the wt (WaveTable) object which generates them using a call to the Oscilliator class. In practice then, samples are pulled through the chain from the bottom as required to render each note in the jMusic score. In this way only the number of samples required by each note (determined by it's length and the sample rate) are computed.

in theory, one sample at a time could be passed down the chain but this would be quite inefficient. in practice an array of samples is passed down the chain. By default, an array of 4096 samples are processed by each object at a time and passed on through the chain.

Gong Example

The code below uses multiple notes played by sine waves to create a gong-like timbre.

import jm.music.data.*;
import jm.music.tools.*;
import jm.JMC;
import jm.audio.*;
import jm.util.*;
  // Gong-like timbres inspired by // Claude Risset's early computer music pieces   public class Gongs implements JMC { public static void main(String[] args) { new Gongs(); } public Gongs() { // musical score construction CPhrase notes1 = new CPhrase(); int[] pitches = {CS4, BF4, D5, E5, A5}; notes1.addChord(pitches, 8.0); notes1.addChord(pitches, 8.0); notes1.addChord(pitches, 8.0); notes1.addChord(pitches, 8.0); Part part0 = new Part("Gongs go here", 0); part0.addCPhrase(notes1); Score score = new Score(part0); View.show(score); // instrument declaration int sampleRate = 22000; Instrument sine = new SimpleSineInst(sampleRate);   // render Write.au(score, "Gongs.au", sine); } }

The SimpleSineInst is used by this example. After creating the score to play the gong sound four times and displaying it using the View.show() method, the Instrument is decalred.

		Instrument sine = new SimpleSineInst(sampleRate);

This is the only instrument required for this piece, so it is passed as an argument to the Write.au() method to render the score as a file named Gongs.au using the line;

		Write.au(score, "Gongs.au", sine);

You might like to experiment with editing the instrument by changing the envelope point values to change the charracteristic of the gong sound.



jMusic Tutorial Index


© 2001 Andrew Brown