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.MidiChannel;
21 import javax.sound.midi.Sequence;
22 import javax.swing.AbstractAction;
23 import javax.swing.Action;
24 import javax.swing.BoxLayout;
25 import javax.swing.JButton;
26 import javax.swing.JCheckBox;
27 import javax.swing.JComboBox;
28 import javax.swing.JComponent;
29 import javax.swing.JDialog;
30 import javax.swing.JLabel;
31 import javax.swing.JOptionPane;
32 import javax.swing.JPanel;
33 import javax.swing.JScrollPane;
34 import javax.swing.JSpinner;
35 import javax.swing.JTabbedPane;
36 import javax.swing.JTextArea;
37 import javax.swing.JTextField;
38 import javax.swing.SpinnerNumberModel;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
42 import camidion.chordhelper.ButtonIcon;
43 import camidion.chordhelper.ChordHelperApplet;
44 import camidion.chordhelper.mididevice.VirtualMidiDevice;
45 import camidion.chordhelper.music.AbstractNoteTrackSpec;
46 import camidion.chordhelper.music.ChordProgression;
47 import camidion.chordhelper.music.DrumTrackSpec;
48 import camidion.chordhelper.music.FirstTrackSpec;
49 import camidion.chordhelper.music.MelodyTrackSpec;
50 import camidion.chordhelper.music.Range;
51 import camidion.chordhelper.pianokeyboard.PianoKeyboardListener;
52 import camidion.chordhelper.pianokeyboard.PianoKeyboardPanel;
55 * 新しいMIDIシーケンスを生成するダイアログ
57 public class NewSequenceDialog extends JDialog {
58 private static final Insets ZERO_INSETS = new Insets(0,0,0,0);
59 private static final Integer[] PPQList = {
60 48,60,80,96,120,160,192,240,320,384,480,960
62 private static final String INITIAL_CHORD_STRING =
63 "Key: C\nC G/B | Am Em/G | F C/E | Dm7 G7 C % | F G7 | Csus4 C\n";
64 private JTextArea chordText = new JTextArea(INITIAL_CHORD_STRING, 18, 30);
65 private JTextField seqNameText = new JTextField();
66 private JComboBox<Integer> ppqComboBox = new JComboBox<Integer>(PPQList);
67 private TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter();
68 private TempoSelecter tempoSelecter = new TempoSelecter();
69 private MeasureSelecter measureSelecter = new MeasureSelecter();
70 private TrackSpecPanel trackSpecPanel = new TrackSpecPanel() {{
71 DrumTrackSpec dts = new DrumTrackSpec(9, "Percussion track");
75 mts = new MelodyTrackSpec(2, "Bass track", new Range(36,48));
79 mts = new MelodyTrackSpec(1, "Chord track", new Range(60,72));
81 mts = new MelodyTrackSpec(0, "Melody track", new Range(60,84));
82 mts.randomMelody = true;
83 mts.beatPattern = 0xFFFF;
84 mts.continuousBeatPattern = 0x820A;
90 public Action openAction = new AbstractAction("New") {
92 String tooltip = "Generate new song - 新しい曲を生成";
93 putValue(Action.SHORT_DESCRIPTION, tooltip);
96 public void actionPerformed(ActionEvent e) { setVisible(true); }
98 private PlaylistTableModel playlist;
102 public Action generateAction = new AbstractAction(
103 "Generate & Add to PlayList", new ButtonIcon(ButtonIcon.EJECT_ICON)
106 public void actionPerformed(ActionEvent event) {
108 playlist.getSequenceModelList().get(playlist.play(getMidiSequence())).setModified(true);
109 } catch (Exception ex) {
110 JOptionPane.showMessageDialog(
111 NewSequenceDialog.this, ex,
112 ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
118 * 新しいMIDIシーケンスを生成するダイアログを構築します。
119 * @param playlist シーケンス追加先プレイリスト
120 * @param midiOutDevice 操作音を出力するMIDI出力デバイス
122 public NewSequenceDialog(PlaylistTableModel playlist, VirtualMidiDevice midiOutDevice) {
123 this.playlist = playlist;
124 trackSpecPanel.setChannels(midiOutDevice.getChannels());
125 setTitle("Generate new sequence - " + ChordHelperApplet.VersionInfo.NAME);
126 add(new JTabbedPane() {{
127 add("Sequence", new JPanel() {{
128 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
130 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
131 add(new JLabel("Sequence name:"));
135 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
136 add(new JLabel("Resolution in PPQ ="));
138 add(measureSelecter);
140 add(new JButton("Randomize (Tempo, Time signature, Chord progression)") {{
141 setMargin(ZERO_INSETS);
142 addActionListener(e->setRandomChordProgression(measureSelecter.getMeasureDuration()));
145 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
148 add(new JLabel("Time signature ="));
149 add(timesigSelecter);
153 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
154 add(new JLabel("Chord progression :"));
155 add(new JLabel("Transpose"));
156 add(new JButton(" + Up ") {{
157 setMargin(ZERO_INSETS);
158 addActionListener(e->{
159 ChordProgression cp = createChordProgression();
161 setChordProgression(cp);
164 add(new JButton(" - Down ") {{
165 setMargin(ZERO_INSETS);
166 addActionListener(e->{
167 ChordProgression cp = createChordProgression();
169 setChordProgression(cp);
172 add(new JButton(" Enharmonic ") {{
173 setMargin(ZERO_INSETS);
174 addActionListener(e->{
175 ChordProgression cp = createChordProgression();
176 cp.toggleEnharmonically();
177 setChordProgression(cp);
180 add(new JButton("Relative key") {{
181 setMargin(ZERO_INSETS);
182 addActionListener(e->{
183 ChordProgression cp = createChordProgression();
184 cp.toggleKeyMajorMinor();
185 setChordProgression(cp);
189 add(new JScrollPane(chordText));
191 setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
192 add(new JButton(generateAction){{setMargin(ZERO_INSETS);}});
195 add("Track", trackSpecPanel);
197 setBounds(250,200,600,540);
203 private ChordProgression createChordProgression() {
204 return new ChordProgression(chordText.getText());
207 * MIDIシーケンスを生成して返します。
210 public Sequence getMidiSequence() {
211 FirstTrackSpec firstTrackSpec = new FirstTrackSpec(
212 seqNameText.getText(),
213 tempoSelecter.getTempoByteArray(),
214 timesigSelecter.getByteArray()
216 return createChordProgression().toMidiSequence(
217 (int)ppqComboBox.getSelectedItem(),
218 measureSelecter.getStartMeasurePosition(),
219 measureSelecter.getEndMeasurePosition(),
221 trackSpecPanel.getTrackSpecs()
225 * コード進行を設定します。テキスト欄に反映されます。
228 public void setChordProgression(ChordProgression cp) {
229 chordText.setText(cp.toString());
232 * テンポ・拍子・コード進行をランダムに設定
233 * @param measureLength 小節数
235 public void setRandomChordProgression(int measureLength) {
236 tempoSelecter.setTempo( 80 + (int)(Math.random() * 100) );
237 int timesig_upper = 4;
238 int timesig_lower_index = 2;
239 switch( (int)(Math.random() * 10) ) {
240 case 0: timesig_upper = 3; break; // 3/4
242 timesigSelecter.setValue((byte)timesig_upper, (byte)timesig_lower_index);
243 setChordProgression(new ChordProgression(measureLength, timesig_upper));
248 private static class TrackSpecPanel extends JPanel
249 implements PianoKeyboardListener, ActionListener, ChangeListener
251 JComboBox<AbstractNoteTrackSpec> trackSelecter = new JComboBox<>();
252 JLabel trackTypeLabel = new JLabel();
253 JTextField nameTextField = new JTextField(20);
254 MidiChannelComboSelecter chSelecter =
255 new MidiChannelComboSelecter("MIDI Channel:");
256 MidiProgramSelecter pgSelecter = new MidiProgramSelecter();
257 MidiProgramFamilySelecter pgFamilySelecter =
258 new MidiProgramFamilySelecter(pgSelecter) {{
259 pgSelecter.setFamilySelecter(pgFamilySelecter);
261 PianoKeyboardPanel keyboardPanel = new PianoKeyboardPanel() {{
262 keyboard.octaveSizeModel.setValue(6);
263 keyboard.setPreferredSize(new Dimension(400,40));
264 keyboard.setMaxSelectable(2);
266 JPanel rangePanel = new JPanel() {{
267 add( new JLabel("Range:") );
270 JCheckBox randomMelodyCheckbox = new JCheckBox("Random melody");
271 JCheckBox bassCheckbox = new JCheckBox("Bass note");
272 JCheckBox randomLyricCheckbox = new JCheckBox("Random lyrics");
273 JCheckBox nsx39Checkbox = new JCheckBox("NSX-39");;
274 BeatPadPanel beatPadPanel = new BeatPadPanel(this);
275 private MidiChannel[] midiChannels;
277 public TrackSpecPanel() {
278 nameTextField.addActionListener(this);
279 keyboardPanel.keyboard.addPianoKeyboardListener(this);
281 add(new JLabel("Track select:"));
286 add(new JLabel("Track name (Press [Enter] key to change):"));
290 add(new VelocitySelecter(keyboardPanel.keyboard.velocityModel));
292 add(pgFamilySelecter);
296 bassCheckbox.addChangeListener(this);
298 randomMelodyCheckbox.addChangeListener(this);
299 add(randomMelodyCheckbox);
300 randomLyricCheckbox.addChangeListener(this);
301 add(randomLyricCheckbox);
302 nsx39Checkbox.addChangeListener(this);
305 trackSelecter.addActionListener(this);
306 chSelecter.comboBox.addActionListener(this);
307 keyboardPanel.keyboard.velocityModel.addChangeListener(
308 e -> getTrackSpec().velocity = keyboardPanel.keyboard.velocityModel.getValue()
310 pgSelecter.addActionListener(this);
313 public void stateChanged(ChangeEvent e) {
314 Object src = e.getSource();
315 if( src == bassCheckbox ) {
316 AbstractNoteTrackSpec ants = getTrackSpec();
317 if( ants instanceof MelodyTrackSpec ) {
318 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
319 mts.isBass = bassCheckbox.isSelected();
322 else if( src == randomMelodyCheckbox ) {
323 AbstractNoteTrackSpec ants = getTrackSpec();
324 if( ants instanceof MelodyTrackSpec ) {
325 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
326 mts.randomMelody = randomMelodyCheckbox.isSelected();
329 else if( src == randomLyricCheckbox ) {
330 AbstractNoteTrackSpec ants = getTrackSpec();
331 if( ants instanceof MelodyTrackSpec ) {
332 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
333 mts.randomLyric = randomLyricCheckbox.isSelected();
336 else if( src == nsx39Checkbox ) {
337 AbstractNoteTrackSpec ants = getTrackSpec();
338 if( ants instanceof MelodyTrackSpec ) {
339 MelodyTrackSpec mts = (MelodyTrackSpec)ants;
340 mts.nsx39 = nsx39Checkbox.isSelected();
345 public void actionPerformed(ActionEvent e) {
346 Object src = e.getSource();
347 AbstractNoteTrackSpec ants;
348 if( src == nameTextField ) {
349 getTrackSpec().name = nameTextField.getText();
351 else if( src == trackSelecter ) {
352 ants = (AbstractNoteTrackSpec)(trackSelecter.getSelectedItem());
353 String trackTypeString = "Track type: " + (
354 ants instanceof DrumTrackSpec ? "Percussion" :
355 ants instanceof MelodyTrackSpec ? "Melody" : "(Unknown)"
357 trackTypeLabel.setText(trackTypeString);
358 nameTextField.setText(ants.name);
359 chSelecter.setSelectedChannel(ants.midiChannel);
360 keyboardPanel.keyboard.velocityModel.setValue(ants.velocity);
361 pgSelecter.setProgram(ants.programNumber);
362 keyboardPanel.keyboard.clear();
363 if( ants instanceof DrumTrackSpec ) {
364 rangePanel.setVisible(false);
365 randomMelodyCheckbox.setVisible(false);
366 randomLyricCheckbox.setVisible(false);
367 nsx39Checkbox.setVisible(false);
368 bassCheckbox.setVisible(false);
370 else if( ants instanceof MelodyTrackSpec ) {
371 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
372 rangePanel.setVisible(true);
373 keyboardPanel.keyboard.setSelectedNote(ts.range.minNote);
374 keyboardPanel.keyboard.setSelectedNote(ts.range.maxNote);
375 keyboardPanel.keyboard.autoScroll(ts.range.minNote);
376 randomMelodyCheckbox.setSelected(ts.randomMelody);
377 randomLyricCheckbox.setSelected(ts.randomLyric);
378 bassCheckbox.setSelected(ts.isBass);
379 randomMelodyCheckbox.setVisible(true);
380 randomLyricCheckbox.setVisible(true);
381 nsx39Checkbox.setVisible(true);
382 bassCheckbox.setVisible(true);
384 beatPadPanel.setTrackSpec(ants);
386 else if( src == chSelecter.comboBox ) {
387 getTrackSpec().midiChannel = chSelecter.getSelectedChannel();
389 else if( src == pgSelecter ) {
390 getTrackSpec().programNumber = pgSelecter.getProgram();
394 public void pianoKeyPressed(int n, InputEvent e) {
396 AbstractNoteTrackSpec ants = getTrackSpec();
397 if( ants instanceof MelodyTrackSpec ) {
398 MelodyTrackSpec ts = (MelodyTrackSpec)ants;
399 ts.range = new Range(keyboardPanel.keyboard.getSelectedNotes());
403 public void pianoKeyReleased(int n, InputEvent e) { noteOff(n); }
404 public void octaveMoved(ChangeEvent event) {}
405 public void octaveResized(ChangeEvent event) {}
406 public void noteOn(int n) {
407 if( midiChannels == null ) return;
408 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
409 mc.noteOn( n, keyboardPanel.keyboard.velocityModel.getValue() );
411 public void noteOff(int n) {
412 if( midiChannels == null ) return;
413 MidiChannel mc = midiChannels[chSelecter.getSelectedChannel()];
414 mc.noteOff( n, keyboardPanel.keyboard.velocityModel.getValue() );
416 public void setChannels( MidiChannel midiChannels[] ) {
417 this.midiChannels = midiChannels;
419 public AbstractNoteTrackSpec getTrackSpec() {
420 Object trackSpecObj = trackSelecter.getSelectedItem();
421 AbstractNoteTrackSpec ants = (AbstractNoteTrackSpec)trackSpecObj;
422 ants.name = nameTextField.getText();
425 public Vector<AbstractNoteTrackSpec> getTrackSpecs() {
426 Vector<AbstractNoteTrackSpec> trackSpecs = new Vector<>();
427 int i=0, n_items = trackSelecter.getItemCount();
428 while( i < n_items ) {
429 trackSpecs.add((AbstractNoteTrackSpec)trackSelecter.getItemAt(i++));
433 public void addTrackSpec(AbstractNoteTrackSpec trackSpec) {
434 trackSelecter.addItem(trackSpec);
437 private static class MeasureSelecter extends JPanel {
438 public MeasureSelecter() {
439 setLayout(new GridLayout(2,3));
441 add(new JLabel("Start",JLabel.CENTER));
442 add(new JLabel("End",JLabel.CENTER));
443 add(new JLabel("Measure",JLabel.RIGHT));
444 add(new JSpinner(startModel));
445 add(new JSpinner(endModel));
447 private SpinnerNumberModel startModel = new SpinnerNumberModel( 3, 1, 9999, 1 );
448 private SpinnerNumberModel endModel = new SpinnerNumberModel( 8, 1, 9999, 1 );
449 public int getStartMeasurePosition() {
450 return startModel.getNumber().intValue();
452 public int getEndMeasurePosition() {
453 return endModel.getNumber().intValue();
455 public int getMeasureDuration() {
456 return getEndMeasurePosition() - getStartMeasurePosition() + 1;
459 //////////////////////////////////////////////////////////////////
464 private static class BeatPadPanel extends JPanel implements ActionListener {
465 PianoKeyboardListener piano_keyboard_listener;
466 JPanel percussion_selecters_panel;
467 java.util.List<JComboBox<String>> percussionSelecters =
468 new ArrayList<JComboBox<String>>() {
470 for( int i=0; i < DrumTrackSpec.defaultPercussions.length; i++ ) {
471 add(new JComboBox<String>());
477 public BeatPadPanel(PianoKeyboardListener pkl) {
478 piano_keyboard_listener = pkl;
479 percussion_selecters_panel = new JPanel();
480 percussion_selecters_panel.setLayout(
481 new BoxLayout( percussion_selecters_panel, BoxLayout.Y_AXIS )
483 for( JComboBox<String> cb : percussionSelecters ) {
484 percussion_selecters_panel.add(cb);
485 cb.addActionListener(this);
487 add( percussion_selecters_panel );
488 add( beat_pad = new BeatPad(pkl) );
489 beat_pad.setPreferredSize( new Dimension(400,200) );
490 setLayout( new BoxLayout( this, BoxLayout.X_AXIS ) );
492 public void actionPerformed(ActionEvent e) {
493 Object src = e.getSource();
494 for( JComboBox<String> cb : percussionSelecters ) {
495 if( src != cb ) continue;
497 (DrumTrackSpec.PercussionComboBoxModel)cb.getModel()
498 ).getSelectedNoteNo();
499 piano_keyboard_listener.pianoKeyPressed(note_no,(InputEvent)null);
502 public void setTrackSpec( AbstractNoteTrackSpec ants ) {
503 beat_pad.setTrackSpec(ants);
504 if( ants instanceof DrumTrackSpec ) {
505 DrumTrackSpec dts = (DrumTrackSpec)ants;
507 for( JComboBox<String> cb : percussionSelecters ) {
508 cb.setModel(dts.models[i++]);
510 percussion_selecters_panel.setVisible(true);
512 else if( ants instanceof MelodyTrackSpec ) {
513 percussion_selecters_panel.setVisible(false);
517 private static class BeatPad extends JComponent implements MouseListener, ComponentListener {
518 PianoKeyboardListener piano_keyboard_listener;
519 private int on_note_no = -1;
520 AbstractNoteTrackSpec track_spec;
522 public static final int MAX_BEATS = 16;
523 Rectangle beat_buttons[][];
524 Rectangle continuous_beat_buttons[][];
526 public BeatPad(PianoKeyboardListener pkl) {
527 piano_keyboard_listener = pkl;
528 addMouseListener(this);
529 addComponentListener(this);
530 // addMouseMotionListener(this);
532 public void paint(Graphics g) {
534 Graphics2D g2 = (Graphics2D) g;
536 int note, beat, mask;
538 if( track_spec instanceof DrumTrackSpec ) {
539 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
540 for( note=0; note<dts.beat_patterns.length; note++ ) {
541 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
542 r = beat_buttons[note][beat];
543 if( (dts.beat_patterns[note] & mask) != 0 )
544 g2.fillRect( r.x, r.y, r.width, r.height );
546 g2.drawRect( r.x, r.y, r.width, r.height );
550 else if( track_spec instanceof MelodyTrackSpec ) {
551 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
552 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
553 r = beat_buttons[0][beat];
554 if( (mts.beatPattern & mask) != 0 )
555 g2.fillRect( r.x, r.y, r.width, r.height );
557 g2.drawRect( r.x, r.y, r.width, r.height );
558 r = continuous_beat_buttons[0][beat];
559 if( (mts.continuousBeatPattern & mask) != 0 )
560 g2.fillRect( r.x, r.y, r.width, r.height );
562 g2.drawRect( r.x, r.y, r.width, r.height );
567 public void componentShown(ComponentEvent e) { }
568 public void componentHidden(ComponentEvent e) { }
569 public void componentMoved(ComponentEvent e) { }
570 public void componentResized(ComponentEvent e) {
573 public void mousePressed(MouseEvent e) {
575 if( on_note_no >= 0 ) {
576 piano_keyboard_listener.pianoKeyPressed( on_note_no ,(InputEvent)e );
579 public void mouseReleased(MouseEvent e) {
580 if( on_note_no >= 0 ) {
581 piano_keyboard_listener.pianoKeyReleased( on_note_no ,(InputEvent)e );
585 public void mouseEntered(MouseEvent e) {
586 if((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
590 public void mouseExited(MouseEvent e) { }
591 public void mouseClicked(MouseEvent e) { }
592 private void sizeChanged() {
593 int beat, note, width, height;
594 Dimension d = getSize();
596 if( track_spec instanceof DrumTrackSpec ) {
597 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
598 num_notes = dts.models.length;
600 beat_buttons = new Rectangle[num_notes][];
601 continuous_beat_buttons = new Rectangle[num_notes][];
602 for( note=0; note<beat_buttons.length; note++ ) {
603 beat_buttons[note] = new Rectangle[MAX_BEATS];
604 continuous_beat_buttons[note] = new Rectangle[MAX_BEATS];
605 for( beat=0; beat<MAX_BEATS; beat++ ) {
606 width = (d.width * 3) / (MAX_BEATS * 4);
607 height = d.height / num_notes - 1;
608 beat_buttons[note][beat] = new Rectangle(
609 beat * d.width / MAX_BEATS,
614 width = d.width / (MAX_BEATS * 3);
615 continuous_beat_buttons[note][beat] = new Rectangle(
616 (beat+1) * d.width / MAX_BEATS - width + 1,
617 note * height + height / 3,
624 private void catchEvent(MouseEvent e) {
625 Point point = e.getPoint();
626 int note, beat, mask;
629 if( track_spec instanceof DrumTrackSpec ) {
630 DrumTrackSpec dts = (DrumTrackSpec)track_spec;
631 for( note=0; note<dts.beat_patterns.length; note++ ) {
632 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
633 if( beat_buttons[note][beat].contains(point) ) {
634 dts.beat_patterns[note] ^= mask;
635 on_note_no = dts.models[note].getSelectedNoteNo();
641 else if( track_spec instanceof MelodyTrackSpec ) {
642 MelodyTrackSpec mts = (MelodyTrackSpec)track_spec;
643 for( beat=0, mask=0x8000; beat<MAX_BEATS; beat++, mask >>>= 1 ) {
644 if( beat_buttons[0][beat].contains(point) ) {
645 mts.beatPattern ^= mask;
648 if( continuous_beat_buttons[0][beat].contains(point) ) {
649 mts.continuousBeatPattern ^= mask;
655 public void setTrackSpec( AbstractNoteTrackSpec ants ) {