Java midi播放质量不一致

时间:2012-05-04 02:15:07

标签: java midi javasound

我最近遇到了一个奇怪的错误,在我正在开发的游戏中玩midis。我以为我的midi代码工作正常,因为它曾经播放midis而听起来并不奇怪。现在每当它播放midis时,它们听起来都很小,回声和响亮。

我很长时间没有触及我的midi播放器代码,所以我想知道最近的Java更新是否有可能暴露出我的代码中一直存在的错误。或许我的Java版本中有一些我不知道的midi bug?

每当我在游戏之外播放时,midis听起来都很好。

我正在运行Java 6,更新31,构建1.6.0_31-b05。这是一个重现问题的SSCCE(它至少在我的JVM上重现它):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.sound.midi.*;
import java.net.URL;

public class MidiSSCCE extends JFrame
{

    public MidiSSCCE()
    {
        super("Sound problem SSCCE");
        this.setSize(200,100);

        // instantiate main window panel

        JPanel screenP = new SSCCEPanel(this);
        this.add(screenP);

        // finishing touches on Game window

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        System.out.println("Game Window successfully created!!!");
    }

    public static void main(String[] args)
    {
        MidiSSCCE gui = new MidiSSCCE();        
    }
}


/**
*   SSCCEPanel is the JPanel that manages the example's timer, painting, and logic. 
**/

class SSCCEPanel extends JPanel
{
    public Frame parentFrame;
    private Timer timer;
    public int logicLoops;
    public double prevFPS;
    boolean timerReady;

    // The MidiPlayer object is used by the example to play the midi.

    public MidiPlayer midiPlayer;

    public SSCCEPanel(Frame parent)
    {
        super(true);
        parentFrame = parent;
        this.setFocusable(true);

        Toolkit.getDefaultToolkit().sync();
        logicLoops = 0;

        midiPlayer = new MidiPlayer();

        TimerListener timerListener = new TimerListener();
        prevFPS = 0;
        timerReady = true;
        timer = new Timer(0,timerListener);
        this.setFPS(60);
        timer.start();
    }

    /** 
    *   setFPS()
    *   Preconditions: fps is a quantity of frames per second
    *   Postconditions: Sets the timer's refresh rate so that it 
    *       fires fps times per second.
    **/

    public void setFPS(int fps)
    {
        int mspf = (int) (1000.0 /fps + 0.5);
        timer.setDelay(mspf);
    }


    /**
    *   This is the JPanel's timer listener. It runs the example's logic and repaint
    *   methods each time it gets a timer signal.
    **/

    private class TimerListener implements ActionListener
    {
        long startTime = System.currentTimeMillis();
        long lastTime = this.startTime;
        int ticks = 0;

        public void actionPerformed(ActionEvent e)
        {
            Object source = e.getSource();
            if(source == timer)
            {
                // perform a loop through the game's logic and repaint.

                synchronized(this)
                {
                    if(timerReady)
                    {
                        timerReady = false;
                        runSSCCELogic();
                        repaint();
                        timerReady = true;
                    }
                }

                // Logic for Frames per Second counter

                this.ticks++;

                long currentTime = System.currentTimeMillis();

                if(currentTime - startTime >= 500) 
                {
                    prevFPS =  1000.0 * ticks/(1.0*currentTime - startTime);
                    System.out.println(prevFPS);
                    startTime = currentTime;
                    ticks = 0;
                }

                lastTime = currentTime;
            }
        }
    }


    /**
    *   repaints the SSCCE.
    *   This just shows the current FPS.
    **/

    public void paintComponent(Graphics g)
    {
            super.paintComponent(g);

            Graphics2D g2D = (Graphics2D) g;
            double roundedFPS = Math.round(prevFPS*10)/10.0;

            g2D.setColor(new Color(0x000000));
            g2D.drawString("FPS: " + roundedFPS, 20,20);
            g.dispose();
    }

    /**
    *   runSSCCEELogic()
    *   This is where the run-time logic for the SSCCE example is. 
    *   All it does is load and play a midi called "mymidi.mid" which is located in the same directory.
    **/

    public void runSSCCELogic()
    {
        if(logicLoops == 1)
        {
            midiPlayer.load("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");
            midiPlayer.play(true);
        }

        logicLoops++;
    }
}



/**
*   MidiPlayer
*   A class that allows midi files to be loaded and played. 
**/

class MidiPlayer
{
    private Sequence seq;
    private Sequencer seqr;
    private Synthesizer synth;
    private Receiver receiver;
    private File midiFile;
    private String midiID;
    private boolean loaded;
    private boolean usingHardwareSoundbank;

    // CONSTRUCTORS

