Manual: Controller

[ javadoc | examples ]

As mentioned in the previous section, JavaSound uses Lines to transfer audio between your program and the system. Each line has controls associated with it that let you change things like panning, balance, gain, and volume. The advantage is that you needn’t worry about implementing these types of controls in your own audio synthesis classes, but the disadvantage is that when playing back audio the controls take effect after your application receives the samples. This means that you could be playing some stereo file with the balance set to hard right and you will not see any difference in the samples that you can access. In other words, while you may expect that the left channel will contain all zeros, it will still contain what actually exists in the left channel of the file. On the other hand, when monitoring the audio input, setting a control will be reflected in the sample buffers because it will take effect before your application receives the audio.

Printing Controls

Not all controls are available on all lines. You can use the printControls method to print a list of the controls available to a controller, as well as the ranges of those controls, in the console of the PDE.

Code Sample (view online)

import ddf.minim.*;
 
AudioOutput out;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
  out.printControls();
}
 
void draw()
{
  background(0);
}
 
void stop()
{
  // always close Minim audio classes
  out.close();
  super.stop();
}

Querying For Controls

Printing the available controls to the console is informative if you are just beginning development, but the reality is that different controls may be available on different computers (this is a problem I hope to address in a future release). To cope with this, Controller provides the hasControl method, so that you can determine if a control exists before you try to use it. The argument to the hasControl method is a Control.Type, which is a class defined by the JavaSound API. Controller has six static members of this type, which correspond to commonly found controls. They are BALANCE, GAIN, PAN, MUTE, SAMPLE_RATE, and VOLUME.

Code Sample (view online)

import ddf.minim.*;
 
AudioOutput out;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
 
  if ( out.hasControl(Controller.PAN) )
  {
    text("The output has a pan control.", 5, 15);
  }
  else
  {
    text("The output doesn't have a pan control.", 5, 15);
  }
 
  if ( out.hasControl(Controller.VOLUME) )
  {
    text("The output has a volume control.", 5, 30);
  }
  else
  {
    text("The output doesn't have a volume control.", 5, 30);
  }
 
  if ( out.hasControl(Controller.SAMPLE_RATE) )
  {
    text("The output has a sample rate control.", 5, 45);
  }
  else
  {
    text("The output doesn't have a sample rate control.", 5, 45);
  }
 
  if ( out.hasControl(Controller.BALANCE) )
  {
    text("The output has a balance control.", 5, 60);
  }
  else
  {
    text("The output doesn't have a balance control.", 5, 60);
  }
 
  if ( out.hasControl(Controller.MUTE) )
  {
    text("The output has a mute control.", 5, 75);
  }
  else
  {
    text("The output doesn't have a mute control.", 5, 75);
  }
 
  if ( out.hasControl(Controller.GAIN) )
  {
    text("The output has a gain control.", 5, 90);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 105);
  }
}
 
void stop()
{
  // always close Minim audio classes
  out.close();
  super.stop();
}

Getting and Setting Controls

Once you know what controls are available you can use the appropriate set and get methods to manipulate them. The getters and setters are:

setBalance(float value)
getBalance()
setGain(float value)
getGain()
setPan(float value)
getPan()
setVolume(float value)
getVolume()

The range for balance is from -1 to 1, for gain it is usually from -80 to 6, for pan it is from -1 to 1, and for volume I don’t know the range because I’ve yet to use a line that it was available on. For all intents and purposes, gain is basically the same as volume. Below is an example that manipulates the gain of an output. Examples for the other controls can be found here.

Code Sample (view online)

import ddf.minim.*;
import ddf.minim.signals.*;
 
AudioOutput out;
Oscillator  osc;
// this class is defined in a second file
// see the online example for the code
WaveformRenderer waveform;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
 
  // see the example AudioOutput >> SawWaveSignal for more about this class
  osc = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(osc);
 
  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform); 
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();
 
  if ( out.hasControl(Controller.GAIN) )
  {
    // map the mouse position to the audible range of the gain
    float val = map(mouseX, 0, width, 6, -48);
    // if a gain control is not available, this will do nothing
    out.setGain(val); 
    // if a gain control is not available this will report zero
    text("The current gain is " + out.getGain() + ".", 5, 15);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 15);
  }
}
 
void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  super.stop();
}

Shifting Controls

All of these controls are so-called float controls because they represent a continuous range of values. Controller provides methods to change the value of one of these controls over time. This is called shifting. The shifting methods are as follows:

shiftBalance(float from, float to, int millis)
shiftGain(float from, float to, int millis)
shiftPan(float from, float to, int millis)
shiftVolume(float from, float to, int millis)

As is probably obvious, from is the value to start at, to is the target value, and millis is how long the shift should take in milliseconds.

In the event that you try to get, set, or shift a control that is not available, Minim will print an error message in the console of the PDE, but your application will not crash. The following code sample demonstrates how you can use each of the shift methods.

Code Sample (view online)

iimport ddf.minim.*;
import ddf.minim.signals.*;
 
