package com.netthreads.network.osc.router.service;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.ShortMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netthreads.network.osc.router.AppInjector;
import com.netthreads.network.osc.router.model.OSCItem;
import com.netthreads.network.osc.router.model.OSCValue;
/**
* MIDI Router runs in it's own thread and send messages as fast as it can.
*
*/
public class MIDIRouter implements Router, Runnable
{
private Logger logger = LoggerFactory.getLogger(MIDIRouter.class);
private final MIDIDeviceCache midiDeviceCache;
private MIDIMessageLookup messageLookup;
private final BlockingQueue<OSCItem> queue;
private ExecutorService pool;
private boolean active;
/**
* MIDI Router Service.
*
*/
public MIDIRouter()
{
queue = new ArrayBlockingQueue<OSCItem>(1024);
// Device cache.
midiDeviceCache = AppInjector.getInjector().getInstance(MIDIDeviceCache.class);
// MIDI Message table.
messageLookup = AppInjector.getInjector().getInstance(MIDIMessageLookup.class);
active = false;
pool = null;
}
/**
* Activate service.
*
*/
@Override
public void start()
{
pool = Executors.newFixedThreadPool(1);
pool.execute(this);
}
/**
* Kill the service.
*
* We override it as we might want to do some sort of cleanup.
*
*/
@Override
public void stop()
{
active = false;
if (pool != null)
{
pool.shutdownNow();
}
}
/**
* Add message to queue.
*
* @param shortMessage
*/
@Override
public synchronized boolean route(OSCItem oscItem)
{
queue.add(oscItem);
return true;
}
/**
* Create service task.
*
*/
@Override
public void run()
{
active = true;
try
{
while (active)
{
// Listen for notes
OSCItem oscItem = queue.take();
routeItem(oscItem);
}
}
catch (InterruptedException interrupted)
{
logger.debug("Stopped");
}
catch (Throwable t)
{
logger.error(t.getLocalizedMessage());
}
}
/**
* Route message according to setting.
*
* @param oscItem
*/
private void routeItem(OSCItem oscItem)
{
// Route with message?
String routeType = oscItem.getRoute();
// Route message to device?
String device = oscItem.getDevice();
// Fetch device and examine it's status.
MidiDevice midiDevice = midiDeviceCache.get(device);
if (midiDevice != null && midiDevice.isOpen())
{
int message = messageLookup.getMessage(routeType);
// Send message.
switch (message)
{
case ShortMessage.CONTINUE:
// TODO
break;
case ShortMessage.NOTE_ON:
// Parameter check.
List<OSCValue> noteOnValues = oscItem.getValues();
if (messageLookup.getParametersCount(routeType) != noteOnValues.size())
{
oscItem.setWorking(OSCItem.WORKING_ERROR);
}
else
{
int noteOn = Integer.valueOf(noteOnValues.get(0).getValue());
int velocityOn = Integer.valueOf(noteOnValues.get(1).getValue());
int channelOn = Integer.valueOf(noteOnValues.get(2).getValue());
sendNote(ShortMessage.NOTE_ON, noteOn, velocityOn, channelOn, midiDevice);
// Set status.
oscItem.setWorking(OSCItem.WORKING_DONE);
}
break;
case ShortMessage.NOTE_OFF:
// Parameter check.
List<OSCValue> noteOffValues = oscItem.getValues();
if (messageLookup.getParametersCount(routeType) != noteOffValues.size())
{
oscItem.setWorking(OSCItem.WORKING_ERROR);
}
else
{
int noteOff = Integer.valueOf(noteOffValues.get(0).getValue());
int velocityOff = Integer.valueOf(noteOffValues.get(1).getValue());
int channelOff = Integer.valueOf(noteOffValues.get(2).getValue());
sendNote(ShortMessage.NOTE_OFF, noteOff, velocityOff, channelOff, midiDevice);
// Set status.
oscItem.setWorking(OSCItem.WORKING_DONE);
}
break;
case ShortMessage.POLY_PRESSURE:
// TODO
break;
case ShortMessage.CHANNEL_PRESSURE:
// TODO
break;
case ShortMessage.PITCH_BEND:
// TODO
break;
case ShortMessage.PROGRAM_CHANGE:
// TODO
break;
case ShortMessage.CONTROL_CHANGE:
// TODO
break;
default:
break;
}
}
}
/**
* Send MIDI note.
*
* @param note
* @param velocity
* @param channel
* @param midiDevice
*/
private void sendNote(int command, int note, int velocity, int channel, MidiDevice midiDevice)
{
try
{
ShortMessage midiMessage = new ShortMessage();
midiMessage.setMessage(command, channel, note, velocity);
midiDevice.getReceiver().send(midiMessage, -1);
}
catch (InvalidMidiDataException e)
{
logger.error(e.getLocalizedMessage());
}
catch (MidiUnavailableException e)
{
logger.error(e.getLocalizedMessage());
}
}
}