/** MIDI keyboard
*
* @author pquiring
*
* Created : Feb 17, 2014
*/
import java.awt.*;
import java.util.*;
import javax.sound.midi.*;
import javaforce.*;
import javaforce.media.*;
public class MidiKeyboard extends javax.swing.JDialog implements Receiver {
/**
* Creates new form MidiKeyboard
*/
public MidiKeyboard(java.awt.Frame parent, boolean modal, Music music) {
super(parent, modal);
initComponents();
buildTables(54.0f);
if (!listDevices()) return;
if (music != null) {
this.music = music;
} else {
this.music = new Music();
this.music.start(20, 40); //20ms buffers, 40 channels max
}
this.music.soundClear();
Library.load();
loadLibrary();
setPosition();
JFImage icon = new JFImage();
icon.loadPNG(this.getClass().getClassLoader().getResourceAsStream("jfmusic.png"));
setIconImage(icon.getImage());
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this method is always regenerated by the Form
* Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
device = new javax.swing.JComboBox();
jLabel2 = new javax.swing.JLabel();
library = new javax.swing.JComboBox();
close = new javax.swing.JButton();
velocity = new javax.swing.JCheckBox();
jLabel3 = new javax.swing.JLabel();
region = new javax.swing.JComboBox();
autoRegion = new javax.swing.JCheckBox();
attenuation = new javax.swing.JSlider();
jLabel4 = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle("Midi Keyboard");
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
formWindowClosing(evt);
}
});
jLabel1.setText("Device:");
device.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
deviceItemStateChanged(evt);
}
});
jLabel2.setText("Instrument:");
library.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
libraryItemStateChanged(evt);
}
});
close.setText("Close");
close.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeActionPerformed(evt);
}
});
velocity.setSelected(true);
velocity.setText("Velocity");
jLabel3.setText("Region:");
autoRegion.setSelected(true);
autoRegion.setText("Auto");
attenuation.setMajorTickSpacing(25);
attenuation.setMinorTickSpacing(5);
attenuation.setPaintTicks(true);
attenuation.setToolTipText("");
attenuation.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
attenuationStateChanged(evt);
}
});
jLabel4.setText("Attenuation");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(device, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(library, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(velocity)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(close))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel3)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(autoRegion)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(region, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jLabel4)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(attenuation, javax.swing.GroupLayout.PREFERRED_SIZE, 319, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(device, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(library, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel3)
.addComponent(region, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(autoRegion))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(attenuation, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(close)
.addComponent(velocity))
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void libraryItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_libraryItemStateChanged
loadRegions();
loadSound();
}//GEN-LAST:event_libraryItemStateChanged
private void deviceItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_deviceItemStateChanged
loadMIDI();
}//GEN-LAST:event_deviceItemStateChanged
private void closeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeActionPerformed
if (exit) System.exit(0); else dispose();
}//GEN-LAST:event_closeActionPerformed
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
if (exit) System.exit(0); else dispose();
}//GEN-LAST:event_formWindowClosing
private void attenuationStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_attenuationStateChanged
float att = getAttenuation();
JFLog.log("attenuation = " + att);
for(int a=0;a<sounds.length;a++) {
music.soundAttenuation(sounds[a], att);
}
}//GEN-LAST:event_attenuationStateChanged
/** Runs MIDI Keyboard stand alone
*
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the dialog */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
MidiKeyboard dialog = new MidiKeyboard(null, true, null);
dialog.exit = true;
dialog.setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JSlider attenuation;
private javax.swing.JCheckBox autoRegion;
private javax.swing.JButton close;
private javax.swing.JComboBox device;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JComboBox library;
private javax.swing.JComboBox region;
private javax.swing.JCheckBox velocity;
// End of variables declaration//GEN-END:variables
private Music music;
private ArrayList<MidiDevice> list = new ArrayList<MidiDevice>();
private MidiDevice midi;
private Transmitter trans;
private int idxes[] = new int[0x80]; //play back channel indexes
private float vols[] = new float[0x80];
private boolean exit = false;
private int regions[][];
private int sounds[]; //loaded sound index
private boolean listDevices() {
device.removeAllItems();
device.addItem("Select device");
list.clear();
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
try {
if (infos == null) throw new Exception("no devices found");
for(int a=0;a<infos.length;a++) {
MidiDevice midiDevice = MidiSystem.getMidiDevice(infos[a]);
if (midiDevice.getMaxTransmitters() == 0) continue;
list.add(midiDevice);
device.addItem(infos[a].getName());
}
if (device.getItemCount() == 0) throw new Exception("no devices found");
return true;
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", "No devices found");
if (exit) System.exit(0); else dispose();
return false;
}
}
private void loadLibrary() {
library.removeAllItems();
ArrayList<Library.Entry> list = Library.getList();
for(int a=0;a<list.size();a++) {
library.addItem(list.get(a).name);
}
}
private void loadRegions() {
int idx = library.getSelectedIndex();
if (idx == -1) return;
Library.Entry entry = Library.get(idx);
region.removeAllItems();
switch (entry.type) {
case WAV:
//no regions
regions = new int[1][3];
regions[0][0] = 0;
regions[0][1] = 0x7f;
regions[0][2] = 0x3c; //middle C (60)
region.addItem("Full Range (Middle C)");
break;
case DLS:
DLS dls = Library.getDLS(entry.dls_idx);
int cnt = dls.getRegionsCount(entry.name);
regions = new int[cnt][3];
for(int a=0;a<cnt;a++) {
DLS.Region r = dls.getRegion(entry.name, a);
regions[a][0] = r.keyMin;
regions[a][1] = r.keyMax;
regions[a][2] = r.unityNote;
region.addItem("Keys " + DLS.getKeyName(r.keyMin) + " thru " + DLS.getKeyName(r.keyMax));
}
break;
}
}
private float getAttenuation() {
return attenuation.getValue() / 100.f * 0.00005f;
}
private void loadSound() {
music.soundClear();
int idx = library.getSelectedIndex();
if (idx == -1) return;
Library.Entry entry = Library.get(idx);
String name = entry.name;
switch (entry.type) {
case WAV:
sounds[0] = music.soundLoad("library/" + entry.name, -1, -1, -1, -1, getAttenuation());
break;
case DLS:
DLS dls = Library.getDLS(entry.dls_idx);
int cnt = dls.getRegionsCount(name);
sounds = new int[cnt];
for(int r=0;r<cnt;r++) {
DLS.Instrument i = dls.getInstrument(name, r);
DLS.Region re = dls.getRegion(name, r);
if (i == null) continue;
int susStart = -1, susEnd = -1;
if (i.loopStart != -1) {
susStart = i.loopStart;
susEnd = i.loopStart + i.loopLength;
}
sounds[r] = music.soundLoad(i.samples, -1, -1, susStart, susEnd, getAttenuation(), re.unityNote);
}
break;
}
}
private void loadMIDI() {
if (midi != null) {
midi.close();
midi = null;
}
int idx = device.getSelectedIndex();
if (idx == -1) return;
if (idx == 0) return;
idx--;
midi = list.get(idx);
try {
trans = midi.getTransmitter();
trans.setReceiver(this);
midi.open();
JFLog.log("selected device:" + midi + "," + trans);
} catch (Exception e) {
JFLog.log(e);
midi.close();
JF.showError("Error", "Failed to open device");
}
}
//C-2=0x00 C-1=0x0c C0=0x18 C1=0x24 C2=0x30 ... C8=0x78 ... C9=0x7f
//C5 = 1.0f
private void buildTables(float baseFreq) {
// JFLog.log("buildTables : baseFreq=" + baseFreq);
for(int a=0;a<=0x7f;a++) {
idxes[a] = -1;
vols[a] = ((float)a) / 127.0f;
}
}
private void setPosition() {
Dimension d = getSize();
Rectangle s = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
setLocation(s.width/2 - d.width/2, s.height/2 - d.height/2);
}
private int getRegionOfNote(int note) {
for(int r=0;r<regions.length;r++) {
if (note >= regions[r][0] && note <= regions[r][1]) return r;
}
return -1;
}
//Receiver.close() - do nothing
public void close() {}
//Receiver.send() - receive MIDI message
public void send(MidiMessage message, long timeStamp) {
if (!(message instanceof ShortMessage)) return;
//decode message
ShortMessage sm = (ShortMessage)message;
int cmd = sm.getCommand() & 0xff;
int d1 = sm.getData1() & 0x7f;
int d2 = sm.getData2() & 0x7f;
String cmdstr = null;
switch (cmd) {
case 0x80: //note off
// note / velocity (zero)
cmdstr = "keyUp";
if (idxes[d1] != -1) {
if (false) {
music.channelKeyUp(idxes[d1]); //sounds abrupt
} else {
music.channelAttenuation(idxes[d1], 0.0001f); //increase attenutation greatly
}
idxes[d1] = -1;
}
break;
case 0x90: //note on
// note / velocity (0-127)
cmdstr = "keyDn";
if (!velocity.isSelected()) d2 = 0x7f;
int regionIdx;
if (autoRegion.isSelected())
regionIdx = getRegionOfNote(d1);
else
regionIdx = region.getSelectedIndex();
if (regionIdx == -1) break;
JFLog.log("region=" + regionIdx);
idxes[d1] = music.soundPlay(sounds[regionIdx], vols[d2], vols[d2], d1);
//remove index from other notes (in case user has still not released note)
for(int a=0;a<0x80;a++) {
if (a == d1) continue;
if (idxes[a] == idxes[d1]) {
idxes[a] = -1;
}
}
break;
case 0xb0: //control change
cmdstr = " ctrl";
// control / value (0-127)
// controls : 1 = modulation : 7 = volume : 4a = ch1 : 47 = ch2 : 49 = ch3 : 48 = ch4
break;
case 0xe0: //pitch wheel
cmdstr = "pitch";
// zero / value (0-127)
break;
}
JFLog.log(String.format("%s:%02x:%02x:%02x", cmdstr, cmd, d1, d2));
}
}