AudioOutput out;
// this class is define in a separate file
// see the online example for the code
WaveformRenderer waveform;
SawWave saw;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
 
  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);
 
  // see the example AudioOutput >> SawWaveSignal for more about this
  saw = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(saw);
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();
 
  if ( out.hasControl(Controller.PAN) )
  {
    text("The current pan value is " + out.getPan() + ".", 5, 15);
  }
  else
  {
    text("The output doesn't have a pan control.", 5, 15);
  }
 
  if ( out.hasControl(Controller.VOLUME) )
  {
    text("The current volume value is " + out.getVolume() + ".", 5, 30);
  }
  else
  {
    text("The output doesn't have a volume control.", 5, 30);
  }
 
  if ( out.hasControl(Controller.BALANCE) )
  {
    text("The current balance value is " + out.getBalance() + ".", 5, 45);
  }
  else
  {
    text("The output doesn't have a balance control.", 5, 45);
  }
 
  if ( out.hasControl(Controller.GAIN) )
  {
    text("The current gain value is " + out.getGain() + ".", 5, 60);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 60);
  }
}
 
void keyReleased()
{
  if ( key == 'v' ) out.shiftVolume(0, 1, 2000);
  if ( key == 'g' ) out.shiftGain(-40, 0, 2000);
  if ( key == 'b' ) out.shiftBalance(-1, 1, 2000);
  if ( key == 'p' ) out.shiftPan(1, -1, 2000);
}
 
void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  super.stop();
}

Muting

Finally, muting is often available as a control on a line. The muting methods are as follows, their names should be pretty self-explanatory:

isMuted()
mute()
unmute()

Once again, if muting is not an available control, muting and unmuting will do nothing and an error will be printed in the console informing you that muting is not available.

Code Sample (view online)

import ddf.minim.*;
import ddf.minim.signals.*;
 
AudioOutput out;
// this class is defined in a separate file
// see the online example for the code
WaveformRenderer waveform;
SawWave saw;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
 
  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);
 
  // see the example AudioOutput >> SawWaveSignal for more about this
  saw = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(saw);
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();
 
  if ( out.hasControl(Controller.MUTE) )
  {
    if (mousePressed) 
    {
      out.mute();
    }
    else 
    {
      out.unmute();
    }
    if ( out.isMuted() )
    {
      text("The output is muted.", 5, 15);
    }
    else
    {
      text("The output is not muted.", 5, 15);
    }
  }
  else
  {
    text("The output doesn't have a mute control.", 5, 15);
  }
}
 
void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  super.stop();
}

Using A Control Directly

All of the methods described so far are actually convenience methods that access the appropriate Controls of a Line and call the appropriate methods. However, it is possible for you to directly access these JavaSound controls and manipulate them yourself. The first way to do this is to use the getControls method of Controller. This method returns an array of Control objects which you will then need to cast to the appropriate class (either FloatControl or BooleanControl). You can determine the control’s type by calling its getType method and then comparing the returned Control.Type to one of the static members of Controller mentioned above. You might also use instanceof to simply determine which class it is before casting it.

Code Sample (view online)

import ddf.minim.*;
// note that you have to import the Control class
import javax.sound.sampled.Control;
 
AudioOutput out;
Control[] controls;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
  controls = out.getControls();
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
 
  for ( int i = 0; i < controls.length; i++ )
  {
    text("Control " + (i+1) + " is a " + controls[i].toString() + ".", 5, 15 + i*15);
  }
}
 
void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  super.stop();
}

Controller also has four methods for getting a specific control. They are:

volume()
gain()
balance()
pan()

Each of these methods returns the appropriate FloatControl. They do not check availablity before trying to get the control from the Line and will throw an IllegalArgumentException if the control is not available. You can avoid this situation by checking for a control’s availability with hasControl before calling one of these methods. Here’s an example of using pan() to access the pan control, rather than using the setPan method.

Code Sample (online example)

import ddf.minim.*;
import ddf.minim.signals.*;
 
AudioOutput out;
Oscillator  osc;
// this class is defined in a separate file
// see the online example for the code
WaveformRenderer waveform;
 
void setup()
{
  size(512, 200);
  // always start Minim first
  Minim.start(this);
  out = Minim.getLineOut();
 
  // see the example AudioOutput >> SawWaveSignal for more about this class
  osc = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(osc);
 
  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform); 
 
  textFont(createFont("Arial", 12));
}
 
void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();
 
  if ( out.hasControl(Controller.PAN) )
  {
    // map the mouse position to the range of the pan
    float val = map(mouseX, 0, width, out.pan().getMinimum(), out.pan().getMaximum());
    out.pan().setValue(val);
    text("The current pan is " + out.pan().getValue() + ".", 5, 15);
  }
  else
  {
    text("There is no pan control for this output.", 5, 15);
  }
}
 
void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  super.stop();
}

Detailing all of the methods of a FloatControl here would be a bit much, so instead I recommend you take a look at the Javadoc for the class or check out my online examples for the class.