Midi Volume (Not jPCT But Java)

Started by AGP, February 03, 2009, 05:48:13 PM

Previous topic - Next topic

AGP

I've read everything on the subject that I could find by searching Google. Figured somebody here might know. I cannot, for the life of me, change the volume of the midi playback in my program. The following is my code. The last part (before the call to start()) is my attempt at discovering which Synthesizer is attached to the Sequencer. And the very last nested loop was an attempt to change everyone's volume. By the way, if my test for synthesizer.getDefaultSoundbank() == null is valid, I'm using the software synthesizer on my computer.

        private void playMidi(String fileName) {
try {
     midiPlayer = MidiSystem.getSequencer(true);
     Synthesizer synthesizer = MidiSystem.getSynthesizer();
     midiPlayer.open();
     synthesizer.open();
     Transmitter transmitter = midiPlayer.getTransmitter();

     if (synthesizer.getDefaultSoundbank() == null) {//MEANING "IF USING HARDWARE (NOT JRE) SYNTHESIZER"
transmitter.setReceiver(MidiSystem.getReceiver());//THIS IS RIGHT FOR HARDWARE
System.out.println("Hardware synthesizer!");
     }
     else {
transmitter.setReceiver(synthesizer.getReceiver());//SYNTHESIZER.GETRECEIVER() IS RIGHT FOR SOFTWARE

System.out.println("Software synthesizer in use!");
     }

     midiPlayer.setSequence(MidiSystem.getSequence(new File(fileName)));

     ShortMessage volumeMessage = new ShortMessage();
     int numberOfChannels = synthesizer.getChannels().length;
     for (int i = 0; i < numberOfChannels; i++) {
volumeMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, 5); //7 IS MASTER VOLUME CONTROLLER, 5 IS VOLUME BETWEEN 0-127
transmitter.getReceiver().send(volumeMessage, -1);
     }
System.out.println("Is the sequencer an instance of Synthesizer? "+(midiPlayer instanceof Synthesizer));

     MidiDevice device = MidiSystem.getMidiDevice(midiPlayer.getDeviceInfo());
     java.util.List<Transmitter> transmitters = device.getTransmitters();
     Transmitter[] transmitterArray = new Transmitter[transmitters.size()];
     transmitterArray = transmitters.toArray(transmitterArray);

for (int y = 0; y < transmitterArray.length; y++) {
for (int x = 0; x < 16; x++) {
volumeMessage.setMessage(ShortMessage.CONTROL_CHANGE, y, 7, 5); //7 IS MASTER VOLUME CONTROLLER, 5 IS VOLUME BETWEEN 0-127
transmitterArray[y].getReceiver().send(volumeMessage, -1);
}
}

     MidiChannel[] channels = synthesizer.getChannels();
     for (int i = 0; i < channels.length; i++){
channels[i].controlChange(7, 5);
System.out.println("Can we lower the volume? "+(channels[i].getController(7)!=0));}

     midiPlayer.start();
}
catch (Exception e) {System.out.println("Midi File Error: "+e.getMessage());}

paulscode

I have been using the following method for changing MIDI volume, which seems to work for me:

/**
* Resets playback volume to the correct level.
*/
    public void resetGain()
    {
        // make sure the value for gain is valid (between 0 and 1)
        if( gain < 0.0f )
            gain = 0.0f;
        if( gain > 1.0f )
            gain = 1.0f;
       
        int midiVolume = (int) ( gain * SoundSystemConfig.getMasterGain()
                                 * 127.0f );
        if( synthesizer != null )
        {
            javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();
            for( int c = 0; channels != null && c < channels.length; c++ )
            {
                channels[c].controlChange( CHANGE_VOLUME, midiVolume );
            }
        }
        else if( sequencer != null && sequencer instanceof Synthesizer )
        {
            synthesizer = (Synthesizer) sequencer;
            javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();
            for( int c = 0; channels != null && c < channels.length; c++ )
            {
                channels[c].controlChange( CHANGE_VOLUME, midiVolume );
            }
        }
        else
        {
            try
            {
                Receiver receiver = MidiSystem.getReceiver();
                ShortMessage volumeMessage= new ShortMessage();
                for( int c = 0; c < 16; c++ )
                {
                    volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, c,
                                              CHANGE_VOLUME, midiVolume );
                    receiver.send( volumeMessage, -1 );
                }
            }
            catch( Exception e )
            {
                errorMessage( "Error resetting gain for MIDI source" );
                printStackTrace( e );
            }
        }
    }


