Java Midi Volume Control | Code Zealot

Java Midi Volume Control

in Java

As you may know already Java supports the MIDI 1.0 spec in its core classes. It is still common for MIDI audio to be used in games because of small size and nostalgia. Nothing makes me smirk more than a really cheesy synthesized MIDI…

But anyway, as I was developing the MIDI support in my base game code I wanted to add volume control. This seems easy, and at first sight/Google its about 4 lines of code.

Setting up to play an MIDI:

// get all the default stuff
Sequencer sequencer = MidiSystem.getSequencer(false);
Synthesizer synthesizer = MidiSystem.getSynthesizer();
Receiver receiver = synthesizer.getReceiver();
// open the synth and sequencer and wire up the receiver and transmitter
synthesizer.open();
sequencer.open();
sequencer.getTransmitter().setReceiver(receiver);
// play the MIDI
sequencer.setSequence(sequence);
sequencer.start();

And to control the volume:

MidiChannel[] channels = synthesizer.getChannels();
for (int i = 0; i < channels.length; i++) {
  channels[i].controlChange(7, volume);
}

Unfortunately it was not so easy. So I attempted to use the various code snippets around the web and nothing ever worked. So after some time of searching (like 2 months…) I finally found the . Read the first paragraph… well what do you know, I happen to be developing and testing on a windows box…

So after my long search I found out what the problem was, the JRE for Windows does not ship with a soundbank and by default the JRE will use the hardware soundbank. So you might ask, “What does it matter that the JRE uses the hardware soundbank instead of a packaged one, the volume control should be the same?” Unfortunately it’s not.

The process is basically the same except that we need to use the default receiver of the MidiSystem object instead of the default synthesizer’s receiver. And in addition to this we need to use the default receiver to modify the volume.

The modified code:

// get all the default stuff
Sequencer sequencer = MidiSystem.getSequencer(false);
Receiver receiver = MidiSystem.getReceiver();
// open the sequencer and wire up the receiver
// and transmitter
sequencer.open();
sequencer.getTransmitter().setReceiver(receiver);
// play the MIDI
sequencer.setSequence(sequence);
sequencer.start();

And to control the volume:

ShortMessage volMessage = new ShortMessage();
for (int i = 0; i < 16; i++) {
  try {
    volMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, midiVolume);
  } catch (InvalidMidiDataException e) {}
  receiver.send(volMessage, -1);
}

10 Comments

