Real time audio parameter control

It is onften necessary or desireable to be able to change the values of the music or sound as it is being generted in real time. Tis tutorial will show how you can pass parater values around the jMusic real time architecture which sonsistes of the RTMixer, RTLine and Instrument classes, all of which need to be subclassed for use in your application.

There are three classes in this example which generates a synthesized bass line and allows real time changes to the panning position and low pass filter cutoff frequency from onscreen sliders:

BassLine.java
RTComposition.java (run this class)
SawLPFInstRT2.java (the instrument class)

Let's look at each in turn.

At the heart of real time audio in jMusic are classes that extend the RTLine class. These class override the getNextNote() method that is called by the RTMixer instance as it runs.

Parameter control is provided by overriding the externalAction() method of classes that extend RTLine. This method is called by the RTMixer whenever control changes from the application are sent to it (broardcast).

The method should check that the control change was one it needs to deal with by examining the actionNumber. Then it should handle the event by updating the notes generated, in this case by changing the note pan position, or update its instrument(s) as required, in this example the filter cutoff value is altered.

import jm.music.rt.RTLine;
import jm.audio.Instrument;
import jm.music.data.Note;
import javax.swing.*;

public class BassLine extends RTLine {
    private Note n = new Note(36, 0.5);
    private int pitch = 36;
    private int[] intervals = {0, 0, 0, 4, 7, 10, 12};
    private double panPosition = 0.5;
   
    /** Constructor */
    public BassLine (Instrument[] instArray) {
        super(instArray);
    }
    /**
    * Generate the next note when requested.
    */
    public synchronized Note getNextNote() {
        n.setPitch(pitch + intervals[(int)(Math.random() * intervals.length)]);
        n.setDynamic((int)(Math.random()* 80 + 45));
        n.setPan(panPosition);
        n.setRhythmValue((int)(Math.random() * 2 + 1) * 0.25);
        n.setDuration(n.getRhythmValue() * 0.9);
        return n;
    }   
   
    /** Allow other classes to set the notes pan value */
    public void setPanValue(double p) {
        this.panPosition = p;
    }
   
    // added for control change
    public synchronized void externalAction(Object obj, int actionNumber){
        if(actionNumber == 1){
            JSlider slider = (JSlider)obj;
            double filterCutoff = (double)(slider.getValue() * 100);
            for(int i=0;i<inst.length;i++){
                double[] values = {filterCutoff};
                inst[i].setController(values);
            }
        }
    }
}


In this example the RTComposition class creates a graphical user interface and sends commands to the RTMixer for broadcasting to the RTLines when the interface is updated, by a user moving a slider.

import jm.JMC;
import jm.music.data.*;
import jm.audio.RTMixer;
import jm.audio.Instrument;
import jm.music.rt.RTLine;

import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.*;

public class RTComposition implements JMC, ChangeListener, ActionListener {
private JSlider cutoff, panner;
private RTMixer mixer;
private JLabel val;
private BassLine bass;
private JButton goBtn;
private RTLine[] lineArray = new RTLine[1];

public static void main(String[] args) {
new RTComposition();
}

public RTComposition() {
int sampleRate = 44100;
int channels = 2;

Instrument[] instArray = new Instrument[1];
for(int i=0;i<instArray.length;i++){
instArray[i] = new SawLPFInstRT2(sampleRate, 1000, channels);
}

bass = new BassLine(instArray);
bass.setTempo(104);
lineArray[0] = bass;

// show slider panel
makeGUI();
}

private void makeGUI() {
JFrame f = new JFrame("Title");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(100, 250);
JPanel panel = new JPanel(new BorderLayout());
f.getContentPane().add(panel);
// pan
panner = new JSlider(1, 1, 100, 50);
panner.setEnabled(false);
panner.addChangeListener(this);
panel.add(panner, "West");
// filter
cutoff = new JSlider(1, 1, 100, 10);
cutoff.setEnabled(false);
cutoff.addChangeListener(this);
panel.add(cutoff, "Center");
// filter value display
val = new JLabel("1000");
panel.add(val, "East");
// start stop
goBtn = new JButton("Start");
goBtn.addActionListener(this);
panel.add(goBtn, "North");

f.pack();
f.setVisible(true);
}

public void stateChanged(ChangeEvent e){
// panner slider moved
if (e.getSource() == panner) {
bass.setPanValue((double)panner.getValue() / 100.0);
}
// filter slider moved
if (e.getSource() == cutoff) {
val.setText("" + (cutoff.getValue() * 100));
mixer.actionLines(cutoff, 1);
}
}

public void actionPerformed(ActionEvent e) {
if(e.getSource() == goBtn) {
mixer = new RTMixer(lineArray);
mixer.begin();
cutoff.setEnabled(true);
panner.setEnabled(true);
goBtn.setEnabled(false);
}
}
}


