/Users/lyon/j4p/src/sound/soundDemo/MidiSynth.java
|
1 package sound.soundDemo;
2
3 /*
4 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
5 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6 */
7
8
9 import javax.sound.midi.*;
10 import javax.swing.*;
11 import javax.swing.border.*;
12 import javax.swing.event.*;
13 import javax.swing.table.AbstractTableModel;
14 import javax.swing.table.TableColumn;
15 import javax.swing.table.TableModel;
16 import java.awt.*;
17 import java.awt.event.*;
18 import java.io.File;
19 import java.io.IOException;
20 import java.util.Vector;
21
22 /**
23 * Illustrates general MIDI melody instruments and MIDI controllers.
24 *
25 * @version @(#)MidiSynth.java 1.16 02/02/06
26 * @author Brian Lichtenwalter
27 */
28 public class MidiSynth extends JPanel implements ControlContext {
29
30 final int PROGRAM = 192;
31 final int NOTEON = 144;
32 final int NOTEOFF = 128;
33 final int SUSTAIN = 64;
34 final int REVERB = 91;
35 final int ON = 0, OFF = 1;
36 final Color jfcBlue = new Color(204, 204, 255);
37 final Color pink = new Color(255, 175, 175);
38 Sequencer sequencer;
39 Sequence sequence;
40 Synthesizer synthesizer;
41 Instrument instruments[];
42 ChannelData channels[];
43 ChannelData cc; // current channel
44 JCheckBox mouseOverCB = new JCheckBox("mouseOver", true);
45 JSlider veloS, presS, bendS, revbS;
46 JCheckBox soloCB, monoCB, muteCB, sustCB;
47 Vector keys = new Vector();
48 Vector whiteKeys = new Vector();
49 JTable table;
50 OuterPiano piano;
51 boolean record;
52 Track track;
53 long startTime;
54 RecordFrame recordFrame;
55 Controls controls;
56
57
58
59 public MidiSynth() {
60 setLayout(new BorderLayout());
61
62 JPanel p = new JPanel();
63 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
64 EmptyBorder eb = new EmptyBorder(5, 5, 5, 5);
65 BevelBorder bb = new BevelBorder(BevelBorder.LOWERED);
66 CompoundBorder cb = new CompoundBorder(eb, bb);
67 p.setBorder(new CompoundBorder(cb, eb));
68 JPanel pp = new JPanel(new BorderLayout());
69 pp.setBorder(new EmptyBorder(10, 20, 10, 5));
70 pp.add(piano = new OuterPiano(this));
71 p.add(pp);
72 p.add(controls = new Controls());
73 p.add(new InstrumentsTable());
74
75 add(p);
76 }
77
78
79 public void open() {
80 try {
81 if (synthesizer == null) {
82 if ((synthesizer = MidiSystem.getSynthesizer()) == null) {
83 System.out.println("getSynthesizer() failed!");
84 return;
85 }
86 }
87 synthesizer.open();
88 sequencer = MidiSystem.getSequencer();
89 sequence = new Sequence(Sequence.PPQ, 10);
90 } catch (Exception ex) {
91 ex.printStackTrace();
92 return;
93 }
94
95 Soundbank sb = synthesizer.getDefaultSoundbank();
96 if (sb != null) {
97 instruments = synthesizer.getDefaultSoundbank().getInstruments();
98 synthesizer.loadInstrument(instruments[0]);
99 }
100 MidiChannel midiChannels[] = synthesizer.getChannels();
101 channels = new ChannelData[midiChannels.length];
102 for (int i = 0; i < channels.length; i++) {
103 channels[i] = new ChannelData(midiChannels[i], i);
104 }
105 cc = channels[0];
106
107 ListSelectionModel lsm = table.getSelectionModel();
108 lsm.setSelectionInterval(0, 0);
109 lsm = table.getColumnModel().getSelectionModel();
110 lsm.setSelectionInterval(0, 0);
111 }
112
113
114 public void close() {
115 if (synthesizer != null) {
116 synthesizer.close();
117 }
118 if (sequencer != null) {
119 sequencer.close();
120 }
121 sequencer = null;
122 synthesizer = null;
123 instruments = null;
124 channels = null;
125 if (recordFrame != null) {
126 recordFrame.dispose();
127 recordFrame = null;
128 }
129 }
130
131
132 /**
133 * given 120 bpm:
134 * (120 bpm) / (60 seconds per minute) = 2 beats per second
135 * 2 / 1000 beats per millisecond
136 * (2 * resolution) ticks per second
137 * (2 * resolution)/1000 ticks per millisecond, or
138 * (resolution / 500) ticks per millisecond
139 * ticks = milliseconds * resolution / 500
140 */
141 public void createShortEvent(int type, int num) {
142 ShortMessage message = new ShortMessage();
143 try {
144 long millis = System.currentTimeMillis() - startTime;
145 long tick = millis * sequence.getResolution() / 500;
146 message.setMessage(type + cc.num, num, cc.velocity);
147 MidiEvent event = new MidiEvent(message, tick);
148 track.add(event);
149 } catch (Exception ex) {
150 ex.printStackTrace();
151 }
152 }
153
154
155 /**
156 * Black and white keys or notes on the piano.
157 */
158 public Key getKey(int x, int y, int w, int h, int num){
159 return new Key(this, x,y,w,h,num);
160 }
161
162
163 /**
164 * Stores MidiChannel information.
165 */
166 class ChannelData {
167
168 MidiChannel channel;
169 boolean solo, mono, mute, sustain;
170 int velocity, pressure, bend, reverb;
171 int row, col, num;
172
173 public ChannelData(MidiChannel channel, int num) {
174 this.channel = channel;
175 this.num = num;
176 velocity = pressure = bend = reverb = 64;
177 }
178
179 public void setComponentStates() {
180 table.setRowSelectionInterval(row, row);
181 table.setColumnSelectionInterval(col, col);
182
183 soloCB.setSelected(solo);
184 monoCB.setSelected(mono);
185 muteCB.setSelected(mute);
186 //sustCB.setSelected(sustain);
187
188 JSlider slider[] = {veloS, presS, bendS, revbS};
189 int v[] = {velocity, pressure, bend, reverb};
190 for (int i = 0; i < slider.length; i++) {
191 TitledBorder tb = (TitledBorder) slider[i].getBorder();
192 String s = tb.getTitle();
193 tb.setTitle(s.substring(0, s.indexOf('=') + 1) + s.valueOf(v[i]));
194 slider[i].repaint();
195 }
196 }
197 } // End class ChannelData
198
199
200 /**
201 * Table for 128 general MIDI melody instuments.
202 */
203 class InstrumentsTable extends JPanel {
204
205 private String names[] = {
206 "Piano", "Chromatic Perc.", "Organ", "Guitar",
207 "Bass", "Strings", "Ensemble", "Brass",
208 "Reed", "Pipe", "Synth Lead", "Synth Pad",
209 "Synth Effects", "Ethnic", "Percussive", "Sound Effects"};
210 private int nRows = 8;
211 private int nCols = names.length; // just show 128 instruments
212
213 public InstrumentsTable() {
214 setLayout(new BorderLayout());
215
216 TableModel dataModel = new AbstractTableModel() {
217 public int getColumnCount() {
218 return nCols;
219 }
220
221 public int getRowCount() {
222 return nRows;
223 }
224
225 public Object getValueAt(int r, int c) {
226 if (instruments != null) {
227 return instruments[c * nRows + r].getName();
228 } else {
229 return Integer.toString(c * nRows + r);
230 }
231 }
232
233 public String getColumnName(int c) {
234 return names[c];
235 }
236
237 public Class getColumnClass(int c) {
238 return getValueAt(0, c).getClass();
239 }
240
241 public boolean isCellEditable(int r, int c) {
242 return false;
243 }
244
245 public void setValueAt(Object obj, int r, int c) {
246 }
247 };
248
249 table = new JTable(dataModel);
250 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
251
252 // Listener for row changes
253 ListSelectionModel lsm = table.getSelectionModel();
254 lsm.addListSelectionListener(new ListSelectionListener() {
255 public void valueChanged(ListSelectionEvent e) {
256 ListSelectionModel sm = (ListSelectionModel) e.getSource();
257 if (!sm.isSelectionEmpty()) {
258 cc.row = sm.getMinSelectionIndex();
259 }
260 programChange(cc.col * nRows + cc.row);
261 }
262 });
263
264 // Listener for column changes
265 lsm = table.getColumnModel().getSelectionModel();
266 lsm.addListSelectionListener(new ListSelectionListener() {
267 public void valueChanged(ListSelectionEvent e) {
268 ListSelectionModel sm = (ListSelectionModel) e.getSource();
269 if (!sm.isSelectionEmpty()) {
270 cc.col = sm.getMinSelectionIndex();
271 }
272 programChange(cc.col * nRows + cc.row);
273 }
274 });
275
276 table.setPreferredScrollableViewportSize(new Dimension(nCols * 110, 200));
277 table.setCellSelectionEnabled(true);
278 table.setColumnSelectionAllowed(true);
279 for (int i = 0; i < names.length; i++) {
280 TableColumn column = table.getColumn(names[i]);
281 column.setPreferredWidth(110);
282 }
283 table.setAutoResizeMode(table.AUTO_RESIZE_OFF);
284
285 JScrollPane sp = new JScrollPane(table);
286 sp.setVerticalScrollBarPolicy(sp.VERTICAL_SCROLLBAR_NEVER);
287 sp.setHorizontalScrollBarPolicy(sp.HORIZONTAL_SCROLLBAR_ALWAYS);
288 add(sp);
289 }
290
291 public Dimension getPreferredSize() {
292 return new Dimension(800, 170);
293 }
294
295 public Dimension getMaximumSize() {
296 return new Dimension(800, 170);
297 }
298
299 private void programChange(int program) {
300 if (instruments != null) {
301 synthesizer.loadInstrument(instruments[program]);
302 }
303 cc.channel.programChange(program);
304 if (record) {
305 createShortEvent(PROGRAM, program);
306 }
307 }
308 }
309
310
311 /**
312 * A collection of MIDI controllers.
313 */
314 class Controls extends JPanel implements ActionListener, ChangeListener, ItemListener {
315
316 public JButton recordB;
317 JMenu menu;
318 int fileNum = 0;
319
320 public Controls() {
321 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
322 setBorder(new EmptyBorder(5, 10, 5, 10));
323
324 JPanel p = new JPanel();
325 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
326
327 veloS = createSlider("Velocity", p);
328 presS = createSlider("Pressure", p);
329 revbS = createSlider("Reverb", p);
330
331 // create a slider with a 14-bit range of values for pitch-bend
332 bendS = create14BitSlider("Bend", p);
333
334 p.add(Box.createHorizontalStrut(10));
335 add(p);
336
337 p = new JPanel();
338 p.setBorder(new EmptyBorder(10, 0, 10, 0));
339 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
340
341 JComboBox combo = new JComboBox();
342 combo.setPreferredSize(new Dimension(120, 25));
343 combo.setMaximumSize(new Dimension(120, 25));
344 for (int i = 1; i <= 16; i++) {
345 combo.addItem("Channel " + String.valueOf(i));
346 }
347 combo.addItemListener(this);
348 p.add(combo);
349 p.add(Box.createHorizontalStrut(20));
350
351 muteCB = createCheckBox("Mute", p);
352 soloCB = createCheckBox("Solo", p);
353 monoCB = createCheckBox("Mono", p);
354 //sustCB = createCheckBox("Sustain", p);
355
356 createButton("All Notes Off", p);
357 p.add(Box.createHorizontalStrut(10));
358 p.add(mouseOverCB);
359 p.add(Box.createHorizontalStrut(10));
360 recordB = createButton("Record...", p);
361 add(p);
362 }
363
364 public JButton createButton(String name, JPanel p) {
365 JButton b = new JButton(name);
366 b.addActionListener(this);
367 p.add(b);
368 return b;
369 }
370
371 private JCheckBox createCheckBox(String name, JPanel p) {
372 JCheckBox cb = new JCheckBox(name);
373 cb.addItemListener(this);
374 p.add(cb);
375 return cb;
376 }
377
378 private JSlider createSlider(String name, JPanel p) {
379 JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 127, 64);
380 slider.addChangeListener(this);
381 TitledBorder tb = new TitledBorder(new EtchedBorder());
382 tb.setTitle(name + " = 64");
383 slider.setBorder(tb);
384 p.add(slider);
385 p.add(Box.createHorizontalStrut(5));
386 return slider;
387 }
388
389 private JSlider create14BitSlider(String name, JPanel p) {
390 JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 16383, 8192);
391 slider.addChangeListener(this);
392 TitledBorder tb = new TitledBorder(new EtchedBorder());
393 tb.setTitle(name + " = 8192");
394 slider.setBorder(tb);
395 p.add(slider);
396 p.add(Box.createHorizontalStrut(5));
397 return slider;
398 }
399
400 public void stateChanged(ChangeEvent e) {
401 JSlider slider = (JSlider) e.getSource();
402 int value = slider.getValue();
403 TitledBorder tb = (TitledBorder) slider.getBorder();
404 String s = tb.getTitle();
405 tb.setTitle(s.substring(0, s.indexOf('=') + 1) + s.valueOf(value));
406 if (s.startsWith("Velocity")) {
407 cc.velocity = value;
408 } else if (s.startsWith("Pressure")) {
409 cc.channel.setChannelPressure(cc.pressure = value);
410 } else if (s.startsWith("Bend")) {
411 cc.channel.setPitchBend(cc.bend = value);
412 } else if (s.startsWith("Reverb")) {
413 cc.channel.controlChange(REVERB, cc.reverb = value);
414 }
415 slider.repaint();
416 }
417
418 public void itemStateChanged(ItemEvent e) {
419 if (e.getSource() instanceof JComboBox) {
420 JComboBox combo = (JComboBox) e.getSource();
421 cc = channels[combo.getSelectedIndex()];
422 cc.setComponentStates();
423 } else {
424 JCheckBox cb = (JCheckBox) e.getSource();
425 String name = cb.getText();
426 if (name.startsWith("Mute")) {
427 cc.channel.setMute(cc.mute = cb.isSelected());
428 } else if (name.startsWith("Solo")) {
429 cc.channel.setSolo(cc.solo = cb.isSelected());
430 } else if (name.startsWith("Mono")) {
431 cc.channel.setMono(cc.mono = cb.isSelected());
432 } else if (name.startsWith("Sustain")) {
433 cc.sustain = cb.isSelected();
434 cc.channel.controlChange(SUSTAIN, cc.sustain ? 127 : 0);
435 }
436 }
437 }
438
439 public void actionPerformed(ActionEvent e) {
440 JButton button = (JButton) e.getSource();
441 if (button.getText().startsWith("All")) {
442 for (int i = 0; i < channels.length; i++) {
443 channels[i].channel.allNotesOff();
444 }
445 for (int i = 0; i < keys.size(); i++) {
446 ((Key) keys.get(i)).setNoteState(OFF);
447 }
448 } else if (button.getText().startsWith("Record")) {
449 if (recordFrame != null) {
450 recordFrame.toFront();
451 } else {
452 recordFrame = new RecordFrame();
453 }
454 }
455 }
456 } // End class Controls
457
458
459 /**
460 * A frame that allows for midi capture & saving the captured data.
461 */
462 class RecordFrame extends JFrame implements ActionListener, MetaEventListener {
463
464 public JButton recordB, playB, saveB;
465 Vector tracks = new Vector();
466 DefaultListModel listModel = new DefaultListModel();
467 TableModel dataModel;
468 JTable table;
469
470
471 public RecordFrame() {
472 super("Midi Capture");
473 addWindowListener(new WindowAdapter() {
474 public void windowClosing(WindowEvent e) {
475 recordFrame = null;
476 }
477 });
478
479 sequencer.addMetaEventListener(this);
480 try {
481 sequence = new Sequence(Sequence.PPQ, 10);
482 } catch (Exception ex) {
483 ex.printStackTrace();
484 }
485
486 //JPanel p1 = new JPanel(new BorderLayout());
487
488 JPanel p2 = new JPanel();
489 p2.setBorder(new EmptyBorder(5, 5, 5, 5));
490 p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS));
491
492 recordB = createButton("Record", p2, true);
493 playB = createButton("Play", p2, false);
494 saveB = createButton("Save...", p2, false);
495
496 getContentPane().add("North", p2);
497 p2.setVisible(false);
498
499 final String[] names = {"Channel #", "Instrument"};
500
501 dataModel = new AbstractTableModel() {
502 public int getColumnCount() {
503 return names.length;
504 }
505
506 public int getRowCount() {
507 return tracks.size();
508 }
509
510 public Object getValueAt(int row, int col) {
511 if (col == 0) {
512 return ((TrackData) tracks.get(row)).chanNum;
513 } else if (col == 1) {
514 return ((TrackData) tracks.get(row)).name;
515 }
516 return null;
517 }
518
519 public String getColumnName(int col) {
520 return names[col];
521 }
522
523 public Class getColumnClass(int c) {
524 return getValueAt(0, c).getClass();
525 }
526
527 public boolean isCellEditable(int row, int col) {
528 return false;
529 }
530
531 public void setValueAt(Object val, int row, int col) {
532 if (col == 0) {
533 ((TrackData) tracks.get(row)).chanNum = (Integer) val;
534 } else if (col == 1) {
535 ((TrackData) tracks.get(row)).name = (String) val;
536 }
537 }
538 };
539
540 table = new JTable(dataModel);
541 TableColumn col = table.getColumn("Channel #");
542 col.setMaxWidth(65);
543 table.sizeColumnsToFit(0);
544
545 JScrollPane scrollPane = new JScrollPane(table);
546 EmptyBorder eb = new EmptyBorder(0, 5, 5, 5);
547 scrollPane.setBorder(new CompoundBorder(eb, new EtchedBorder()));
548
549 getContentPane().add("Center", scrollPane);
550 pack();
551 Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
552 int w = 210;
553 int h = 160;
554 setLocation(d.width / 2 - w / 2, d.height / 2 - h / 2);
555 setSize(w, h);
556 setVisible(true);
557 }
558
559
560 public JButton createButton(String name, JPanel p, boolean state) {
561 JButton b = new JButton(name);
562 b.setFont(new Font("serif", Font.PLAIN, 10));
563 b.setEnabled(state);
564 b.addActionListener(this);
565 p.add(b);
566 return b;
567 }
568
569
570 public void actionPerformed(ActionEvent e) {
571 JButton button = (JButton) e.getSource();
572 if (button.equals(recordB)) {
573 record = recordB.getText().startsWith("Record");
574 if (record) {
575 track = sequence.createTrack();
576 startTime = System.currentTimeMillis();
577
578 // add a program change right at the beginning of
579 // the track for the current instrument
580 createShortEvent(PROGRAM, cc.col * 8 + cc.row);
581
582 recordB.setText("Stop");
583 playB.setEnabled(false);
584 saveB.setEnabled(false);
585 } else {
586 String name = null;
587 if (instruments != null) {
588 name = instruments[cc.col * 8 + cc.row].getName();
589 } else {
590 name = Integer.toString(cc.col * 8 + cc.row);
591 }
592 tracks.add(new TrackData(cc.num + 1, name, track));
593 table.tableChanged(new TableModelEvent(dataModel));
594 recordB.setText("Record");
595 playB.setEnabled(true);
596 saveB.setEnabled(true);
597 }
598 } else if (button.equals(playB)) {
599 if (playB.getText().startsWith("Play")) {
600 try {
601 sequencer.open();
602 sequencer.setSequence(sequence);
603 } catch (Exception ex) {
604 ex.printStackTrace();
605 }
606 sequencer.start();
607 playB.setText("Stop");
608 recordB.setEnabled(false);
609 } else {
610 sequencer.stop();
611 playB.setText("Play");
612 recordB.setEnabled(true);
613 }
614 } else if (button.equals(saveB)) {
615 try {
616 File file = new File(System.getProperty("user.dir"));
617 JFileChooser fc = new JFileChooser(file);
618 fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
619 public boolean accept(File f) {
620 if (f.isDirectory()) {
621 return true;
622 }
623 return false;
624 }
625
626 public String getDescription() {
627 return "Save as .mid file.";
628 }
629 });
630 if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
631 saveMidiFile(fc.getSelectedFile());
632 }
633 } catch (SecurityException ex) {
634 JavaSound.showInfoDialog();
635 ex.printStackTrace();
636 } catch (Exception ex) {
637 ex.printStackTrace();
638 }
639 }
640 }
641
642
643 public void meta(MetaMessage message) {
644 if (message.getType() == 47) { // 47 is end of track
645 playB.setText("Play");
646 recordB.setEnabled(true);
647 }
648 }
649
650
651 public void saveMidiFile(File file) {
652 try {
653 int[] fileTypes = MidiSystem.getMidiFileTypes(sequence);
654 if (fileTypes.length == 0) {
655 System.out.println("Can't save sequence");
656 } else {
657 if (MidiSystem.write(sequence, fileTypes[0], file) == -1) {
658 throw new IOException("Problems writing to file");
659 }
660 }
661 } catch (SecurityException ex) {
662 JavaSound.showInfoDialog();
663 } catch (Exception ex) {
664 ex.printStackTrace();
665 }
666 }
667
668
669 class TrackData extends Object {
670 Integer chanNum;
671 String name;
672 Track track;
673
674 public TrackData(int chanNum, String name, Track track) {
675 this.chanNum = new Integer(chanNum);
676 this.name = name;
677 this.track = track;
678 }
679 } // End class TrackData
680 } // End class RecordFrame
681
682
683 public static void main(String args[]) {
684 final MidiSynth midiSynth = new MidiSynth();
685 midiSynth.open();
686 JFrame f = new JFrame("Midi Synthesizer");
687 f.addWindowListener(new WindowAdapter() {
688 public void windowClosing(WindowEvent e) {
689 System.exit(0);
690 }
691 });
692 f.getContentPane().add("Center", midiSynth);
693 f.pack();
694 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
695 int w = 760;
696 int h = 470;
697 f.setLocation(screenSize.width / 2 - w / 2, screenSize.height / 2 - h / 2);
698 f.setSize(w, h);
699 f.setVisible(true);
700 }
701 }
702