In this code, 'gain' is a float between 0.0f and 1.0f, 'sequencer' was created from MidiSystem.getSequencer(), and 'synthesizer' was created from MidiSystem.getSynthesizer() if 'sequencer' was not an instance of Synthesizer.  For reference, see the 'MidiChannel' class in the source code for SoundSystem.

AGP

#2
Have a look at mine. It's no different than yours as far as I can tell.

An observation: wouldn't the following code (your code) always yield true for the first one (or false for both, but never true for the second)? You would have to flip that test to make sure either can be true.
        if( synthesizer != null ) {
...
        }
        else if( sequencer != null && sequencer instanceof Synthesizer){
...
        }

AGP

By the way, I assume your CHANGE_VOLUME is 7. If not what is it?

paulscode

#4
Quote from: AGP on February 03, 2009, 10:07:21 PM
An observation: wouldn't the following code (your code) always yield true for the first one (or false for both, but never true for the second)? You would have to flip that test to make sure either can be true.
        if( synthesizer != null ) {
...
        }
        else if( sequencer != null && sequencer instanceof Synthesizer){
...
        }


No, because as I mentioned, in my code, 'synthesizer' was only instantiated if sequencer was not an instance of Synthesizer.  So in the case that sequencer is an instance of Synthesizer, the first would yield false, and the second would yield true.  I could of course remove that 'else if' by calling synthesizer = (Synthesizer) sequencer; earlier in the code when checking if sequencer is an instance of synthesizer.

paulscode

#5
Actually, upon closer examination, I think your problem might be coming from the fact that you are sending the volume change messages BEFORE you started the sequence.  Generally, when the sequence begins, default volume messages are sent, so you need to change the volume after starting the sequence.

I am running some tests, so I'll update you if I can get the volume changes to work properly.

paulscode

#6
Ok, I altered your playMidi method slightly, and it seems to be working.  One important thing to note - volume changes for MIDI are not a linear function, and they can be overwritten if volume events are written into the sequence by the composer.  Also, depending on the MIDI file and synthesizer, a volume of "0" does not necessarily mean completely silent.  That being said, at least it is usually possible to reduce the volume down to an acceptable level.  Here is the altered playMidi method (in a simple application), which works fine on my computer:

import java.io.IOException;
import java.net.URL;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Transmitter;

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

    public MIDIVolumeProblem()
    {
        playMidi( "beethoven.mid", 0 );
       
        try
        {
            Thread.sleep( 60000 );
        }
        catch( InterruptedException ie )
        {}
    }
   
    private void playMidi( String filename, int midiVolume )
    {
        int CHANGE_VOLUME = 7;
        Sequencer midiPlayer = null;
        Synthesizer synthesizer = null;
        Sequence sequence = null;
       
        URL midiFile = getClass().getClassLoader().getResource(
                                                              "beethoven.mid" );
       
        if( midiFile == null )
        {
            System.err.println( "Unable to load Midi file." );
            return;
        }
        try
        {
            sequence = MidiSystem.getSequence( midiFile );
            midiPlayer = MidiSystem.getSequencer();
            midiPlayer.open();
            midiPlayer.setSequence( sequence );
        }
        catch( IOException ioe )
        {
            System.err.println( "Input failed while reading from MIDI file." );
            ioe.printStackTrace();
            return;
        }
        catch( InvalidMidiDataException imde )
        {
            System.err.println( "Invalid MIDI data encountered, or not a MIDI "
                                + "file." );
            imde.printStackTrace();
            return;
        }
        catch( MidiUnavailableException mue )
        {
            System.err.println( "MIDI unavailable, or MIDI device is already "
                                + "in use." );
            mue.printStackTrace();
            return;
        }
        catch( Exception e )
        {
            System.err.println( "Problem loading MIDI file." );
            e.printStackTrace();
            return;
        }
       
        if( midiPlayer instanceof Synthesizer )
        {
            synthesizer = (Synthesizer) midiPlayer;
        }
        else
        {
            try
            {
                synthesizer = MidiSystem.getSynthesizer();
                synthesizer.open();
                Receiver receiver = synthesizer.getReceiver();
                Transmitter transmitter = midiPlayer.getTransmitter();
                transmitter.setReceiver( receiver );
            }
            catch( MidiUnavailableException mue )
            {
                System.err.println( "MIDI unavailable, or MIDI device is "
                                    + "already in use." );
                mue.printStackTrace();
                return;
            }
            catch( Exception e )
            {
                System.err.println( "Problem initializing the MIDI "
                                    + "synthesizer." );
                e.printStackTrace();
                return;
            }
        }
       
        midiPlayer.start();
       
        // wait a couple of seconds so you can hear the volume change:
        // TODO: remove this
        try
        {
            Thread.sleep( 2000 );
        }
        catch( InterruptedException ie )
        {}
       
        javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();
        for( int c = 0; channels != null && c < channels.length; c++ )
        {
            channels[c].controlChange( CHANGE_VOLUME, midiVolume );
        }
    }
}


