/*
* Created on Feb 22, 2006
*
* Copyright (c) 2006 P.J.Leonard
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika 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 2 of the License, or
* (at your option) any later version.
* Frinika 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 Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer.model;
import com.frinika.global.FrinikaConfig;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.Instrument;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.swing.Icon;
import rasmus.midi.provider.RasmusSynthesizer;
import rasmus.midi.provider.RasmusSynthesizer.LoadedInstrument;
import com.frinika.tootX.MidiPeakMonitor;
import com.frinika.tootX.midi.MidiInDeviceManager;
import com.frinika.project.ProjectContainer;
import com.frinika.sequencer.FrinikaTrackWrapper;
import com.frinika.sequencer.MidiResource;
import com.frinika.sequencer.gui.mixer.SynthWrapper;
import com.frinika.sequencer.midi.MidiListProvider;
import com.frinika.sequencer.midi.MidiMessageListener;
import com.frinika.sequencer.midi.MonitorReceiver;
import com.frinika.sequencer.patchname.MyPatch;
import com.frinika.synth.Synth;
import com.frinika.synth.SynthRack;
import com.frinika.synth.synths.MySampler;
import com.frinika.synth.synths.sampler.settings.SampledSoundSettings;
import java.util.Collection;
import java.util.HashSet;
public class MidiLane extends Lane implements RecordableLane {
/**
*
*/
private static final long serialVersionUID = 7079152267067539976L;
static Icon iconNoteLane = new javax.swing.ImageIcon(
RasmusSynthesizer.class.getResource("/icons/midilane.png"));
static Icon iconDrumLane = new javax.swing.ImageIcon(
RasmusSynthesizer.class.getResource("/icons/drumlane.png"));
transient FrinikaTrackWrapper ftw;
MidiPart trackHeaderPart = null;
ProgramChangeEvent programEvent;
String voiceName;
/**
* Used to store the midi device/channel used when saving
*/
Integer midiDeviceIndex;
int midiChannel;
/**
* Stuff to let the GUI know what sort of lane it is.
*
*/
static public final int UNKNOWN_TYPE = 0;
static public final int MELODIC = 1;
static public final int DRUM = 2;
static public final int SCORE = 4;
static public final int META=8;
static int nameCount = 0;
int laneType;
MidiPlayOptions playOptions = new MidiPlayOptions(); // Jens
String patchMapName;
public String getPatchMapName() {
return patchMapName;
}
public void setPatchMapName(String patchMapName) {
this.patchMapName = patchMapName;
}
transient Collection<MidiMessageListener> midiMessageListeners = new HashSet<MidiMessageListener>(); // Jens
transient MidiPeakMonitor peakMonitor = null;
transient String keyNames[] = null;
transient boolean isSolo;
/* Serializable constructor */
protected MidiLane() {
}
/**
* Constructor for deepClone
*
* @param cloneMe
*/
private MidiLane(MidiLane cloneMe) {
super("Copy of " + cloneMe.getName(), cloneMe.project);
trackHeaderPart = (MidiPart) (cloneMe.trackHeaderPart.deepCopy(null));
trackHeaderPart.lane = this;
midiDeviceIndex = cloneMe.midiDeviceIndex;
midiChannel = cloneMe.midiChannel;
keyNames = cloneMe.keyNames;
for (Part part : cloneMe.getParts()) {
part.deepCopy(this);
}
ftw = cloneMe.ftw.getSequence().createFrinikaTrack();
ftw.setMidiChannel(midiChannel);
setUpKeys();
}
/**
* Construct an empty lane
*
* @param ftw
* @param project
*/
public MidiLane(FrinikaTrackWrapper ftw, ProjectContainer project) {
super("Midi " + nameCount++, project);
this.ftw = ftw;
// cntrlList=new ControllerList(); // TODO different lists
// We do not want this part to be in the part list
trackHeaderPart = new MidiPart();
trackHeaderPart.lane = this;
programEvent = new ProgramChangeEvent(trackHeaderPart, 0, 0, 0, 0);
programEvent.commitAdd();
midiChannel = ftw.getMidiChannel();
}
public MidiPart getHeadPart() {
return trackHeaderPart;
}
// Hide this because midi lane duplicates some of the state and we don't
// want anyone to cheat.
public FrinikaTrackWrapper getTrack() {
return ftw;
}
public MidiDevice getMidiDevice() {
return ftw.getMidiDevice();
}
/**
*
* @return The reciever associated with this lane
*/
public Receiver getReceiver() {
MidiDevice dev = ftw.getMidiDevice();
if (dev == null) {
return null;
}
try {
// Jens:
// return getTrack().getMidiDevice().getReceiver();
Receiver r = ftw.getMidiDevice().getReceiver();
if (r != null) {
return new MonitorReceiver(midiMessageListeners, r);
} else {
return null;
}
} catch (MidiUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public int getMidiChannel() {
return ftw.getMidiChannel();
}
/**
* The midi channel stored in the saved project
* @return
*/
public int getStoredMidiChannel() {
return midiChannel;
}
public void restoreFromClone(EditHistoryRecordable object) {
// TODO Auto-generated method stub
}
public ControllerListProvider getControllerList() {
MidiDevice dev = getMidiDevice();
if (dev == null) {
return MidiResource.getDefaultControllerList();
}
if (dev instanceof MidiListProvider) {
return ((MidiListProvider) dev).getControllerList();
}
// TODO default
return MidiResource.getDefaultControllerList();
}
// TODO Can we purge these 2 and replace with mute ?
public void attachFTW() {
ftw.attachToSequence();
}
public void detachFTW() {
ftw.detachFromSequence();
}
/*
* public ProgramChangeEvent getPatch(){ return patch; }
*/
/**
* This is used to find the entry in Voice To Patch map.
*
* @return name associated with the midipatch (prog,bank) pair
*/
public String getVoiceName() {
return voiceName;
}
public void setVoiceName(String name) {
voiceName = name;
}
private void writeObject(ObjectOutputStream out) throws IOException {
this.midiChannel = ftw.getMidiChannel();
if (ftw.getMidiDevice() != null) {
this.midiDeviceIndex = project.getMidiDeviceIndex(ftw.getMidiDevice());
}
out.defaultWriteObject();
}
@Override
/**
* This will set the correct MidiDevice when the project is reloaded
*/
public void onLoad() {
super.onLoad();
ftw.setMidiChannel(midiChannel);
if (midiDeviceIndex != null) {
try {
ftw.setMidiDevice(project.getSequencer().listMidiOutDevices().get(midiDeviceIndex));
} catch (Exception e) {
System.out.println("WARNING: Was unable to connect to external midi device");
}
}
setUpKeys();
}
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException {
// Create the Frinika Track Wrapper so that when reading MultiEvents the
// MidiEvents can be generated as well
/**
* PJS: The sequence is created for the first time here - reason why not
* in the ProjectContainer is because the ticksPerQuarterNote variable
* might not be loaded before the object is entirely read. Chicken and
* egg problem - but this solves it...
*/
if (this.getProject().getSequence() == null) {
this.getProject().createSequence();
}
this.ftw = this.getProject().getSequence().createFrinikaTrack();
in.defaultReadObject();
if (programEvent == null) { // previous versions
trackHeaderPart = new MidiPart();
trackHeaderPart.lane = this;
programEvent = new ProgramChangeEvent(trackHeaderPart, 0, 0, 0, 0);
}
midiMessageListeners = new HashSet<MidiMessageListener>(); // Jens
programEvent.commitAdd();
// getPlayOptions();
// setUpKeys();
}
public void setProgram(int prog, int msb, int lsb) {
programEvent.commitRemove();
programEvent.setProgram(prog, msb, lsb);
programEvent.commitAdd();
setUpKeys();
}
public void setProgram(MyPatch patch) {
programEvent.commitRemove();
programEvent.setProgram(patch.prog, patch.msb, patch.lsb);
programEvent.commitAdd();
setUpKeys();
}
public MyPatch getProgram() {
MyPatch patch = programEvent.getPatch();
if (patch != null) {
return patch;
}
FrinikaTrackWrapper track = ftw;
int count = track.size();
for (int i = 0; i < count; i++) {
MidiEvent event = track.get(i);
if (event.getTick() != 0) {
return patch;
}
MidiMessage msg = event.getMessage();
if (msg instanceof ShortMessage) {
ShortMessage sms = (ShortMessage) msg;
if (sms.getCommand() == ShortMessage.PROGRAM_CHANGE) {
if (patch == null) {
patch = new MyPatch(0, 0, 0);
}
patch.prog = sms.getData1();
}
if (sms.getCommand() == ShortMessage.CONTROL_CHANGE) {
if (sms.getData1() == 0) {
if (patch == null) {
patch = new MyPatch(0, 0, 0);
}
patch.msb = sms.getData2();
}
if (sms.getData1() == 0x20) {
if (patch == null) {
patch = new MyPatch(0, 0, 0);
}
patch.lsb = sms.getData2();
}
}
}
}
return patch;
}
public Selectable deepCopy(Selectable parent) {
MidiLane clone = new MidiLane(this);
return clone;
}
/**
* Lanes just move down the list. SIlly generic interface.
*/
public void deepMove(long tick) {
}
public void addToModel() {
super.addToModel();
onLoad();
}
public boolean isRecording() {
return project.getSequencer().isRecording(this);
}
public boolean isMute() {
// return project.getSequencer().isMute(this);
return playOptions.muted; // Jens
}
public boolean isSolo() {
return isSolo;
// return project.getSequencer().isSolo(this);
}
public void setRecording(boolean b) {
MidiInDeviceManager.open(FrinikaConfig.getMidiInDeviceList()); // Possibly redundant now?
if (b) {
project.getSequencer().recordEnable(this);
} else {
project.getSequencer().recordDisable(this);
}
}
public void setMute(boolean b) {
// project.getSequencer().setMute(this,b);
playOptions.muted = b; // Jens
}
public void setSolo(boolean b) {
isSolo = b;
// project.getSequencer().setSolo(this, b);
}
public double getMonitorValue() {
if (peakMonitor == null) {
peakMonitor = new MidiPeakMonitor();
addMidiMessageListener(peakMonitor);
}
return peakMonitor.getPeak();
}
// Jens:
public MidiPlayOptions getPlayOptions() {
if (playOptions == null) {
playOptions = new MidiPlayOptions();
} // hack to allow loading of older files
return playOptions;
}
public void addMidiMessageListener(MidiMessageListener l) {
midiMessageListeners.add(l);
}
public void removeMidiMessageListener(MidiMessageListener l) {
midiMessageListeners.remove(l);
}
public void setMidiChannel(int channel) {
midiChannel = channel;
ftw.setMidiChannel(midiChannel);
setUpKeys();
}
public boolean isDrumLane() {
// System. out.println(" Chn =" + midiChannel);
return (laneType == DRUM) || (laneType == UNKNOWN_TYPE && midiChannel == 9);
}
public void setMidiDevice(MidiDevice dev) {
ftw.setMidiDevice(dev);
setUpKeys();
}
public void setType(int type) {
laneType = type;
setUpKeys();
}
public int getType() {
return laneType;
}
public void setKeyNames(String[] keyNames) {
if (keyNames == null) {
keyNames = null;
} else {
if (keyNames.length != 128) {
String[] nn = new String[128];
for (int i = 0; i < keyNames.length; i++) {
nn[i] = keyNames[i];
}
for (int i = keyNames.length; i < 128; i++) {
nn[i] = "";
}
System.err.println("Strange keyNames.length =" + keyNames.length);
// try {
// throw new Exception("Strange keyNames.length =" + keyNames.length);
// } catch (Exception ex) {
// Logger.getLogger(MidiLane.class.getName()).log(Level.SEVERE, null, ex);
// }
keyNames = nn;
}
this.keyNames = keyNames;
}
notifyFocusListeners();
}
// possibly .......,.
@Deprecated
protected void setUpKeys() {
getPlayOptions();
if (!isDrumLane()) {
getPlayOptions().drumMapped = false;
} else {
if (playOptions.noteMap != null) {
playOptions.drumMapped = true;
}
}
notifyFocusListeners();
if (true) {
return;
}
keyNames = null;
if (!isDrumLane()) {
playOptions.drumMapped = false;
} else {
if (playOptions.noteMap != null) {
playOptions.drumMapped = true;
}
Instrument inst = null;
MidiDevice dev = getMidiDevice();
if (dev instanceof SynthRack) {
Synth syn = ((SynthRack) dev).getSynth(midiChannel);
if (syn instanceof MySampler) {
keyNames = new String[128];
MySampler mys = (MySampler) syn;
SampledSoundSettings[][] ssss = mys.sampledSounds;
for (int i = 0; i < 128; i++) {
if (ssss[i][0] != null) {
keyNames[i] = ssss[i][0].toString();
}
}
}
} else if (dev instanceof SynthWrapper) {
dev = ((SynthWrapper) dev).getRealDevice();
if (dev instanceof Synthesizer) {
Synthesizer synth = (Synthesizer) dev;
// System.out.println(synth);
MyPatch patch = getProgram();
Method getChannels = null;
// System.out.println(" LANE PATCH " + patch);
Instrument insts[] = synth.getLoadedInstruments();
for (Instrument ins : insts) {
// System.out.println(" INST :" + ins);
Instrument li = (Instrument) ins;
boolean[] channels = null;
try {
if (getChannels != null) {
if (getChannels.getDeclaringClass() != li.getClass()) {
getChannels = null;
}
}
if (getChannels == null) {
getChannels = li.getClass().getMethod(
"getChannels");
}
if (getChannels != null) {
channels = (boolean[]) getChannels.invoke(li, (Object[]) null);
}
} catch (Exception e) {
}
// System. out.print(ins.getName() + " "
// + ins.getPatch().getBank() + " "
// + ins.getPatch().getProgram() + " ");
// for (int i = 0; i < li.getChannels().length; i++) {
// if (li.getChannels()[i])
// System. out.print(i + "|");
// }
//
// System. out.println(li.getChannels());
if (channels != null) {
if ((ins.getPatch().getProgram() == patch.prog) && channels[midiChannel]) {
inst = ins;
break;
}
}
}
if (inst == null) {
insts = synth.getAvailableInstruments();
for (Instrument ins : insts) {
Instrument li = (Instrument) ins;
boolean[] channels = null;
try {
if (getChannels != null) {
if (getChannels.getDeclaringClass() != li.getClass()) {
getChannels = null;
}
}
if (getChannels == null) {
getChannels = li.getClass().getMethod(
"getChannels");
}
if (getChannels != null) {
channels = (boolean[]) getChannels.invoke(li, (Object[]) null);
}
} catch (Exception e) {
}
// System. out.print(ins.getName() + " "
// + ins.getPatch().getBank() + " "
// + ins.getPatch().getProgram() + " ");
// for (int i = 0; i < li.getChannels().length; i++) {
// if (li.getChannels()[i])
// System. out.print(i + "|");
// }
//
// System. out.println(li.getChannels());
if (channels != null) {
if ((ins.getPatch().getProgram() == patch.prog) && channels[midiChannel]) {
inst = ins;
break;
}
}
}
}
if (inst != null) {
try {
Method getKeys = inst.getClass().getMethod(
"getKeys");
if (getKeys != null) {
keyNames = (String[]) getKeys.invoke(inst, (Object[]) null);
}
} catch (Exception e) {
}
}
}
}
}
// System. out.println(" Setup keynames " + keyNames);
notifyFocusListeners();
}
private void notifyFocusListeners() {
Part f = project.getPartSelection().getFocus();
if (f == null) {
return;
}
if (f.getLane() == this) {
project.getPartSelection().setDirty();
project.getPartSelection().notifyListeners();
}
}
public String[] getKeyNames() {
return keyNames;
}
public void setDrumMapping(int k, int index) {
playOptions.drumMapped = true;
if (playOptions.noteMap == null) {
playOptions.noteMap = new int[128];
for (int i = 0; i < 128; i++) {
playOptions.noteMap[i] = i;
}
}
playOptions.noteMap[k] = index;
System.out.println(k + "--->" + index);
notifyFocusListeners();
}
public int mapNote(int num) {
if (playOptions.drumMapped) {
return playOptions.noteMap[num];
} else {
return num;
}
}
@Override
public Part createPart() {
return new MidiPart(this);
}
@Override
public Icon getIcon() {
if (isDrumLane()) {
return iconDrumLane;
} else {
return iconNoteLane;
}
}
/**
* The index of the midiDevice according to the stored order in the saved project
* @return
*/
public Integer getMidiDeviceIndex() {
//
// PJL added this (not sure about this)
if (midiDeviceIndex == null) {
this.midiChannel = ftw.getMidiChannel();
if (ftw.getMidiDevice() != null) {
this.midiDeviceIndex = project.getMidiDeviceIndex(ftw.getMidiDevice());
}
}
// //-----------------
return midiDeviceIndex;
}
}