To make this all work, we need to have an instrument class that will respond to the control change messages being passed around. The key to this is having the instrument implement a
setController() method that is called by the RTLine sub class. An important consideration is that the audio object, in this case the Filter instance called filt, needs to be declared as an instance variable so that is it visible to the setController() method. An array is passed to this method which allows for a group of parameter changes to be passed at one time. In this case there is only one value, so it is accessed as the zeroth index in the array.

import jm.audio.Instrument;
import jm.audio.io.*;
import jm.audio.synth.*;
import jm.music.data.Note;
import jm.audio.AudioObject;

/**
 * A monophonic sawtooth waveform instrument implementation
 * which includes a static low pass filter.
 * @author Andrew Brown
 */

public final class SawLPFInstRT2 extends Instrument{
    //----------------------------------------------
    // Instance variables
    //----------------------------------------------
    private int sampleRate;
    private int filterCutoff;
    private int channels;
    private Filter filt;

    //----------------------------------------------
    // Constructor
    //----------------------------------------------
    public SawLPFInstRT2(){
        this(44100);
    }
    /**
     * Basic default constructor to set an initial
     * sampling rate and use a default cutoff.
     * @param sampleRate
     */
    public SawLPFInstRT2(int sampleRate){
        this(sampleRate, 1000);
    }
       
     /**
    *  Constructor that sets sample rate and the filter cutoff frequency.
     * @param sampleRate The number of samples per second (quality)
     * @param filterCutoff The frequency above which overtones are cut
     */
     public SawLPFInstRT2(int sampleRate, int filterCutoff){
        this(sampleRate, filterCutoff, 1);
    }
    
    /**
    *  Constructor that sets sample rate, filter cutoff frequency, and channels.
    * @param sampleRate The number of samples per second (quality)
    * @param filterCutoff The frequency above which overtones are cut
    * @param channels 1 = mono, 2 = stereo.
    */
     public SawLPFInstRT2(int sampleRate, int filterCutoff, int channels){
        this.sampleRate = sampleRate;
        this.filterCutoff = filterCutoff;
        this.channels = channels;
    }

    //----------------------------------------------
    // Methods
    //----------------------------------------------  
    /**
     * Initialisation method used to build the objects that
     * this instrument will use and specify their interconnections.
     */
    public void createChain(){
        Oscillator wt = new Oscillator(this,
            Oscillator.SAWTOOTH_WAVE, this.sampleRate, this.channels);
        filt = new Filter(wt, this.filterCutoff, Filter.LOW_PASS);
        Envelope env = new Envelope(filt,
             new double[] {0.0, 0.0, 0.02, 1.0, 0.2, 0.5, 0.8, 0.3, 1.0, 0.0});
        Volume vol = new Volume(env);
        StereoPan pan = new StereoPan(vol);
    }   
   
    /** Changes the specified controller when called */
    public void setController(double[] controlValues){
        filt.setCutOff(controlValues[0]);
    }
}




While this message passing process is somewhat complicated to start with you will find that it is very flexible and should allow for quite sophisticated real time control of audio parameters.


© 2004 Andrew Brown
jMusic Tutorial Index