10 Comments

  1. Hi I wrote the below code as explained by you.But the Volume controller is not working .
    Could U please help me to find out the issue.

    import java.io.File;
    import java.io.IOException;
    import java.net.MalformedURLException;
    
    import javax.sound.midi.*;
    
    public class MidiSynthesizerSample {
    	public static void main(String[] args) {
    		// get all the default stuff
    		try {
    			Sequence sequence = MidiSystem.getSequence(new File("c:/1.mid"));
    			Sequencer sequencer = MidiSystem.getSequencer(false);
    			Receiver receiver = MidiSystem.getReceiver();
    			// open the sequencer and wire up the receiver
    			// and transmitter
    			sequencer.open();
    			sequencer.getTransmitter().setReceiver(receiver);
    			// play the MIDI
    			sequencer.setSequence(sequence);
                            setVolume(0);
    			sequencer.start();
    
    		} catch (MalformedURLException e) {
    
    		} catch (IOException e) {
    
    		} catch (MidiUnavailableException e) {
    
    		} catch (InvalidMidiDataException e) {
    		}
    
    		
    	}
    
    	public static void setVolume(int value) {
    		try {
    			ShortMessage volumeMessage = new ShortMessage();
    			for (int i = 0; i &lt; 16; i++) {
    				volumeMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7,
    						value);
    				MidiSystem.getReceiver().send(volumeMessage, -1);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    
  2. Looks like your code is good. I tried my own Midi class (which basically has the same code as yours) and it seemed to work…strange. Take a look at this modified code:

    import java.io.File;
    import java.io.IOException;
    import java.net.MalformedURLException;
    
    import javax.sound.midi.*;
    
    public class MidiSynthesizerSample {
    	public static void main(String[] args) {
    		// get all the default stuff
    		try {
    			Sequence sequence = MidiSystem.getSequence(new File("c:/bass_1.mid"));
    			Sequencer sequencer = MidiSystem.getSequencer(false);
    			Receiver receiver = MidiSystem.getReceiver();
    			// open the sequencer and wire up the receiver
    			// and transmitter
    
    			sequencer.open();
    			sequencer.getTransmitter().setReceiver(receiver);
    			// play the MIDI
    			sequencer.setSequence(sequence);
    			sequencer.start();
    
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e1) {
    				// TODO Auto-generated catch block
    				e1.printStackTrace();
    			}
    
    			setVolume(20);
    
    			try {
    				Thread.sleep(5000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    
    			sequencer.close();
    			receiver.close();
    
    		} catch (MalformedURLException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} catch (InvalidMidiDataException e) {
    			e.printStackTrace();
    		} catch (MidiUnavailableException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void setVolume(int value) {
    		try {
    			ShortMessage volumeMessage = new ShortMessage();
    			for (int i = 0; i &lt; 16; i++) {
    				volumeMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, value);
    				MidiSystem.getReceiver().send(volumeMessage, -1);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    

    It works for me. It seems that you have to give the sequencer time to open before actually setting the volume in the case that Java uses the hardware synthesizer (I have it pausing for 1 second here but I tried 10 milliseconds and that seemed to work as well). I tested with a couple of midi’s that I had and it reminded me of another problem you might run into: midi files can contain the volume control messages! So make sure you midi is an midi that the volume can be changed on.

    One more thing. If you are deploying a real app that uses Midi, I would recommend just packaging a soundbank with your application and load it up like:

    synthesizer = MidiSystem.getSynthesizer();
    synthesizer.loadAllInstruments(MidiSystem.getSoundbank(inputStream));
    
    

    This will allow you to use the default synthesizer instead of using the finicky hardware one.

  3. Hi,
    1.I tried as U have mentioned/Provided but even now the Volume is not changing.I tried adding the Thread also..But no vain.Can U please share me the Midi where the volume controller information is available.

    2. I tried using the default synthesizer as provided by U and as below.

       File file = new File("c:/1.mid");
       Synthesizer synthesizer = MidiSystem.getSynthesizer();
       synthesizer.loadAllInstruments(MidiSystem.getSoundbank(file));
    
    

    But I am getting the below exception.
    javax.sound.midi.InvalidMidiDataException: cannot get soundbank from stream
    at javax.sound.midi.MidiSystem.getSoundbank(Unknown Source)
    at MidiSynthesizerSample.main(MidiSynthesizerSample.java:18)

    Please HELP me to fix it.

  4. 1. Try your code on this midi file and see if it works. Unfortunately I’m not familiar with any methods to modify or inspect midi files so I can’t help you find out if the file you are using is the problem or not. The linked midi file is the one I tested with and it seems to work.

    2. If you want to use the default synthesizer you must go out and download a soundbank from . Then you basically do everything the same:

    // the sound bank file
    File soundbank = new File("c:/soundbank.gm");
    // the midi file
    File sequence = new File("c:/1.mid");
    // get the sequencer
    Sequencer sequencer = MidiSystem.getSequencer(false);
    // get the synthesizer
    Synthesizer synthesizer = MidiSystem.getSynthesizer();
    // load up the instruments into the synthesizer
    synthesizer.loadAllInstruments(MidiSystem.getSoundbank(soundbank));
    // open the synthesizer
    synthesizer.open();
    // get the synthesizer's receiver
    Receiver receiver = synthesizer.getReceiver();
    // open the sequencer
    sequencer.open();
    // wire up the two
    sequencer.getTransmitter().setReceiver(receiver);
    // set the midi file to play
    sequencer.setSequence(sequence);
    // start the playback
    sequencer.start();
    // set the volume the normal way
    setVolume(10);
    
    public static void setVolume(int volume, Synthesizer synthesizer) {
      MidiChannel[] channels = synthesizer.getChannels();
      // set the master volume for each channel
      for (int i = 0; i < channels.length; i++) {
        channels[i].controlChange(7, volume);
      }
    }
    
    

    You can also take a look at my implementation which seems to work for me here. Feel free to copy and modify. You use it like the following:

    // create a new midi object
    // NOTE: the file must be on the classpath
    Midi midi = new Midi("/1.mid");
    midi.open();
    midi.play();
    midi.setVolume(10);
    // then when you want to stop it
    midi.stop();
    // dont forget to close it when you app closes
    midi.close();
    
    
  5. Hi,
    I am able to Set the Volume Now. :). The initialization of the receiver was not correct in my code.Your code help me to find that out and fix it.

    synthesizer.open();
    receiver = synthesizer.getReceiver();
    
    

    Thanks a lot for Your Help.

    As U have mentioned before (give the sequencer time to open before actually setting the volume). How can I achieve it without putting the Sleep.
    I have tried with

    File soundbank= new File("c:/soundbank.gm");
    synthesizer.loadAllInstruments(MidiSystem.getSoundbank(soundbank));
    
    

    and also tried to see the infinite while loop of like below

    while (sequencer.getMicrosecondPosition()<=0) {
      System.out.println("Current Position:"+sequencer.getMicrosecondPosition());
    }
    setVolume(20);
    
    

    But both are not working.

  6. Awesome, I glad to hear that, no pun intended. Yes, as you point out a busy wait will probably not work, however a Thread.sleep(10) will (at least did for me).

    If you didn’t want to do this then you may be able to listen for a “start” event, then set the volume. I have not done this myself you but if you want, you can check out how to listen for events . You can listen for either Meta events or control events.

    If it exists, a start event would be a Meta event. If you look at my code you’ll see that I already listen for a “track end” event. It should be very similar to this. Once you receive the event, set the volume. I’m not sure what the event number is but you can reference to try to figure it out.

    As far as my code is concerned, it seems like I must be doing enough before setting the volume so that I don’t have this problem. However, I have noticed that this problem will crop up from time to time.

    Hope this helps

  7. It worked :) Thanks a lot for the gr8 help which you have done.
    No Thread.sleep and volume is set as it starts playing or initialized.

  8. Hi,
    Again a wired issue. At some time I get the below error
    javax.sound.midi.MidiUnavailableException: MIDI OUT transmitter not available
    while executing the line
    this.sequencer.getTransmitter().setReceiver(receiver);
    Any idea why is it happening?.

  9. I can’t say that I have ever seen that error before. I’m sure you are already doing this, but make sure you call the close method on all the MIDI stuff when you are done; see my code again. This could happen if you played a bunch of MIDIs and didn’t close the resources. Do you have the same problem using my code?

  10. Hi William,
    :) I have closed all the object as very much similar to what U have done. In fact I restarted my system after that.Then also 1st time when I execute itself I am getting this error.I changed my JDK ver from 1.6 to 1.5 now its working.
    Dono shld I be happy or not..:)

    Thanks a lot for Ur help.

    Regards,
    Uvaraj S

Leave a Reply

Using in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href="/"> <b> <blockquote> <code> <em> <i> <strike> <strong>