1 package camidion.chordhelper.midieditor;
3 import java.awt.Dimension;
4 import java.awt.Graphics;
5 import java.awt.Graphics2D;
6 import java.awt.GridLayout;
7 import java.awt.Insets;
9 import java.awt.Rectangle;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.ComponentEvent;
13 import java.awt.event.ComponentListener;
14 import java.awt.event.InputEvent;
15 import java.awt.event.MouseEvent;
16 import java.awt.event.MouseListener;
17 import java.util.ArrayList;
18 import java.util.Vector;
20 import javax.sound.midi.InvalidMidiDataException;
21 import javax.sound.midi.MidiChannel;
22 import javax.sound.midi.Sequence;
23 import javax.swing.AbstractAction;
24 import javax.swing.Action;
25 import javax.swing.BoxLayout;
26 import javax.swing.JButton;
27 import javax.swing.JCheckBox;
28 import javax.swing.JComboBox;
29 import javax.swing.JComponent;
30 import javax.swing.JDialog;
31 import javax.swing.JLabel;
32 import javax.swing.JOptionPane;
33 import javax.swing.JPanel;
34 import javax.swing.JScrollPane;
35 import javax.swing.JSpinner;
36 import javax.swing.JTabbedPane;
37 import javax.swing.JTextArea;
38 import javax.swing.JTextField;
39 import javax.swing.SpinnerNumberModel;
40 import javax.swing.event.ChangeEvent;
41 import javax.swing.event.ChangeListener;
43 import camidion.chordhelper.ButtonIcon;
44 import camidion.chordhelper.ChordHelperApplet;
45 import camidion.chordhelper.mididevice.VirtualMidiDevice;
46 import camidion.chordhelper.music.AbstractNoteTrackSpec;
47 import camidion.chordhelper.music.ChordProgression;
48 import camidion.chordhelper.music.DrumTrackSpec;
49 import camidion.chordhelper.music.FirstTrackSpec;
50 import camidion.chordhelper.music.MelodyTrackSpec;
51 import camidion.chordhelper.music.Range;
52 import camidion.chordhelper.pianokeyboard.PianoKeyboardListener;
53 import camidion.chordhelper.pianokeyboard.PianoKeyboardPanel;
56 * 新しいMIDIシーケンスを生成するダイアログ
58 public class NewSequenceDialog extends JDialog {
59 private static final Insets ZERO_INSETS = new Insets(0,0,0,0);
60 private static final Integer[] PPQList = {
61 48,60,80,96,120,160,192,240,320,384,480,960
63 private static final String INITIAL_CHORD_STRING =
64 "Key: C\nC G/B | Am Em/G | F C/E | Dm7 G7 C % | F G7 | Csus4 C\n";
65 private JTextArea chordText = new JTextArea(INITIAL_CHORD_STRING, 18, 30);
66 private JTextField seqNameText = new JTextField();
67 private JComboBox<Integer> ppqComboBox = new JComboBox<Integer>(PPQList);
68 private TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter();
69 private TempoSelecter tempoSelecter = new TempoSelecter();
70 private MeasureSelecter measureSelecter = new MeasureSelecter();
71 private TrackSpecPanel trackSpecPanel = new TrackSpecPanel() {{
72 DrumTrackSpec dts = new DrumTrackSpec(9, "Percussion track");
76 mts = new MelodyTrackSpec(2, "Bass track", new Range(36,48));
80 mts = new MelodyTrackSpec(1, "Chord track", new Range(60,72));
82 mts = new MelodyTrackSpec(0, "Melody track", new Range(60,84));
83 mts.randomMelody = true;
84 mts.beatPattern = 0xFFFF;
85 mts.continuousBeatPattern = 0x820A;
91 public Action openAction = new AbstractAction("New") {
93 String tooltip = "Generate new song - 新しい曲を生成";
94 putValue(Action.SHORT_DESCRIPTION, tooltip);
97 public void actionPerformed(ActionEvent e) { setVisible(true); }
99 private PlaylistTableModel playlist;
103 public Action generateAction = new AbstractAction(
104 "Generate & Add to PlayList", new ButtonIcon(ButtonIcon.EJECT_ICON)
107 public void actionPerformed(ActionEvent event) {
109 int index = playlist.addSequenceAndPlay(getMidiSequence());
110 playlist.getSequenceModelList().get(index).setModified(true);
111 } catch (InvalidMidiDataException ex) {
112 ex.printStackTrace();
113 JOptionPane.showMessageDialog(
114 NewSequenceDialog.this, ex.getMessage(),
115 ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
121 * 新しいMIDIシーケンスを生成するダイアログを構築します。
122 * @param playlist シーケンス追加先プレイリスト
123 * @param midiOutDevice 操作音を出力するMIDI出力デバイス
125 public NewSequenceDialog(PlaylistTableModel playlist, VirtualMidiDevice midiOutDevice) {
126 this.playlist = playlist;
127 trackSpecPanel.setChannels(midiOutDevice.getChannels());
128 setTitle("Generate new sequence - " + ChordHelperApplet.VersionInfo.NAME);
129 add(new JTabbedPane() {{
130 add("Sequence", new JPanel() {{
131 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
133 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
134 add(new JLabel("Sequence name:"));
138 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
139 add(new JLabel("Resolution in PPQ ="));
141 add(measureSelecter);
143 add(new JButton("Randomize (Tempo, Time signature, Chord progression)") {{
144 setMargin(ZERO_INSETS);
145 addActionListener(e->setRandomChordProgression(measureSelecter.getMeasureDuration()));
148 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
151 add(new JLabel("Time signature ="));
152 add(timesigSelecter);
156 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
157 add(new JLabel("Chord progression :"));
158 add(new JLabel("Transpose"));
159 add(new JButton(" + Up ") {{
160 setMargin(ZERO_INSETS);
161 addActionListener(e->{
162 ChordProgression cp = createChordProgression();
164 setChordProgression(cp);
167 add(new JButton(" - Down ") {{
168 setMargin(ZERO_INSETS);
169 addActionListener(e->{
170 ChordProgression cp = createChordProgression();
172 setChordProgression(cp);
175 add(new JButton(" Enharmonic ") {{
176 setMargin(ZERO_INSETS);
177 addActionListener(e->{
178 ChordProgression cp = createChordProgression();
179 cp.toggleEnharmonically();
180 setChordProgression(cp);
183 add(new JButton("Relative key") {{
184 setMargin(ZERO_INSETS);
185 addActionListener(e->{
186 ChordProgression cp = createChordProgression();
187 cp.toggleKeyMajorMinor();
188 setChordProgression(cp);
192 add(new JScrollPane(chordText));
194 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
195 add(new JButton(generateAction){{setMargin(ZERO_INSETS);}});
198 add("Track", trackSpecPanel);
200 setBounds(250,200,600,540);
206 private ChordProgression createChordProgression() {
207 return new ChordProgression(chordText.getText());
210 * MIDIシーケンスを生成して返します。
213 public Sequence getMidiSequence() {
214 FirstTrackSpec firstTrackSpec = new FirstTrackSpec(
215 seqNameText.getText(),
216 tempoSelecter.getTempoByteArray(),
217 timesigSelecter.getByteArray()
219 return createChordProgression().toMidiSequence(
220 (int)ppqComboBox.getSelectedItem(),
221 measureSelecter.getStartMeasurePosition(),
222 measureSelecter.getEndMeasurePosition(),
224 trackSpecPanel.getTrackSpecs()
228 * コード進行を設定します。テキスト欄に反映されます。
231 public void setChordProgression(ChordProgression cp) {
232 chordText.setText(cp.toString());
235 * テンポ・拍子・コード進行をランダムに設定
236 * @param measureLength 小節数
238 public void setRandomChordProgression(int measureLength) {
239 tempoSelecter.setTempo( 80 + (int)(Math.random() * 100) );
240 int timesig_upper = 4;
241 int timesig_lower_index = 2;
242 switch( (int)(Math.random() * 10) ) {
243 case 0: timesig_upper = 3; break; // 3/4
245 timesigSelecter.setValue((byte)timesig_upper, (byte)timesig_lower_index);
246 setChordProgression(new ChordProgression(measureLength, timesig_upper));
251 private static class TrackSpecPanel extends JPanel
252 implements PianoKeyboardListener, ActionListener, ChangeListener
254 JComboBox<AbstractNoteTrackSpec> trackSelecter = new JComboBox<>();
255 JLabel trackTypeLabel = new JLabel();
256 JTextField nameTextField = new JTextField(20);
257 MidiChannelComboSelecter chSelecter =
258 new MidiChannelComboSelecter("MIDI Channel:");
259 MidiProgramSelecter pgSelecter = new MidiProgramSelecter();
260 MidiProgramFamilySelecter pgFamilySelecter =
261 new MidiProgramFamilySelecter(pgSelecter) {{
262 pgSelecter.setFamilySelecter(pgFamilySelecter);
264 PianoKeyboardPanel keyboardPanel = new PianoKeyboardPanel() {{
265 keyboard.octaveSizeModel.setValue(6);
266 keyboard.setPreferredSize(new Dimension(400,40));
267 keyboard.setMaxSelectable(2);
269 JPanel rangePanel = new JPanel() {{
270 add( new JLabel("Range:") );
273 JCheckBox randomMelodyCheckbox = new JCheckBox("Random melody");
274 JCheckBox bassCheckbox = new JCheckBox("Bass note");
275 JCheckBox randomLyricCheckbox = new JCheckBox("Random lyrics");
276 JCheckBox nsx39Checkbox = new JCheckBox("NSX-39");;
277 BeatPadPanel beatPadPanel = new BeatPadPanel(this);
278 private MidiChannel[] midiChannels;
280 public TrackSpecPanel() {
281 nameTextField.addActionListener(this);
282 keyboardPanel.keyboard.addPianoKeyboardListener(this);
284 add(new JLabel("Track select:"));
289 add(new JLabel("Track name (Press [Enter] key to change):"));
293 add(new VelocitySelecter(keyboardPanel.keyboard.velocityModel));
295 add(pgFamilySelecter);
299 bassCheckbox.addChangeListener(this);
301 randomMelodyCheckbox.addChangeListener(this);
302 add(randomMelodyCheckbox);
303 randomLyricCheckbox.addChangeListener(this);
304 add(randomLyricCheckbox);
305 nsx39Checkbox.addChangeListener(this);
308 trackSelecter.addActionListener(this);
309 chSelecter.comboBox.addActionListener(this);
310 keyboardPanel.keyboard.velocityModel.addChangeListener(
311 e -> getTrackSpec().velocity = keyboardPanel.keyboard.velocityModel.getValue()
313 pgSelecter.addActionListener(this);
316 public void stateChanged(ChangeEvent e) {
317 Object src = e.getSource();
318 if( src == bassCheckbox ) {
319 AbstractNoteTrackSpec ants = getTrackSpec();
320 if( ants instanceof MelodyTrackSpec ) {
321 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
322 mts.isBass = bassCheckbox.isSelected();
325 else if( src == randomMelodyCheckbox ) {
326 AbstractNoteTrackSpec ants = getTrackSpec();
327 if( ants instanceof MelodyTrackSpec ) {
328 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
329 mts.randomMelody = randomMelodyCheckbox.isSelected();
332 else if( src == randomLyricCheckbox ) {
333 AbstractNoteTrackSpec ants = getTrackSpec();
334 if( ants instanceof MelodyTrackSpec ) {
335 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
336 mts.randomLyric = randomLyricCheckbox.isSelected();
339 else if( src == nsx39Checkbox ) {
340 AbstractNoteTrackSpec ants = getTrackSpec();
341 if( ants instanceof MelodyTrackSpec ) {
342 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
343 mts.nsx39 = nsx39Checkbox.isSelected();
348 public void actionPerformed(ActionEvent e) {
349 Object src = e.getSource();
350 AbstractNoteTrackSpec ants;
351 if( src == nameTextField ) {
352 getTrackSpec().name = nameTextField.getText();
354 else if( src == trackSelecter ) {
355 ants = (AbstractNoteTrackSpec)(trackSelecter.getSelectedItem());
356 String trackTypeString = "Track type: " + (
357 ants instanceof DrumTrackSpec ? "Percussion" :
358 ants instanceof MelodyTrackSpec ? "Melody" : "(Unknown)"
360 trackTypeLabel.setText(trackTypeString);
361 nameTextField.setText(ants.name);
362 chSelecter.setSelectedChannel(ants.midiChannel);
363 keyboardPanel.keyboard.velocityModel.setValue(ants.velocity);
364 pgSelecter.setProgram(ants.programNumber);
365 keyboardPanel.keyboard.clear();
366 if( ants instanceof DrumTrackSpec ) {
367 rangePanel.setVisible(false);
368 randomMelodyCheckbox.setVisible(false);
369 randomLyricCheckbox.setVisible(false);
370 nsx39Checkbox.setVisible(false);
371 bassCheckbox.setVisible(false);
373 else if( ants instanceof MelodyTrackSpec ) {
374 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
375 rangePanel.setVisible(true);
376 keyboardPanel.keyboard.setSelectedNote(ts.range.minNote);
377 keyboardPanel.keyboard.setSelectedNote(ts.range.maxNote);
378 keyboardPanel.keyboard.autoScroll(ts.range.minNote);
379 randomMelodyCheckbox.setSelected(ts.randomMelody);
380 randomLyricCheckbox.setSelected(ts.randomLyric);
381 bassCheckbox.setSelected(ts.isBass);
382 randomMelodyCheckbox.setVisible(true);
383 randomLyricCheckbox.setVisible(true);
384 nsx39Checkbox.setVisible(true);
385 bassCheckbox.setVisible(true);
387 beatPadPanel.setTrackSpec(ants);
389 else if( src == chSelecter.comboBox ) {
390 getTrackSpec().midiChannel = chSelecter.getSelectedChannel();
392 else if( src == pgSelecter ) {
393 getTrackSpec().programNumber = pgSelecter.getProgram();
397 public void pianoKeyPressed(int n, InputEvent e) {
399 AbstractNoteTrackSpec ants = getTrackSpec();
400 if( ants instanceof MelodyTrackSpec ) {
401 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
402 ts.range = new Range(keyboardPanel.keyboard.getSelectedNotes());
406 public void pianoKeyReleased(int n, InputEvent e) { noteOff(n); }
407 public void octaveMoved(ChangeEvent event) {}
408 public void octaveResized(ChangeEvent event) {}
409 public void noteOn(int n) {
410 if( midiChannels == null ) return;
411 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
412 mc.noteOn( n, keyboardPanel.keyboard.velocityModel.getValue() );
414 public void noteOff(int n) {
415 if( midiChannels == null ) return;
416 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
417 mc.noteOff( n, keyboardPanel.keyboard.velocityModel.getValue() );
419 public void setChannels( MidiChannel midiChannels[] ) {
420 this.midiChannels = midiChannels;
422 public AbstractNoteTrackSpec getTrackSpec() {
423 Object trackSpecObj = trackSelecter.getSelectedItem();
424 AbstractNoteTrackSpec ants = (AbstractNoteTrackSpec)trackSpecObj;
425 ants.name = nameTextField.getText();
428 public Vector<AbstractNoteTrackSpec> getTrackSpecs() {
429 Vector<AbstractNoteTrackSpec> trackSpecs = new Vector<>();
430 int i=0, n_items = trackSelecter.getItemCount();
431 while( i < n_items ) {
432 trackSpecs.add((AbstractNoteTrackSpec)trackSelecter.getItemAt(i++));
436 public void addTrackSpec(AbstractNoteTrackSpec trackSpec) {
437 trackSelecter.addItem(trackSpec);
440 private static class MeasureSelecter extends JPanel {
441 public MeasureSelecter() {
442 setLayout(new GridLayout(2,3));
444 add(new JLabel("Start",JLabel.CENTER));
445 add(new JLabel("End",JLabel.CENTER));
446 add(new JLabel("Measure",JLabel.RIGHT));
447 add(new JSpinner(startModel));
448 add(new JSpinner(endModel));
450 private SpinnerNumberModel startModel = new SpinnerNumberModel( 3, 1, 9999, 1 );
451 private SpinnerNumberModel endModel = new SpinnerNumberModel( 8, 1, 9999, 1 );
452 public int getStartMeasurePosition() {
453 return startModel.getNumber().intValue();
455 public int getEndMeasurePosition() {
456 return endModel.getNumber().intValue();
458 public int getMeasureDuration() {
459 return getEndMeasurePosition() - getStartMeasurePosition() + 1;
462 //////////////////////////////////////////////////////////////////
467 private static class BeatPadPanel extends JPanel implements ActionListener {
468 PianoKeyboardListener piano_keyboard_listener;
469 JPanel percussion_selecters_panel;
470 java.util.List<JComboBox<String>> percussionSelecters =
471 new ArrayList<JComboBox<String>>() {
473 for( int i=0; i < DrumTrackSpec.defaultPercussions.length; i++ ) {
474 add(new JComboBox<String>());
480 public BeatPadPanel(PianoKeyboardListener pkl) {
481 piano_keyboard_listener = pkl;
482 percussion_selecters_panel = new JPanel();
483 percussion_selecters_panel.setLayout(
484 new BoxLayout( percussion_selecters_panel, BoxLayout.Y_AXIS )
486 for( JComboBox<String> cb : percussionSelecters ) {
487 percussion_selecters_panel.add(cb);
488 cb.addActionListener(this);
490 add( percussion_selecters_panel );
491 add( beat_pad = new BeatPad(pkl) );
492 beat_pad.setPreferredSize( new Dimension(400,200) );
493 setLayout( new BoxLayout( this, BoxLayout.X_AXIS ) );
495 public void actionPerformed(ActionEvent e) {
496 Object src = e.getSource();
497 for( JComboBox<String> cb : percussionSelecters ) {
498 if( src != cb ) continue;
500 (DrumTrackSpec.PercussionComboBoxModel)cb.getModel()
501 ).getSelectedNoteNo();
502 piano_keyboard_listener.pianoKeyPressed(note_no,(InputEvent)null);
505 public void setTrackSpec( AbstractNoteTrackSpec ants ) {
506 beat_pad.setTrackSpec(ants);
507 if( ants instanceof DrumTrackSpec ) {
508 DrumTrackSpec dts = (DrumTrackSpec)ants;
510 for( JComboBox<String> cb : percussionSelecters ) {
511 cb.setModel(dts.models[i++]);
513 percussion_selecters_panel.setVisible(true);
515 else if( ants instanceof MelodyTrackSpec ) {
516 percussion_selecters_panel.setVisible(false);
520 private static class BeatPad extends JComponent implements MouseListener, ComponentListener {
521 PianoKeyboardListener piano_keyboard_listener;
522 private int on_note_no = -1;
523 AbstractNoteTrackSpec track_spec;
525 public static final int MAX_BEATS = 16;
526 Rectangle beat_buttons[][];
527 Rectangle continuous_beat_buttons[][];
529 public BeatPad(PianoKeyboardListener pkl) {
530 piano_keyboard_listener = pkl;
531 addMouseListener(this);
532 addComponentListener(this);
533 // addMouseMotionListener(this);
535 public void paint(Graphics g) {
537 Graphics2D g2 = (Graphics2D) g;
539 int note, beat, mask;
541 if( track_spec instanceof DrumTrackSpec ) {
542 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
543 for( note=0; note<dts.beat_patterns.length; note++ ) {
544 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
545 r = beat_buttons[note][beat];
546 if( (dts.beat_patterns[note] & mask) != 0 )
547 g2.fillRect( r.x, r.y, r.width, r.height );
549 g2.drawRect( r.x, r.y, r.width, r.height );
553 else if( track_spec instanceof MelodyTrackSpec ) {
554 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
555 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
556 r = beat_buttons[0][beat];
557 if( (mts.beatPattern & mask) != 0 )
558 g2.fillRect( r.x, r.y, r.width, r.height );
560 g2.drawRect( r.x, r.y, r.width, r.height );
561 r = continuous_beat_buttons[0][beat];
562 if( (mts.continuousBeatPattern & mask) != 0 )
563 g2.fillRect( r.x, r.y, r.width, r.height );
565 g2.drawRect( r.x, r.y, r.width, r.height );
570 public void componentShown(ComponentEvent e) { }
571 public void componentHidden(ComponentEvent e) { }
572 public void componentMoved(ComponentEvent e) { }
573 public void componentResized(ComponentEvent e) {
576 public void mousePressed(MouseEvent e) {
578 if( on_note_no >= 0 ) {
579 piano_keyboard_listener.pianoKeyPressed( on_note_no ,(InputEvent)e );
582 public void mouseReleased(MouseEvent e) {
583 if( on_note_no >= 0 ) {
584 piano_keyboard_listener.pianoKeyReleased( on_note_no ,(InputEvent)e );
588 public void mouseEntered(MouseEvent e) {
589 if((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
593 public void mouseExited(MouseEvent e) { }
594 public void mouseClicked(MouseEvent e) { }
595 private void sizeChanged() {
596 int beat, note, width, height;
597 Dimension d = getSize();
599 if( track_spec instanceof DrumTrackSpec ) {
600 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
601 num_notes = dts.models.length;
603 beat_buttons = new Rectangle[num_notes][];
604 continuous_beat_buttons = new Rectangle[num_notes][];
605 for( note=0; note<beat_buttons.length; note++ ) {
606 beat_buttons[note] = new Rectangle[MAX_BEATS];
607 continuous_beat_buttons[note] = new Rectangle[MAX_BEATS];
608 for( beat=0; beat<MAX_BEATS; beat++ ) {
609 width = (d.width * 3) / (MAX_BEATS * 4);
610 height = d.height / num_notes - 1;
611 beat_buttons[note][beat] = new Rectangle(
612 beat * d.width / MAX_BEATS,
617 width = d.width / (MAX_BEATS * 3);
618 continuous_beat_buttons[note][beat] = new Rectangle(
619 (beat+1) * d.width / MAX_BEATS - width + 1,
620 note * height + height / 3,
627 private void catchEvent(MouseEvent e) {
628 Point point = e.getPoint();
629 int note, beat, mask;
632 if( track_spec instanceof DrumTrackSpec ) {
633 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
634 for( note=0; note<dts.beat_patterns.length; note++ ) {
635 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
636 if( beat_buttons[note][beat].contains(point) ) {
637 dts.beat_patterns[note] ^= mask;
638 on_note_no = dts.models[note].getSelectedNoteNo();
644 else if( track_spec instanceof MelodyTrackSpec ) {
645 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
646 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
647 if( beat_buttons[0][beat].contains(point) ) {
648 mts.beatPattern ^= mask;
651 if( continuous_beat_buttons[0][beat].contains(point) ) {
652 mts.continuousBeatPattern ^= mask;
658 public void setTrackSpec( AbstractNoteTrackSpec ants ) {