/* For License see bottom */
/*
* MidiReader.java
*
* Created on 1. Juli 2007, 20:26
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package jrackattack.midi;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import jonkoshare.util.VersionInformation;
import jrackattack.JRackAttack;
import jrackattack.gui.JRackAttackFrame;
import org.apache.log4j.Logger;
/**
* One half of the MIDI-messaging and the reading one. This thread runs to get
* filled with all the fine parameter changes one causes. This thread is not
* directly responsible for keyboard events, have a look at
* {@link jrackattack.midi.MidiReader.KeyboardReceiver KeyboardReceiver} for
* this.
* <p>
* TODO: Re-implement as
* {@link java.util.concurrent.ScheduledThreadPoolExecutor}.
*
* @author methke01
*/
@VersionInformation(lastChanged = "$LastChangedDate: 2009-07-25 04:59:33 -0500 (Sat, 25 Jul 2009) $", authors = { "Alexander Methke" }, revision = "$LastChangedRevision: 11 $", lastEditor = "$LastChangedBy: onkobu $", id = "$Id")
public class MidiReader extends Thread implements Receiver {
private static final Logger log = Logger.getLogger(MidiReader.class);
/** Creates a new instance of MidiReader */
public MidiReader() {
}
// Thread
@Override
public void run() {
runFlag = true;
while (runFlag) {
try {
sleep(200);
} catch (InterruptedException e) {
}
}
}
// Receiver
public void close() {
// do nothing?
}
public void send(MidiMessage message, long timeStamp) {
if (JRackAttack.spoolMidi()) {
debug("receiving " + MidiReader.formatMidiMessage(message));
debug(message.getLength() + " bytes)");
}
// Clock and active sensing won't be treated
// in any way
if (message.getStatus() == ShortMessage.ACTIVE_SENSING
|| message.getStatus() == ShortMessage.TIMING_CLOCK) {
return;
}
if (!isWaldorfSysex(message)) {
JRackAttackFrame.getInstance().handleError(
"message.sysex_not_waldorf", "EOX");
return;
}
soundBytes = message.getMessage();
switch (soundBytes[4]) {
case RackAttack.SOUND_DUMP: {
fill(new SoundParameter());
}
break;
case RackAttack.FX_DUMP: {
fill(new FXParameter());
}
break;
case RackAttack.GLOBAL_DUMP: {
fill(new GlobalParameter());
}
break;
case RackAttack.PATTERN_DUMP: {
fill(new PatternParameter());
}
break;
case RackAttack.PROGRAM_DUMP: {
fill(new ProgramParameter());
}
break;
case RackAttack.SOUND_CHANGE: {
fill(new ParameterChange(ParameterChange.Type.SND));
}
break;
case RackAttack.FX_CHANGE: {
fill(new ParameterChange(ParameterChange.Type.FX));
}
break;
case RackAttack.GLOBAL_CHANGE: {
fill(new ParameterChange(ParameterChange.Type.GLB));
}
break;
case RackAttack.PATTERN_CHANGE: {
fill(new ParameterChange(ParameterChange.Type.PAT));
}
break;
case RackAttack.PROGRAM_CHANGE: {
fill(new ParameterChange(ParameterChange.Type.PRG));
}
break;
default: {
log("Sysex-Message unknown: " + formatMidiMessage(message));
}
}
}
public boolean isRunning() {
return runFlag;
}
public void setRunning(boolean state) {
runFlag = state;
}
public void done() {
runFlag = false;
}
public void fill(SoundParameter param) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
param.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setSoundParameter(param);
MidiThread.getInstance().receivedData();
}
public void fill(PatternParameter param) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
param.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setPatternParameter(param);
MidiThread.getInstance().receivedData();
}
public void fill(ProgramParameter param) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
// when loading a program from edit buffer it has no
// program number, so we need to redefine this
//
if (soundBytes[5] == RackAttack.EDIT_BUFFER
&& getNextProgramNumber() != -1) {
soundBytes[6] = (byte) (getNextProgramNumber() & 0x7f);
setNextProgramNumber(-1);
}
param.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setProgramParameter(param);
MidiThread.getInstance().receivedData();
}
public void fill(GlobalParameter param) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
param.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setGlobalParameter(param);
MidiThread.getInstance().receivedData();
}
public void fill(FXParameter param) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
param.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setFxParameter(param);
MidiThread.getInstance().receivedData();
}
public void fill(ParameterChange pc) {
if (soundBytes == null) {
throw new IllegalStateException("cannot fill without recorded data");
}
pc.parse(soundBytes);
soundBytes = null;
JRackAttackFrame.getInstance().setParameterChange(pc);
}
// private String formatMidiEvent(MidiEvent msg) {
// if (msg == null) {
// return "";
// }
// return formatMidiMessage(msg.getMessage());
// }
public static final String formatMidiMessage(MidiMessage msg) {
if (msg == null) {
return "";
}
byte bts[] = msg.getMessage();
StringBuffer sb = new StringBuffer();
for (byte b : bts) {
String str = Integer.toHexString(b & 0xFF);
if (str.length() < 2) {
sb.append("0");
}
sb.append(str);
sb.append(" ");
}
return sb.toString();
}
protected void debug(String msg) {
log.debug(msg);
}
private boolean runFlag;
private byte[] soundBytes;
/**
* Holds value of property midiInDevice.
*/
private MidiDevice midiInDevice;
/**
* Getter for property midiInDevice.
*
* @return Value of property midiInDevice.
*/
public MidiDevice getMidiInDevice() {
return this.midiInDevice;
}
/**
* Setter for property midiInDevice.
*
* @param midiInDevice
* New value of property midiInDevice.
*/
public void setMidiInDevice(MidiDevice midiInDevice) {
if (this.midiInDevice != null && midiInDevice.isOpen()) {
midiInDevice.close();
}
this.midiInDevice = midiInDevice;
if (this.midiInDevice != null && !midiInDevice.isOpen()) {
if (midiInDevice.getMaxTransmitters() == 0) {
JRackAttackFrame.getInstance()
.handleError("msg.no_transmitter");
} else {
try {
midiInDevice.open();
midiInDevice.getTransmitter().setReceiver(this);
} catch (Exception ex) {
JRackAttackFrame.getInstance().handleError(
"msg.midi_in_init_failed", ex);
}
}
}
}
protected boolean isWaldorfSysex(MidiMessage msg) {
return (msg.getMessage()[0] & 0xff) == 0xf0
&& msg.getMessage()[1] == RackAttack.WALDORF_MANUFACTURER_BYTE
&& msg.getMessage()[2] == RackAttack.RACK_ATTACK_MODEL_BYTE;
}
protected void log(String msg) {
log.debug(msg);
}
/**
* Holds value of property keyInDevice.
*/
private MidiDevice keyInDevice;
/**
* Getter for property keyInDevice.
*
* @return Value of property keyInDevice.
*/
public MidiDevice getKeyInDevice() {
return this.keyInDevice;
}
/**
* Setter for property keyInDevice.
*
* @param keyInDevice
* New value of property keyInDevice.
*/
public void setKeyInDevice(MidiDevice keyInDevice) {
if (this.keyInDevice != null && this.keyInDevice.isOpen()) {
this.keyInDevice.close();
}
this.keyInDevice = keyInDevice;
if (this.keyInDevice != null && !this.keyInDevice.isOpen()) {
try {
this.keyInDevice.open();
this.keyInDevice.getTransmitter().setReceiver(
new KeyBoardReceiver());
} catch (MidiUnavailableException e) {
log.error("error", e);
}
}
}
/**
* Receiver for keyboard events. If you have MIDI-IN and Keyboard-In
* configured correctly, you'll be able to play the RackAttack through the
* GUI with a master keyboard or similar. All emitted events from this
* device will be routet through this Receiver and back into the
* {@link jrackattack.midi.MidiThread MidiThread} to be sent to your
* RackAttack.
*
*/
public class KeyBoardReceiver implements Receiver {
public void send(MidiMessage message, long timeStamp) {
// looks for the active MIDI channel to reprogram
// the incoming NOTE-events to become events of the
// right channel (RackAttack has 12 of them each with
// a possibly different sound loaded)
try {
if (message.getStatus() == ShortMessage.NOTE_ON
|| message.getStatus() == ShortMessage.NOTE_OFF) {
ShortMessage sm = (ShortMessage) message;
sm.setMessage(sm.getCommand(), JRackAttackFrame
.getInstance().getActiveMidiChannel(), sm
.getData1(), sm.getData2());
}
MidiThread.getInstance().enqueueEvent(message);
} catch (MidiUnavailableException e) {
log.error("error", e);
} catch (InvalidMidiDataException e) {
log.error("error", e);
}
}
public void close() {
}
}
/**
* Holds value of property nextProgramNumber.
*/
private int nextProgramNumber;
/**
* Getter for property nextProgramNumber.
*
* @return Value of property nextProgramNumber.
*/
public int getNextProgramNumber() {
return this.nextProgramNumber;
}
/**
* Setter for property nextProgramNumber.
*
* @param nextProgramNumber
* New value of property nextProgramNumber.
*/
public void setNextProgramNumber(int nextProgramNumber) {
this.nextProgramNumber = nextProgramNumber;
}
}
/*
* Copyright (C) 2008 Alexander Methke
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program (gplv3.txt).
*/