Oh, and I should mention, if you use a program (such as Cakewalk) to compose or edit your MIDI files, if you set the default volume for each channel very low, then you should have more control over the volume in your program (I haven't tested that yet myself, but that's the advice I got from someone else).  Also, possible values for midVolume can actually go up to 255 (anything over 127 is an increase in the channel's volume, while values lower than 127 decrease the volume).

AGP

Thanks for your help, pal, but as yours hasn't worked for me either, I'm going to assume that it's my hardware (or maybe fact that I'm running the Windows 7 beta). But moving the start() call up was a good suggestion.

paulscode

#8
Hm, that is odd.  Have you tried using different midi files to see if volume changes on any of them?

I went ahead and added back in the checking for default soundbank code.  It doesn't seem to be required on my computer, but who knows - it could be required on some computers.  This way also seems to work fine on my computer:

    private void playMidi( String filename, int midiVolume )
    {
        int CHANGE_VOLUME = 7;
        Sequencer midiPlayer = null;
        Synthesizer synthesizer = null;
        Sequence sequence = null;
       
        URL midiFile = getClass().getClassLoader().getResource(
                                                              "beethoven.mid" );
       
        if( midiFile == null )
        {
            System.err.println( "Unable to load Midi file." );
            return;
        }
        try
        {
            sequence = MidiSystem.getSequence( midiFile );
            midiPlayer = MidiSystem.getSequencer();
            midiPlayer.open();
            midiPlayer.setSequence( sequence );
        }
        catch( IOException ioe )
        {
            System.err.println( "Input failed while reading from MIDI file." );
            ioe.printStackTrace();
            return;
        }
        catch( InvalidMidiDataException imde )
        {
            System.err.println( "Invalid MIDI data encountered, or not a MIDI "
                                + "file." );
            imde.printStackTrace();
            return;
        }
        catch( MidiUnavailableException mue )
        {
            System.err.println( "MIDI unavailable, or MIDI device is already "
                                + "in use." );
            mue.printStackTrace();
            return;
        }
        catch( Exception e )
        {
            System.err.println( "Problem loading MIDI file." );
            e.printStackTrace();
            return;
        }
       
        if( midiPlayer instanceof Synthesizer )
        {
            synthesizer = (Synthesizer) midiPlayer;
        }
        else
        {
            try
            {
                synthesizer = MidiSystem.getSynthesizer();
                synthesizer.open();
                if( synthesizer.getDefaultSoundbank() == null )
                {
                    midiPlayer.getTransmitter().setReceiver(
                                                     MidiSystem.getReceiver() );

                }
                else
                {
                    midiPlayer.getTransmitter().setReceiver(
                                                    synthesizer.getReceiver() );
                }
            }
            catch( MidiUnavailableException mue )
            {
                System.err.println( "MIDI unavailable, or MIDI device is "
                                    + "already in use." );
                mue.printStackTrace();
                return;
            }
            catch( Exception e )
            {
                System.err.println( "Problem initializing the MIDI "
                                    + "synthesizer." );
                e.printStackTrace();
                return;
            }
        }
       
        midiPlayer.start();
       
        try
        {
            Thread.sleep( 2000 );
        }
        catch( InterruptedException ie )
        {}
       
        if( synthesizer.getDefaultSoundbank() == null )
        {
            try
            {
                ShortMessage volumeMessage = new ShortMessage();
                for( int i = 0; i < 16; i++ )
                {
                    volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i,
                                              CHANGE_VOLUME, midiVolume );
                    MidiSystem.getReceiver().send( volumeMessage, -1 );
                }
            }
            catch( InvalidMidiDataException imde )
            {
                System.err.println( "Invalid MIDI data encountered, or not a"
                                    + "MIDI file." );
                imde.printStackTrace();
                return;
            }
            catch( MidiUnavailableException mue )
            {
                System.err.println( "MIDI unavailable, or MIDI device is "
                                    + "already in use." );
                mue.printStackTrace();
                return;
            }
        }
        else
        {
            javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();

            for( int c = 0; channels != null && c < channels.length; c++ )
            {
                channels[c].controlChange( CHANGE_VOLUME, midiVolume );
            }
        }
    }


