La synchronisation du séquenceur Java Midi est désactivée

Aug 31 2020

Depuis quelques jours, j'essaye de coder un métronome en Java pour m'entraîner. J'ai fait un simple rythme midi 4/4 que le programme joue avec l'utilisation de la bibliothèque javax.sound.midi .
Mon problème principal est que le séquenceur semble jouer le premier temps hors temps. Si je mets la séquence en boucle, cela se produit uniquement sur la première boucle. En passant, si je change le bpm de la piste, il se réinitialise après la première boucle.
De plus, j'ai essayé plusieurs fichiers midi juste au cas où il y aurait un problème avec le fichier midi que j'ai créé mais tous mes tests ont eu les mêmes résultats.
Voici mon code qui gère la lecture 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() 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 --> 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("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;
    }

}

Ma classe principale

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

Réponses

1 jjazzboss Sep 01 2020 at 00:59

Votre code semble correct.

Le battement initial qui est décalé est généralement dû au périphérique Midi connecté (dans mon cas, lorsque j'utilise ma carte son MIDI USB externe). Essayez avec un autre MidiDevice si vous le pouvez.

Si vous ne pouvez pas, la solution de contournement consiste à décaler tous les temps MidiEventscréés Sequencepar disons 4, puis à utiliser Sequencer.setLoopEndPoint(long tick)et Sequencer.setLoopStartPoint(long tick)pour faire démarrer la boucle au nouveau point de départ.

Pour le changement de tempo après le démarrage, c'est un bogue JDK. La solution de contournement consiste à appeler Sequencer.setTempoInBPM()juste après Sequencer.start().