最近、開発中のゲームで 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();
}
}
}