/** 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)); } }