0

最近、開発中のゲームで midis をプレイしているときに奇妙なバグに遭遇しました。私の midi コードは正常に動作していると思っていました。今では、ミディスを再生するときはいつでも、すべて小さく、エコーのように、大きく聞こえます。

私は長い間 MIDI プレーヤーのコードに触れていなかったので、最近の Java の更新により、ずっと私のコードにあったバグが明らかになったのではないかと考えています。それとも、私のバージョンの Java に、私が気付いていない何らかの MIDI バグがあるのでしょうか?

ゲーム以外で再生するときはいつでも、ミディスは問題なく聞こえます。

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();
        }
    }

}
4

3 に答える 3

2

MIDIの音質は、サウンドを生成するシンセに依存します。それはあなたのコードとは何の関係もないはずです。

ほとんどの場合、これはサウンドカードの問題ですが、特に最近では、それがサウンドを生成する原因であるとは限りません。Windowsの下には、それをすべて行うMicrosoftのソフトウェアシンセサイザーがあります。いずれにせよ、これはあなたのコードとは何の関係もありません。

于 2012-05-07T03:00:08.853 に答える
0

問題は私の JRE ビルドにあることがわかりました。アンドリューが私にリンクしてくれたミディを演奏するためのこの簡単な例を実行してみました:

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 でこれ以上追求する価値はありません。

于 2012-05-06T17:26:55.080 に答える
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();
        }
     }

  }
于 2012-06-09T22:32:58.267 に答える