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.JPanel;
\r
32 import camidion.chordhelper.ButtonIcon;
\r
33 import camidion.chordhelper.ChordDisplayLabel;
\r
34 import camidion.chordhelper.chorddiagram.CapoSelecterView;
\r
35 import camidion.chordhelper.midieditor.SequenceTickIndex;
\r
36 import camidion.chordhelper.music.Chord;
\r
37 import camidion.chordhelper.music.Key;
\r
38 import camidion.chordhelper.music.Music;
\r
39 import camidion.chordhelper.music.NoteSymbol;
\r
40 import camidion.chordhelper.music.SymbolLanguage;
\r
43 * MIDI Chord Helper 用のコードボタンマトリクス
\r
46 * Copyright (C) 2004-2013 Akiyoshi Kamide
\r
47 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
49 public class ChordMatrix extends JPanel
\r
50 implements MouseListener, KeyListener, MouseMotionListener, MouseWheelListener
\r
55 public static final int N_COLUMNS = Music.SEMITONES_PER_OCTAVE * 2 + 1;
\r
59 public static final int CHORD_BUTTON_ROWS = 3;
\r
63 public Co5Label keysigLabels[] = new Co5Label[ N_COLUMNS ];
\r
67 public ChordLabel chordLabels[] = new ChordLabel[N_COLUMNS * CHORD_BUTTON_ROWS];
\r
71 public ChordDisplayLabel chordDisplay = new ChordDisplayLabel("Chord Pad", this, null);
\r
73 private static class ChordLabelSelection {
\r
74 ChordLabel chordLabel;
\r
77 public ChordLabelSelection(ChordLabel chordLabel, int bitIndex) {
\r
78 this.chordLabel = chordLabel;
\r
79 this.bitIndex = bitIndex;
\r
80 this.isSus4 = chordLabel.isSus4;
\r
82 public void setCheckBit(boolean isOn) {
\r
83 chordLabel.setCheckBit(isOn, bitIndex);
\r
85 public boolean setBassCheckBit(boolean isOn) {
\r
86 if( bitIndex == 0 && ! isSus4 ) {
\r
87 chordLabel.setCheckBit(isOn, 6);
\r
93 private static class ChordLabelSelections {
\r
95 int bass_weight = 0;
\r
96 boolean is_active = false;
\r
97 boolean is_bass_active = false;
\r
98 private ChordLabelSelection acls[];
\r
99 public ChordLabelSelections(ArrayList<ChordLabelSelection> al) {
\r
100 acls = al.toArray(new ChordLabelSelection[al.size()]);
\r
102 void addWeight(int weight_diff) {
\r
103 if( (weight += weight_diff) < 0 ) weight = 0;
\r
104 if( (weight > 0) != is_active ) {
\r
105 is_active = !is_active;
\r
106 for( ChordLabelSelection cls : acls ) {
\r
107 cls.setCheckBit(is_active);
\r
111 void addBassWeight(int weight_diff) {
\r
112 if( (bass_weight += weight_diff) < 0 ) bass_weight = 0;
\r
113 if( (bass_weight > 0) != is_bass_active ) {
\r
114 is_bass_active = !is_bass_active;
\r
115 for( ChordLabelSelection cls : acls ) {
\r
116 if( ! cls.setBassCheckBit(is_bass_active) ) {
\r
117 // No more root major/minor
\r
122 addWeight(weight_diff);
\r
124 void clearWeight() {
\r
125 weight = bass_weight = 0;
\r
126 is_active = is_bass_active = false;
\r
127 for( ChordLabelSelection cls : acls ) {
\r
128 cls.setCheckBit(false);
\r
129 cls.setBassCheckBit(false);
\r
133 private ChordLabelSelections chordLabelSelections[] =
\r
134 new ChordLabelSelections[Music.SEMITONES_PER_OCTAVE];
\r
136 * 発音中のノート表示をクリアします。
\r
138 public void clearIndicators() {
\r
139 for( int i=0; i<chordLabelSelections.length; i++ ) {
\r
140 chordLabelSelections[i].clearWeight();
\r
145 * MIDIのノートイベント(ON/OFF)を受け取ります。
\r
146 * @param isNoteOn ONのときtrue
\r
147 * @param noteNumber ノート番号
\r
149 public void note(boolean isNoteOn, int noteNumber) {
\r
150 int weightDiff = (isNoteOn ? 1 : -1);
\r
151 ChordLabelSelections cls = chordLabelSelections[Music.mod12(noteNumber)];
\r
152 if( noteNumber < 49 )
\r
153 cls.addBassWeight(weightDiff);
\r
155 cls.addWeight(weightDiff);
\r
161 private class Co5Label extends JLabel {
\r
162 public boolean isSelected = false;
\r
163 public int co5Value = 0;
\r
164 private Color indicatorColor;
\r
165 public Co5Label(int v) {
\r
166 Key key = new Key(co5Value = v);
\r
168 setBackground(false);
\r
169 setForeground( currentColorset.foregrounds[0] );
\r
170 setHorizontalAlignment( JLabel.CENTER );
\r
171 String tip = "Key signature: ";
\r
172 if( v != key.toCo5() ) {
\r
173 tip += "out of range" ;
\r
176 tip += key.signatureDescription() + " " +
\r
177 key.toStringIn(SymbolLanguage.IN_JAPANESE);
\r
179 setIcon(new ButtonIcon(ButtonIcon.NATURAL_ICON));
\r
182 setFont( getFont().deriveFont(Font.PLAIN) );
\r
183 setText( key.signature() );
\r
186 setToolTipText(tip);
\r
188 public void paint(Graphics g) {
\r
190 Dimension d = getSize();
\r
191 if( ChordMatrix.this.isFocusOwner() && isSelected ) {
\r
192 g.setColor( currentColorset.focus[1] );
\r
193 g.drawRect( 0, 0, d.width-1, d.height-1 );
\r
195 if( !isSelected || !isPlaying || currentBeat+1 == timesigUpper ) {
\r
198 if( currentBeat == 0 ) {
\r
199 g.setColor( indicatorColor );
\r
200 g.drawRect( 2, 2, d.width-5, d.height-5 );
\r
201 g.setColor( isDark ? indicatorColor.darker() : indicatorColor.brighter() );
\r
202 g.drawRect( 0, 0, d.width-1, d.height-1 );
\r
205 Color color = currentColorset.indicators[0];
\r
206 g.setColor( color );
\r
207 if( currentBeat == 1 ) {
\r
210 g.drawLine( 2, d.height-3, d.width-3, d.height-3 );
\r
211 g.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
\r
212 g.drawLine( 2, 2, 2, d.height-3 );
\r
213 g.setColor( isDark ? color.darker() : color.brighter() );
\r
214 g.drawLine( 0, d.height-1, d.width-1, d.height-1 );
\r
215 g.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
\r
216 g.drawLine( 0, 0, 0, d.height-1 );
\r
222 int vertical_top = (d.height-1) * (currentBeat-1) / (timesigUpper-2) ;
\r
223 g.drawLine( 2, vertical_top == 0 ? 2 : vertical_top, 2, d.height-3 );
\r
224 g.setColor( isDark ? color.darker() : color.brighter() );
\r
225 g.drawLine( 0, vertical_top, 0, d.height-1 );
\r
228 public void setBackground(boolean isActive) {
\r
229 super.setBackground(currentColorset.backgrounds[isActive?2:0]);
\r
230 setIndicatorColor();
\r
233 public void setSelection(boolean isSelected) {
\r
234 this.isSelected = isSelected;
\r
237 public void setSelection() {
\r
238 setForeground(currentColorset.foregrounds[isSelected?1:0]);
\r
240 public void setIndicatorColor() {
\r
241 if( co5Value < 0 ) {
\r
242 indicatorColor = currentColorset.indicators[2];
\r
244 else if( co5Value > 0 ) {
\r
245 indicatorColor = currentColorset.indicators[1];
\r
248 indicatorColor = currentColorset.foregrounds[1];
\r
255 private class ChordLabel extends JLabel {
\r
256 public byte checkBits = 0;
\r
257 public int co5Value;
\r
258 public boolean isMinor;
\r
259 public boolean isSus4;
\r
260 public boolean isSelected = false;
\r
261 public Chord chord;
\r
263 private boolean inActiveZone = true;
\r
264 private Font boldFont;
\r
265 private Font plainFont;
\r
266 private int indicatorColorIndices[] = new int[5];
\r
267 private byte indicatorBits = 0;
\r
269 public ChordLabel(Chord chord) {
\r
270 this.chord = chord;
\r
271 isMinor = chord.isSet(Chord.Interval.MINOR);
\r
272 isSus4 = chord.isSet(Chord.Interval.SUS4);
\r
273 co5Value = chord.rootNoteSymbol().toCo5();
\r
274 if( isMinor ) co5Value -= 3;
\r
275 String labelText = ( isSus4 ? chord.symbolSuffix() : chord.toString() );
\r
276 if( isMinor && labelText.length() > 3 ) {
\r
277 float small_point_size = getFont().getSize2D() - 2;
\r
278 boldFont = getFont().deriveFont(Font.BOLD, small_point_size);
\r
279 plainFont = getFont().deriveFont(Font.PLAIN, small_point_size);
\r
282 boldFont = getFont().deriveFont(Font.BOLD);
\r
283 plainFont = getFont().deriveFont(Font.PLAIN);
\r
287 setForeground( currentColorset.foregrounds[0] );
\r
289 setHorizontalAlignment( JLabel.CENTER );
\r
290 setText(labelText);
\r
291 setToolTipText( "Chord: " + chord.toName() );
\r
293 public void paint(Graphics g) {
\r
295 Dimension d = getSize();
\r
296 Graphics2D g2 = (Graphics2D) g;
\r
297 Color color = null;
\r
299 if( ! inActiveZone ) {
\r
300 g2.setColor( Color.gray );
\r
303 if( (indicatorBits & 32) != 0 ) {
\r
305 // Draw square [] with 3rd/sus4th note color
\r
307 if( inActiveZone ) {
\r
308 color = currentColorset.indicators[indicatorColorIndices[1]];
\r
309 g2.setColor( color );
\r
311 g2.drawRect( 0, 0, d.width-1, d.height-1 );
\r
312 g2.drawRect( 2, 2, d.width-5, d.height-5 );
\r
314 if( (indicatorBits & 1) != 0 ) {
\r
316 // Draw ||__ with root note color
\r
318 if( inActiveZone ) {
\r
319 color = currentColorset.indicators[indicatorColorIndices[0]];
\r
320 g2.setColor( color );
\r
322 g2.drawLine( 0, 0, 0, d.height-1 );
\r
323 g2.drawLine( 2, 2, 2, d.height-3 );
\r
325 if( (indicatorBits & 64) != 0 ) {
\r
326 // Draw bass mark with root note color
\r
328 if( inActiveZone ) {
\r
329 color = currentColorset.indicators[indicatorColorIndices[0]];
\r
330 g2.setColor( color );
\r
332 g2.fillRect( 6, d.height-7, d.width-12, 2 );
\r
334 if( (indicatorBits & 4) != 0 ) {
\r
336 // Draw short __ii with parfect 5th color
\r
338 if( inActiveZone ) {
\r
339 color = currentColorset.indicators[indicatorColorIndices[2]];
\r
340 g2.setColor( color );
\r
342 g2.drawLine( d.width-1, d.height*3/4, d.width-1, d.height-1 );
\r
343 g2.drawLine( d.width-3, d.height*3/4, d.width-3, d.height-3 );
\r
345 if( (indicatorBits & 2) != 0 ) {
\r
347 // Draw __ with 3rd note color
\r
349 if( inActiveZone ) {
\r
350 color = currentColorset.indicators[indicatorColorIndices[1]];
\r
351 g2.setColor( color );
\r
353 g2.drawLine( 0, d.height-1, d.width-1, d.height-1 );
\r
354 g2.drawLine( 2, d.height-3, d.width-3, d.height-3 );
\r
356 if( (indicatorBits & 8) != 0 ) {
\r
358 // Draw circle with diminished 5th color
\r
360 if( inActiveZone ) {
\r
361 g2.setColor( currentColorset.indicators[indicatorColorIndices[3]] );
\r
363 g2.drawOval( 1, 1, d.width-2, d.height-2 );
\r
365 if( (indicatorBits & 16) != 0 ) {
\r
367 // Draw + with augument 5th color
\r
369 if( inActiveZone ) {
\r
370 g2.setColor( currentColorset.indicators[indicatorColorIndices[4]] );
\r
372 g2.drawLine( 1, 3, d.width-3, 3 );
\r
373 g2.drawLine( 1, 4, d.width-3, 4 );
\r
374 g2.drawLine( d.width/2-1, 0, d.width/2-1, 7 );
\r
375 g2.drawLine( d.width/2, 0, d.width/2, 7 );
\r
378 public void setCheckBit( boolean is_on, int bit_index ) {
\r
380 // Check bits: x6x43210
\r
382 // 4:Augumented5th, 3:Diminished5th, 2:Parfect5th,
\r
383 // 1:Major3rd/minor3rd/sus4th, 0:Root
\r
385 byte mask = ((byte)(1<<bit_index));
\r
386 byte old_check_bits = checkBits;
\r
391 checkBits &= ~mask;
\r
393 if( old_check_bits == checkBits ) {
\r
397 // Indicator bits: x6543210 6:Bass||_ 5:[] 4:+ 3:O 2:_ii 1:__ 0:||_
\r
399 byte indicator_bits = 0;
\r
400 if( (checkBits & 1) != 0 ) {
\r
401 if( (checkBits & 7) == 7 ) { // All triad notes appared
\r
404 indicator_bits |= 0x20;
\r
406 // Draw different-colored vertical lines
\r
407 if( indicatorColorIndices[0] != indicatorColorIndices[1] ) {
\r
408 indicator_bits |= 1;
\r
410 if( indicatorColorIndices[2] != indicatorColorIndices[1] ) {
\r
411 indicator_bits |= 4;
\r
414 else if( !isSus4 ) {
\r
416 // Draw vertical lines || ii
\r
417 indicator_bits |= 5;
\r
419 if( (checkBits & 2) != 0 && (!isMinor || (checkBits & 0x18) != 0) ) {
\r
421 // Draw horizontal bottom lines __
\r
422 indicator_bits |= 2;
\r
426 if( isMinor || (checkBits & 2) != 0 ) {
\r
427 indicator_bits |= (byte)(checkBits & 0x18); // Copy bit 3 and bit 4
\r
429 if( (checkBits & 0x40) != 0 ) {
\r
430 indicator_bits |= 0x40; // Bass
\r
434 if( this.indicatorBits == indicator_bits ) {
\r
435 // No shapes changed
\r
438 this.indicatorBits = indicator_bits;
\r
441 public void setBackground(int i) {
\r
447 super.setBackground(currentColorset.backgrounds[i]);
\r
453 public void setSelection(boolean is_selected) {
\r
454 this.isSelected = is_selected;
\r
457 public void setSelection() {
\r
458 setForeground(currentColorset.foregrounds[this.isSelected?1:0]);
\r
460 public void setBold(boolean is_bold) {
\r
461 setFont( is_bold ? boldFont : plainFont );
\r
463 public void keyChanged() {
\r
464 int co5_key = capoKey.toCo5();
\r
465 int co5_offset = co5Value - co5_key;
\r
466 inActiveZone = (co5_offset <= 6 && co5_offset >= -6) ;
\r
467 int root_note = chord.rootNoteSymbol().toNoteNumber();
\r
469 // Reconstruct color index
\r
472 indicatorColorIndices[0] = Music.isOnScale(
\r
474 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
477 indicatorColorIndices[1] = Music.isOnScale(
\r
478 root_note+(isMinor?3:isSus4?5:4), co5_key
\r
479 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
482 indicatorColorIndices[2] = Music.isOnScale(
\r
483 root_note+7, co5_key
\r
484 ) ? 0 : co5_offset > 0 ? 1 : 2;
\r
487 indicatorColorIndices[3] = Music.isOnScale(
\r
488 root_note+6, co5_key
\r
489 ) ? 0 : co5_offset > 4 ? 1 : 2;
\r
492 indicatorColorIndices[4] = Music.isOnScale(
\r
493 root_note+8, co5_key
\r
494 ) ? 0 : co5_offset > -3 ? 1 : 2;
\r
501 public class ColorSet {
\r
502 Color[] focus = new Color[2]; // 0:lost 1:gained
\r
503 Color[] foregrounds = new Color[2]; // 0:unselected 1:selected
\r
504 public Color[] backgrounds = new Color[4]; // 0:remote 1:left 2:local 3:right
\r
505 Color[] indicators = new Color[3]; // 0:natural 1:sharp 2:flat
\r
507 public ColorSet normalModeColorset = new ColorSet() {
\r
509 foregrounds[0] = null;
\r
510 foregrounds[1] = new Color(0xFF,0x3F,0x3F);
\r
511 backgrounds[0] = new Color(0xCF,0xFF,0xCF);
\r
512 backgrounds[1] = new Color(0x9F,0xFF,0xFF);
\r
513 backgrounds[2] = new Color(0xFF,0xCF,0xCF);
\r
514 backgrounds[3] = new Color(0xFF,0xFF,0x9F);
\r
515 indicators[0] = new Color(0xFF,0x3F,0x3F);
\r
516 indicators[1] = new Color(0xCF,0x6F,0x00);
\r
517 indicators[2] = new Color(0x3F,0x3F,0xFF);
\r
519 focus[1] = getBackground().darker();
\r
522 public ColorSet darkModeColorset = new ColorSet() {
\r
524 foregrounds[0] = Color.gray.darker();
\r
525 foregrounds[1] = Color.pink.brighter();
\r
526 backgrounds[0] = Color.black;
\r
527 backgrounds[1] = new Color(0x00,0x18,0x18);
\r
528 backgrounds[2] = new Color(0x20,0x00,0x00);
\r
529 backgrounds[3] = new Color(0x18,0x18,0x00);
\r
530 indicators[0] = Color.pink;
\r
531 indicators[1] = Color.yellow;
\r
532 indicators[2] = Color.cyan;
\r
533 focus[0] = Color.black;
\r
534 focus[1] = getForeground().brighter();
\r
537 private ColorSet currentColorset = normalModeColorset;
\r
540 * カポ値選択コンボボックスのデータモデル
\r
541 * (コードボタン側とコードダイアグラム側の両方から参照される)
\r
543 public ComboBoxModel<Integer> capoValueModel =
\r
544 new DefaultComboBoxModel<Integer>() {
\r
546 for( int i=1; i<=Music.SEMITONES_PER_OCTAVE-1; i++ )
\r
551 * カポ値選択コンボボックス(コードボタン側ビュー)
\r
553 public CapoSelecterView capoSelecter = new CapoSelecterView(capoValueModel) {
\r
554 private void capoChanged() {
\r
555 ChordMatrix.this.capoChanged(getCapo());
\r
558 checkbox.addItemListener(
\r
559 new ItemListener() {
\r
560 public void itemStateChanged(ItemEvent e) {capoChanged();}
\r
563 valueSelecter.addActionListener(
\r
564 new ActionListener() {
\r
565 public void actionPerformed(ActionEvent e) {capoChanged();}
\r
574 public ChordMatrix() {
\r
576 Dimension buttonSize = new Dimension(28,26);
\r
578 // Make key-signature labels and chord labels
\r
580 for (i=0, v= -Music.SEMITONES_PER_OCTAVE; i<N_COLUMNS; i++, v++) {
\r
581 l = new Co5Label(v);
\r
582 l.addMouseListener(this);
\r
583 l.addMouseMotionListener(this);
\r
584 add( keysigLabels[i] = l );
\r
585 l.setPreferredSize(buttonSize);
\r
588 for (i=0; i < N_COLUMNS * CHORD_BUTTON_ROWS; i++) {
\r
589 row = i / N_COLUMNS;
\r
590 v = i - (N_COLUMNS * row) - 12;
\r
591 Chord chord = new Chord(
\r
592 new NoteSymbol(row==2 ? v+3 : v)
\r
594 if( row==0 ) chord.set(Chord.Interval.SUS4);
\r
595 else if( row==2 ) chord.set(Chord.Interval.MINOR);
\r
596 ChordLabel cl = new ChordLabel(chord);
\r
597 cl.addMouseListener(this);
\r
598 cl.addMouseMotionListener(this);
\r
599 cl.addMouseWheelListener(this);
\r
600 add(chordLabels[i] = cl);
\r
601 cl.setPreferredSize(buttonSize);
\r
603 setFocusable(true);
\r
605 addKeyListener(this);
\r
606 addFocusListener(new FocusListener() {
\r
607 public void focusGained(FocusEvent e) {
\r
610 public void focusLost(FocusEvent e) {
\r
611 selectedChord = selectedChordCapo = null;
\r
612 fireChordChanged();
\r
616 setLayout(new GridLayout( 4, N_COLUMNS, 2, 2 ));
\r
617 setKeySignature( new Key() );
\r
619 // Make chord label selections index
\r
622 ArrayList<ChordLabelSelection> al;
\r
624 for( int note_no=0; note_no<chordLabelSelections.length; note_no++ ) {
\r
625 al = new ArrayList<ChordLabelSelection>();
\r
627 // Root major/minor chords
\r
628 for( ChordLabel cl : chordLabels ) {
\r
629 if( ! cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
\r
630 al.add(new ChordLabelSelection( cl, 0 )); // Root
\r
633 // Root sus4 chords
\r
634 for( ChordLabel cl : chordLabels ) {
\r
635 if( cl.isSus4 && cl.chord.indexOf(note_no) == 0 ) {
\r
636 al.add(new ChordLabelSelection( cl, 0 )); // Root
\r
639 // 3rd,sus4th,5th included chords
\r
640 for( ChordLabel cl : chordLabels ) {
\r
641 noteIndex = cl.chord.indexOf(note_no);
\r
642 if( noteIndex == 1 || noteIndex == 2 ) {
\r
643 al.add(new ChordLabelSelection( cl, noteIndex )); // 3rd,sus4,P5
\r
646 // Diminished chords (major/minor chord button only)
\r
647 for( ChordLabel cl : chordLabels ) {
\r
648 if( cl.isSus4 ) continue;
\r
649 (chord = cl.chord.clone()).set(Chord.Interval.FLAT5);
\r
650 if( chord.indexOf(note_no) == 2 ) {
\r
651 al.add(new ChordLabelSelection( cl, 3 ));
\r
654 // Augumented chords (major chord button only)
\r
655 for( ChordLabel cl : chordLabels ) {
\r
656 if( cl.isSus4 || cl.isMinor ) continue;
\r
657 (chord = cl.chord.clone()).set(Chord.Interval.SHARP5);
\r
658 if( chord.indexOf(note_no) == 2 ) {
\r
659 al.add(new ChordLabelSelection( cl, 4 ));
\r
662 chordLabelSelections[note_no] = new ChordLabelSelections(al);
\r
667 public void mousePressed(MouseEvent e) {
\r
668 Component obj = e.getComponent();
\r
669 if( obj instanceof ChordLabel ) {
\r
670 ChordLabel cl = (ChordLabel)obj;
\r
671 Chord chord = cl.chord.clone();
\r
672 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
673 if( e.isShiftDown() )
\r
674 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
676 chord.set(Chord.Interval.SEVENTH);
\r
678 else if( e.isShiftDown() )
\r
679 chord.set(Chord.Interval.SIXTH);
\r
680 if( e.isControlDown() )
\r
681 chord.set(Chord.Interval.NINTH);
\r
683 chord.clear(Chord.OffsetIndex.NINTH);
\r
685 if( e.isAltDown() ) {
\r
687 chord.set(Chord.Interval.MAJOR); // To cancel sus4
\r
688 chord.set(Chord.Interval.SHARP5);
\r
690 else chord.set(Chord.Interval.FLAT5);
\r
692 if( selectedChordLabel != null ) {
\r
693 selectedChordLabel.setSelection(false);
\r
695 (selectedChordLabel = cl).setSelection(true);
\r
696 setSelectedChord(chord);
\r
698 else if( obj instanceof Co5Label ) {
\r
699 int v = ((Co5Label)obj).co5Value;
\r
700 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
701 setKeySignature( new Key(Music.oppositeCo5(v)) );
\r
703 else if ( v == key.toCo5() ) {
\r
705 // Cancel selected chord
\r
707 setSelectedChord( (Chord)null );
\r
711 setKeySignature( new Key(v) );
\r
714 requestFocusInWindow();
\r
717 public void mouseReleased(MouseEvent e) { destinationChordLabel = null; }
\r
718 public void mouseEntered(MouseEvent e) { }
\r
719 public void mouseExited(MouseEvent e) { }
\r
720 public void mouseClicked(MouseEvent e) { }
\r
721 public void mouseDragged(MouseEvent e) {
\r
722 Component obj = e.getComponent();
\r
723 if( obj instanceof ChordLabel ) {
\r
724 ChordLabel l_src = (ChordLabel)obj;
\r
725 Component obj2 = this.getComponentAt(
\r
726 l_src.getX() + e.getX(),
\r
727 l_src.getY() + e.getY()
\r
729 if( obj2 == this ) {
\r
731 // Entered gap between chord buttons - do nothing
\r
736 ( (obj2 instanceof ChordLabel ) ? (ChordLabel)obj2 : null );
\r
737 if( l_dst == l_src ) {
\r
739 // Returned to original chord button
\r
741 destinationChordLabel = null;
\r
744 if( destinationChordLabel != null ) {
\r
746 // Already touched another chord button
\r
750 Chord chord = l_src.chord.clone();
\r
751 if( l_src.isMinor ) {
\r
752 if( l_dst == null ) { // Out of chord buttons
\r
754 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
756 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
758 chord.set(Chord.Interval.SIXTH);
\r
760 else { // Left or up from minor to major
\r
762 chord.set(Chord.Interval.SEVENTH);
\r
765 else if( l_src.isSus4 ) {
\r
766 if( l_dst == null ) { // Out of chord buttons
\r
769 else if( ! l_dst.isSus4 ) { // Down from sus4 to major
\r
770 chord.set(Chord.Interval.MAJOR);
\r
772 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
773 chord.set(Chord.Interval.NINTH);
\r
777 chord.set(Chord.Interval.SEVENTH);
\r
781 if( l_dst == null ) { // Out of chord buttons
\r
784 else if( l_dst.isSus4 ) { // Up from major to sus4
\r
785 chord.set(Chord.Interval.NINTH);
\r
787 else if( l_src.co5Value < l_dst.co5Value ) { // Right
\r
789 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
791 else if( l_dst.isMinor ) { // Down from major to minor
\r
793 chord.set(Chord.Interval.SIXTH);
\r
797 chord.set(Chord.Interval.SEVENTH);
\r
800 if( chord.isSet(Chord.OffsetIndex.NINTH) || (l_src.isSus4 && (l_dst == null || ! l_dst.isSus4) ) ) {
\r
801 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
802 if( e.isShiftDown() ) {
\r
803 chord.set(Chord.Interval.MAJOR_SEVENTH);
\r
806 chord.set(Chord.Interval.SEVENTH);
\r
809 else if( e.isShiftDown() ) {
\r
810 chord.set(Chord.Interval.SIXTH);
\r
814 if( e.isControlDown() )
\r
815 chord.set(Chord.Interval.NINTH);
\r
817 chord.clear(Chord.OffsetIndex.NINTH);
\r
819 if( e.isAltDown() ) {
\r
820 if( l_src.isSus4 ) {
\r
821 chord.set(Chord.Interval.MAJOR);
\r
822 chord.set(Chord.Interval.SHARP5);
\r
825 chord.set(Chord.Interval.FLAT5);
\r
828 setSelectedChord(chord);
\r
829 destinationChordLabel = (l_dst == null ? l_src : l_dst ) ;
\r
831 else if( obj instanceof Co5Label ) {
\r
832 Co5Label l_src = (Co5Label)obj;
\r
833 Component obj2 = this.getComponentAt(
\r
834 l_src.getX() + e.getX(),
\r
835 l_src.getY() + e.getY()
\r
837 if( !(obj2 instanceof Co5Label) ) {
\r
840 Co5Label l_dst = (Co5Label)obj2;
\r
841 int v = l_dst.co5Value;
\r
842 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
843 setKeySignature( new Key(Music.oppositeCo5(v)) );
\r
846 setKeySignature( new Key(v) );
\r
851 public void mouseMoved(MouseEvent e) { }
\r
852 public void mouseWheelMoved(MouseWheelEvent e) {
\r
853 if( selectedChord != null ) {
\r
854 if( e.getWheelRotation() > 0 ) { // Wheel moved down
\r
855 if( --selectedNoteIndex < 0 ) {
\r
856 selectedNoteIndex = selectedChord.numberOfNotes() - 1;
\r
859 else { // Wheel moved up
\r
860 if( ++selectedNoteIndex >= selectedChord.numberOfNotes() ) {
\r
861 selectedNoteIndex = 0;
\r
864 fireChordChanged();
\r
867 private Chord.Interval pcKeyNextShift7;
\r
868 public void keyPressed(KeyEvent e) {
\r
869 int i = -1, i_col = -1, i_row = 1;
\r
870 boolean shiftPressed = false; // True if Shift-key pressed or CapsLocked
\r
871 char keyChar = e.getKeyChar();
\r
872 int keyCode = e.getKeyCode();
\r
873 ChordLabel cl = null;
\r
874 Chord chord = null;
\r
875 int key_co5 = key.toCo5();
\r
876 // System.out.println( keyChar + " Pressed on chord matrix" );
\r
878 if( (i = "6 ".indexOf(keyChar)) >= 0 ) {
\r
879 selectedChord = selectedChordCapo = null;
\r
880 fireChordChanged();
\r
881 pcKeyNextShift7 = null;
\r
884 else if( (i = "asdfghjkl;:]".indexOf(keyChar)) >= 0 ) {
\r
885 i_col = i + key_co5 + 7;
\r
887 else if( (i = "ASDFGHJKL+*}".indexOf(keyChar)) >= 0 ) {
\r
888 i_col = i + key_co5 + 7;
\r
889 shiftPressed = true;
\r
891 else if( (i = "zxcvbnm,./\\".indexOf(keyChar)) >=0 ) {
\r
892 i_col = i + key_co5 + 7;
\r
895 else if( (i = "ZXCVBNM<>?_".indexOf(keyChar)) >=0 ) {
\r
896 i_col = i + key_co5 + 7;
\r
898 shiftPressed = true;
\r
900 else if( (i = "qwertyuiop@[".indexOf(keyChar)) >= 0 ) {
\r
901 i_col = i + key_co5 + 7;
\r
904 else if( (i = "QWERTYUIOP`{".indexOf(keyChar)) >= 0 ) {
\r
905 i_col = i + key_co5 + 7;
\r
907 shiftPressed = true;
\r
909 else if( keyChar == '5' ) {
\r
910 pcKeyNextShift7 = Chord.Interval.MAJOR_SEVENTH; return;
\r
912 else if( keyChar == '7' ) {
\r
913 pcKeyNextShift7 = Chord.Interval.SEVENTH; return;
\r
915 // Shift current key-signature
\r
916 else if( keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_KP_LEFT ) {
\r
918 setKeySignature( new Key(key_co5-1) );
\r
921 else if( keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_KP_RIGHT ) {
\r
923 setKeySignature( new Key(key_co5+1) );
\r
926 else if( keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_KP_DOWN ) {
\r
928 Key key = new Key(key_co5);
\r
930 setKeySignature(key);
\r
933 else if( keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_KP_UP ) {
\r
935 Key key = new Key(key_co5);
\r
937 setKeySignature(key);
\r
940 if( i < 0 ) // No key char found
\r
942 if( i_col < 0 ) i_col += 12; else if( i_col > N_COLUMNS ) i_col -= 12;
\r
943 cl = chordLabels[i_col + N_COLUMNS * i_row];
\r
944 chord = cl.chord.clone();
\r
945 if( shiftPressed ) {
\r
946 chord.set(Chord.Interval.SEVENTH);
\r
948 // specify by previous key
\r
949 else if( pcKeyNextShift7 == null ) {
\r
950 chord.clear(Chord.OffsetIndex.SEVENTH);
\r
953 chord.set(pcKeyNextShift7);
\r
955 if( e.isAltDown() ) {
\r
957 chord.set(Chord.Interval.MAJOR); // To cancel sus4
\r
958 chord.set(Chord.Interval.SHARP5);
\r
961 chord.set(Chord.Interval.FLAT5);
\r
964 if( e.isControlDown() ) { // Cannot use for ninth ?
\r
965 chord.set(Chord.Interval.NINTH);
\r
967 if( selectedChordLabel != null ) clear();
\r
968 (selectedChordLabel = cl).setSelection(true);
\r
969 setSelectedChord(chord);
\r
970 pcKeyNextShift7 = null;
\r
973 public void keyReleased(KeyEvent e) { }
\r
974 public void keyTyped(KeyEvent e) { }
\r
976 public void addChordMatrixListener(ChordMatrixListener l) {
\r
977 listenerList.add(ChordMatrixListener.class, l);
\r
979 public void removeChordMatrixListener(ChordMatrixListener l) {
\r
980 listenerList.remove(ChordMatrixListener.class, l);
\r
982 protected void fireChordChanged() {
\r
983 Object[] listeners = listenerList.getListenerList();
\r
984 for (int i = listeners.length-2; i>=0; i-=2) {
\r
985 if (listeners[i]==ChordMatrixListener.class) {
\r
986 ((ChordMatrixListener)listeners[i+1]).chordChanged();
\r
989 if( selectedChord == null ) clearIndicators();
\r
991 public void fireKeySignatureChanged() {
\r
992 Object[] listeners = listenerList.getListenerList();
\r
993 for (int i = listeners.length-2; i>=0; i-=2) {
\r
994 if (listeners[i]==ChordMatrixListener.class) {
\r
995 ((ChordMatrixListener)listeners[i+1]).keySignatureChanged();
\r
999 private Key key = null;
\r
1000 private Key capoKey = null;
\r
1001 public Key getKeySignature() { return key; }
\r
1002 public Key getKeySignatureCapo() { return capoKey; }
\r
1003 public void setKeySignature( Key key ) {
\r
1004 if( key == null || this.key != null && key.equals(this.key) )
\r
1007 // Clear old value
\r
1008 if( this.key == null ) {
\r
1009 for( i = 0; i < keysigLabels.length; i++ ) {
\r
1010 keysigLabels[i].setBackground(false);
\r
1014 keysigLabels[this.key.toCo5() + 12].setSelection(false);
\r
1015 for( i = Music.mod12(this.key.toCo5()); i < N_COLUMNS; i+=12 ) {
\r
1016 keysigLabels[i].setBackground(false);
\r
1020 keysigLabels[i = key.toCo5() + 12].setSelection(true);
\r
1021 for( i = Music.mod12(key.toCo5()); i < N_COLUMNS; i+=12 ) {
\r
1022 keysigLabels[i].setBackground(true);
\r
1024 // Change chord-label's color & font
\r
1025 int i_color, old_i_color;
\r
1026 for( ChordLabel cl : chordLabels ) {
\r
1027 i_color = ((cl.co5Value - key.toCo5() + 31)/3) & 3;
\r
1028 if( this.key != null ) {
\r
1029 old_i_color = ((cl.co5Value - this.key.toCo5() + 31)/3) & 3;
\r
1030 if( i_color != old_i_color ) {
\r
1031 cl.setBackground(i_color);
\r
1034 else cl.setBackground(i_color);
\r
1035 if( !(cl.isSus4) ) {
\r
1036 if( this.key != null && Music.mod12(cl.co5Value - this.key.toCo5()) == 0)
\r
1037 cl.setBold(false);
\r
1038 if( Music.mod12( cl.co5Value - key.toCo5() ) == 0 )
\r
1042 this.capoKey = (this.key = key).clone().transpose(capoSelecter.getCapo());
\r
1043 for( ChordLabel cl : chordLabels ) cl.keyChanged();
\r
1044 fireKeySignatureChanged();
\r
1046 private int capo = 0;
\r
1049 * @param newCapo 新しいカポ位置
\r
1051 protected void capoChanged(int newCapo) {
\r
1052 if( this.capo == newCapo )
\r
1054 (this.capoKey = this.key.clone()).transpose(this.capo = newCapo);
\r
1055 selectedChordCapo = (
\r
1056 selectedChord == null ? null : selectedChord.clone().transpose(newCapo)
\r
1058 for( ChordLabel cl : chordLabels ) cl.keyChanged();
\r
1059 fireKeySignatureChanged();
\r
1065 public ChordGuide chordGuide = new ChordGuide(this);
\r
1070 private ChordLabel destinationChordLabel = null;
\r
1072 * ドラッグされたかどうか調べます。
\r
1073 * @return ドラッグ先コードボタンがあればtrue
\r
1075 public boolean isDragged() {
\r
1076 return destinationChordLabel != null ;
\r
1079 private boolean isDark = false;
\r
1080 public void setDarkMode(boolean is_dark) {
\r
1081 this.isDark = is_dark;
\r
1082 currentColorset = (is_dark ? darkModeColorset : normalModeColorset);
\r
1083 setBackground( currentColorset.focus[0] );
\r
1084 Key prev_key = key;
\r
1086 setKeySignature(prev_key);
\r
1087 for( int i=0; i < keysigLabels.length; i++ ) keysigLabels[i].setSelection();
\r
1088 for( int i=0; i < chordLabels.length; i++ ) chordLabels[i].setSelection();
\r
1089 chordGuide.setDarkMode( is_dark );
\r
1090 chordDisplay.setDarkMode( is_dark );
\r
1091 Color col = is_dark ? Color.black : null;
\r
1092 capoSelecter.setBackground( col );
\r
1093 capoSelecter.valueSelecter.setBackground( col );
\r
1096 private boolean isPlaying = false;
\r
1097 public boolean isPlaying() { return isPlaying; }
\r
1098 public void setPlaying(boolean is_playing) {
\r
1099 this.isPlaying = is_playing;
\r
1103 private byte currentBeat = 0;
\r
1104 private byte timesigUpper = 4;
\r
1105 public void setBeat(SequenceTickIndex sequenceTickIndex) {
\r
1106 byte beat = (byte)(sequenceTickIndex.lastBeat);
\r
1107 byte tsu = sequenceTickIndex.timesigUpper;
\r
1108 if( currentBeat == beat && timesigUpper == tsu )
\r
1110 timesigUpper = tsu;
\r
1111 currentBeat = beat;
\r
1112 keysigLabels[ key.toCo5() + 12 ].repaint();
\r
1115 private ChordLabel selectedChordLabel = null;
\r
1116 public JComponent getSelectedButton() {
\r
1117 return selectedChordLabel;
\r
1119 private Chord selectedChord = null;
\r
1120 public Chord getSelectedChord() {
\r
1121 return selectedChord;
\r
1123 private Chord selectedChordCapo = null;
\r
1124 public Chord getSelectedChordCapo() {
\r
1125 return selectedChordCapo;
\r
1127 public void setSelectedChordCapo( Chord chord ) {
\r
1128 setNoteIndex(-1); // Cancel arpeggio mode
\r
1129 selectedChord = (chord == null ? null : chord.clone().transpose(-capo,capoKey));
\r
1130 selectedChordCapo = chord;
\r
1131 fireChordChanged();
\r
1133 public void setSelectedChord( Chord chord ) {
\r
1134 setNoteIndex(-1); // Cancel arpeggio mode
\r
1135 selectedChord = chord;
\r
1136 selectedChordCapo = (chord == null ? null : chord.clone().transpose(capo,key));
\r
1137 fireChordChanged();
\r
1139 public void setSelectedChord( String chordSymbol ) {
\r
1140 Chord chord = null;
\r
1141 if( chordSymbol != null && ! chordSymbol.isEmpty() ) {
\r
1143 chord = new Chord(chordSymbol);
\r
1144 } catch( IllegalArgumentException ex ) {
\r
1145 // 手入力で誤ったコードが入力されても無視
\r
1148 setSelectedChord(chord);
\r
1151 private int selectedNoteIndex = -1;
\r
1152 public int getNoteIndex() {
\r
1153 return selectedChord == null || selectedNoteIndex < 0 ? -1 : selectedNoteIndex;
\r
1155 public void setNoteIndex(int noteIndex) {
\r
1156 selectedNoteIndex = noteIndex;
\r
1158 public void clear() {
\r
1159 if( selectedChordLabel != null ) {
\r
1160 selectedChordLabel.setSelection(false);
\r
1161 selectedChordLabel = null;
\r
1163 selectedChord = null; selectedNoteIndex = -1;
\r