001package bradleyross.music.MidiRouter; 002// import java.awt.FlowLayout; 003import java.awt.GridLayout; 004import java.awt.GridBagLayout; 005// import java.awt.GridBagLayoutInfo; 006import java.awt.GridBagConstraints; 007import java.awt.HeadlessException; 008import java.awt.Dimension; 009import java.awt.Color; 010// import java.awt.Component; 011import java.awt.event.MouseListener; 012import java.awt.event.MouseEvent; 013// import java.awt.event.ComponentListener; 014// import java.awt.event.ComponentEvent; 015import java.awt.event.ActionListener; 016import java.awt.event.ActionEvent; 017import java.io.File; 018import java.io.FileInputStream; 019import java.io.PrintWriter; 020import java.io.IOException; 021import java.io.FileNotFoundException; 022import java.io.UnsupportedEncodingException; 023// import java.nio.ByteBuffer; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Date; 027import java.util.GregorianCalendar; 028import javax.sound.midi.MidiSystem; 029import javax.sound.midi.MidiDevice; 030import javax.sound.midi.MidiUnavailableException; 031import javax.sound.midi.InvalidMidiDataException; 032import javax.sound.midi.Synthesizer; 033import javax.sound.midi.Transmitter; 034import javax.sound.midi.Sequence; 035import javax.sound.midi.Sequencer; 036import javax.sound.midi.Receiver; 037// import javax.sound.midi.MidiDeviceReceiver; 038// import javax.sound.midi.MidiDeviceTransmitter; 039import javax.sound.midi.MidiMessage; 040import javax.sound.midi.MidiChannel; 041import javax.swing.SwingUtilities; 042import javax.swing.JFrame; 043import javax.swing.JPanel; 044import javax.swing.JTextArea; 045import javax.swing.JMenuBar; 046import javax.swing.JMenu; 047import javax.swing.JMenuItem; 048import javax.swing.JFileChooser; 049import javax.swing.JOptionPane; 050import javax.swing.JScrollPane; 051// import javax.swing.JButton; 052import javax.swing.BoxLayout; 053// import javax.swing.border.Border; 054import javax.swing.BorderFactory; 055// import javax.swing.JTextField; 056import javax.swing.JLabel; 057/** 058 * Sample Java MIDI router. 059 * 060 * @author Bradley Ross 061 * 062 * <p>Does MidiSystem.getMidiDevice always return the same object given the 063 * same MidiDevice.Info - The answer is no when the MIDI device is 064 * a software synthesizer or sequencer.</p> 065 * 066 * @see FlowLayout 067 * @see GridLayout 068 * @see GridBagLayout 069 * @see BoxLayout 070 * 071 */ 072public class MidiRouter implements Runnable{ 073 /** 074 * A value of zero means no diagnostic output, while 075 * higher values create more output. 076 */ 077 protected int debugFlag = 1; 078 /** 079 * Outermost panel for the main window. 080 */ 081 protected JPanel outer; 082 /** 083 * Pane at the top of the main window that 084 * contains general information. 085 */ 086 protected JPanel top; 087 /** 088 * Pane containing a list of potential MIDI 089 * sources. 090 */ 091 protected JPanel left; 092 /** 093 * Pane containing a list of potential MIDI 094 * destinations. 095 */ 096 protected JPanel right; 097 /** 098 * Swing {@link JFrame} attached to the main window. 099 */ 100 protected JFrame frame; 101 /** 102 * Menu bar for the main window. 103 */ 104 protected JMenuBar menuBar; 105 106 107 protected String title; 108 /** 109 * MIDI file to be used as source of MIDI information 110 * (e.g.: {@link Sequencer}). 111 */ 112 protected File sourceFile; 113 /** 114 * MIDI file to be used as destination of MIDI 115 * information (e.g.: {@link Sequencer} and 116 * {@link Synthesizer}) 117 */ 118 protected File destFile; 119 protected MidiDevice.Info[] allDevices; 120 protected ArrayList<MidiDevice> sourceMidiDevices; 121 protected ArrayList<MidiDevice> destMidiDevices; 122 protected ArrayList<MidiDevice.Info> sourceDevices; 123 protected ArrayList<MidiDevice.Info> destDevices; 124 protected ArrayList<Listing> sourceItems; 125 protected ArrayList<Listing> destItems; 126 127 protected MidiMenuBar midiMenuBar = null; 128 protected File inputFile = null; 129 protected File outputFile = null; 130 protected File logFile = null; 131 protected Listing selectedSource = null; 132 protected Listing selectedDest = null; 133 protected Connection connection = new Connection(); 134 /** 135 * This acts as a {@Link Receiver} of MIDI 136 * information and displays the data in a window. 137 */ 138 protected LogReceiver logReceiver; 139 protected GregorianCalendar calendar; 140 /** 141 * Time MIDI connection was opened. 142 */ 143 protected long musicStart; 144 /** 145 * This Connection object will send information from the 146 * source object to the log window. 147 */ 148 protected Connection logConnection = new Connection(); 149 /** 150 * If true, no changes are allowed to the selections in the 151 * source and destination columns. 152 */ 153 boolean selectionLocked = false; 154 /** 155 * If true, MIDI messages from the source will be echoed to a 156 * Java Swing window. 157 */ 158 boolean useLogWindow = true; 159 /** 160 * Specifies the way in which an endpoint is specified. 161 * 162 * @author Bradley Ross 163 * 164 */ 165 protected enum specType { 166 /** 167 * Specifying a {@link Receiver} object. 168 */ 169 RECEIVER, 170 /** 171 * Specifying a {@link Transmitter} object. 172 */ 173 TRANSMITTER, 174 /** 175 * Specifying a {@link MidiDevice} object. 176 */ 177 DEVICE, 178 /** 179 * Specifying a {@link MidiDevice.Info} object. 180 */ 181 INFO } 182 public MidiRouter() { 183 logReceiver = new LogReceiver(); 184 calendar = new GregorianCalendar(); 185 } 186 /** 187 * Constructs the menu bar. 188 * 189 * @author Bradley Ross 190 * 191 */ 192 protected class MidiMenuBar implements ActionListener { 193 194 JMenuItem toggleLog = null; 195 /** 196 * This is the menu bar for the application. 197 */ 198 JMenuBar menuBar = null; 199 200 /** 201 * Constructor populates Java application menu bar. 202 * 203 * @param value JMenuBar structure belonging to {@link frame}. 204 */ 205 public MidiMenuBar(JMenuBar value) { 206 menuBar = value; 207 /* 208 * Main menu 209 */ 210 JMenu mainMenu = new JMenu("Midi Router"); 211 menuBar.add(mainMenu); 212 JMenuItem aboutRouter = new JMenuItem("About MIDI Router"); 213 mainMenu.add(aboutRouter); 214 aboutRouter.addActionListener(this); 215 aboutRouter.setActionCommand("about"); 216 mainMenu.addSeparator(); 217 JMenuItem preferences = new JMenuItem("Preferences"); 218 preferences.setActionCommand("preferences"); 219 mainMenu.add(preferences); 220 preferences.addActionListener(this); 221 mainMenu.addSeparator(); 222 JMenuItem quit = new JMenuItem("Quit"); 223 quit.setActionCommand("quit"); 224 mainMenu.add(quit); 225 quit.addActionListener(this); 226 /* 227 * File menu 228 */ 229 JMenu fileMenu = new JMenu("File"); 230 JMenuItem setInput = new JMenuItem("Choose Input File"); 231 setInput.setActionCommand("setInput"); 232 setInput.addActionListener(this); 233 JMenuItem setOutput = new JMenuItem("Choose Output File"); 234 setOutput.setActionCommand("setOutput"); 235 setOutput.addActionListener(this); 236 JMenuItem setLog = new JMenuItem("Choose Log File"); 237 setLog.setActionCommand("setLog"); 238 setLog.addActionListener(this); 239 toggleLog = new JMenuItem(); 240 if (useLogWindow) { 241 toggleLog.setText("Turn Log Window off"); 242 } else { 243 toggleLog.setText("Turn Log Window on"); 244 } 245 toggleLog.setActionCommand("toggleLog"); 246 toggleLog.addActionListener(this); 247 menuBar.add(fileMenu); 248 fileMenu.add(setInput); 249 fileMenu.add(setOutput); 250 fileMenu.add(setLog); 251 fileMenu.add(toggleLog); 252 /* Connections menu */ 253 JMenu connections = new JMenu("Connections"); 254 JMenuItem openConnection = new JMenuItem("Open"); 255 openConnection.setActionCommand("openConnection"); 256 openConnection.addActionListener(this); 257 connections.add(openConnection); 258 JMenuItem closeConnection = new JMenuItem("Close/Save"); 259 closeConnection.setActionCommand("closeConnection"); 260 closeConnection.addActionListener(this); 261 connections.add(closeConnection); 262 menuBar.add(connections); 263 } 264 /** 265 * Listener for menu bar. 266 * 267 * @param e action transmitted from clicking menu item 268 */ 269 public void actionPerformed(ActionEvent e) { 270 try { 271 String command = e.getActionCommand(); 272 if (debugFlag > 5) { 273 System.out.println(command); 274 } 275 if (command.equalsIgnoreCase("about")) { 276 System.out.println("About menu item clicked"); 277 278 } else if (command.equalsIgnoreCase("preferences")) { 279 System.out.println("Preferences menu item clicked"); 280 281 } else if (command.equalsIgnoreCase("quit")) { 282 frame.setVisible(false); 283 frame.dispose(); 284 } else if (command.equalsIgnoreCase("setInput")) { 285 if (debugFlag > 0) { 286 System.out.println("Choose Input File menu item clicked"); 287 } 288 JFileChooser chooser = new JFileChooser(); 289 int returnVal = chooser.showOpenDialog(frame); 290 if (returnVal == JFileChooser.APPROVE_OPTION) { 291 inputFile = chooser.getSelectedFile(); 292 } 293 if (inputFile != null) { 294 String text = "Input file is " + inputFile.getName(); 295 System.out.println(text); 296 } else { 297 showPopup("Problem", "No input file selected"); 298 } 299 } else if (command.equalsIgnoreCase("setOutput")) { 300 if (debugFlag > 0) { 301 System.out.println("Choose Output File menu item clicked"); 302 } 303 JFileChooser chooser = new JFileChooser(); 304 int returnVal = chooser.showSaveDialog(frame); 305 if (returnVal == JFileChooser.APPROVE_OPTION) { 306 outputFile = chooser.getSelectedFile(); 307 } 308 if (outputFile != null) { 309 String text = "Output File is " + outputFile.getName(); 310 System.out.println(text); 311 } else { 312 showPopup("Problem", "No output file selected"); 313 } 314 315 } else if (command.equalsIgnoreCase("setLog")) { 316 if (debugFlag > 0) { 317 System.out.println("Choose Log File menu item clicked"); 318 } 319 JFileChooser chooser = new JFileChooser(); 320 int returnVal = chooser.showSaveDialog(frame); 321 if (returnVal == JFileChooser.APPROVE_OPTION) { 322 logFile = chooser.getSelectedFile(); 323 } 324 if (logFile != null) { 325 String text = "Log File is " + logFile.getName(); 326 System.out.println(text); 327 } else { 328 showPopup("Problem", "No log file selected"); 329 } 330 } else if (command.equalsIgnoreCase("openConnection")) { 331 selectionLocked = true; 332 if (debugFlag > 0) { 333 System.out.println("Open Connection menu item clicked"); 334 if (selectedSource != null && selectedSource.isSequencer) { 335 System.out.println("Source is sequencer"); 336 } 337 if (selectedDest != null && selectedDest.isSequencer) { 338 System.out.println("Destination is sequencer"); 339 } 340 if (selectedDest != null && selectedDest.isSynthesizer) { 341 System.out.println("Destination is synthesizer"); 342 } 343 } 344 if (selectedSource == null) { 345 String text = "Source MIDI not selected - Select a source"; 346 showPopup("Problem", text); 347 return; 348 } 349 350 if (selectedDest == null) { 351 String text = "Destination MIDI not selected - Select a destination"; 352 showPopup("Problem", text); 353 return; 354 } 355 if (selectedSource.isSequencer && 356 (inputFile == null || !inputFile.exists())) { 357 String text = "Must select input file for sequencer as input" + 358 System.lineSeparator() + 359 "Use File -> Choose Input File menu item to select file"; 360 showPopup("Problem", text); 361 return; 362 } 363 364 if (selectedDest.isSequencer && outputFile == null) { 365 String text = "Must select output file for sequencer as output" + 366 System.lineSeparator() + 367 "Use File -> Choose Output File menu item to select file"; 368 showPopup("Problem", text); 369 return; 370 } 371 if (useLogWindow) { 372 StringBuffer text = 373 new StringBuffer("Starting setup of connection to log window"); 374 System.out.println(text.toString()); 375 logConnection.setSourceDevice(selectedSource.getMidiDevice()); 376 logConnection.setDestReceiver(logReceiver); 377 logConnection.openConnection(); 378 if (debugFlag > 0) { 379 System.out.println("Log connection opened"); 380 } 381 } 382 MidiDevice sourceMidiDevice = selectedSource.getMidiDevice(); 383 connection.setSourceDevice(sourceMidiDevice); 384 if (!sourceMidiDevice.isOpen()) { 385 sourceMidiDevice.open(); 386 } 387 MidiDevice destMidiDevice = selectedDest.getMidiDevice(); 388 connection.setDestDevice(destMidiDevice); 389 if (!destMidiDevice.isOpen()) { 390 destMidiDevice.open(); 391 } 392 // connection.setSourceInfo(selectedSource.getMidiDeviceInfo()); 393 // connection.setDestInfo(selectedDest.getMidiDeviceInfo()); 394 if (sourceFile != null) { 395 connection.setSourceFile(sourceFile); 396 } 397 if (destFile != null) { 398 connection.setDestFile(destFile); 399 } 400 connection.openConnection(); 401 if (debugFlag > 0) { 402 System.out.println("Data connection opened"); 403 MidiDevice source = selectedSource.getMidiDevice(); 404 StringBuffer text2 = new StringBuffer("There are " + 405 Integer.toString(source.getTransmitters().size()) + 406 " transmitters for source"); 407 System.out.println(text2.toString()); 408 } 409 if (selectedSource.isSequencer) { 410 if (debugFlag > 0) { 411 System.out.println("About to start playing MIDI file"); 412 } 413 Sequence sequence2 = MidiSystem.getSequence(new FileInputStream(inputFile)); 414 Sequencer sequencer = (Sequencer) selectedSource.getMidiDevice(); 415 sequencer.setSequence(sequence2); 416 sequencer.start(); 417 } 418 } else if (command.equalsIgnoreCase("closeConnection")) { 419 selectionLocked = false; 420 connection.closeConnection(); 421 System.out.println("Data connection closed"); 422 if (useLogWindow) { 423 logConnection.closeConnection(); 424 System.out.println("Log connection closed"); 425 } 426 selectedSource.getMidiDevice().close(); 427 selectedDest.getMidiDevice().close(); 428 } else if (command.equalsIgnoreCase("toggleLog")) { 429 if (selectionLocked) { return; } 430 if (useLogWindow) { 431 useLogWindow = false; 432 toggleLog.setText("Turn Log Window on"); 433 } else { 434 useLogWindow = true; 435 toggleLog.setText("Turn Log Window off"); 436 } 437 } 438 } catch (HeadlessException e2) { 439 e2.printStackTrace(); 440 String text = "Display not found"; 441 showPopup("Error", text); 442 } catch (MidiUnavailableException ex) { 443 ex.printStackTrace(); 444 String text = "MidiUnavailableException Error"; 445 showPopup("Error", text); 446 } catch (InvalidMidiDataException ex) { 447 ex.printStackTrace(); 448 showPopup("Error", "InvalidMidiDataException"); 449 } catch (IOException ex) { 450 ex.printStackTrace(); 451 showPopup("Error", "IOException error"); 452 } 453 } 454 } 455 /** 456 * Represents connection between MIDI devices. 457 * 458 * @author Bradley Ross 459 * 460 */ 461 protected class Connection { 462 /** 463 * Type of specification for the source of the connection. 464 */ 465 protected specType sourceType; 466 protected MidiDevice.Info sourceInfo = null; 467 protected MidiDevice sourceDevice = null; 468 protected Transmitter sourceTransmitter = null; 469 protected boolean sourceIsSequencer = false; 470 protected boolean sourceIsSynthesizer = false; 471 protected File sourceFile = null; 472 protected Receiver receiver = null; 473 protected Transmitter transmitter = null; 474 /** 475 * Provide information on the source device. 476 * @param value MidiDevice used for input to connection 477 */ 478 public void setSourceDevice(MidiDevice value) throws MidiUnavailableException { 479 if (debugFlag > 0) { 480 System.out.println("Starting Connection.setSourceDevice"); 481 } 482 sourceType = specType.DEVICE; 483 sourceDevice = value; 484 sourceInfo = value.getDeviceInfo(); 485 if (Sequencer.class.isAssignableFrom(sourceDevice.getClass())) { 486 sourceIsSequencer = true; 487 } 488 if (Synthesizer.class.isAssignableFrom(sourceDevice.getClass())) { 489 sourceIsSynthesizer = true; 490 } 491 } 492 public MidiDevice getSourceDevice() { 493 return sourceDevice; 494 } 495 /** 496 * Specify device providing input to connection. 497 * <p>This method is deprecated because there is a problem 498 * with the Java code when dealing with software synthesizers 499 * and sequencers. {@link MidiDevice.getDeviceInfo()} and 500 * {@link MidiSystem.getMidiDevice(MidiDevice.Info)} are not 501 * inverse functions in this situation.</p> 502 * 503 * @param input device 504 */ 505 @Deprecated 506 public void setSourceInfo(MidiDevice.Info value) { 507 sourceInfo = value; 508 sourceType = specType.INFO; 509 try { 510 sourceDevice = MidiSystem.getMidiDevice(sourceInfo); 511 if (Sequencer.class.isAssignableFrom(sourceDevice.getClass())) { 512 sourceIsSequencer = true; 513 } 514 if (Synthesizer.class.isAssignableFrom(sourceDevice.getClass())) { 515 sourceIsSynthesizer = true; 516 } 517 } catch (MidiUnavailableException ex) { 518 ex.printStackTrace(); 519 } 520 } 521 /** 522 * Specifies the {@link Transmitter} object that is the 523 * source of the MIDI data. 524 * @param value 525 */ 526 public void setSourceTransmitter(Transmitter value) { 527 if (debugFlag > 0) { 528 System.out.println("Starting Connection.setSourceTransmitter"); 529 } 530 if (value == null) { 531 throw new NullPointerException("Input value is null"); 532 } 533 sourceType = specType.TRANSMITTER; 534 transmitter = value; 535 sourceTransmitter = value; 536 } 537 public MidiDevice.Info getSourceInfo() { 538 return sourceInfo; 539 } 540 public void setSourceFile(File value) { 541 sourceFile = value; 542 } 543 public File getSourceFile() { 544 return sourceFile; 545 } 546 /** 547 * Type of specification for the destination of the connection. 548 */ 549 protected specType destType; 550 protected MidiDevice.Info destInfo = null; 551 protected MidiDevice destDevice = null; 552 protected Receiver destReceiver = null; 553 protected boolean destIsSequencer = false; 554 protected boolean destIsSynthesizer = false; 555 protected File destFile = null; 556 protected boolean active = false; 557 public void setDestDevice(MidiDevice value) { 558 if (value == null) { 559 throw new NullPointerException("Input value is null"); 560 } 561 if (debugFlag > 0) { 562 System.out.println("Starting Connection.setDestDevice"); 563 } 564 destDevice = value; 565 destType = specType.DEVICE; 566 destInfo = value.getDeviceInfo(); 567 if (Sequencer.class.isAssignableFrom(destDevice.getClass())) { 568 destIsSequencer = true; 569 } 570 if (Synthesizer.class.isAssignableFrom(destDevice.getClass())) { 571 destIsSynthesizer = true; 572 ((Synthesizer) destDevice).getDefaultSoundbank(); 573 } 574 } 575 /** 576 * Specify MIDI device to receive information from connection. 577 * 578 * <p>This method is deprecated because there is a problem 579 * with the Java code when dealing with software synthesizers 580 * and sequencers. {@link MidiDevice.getDeviceInfo()} and 581 * {@link MidiSystem.getMidiDevice(MidiDevice.Info)} are not 582 * inverse functions in this situation.</p> 583 * 584 * @param value device receiving information from connection 585 */ 586 @Deprecated 587 public void setDestInfo(MidiDevice.Info value) { 588 destType = specType.INFO; 589 destInfo = value; 590 try { 591 MidiDevice destDevice = MidiSystem.getMidiDevice(destInfo); 592 if (Sequencer.class.isAssignableFrom(destDevice.getClass())) { 593 destIsSequencer = true; 594 } 595 if (Synthesizer.class.isAssignableFrom(destDevice.getClass())) { 596 destIsSynthesizer = true; 597 } 598 } catch (MidiUnavailableException ex) { 599 ex.printStackTrace(); 600 } 601 } 602 public void setDestReceiver(Receiver value) { 603 if (debugFlag > 0) { 604 StringBuffer text3 = new StringBuffer(); 605 text3.append("Starting Connection.setDestReceiver"); 606 if (value == null) { 607 text3.append(System.lineSeparator() + "Input parameter is null"); 608 } 609 System.out.println(text3.toString()); 610 } 611 if (value == null) { 612 throw new NullPointerException("Input value is null"); 613 } 614 destType = specType.RECEIVER; 615 receiver = value; 616 destReceiver = value; 617 } 618 public Receiver getDestReceiver() { 619 return destReceiver; 620 } 621 public void setDestFile(File value) { 622 destFile = value; 623 624 } 625 public File getDestFile() { 626 return destFile; 627 } 628 public void openConnection() 629 throws MidiUnavailableException, IOException, InvalidMidiDataException { 630 System.out.println("Running Connection.openConnection"); 631 632 musicStart = (new Date()).getTime(); 633 if (destType == specType.DEVICE || destType == specType.INFO) { 634 if (!destDevice.isOpen()) { 635 System.out.println(" Open destination device"); 636 destDevice.open(); 637 } else { 638 System.out.println(" Destination device was open"); 639 } 640 receiver = destDevice.getReceiver(); 641 destReceiver = receiver; 642 if (Synthesizer.class.isAssignableFrom(destDevice.getClass())) { 643 Synthesizer synth = (Synthesizer) destDevice; 644 MidiChannel[] channels = synth.getChannels(); 645 /* 646 * Set a starting value for the instrument 647 * 648 */ 649 System.out.println(" Set default instrument for dest."); 650 for (MidiChannel item : channels) { 651 item.programChange(0, 45); 652 } 653 } 654 } else if (destType == specType.RECEIVER) { 655 656 657 } else { 658 System.out.println("Receiver not set"); 659 } 660 if (sourceType == specType.DEVICE || sourceType == specType.INFO) { 661 transmitter = sourceTransmitter; 662 if (!sourceDevice.isOpen()) { 663 System.out.println(" Opening source device"); 664 sourceDevice.open(); 665 } else { 666 System.out.println(" Source device was open"); 667 } 668 669 transmitter = sourceDevice.getTransmitter(); 670 sourceTransmitter = transmitter; 671 } else if (sourceType == specType.TRANSMITTER) { 672 673 } else { 674 System.out.println("Transmitter not set"); 675 } 676 transmitter.setReceiver(receiver); 677 active = true; 678 } 679 public void closeConnection() throws MidiUnavailableException { 680 System.out.println("Calling Connection.closeConnection"); 681 if (sourceType != specType.TRANSMITTER) { 682 transmitter.close(); 683 int xmtrCount = sourceDevice.getTransmitters().size(); 684 System.out.println(Integer.toString(xmtrCount) + " transmitters for source"); 685 } 686 if (destType != specType.RECEIVER) { 687 receiver.close(); 688 int rcvrCount = destDevice.getReceivers().size(); 689 System.out.println(Integer.toString(rcvrCount) + " receivers for dest."); 690 } 691 active = false; 692 693 } 694 } 695 /** 696 * Opens the main window for the application. 697 * 698 * @author Bradley Ross 699 * 700 */ 701 protected class Outer implements Runnable { 702 public void run() { 703 frame = new JFrame(); 704 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 705 // frame.addComponentListener(sizeListener); 706 menuBar = new JMenuBar(); 707 frame.setJMenuBar(menuBar); 708 midiMenuBar = new MidiMenuBar(menuBar); 709 title = "Test for MIDI Application"; 710 frame.setTitle(title); 711 // menuBar.add(fileMenu); 712 GridBagLayout layout = new GridBagLayout(); 713 frame.setLayout(layout); 714 GridBagConstraints upper = new GridBagConstraints(); 715 GridBagConstraints lower = new GridBagConstraints(); 716 upper.gridwidth = GridBagConstraints.REMAINDER; 717 upper.gridheight = 1; 718 upper.fill = GridBagConstraints.HORIZONTAL; 719 upper.weightx = 1.0d; 720 upper.weighty = 0.0d; 721 lower.gridwidth = 1; 722 lower.gridheight = 1; 723 lower.fill = GridBagConstraints.BOTH; 724 lower.weightx = 1.0d; 725 lower.weighty = 1.0d; 726 top = new Top(); 727 layout.setConstraints(top, upper); 728 top.doLayout(); 729 frame.add(top); 730 left = new Left(); 731 layout.setConstraints(left, lower); 732 left.doLayout(); 733 frame.add(left); 734 right = new Right(); 735 layout.setConstraints(right, lower); 736 right.doLayout(); 737 frame.add(right); 738 frame.setMinimumSize(new Dimension(800,750)); 739 //frame.pack(); 740 frame.setVisible(true); 741 } 742 } 743 /** 744 * This represents a cell in the source or destinations column. 745 * 746 * <p>The MIDI devices represented by these cells must be 747 * defined using a {@link MidiDevice} object. 748 * 749 * @author Bradley Ross 750 * 751 */ 752 @SuppressWarnings("serial") 753 protected abstract class Listing extends JPanel implements MouseListener { 754 protected boolean selected = false; 755 protected JTextArea contents; 756 protected MidiDevice device = null; 757 protected MidiDevice.Info info = null; 758 // protected Listing selectedValue; 759 protected List<Listing> parentList; 760 protected boolean isSynthesizer = false; 761 protected boolean isSequencer = false; 762 /** 763 * Specify the MIDI device using a 764 * {@link MidiDevice object}. 765 * 766 * @param device identifies MIDI device 767 */ 768 public Listing(MidiDevice device) { 769 setMidiDevice(device); 770 setMidiDeviceInfo(device.getDeviceInfo()); 771 createComponent(); 772 } 773 /** 774 * Specify the MIDI device using a {@link MidiDevice.Info} object. 775 * 776 * <p>This is deprecated since 777 * {@link MidiSystem.getMidiDevice(MidiDevice.Info)} can't 778 * be relied upon to identify a single device, especially when 779 * dealing with synthesizers and sequencers. The problem 780 * may be related to software MIDI devices.</p> 781 * 782 * @param info identifies MIDI device 783 */ 784 @Deprecated 785 public Listing(MidiDevice.Info info) { 786 setMidiDeviceInfo(info); 787 try { 788 device = MidiSystem.getMidiDevice(info); 789 } catch (MidiUnavailableException ex) { 790 ex.printStackTrace(); 791 } 792 createComponent(); 793 } 794 /** 795 * Creates the {@link JTextArea} object that contains 796 * the content of the cell. 797 */ 798 protected void createComponent() { 799 StringBuffer text = new StringBuffer(); 800 text.append(info.getName() + " : " + info.getVendor() + 801 " : " + info.getVersion() + System.lineSeparator()); 802 text.append(info.getDescription() + System.lineSeparator()); 803 if (Synthesizer.class.isAssignableFrom(device.getClass())) { 804 text.append("Synthesizer" + System.lineSeparator()); 805 isSynthesizer = true; 806 } 807 if (Sequencer.class.isAssignableFrom(device.getClass())) { 808 text.append("Sequencer" + System.lineSeparator()); 809 isSequencer = true; 810 } 811 text.append("Class: " + device.getClass() + System.lineSeparator()); 812 BoxLayout layout = new BoxLayout(this, BoxLayout.X_AXIS); 813 this.setLayout(layout); 814 contents = new JTextArea(text.toString()); 815 contents.setEditable(false); 816 contents.addMouseListener(this); 817 contents.setBackground(Color.WHITE); 818 this.add(contents); 819 setBorder(BorderFactory.createLineBorder(Color.CYAN, 3)); 820 } 821 public MidiDevice getMidiDevice() { 822 return device; 823 } 824 protected void setMidiDevice(MidiDevice value) { 825 device = value; 826 } 827 public MidiDevice.Info getMidiDeviceInfo() { 828 return info; 829 } 830 protected void setMidiDeviceInfo(MidiDevice.Info value) { 831 info = value; 832 } 833 /** 834 * Handles mouse clicks within the cell. 835 */ 836 public void mouseClicked(MouseEvent event) { 837 if (selectionLocked) { return; } 838 if (selected) { 839 selected = false; 840 setSelectedValue(null); 841 contents.setBackground(Color.WHITE); 842 } else { 843 for (Listing item : parentList) { 844 item.clear(); 845 } 846 selected = true; 847 setSelectedValue(this); 848 contents.setBackground(Color.LIGHT_GRAY); 849 } 850 if (debugFlag > 0) { 851 System.out.println("Cell in list of devices clicked"); 852 StringBuffer diag1 = new StringBuffer(); 853 diag1.append("Selected source is "); 854 if (selectedSource == null) { 855 diag1.append("null"); 856 } else { 857 diag1.append("not null"); 858 } 859 diag1.append(System.lineSeparator()); 860 diag1.append("Selected destination is "); 861 if (selectedDest == null) { 862 diag1.append("null"); 863 } else { 864 diag1.append("not null"); 865 } 866 System.out.println(diag1.toString()); 867 } 868 } 869 public void mousePressed(MouseEvent e) { ; } 870 public void mouseReleased(MouseEvent e) { ; } 871 public void mouseEntered(MouseEvent e) { ; } 872 public void mouseExited(MouseEvent e) { ; } 873 protected void clear() { 874 selected = false; 875 contents.setBackground(Color.WHITE); 876 } 877 /** 878 * Depending on the subclass, this method will set either 879 * {@link selectedSource} or {@link selectedDest}. 880 * @param value logical value to be used 881 */ 882 protected abstract void setSelectedValue(Listing value); 883 } 884 /** 885 * Creates a panel that contains information on a potential destination. 886 * 887 * @author Bradley Ross 888 * 889 */ 890 @SuppressWarnings("serial") 891 protected class DestListing extends Listing { 892 public DestListing(MidiDevice.Info info) { 893 super( info); 894 parentList = destItems; 895 } 896 public DestListing(MidiDevice device) { 897 super(device); 898 parentList = destItems; 899 } 900 protected void setSelectedValue(Listing value) { 901 selectedDest = value; 902 } 903 } 904 /** 905 * Creates a panel that contains information on a potential 906 * source. 907 * @author Bradley Ross 908 * 909 */ 910 @SuppressWarnings("serial") 911 protected class SourceListing extends Listing { 912 913 public SourceListing(MidiDevice.Info info) { 914 super( info); 915 parentList = sourceItems; 916 } 917 public SourceListing(MidiDevice device) { 918 super(device); 919 parentList = sourceItems; 920 } 921 protected void setSelectedValue(Listing value) { 922 selectedSource = value; 923 } 924 } 925 /** 926 * Create a widow displaying text and optionally save the text to a file. 927 * 928 * @author Bradley Ross 929 * 930 */ 931 protected class DisplayFrame { 932 JFrame displayFrame = null; 933 boolean isOpen = false; 934 JTextArea textArea = null; 935 JMenuBar menuBar = null; 936 MenuHandler menuHandler = null; 937 protected class MenuHandler implements ActionListener { 938 /** 939 * The constructor creates the items for the menu bar. 940 * 941 * @param menuBar JMenuBar object used for the frame 942 */ 943 public MenuHandler(JMenuBar menuBar) { 944 JMenu fileMenu = new JMenu("File"); 945 JMenuItem saveContents = new JMenuItem("Save"); 946 saveContents.setActionCommand("save"); 947 saveContents.addActionListener(this); 948 menuBar.add(fileMenu); 949 fileMenu.add(saveContents); 950 } 951 952 /** 953 * Actions to be taken when menu item is clicked. 954 * 955 * @param ev Event created by clicking on menu item 956 */ 957 public void actionPerformed(ActionEvent ev) { 958 String command = ev.getActionCommand(); 959 if (command.equalsIgnoreCase("save")) { 960 try { 961 File saveFile = null; 962 JFileChooser chooser = new JFileChooser(); 963 int returnVal = chooser.showSaveDialog(frame); 964 if (returnVal == JFileChooser.APPROVE_OPTION) { 965 saveFile = chooser.getSelectedFile(); 966 saveFile.createNewFile(); 967 } 968 if (saveFile != null) { 969 String text = "Saving display to " + saveFile.getName(); 970 System.out.println(text); 971 PrintWriter writer = new PrintWriter(saveFile, "UTF-8"); 972 textArea.write(writer); 973 writer.close(); 974 } else { 975 showPopup("Problem", "No save file selected"); 976 } 977 } catch (FileNotFoundException ex) { 978 String text = "FileNotFoundException encountered " + 979 "while saving contents of window to file"; 980 showPopup("Error", text); 981 ex.printStackTrace(); 982 } catch (UnsupportedEncodingException ex) { 983 String text = "UnsupportedEncodingException encountered " + 984 "while saving contents of window to file"; 985 showPopup("Error", text); 986 ex.printStackTrace(); 987 } catch (IOException ex) { 988 String text = "IOException encountered " + 989 "while saving contents of window to file"; 990 showPopup("Error", text); 991 ex.printStackTrace(); 992 } 993 } 994 } 995 } 996 /** 997 * Open the window that displays information. 998 */ 999 protected void open() { 1000 if (isOpen) { return; } 1001 isOpen = true; 1002 displayFrame = new JFrame(); 1003 displayFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 1004 menuBar = new JMenuBar(); 1005 menuHandler = new MenuHandler(menuBar); 1006 displayFrame.setJMenuBar(menuBar); 1007 displayFrame.setSize(new Dimension(600, 600)); 1008 GridBagLayout layout1 = new GridBagLayout(); 1009 GridBagConstraints c = new GridBagConstraints(); 1010 displayFrame.setLayout(layout1); 1011 c.fill = GridBagConstraints.BOTH; 1012 c.weightx = 1.0d; 1013 c.weighty = 1.0d; 1014 textArea = new JTextArea(); 1015 textArea.setColumns(80); 1016 textArea.setRows(100); 1017 textArea.setLineWrap(true); 1018 textArea.setWrapStyleWord(true); 1019 textArea.setBackground(new Color(200, 255, 255)); 1020 JScrollPane scrollPane = new JScrollPane(textArea); 1021 layout1.setConstraints(scrollPane, c); 1022 displayFrame.add(scrollPane); 1023 displayFrame.setVisible(true); 1024 } 1025 /** 1026 * Close the window for the display. 1027 */ 1028 protected void close() { 1029 if (!isOpen) { return; } 1030 isOpen = false; 1031 displayFrame.setVisible(false); 1032 displayFrame.dispose(); 1033 } 1034 public JFrame getJFrame() { 1035 return frame; 1036 } 1037 /** 1038 * Send a message to the display. 1039 * @param text message to be displayed 1040 */ 1041 protected void write(String text) { 1042 if (!isOpen) { return; } 1043 textArea.append(text + System.lineSeparator()); 1044 } 1045 } 1046 /** 1047 * Displays MIDI information in a Java Swing window. 1048 * @author Bradley Ross 1049 * 1050 */ 1051 protected class LogReceiver implements Receiver { 1052 DisplayFrame display = null; 1053 boolean isOpen = false; 1054 public LogReceiver() { 1055 display = new DisplayFrame(); 1056 display.open(); 1057 isOpen = true; 1058 } 1059 1060 /** 1061 * Used by MIDI transmitter to send message to this receiver. 1062 * 1063 * <p><a href="https://www.midi.org/specifications/item/table-1-summary-of-midi-message" 1064 * target="_blank"> 1065 * https://www.midi.org/specifications/item/table-1-summary-of-midi-message</a> 1066 * List of MIDI codes. 1067 * </p> 1068 * @param message string of bytes containing message 1069 * @param timeStamp time generated by transmitting device 1070 */ 1071 public void send(MidiMessage message, long timeStamp) { 1072 StringBuffer build = new StringBuffer(); 1073 int length = message.getLength(); 1074 int status = message.getStatus(); 1075 byte[] data = message.getMessage(); 1076 if (timeStamp > 0) { 1077 String seconds = Long.toString(timeStamp/1000000l); 1078 String micros = String.format("%06d", timeStamp % 1000000l); 1079 build.append(" " + seconds + "." + micros + " "); 1080 } else { 1081 long time = (new Date()).getTime() - musicStart; 1082 String seconds = String.format("%6d", time / 1000l); 1083 String millis = String.format("%03d", time % 1000l); 1084 build.append(" " + seconds + "." + millis + " "); 1085 } 1086 build.append("Status: " + String.format("%02x", status)); 1087 build.append(" Length: " + Integer.toString(length) + " -- "); 1088 for (int i = 0; i < data.length; i++) { 1089 build.append(String.format("%02x", data[i])); 1090 if (i > 10) { 1091 build.append(" ..."); 1092 break; 1093 } 1094 build.append(" "); 1095 } 1096 display.write(build.toString()); 1097 } 1098 public void close() { 1099 System.out.println("Calling LogReceiver.close()"); 1100 } 1101 1102 } 1103 /** 1104 * Creates top panel of display. 1105 * @author Bradley Ross 1106 * 1107 */ 1108 @SuppressWarnings("serial") 1109 protected class Top extends JPanel { 1110 public Top() { 1111 BoxLayout layout = new BoxLayout(this, BoxLayout.Y_AXIS); 1112 this.setLayout(layout); 1113 JTextArea textBlock = new JTextArea(); 1114 textBlock.setEditable(false); 1115 textBlock.append("The purpose of this application is to " + 1116 "route MIDI information between devices" + 1117 System.lineSeparator()); 1118 this.add(textBlock); 1119 setBorder(BorderFactory.createLineBorder(Color.red, 3)); 1120 } 1121 } 1122 /** 1123 * Creates the panel at the lower left corner of the window which 1124 * contains the list of possible sources. 1125 * @author Bradley Ross 1126 * 1127 */ 1128 @SuppressWarnings("serial") 1129 protected class Left extends JPanel { 1130 protected BoxLayout layout; 1131 protected JLabel titleField = new JLabel("Sources"); 1132 public Left() { 1133 rebuild(); 1134 } 1135 protected void rebuild() { 1136 sourceItems = new ArrayList<Listing>(); 1137 layout = new BoxLayout(this, BoxLayout.Y_AXIS); 1138 this.setLayout(layout); 1139 setBorder(BorderFactory.createLineBorder(Color.green, 3)); 1140 this.add(titleField); 1141 for (MidiDevice.Info item : sourceDevices) { 1142 SourceListing block = new SourceListing(item); 1143 this.add((Listing) block); 1144 sourceItems.add((Listing) block); 1145 } 1146 } 1147 } 1148 /** 1149 * Creates the panel at the lower right hand corner of the 1150 * window which contains information for destination devices. 1151 * 1152 * @author Bradley Ross 1153 * 1154 */ 1155 @SuppressWarnings("serial") 1156 protected class Right extends JPanel { 1157 protected BoxLayout layout; 1158 protected JLabel titleField = new JLabel("Destinations"); 1159 public Right() { 1160 rebuild(); 1161 } 1162 protected void rebuild() { 1163 destItems = new ArrayList<Listing>(); 1164 layout = new BoxLayout(this, BoxLayout.Y_AXIS); 1165 this.setLayout(layout); 1166 setBorder(BorderFactory.createLineBorder(Color.BLACK, 3)); 1167 this.add(titleField); 1168 for (MidiDevice.Info item : destDevices) { 1169 DestListing block = new DestListing(item); 1170 this.add((Listing) block); 1171 destItems.add((Listing) block); 1172 } 1173 } 1174 } 1175 /** 1176 * Display a message in a popup window. 1177 * 1178 * @param message message to appear in popup window 1179 * @throws HeadlessException 1180 */ 1181 protected void showPopup(String message) throws HeadlessException { 1182 showPopup("Problem Detected", message); 1183 } 1184 /** 1185 * Display a message in a popup window. 1186 * 1187 * @param title text to be displayed on title of popup window 1188 * @param message message to appear in popup window 1189 * @throws HeadlessException 1190 */ 1191 protected void showPopup(String title, String message) throws HeadlessException{ 1192 JOptionPane.showMessageDialog(frame, message, title, JOptionPane.INFORMATION_MESSAGE); 1193 } 1194 /** 1195 * Constructs the lists of potential source and destination devices. 1196 */ 1197 protected void buildLists() { 1198 allDevices = MidiSystem.getMidiDeviceInfo(); 1199 destDevices = new ArrayList<MidiDevice.Info>(); 1200 sourceDevices = new ArrayList<MidiDevice.Info>(); 1201 for (MidiDevice.Info item : allDevices) { 1202 MidiDevice device; 1203 try { 1204 device = MidiSystem.getMidiDevice(item); 1205 1206 if (device.getMaxReceivers() != 0) { 1207 destDevices.add(item); 1208 } 1209 if (device.getMaxTransmitters() != 0) { 1210 sourceDevices.add(item); 1211 } 1212 if (device.getMaxReceivers() == 0 && 1213 device.getMaxTransmitters() == 0) { 1214 System.out.println(item.getName()); 1215 } 1216 } catch (MidiUnavailableException e) { 1217 e.printStackTrace(); 1218 } 1219 } 1220 } 1221 /** 1222 * Starts the Swing graphics operations. 1223 */ 1224 public void run() { 1225 buildLists(); 1226 /* Start Swing graphics */ 1227 try { 1228 Outer instance = new Outer(); 1229 SwingUtilities.invokeLater(instance); 1230 } catch (Exception e) { 1231 e.printStackTrace(); 1232 } 1233 } 1234 /** 1235 * This is a test of the {@link DisplayFrame} class. 1236 */ 1237 protected void testDisplayFrame() { 1238 DisplayFrame display = new DisplayFrame(); 1239 display.open(); 1240 for (int i = 0; i <= 100; i++) { 1241 display.write(Integer.toString(i) + " This is a test of the system"); 1242 } 1243 } 1244 /** 1245 * Convenience method for tests of log software. 1246 * @param values items used to build Midi message 1247 * @return message 1248 */ 1249 protected byte[] buildMessage2(int... values) { 1250 return buildMessage(values); 1251 } 1252 /** 1253 * Convenience method for tests of log software. 1254 * @param data items used to build Midi message 1255 * @return message 1256 */ 1257 protected byte[] buildMessage(int[] data) { 1258 byte[] buffer = new byte[data.length]; 1259 for (int i = 0; i < data.length; i++) { 1260 if (data[i] > 255) { 1261 buffer[i] = (byte)( data[i] - 256); 1262 } else { 1263 buffer[i] = (byte) data[i]; 1264 } 1265 } 1266 return buffer; 1267 } 1268 /** 1269 * Subclass of MidiMessage used for testing. 1270 * @author Bradley Ross 1271 * 1272 * <p>The constructor for MidiMessage is protected, which 1273 * makes it difficult to use in test cases.</p> 1274 * 1275 */ 1276 protected class TestMidiMessage extends MidiMessage { 1277 protected byte[] inter; 1278 public TestMidiMessage(byte[] data) { 1279 super(data); 1280 inter = data; 1281 } 1282 public TestMidiMessage clone() { 1283 return new TestMidiMessage(inter); 1284 } 1285 } 1286 /** 1287 * This is a test of the {@link LogReceiver} class. 1288 * 1289 * <p>Must use subclass of MidiMessage.</p> 1290 */ 1291 protected void testLogReceiver() { 1292 LogReceiver receiver = new LogReceiver(); 1293 byte[] message = buildMessage2(0, 255, 128, 17); 1294 TestMidiMessage testObject = new TestMidiMessage(message); 1295 receiver.send(testObject, 1000000l); 1296 for (int i = 0; i < message.length; i++) { 1297 System.out.print(String.format("%O2x ", message[i])); 1298 } 1299 System.out.println(); 1300 receiver.close(); 1301 } 1302 /** 1303 * Test driver. 1304 * 1305 * @param args first argument can optionally run test cases 1306 */ 1307 public static void main(String[] args) { 1308 MidiRouter instance = new MidiRouter(); 1309 if (args.length == 0) { 1310 instance.run(); 1311 return; 1312 } 1313 if (args[0].equalsIgnoreCase("display")) { 1314 instance.testDisplayFrame(); 1315 } 1316 if (args[0].equalsIgnoreCase("log")) { 1317 instance.testLogReceiver(); 1318 } 1319 } 1320}