// Midi show control program in Java // By Eric // http://www.awesomelicious.com/ import java.io.*; import java.util.*; import javax.sound.midi.*; import java.util.concurrent.LinkedBlockingQueue; import java.net.*; public class MidiPlayback { protected static Sequencer sequencer; protected static Synthesizer synthesizer; protected static boolean stillRunning=true; public static void main(String[] args) throws InvalidMidiDataException,IOException,MidiUnavailableException { // If you do not have a Siteplayer ethernet controller, write your own class implementing LightController specific to your needs. // Also available here is FakeLights- see below // LightController controller=new FakeLights(); // Create a fake light (prints events to terminal) LightController controller=new EthernetLights(); // Create a light controller controller.initialize(); // and initialize it for(int i=10;i>0;i--) // Countdown { System.out.println(i); try { Thread.sleep(1000); } catch(InterruptedException e) { } } Sequence sequence = MidiSystem.getSequence(new File("song.mid")); // Load up song sequencer = MidiSystem.getSequencer(); sequencer.addMetaEventListener(new MetaEventListener() // Java midi quit bug { public void meta(MetaMessage event) { if (event.getType() == 47) { sequencer.close(); synthesizer.close(); System.out.println("Finished."); // controller.close(); System.exit(0); } } }); sequencer.open(); sequencer.setSequence(sequence); synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); sequencer.getTransmitter().setReceiver(new NoteEventReceiver(13,controller,200)); // Set the midi reciever to a custom object (NoteEventReciever) sequencer.start(); // Start playing } } // This class is a custom midi receiver to trap note events class NoteEventReceiver implements Receiver { final static int offset=60; // Offset from zero- 60 makes middle C = 0 protected int chan; // Channel with light events protected int lag; protected LinkedBlockingQueue queue; // Queue for light events // (Blocking queue is thread-safe) protected LightController controller; // The LightController to send the events to protected Thread doEventsThread; // Thread for actually sending the events public NoteEventReceiver(int chan,LightController controller,int lag) // Constructor { // Initialize this.chan=chan; this.controller=controller; this.lag=lag; queue=new LinkedBlockingQueue(); // Start the queue monitor that sends the events doEventsThread=new MonitorQueueThread(this); doEventsThread.start(); } // Called by the sequencer every time a note occurs public void send(MidiMessage message,long timeStamp) { byte[] data=message.getMessage(); if(data[0]==(byte)(127+chan)) // Note off event on channel chan new LagToQueue(this,lag,new LightEvent(data[1]-offset,false)).start(); // Queue up "off" event (data[0] represents pitch, subtracting offset zero-indexes) else if(data[0]==(byte)(143+chan)) // Note on new LagToQueue(this,lag,new LightEvent(data[1]-offset,true)).start(); // Queue up "on" event } public void close() // Deconstruct { doEventsThread.interrupt(); // Halt the thread while(doEventsThread.isAlive()); // Wait for haltation } // Getters public LinkedBlockingQueue getQueue() { return queue; } public LightController getController() { return controller; } } // Thread for monitoring the event queue and sending the events // It is necessary to do this in a separate thread so the time taken to send an event doesn't interfere with the music being played class MonitorQueueThread extends Thread { protected NoteEventReceiver caller; // Calling object, to get parameters from protected LightController controller; // Controller to send events to public MonitorQueueThread(NoteEventReceiver caller) { super(); this.caller=caller; this.controller=caller.getController(); } public void run() { LinkedBlockingQueue queue; queue=caller.getQueue(); // Get queue for(;;) { try { controller.doEvent((LightEvent)queue.take()); // Wait for the event and send it } catch(InterruptedException e) { return; } } } } // Simple class for storing a single light event class LightEvent { protected int number; // Light # protected boolean on; // true=on, false=off public LightEvent(int number,boolean on) { this.number=number; this.on=on; } public int getNumber() { return number; } public boolean getOn() { return on; } public String toString() { return "Light "+number+(on?" on":" off"); } } // This interface makes it easy to create alternate controller schemes interface LightController { void initialize(); // Set up connection if necessary void doEvent(LightEvent what); // Send a light event void close(); // Close connection gracefully } // This prints LightEvents it gets sent to the terminal- it doesn't actually control lights class FakeLights implements LightController { public void initialize() { System.out.println("Fake lights initialized."); } public void doEvent(LightEvent what) { System.out.println(what); } public void close() { System.out.println("Fake lights closed."); } } // This sends light signals to a SitePlayer through the UDP protocol class EthernetLights implements LightController { private int port=26482; // Default port private InetAddress ip; private byte[] buffer=new byte[] { 0x01,(byte)0xFE,0x00,(byte)0xFF,0x00, // Write 0 to FF00 0x01,(byte)0xFE,0x00,(byte)0xFF,0x00, // Write X to FF00 (stored as zero for now, will be modified in code) 0x00,0x00 // Terminate packet }; private DatagramSocket sock; private DatagramPacket packet; public EthernetLights() throws UnknownHostException { ip=InetAddress.getByName("192.168.1.250"); // Default IP } public EthernetLights(InetAddress ip,int port) { this.ip=ip; this.port=port; } public void initialize() // Creates a UDP connection, or not... { try { sock=new DatagramSocket(port); } catch(SocketException e) { System.out.println("Error creating socket: "+e.getMessage()); } packet=new DatagramPacket(buffer,buffer.length,ip,port); } public void doEvent(LightEvent what) // Sends the event { buffer[9]=(byte)(what.getNumber()*2+(what.getOn()?3:2)); // Remember that X up there? This changes it // Adds 2/3 because Light 0 is nonexistant, so sending a 0 or 1 would do nothing try { sock.send(packet); } catch(IOException e) { System.out.println("Error sending packet: "+e.getMessage()); } } public void close() { sock.close(); } } // This class delays a bit before adding an event to the queue enabling exact synchronization between lights and music class LagToQueue extends Thread { NoteEventReceiver caller; int lagTime; LightEvent add; public LagToQueue(NoteEventReceiver caller,int lagTime,LightEvent add) { super(); this.caller=caller; this.lagTime=lagTime; this.add=add; } public void run() { try { Thread.sleep(lagTime); // lag } catch(InterruptedException e) { } caller.getQueue().offer(add); // add it } }