    public MidiPlayer()
    {
        loaded = false;
        try
        {
            seqr = MidiSystem.getSequencer();
            synth = MidiSystem.getSynthesizer();
        }
        catch(Exception e)
        {
            System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
        }
    }

    /**
    *   MidiPlayer(String fileName)
    *   Constructor that also loads an initial midi file.
    *   Preconditions: fileName is the name of the midi file to be loaded. 
    *   Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
    **/

    public MidiPlayer(String fileName)
    {
        this();
        load(fileName);
    }


    // DATA METHODS

    /**
    *   load(String fileName)
    *   loads a midi file into this MidiPlayer.
    *   Preconditions: fileName is the name of the midi file to be loaded.
    *   Postconditions: fileName is loaded and is ready to be played.
    **/

    public void load(String fileName)
    {
        this.unload();
        try
        {
            URL midiURL =  new URL(fileName);
        //  midiFile = new File(fileName);
            seq = MidiSystem.getSequence(midiURL);

            seqr.open();
            synth.open();

            System.out.println("MidiDeviceInfo: ");
            for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
            {
                System.out.println("\t" + info);
            }
            System.out.println();

            if(synth.getDefaultSoundbank() == null)
            {
                receiver = MidiSystem.getReceiver();
                usingHardwareSoundbank = true;
                System.out.println("using hardware soundbank");
            }
            else
            {
                receiver = synth.getReceiver();
                usingHardwareSoundbank = false;
                System.out.println("using default software soundbank:" + synth.getDefaultSoundbank());
            }
            seqr.getTransmitter().setReceiver(receiver);

            seqr.setSequence(seq);
            loaded = true;
        }
        catch(IOException ioe)
        {
            System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
        }
        catch(InvalidMidiDataException imde)
        {
            System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
        }
        catch(Exception e)
        {
            System.out.println("MIDI error: Unexplained error occured while loading midi.");
        }
    }

    /**
    *   unload()
    *   Unloads the current midi from the MidiPlayer and releases its resources from memory.
    **/

    public void unload()
    {
        this.stop();
        seqr.close();
        midiFile = null;
        loaded = false;
    }

    // OTHER METHODS

    /**
    *   setMidiID(String id)
    *   associates a String ID with the current midi.
    *   Preconditions: id is the ID we are associating with the current midi.
    **/

    public void setMidiID(String id)
    {
        midiID = id;
    }

    /**
    *   getMidiID(String id)
    *
    **/

    public String getMidiID()
    {
        return new String(midiID);
    }

    /**
    *   play(boolean reset)
    *   plays the currently loaded midi.
    *   Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
    *   Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0). 
    *       If reset is false, then the loaded midi resumes playing from its current position.
    **/

    public void play(boolean reset)
    {
        if(reset)
            seqr.setTickPosition(seqr.getLoopStartPoint());
        seqr.start();
    }

    /**
    *   stop()
    *   Pauses the current midi if it was playing.
    **/

    public void stop()
    {
        if(seqr.isOpen())
            seqr.stop();
    }

    /**
    *   isRunning()
    *   Returns true if the current midi is playing. Returns false otherwise.
    **/

    public boolean isRunning()
    {
        return seqr.isRunning();
    }


    /**
    *   loop(int times)
    *   Sets the current midi to loop from start to finish a specific number of times.
    *   Preconditions: times is the number of times we want our midi to loop.
    *   Postconditions: The current midi is set to loop times times. 
    *       If times = -1, the current midi will be set to loop infinitely.
    **/

    public void loop(int times)
    {
        loop(times,0,-1);
    }

    /**
    *   loop(int times)
    *   Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
    *   Preconditions: times is the number of times we want our midi to loop.
    *       start is our loop's start point in ticks.
    *       end is our loop's end point in ticks.
    *   Postconditions: The current midi is set to loop from tick start to tick end times times. 
    *       If times = -1, the current midi will be set to loop infinitely.
    **/

    public void loop(int times, long start, long end)
    {
        if(start < 0)
            start = 0;
        if(end > seqr.getSequence().getTickLength() || end <= 0)
            end = seqr.getSequence().getTickLength();

        if(start >= end && end != -1)
            start = end-1;

        seqr.setLoopStartPoint(start);
        seqr.setLoopEndPoint(end);

        if(times == -1)
            seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
        else
            seqr.setLoopCount(times);

    }


    public void setVolume(double vol)
    {
        try 
        {
            if(usingHardwareSoundbank)
            {
                ShortMessage volumeMessage = new ShortMessage();
                for ( int i = 0; i < 16; i++ ) 
                {
                    volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
                    receiver.send( volumeMessage, -1 );
                }
            }
            else
            {
                MidiChannel[] channels = synth.getChannels();
                for( int c = 0; channels != null && c < channels.length; c++ )
                {
                  channels[c].controlChange( 7, (int)( vol*127) );
                }
            }
        } 
        catch ( Exception e ) 
        {
            e.printStackTrace();
        }
    }

}

