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;
8 import java.awt.Rectangle;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
11 import java.awt.event.ComponentEvent;
12 import java.awt.event.ComponentListener;
13 import java.awt.event.InputEvent;
14 import java.awt.event.MouseEvent;
15 import java.awt.event.MouseListener;
16 import java.util.ArrayList;
17 import java.util.Vector;
19 import javax.sound.midi.MidiChannel;
20 import javax.sound.midi.Sequence;
21 import javax.swing.AbstractAction;
22 import javax.swing.Action;
23 import javax.swing.BoxLayout;
24 import javax.swing.JButton;
25 import javax.swing.JCheckBox;
26 import javax.swing.JComboBox;
27 import javax.swing.JComponent;
28 import javax.swing.JDialog;
29 import javax.swing.JLabel;
30 import javax.swing.JOptionPane;
31 import javax.swing.JPanel;
32 import javax.swing.JScrollPane;
33 import javax.swing.JSpinner;
34 import javax.swing.JTabbedPane;
35 import javax.swing.JTextArea;
36 import javax.swing.JTextField;
37 import javax.swing.SpinnerNumberModel;
38 import javax.swing.event.ChangeEvent;
39 import javax.swing.event.ChangeListener;
41 import camidion.chordhelper.ButtonIcon;
42 import camidion.chordhelper.ChordHelperApplet;
43 import camidion.chordhelper.mididevice.VirtualMidiDevice;
44 import camidion.chordhelper.music.AbstractNoteTrackSpec;
45 import camidion.chordhelper.music.ChordProgression;
46 import camidion.chordhelper.music.DrumTrackSpec;
47 import camidion.chordhelper.music.FirstTrackSpec;
48 import camidion.chordhelper.music.MelodyTrackSpec;
49 import camidion.chordhelper.music.Range;
50 import camidion.chordhelper.pianokeyboard.PianoKeyboardListener;
51 import camidion.chordhelper.pianokeyboard.PianoKeyboardPanel;
54 * 新しいMIDIシーケンスを生成するダイアログ
56 public class NewSequenceDialog extends JDialog {
57 private static final Integer[] PPQList = {
58 48,60,80,96,120,160,192,240,320,384,480,960
60 private static final String INITIAL_CHORD_STRING =
61 "Key: C\nC G/B | Am Em/G | F C/E | Dm7 G7 C % | F G7 | Csus4 C\n";
62 private JTextArea chordText = new JTextArea(INITIAL_CHORD_STRING, 18, 30);
63 private JTextField seqNameText = new JTextField();
64 private JComboBox<Integer> ppqComboBox = new JComboBox<Integer>(PPQList);
65 private TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter();
66 private TempoSelecter tempoSelecter = new TempoSelecter();
67 private MeasureSelecter measureSelecter = new MeasureSelecter();
68 private TrackSpecPanel trackSpecPanel = new TrackSpecPanel() {{
69 DrumTrackSpec dts = new DrumTrackSpec(9, "Percussion track");
73 mts = new MelodyTrackSpec(2, "Bass track", new Range(36,48));
77 mts = new MelodyTrackSpec(1, "Chord track", new Range(60,72));
79 mts = new MelodyTrackSpec(0, "Melody track", new Range(60,84));
80 mts.randomMelody = true;
81 mts.beatPattern = 0xFFFF;
82 mts.continuousBeatPattern = 0x820A;
88 public Action openAction = new AbstractAction("New") {
90 String tooltip = "Generate new song - 新しい曲を生成";
91 putValue(Action.SHORT_DESCRIPTION, tooltip);
94 public void actionPerformed(ActionEvent e) { setVisible(true); }
96 private PlaylistTableModel playlist;
100 public Action generateAction = new AbstractAction(
101 "Generate & Add to PlayList", new ButtonIcon(ButtonIcon.EJECT_ICON)
104 public void actionPerformed(ActionEvent event) {
106 int index = playlist.play(getMidiSequence());
107 playlist.getSequenceModelList().get(index).setModified(true);
108 } catch (Exception ex) {
109 JOptionPane.showMessageDialog(
110 NewSequenceDialog.this, ex,
111 ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
117 * 新しいMIDIシーケンスを生成するダイアログを構築します。
118 * @param playlist シーケンス追加先プレイリスト
119 * @param midiOutDevice 操作音を出力するMIDI出力デバイス
121 public NewSequenceDialog(PlaylistTableModel playlist, VirtualMidiDevice midiOutDevice) {
122 this.playlist = playlist;
123 trackSpecPanel.setChannels(midiOutDevice.getChannels());
124 setTitle("Generate new sequence - " + ChordHelperApplet.VersionInfo.NAME);
125 add(new JTabbedPane() {{
126 add("Sequence", new JPanel() {{
127 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
129 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
130 add(new JLabel("Sequence name:"));
134 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
135 add(new JLabel("Resolution in PPQ ="));
137 add(measureSelecter);
139 add(new JButton("Randomize (Tempo, Time signature, Chord progression)") {{
140 setMargin(ChordHelperApplet.ZERO_INSETS);
141 addActionListener(e->setRandomChordProgression(measureSelecter.getMeasureDuration()));
144 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
147 add(new JLabel("Time signature ="));
148 add(timesigSelecter);
152 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
153 add(new JLabel("Chord progression :"));
154 add(new JLabel("Transpose"));
155 add(new JButton(" + Up ") {{
156 setMargin(ChordHelperApplet.ZERO_INSETS);
157 addActionListener(e->{
158 ChordProgression cp = createChordProgression();
160 setChordProgression(cp);
163 add(new JButton(" - Down ") {{
164 setMargin(ChordHelperApplet.ZERO_INSETS);
165 addActionListener(e->{
166 ChordProgression cp = createChordProgression();
168 setChordProgression(cp);
171 add(new JButton(" Enharmonic ") {{
172 setMargin(ChordHelperApplet.ZERO_INSETS);
173 addActionListener(e->{
174 ChordProgression cp = createChordProgression();
175 cp.toggleEnharmonically();
176 setChordProgression(cp);
179 add(new JButton("Relative key") {{
180 setMargin(ChordHelperApplet.ZERO_INSETS);
181 addActionListener(e->{
182 ChordProgression cp = createChordProgression();
183 cp.toggleKeyMajorMinor();
184 setChordProgression(cp);
188 add(new JScrollPane(chordText));
190 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
191 add(new JButton(generateAction){{
192 setMargin(ChordHelperApplet.ZERO_INSETS);
196 add("Track", trackSpecPanel);
198 setBounds(250,200,600,540);
204 private ChordProgression createChordProgression() {
205 return new ChordProgression(chordText.getText());
208 * MIDIシーケンスを生成して返します。
211 public Sequence getMidiSequence() {
212 FirstTrackSpec firstTrackSpec = new FirstTrackSpec(
213 seqNameText.getText(),
214 tempoSelecter.getTempoByteArray(),
215 timesigSelecter.getByteArray()
217 return createChordProgression().toMidiSequence(
218 (int)ppqComboBox.getSelectedItem(),
219 measureSelecter.getStartMeasurePosition(),
220 measureSelecter.getEndMeasurePosition(),
222 trackSpecPanel.getTrackSpecs()
226 * コード進行を設定します。テキスト欄に反映されます。
229 public void setChordProgression(ChordProgression cp) {
230 chordText.setText(cp.toString());
233 * テンポ・拍子・コード進行をランダムに設定
234 * @param measureLength 小節数
236 public void setRandomChordProgression(int measureLength) {
237 tempoSelecter.setTempo( 80 + (int)(Math.random() * 100) );
238 int timesig_upper = 4;
239 int timesig_lower_index = 2;
240 switch( (int)(Math.random() * 10) ) {
241 case 0: timesig_upper = 3; break; // 3/4
243 timesigSelecter.setValue((byte)timesig_upper, (byte)timesig_lower_index);
244 setChordProgression(new ChordProgression(measureLength, timesig_upper));
249 private static class TrackSpecPanel extends JPanel
250 implements PianoKeyboardListener, ActionListener, ChangeListener
252 JComboBox<AbstractNoteTrackSpec> trackSelecter = new JComboBox<>();
253 JLabel trackTypeLabel = new JLabel();
254 JTextField nameTextField = new JTextField(20);
255 MidiChannelComboSelecter chSelecter =
256 new MidiChannelComboSelecter("MIDI Channel:");
257 MidiProgramSelecter pgSelecter = new MidiProgramSelecter();
258 MidiProgramFamilySelecter pgFamilySelecter =
259 new MidiProgramFamilySelecter(pgSelecter) {{
260 pgSelecter.setFamilySelecter(pgFamilySelecter);
262 PianoKeyboardPanel keyboardPanel = new PianoKeyboardPanel() {{
263 keyboard.octaveSizeModel.setValue(6);
264 keyboard.setPreferredSize(new Dimension(400,40));
265 keyboard.setMaxSelectable(2);
267 JPanel rangePanel = new JPanel() {{
268 add( new JLabel("Range:") );
271 JCheckBox randomMelodyCheckbox = new JCheckBox("Random melody");
272 JCheckBox bassCheckbox = new JCheckBox("Bass note");
273 JCheckBox randomLyricCheckbox = new JCheckBox("Random lyrics");
274 JCheckBox nsx39Checkbox = new JCheckBox("NSX-39");;
275 BeatPadPanel beatPadPanel = new BeatPadPanel(this);
276 private MidiChannel[] midiChannels;
278 public TrackSpecPanel() {
279 nameTextField.addActionListener(this);
280 keyboardPanel.keyboard.addPianoKeyboardListener(this);
282 add(new JLabel("Track select:"));
287 add(new JLabel("Track name (Press [Enter] key to change):"));
291 add(new VelocitySelecter(keyboardPanel.keyboard.velocityModel));
293 add(pgFamilySelecter);
297 bassCheckbox.addChangeListener(this);
299 randomMelodyCheckbox.addChangeListener(this);
300 add(randomMelodyCheckbox);
301 randomLyricCheckbox.addChangeListener(this);
302 add(randomLyricCheckbox);
303 nsx39Checkbox.addChangeListener(this);
306 trackSelecter.addActionListener(this);
307 chSelecter.comboBox.addActionListener(this);
308 keyboardPanel.keyboard.velocityModel.addChangeListener(
309 e -> getTrackSpec().velocity = keyboardPanel.keyboard.velocityModel.getValue()
311 pgSelecter.addActionListener(this);
314 public void stateChanged(ChangeEvent e) {
315 Object src = e.getSource();
316 if( src == bassCheckbox ) {
317 AbstractNoteTrackSpec ants = getTrackSpec();
318 if( ants instanceof MelodyTrackSpec ) {
319 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
320 mts.isBass = bassCheckbox.isSelected();
323 else if( src == randomMelodyCheckbox ) {
324 AbstractNoteTrackSpec ants = getTrackSpec();
325 if( ants instanceof MelodyTrackSpec ) {
326 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
327 mts.randomMelody = randomMelodyCheckbox.isSelected();
330 else if( src == randomLyricCheckbox ) {
331 AbstractNoteTrackSpec ants = getTrackSpec();
332 if( ants instanceof MelodyTrackSpec ) {
333 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
334 mts.randomLyric = randomLyricCheckbox.isSelected();
337 else if( src == nsx39Checkbox ) {
338 AbstractNoteTrackSpec ants = getTrackSpec();
339 if( ants instanceof MelodyTrackSpec ) {
340 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
341 mts.nsx39 = nsx39Checkbox.isSelected();
346 public void actionPerformed(ActionEvent e) {
347 Object src = e.getSource();
348 AbstractNoteTrackSpec ants;
349 if( src == nameTextField ) {
350 getTrackSpec().name = nameTextField.getText();
352 else if( src == trackSelecter ) {
353 ants = (AbstractNoteTrackSpec)(trackSelecter.getSelectedItem());
354 String trackTypeString = "Track type: " + (
355 ants instanceof DrumTrackSpec ? "Percussion" :
356 ants instanceof MelodyTrackSpec ? "Melody" : "(Unknown)"
358 trackTypeLabel.setText(trackTypeString);
359 nameTextField.setText(ants.name);
360 chSelecter.setSelectedChannel(ants.midiChannel);
361 keyboardPanel.keyboard.velocityModel.setValue(ants.velocity);
362 pgSelecter.setProgram(ants.programNumber);
363 keyboardPanel.keyboard.clear();
364 if( ants instanceof DrumTrackSpec ) {
365 rangePanel.setVisible(false);
366 randomMelodyCheckbox.setVisible(false);
367 randomLyricCheckbox.setVisible(false);
368 nsx39Checkbox.setVisible(false);
369 bassCheckbox.setVisible(false);
371 else if( ants instanceof MelodyTrackSpec ) {
372 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
373 rangePanel.setVisible(true);
374 keyboardPanel.keyboard.setSelectedNote(ts.range.minNote);
375 keyboardPanel.keyboard.setSelectedNote(ts.range.maxNote);
376 keyboardPanel.keyboard.autoScroll(ts.range.minNote);
377 randomMelodyCheckbox.setSelected(ts.randomMelody);
378 randomLyricCheckbox.setSelected(ts.randomLyric);
379 bassCheckbox.setSelected(ts.isBass);
380 randomMelodyCheckbox.setVisible(true);
381 randomLyricCheckbox.setVisible(true);
382 nsx39Checkbox.setVisible(true);
383 bassCheckbox.setVisible(true);
385 beatPadPanel.setTrackSpec(ants);
387 else if( src == chSelecter.comboBox ) {
388 getTrackSpec().midiChannel = chSelecter.getSelectedChannel();
390 else if( src == pgSelecter ) {
391 getTrackSpec().programNumber = pgSelecter.getProgram();
395 public void pianoKeyPressed(int n, InputEvent e) {
397 AbstractNoteTrackSpec ants = getTrackSpec();
398 if( ants instanceof MelodyTrackSpec ) {
399 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
400 ts.range = new Range(keyboardPanel.keyboard.getSelectedNotes());
404 public void pianoKeyReleased(int n, InputEvent e) { noteOff(n); }
405 public void octaveMoved(ChangeEvent event) {}
406 public void octaveResized(ChangeEvent event) {}
407 public void noteOn(int n) {
408 if( midiChannels == null ) return;
409 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
410 mc.noteOn( n, keyboardPanel.keyboard.velocityModel.getValue() );
412 public void noteOff(int n) {
413 if( midiChannels == null ) return;
414 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
415 mc.noteOff( n, keyboardPanel.keyboard.velocityModel.getValue() );
417 public void setChannels( MidiChannel midiChannels[] ) {
418 this.midiChannels = midiChannels;
420 public AbstractNoteTrackSpec getTrackSpec() {
421 Object trackSpecObj = trackSelecter.getSelectedItem();
422 AbstractNoteTrackSpec ants = (AbstractNoteTrackSpec)trackSpecObj;
423 ants.name = nameTextField.getText();
426 public Vector<AbstractNoteTrackSpec> getTrackSpecs() {
427 Vector<AbstractNoteTrackSpec> trackSpecs = new Vector<>();
428 int i=0, n_items = trackSelecter.getItemCount();
429 while( i < n_items ) {
430 trackSpecs.add((AbstractNoteTrackSpec)trackSelecter.getItemAt(i++));
434 public void addTrackSpec(AbstractNoteTrackSpec trackSpec) {
435 trackSelecter.addItem(trackSpec);
438 private static class MeasureSelecter extends JPanel {
439 public MeasureSelecter() {
440 setLayout(new GridLayout(2,3));
442 add(new JLabel("Start",JLabel.CENTER));
443 add(new JLabel("End",JLabel.CENTER));
444 add(new JLabel("Measure",JLabel.RIGHT));
445 add(new JSpinner(startModel));
446 add(new JSpinner(endModel));
448 private SpinnerNumberModel startModel = new SpinnerNumberModel( 3, 1, 9999, 1 );
449 private SpinnerNumberModel endModel = new SpinnerNumberModel( 8, 1, 9999, 1 );
450 public int getStartMeasurePosition() {
451 return startModel.getNumber().intValue();
453 public int getEndMeasurePosition() {
454 return endModel.getNumber().intValue();
456 public int getMeasureDuration() {
457 return getEndMeasurePosition() - getStartMeasurePosition() + 1;
460 //////////////////////////////////////////////////////////////////
465 private static class BeatPadPanel extends JPanel implements ActionListener {
466 PianoKeyboardListener piano_keyboard_listener;
467 JPanel percussion_selecters_panel;
468 java.util.List<JComboBox<String>> percussionSelecters =
469 new ArrayList<JComboBox<String>>() {
471 for( int i=0; i < DrumTrackSpec.defaultPercussions.length; i++ ) {
472 add(new JComboBox<String>());
478 public BeatPadPanel(PianoKeyboardListener pkl) {
479 piano_keyboard_listener = pkl;
480 percussion_selecters_panel = new JPanel();
481 percussion_selecters_panel.setLayout(
482 new BoxLayout( percussion_selecters_panel, BoxLayout.Y_AXIS )
484 for( JComboBox<String> cb : percussionSelecters ) {
485 percussion_selecters_panel.add(cb);
486 cb.addActionListener(this);
488 add( percussion_selecters_panel );
489 add( beat_pad = new BeatPad(pkl) );
490 beat_pad.setPreferredSize( new Dimension(400,200) );
491 setLayout( new BoxLayout( this, BoxLayout.X_AXIS ) );
493 public void actionPerformed(ActionEvent e) {
494 Object src = e.getSource();
495 for( JComboBox<String> cb : percussionSelecters ) {
496 if( src != cb ) continue;
498 (DrumTrackSpec.PercussionComboBoxModel)cb.getModel()
499 ).getSelectedNoteNo();
500 piano_keyboard_listener.pianoKeyPressed(note_no,(InputEvent)null);
503 public void setTrackSpec( AbstractNoteTrackSpec ants ) {
504 beat_pad.setTrackSpec(ants);
505 if( ants instanceof DrumTrackSpec ) {
506 DrumTrackSpec dts = (DrumTrackSpec)ants;
508 for( JComboBox<String> cb : percussionSelecters ) {
509 cb.setModel(dts.models[i++]);
511 percussion_selecters_panel.setVisible(true);
513 else if( ants instanceof MelodyTrackSpec ) {
514 percussion_selecters_panel.setVisible(false);
518 private static class BeatPad extends JComponent implements MouseListener, ComponentListener {
519 PianoKeyboardListener piano_keyboard_listener;
520 private int on_note_no = -1;
521 AbstractNoteTrackSpec track_spec;
523 public static final int MAX_BEATS = 16;
524 Rectangle beat_buttons[][];
525 Rectangle continuous_beat_buttons[][];
527 public BeatPad(PianoKeyboardListener pkl) {
528 piano_keyboard_listener = pkl;
529 addMouseListener(this);
530 addComponentListener(this);
531 // addMouseMotionListener(this);
533 public void paint(Graphics g) {
535 Graphics2D g2 = (Graphics2D) g;
537 int note, beat, mask;
539 if( track_spec instanceof DrumTrackSpec ) {
540 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
541 for( note=0; note<dts.beat_patterns.length; note++ ) {
542 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
543 r = beat_buttons[note][beat];
544 if( (dts.beat_patterns[note] & mask) != 0 )
545 g2.fillRect( r.x, r.y, r.width, r.height );
547 g2.drawRect( r.x, r.y, r.width, r.height );
551 else if( track_spec instanceof MelodyTrackSpec ) {
552 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
553 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
554 r = beat_buttons[0][beat];
555 if( (mts.beatPattern & mask) != 0 )
556 g2.fillRect( r.x, r.y, r.width, r.height );
558 g2.drawRect( r.x, r.y, r.width, r.height );
559 r = continuous_beat_buttons[0][beat];
560 if( (mts.continuousBeatPattern & mask) != 0 )
561 g2.fillRect( r.x, r.y, r.width, r.height );
563 g2.drawRect( r.x, r.y, r.width, r.height );
568 public void componentShown(ComponentEvent e) { }
569 public void componentHidden(ComponentEvent e) { }
570 public void componentMoved(ComponentEvent e) { }
571 public void componentResized(ComponentEvent e) {
574 public void mousePressed(MouseEvent e) {
576 if( on_note_no >= 0 ) {
577 piano_keyboard_listener.pianoKeyPressed( on_note_no ,(InputEvent)e );
580 public void mouseReleased(MouseEvent e) {
581 if( on_note_no >= 0 ) {
582 piano_keyboard_listener.pianoKeyReleased( on_note_no ,(InputEvent)e );
586 public void mouseEntered(MouseEvent e) {
587 if((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
591 public void mouseExited(MouseEvent e) { }
592 public void mouseClicked(MouseEvent e) { }
593 private void sizeChanged() {
594 int beat, note, width, height;
595 Dimension d = getSize();
597 if( track_spec instanceof DrumTrackSpec ) {
598 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
599 num_notes = dts.models.length;
601 beat_buttons = new Rectangle[num_notes][];
602 continuous_beat_buttons = new Rectangle[num_notes][];
603 for( note=0; note<beat_buttons.length; note++ ) {
604 beat_buttons[note] = new Rectangle[MAX_BEATS];
605 continuous_beat_buttons[note] = new Rectangle[MAX_BEATS];
606 for( beat=0; beat<MAX_BEATS; beat++ ) {
607 width = (d.width * 3) / (MAX_BEATS * 4);
608 height = d.height / num_notes - 1;
609 beat_buttons[note][beat] = new Rectangle(
610 beat * d.width / MAX_BEATS,
615 width = d.width / (MAX_BEATS * 3);
616 continuous_beat_buttons[note][beat] = new Rectangle(
617 (beat+1) * d.width / MAX_BEATS - width + 1,
618 note * height + height / 3,
625 private void catchEvent(MouseEvent e) {
626 Point point = e.getPoint();
627 int note, beat, mask;
630 if( track_spec instanceof DrumTrackSpec ) {
631 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
632 for( note=0; note<dts.beat_patterns.length; note++ ) {
633 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
634 if( beat_buttons[note][beat].contains(point) ) {
635 dts.beat_patterns[note] ^= mask;
636 on_note_no = dts.models[note].getSelectedNoteNo();
642 else if( track_spec instanceof MelodyTrackSpec ) {
643 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
644 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
645 if( beat_buttons[0][beat].contains(point) ) {
646 mts.beatPattern ^= mask;
649 if( continuous_beat_buttons[0][beat].contains(point) ) {
650 mts.continuousBeatPattern ^= mask;
656 public void setTrackSpec( AbstractNoteTrackSpec ants ) {