1 package camidion.chordhelper.chordmatrix;
\r
3 import java.awt.Color;
\r
4 import java.awt.Component;
\r
5 import java.awt.Dimension;
\r
6 import java.awt.Font;
\r
7 import java.awt.Graphics;
\r
8 import java.awt.Graphics2D;
\r
9 import java.awt.GridLayout;
\r
10 import java.awt.event.ActionEvent;
\r
11 import java.awt.event.ActionListener;
\r
12 import java.awt.event.FocusEvent;
\r
13 import java.awt.event.FocusListener;
\r
14 import java.awt.event.InputEvent;
\r
15 import java.awt.event.ItemEvent;
\r
16 import java.awt.event.ItemListener;
\r
17 import java.awt.event.KeyEvent;
\r
18 import java.awt.event.KeyListener;
\r
19 import java.awt.event.MouseEvent;
\r
20 import java.awt.event.MouseListener;
\r
21 import java.awt.event.MouseMotionListener;
\r
22 import java.awt.event.MouseWheelEvent;
\r
23 import java.awt.event.MouseWheelListener;
\r
24 import java.util.ArrayList;
\r
26 import javax.swing.ComboBoxModel;
\r
27 import javax.swing.DefaultComboBoxModel;
\r
28 import javax.swing.JComponent;
\r
29 import javax.swing.JLabel;
\r
30 import javax.swing.JOptionPane;
\r
31 import javax.swing.JPanel;
\r
33 import camidion.chordhelper.ButtonIcon;
\r
34 import camidion.chordhelper.ChordDisplayLabel;
\r
35 import camidion.chordhelper.chorddiagram.CapoSelecterView;
\r
36 import camidion.chordhelper.midieditor.SequenceTickIndex;
\r
37 import camidion.chordhelper.music.Chord;
\r
38 import camidion.chordhelper.music.Key;
\r
39 import camidion.chordhelper.music.Music;
\r
40 import camidion.chordhelper.music.NoteSymbol;
\r
41 import camidion.chordhelper.music.SymbolLanguage;
\r
44 * MIDI Chord Helper 用のコードボタンマトリクス
\r
47 * Copyright (C) 2004-2013 Akiyoshi Kamide
\r
48 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
50 public class ChordMatrix extends JPanel
\r
51 implements MouseListener, KeyListener, MouseMotionListener, MouseWheelListener
\r
56 public static final int N_COLUMNS = Music.SEMITONES_PER_OCTAVE * 2 + 1;
\r
60 public static final int CHORD_BUTTON_ROWS = 3;
\r
64 public Co5Label keysigLabels[] = new Co5Label[ N_COLUMNS ];
\r
68 public ChordLabel chordLabels[] = new ChordLabel[N_COLUMNS * CHORD_BUTTON_ROWS];
\r
72 public ChordDisplayLabel chordDisplay = new ChordDisplayLabel("Chord Pad", this, null);
\r
74 private static class ChordLabelSelection {
\r
75 ChordLabel chordLabel;
\r
78 public ChordLabelSelection(ChordLabel chordLabel, int bitIndex) {
\r
79 this.chordLabel = chordLabel;
\r
80 this.bitIndex = bitIndex;
\r
81 this.isSus4 = chordLabel.isSus4;
\r
83 public void setCheckBit(boolean isOn) {
\r
84 chordLabel.setCheckBit(isOn, bitIndex);
\r
86 public boolean setBassCheckBit(boolean isOn) {
\r
87 if( bitIndex == 0 && ! isSus4 ) {
\r
88 chordLabel.setCheckBit(isOn, 6);
\r
94 private static class ChordLabelSelections {
\r
96 int bass_weight = 0;
\r
97 boolean is_active = false;
\r
98 boolean is_bass_active = false;
\r
99 private ChordLabelSelection acls[];
\r
100 public ChordLabelSelections(ArrayList<ChordLabelSelection> al) {
\r
101 acls = al.toArray(new ChordLabelSelection[al.size()]);
\r
103 void addWeight(int weight_diff) {
\r
104 if( (weight += weight_diff) < 0 ) weight = 0;
\r
105 if( (weight > 0) != is_active ) {
\r
106 is_active = !is_active;
\r
107 for( ChordLabelSelection cls : acls ) {
\r
108 cls.setCheckBit(is_active);
\r
112 void addBassWeight(int weight_diff) {
\r
113 if( (bass_weight += weight_diff) < 0 ) bass_weight = 0;
\r
114 if( (bass_weight > 0) != is_bass_active ) {
\r
115 is_bass_active = !is_bass_active;
\r
116 for( ChordLabelSelection cls : acls ) {
\r
117 if( ! cls.setBassCheckBit(is_bass_active) ) {
\r
118 // No more root major/minor
\r
123 addWeight(weight_diff);
\r
125 void clearWeight() {
\r
126 weight = bass_weight = 0;
\r
127 is_active = is_bass_active = false;
\r
128 for( ChordLabelSelection cls : acls ) {
\r
129 cls.setCheckBit(false);
\r
130 cls.setBassCheckBit(false);
\r
134 private ChordLabelSelections chordLabelSelections[] =
\r
135 new ChordLabelSelections[Music.SEMITONES_PER_OCTAVE];
\r
137 * 発音中のノート表示をクリアします。
\r
139 public void clearIndicators() {
\r
140 for( int i=0; i<chordLabelSelections.length; i++ ) {
\r
141 chordLabelSelections[i].clearWeight();
\r
146 * MIDIのノートイベント(ON/OFF)を受け取ります。
\r
147 * @param isNoteOn ONのときtrue
\r
148 * @param noteNumber ノート番号
\r
150 public void note(boolean isNoteOn, int noteNumber) {
\r
151 int weightDiff = (isNoteOn ? 1 : -1);
\r
152 ChordLabelSelections cls = chordLabelSelections[Music.mod12(noteNumber)];
\r
153 if( noteNumber < 49 )
\r
154 cls.addBassWeight(weightDiff);
\r
156 cls.addWeight(weightDiff);
\r
162 private class Co5Label extends JLabel {
\r
163 public boolean isSelected = false;
\r
164 public int co5Value = 0;
\r
165 private Color indicatorColor;
\r
166 public Co5Label(int v) {
\r
167 Key key = new Key(co5Value = v);
\r
169 setBackground(false);
\r
170 setForeground( currentColorset.foregrounds[0] );
\r
171 setHorizontalAlignment( JLabel.CENTER );
\r
172 String tip = "Key signature: ";
\r
173 if( v != key.toCo5() ) {
\r
174 tip += "out of range" ;
\r
177 tip += key.signatureDescription() + " " +
\r
178 key.toStringIn(SymbolLanguage.IN_JAPANESE);
\r
180 setIcon(new ButtonIcon(ButtonIcon.NATURAL_ICON));
\r
183 setFont( getFont().deriveFont(Font.PLAIN) );
\r
184 setText( key.signature() );
\r
187 setToolTipText(tip);
\r
189 public void paint(Graphics g) {
\r
191 Dimension d = getSize();
\r
192 if( ChordMatrix.this.isFocusOwner() && isSelected ) {
\r
193 g.setColor( currentColorset.focus[1] );
\r
194 g.drawRect( 0, 0, d.width-1, d.height-1 );
\r
196 if( !isSelected || !isPlaying || currentBeat+1 == timesigUpper ) {
\r
199 if( currentBeat == 0 ) {
\r
200 g.setColor( indicatorColor );
\r
201 g.drawRect( 2, 2, d.width-5, d.height-5 );
\r
202 g.setColor( isDark ? indicatorColor.darker() : indicatorColor.brighter() );
\r
203 g.drawRect( 0, 0, d.width-1, d.height-1 );
\r
206 Color color = currentColorset.indicators[0];
\r
207 g.setColor( color );
\r
208 if( currentBeat == 1 ) {
\r
211 g.drawLine( 2, d.height-3, d.width-3, d.height-3 );
\r
212 g.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
\r
213 g.drawLine( 2, 2, 2, d.height-3 );
\r
214 g.setColor( isDark ? color.darker() : color.brighter() );
\r
215 g.drawLine( 0, d.height-1, d.width-1, d.height-1 );
\r
216 g.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
\r
217 g.drawLine( 0, 0, 0, d.height-1 );
\r
223 int vertical_top = (d.height-1) * (currentBeat-1) / (timesigUpper-2) ;
\r
224 g.drawLine( 2, vertical_top == 0 ? 2 : vertical_top, 2, d.height-3 );
\r
225 g.setColor( isDark ? color.darker() : color.brighter() );
\r
226 g.drawLine( 0, vertical_top, 0, d.height-1 );
\r
229 public void setBackground(boolean isActive) {
\r
230 super.setBackground(currentColorset.backgrounds[isActive?2:0]);
\r
231 setIndicatorColor();
\r
234 public void setSelection(boolean isSelected) {
\r
235 this.isSelected = isSelected;
\r
238 public void setSelection() {
\r
239 setForeground(currentColorset.foregrounds[isSelected?1:0]);
\r
241 public void setIndicatorColor() {
\r
242 if( co5Value < 0 ) {
\r
243 indicatorColor = currentColorset.indicators[2];
\r
245 else if( co5Value > 0 ) {
\r
246 indicatorColor = currentColorset.indicators[1];
\r
249 indicatorColor = currentColorset.foregrounds[1];
\r
256 private class ChordLabel extends JLabel {
\r
257 public byte checkBits = 0;
\r
258 public int co5Value;
\r
259 public boolean isMinor;
\r
260 public boolean isSus4;
\r
261 public boolean isSelected = false;
\r
262 public Chord chord;
\r
264 private boolean inActiveZone = true;
\r
265 private Font boldFont;
\r
266 private Font plainFont;
\r
267 private int indicatorColorIndices[] = new int[5];
\r
268 private byte indicatorBits = 0;
\r
270 public ChordLabel(Chord chord) {
\r
271 this.chord = chord;
\r
272 isMinor = chord.isSet(Chord.Interval.MINOR);
\r
273 isSus4 = chord.isSet(Chord.Interval.SUS4);
\r
274 co5Value = chord.rootNoteSymbol().toCo5();
\r
275 if( isMinor ) co5Value -= 3;
\r
276 String labelText = ( isSus4 ? chord.symbolSuffix() : chord.toString() );
\r
277 if( isMinor && labelText.length() > 3 ) {
\r
278 float small_point_size = getFont().getSize2D() - 2;
\r
279 boldFont = getFont().deriveFont(Font.BOLD, small_point_size);
\r
280 plainFont = getFont().deriveFont(Font.PLAIN, small_point_size);
\r
283 boldFont = getFont().deriveFont(Font.BOLD);
\r
284 plainFont = getFont().deriveFont(Font.PLAIN);
\r
288 setForeground( currentColorset.foregrounds[0] );
\r
290 setHorizontalAlignment( JLabel.CENTER );
\r
291 setText(labelText);
\r
292 setToolTipText( "Chord: " + chord.toName() );
\r
294 public void paint(Graphics g) {
\r
296 Dimension d = getSize();
\r
297 Graphics2D g2 = (Graphics2D) g;
\r
298 Color color = null;
\r
300 if( ! inActiveZone ) {
\r
301 g2.setColor( Color.gray );
\r
304 if( (indicatorBits & 32) != 0 ) {
\r
306 // Draw square [] with 3rd/sus4th note color
\r
308 if( inActiveZone ) {
\r
309 color = currentColorset.indicators[indicatorColorIndices[1]];
\r
310 g2.setColor( color );
\r
312 g2.drawRect( 0, 0, d.width-1, d.height-1 );
\r
313 g2.drawRect( 2, 2, d.width-5, d.height-5 );
\r
315 if( (indicatorBits & 1) != 0 ) {
\r
317 // Draw ||__ with root note color
\r
319 if( inActiveZone ) {
\r
320 color = currentColorset.indicators[indicatorColorIndices[0]];
\r
321 g2.setColor( color );
\r
323 g2.drawLine( 0, 0, 0, d.height-1 );
\r
324 g2.drawLine( 2, 2, 2, d.height-3 );
\r
326 if( (indicatorBits & 64) != 0 ) {
\r
327 // Draw bass mark with root note color
\r
329 if( inActiveZone ) {
\r
330 color = currentColorset.indicators[indicatorColorIndices[0]];
\r
331 g2.setColor( color );
\r
333 g2.fillRect( 6, d.height-7, d.width-12, 2 );
\r
335 if( (indicatorBits & 4) != 0 ) {
\r
337 // Draw short __ii with parfect 5th color
\r
339 if( inActiveZone ) {
\r
340 color = currentColorset.indicators[indicatorColorIndices[2]];
\r
341 g2.setColor( color );
\r
343 g2.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
\r
344 g2.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
\r
346 if( (indicatorBits & 2) != 0 ) {
\r
348 // Draw __ with 3rd note color
\r
350 if( inActiveZone ) {
\r
351 color = currentColorset.indicators[indicatorColorIndices[1]];
\r
352 g2.setColor( color );
\r
354 g2.drawLine( 0, d.height-1, d.width-1, d.height-1 );
\r
355 g2.drawLine( 2, d.height-3, d.width-3, d.height-3 );
\r
357 if( (indicatorBits & 8) != 0 ) {
\r
359 // Draw circle with diminished 5th color
\r
361 if( inActiveZone ) {
\r
362 g2.setColor( currentColorset.indicators[indicatorColorIndices[3]] );
\r
364 g2.drawOval( 1, 1, d.width-2, d.height-2 );
\r
366 if( (indicatorBits & 16) != 0 ) {
\r
368 // Draw + with augument 5th color
\r
370 if( inActiveZone ) {
\r
371 g2.setColor( currentColorset.indicators[indicatorColorIndices[4]] );
\r
373 g2.drawLine( 1, 3, d.width-3, 3 );
\r
374 g2.drawLine( 1, 4, d.width-3, 4 );
\r
375 g2.drawLine( d.width/2-1, 0, d.width/2-1, 7 );
\r
376 g2.drawLine( d.width/2, 0, d.width/2, 7 );
\r
379 public void setCheckBit( boolean is_on, int bit_index ) {
\r
381 // Check bits: x6x43210
\r
383 // 4:Augumented5th, 3:Diminished5th, 2:Parfect5th,
\r
384 // 1:Major3rd/minor3rd/sus4th, 0:Root
\r
386 byte mask = ((byte)(1<<bit_index));
\r
387 byte old_check_bits = checkBits;
\r
392 checkBits &= ~mask;
\r
394 if( old_check_bits == checkBits ) {
\r
398 // Indicator bits: x6543210 6:Bass||_ 5:[] 4:+ 3:O 2:_ii 1:__ 0:||_
\r
400 byte indicator_bits = 0;
\r
401 if( (checkBits & 1) != 0 ) {
\r
402 if( (checkBits & 7) == 7 ) { // All triad notes appared
\r
405 indicator_bits |= 0x20;
\r
407 // Draw different-colored vertical lines
\r
408 if( indicatorColorIndices[0] != indicatorColorIndices[1] ) {
\r
409 indicator_bits |= 1;
\r
411 if( indicatorColorIndices[2] != indicatorColorIndices[1] ) {
\r
412 indicator_bits |= 4;
\r
415 else if( !isSus4 ) {
\r
417 // Draw vertical lines || ii
\r
418 indicator_bits |= 5;
\r
420 if( (checkBits & 2) != 0 && (!isMinor || (checkBits & 0x18) != 0) ) {
\r
422 // Draw horizontal bottom lines __
\r
423 indicator_bits |= 2;
\r
427 if( isMinor || (checkBits & 2) != 0 ) {
\r
428 indicator_bits |= (byte)(checkBits & 0x18); // Copy bit 3 and bit 4
\r
430 if( (checkBits & 0x40) != 0 ) {
\r
431 indicator_bits |= 0x40; // Bass
\r
435 if( this.indicatorBits == indicator_bits ) {
\r
436 // No shapes changed
\r
439 this.indicatorBits = indicator_bits;
\r
442 public void setBackground(int i) {
\r
448 super.setBackground(currentColorset.backgrounds[i]);
\r
454 public void setSelection(boolean is_selected) {
\r
455 this.isSelected = is_selected;
\r
458 public void setSelection() {
\r
459 setForeground(currentColorset.foregrounds[this.isSelected?1:0]);
\r
461 public void setBold(boolean is_bold) {
\r
462 setFont( is_bold ? boldFont : plainFont );
\r
464 public void keyChanged() {
\r
465 int co5_key = capoKey.toCo5();
\r
466 int co5_offset = co5Value - co5_key;
\r
467 inActiveZone = (co5_offset <= 6 && co5_offset >= -6) ;
\r
468 int root_note = chord.rootNoteSymbol().toNoteNumber();
\r
470 // Reconstruct color index
\r
473 indicatorColorIndices[0] = Music.isOnScale(
\r
475 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
478 indicatorColorIndices[1] = Music.isOnScale(
\r
479 root_note+(isMinor?3:isSus4?5:4), co5_key
\r
480 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
483 indicatorColorIndices[2] = Music.isOnScale(
\r
484 root_note+7, co5_key
\r
485 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
488 indicatorColorIndices[3] = Music.isOnScale(
\r
489 root_note+6, co5_key
\r
490 ) ? 0 : co5_offset > 4 ? 1 : 2;
\r
493 indicatorColorIndices[4] = Music.isOnScale(
\r
494 root_note+8, co5_key
\r
495 ) ? 0 : co5_offset > -3 ? 1 : 2;
\r
502 public class ColorSet {
\r
503 Color[] focus = new Color[2]; // 0:lost 1:gained
\r
504 Color[] foregrounds = new Color[2]; // 0:unselected 1:selected
\r
505 public Color[] backgrounds = new Color[4]; // 0:remote 1:left 2:local 3:right
\r
506 Color[] indicators = new Color[3]; // 0:natural 1:sharp 2:flat
\r
508 public ColorSet normalModeColorset = new ColorSet() {
\r
510 foregrounds[0] = null;
\r
511 foregrounds[1] = new Color(0xFF,0x3F,0x3F);
\r
512 backgrounds[0] = new Color(0xCF,0xFF,0xCF);
\r
513 backgrounds[1] = new Color(0x9F,0xFF,0xFF);
\r
514 backgrounds[2] = new Color(0xFF,0xCF,0xCF);
\r
515 backgrounds[3] = new Color(0xFF,0xFF,0x9F);
\r
516 indicators[0] = new Color(0xFF,0x3F,0x3F);
\r
517 indicators[1] = new Color(0xCF,0x6F,0x00);
\r
518 indicators[2] = new Color(0x3F,0x3F,0xFF);
\r
520 focus[1] = getBackground().darker();
\r
523 public ColorSet darkModeColorset = new ColorSet() {
\r
525 foregrounds[0] = Color.gray.darker();
\r
526 foregrounds[1] = Color.pink.brighter();
\r
527 backgrounds[0] = Color.black;
\r
528 backgrounds[1] = new Color(0x00,0x18,0x18);
\r
529 backgrounds[2] = new Color(0x20,0x00,0x00);
\r
530 backgrounds[3] = new Color(0x18,0x18,0x00);
\r
531 indicators[0] = Color.pink;
\r
532 indicators[1] = Color.yellow;
\r
533 indicators[2] = Color.cyan;
\r
534 focus[0] = Color.black;
\r
535 focus[1] = getForeground().brighter();
\r
538 private ColorSet currentColorset = normalModeColorset;
\r
541 * カポ値選択コンボボックスのデータモデル
\r
542 * (コードボタン側とコードダイアグラム側の両方から参照される)
\r
544 public ComboBoxModel<Integer> capoValueModel =
\r
545 new DefaultComboBoxModel<Integer>() {
\r
547 for( int i=1; i<=Music.SEMITONES_PER_OCTAVE-1; i++ )
\r
552 * カポ値選択コンボボックス(コードボタン側ビュー)
\r
554 public CapoSelecterView capoSelecter = new CapoSelecterView(capoValueModel) {
\r
555 private void capoChanged() {
\r
556 ChordMatrix.this.capoChanged(getCapo());
\r
559 checkbox.addItemListener(
\r
560 new ItemListener() {
\r
561 public void itemStateChanged(ItemEvent e) {capoChanged();}
\r
564 valueSelecter.addActionListener(
\r
565 new ActionListener() {
\r
566 public void actionPerformed(ActionEvent e) {capoChanged();}
\r
575 public ChordMatrix() {
\r
577 Dimension buttonSize = new Dimension(28,26);
\r
579 // Make key-signature labels and chord labels
\r
581 for (i=0, v= -Music.SEMITONES_PER_OCTAVE; i<N_COLUMNS; i++, v++) {
\r
582 l = new Co5Label(v);
\r
583 l.addMouseListener(this);
\r
584 l.addMouseMotionListener(this);
\r
585 add( keysigLabels[i] = l );
\r
586 l.setPreferredSize(buttonSize);
\r
589 for (i=0; i < N_COLUMNS * CHORD_BUTTON_ROWS; i++) {
\r
590 row = i / N_COLUMNS;
\r
591 v = i - (N_COLUMNS * row) - 12;
\r
592 Chord chord = new Chord(
\r
593 new NoteSymbol(row==2 ? v+3 : v)
\r
595 if( row==0 ) chord.set(Chord.Interval.SUS4);
\r
596 else if( row==2 ) chord.set(Chord.Interval.MINOR);
\r
597 ChordLabel cl = new ChordLabel(chord);
\r
598 cl.addMouseListener(this);
\r
599 cl.addMouseMotionListener(this);
\r
600 cl.addMouseWheelListener(this);
\r
601 add(chordLabels[i] = cl);
\r
602 cl.setPreferredSize(buttonSize);
\r
604 setFocusable(true);
\r
606 addKeyListener(this);
\r
607 addFocusListener(new FocusListener() {
\r
608 public void focusGained(FocusEvent e) {
\r
611 public void focusLost(FocusEvent e) {
\r
612 selectedChord = selectedChordCapo = null;
\r
613 fireChordChanged();
\r
617 setLayout(new GridLayout( 4, N_COLUMNS, 2, 2 ));
\r
618 setKeySignature( new Key() );
\r
620 // Make chord label selections index
\r
623 ArrayList<ChordLabelSelection> al;
\r
625 for( int note_no=0; note_no<chordLabelSelections.length; note_no++ ) {
\r
626 al = new ArrayList<ChordLabelSelection>();
\r
628 // Root major/minor chords
\r
629 for( ChordLabel cl : chordLabels ) {
\r
630 if( ! cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
\r
631 al.add(new ChordLabelSelection( cl, 0 )); // Root
\r
634 // Root sus4 chords
\r
635 for( ChordLabel cl : chordLabels ) {
\r
636 if( cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
\r
637 al.add(new ChordLabelSelection( cl, 0 )); // Root
\r
640 // 3rd,sus4th,5th included chords
\r
641 for( ChordLabel cl : chordLabels ) {
\r
642 noteIndex = cl.chord.indexOf(note_no);
\r
643 if( noteIndex == 1 || noteIndex == 2 ) {
\r
644 al.add(new ChordLabelSelection( cl, noteIndex )); // 3rd,sus4,P5
\r
647 // Diminished chords (major/minor chord button only)
\r
648 for( ChordLabel cl : chordLabels ) {
\r
649 if( cl.isSus4 ) continue;
\r
650 (chord = cl.chord.clone()).set(Chord.Interval.FLAT5);
\r
651 if( chord.indexOf(note_no) == 2 ) {
\r
652 al.add(new ChordLabelSelection( cl, 3 ));
\r
655 // Augumented chords (major chord button only)
\r
656 for( ChordLabel cl : chordLabels ) {
\r
657 if( cl.isSus4 || cl.isMinor ) continue;
\r
658 (chord = cl.chord.clone()).set(Chord.Interval.SHARP5);
\r
659 if( chord.indexOf(note_no) == 2 ) {
\r
660 al.add(new ChordLabelSelection( cl, 4 ));
\r
663 chordLabelSelections[note_no] = new ChordLabelSelections(al);
\r
668 public void mousePressed(MouseEvent e) {
\r
669 Component obj = e.getComponent();
\r
670 if( obj instanceof ChordLabel ) {
\r
671 ChordLabel cl = (ChordLabel)obj;
\r
672 Chord chord = cl.chord.clone();
\r
673 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
674 if( e.isShiftDown() )
\r
675 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
677 chord.set(Chord.Interval.SEVENTH);
\r
679 else if( e.isShiftDown() )
\r
680 chord.set(Chord.Interval.SIXTH);
\r
681 if( e.isControlDown() )
\r
682 chord.set(Chord.Interval.NINTH);
\r
684 chord.clear(Chord.OffsetIndex.NINTH);
\r
686 if( e.isAltDown() ) {
\r
688 chord.set(Chord.Interval.MAJOR); // To cancel sus4
\r
689 chord.set(Chord.Interval.SHARP5);
\r
691 else chord.set(Chord.Interval.FLAT5);
\r
693 if( selectedChordLabel != null ) {
\r
694 selectedChordLabel.setSelection(false);
\r
696 (selectedChordLabel = cl).setSelection(true);
\r
697 setSelectedChord(chord);
\r
699 else if( obj instanceof Co5Label ) {
\r
700 int v = ((Co5Label)obj).co5Value;
\r
701 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
702 setKeySignature( new Key(Music.oppositeCo5(v)) );
\r
704 else if ( v == key.toCo5() ) {
\r
706 // Cancel selected chord
\r
708 setSelectedChord( (Chord)null );
\r
712 setKeySignature( new Key(v) );
\r
715 requestFocusInWindow();
\r
718 public void mouseReleased(MouseEvent e) { destinationChordLabel = null; }
\r
719 public void mouseEntered(MouseEvent e) { }
\r
720 public void mouseExited(MouseEvent e) { }
\r
721 public void mouseClicked(MouseEvent e) { }
\r
722 public void mouseDragged(MouseEvent e) {
\r
723 Component obj = e.getComponent();
\r
724 if( obj instanceof ChordLabel ) {
\r
725 ChordLabel l_src = (ChordLabel)obj;
\r
726 Component obj2 = this.getComponentAt(
\r
727 l_src.getX() + e.getX(),
\r
728 l_src.getY() + e.getY()
\r
730 if( obj2 == this ) {
\r
732 // Entered gap between chord buttons - do nothing
\r
737 ( (obj2 instanceof ChordLabel ) ? (ChordLabel)obj2 : null );
\r
738 if( l_dst == l_src ) {
\r
740 // Returned to original chord button
\r
742 destinationChordLabel = null;
\r
745 if( destinationChordLabel != null ) {
\r
747 // Already touched another chord button
\r
751 Chord chord = l_src.chord.clone();
\r
752 if( l_src.isMinor ) {
\r
753 if( l_dst == null ) { // Out of chord buttons
\r
755 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
757 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
759 chord.set(Chord.Interval.SIXTH);
\r
761 else { // Left or up from minor to major
\r
763 chord.set(Chord.Interval.SEVENTH);
\r
766 else if( l_src.isSus4 ) {
\r
767 if( l_dst == null ) { // Out of chord buttons
\r
770 else if( ! l_dst.isSus4 ) { // Down from sus4 to major
\r
771 chord.set(Chord.Interval.MAJOR);
\r
773 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
774 chord.set(Chord.Interval.NINTH);
\r
778 chord.set(Chord.Interval.SEVENTH);
\r
782 if( l_dst == null ) { // Out of chord buttons
\r
785 else if( l_dst.isSus4 ) { // Up from major to sus4
\r
786 chord.set(Chord.Interval.NINTH);
\r
788 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
790 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
792 else if( l_dst.isMinor ) { // Down from major to minor
\r
794 chord.set(Chord.Interval.SIXTH);
\r
798 chord.set(Chord.Interval.SEVENTH);
\r
801 if( chord.isSet(Chord.OffsetIndex.NINTH) || (l_src.isSus4 && (l_dst == null || ! l_dst.isSus4) ) ) {
\r
802 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
803 if( e.isShiftDown() ) {
\r
804 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
807 chord.set(Chord.Interval.SEVENTH);
\r
810 else if( e.isShiftDown() ) {
\r
811 chord.set(Chord.Interval.SIXTH);
\r
815 if( e.isControlDown() )
\r
816 chord.set(Chord.Interval.NINTH);
\r
818 chord.clear(Chord.OffsetIndex.NINTH);
\r
820 if( e.isAltDown() ) {
\r
821 if( l_src.isSus4 ) {
\r
822 chord.set(Chord.Interval.MAJOR);
\r
823 chord.set(Chord.Interval.SHARP5);
\r
826 chord.set(Chord.Interval.FLAT5);
\r
829 setSelectedChord(chord);
\r
830 destinationChordLabel = (l_dst == null ? l_src : l_dst ) ;
\r
832 else if( obj instanceof Co5Label ) {
\r
833 Co5Label l_src = (Co5Label)obj;
\r
834 Component obj2 = this.getComponentAt(
\r
835 l_src.getX() + e.getX(),
\r
836 l_src.getY() + e.getY()
\r
838 if( !(obj2 instanceof Co5Label) ) {
\r
841 Co5Label l_dst = (Co5Label)obj2;
\r
842 int v = l_dst.co5Value;
\r
843 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
844 setKeySignature( new Key(Music.oppositeCo5(v)) );
\r
847 setKeySignature( new Key(v) );
\r
852 public void mouseMoved(MouseEvent e) { }
\r
853 public void mouseWheelMoved(MouseWheelEvent e) {
\r
854 if( selectedChord != null ) {
\r
855 if( e.getWheelRotation() > 0 ) { // Wheel moved down
\r
856 if( --selectedNoteIndex < 0 ) {
\r
857 selectedNoteIndex = selectedChord.numberOfNotes() - 1;
\r
860 else { // Wheel moved up
\r
861 if( ++selectedNoteIndex >= selectedChord.numberOfNotes() ) {
\r
862 selectedNoteIndex = 0;
\r
865 fireChordChanged();
\r
868 private Chord.Interval pcKeyNextShift7;
\r
869 public void keyPressed(KeyEvent e) {
\r
870 int i = -1, i_col = -1, i_row = 1;
\r
871 boolean shiftPressed = false; // True if Shift-key pressed or CapsLocked
\r
872 char keyChar = e.getKeyChar();
\r
873 int keyCode = e.getKeyCode();
\r
874 ChordLabel cl = null;
\r
875 Chord chord = null;
\r
876 int key_co5 = key.toCo5();
\r
877 // System.out.println( keyChar + " Pressed on chord matrix" );
\r
879 if( (i = "6 ".indexOf(keyChar)) >= 0 ) {
\r
880 selectedChord = selectedChordCapo = null;
\r
881 fireChordChanged();
\r
882 pcKeyNextShift7 = null;
\r
885 else if( (i = "asdfghjkl;:]".indexOf(keyChar)) >= 0 ) {
\r
886 i_col = i + key_co5 + 7;
\r
888 else if( (i = "ASDFGHJKL+*}".indexOf(keyChar)) >= 0 ) {
\r
889 i_col = i + key_co5 + 7;
\r
890 shiftPressed = true;
\r
892 else if( (i = "zxcvbnm,./\\".indexOf(keyChar)) >=0 ) {
\r
893 i_col = i + key_co5 + 7;
\r
896 else if( (i = "ZXCVBNM<>?_".indexOf(keyChar)) >=0 ) {
\r
897 i_col = i + key_co5 + 7;
\r
899 shiftPressed = true;
\r
901 else if( (i = "qwertyuiop@[".indexOf(keyChar)) >= 0 ) {
\r
902 i_col = i + key_co5 + 7;
\r
905 else if( (i = "QWERTYUIOP`{".indexOf(keyChar)) >= 0 ) {
\r
906 i_col = i + key_co5 + 7;
\r
908 shiftPressed = true;
\r
910 else if( keyChar == '5' ) {
\r
911 pcKeyNextShift7 = Chord.Interval.MAJOR_SEVENTH; return;
\r
913 else if( keyChar == '7' ) {
\r
914 pcKeyNextShift7 = Chord.Interval.SEVENTH; return;
\r
916 // Shift current key-signature
\r
917 else if( keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_KP_LEFT ) {
\r
919 setKeySignature( new Key(key_co5-1) );
\r
922 else if( keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_KP_RIGHT ) {
\r
924 setKeySignature( new Key(key_co5+1) );
\r
927 else if( keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_KP_DOWN ) {
\r
929 Key key = new Key(key_co5);
\r
931 setKeySignature(key);
\r
934 else if( keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_KP_UP ) {
\r
936 Key key = new Key(key_co5);
\r
938 setKeySignature(key);
\r
941 if( i < 0 ) // No key char found
\r
943 if( i_col < 0 ) i_col += 12; else if( i_col > N_COLUMNS ) i_col -= 12;
\r
944 cl = chordLabels[i_col + N_COLUMNS * i_row];
\r
945 chord = cl.chord.clone();
\r
946 if( shiftPressed ) {
\r
947 chord.set(Chord.Interval.SEVENTH);
\r
949 // specify by previous key
\r
950 else if( pcKeyNextShift7 == null ) {
\r
951 chord.clear(Chord.OffsetIndex.SEVENTH);
\r
954 chord.set(pcKeyNextShift7);
\r
956 if( e.isAltDown() ) {
\r
958 chord.set(Chord.Interval.MAJOR); // To cancel sus4
\r
959 chord.set(Chord.Interval.SHARP5);
\r
962 chord.set(Chord.Interval.FLAT5);
\r
965 if( e.isControlDown() ) { // Cannot use for ninth ?
\r
966 chord.set(Chord.Interval.NINTH);
\r
968 if( selectedChordLabel != null ) clear();
\r
969 (selectedChordLabel = cl).setSelection(true);
\r
970 setSelectedChord(chord);
\r
971 pcKeyNextShift7 = null;
\r
974 public void keyReleased(KeyEvent e) { }
\r
975 public void keyTyped(KeyEvent e) { }
\r
977 public void addChordMatrixListener(ChordMatrixListener l) {
\r
978 listenerList.add(ChordMatrixListener.class, l);
\r
980 public void removeChordMatrixListener(ChordMatrixListener l) {
\r
981 listenerList.remove(ChordMatrixListener.class, l);
\r
983 protected void fireChordChanged() {
\r
984 Object[] listeners = listenerList.getListenerList();
\r
985 for (int i = listeners.length-2; i>=0; i-=2) {
\r
986 if (listeners[i]==ChordMatrixListener.class) {
\r
987 ((ChordMatrixListener)listeners[i+1]).chordChanged();
\r
990 if( selectedChord == null ) clearIndicators();
\r
992 public void fireKeySignatureChanged() {
\r
993 Object[] listeners = listenerList.getListenerList();
\r
994 for (int i = listeners.length-2; i>=0; i-=2) {
\r
995 if (listeners[i]==ChordMatrixListener.class) {
\r
996 ((ChordMatrixListener)listeners[i+1]).keySignatureChanged();
\r
1000 private Key key = null;
\r
1001 private Key capoKey = null;
\r
1002 public Key getKeySignature() { return key; }
\r
1003 public Key getKeySignatureCapo() { return capoKey; }
\r
1004 public void setKeySignature( Key key ) {
\r
1005 if( key == null || this.key != null && key.equals(this.key) )
\r
1008 // Clear old value
\r
1009 if( this.key == null ) {
\r
1010 for( i = 0; i < keysigLabels.length; i++ ) {
\r
1011 keysigLabels[i].setBackground(false);
\r
1015 keysigLabels[this.key.toCo5() + 12].setSelection(false);
\r
1016 for( i = Music.mod12(this.key.toCo5()); i < N_COLUMNS; i+=12 ) {
\r
1017 keysigLabels[i].setBackground(false);
\r
1021 keysigLabels[i = key.toCo5() + 12].setSelection(true);
\r
1022 for( i = Music.mod12(key.toCo5()); i < N_COLUMNS; i+=12 ) {
\r
1023 keysigLabels[i].setBackground(true);
\r
1025 // Change chord-label's color & font
\r
1026 int i_color, old_i_color;
\r
1027 for( ChordLabel cl : chordLabels ) {
\r
1028 i_color = ((cl.co5Value - key.toCo5() + 31)/3) & 3;
\r
1029 if( this.key != null ) {
\r
1030 old_i_color = ((cl.co5Value - this.key.toCo5() + 31)/3) & 3;
\r
1031 if( i_color != old_i_color ) {
\r
1032 cl.setBackground(i_color);
\r
1035 else cl.setBackground(i_color);
\r
1036 if( !(cl.isSus4) ) {
\r
1037 if( this.key != null && Music.mod12(cl.co5Value - this.key.toCo5()) == 0)
\r
1038 cl.setBold(false);
\r
1039 if( Music.mod12( cl.co5Value - key.toCo5() ) == 0 )
\r
1043 this.capoKey = (this.key = key).clone().transpose(capoSelecter.getCapo());
\r
1044 for( ChordLabel cl : chordLabels ) cl.keyChanged();
\r
1045 fireKeySignatureChanged();
\r
1047 private int capo = 0;
\r
1050 * @param newCapo 新しいカポ位置
\r
1052 protected void capoChanged(int newCapo) {
\r
1053 if( this.capo == newCapo )
\r
1055 (this.capoKey = this.key.clone()).transpose(this.capo = newCapo);
\r
1056 selectedChordCapo = (
\r
1057 selectedChord == null ? null : selectedChord.clone().transpose(newCapo)
\r
1059 for( ChordLabel cl : chordLabels ) cl.keyChanged();
\r
1060 fireKeySignatureChanged();
\r
1066 public ChordGuide chordGuide = new ChordGuide(this);
\r
1071 private ChordLabel destinationChordLabel = null;
\r
1073 * ドラッグされたかどうか調べます。
\r
1074 * @return ドラッグ先コードボタンがあればtrue
\r
1076 public boolean isDragged() {
\r
1077 return destinationChordLabel != null ;
\r
1080 private boolean isDark = false;
\r
1081 public void setDarkMode(boolean is_dark) {
\r
1082 this.isDark = is_dark;
\r
1083 currentColorset = (is_dark ? darkModeColorset : normalModeColorset);
\r
1084 setBackground( currentColorset.focus[0] );
\r
1085 Key prev_key = key;
\r
1087 setKeySignature(prev_key);
\r
1088 for( int i=0; i < keysigLabels.length; i++ ) keysigLabels[i].setSelection();
\r
1089 for( int i=0; i < chordLabels.length; i++ ) chordLabels[i].setSelection();
\r
1090 chordGuide.setDarkMode( is_dark );
\r
1091 chordDisplay.setDarkMode( is_dark );
\r
1092 Color col = is_dark ? Color.black : null;
\r
1093 capoSelecter.setBackground( col );
\r
1094 capoSelecter.valueSelecter.setBackground( col );
\r
1097 private boolean isPlaying = false;
\r
1098 public boolean isPlaying() { return isPlaying; }
\r
1099 public void setPlaying(boolean is_playing) {
\r
1100 this.isPlaying = is_playing;
\r
1104 private byte currentBeat = 0;
\r
1105 private byte timesigUpper = 4;
\r
1106 public void setBeat(SequenceTickIndex sequenceTickIndex) {
\r
1107 byte beat = (byte)(sequenceTickIndex.lastBeat);
\r
1108 byte tsu = sequenceTickIndex.timesigUpper;
\r
1109 if( currentBeat == beat && timesigUpper == tsu )
\r
1111 timesigUpper = tsu;
\r
1112 currentBeat = beat;
\r
1113 keysigLabels[ key.toCo5() + 12 ].repaint();
\r
1116 private ChordLabel selectedChordLabel = null;
\r
1117 public JComponent getSelectedButton() {
\r
1118 return selectedChordLabel;
\r
1120 private Chord selectedChord = null;
\r
1121 public Chord getSelectedChord() {
\r
1122 return selectedChord;
\r
1124 private Chord selectedChordCapo = null;
\r
1125 public Chord getSelectedChordCapo() {
\r
1126 return selectedChordCapo;
\r
1128 public void setSelectedChordCapo( Chord chord ) {
\r
1129 setNoteIndex(-1); // Cancel arpeggio mode
\r
1130 selectedChord = (chord == null ? null : chord.clone().transpose(-capo,capoKey));
\r
1131 selectedChordCapo = chord;
\r
1132 fireChordChanged();
\r
1134 public void setSelectedChord( Chord chord ) {
\r
1135 setNoteIndex(-1); // Cancel arpeggio mode
\r
1136 selectedChord = chord;
\r
1137 selectedChordCapo = (chord == null ? null : chord.clone().transpose(capo,key));
\r
1138 fireChordChanged();
\r
1142 * @param chordSymbol コード名
\r
1144 public void setSelectedChord(String chordSymbol) throws IllegalArgumentException {
\r
1145 Chord chord = null;
\r
1146 if( chordSymbol != null && ! chordSymbol.isEmpty() ) {
\r
1148 chord = new Chord(chordSymbol);
\r
1149 } catch( IllegalArgumentException e ) {
\r
1150 JOptionPane.showMessageDialog(
\r
1151 null, e.getMessage(), "Input error",
\r
1152 JOptionPane.ERROR_MESSAGE
\r
1157 setSelectedChord(chord);
\r
1160 private int selectedNoteIndex = -1;
\r
1161 public int getNoteIndex() {
\r
1162 return selectedChord == null || selectedNoteIndex < 0 ? -1 : selectedNoteIndex;
\r
1164 public void setNoteIndex(int noteIndex) {
\r
1165 selectedNoteIndex = noteIndex;
\r
1167 public void clear() {
\r
1168 if( selectedChordLabel != null ) {
\r
1169 selectedChordLabel.setSelection(false);
\r
1170 selectedChordLabel = null;
\r
1172 selectedChord = null; selectedNoteIndex = -1;
\r