3 个答案:

答案 0 :(得分:2)

MIDI的音质取决于产生声音的合成器。它与您的代码无关。

最有可能的是,这是声卡的一个问题,但这并不总是会产生声音,尤其是现在。在Windows下,有一个来自Microsoft的软件合成器可以完成所有这些工作。在任何情况下,这都与您的代码没有任何关系。

答案 1 :(得分:0)

事实证明问题在于我的JRE构建。我尝试运行这个简单的例子来播放安德鲁把我联系到的midis:

import javax.sound.midi.*;
import java.net.URL;

class PlayMidi {

     public static void main(String[] args) throws Exception {
          URL url = new URL("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");

          Sequence sequence = MidiSystem.getSequence(url);
          Sequencer sequencer = MidiSystem.getSequencer();

          sequencer.open();
          sequencer.setSequence(sequence);

          sequencer.start();
     }
}

并且midi的音质仍然没有提高。我使用Java 6_31和Java 6_32运行了我的SSCCE和上面的最小示例。

总之,这是Java 6_31和Java 6_32中固有的问题。所以,我想我运气不好,直到Sun / Oracle发布下一个JRE版本,希望能解决这个问题。

编辑:

我刚刚安装了一台安装了Java 6_31的朋友在他的机器上测试了这个。当他运行我的java示例而不是在Java之外播放midi时,他没有注意到声音质量的任何差异。因此,问题也可能与我的机器有关,而不是它是Java中的错误。然而,另一位朋友刚刚对它进行了测试,并且遇到了同样的问题。

总之,问题要么是过去6_31的Java版本中固有的,要么是某些机器的声音设备,或两者兼而有之。在本机java中,这个问题可能不值得追求。

答案 2 :(得分:0)

