PmDefaultsFrame.java (15543B)
1 // PmDefaults -- a small application to set PortMIDI default input/output 2 3 /* Implementation notes: 4 5 This program uses PortMidi to enumerate input and output devices and also 6 to send output messages to test output devices and 7 to receive input messages to test input devices. 8 9 */ 10 11 package pmdefaults; 12 13 import javax.swing.*; 14 import java.awt.*; 15 import java.awt.event.*; 16 import java.lang.Math.*; 17 import jportmidi.*; 18 import jportmidi.JPortMidiApi.*; 19 import java.util.ArrayList; 20 import java.util.prefs.*; 21 import java.net.*; 22 23 public class PmDefaultsFrame extends JFrame 24 implements ActionListener, ComponentListener { 25 26 // This class extends JPortMidi in order to override midi input handling 27 // In this case, midi input simply blinks the activity light 28 public class JPM extends JPortMidi { 29 ActivityLight light; 30 PmDefaultsFrame frame; 31 int lightTime; 32 boolean lightState; 33 int now; // current time in ms 34 final int HALF_BLINK_PERIOD = 250; // ms 35 36 public JPM(ActivityLight al, PmDefaultsFrame df) 37 throws JPortMidiException { 38 light = al; 39 frame = df; 40 lightTime = 0; 41 lightState = false; // meaning "off" 42 now = 0; 43 } 44 45 public void poll(int ms) throws JPortMidiException { 46 // blink the light. lightState is initially false (off). 47 // to blink the light, set lightState to true and lightTime 48 // to now + 0.25s; turn on light 49 // now > ligntTime && lightState => set lightTime = now + 0.25s; 50 // set lightState = false 51 // turn off light 52 // light can be blinked again when now > lightTime && !lightState 53 now = ms; 54 if (now > lightTime && lightState) { 55 lightTime = now + HALF_BLINK_PERIOD; 56 lightState = false; 57 light.setState(false); 58 } 59 super.poll(); 60 } 61 62 public void handleMidiIn(PmEvent buffer) { 63 System.out.println("midi in: now " + now + 64 " lightTime " + lightTime + 65 " lightState " + lightState); 66 if (now > lightTime && !lightState) { 67 lightState = true; 68 lightTime = now + HALF_BLINK_PERIOD; 69 light.setState(true); 70 } 71 } 72 } 73 74 public class ActivityLight extends JPanel { 75 Color color; 76 final Color OFF_COLOR = Color.BLACK; 77 final Color ON_COLOR = Color.GREEN; 78 79 ActivityLight() { 80 super(); 81 Dimension size = new Dimension(50, 25); 82 setMaximumSize(size); 83 setPreferredSize(size); 84 setMinimumSize(size); 85 color = OFF_COLOR; 86 System.out.println("ActivityLight " + getSize()); 87 } 88 89 public void setState(boolean on) { 90 color = (on ? ON_COLOR : OFF_COLOR); 91 repaint(); 92 } 93 94 public void paintComponent(Graphics g) { 95 super.paintComponent(g); // paint background 96 g.setColor(color); 97 int x = (getWidth() / 2) - 5; 98 int y = (getHeight() / 2) - 5; 99 g.fillOval(x, y, 10, 10); 100 g.setColor(OFF_COLOR); 101 g.drawOval(x, y, 10, 10); 102 } 103 } 104 105 private JLabel inputLabel; 106 private JComboBox inputSelection; 107 // inputIds maps from the index of an item in inputSelection to the 108 // device ID. Used to open the selected device. 109 private ArrayList<Integer> inputIds; 110 private ActivityLight inputActivity; 111 private JLabel outputLabel; 112 private JComboBox outputSelection; 113 // analogous to inputIds, outputIds maps selection index to device ID 114 private ArrayList<Integer> outputIds; 115 private JButton testButton; 116 private JButton refreshButton; 117 private JButton updateButton; 118 private JButton closeButton; 119 private JLabel logo; 120 121 private JPM jpm; 122 private Timer timer; 123 124 public void componentResized(ComponentEvent e) { 125 System.out.println(e); 126 if (e.getComponent() == this) { 127 Insets insets = getInsets(); 128 Dimension dim = getSize(); 129 layoutComponents(dim.width - insets.left - insets.right, 130 dim.height - insets.top - insets.bottom); 131 } 132 } 133 public void componentMoved(ComponentEvent e) { 134 System.out.println(e); 135 } 136 public void componentHidden(ComponentEvent e) { 137 System.out.println(e); 138 } 139 public void componentShown(ComponentEvent e) { 140 System.out.println(e); 141 } 142 143 144 PmDefaultsFrame(String title) { 145 super(title); 146 initComponents(); 147 System.out.println("initComponents returned\n"); 148 pack(); // necessary before calling getInsets(); 149 // initially, only width matters to layout: 150 layoutComponents(550, 300); 151 System.out.println("after layout, pref " + getPreferredSize()); 152 // now, based on layout-computed preferredSize, set the Frame size 153 Insets insets = getInsets(); 154 Dimension dim = getPreferredSize(); 155 dim.width += (insets.left + insets.right); 156 dim.height += (insets.top + insets.bottom); 157 setSize(dim); 158 System.out.println("size" + getPreferredSize()); 159 addComponentListener(this); 160 161 timer = new Timer(50 /* ms */, this); 162 timer.addActionListener(this); 163 try { 164 jpm = new JPM(inputActivity, this); 165 jpm.setTrace(true); 166 loadDeviceChoices(); 167 timer.start(); // don't start timer if there's an error 168 } catch(JPortMidiException e) { 169 System.out.println(e); 170 } 171 } 172 173 void openInputSelection() { 174 int id = inputSelection.getSelectedIndex(); 175 if (id < 0) return; // nothing selected 176 id = (Integer) (inputIds.get(id)); // map to device ID 177 // openInput will close any previously open input stream 178 try { 179 jpm.openInput(id, 100); // buffer size hopes to avoid overflow 180 } catch(JPortMidiException e) { 181 System.out.println(e); 182 } 183 } 184 185 // make a string to put into preferences describing this device 186 String makePrefName(int id) { 187 String name = jpm.getDeviceName(id); 188 String interf = jpm.getDeviceInterf(id); 189 // the syntax requires comma-space separator (see portmidi.h) 190 return interf + ", " + name; 191 } 192 193 194 public void savePreferences() { 195 Preferences prefs = Preferences.userRoot().node("/PortMidi"); 196 int id = outputSelection.getSelectedIndex(); 197 if (id >= 0) { 198 String prefName = makePrefName(outputIds.get(id)); 199 System.out.println("output pref: " + prefName); 200 prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); 201 } 202 id = inputSelection.getSelectedIndex(); 203 if (id >= 0) { 204 String prefName = makePrefName(inputIds.get(id)); 205 System.out.println("input pref: " + prefName); 206 prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); 207 } 208 try { 209 prefs.flush(); 210 } catch(BackingStoreException e) { 211 System.out.println(e); 212 } 213 } 214 215 public void actionPerformed(ActionEvent e) { 216 Object source = e.getSource(); 217 try { 218 if (source == timer) { 219 jpm.poll(jpm.timeGet()); 220 } else if (source == refreshButton) { 221 if (jpm.isOpenInput()) jpm.closeInput(); 222 if (jpm.isOpenOutput()) jpm.closeOutput(); 223 jpm.refreshDeviceLists(); 224 loadDeviceChoices(); 225 } else if (source == updateButton) { 226 savePreferences(); 227 } else if (source == closeButton) { 228 if (jpm.isOpenInput()) jpm.closeInput(); 229 if (jpm.isOpenOutput()) jpm.closeOutput(); 230 } else if (source == testButton) { 231 sendTestMessages(); 232 } else if (source == inputSelection) { 233 // close previous selection and open new one 234 openInputSelection(); 235 } else if (source == outputSelection) { 236 jpm.closeOutput(); // remains closed until Test button reopens 237 } 238 } catch(JPortMidiException ex) { 239 System.out.println(ex); 240 } 241 }; 242 243 private void layoutComponents(int width, int height) { 244 // I tried to do this with various layout managers, but failed 245 // It seems pretty straightforward to just compute locations and 246 // sizes. 247 248 int gap = 2; // pixel separation between components 249 int indent = 20; 250 int y = gap; 251 252 // inputLabel goes in upper left 253 inputLabel.setLocation(0, y); 254 inputLabel.setSize(inputLabel.getPreferredSize()); 255 256 // inputSelection goes below and indented, width based on panel 257 y += inputLabel.getHeight() + gap; 258 inputSelection.setLocation(indent, y); 259 // size of inputSelection must leave room at right for inputButton 260 // (in fact, inputActivity goes there, but we'll make inputSelection 261 // and outputSelection the same size, based on leaving room for 262 // testButton, which is larger than inputActivity.) 263 Dimension dim = inputSelection.getPreferredSize(); 264 Dimension dimButton = testButton.getPreferredSize(); 265 // make button and selection the same height so they align 266 dim.height = dimButton.height = Math.max(dim.height, dimButton.height); 267 // make selection width as wide as possible 268 dim.width = width - indent - dimButton.width - gap; 269 inputSelection.setSize(dim); 270 271 // inputActivity goes to the right of inputSelection 272 inputActivity.setLocation(indent + dim.width + gap, y); 273 // square size to match the height of inputSelection 274 inputActivity.setSize(dim.height, dim.height); 275 276 // outputLabel goes below 277 y += dim.height + gap; 278 outputLabel.setLocation(0, y); 279 outputLabel.setSize(outputLabel.getPreferredSize()); 280 281 // outputSelection is like inputSelection 282 y += outputLabel.getHeight() + gap; 283 outputSelection.setLocation(indent, y); 284 outputSelection.setSize(dim); 285 286 // testButton is like inputActivity 287 testButton.setLocation(indent + dim.width + gap, y); 288 testButton.setSize(dimButton); 289 System.out.println("button " + dimButton + " selection " + dim); 290 291 // refreshButton is below 292 y += dim.height + gap; 293 dim = refreshButton.getPreferredSize(); 294 refreshButton.setLocation(indent, y); 295 refreshButton.setSize(dim); 296 297 // updateButton to right of refreshButton 298 int x = indent + dim.width + gap; 299 updateButton.setLocation(x, y); 300 dim = updateButton.getPreferredSize(); 301 updateButton.setSize(dim); 302 303 // closeButton to right of updateButton 304 x += dim.width + gap; 305 closeButton.setLocation(x, y); 306 dim = closeButton.getPreferredSize(); 307 closeButton.setSize(dim); 308 309 // place logo centered at bottom 310 y += dim.height + gap; 311 logo.setLocation((width - logo.getWidth()) / 2, 312 height - gap - logo.getHeight()); 313 314 // set overall size 315 y += logo.getHeight() + gap; 316 System.out.println("computed best size " + width + ", " + y); 317 setPreferredSize(new Dimension(width, y)); 318 } 319 320 private void initComponents() { 321 Container wholePanel = getContentPane(); 322 wholePanel.setLayout(null); 323 setLayout(null); 324 325 inputLabel = new JLabel(); 326 inputLabel.setText("Default Input"); 327 wholePanel.add(inputLabel); 328 329 inputSelection = new JComboBox(); 330 inputSelection.addActionListener(this); 331 inputSelection.setLocation(20, 30); 332 inputSelection.setSize(inputSelection.getPreferredSize()); 333 System.out.println("Adding inputSelection to panel"); 334 wholePanel.add(inputSelection); 335 inputIds = new ArrayList<Integer>(); 336 337 inputActivity = new ActivityLight(); 338 wholePanel.add(inputActivity); 339 340 outputLabel = new JLabel(); 341 outputLabel.setText("Default Output"); 342 wholePanel.add(outputLabel); 343 344 outputSelection = new JComboBox(); 345 outputSelection.addActionListener(this); 346 wholePanel.add(outputSelection); 347 testButton = new JButton(); 348 testButton.setText("Test"); 349 testButton.addActionListener(this); 350 wholePanel.add(testButton); 351 outputIds = new ArrayList<Integer>(); 352 353 refreshButton = new JButton(); 354 refreshButton.setText("Refresh Device Lists"); 355 System.out.println("refresh " + refreshButton.getPreferredSize()); 356 System.out.println(getLayout()); 357 refreshButton.addActionListener(this); 358 wholePanel.add(refreshButton); 359 360 updateButton = new JButton(); 361 updateButton.setText("Update Preferences"); 362 updateButton.setSize(refreshButton.getPreferredSize()); 363 updateButton.addActionListener(this); 364 wholePanel.add(updateButton); 365 366 closeButton = new JButton(); 367 closeButton.setText("Close/Release Ports"); 368 closeButton.setSize(refreshButton.getPreferredSize()); 369 closeButton.addActionListener(this); 370 wholePanel.add(closeButton); 371 372 // load the logo from the jar file (on Linux and Windows) 373 ClassLoader cldr = this.getClass().getClassLoader(); 374 ImageIcon icon; 375 URL logoURL = cldr.getResource("portmusic_logo.png"); 376 if (logoURL == null) { 377 // on Mac, load from bundle 378 icon = new ImageIcon("portmusic_logo.png"); 379 } else { 380 icon = new ImageIcon(logoURL); 381 } 382 logo = new JLabel(icon); 383 logo.setSize(logo.getPreferredSize()); 384 wholePanel.add(logo); 385 386 setVisible(true); 387 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 388 } 389 390 void loadDeviceChoices() throws JPortMidiException { 391 // initialize and load combo boxes with device descriptions 392 int n = jpm.countDevices(); 393 inputSelection.removeAllItems(); 394 inputIds.clear(); 395 outputSelection.removeAllItems(); 396 outputIds.clear(); 397 for (int i = 0; i < n; i++) { 398 String interf = jpm.getDeviceInterf(i); 399 String name = jpm.getDeviceName(i); 400 System.out.println("name " + name); 401 String selection = name + " [" + interf + "]"; 402 if (jpm.getDeviceInput(i)) { 403 inputIds.add(i); 404 inputSelection.addItem(selection); 405 } else { 406 outputIds.add(i); 407 outputSelection.addItem(selection); 408 } 409 } 410 } 411 412 void sendTestMessages() { 413 try { 414 if (!jpm.isOpenOutput()) { 415 int id = outputSelection.getSelectedIndex(); 416 if (id < 0) return; // nothing selected 417 id = (Integer) (outputIds.get(id)); 418 System.out.println("calling openOutput"); 419 jpm.openOutput(id, 10, 10); 420 } 421 jpm.midiNote(0, 67, 100); // send an A (440) 422 jpm.midiNote(0, 67, 0, jpm.timeGet() + 500); 423 } catch(JPortMidiException e) { 424 System.out.println(e); 425 } 426 } 427 } 428