我最近遇到了一个奇怪的错误,在我正在开发的游戏中玩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();
}
}
}
答案 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();
}
}
}