我对我的MidiPlayer代码进行了更改,似乎已经解决了我的问题。我将代码移动到将构造函数加载到构造函数的位置,而不是加载midi的方法。它听起来不再响亮而且很小。

  package gameEngine;

  import javax.sound.midi.*;
  import java.io.File;
  import java.io.IOException;
  import java.net.URL;

  /**
  * MidiPlayer
  * author: Stephen Lindberg
  * Last modified: Oct 14, 2011
  * 
  * A class that allows midi files to be loaded and played.
  **/

  public class MidiPlayer
  {
     private Sequence seq;
     private Sequencer seqr;
     private Synthesizer synth;
     private Receiver receiver;
     private File midiFile;
     private String midiID;
     private boolean loaded;
     private boolean usingHardwareSoundbank;
     private float defaultTempo;

     // CONSTRUCTORS

     public MidiPlayer()
     {
        loaded = false;
        try
        {
           seqr = MidiSystem.getSequencer();
           synth = MidiSystem.getSynthesizer();

           // print the user's midi device info
           System.out.println("Setting up Midi Player...");
           System.out.println("MidiDeviceInfo: ");
           for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
           {
              System.out.println("\t" + info.getName() + ": " +info.getDescription());
           }
           System.out.println();

           // obtain the receiver. This will be used for changing volume.

           Soundbank soundbank = synth.getDefaultSoundbank();
           if(soundbank == null)
           {
              receiver = MidiSystem.getReceiver();
              usingHardwareSoundbank = true;
              System.out.println("using hardware soundbank");
           }
           else
           {
              synth.loadAllInstruments(soundbank);
              receiver = synth.getReceiver();
              usingHardwareSoundbank = false;
              System.out.println("using default software soundbank:" + soundbank);
           }
           seqr.getTransmitter().setReceiver(receiver);

        }
        catch(Exception e)
        {
           System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
        }
     }

     /**
     *  MidiPlayer(String fileName)
     *  Constructor that also loads an initial midi file.
     *  Preconditions: fileName is the name of the midi file to be loaded. 
     *  Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
     **/

     public MidiPlayer(String fileName)
     {
        this();
        load(fileName);
     }


     // DATA METHODS

     /**
     *  load(String fileName)
     *  loads a midi file into this MidiPlayer.
     *  Preconditions: fileName is the name of the midi file to be loaded.
     *  Postconditions: fileName is loaded and is ready to be played.
     **/

     public void load(String fileName)
     {
        this.unload();
        try
        {
           URL midiURL =  getClass().getClassLoader().getResource(fileName);
           seq = MidiSystem.getSequence(midiURL);

           seqr.open();
           synth.open();

           // load our sequence into the sequencer.

           seqr.setSequence(seq);
           loaded = true;
           defaultTempo = seqr.getTempoInBPM();
        }
        catch(IOException ioe)
        {
           System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
        }
        catch(InvalidMidiDataException imde)
        {
           System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
        }
        catch(Exception e)
        {
           System.out.println("MIDI error: Unexplained error occured while loading midi.");
        }
     }

     /**
     *  unload()
     *  Unloads the current midi from the MidiPlayer and releases its resources from memory.
     **/

     public void unload()
     {
        this.stop();
        seqr.close();
        synth.close();
        midiFile = null;
        loaded = false;
     }

     // OTHER METHODS

     /**
     *  setMidiID(String id)
     *  associates a String ID with the current midi.
     *  Preconditions: id is the ID we are associating with the current midi.
     **/

     public void setMidiID(String id)
     {
        midiID = id;
     }

     /**
     *  getMidiID(String id)
     *
     **/

     public String getMidiID()
     {
        return new String(midiID);
     }

     /**
     *  play(boolean reset)
     *  plays the currently loaded midi.
     *  Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
     *  Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0). 
     *      If reset is false, then the loaded midi resumes playing from its current position.
     **/

     public void play(boolean reset)
     {
        if(reset)
           seqr.setTickPosition(seqr.getLoopStartPoint());
        seqr.start();
     }

     /**
     *  stop()
     *  Pauses the current midi if it was playing.
     **/

     public void stop()
     {
        if(seqr.isOpen())
           seqr.stop();
     }

     /**
     *  isRunning()
     *  Returns true if the current midi is playing. Returns false otherwise.
     **/

     public boolean isRunning()
     {
        return seqr.isRunning();
     }



     /**
     *  getTempo()
     *  Returns the current tempo of the MidiPlayer in BPM (Beats per Minute).
     **/

     public float getTempo()
     {
        return seqr.getTempoInBPM();
     }

     /**
     *  loop(int times)
     *  Sets the current midi to loop from start to finish a specific number of times.
     *  Preconditions: times is the number of times we want our midi to loop.
     *  Postconditions: The current midi is set to loop times times. 
     *      If times = -1, the current midi will be set to loop infinitely.
     **/

     public void loop(int times)
     {
        loop(times,0,-1);
     }

     /**
     *  loop(int times)
     *  Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
     *  Preconditions: times is the number of times we want our midi to loop.
     *      start is our loop's start point in ticks.
     *      end is our loop's end point in ticks.
     *  Postconditions: The current midi is set to loop from tick start to tick end times times. 
     *      If times = -1, the current midi will be set to loop infinitely.
     **/

     public void loop(int times, long start, long end)
     {
        if(start < 0)
           start = 0;
        if(end > seqr.getSequence().getTickLength() || end <= 0)
           end = seqr.getSequence().getTickLength();

        if(start >= end && end != -1)
           start = end-1;

        seqr.setLoopStartPoint(start);
        seqr.setLoopEndPoint(end);

        if(times == -1)
           seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
        else
           seqr.setLoopCount(times);

     }

     /**
     *  resetTempo()
     *  Resets the MidiPlayer's tempo the the initial tempo of its current midi.
     **/

     public void resetTempo()
     {
        this.changeTempo(this.defaultTempo);
     }

     /**
     *  changeTempo(float bpm)
     *  Changes the MidiPlayer's current tempo.
     *  Preconditions: bpm is the MidiPlayer's new tempo in BPM (Beats per Minute).
     *  Postconditions: The MidiPlayer's current tempo is set to bpm BPM.
     **/

     public void changeTempo(float bpm)
     {
        double lengthCoeff = bpm/seqr.getTempoInBPM();

        seqr.setLoopStartPoint((long) (seqr.getLoopStartPoint()*lengthCoeff));
        seqr.setLoopEndPoint((long) (seqr.getLoopEndPoint()*lengthCoeff));

        seqr.setTempoInBPM(bpm);
     }


     public void setVolume(double vol)
     {
        System.out.println("Midi volume change request: " + vol);

        try 
        {
           if(usingHardwareSoundbank)
           {
              ShortMessage volumeMessage = new ShortMessage();
              for ( int i = 0; i < 16; i++ ) 
              {
                 volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
                 receiver.send( volumeMessage, -1 );
              }
           }
           else
           {
              MidiChannel[] channels = synth.getChannels();

              for( int c = 0; c < channels.length; c++ )
              {
                 if(channels[c] != null) {
                    channels[c].controlChange( 7, (int)( vol*127) );
                 }
              }
           }
        } 
        catch ( Exception e ) 
        {
           e.printStackTrace();
        }
     }

  }