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)
/** * This sketch demonstrates how to use the <code>printControls</code> method of a <code>Controller</code> object. * The class used here is an <code>AudioOutput</code> but you can also print the controls of <code>AudioSample</code>, * <code>AudioSnippet</code>, <code>AudioInput</code>, and <code>AudioPlayer</code> objects. <code>printControls</code> * will print out all the available controls (such as volume, gain, etc) and their ranges to the console. * If you are running this sketch in a web browser you will have to open the Java console to see the printout. <br /> */ import ddf.minim.*; Minim minim; AudioOutput out; void setup() { size(512, 200); minim = new Minim(this); out = minim.getLineOut(); out.printControls(); } void draw() { background(0); } void stop() { // always close Minim audio classes when you are finished with them out.close(); minim.stop(); 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)
/** * This sketch demonstrates how to use the <code>hasControl</code> method of a <code>Controller</code> object. * The class used here is an <code>AudioOutput</code> but you can also use test for controls of * <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, and <code>AudioPlayer</code> objects. * <code>hasControl</code> takes one of the static control types of <code>Controller</code> as the argument. These are * <code>Controller.BALANCE</code>, <code>Controller.GAIN</code>, <code>Controller.MUTE</code>, <code>Controller.PAN</code>, * <code>Controller.SAMPLE_RATE</code>, and <code>Controller.VOLUME</code>. You should always check if a control * is available before you attempt to use it. */ import ddf.minim.*; Minim minim; AudioOutput out; void setup() { size(512, 200); minim = new Minim(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 when you are finished with them out.close(); minim.stop(); 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)
/** * This sketch demonstrates how to use the <code>getGain</code> and <code>setGain</code> methods of a * <code>Controller</code> object. The class used here is an <code>AudioOutput</code> but you can also * get and set the gain of <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, * and <code>AudioPlayer</code> objects. <code>getGain</code> and <code>setGain</code> will get and set * the gain of the <code>DataLine</code> that is being used for input or output, but only if that line has * a gain control. A <code>DataLine</code> is a low-level JavaSound class that is used for sending audio to, * or receiving audio from, the audio system. You will notice in this sketch that you will hear the gain * changing (if it's available) but you will not see any difference in the waveform being drawn. The reason for this * is that what you see in the output's sample buffers is what it sends to the audio system. The system makes the * gain change after receiving the samples. */ import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; Oscillator osc; WaveformRenderer waveform; void setup() { size(512, 200); minim = new Minim(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(); minim.stop(); 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 ms) shiftGain(float from, float to, int ms) shiftPan(float from, float to, int ms) shiftVolume(float from, float to, int ms)
As is probably obvious, from is the value to start at, to is the target value, and ms 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)
/** * This sketch demonstrates how to use the <code>shiftVolume</code>, <code>shiftGain</code>, <code>shiftBalance</code>, * and <code>shiftPan</code> methods of a <code>Controller</code> object. The class used here is an <code>AudioOutput</code> * but you can also shift controls of <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, * and <code>AudioPlayer</code> objects. The shift methods allow you to transition the value of a control from one * value to another one over a given number of milliseconds. Shifting will only work if the control is available * (see the example hasControl for more about that). Also, please note that the shift methods of <code>Controller</code> * are not the same thing as the method <code>FloatControl.shift</code>. A <code>Controller</code>'s shift methods will * always work if a control is available, but a <code>FloatControl</code>'s shift method will only work if the control * supports shifting (see the example Controller >> FloatControl >> shift for more). * <p> * Press 'v' to shift the volume.<br /> * Press 'g' to shift the gain.<br /> * Press 'b' to shift the balance.<br /> * Press 'p' to shift the pan.<br /> */ import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; WaveformRenderer waveform; SawWave saw; void setup() { size(512, 200); minim = new Minim(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(); minim.stop(); 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)
/** * This sketch demonstrates how to use the <code>mute</code>, <code>unmute</code>, and <code>isMuted</code> methods * of a <code>Controller</code> object. The class used here is an <code>AudioOutput</code> but you can also * mute/unmute <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, and <code>AudioPlayer</code> objects. * <code>isMuted</code> returns true or false depending on the current mute state. <code>mute</code> and <code>unmute</code> * do exactly what they claim to. However, it is possible that your object will not have a mute control, in which case * <code>isMuted</code> will always return false and <code>mute</code> and <code>unmute</code> will do nothing. If * muting is available you will notice that muting the output doesn't change the waveform being drawn. The reason for * this is that the <code>DataLine</code> carrying the audio to the system is what is being muted and *not* the * output's signal generation (see the example Polyphonic >> soundNoSound for more about that). * <p> * Hold down the mouse button to mute the output and release the mouse button to unmute it. */ import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; WaveformRenderer waveform; SawWave saw; void setup() { size(512, 200); minim = new Minim(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(); minim.stop(); 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)
/** * This sketch demonstrates how to use the <code>getControls</code> method of a <code>Controller</code> object. * The class used here is an <code>AudioOutput</code> but you can also get the controls of * <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, and <code>AudioPlayer</code> objects. * <code>getControls</code> returns an array of JavaSound <code>Control</code> objects. You can determine what type * of <code>Control</code> each one is by using the <code>getType</code> method and then comparing the type to one * of the static control types of <code>Controller</code> (see the hasControl example). If you plan to use the * <code>getControls</code> method you must also import the <code>Control</code> class from JavaSound (see the source). */ import ddf.minim.*; import ddf.minim.signals.*; import javax.sound.sampled.Control; Minim minim; AudioOutput out; Control[] controls; void setup() { size(512, 200); minim = new Minim(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(); minim.stop(); super.stop(); }
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)
/** * This sketch demonstrates how to use the <code>getValue</code> and <code>setValue</code> methods of a * <code>FloatControl</code> object. A <code>FloatControl</code> is what is returned by the <code>gain</code>, * <code>volume</code>, <code>pan</code>, and <code>balance</code> methods of a <code>Controller</code> object. * The class used here is an <code>AudioOutput</code> but these control methods are also available on * <code>AudioSample</code>, <code>AudioSnippet</code>, <code>AudioInput</code>, and <code>AudioPlayer</code> objects. * The <code>FloatControl</code> class is defined by the JavaSound API and it * represents a control of a <code>DataLine</code>. A <code>DataLine</code> is a low-level JavaSound class that * is used for sending audio to, or receiving audio from, the audio system. <code>getValue</code> and <code>setValue</code> * allow you to get and set the value of the control. The default implementation simply sets the value as indicated. * If the value indicated is greater than the maximum value, or smaller than the minimum value, * an <code>IllegalArgumentException</code> is thrown. You can avoid having to possibly deal with this exception by using * the appropriate set method of <code>Controller</code>, which will keep the value in the allowed range. * <p> * Move the mouse left and right to change the pan value. */ import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; Oscillator osc; WaveformRenderer waveform; void setup() { size(512, 200); minim = new Minim(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(); minim.stop(); 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.
hi there!
is there anyway to control the sample rate, thus changing playback speed of an audioplayer file while playing??
tried lots of stuff, but nothing works.
the code handling sampling rate looks like this so far:
if (player.isPlaying()) {
if (player.hasControl(Controller.SAMPLE_RATE)) {
float val = map(mouseX, 0, width, 0.0, 44100.0);
println(player.sampleRate().setValue(val));
}
}
i get “Cannot invoke setValue(float) on the primitive type float”
any help is very much appreciated!
cheers.
The sampleRate() method you are calling returns a float value that is the current sample rate of the player. It’s not like the pan() method, which returns a FloatControl that you can use to change the pan value. A sample rate control is typically not available, but if it is, you can get it by using player.getControl(Controller.SAMPLE_RATE). If there is no sample rate control available, this method will return null, so be sure you check for that or your program will crash when you try to use the return value.
hi. i’ve been working through your tutorial which i’ve found to be quite enjoyable!
on the examples above:
import ddf.minim.*;
import ddf.minim.signals.*;
Minim minim;
AudioOutput out;
WaveformRenderer waveform;
SawWave saw;
i get a “Cannot find a class or type named “WaveformRenderer” error.
i’m using processing 1.0.9 with minim 2.0.2
any ideas about this?
thanks!
okay! i finally figured out what is going on!
it was just that missing piece of code defining the WaveformRenderer class…
this is really cool! thanks again!
jasper
when I try to call this method on my audioplayer object:
audioPlayerObject.hasControl(Controller.VOLUME)
it tells me that the function does not exist. How do you change volume or gain on an audioplayer object???
hasControl most certainly exists: http://code.compartmental.net/minim/javadoc/ddf/minim/Controller.html#hasControl(javax.sound.sampled.Control.Type)
Is your program running and it’s printing out that the *control* doesn’t exist? That is sometimes the case, depending on the Java implementation you are running on. Often, if something doesn’t have a VOLUME control, it will have a GAIN.
Loads of thanks for your impressive work.
I’m wondering if there is any way of changing the playback speed of recorded audio. ..or if you have any advice for me where to start looking if I want to create a class (within or without Minim) which can do this.