Sorry I couldn't be of any more help.

paulscode

I would like to see if this volume problem exists on a lot of computers, so I created a test applet:

MIDI Volume Test

This applet uses the same code as in my last post for changing midi volume.  Use the up and down arrows to change the volume (you might have to go down a ways before you hear a change).

If any of you all have the time, would you please run this applet, and let me know if the volume changes correctly for you?  Please mention your operating system and hardware.  Thanks in advance!

AGP

About the soundbank test, that's the whole point: it works in software on most computers, but hardware on most Windows JREs. The point is to work on all computers. And your applet didn't work on my laptop (the computer I'm currently using since I'm not home), whose sound chip I can't identify since Windows 7 only tells me "High Definition Audio Device" and it's a Microsoft driver.

AGP

To clarify: the applet ran fine, but the volume (whose value changed) didn't change.

paulscode

Quote from: AGP on February 04, 2009, 05:45:33 AM
About the soundbank test, that's the whole point: it works in software on most computers, but hardware on most Windows JREs. The point is to work on all computers. And your applet didn't work on my laptop (the computer I'm currently using since I'm not home), whose sound chip I can't identify since Windows 7 only tells me "High Definition Audio Device" and it's a Microsoft driver.
That is strange.  I am running Windows XP and Windows Vista on the three test machines I used to test MIDI so far, with various sound cards - RealTec Ac'97, ESS Meistro, and SigmaTel High Definition Audio Device, and volume changes work fine for all of them in both applets and applications (to clarify, volume changes and becomes quiet/loud, but doesn't necessarily become completely silent with a volume message of 0).
I'm wondering if it is a Windows 7 issue, as you mentioned.  I don't have that OS to test it here.  When I have more time later, I am going to create a more verbose test program to post on various forums, and I will try it out on more of my test machines as well.  I'm glad you brought this to my attention - If possible, I would like to figure out a solution that works for all systems.

C3R14L.K1L4

#13
Quote from: paulscode on February 04, 2009, 04:52:34 AM
If any of you all have the time, would you please run this applet, and let me know if the volume changes correctly for you?  Please mention your operating system and hardware.  Thanks in advance!

Volume change works, but when set to '0' I still hear music.
Using kX Driver on a Audigy2. The same on my laptop with a integrated Conexant High Definition Audio chip. Both pcs with XP SP2/SP3.

EgonOlsen

Quote from: C3R14L.K1L4 on February 04, 2009, 10:19:08 PM
Volume change works, but when set to '0' I still hear music.
Using kX Driver on a Audigy2. The same on my laptop with a integrated Conexant High Definition Audio chip. Both pcs with XP SP2/SP3.
The same here (Vista Ultimate, Java 6, RealTek HD Audio (onboard)).