Java Midi sequencer timing is off

huangapple go评论59阅读模式
英文:

Java Midi sequencer timing is off

问题

过去的几天里,我一直在尝试用Java编写一个节拍器作为练习。我使用了 javax.sound.midi 库来制作一个简单的4/4节拍,程序通过这个库播放节拍。我的主要问题是,Sequencer似乎会在第一个节拍上播放失调。如果我设置循环序列,这只会在第一次循环时发生。另外,如果我更改音轨的bpm,它会在第一次循环后重置。此外,我尝试了多个MIDI文件,以防我创建的MIDI文件有问题,但所有的测试结果都相同。

这是处理MIDI播放的代码部分:

public class MidiHandler 
{
    private Sequencer sequencer;
    private Sequence seq;
    private float newTempoFactor;
	
    public MidiHandler()
    {
        try 
        {
            sequencer = MidiSystem.getSequencer();
            if (sequencer == null)
            {
                System.err.println("Sequencer not supported");
            }
            sequencer.open();
        } 
        catch (MidiUnavailableException ex) 
        {
            Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void setAudioTrack(String filePath)
    {
        try 
        {
            seq = MidiSystem.getSequence(new File(filePath));
            sequencer.setSequence(seq);
        } 
        catch (InvalidMidiDataException | IOException ex) 
        {
            Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void playTrack(float bpm) throws InterruptedException
    {
        try 
        { 
            seq = editEvents(); // editEvents()方法将所有MIDI事件向前推进100个ticks
            sequencer.setSequence(seq);
        } 
        catch (InvalidMidiDataException ex) 
        {
            Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
        sequencer.start();
        // sequencer.setTempoInBPM(bpm);
        newTempoFactor = bpm / 120;
        sequencer.setTempoFactor(newTempoFactor); // 默认的节拍是120bpm --> 节拍因子 = 1

        sequencer.setLoopStartPoint(100); // 将循环的起始/结束点向前推进100个ticks
        sequencer.setLoopEndPoint(seq.getTickLength());
    }    

    public Sequence editEvents() 
    {
        Sequence seq = this.seq;
        try 
        {
            seq = MidiSystem.getSequence(new File("res//myTrack.mid"));
            for (Track track : seq.getTracks()) 
            {
                for (int i = 0; i < track.size(); i++) 
                { 
                    MidiEvent event = track.get(i);
                    event.setTick(event.getTick() + 100);  
                }
            }
        } 
        catch (InvalidMidiDataException | IOException ex) 
        {
            Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return seq;
    }
}

我的主类:

public class main 
{
    public static void main(String[] args) 
    {
        try 
        {
            MidiHandler mh = new MidiHandler();
            mh.setAudioTrack("res//myTrack.mid");
            mh.playTrack(120f);
        } 
        catch (SecurityException | InterruptedException ex) 
        {
            Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
英文:

For the past few days, I've been trying to code a metronome in Java for practice. I have made a simple 4/4 midi beat that the program plays with the use of javax.sound.midi library.<br/>
My main issue is that the sequencer appears to play the first beat off-time. If I set the sequence to loop, this occurs only the on the first loop. On a side note if I change the track's bpm, it resets after the first loop.<br/>Also, I have tried multiple midi files just in case there was an issue with the midi file that I created but all my tests had the same results.<br/>
Here is my code that handles the midi playback:<br/>

public class MidiHandler 
{
private Sequencer sequencer;
private Sequence seq;
private float newTempoFactor;
public MidiHandler()
{
try 
{
sequencer = MidiSystem.getSequencer();
if (sequencer == null)
{
System.err.println(&quot;Sequencer not supported&quot;);
}
sequencer.open();
} 
catch (MidiUnavailableException ex) 
{
Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void setAudioTrack(String filePath)
{
try 
{
seq= MidiSystem.getSequence(new File(filePath));
sequencer.setSequence(seq);
} 
catch (InvalidMidiDataException | IOException ex) 
{
Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void playTrack(float bpm) throws InterruptedException
{
try 
{ 
seq=editEvents();//editEvents() method pushes all midi events 100 ticks forward
sequencer.setSequence(seq);
} 
catch (InvalidMidiDataException ex) 
{
Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
}
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
sequencer.start();
//sequencer.setTempoInBPM(bpm);
newTempoFactor=bpm/120;
sequencer.setTempoFactor(newTempoFactor);//Default tempo is 120bpm --&gt; Tempo factor =1 
sequencer.setLoopStartPoint(100);//Shift the loop start/end by 100 ticks
sequencer.setLoopEndPoint(seq.getTickLength());
}    
public Sequence editEvents() 
{
Sequence seq= this.seq;
try 
{
seq = MidiSystem.getSequence(new File(&quot;res//myTrack.mid&quot;));
for (Track track :  seq.getTracks()) 
{
for (int i=0; i &lt; track.size(); i++) 
{ 
MidiEvent event = track.get(i);
event.setTick(event.getTick()+100);  
}
}
} 
catch (InvalidMidiDataException | IOException ex) 
{
Logger.getLogger(MidiHandler.class.getName()).log(Level.SEVERE, null, ex);
}
return seq;
}
}

My main class<br/>

public class main 
{
public static void main(String[] args) 
{
try 
{
MidiHandler mh = new MidiHandler();
mh.setAudioTrack(&quot;res//myTrack.mid&quot;);
mh.playTrack(120f);
} 
catch (SecurityException | InterruptedException ex) 
{
Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

答案1

得分: 1

你的代码看起来没问题。

最初的节拍不准确通常是由连接的外部 Midi 设备引起的(在我的情况下,当我使用外部 USB Midi 声卡时出现这种情况)。如果可以的话,尝试使用不同的 Midi 设备。

如果不能更换设备,一个解决方法是将创建的“Sequence”的所有“MidiEvents”整体后移,比如说后移 4 拍,然后使用“Sequencer.setLoopEndPoint(long tick)”和“Sequencer.setLoopStartPoint(long tick)”来使循环从新的起始点开始。

关于开始后的节奏变化,这是一个 JDK 的 bug。解决方法是在“Sequencer.start()”后立即调用“Sequencer.setTempoInBPM()”。

英文:

Your code looks OK.

The initial beat which is off-timing is usually due to the connected out Midi device (in my case when I use my external USB Midi soundcard). Try with a different MidiDevice if you can.

If you can't, the workaround is to shift all the MidiEvents of the created Sequence by say 4 beats, then use Sequencer.setLoopEndPoint(long tick) and Sequencer.setLoopStartPoint(long tick) to make loop start at the new starting point.

For the tempo change after start, it's a JDK bug. The workaround is to call Sequencer.setTempoInBPM() right after Sequencer.start().

huangapple
  • 本文由 发表于 2020年8月31日 20:29:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/63670860.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定