Coverage Report - net.cscott.sdr.sound.MidiThread
 
Classes in this File Line Coverage Branch Coverage Complexity
MidiThread
0%
0/40
0%
0/14
3
MidiThread$1
0%
0/6
0%
0/4
3
 
 1  
 package net.cscott.sdr.sound;
 2  
 /*
 3  
  *        Derived from LoopingMidiPlayer15.java, from jsresources.org
 4  
  *      Requires JDK1.5 or later.
 5  
  */
 6  
 
 7  
 /* for our test files, tempo is 120bpm, and resolution is 384 PPQ.  This
 8  
  * means that a midi tick is 1/768s, which is more than adequate resolution
 9  
  * for (say) 15-30fps animation. */
 10  
 
 11  
 import java.io.IOException;
 12  
 import java.util.concurrent.BlockingQueue;
 13  
 
 14  
 import javax.sound.midi.InvalidMidiDataException;
 15  
 import javax.sound.midi.MetaEventListener;
 16  
 import javax.sound.midi.MetaMessage;
 17  
 import javax.sound.midi.MidiSystem;
 18  
 import javax.sound.midi.MidiUnavailableException;
 19  
 import javax.sound.midi.Receiver;
 20  
 import javax.sound.midi.Sequence;
 21  
 import javax.sound.midi.Sequencer;
 22  
 import javax.sound.midi.ShortMessage;
 23  
 import javax.sound.midi.Soundbank;
 24  
 import javax.sound.midi.Synthesizer;
 25  
 import javax.sound.midi.Transmitter;
 26  
 
 27  
 import net.cscott.sdr.BeatTimer;
 28  
 import net.cscott.sdr.anim.SilentBeatTimer;
 29  
 
 30  0
 public class MidiThread extends Thread
 31  
 {
 32  
     private final BlockingQueue<BeatTimer> rendezvousBT;
 33  0
     public MidiThread(BlockingQueue<BeatTimer> rendezvousBT) {
 34  0
         this.rendezvousBT = rendezvousBT;
 35  0
     }
 36  
     @Override
 37  
     public void run() {
 38  
         BeatTimer beatTimer;
 39  
         try {
 40  0
             beatTimer = playMidi();
 41  0
         } catch (Exception e) {
 42  
             // no music. =(
 43  0
             beatTimer = new SilentBeatTimer();
 44  0
         }
 45  
         // send the beat timer to the rest of the system.
 46  0
         rendezvousBT.add(beatTimer);
 47  0
     }
 48  
     public BeatTimer playMidi() 
 49  
     throws MidiUnavailableException, InvalidMidiDataException, IOException
 50  
     {
 51  
         final Sequencer sequencer;
 52  
         final Synthesizer synthesizer;
 53  
         
 54  
         /* Use high-quality soundbank. */
 55  0
         Soundbank soundbank = MidiSystem.getSoundbank
 56  
         (MidiThread.class.getClassLoader().getResource
 57  
                 ("net/cscott/sdr/sound/soundbank-deluxe.gm"));
 58  
         //soundbank=null;
 59  
         
 60  
         /* We read in the MIDI file to a Sequence object.  This object
 61  
          * is set at the Sequencer later.
 62  
          */
 63  0
         Sequence sequence = MidiSystem.getSequence
 64  
         (MidiThread.class.getClassLoader().getResource
 65  
                 ("net/cscott/sdr/sound/saturday-night.midi"));
 66  
         
 67  
         // print out some info about timing resolution.
 68  0
         System.out.println("Division type: "+sequence.getDivisionType());
 69  0
         System.out.println("Resolution: "+sequence.getResolution());
 70  
         assert sequence.getDivisionType()==Sequence.PPQ :
 71  0
             "don't know how to sync non-PPQ tracks";
 72  0
         @SuppressWarnings("unused") int ticksPerBeat = sequence.getResolution();
 73  
         
 74  
         /* Now, we need a Sequencer to play the sequence.  Here, we
 75  
          * simply request the default sequencer without an implicitly
 76  
          * connected synthesizer
 77  
          */
 78  0
         sequencer = MidiSystem.getSequencer(false);
 79  
         
 80  
         /* The Sequencer is still a dead object.  We have to open() it
 81  
          * to become live.  This is necessary to allocate some
 82  
          * ressources in the native part.
 83  
          */
 84  0
         sequencer.open();
 85  
         
 86  
         /* Next step is to tell the Sequencer which Sequence it has to
 87  
          * play. In this case, we set it as the Sequence object
 88  
          * created above.
 89  
          */
 90  0
         sequencer.setSequence(sequence);
 91  
         // XXX: at this point, it would be safe to create/return the BeatTimer
 92  
 
 93  
         // Mississippi Sawyer is *half note*=120; Java doesn't seem to grok
 94  
         // this.  Something like the following is needed for these cases:
 95  
         // sequencer.setTempoFactor(2f);
 96  
         
 97  
         /* We try to get the default synthesizer, open() it and chain
 98  
          * it to the sequencer with a Transmitter-Receiver pair.
 99  
          */
 100  0
         synthesizer = MidiSystem.getSynthesizer();
 101  0
         synthesizer.open();
 102  0
         boolean loaded = false;
 103  0
         if (soundbank!=null)
 104  0
             loaded = synthesizer.loadAllInstruments(soundbank);
 105  0
         System.out.println("Instruments "+(loaded?"":"not ")+"loaded.");
 106  0
         Receiver        synthReceiver = synthesizer.getReceiver();
 107  0
         Transmitter        seqTransmitter = sequencer.getTransmitter();
 108  0
         seqTransmitter.setReceiver(synthReceiver);
 109  
         
 110  
         /* To free system resources, it is recommended to close the
 111  
          * synthesizer and sequencer properly.
 112  
          *
 113  
          * To accomplish this, we register a Listener to the
 114  
          * Sequencer. It is called when there are "meta" events. Meta
 115  
          * event 47 is end of track.
 116  
          *
 117  
          * Thanks to Espen Riskedal for finding this trick.
 118  
          */
 119  0
         sequencer.addMetaEventListener(new MetaEventListener()
 120  0
                 {
 121  
             public void meta(MetaMessage event)
 122  
             {
 123  0
                 if (event.getType() == 47)
 124  
                 {
 125  0
                     sequencer.close();
 126  0
                     if (synthesizer != null)
 127  
                     {
 128  0
                         synthesizer.close();
 129  
                     }
 130  
                     //System.exit(0);
 131  
                 }
 132  0
             }
 133  
                 });
 134  
         
 135  
         /* Here, we set the loop points to loop over the whole
 136  
          * sequence. Setting the loop end point to -1 means using the
 137  
          * last tick of the sequence as end point of the loop.
 138  
          *
 139  
          * Furthermore, we set the number of loops to loop infinitely.
 140  
          */
 141  0
         sequencer.setLoopStartPoint(0);
 142  0
         sequencer.setLoopEndPoint(-1);
 143  0
         sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
 144  
         
 145  
         /* WORKAROUND for BUG in jdk 1.6 (at least): loop control has an
 146  
          * off-by-one error and doesn't play the (usually note off) messages
 147  
          * in the last tick of the song; it just jumps to tick 0 and plays
 148  
          * the note-on messages there.  Workaround this by manually kludging
 149  
          * in ALL_NOTE_OFF messages on all channels one tick before the end
 150  
          * of the song.  Sigh. */
 151  0
         for (int i=0; i<16; i++) {
 152  0
             javax.sound.midi.ShortMessage notesOff =
 153  
                 new javax.sound.midi.ShortMessage();
 154  0
             notesOff.setMessage(ShortMessage.CONTROL_CHANGE+i,123,0);
 155  0
             for (javax.sound.midi.Track t : sequence.getTracks()) {
 156  0
                 long end = (t.ticks()<=0)?0L:(t.ticks()-1);
 157  0
                 t.add(new javax.sound.midi.MidiEvent(notesOff, end));
 158  
             }
 159  
         }
 160  
         
 161  
         /* Ok, play the music!
 162  
          */
 163  0
         sequencer.start();
 164  
         
 165  0
         return new MidiTimer(sequencer);
 166  
     }
 167  
 }