OSDN Git Service

リファクタリング for ハロウィンバージョン
[midichordhelper/MIDIChordHelper.git] / src / PianoKeyboard.java
1 \r
2 import java.awt.BorderLayout;\r
3 import java.awt.Color;\r
4 import java.awt.Dimension;\r
5 import java.awt.Graphics;\r
6 import java.awt.Graphics2D;\r
7 import java.awt.Insets;\r
8 import java.awt.Point;\r
9 import java.awt.Rectangle;\r
10 import java.awt.event.ActionEvent;\r
11 import java.awt.event.ComponentAdapter;\r
12 import java.awt.event.ComponentEvent;\r
13 import java.awt.event.FocusEvent;\r
14 import java.awt.event.FocusListener;\r
15 import java.awt.event.InputEvent;\r
16 import java.awt.event.KeyAdapter;\r
17 import java.awt.event.KeyEvent;\r
18 import java.awt.event.MouseAdapter;\r
19 import java.awt.event.MouseEvent;\r
20 import java.awt.event.MouseMotionAdapter;\r
21 import java.util.EventListener;\r
22 import java.util.LinkedList;\r
23 import java.util.Vector;\r
24 \r
25 import javax.sound.midi.MidiChannel;\r
26 import javax.swing.AbstractAction;\r
27 import javax.swing.Action;\r
28 import javax.swing.Box;\r
29 import javax.swing.BoxLayout;\r
30 import javax.swing.DefaultBoundedRangeModel;\r
31 import javax.swing.JButton;\r
32 import javax.swing.JComponent;\r
33 import javax.swing.JPanel;\r
34 import javax.swing.JScrollBar;\r
35 import javax.swing.JSlider;\r
36 import javax.swing.event.ChangeEvent;\r
37 import javax.swing.event.ChangeListener;\r
38 import javax.swing.event.ListDataEvent;\r
39 import javax.swing.event.ListDataListener;\r
40 \r
41 interface PianoKeyboardListener extends EventListener {\r
42         void pianoKeyPressed(int note_no, InputEvent event);\r
43         void pianoKeyReleased(int note_no, InputEvent event);\r
44         void octaveMoved(ChangeEvent e);\r
45         void octaveResized(ChangeEvent e);\r
46 }\r
47 abstract class PianoKeyboardAdapter implements PianoKeyboardListener {\r
48         public void pianoKeyPressed(int n, InputEvent e) { }\r
49         public void pianoKeyReleased(int n, InputEvent e) { }\r
50         public void octaveMoved(ChangeEvent e) { }\r
51         public void octaveResized(ChangeEvent e) { }\r
52 }\r
53 \r
54 /**\r
55  * Piano Keyboard class for MIDI Chord Helper\r
56  *\r
57  * @author\r
58  *      Copyright (C) 2004-2013 Akiyoshi Kamide\r
59  *      http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
60  */\r
61 public class PianoKeyboard extends JComponent {\r
62         /**\r
63          * 最小オクターブ幅\r
64          */\r
65         public static final int MIN_OCTAVES = 3;\r
66         /**\r
67          * 最大オクターブ幅\r
68          */\r
69         public static final int MAX_OCTAVES = MIDISpec.MAX_NOTE_NO / 12 + 1;\r
70         /**\r
71          * 濃いピンク\r
72          */\r
73         public static final Color DARK_PINK = new Color(0xFF,0x50,0x80);\r
74 \r
75         /** 白鍵のサイズ */\r
76         Dimension       whiteKeySize;\r
77         /** 黒鍵のサイズ */\r
78         Dimension       blackKeySize;\r
79         /** ダークモードならtrue */\r
80         boolean         isDark = false;\r
81         /** 1オクターブあたりの幅 */\r
82         float widthPerOctave = Music.SEMITONES_PER_OCTAVE * 10;\r
83 \r
84         /** すべてのピアノキー */\r
85         PianoKey[] keys;\r
86         /** 黒鍵 */\r
87         PianoKey[] blackKeys;\r
88         /** 白鍵 */\r
89         PianoKey[] whiteKeys;\r
90 \r
91         DefaultBoundedRangeModel octaveRangeModel;\r
92         DefaultBoundedRangeModel octaveSizeModel;\r
93         VelocityModel velocityModel = new VelocityModel();\r
94         DefaultMidiChannelComboBoxModel\r
95                 midiChComboboxModel = new DefaultMidiChannelComboBoxModel();\r
96 \r
97         NoteList selectedKeyNoteList = new NoteList();\r
98         Music.Key       keySignature = null;\r
99         Music.Chord     chord = null;\r
100 \r
101         class NoteList extends LinkedList<Integer> { }\r
102 \r
103         public ChordMatrix chord_matrix;\r
104         public ChordDisplay chordDisplay;\r
105         public AnoGakkiLayeredPane anoGakkiLayeredPane;\r
106         public MidiChannelButtonSelecter midi_ch_button_selecter;\r
107 \r
108         NoteList[] channelNotes = new NoteList[MIDISpec.MAX_CHANNELS];\r
109         int[] pitchBendValues = new int[MIDISpec.MAX_CHANNELS];\r
110         int[] pitch_bend_sensitivities = new int[MIDISpec.MAX_CHANNELS];\r
111         int[] modulations = new int[MIDISpec.MAX_CHANNELS];\r
112         VirtualMidiDevice midiDevice = new AbstractVirtualMidiDevice() {\r
113                 {\r
114                         info = new MyInfo();\r
115                         setReceiver( new AbstractMidiStatus() {\r
116                                 {\r
117                                         for( int i=0; i<MIDISpec.MAX_CHANNELS; i++ )\r
118                                                 add(new MidiChannelStatus(i));\r
119                                 }\r
120                         });\r
121                 }\r
122                 class MyInfo extends Info {\r
123                         protected MyInfo() {\r
124                                 super(\r
125                                         "MIDI Keyboard",\r
126                                         "Unknown vendor",\r
127                                         "Software MIDI keyboard",\r
128                                         ""\r
129                                 );\r
130                         }\r
131                 }\r
132         };\r
133         class MidiChannelStatus extends AbstractMidiChannelStatus {\r
134                 public MidiChannelStatus(int channel) {\r
135                         super(channel);\r
136                         channelNotes[channel] = new NoteList();\r
137                         pitch_bend_sensitivities[channel] = 2; // Default is wholetone = 2 semitones\r
138                 }\r
139                 public void fireRpnChanged() {\r
140                         if( data_for != DATA_FOR_RPN ) return;\r
141 \r
142                         // RPN (MSB) - Accept 0x00 only\r
143                         if( controller_values[0x65] != 0x00 ) return;\r
144 \r
145                         // RPN (LSB)\r
146                         switch( controller_values[0x64] ) {\r
147                         case 0x00: // Pitch Bend Sensitivity\r
148                                 if( controller_values[0x06] == 0 ) return;\r
149                                 pitch_bend_sensitivities[channel] = controller_values[0x06];\r
150                                 break;\r
151                         }\r
152                 }\r
153                 //\r
154                 // MidiChannel interface\r
155                 //\r
156                 public void noteOff( int note_no, int velocity ) {\r
157                         noteOff(note_no);\r
158                 }\r
159                 public void noteOff( int note_no ) {\r
160                         keyOff( channel, note_no );\r
161                         if( chord_matrix != null ) {\r
162                                 if( ! isRhythmPart() )\r
163                                         chord_matrix.note(false, note_no);\r
164                         }\r
165                         if( midi_ch_button_selecter != null ) {\r
166                                 midi_ch_button_selecter.repaint();\r
167                         }\r
168                 }\r
169                 public void noteOn( int note_no, int velocity ) {\r
170                         if( velocity <= 0 ) {\r
171                                 noteOff(note_no); return;\r
172                         }\r
173                         keyOn( channel, note_no );\r
174                         if( midiChComboboxModel.getSelectedChannel() == channel ) {\r
175                                 if( chordDisplay != null ) {\r
176                                         if( chord_matrix != null && chord_matrix.isPlaying() )\r
177                                                 chordDisplay.setNote(-1);\r
178                                         else\r
179                                                 chordDisplay.setNote( note_no, isRhythmPart() );\r
180                                 }\r
181                                 if( anoGakkiLayeredPane != null ) {\r
182                                         PianoKey piano_key = getPianoKey(note_no);\r
183                                         if( piano_key != null )\r
184                                                 anoGakkiLayeredPane.start(\r
185                                                                 PianoKeyboard.this, piano_key.indicator\r
186                                                                 );\r
187                                 }\r
188                         }\r
189                         if( chord_matrix != null ) {\r
190                                 if( ! isRhythmPart() )\r
191                                         chord_matrix.note(true, note_no);\r
192                         }\r
193                         if( midi_ch_button_selecter != null ) {\r
194                                 midi_ch_button_selecter.repaint();\r
195                         }\r
196                 }\r
197                 public void allNotesOff() {\r
198                         allKeysOff( channel, -1 );\r
199                         if( chord_matrix != null )\r
200                                 chord_matrix.clearIndicators();\r
201                 }\r
202                 public void setPitchBend(int bend) {\r
203                         super.setPitchBend(bend);\r
204                         pitchBendValues[channel] = bend;\r
205                         repaintNotes();\r
206                 }\r
207                 public void resetAllControllers() {\r
208                         super.resetAllControllers();\r
209                         //\r
210                         // See also: Response to Reset All Controllers\r
211                         //     http://www.midi.org/about-midi/rp15.shtml\r
212                         //\r
213                         pitchBendValues[channel] = MIDISpec.PITCH_BEND_NONE;\r
214                         modulations[channel] = 0;\r
215                         repaintNotes();\r
216                 }\r
217                 public void controlChange(int controller, int value) {\r
218                         super.controlChange(controller,value);\r
219                         switch( controller ) {\r
220                         case 0x01: // Moduration (MSB)\r
221                                 modulations[channel] = value;\r
222                                 repaintNotes();\r
223                                 break;\r
224                         }\r
225                 }\r
226                 private void repaintNotes() {\r
227                         if( midiChComboboxModel.getSelectedChannel() != channel\r
228                                         || channelNotes[channel] == null\r
229                                         )\r
230                                 return;\r
231                         if( channelNotes[channel].size() > 0 || selectedKeyNoteList.size() > 0 )\r
232                                 repaint();\r
233                 }\r
234         }\r
235         public MidiChannel getSelectedChannel() {\r
236                 return midiDevice.getChannels()[midiChComboboxModel.getSelectedChannel()];\r
237         }\r
238         public void note(boolean is_on, int note_no) {\r
239                 MidiChannel ch = getSelectedChannel();\r
240                 int velocity = velocityModel.getValue();\r
241                 if( is_on )\r
242                         ch.noteOn(note_no,velocity);\r
243                 else\r
244                         ch.noteOff(note_no,velocity);\r
245         }\r
246         public void noteOn(int note_no) { note(true,note_no); }\r
247         public void noteOff(int note_no) { note(false,note_no); }\r
248 \r
249         class PianoKey extends Rectangle {\r
250                 public boolean isBlack = false;\r
251                 public int position = 0;\r
252                 public String binded_key_char = null;\r
253                 public Rectangle indicator;\r
254                 public boolean out_of_bounds = false;\r
255                 public PianoKey( Point p, Dimension d, Dimension indicator_size ) {\r
256                         super(p,d);\r
257                         Point indicator_position = new Point(\r
258                                         p.x + (d.width - indicator_size.width) / 2,\r
259                                         p.y + d.height - indicator_size.height - indicator_size.height / 2 + 2\r
260                                         );\r
261                         indicator = new Rectangle( indicator_position, indicator_size );\r
262                 }\r
263                 int getNote(int chromatic_offset) {\r
264                         int n = position + chromatic_offset;\r
265                         return (out_of_bounds = ( n > MIDISpec.MAX_NOTE_NO )) ? -1 : n;\r
266                 }\r
267                 boolean paintKey(Graphics2D g2, boolean is_pressed) {\r
268                         if( out_of_bounds ) return false;\r
269                         g2.fill3DRect( x, y, width, height, !is_pressed );\r
270                         return true;\r
271                 }\r
272                 boolean paintKey(Graphics2D g2) {\r
273                         return paintKey(g2,false);\r
274                 }\r
275                 boolean paintKeyBinding(Graphics2D g2) {\r
276                         if( binded_key_char == null ) return false;\r
277                         g2.drawString( binded_key_char, x + width/3, indicator.y - 2 );\r
278                         return true;\r
279                 }\r
280                 boolean paintIndicator(Graphics2D g2, boolean is_small, int pitch_bend_value) {\r
281                         if( is_small ) {\r
282                                 g2.fillOval(\r
283                                         indicator.x + indicator.width/4,\r
284                                         indicator.y + indicator.height/4 + 1,\r
285                                         indicator.width/2,\r
286                                         indicator.height/2\r
287                                 );\r
288                         }\r
289                         else {\r
290                                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
291                                 int sens = pitch_bend_sensitivities[current_channel];\r
292                                 if( sens == 0 ) {\r
293                                         sens = 2;\r
294                                 }\r
295                                 int x_offset = (\r
296                                         7 * whiteKeySize.width * sens * (pitch_bend_value - MIDISpec.PITCH_BEND_NONE)\r
297                                 ) / (12 * 8192);\r
298                                 int additional_height = indicator.height * modulations[current_channel] / 256 ;\r
299                                 int y_offset = additional_height / 2 ;\r
300                                 g2.fillOval(\r
301                                         indicator.x + ( x_offset < 0 ? x_offset : 0 ),\r
302                                         indicator.y - y_offset,\r
303                                         indicator.width + ( x_offset < 0 ? -x_offset : x_offset ),\r
304                                         indicator.height + additional_height\r
305                                 );\r
306                         }\r
307                         return true;\r
308                 }\r
309                 boolean paintIndicator(Graphics2D g2, boolean is_small) {\r
310                         return paintIndicator( g2, is_small, 0 );\r
311                 }\r
312         }\r
313         //\r
314         // Constructors\r
315         //\r
316         public PianoKeyboard() {\r
317                 setLayout(new BorderLayout());\r
318                 setFocusable(true);\r
319                 addFocusListener( new FocusListener() {\r
320                         public void focusGained(FocusEvent e) { repaint(); }\r
321                         public void focusLost(FocusEvent e)   { repaint(); }\r
322                 });\r
323                 addMouseListener( new MouseAdapter() {\r
324                         public void mousePressed(MouseEvent e) {\r
325                                 int n = getNote(e.getPoint()); if( n < 0 ) return;\r
326                                 int currentChannel = midiChComboboxModel.getSelectedChannel();\r
327                                 if( channelNotes[currentChannel].contains(n) ) return;\r
328                                 chord = null;\r
329                                 keyOn( currentChannel, n );\r
330                                 noteOn(n);\r
331                                 firePianoKeyPressed( n, e );\r
332                                 requestFocusInWindow();\r
333                                 repaint();\r
334                         }\r
335                         public void mouseReleased(MouseEvent e) {\r
336                                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
337                                 if( channelNotes[current_channel].isEmpty() ) return;\r
338                                 int n = channelNotes[current_channel].poll();\r
339                                 keyOff( current_channel, n );\r
340                                 noteOff(n);\r
341                                 firePianoKeyReleased( n, e );\r
342                         }\r
343                 });\r
344                 addMouseMotionListener( new MouseMotionAdapter() {\r
345                         public void mouseDragged(MouseEvent e) {\r
346                                 int n = getNote(e.getPoint()); if( n < 0 ) return;\r
347                                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
348                                 if( channelNotes[current_channel].contains(n) ) return;\r
349                                 if( channelNotes[current_channel].size() > 0 ) {\r
350                                         int old_n = channelNotes[current_channel].poll();\r
351                                         keyOff( current_channel, old_n );\r
352                                         noteOff(old_n);\r
353                                         firePianoKeyReleased( old_n, e );\r
354                                 }\r
355                                 keyOn( current_channel, n );\r
356                                 noteOn(n);\r
357                                 firePianoKeyPressed( n, e );\r
358                         }\r
359                 });\r
360                 addKeyListener( new KeyAdapter() {\r
361                         public void keyPressed(KeyEvent e) {\r
362                                 int key_code = e.getKeyCode();\r
363                                 if( key_code == KeyEvent.VK_LEFT || key_code == KeyEvent.VK_KP_LEFT ) {\r
364                                         octaveRangeModel.setValue( octaveRangeModel.getValue() - 1 );\r
365                                         return;\r
366                                 }\r
367                                 else if( key_code == KeyEvent.VK_RIGHT || key_code == KeyEvent.VK_KP_RIGHT ) {\r
368                                         octaveRangeModel.setValue( octaveRangeModel.getValue() + 1 );\r
369                                         return;\r
370                                 }\r
371                                 int n = getNote(e); if( n < 0 ) return;\r
372                                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
373                                 if( channelNotes[current_channel].contains(n) ) return;\r
374                                 chord = null;\r
375                                 keyOn( current_channel, n );\r
376                                 noteOn(n);\r
377                                 firePianoKeyPressed( n, e );\r
378                         }\r
379                         public void keyReleased(KeyEvent e) {\r
380                                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
381                                 int n = getNote(e);\r
382                                 if( n < 0 || ! channelNotes[current_channel].contains(n) ) return;\r
383                                 keyOff( current_channel, n );\r
384                                 noteOff(n);\r
385                                 firePianoKeyReleased( n, e );\r
386                         }\r
387                 });\r
388                 int octaves = getPerferredOctaves();\r
389                 octaveSizeModel = new DefaultBoundedRangeModel(\r
390                         octaves, 0, MIN_OCTAVES, MAX_OCTAVES\r
391                 );\r
392                 octaveSizeModel.addChangeListener( new ChangeListener() {\r
393                         public void stateChanged(ChangeEvent e) {\r
394                                 fireOctaveResized(e);\r
395                                 octaveSizeChanged();\r
396                         }\r
397                 });\r
398                 octaveRangeModel = new DefaultBoundedRangeModel(\r
399                         (MAX_OCTAVES - octaves) / 2, octaves, 0, MAX_OCTAVES\r
400                 );\r
401                 octaveRangeModel.addChangeListener( new ChangeListener() {\r
402                         public void stateChanged(ChangeEvent e) {\r
403                                 fireOctaveMoved(e);\r
404                                 checkOutOfBounds();\r
405                                 repaint();\r
406                         }\r
407                 });\r
408                 addComponentListener( new ComponentAdapter() {\r
409                         public void componentResized(ComponentEvent e) {\r
410                                 octaveSizeModel.setValue( getPerferredOctaves() );\r
411                                 octaveSizeChanged();\r
412                         }\r
413                 });\r
414                 midiChComboboxModel.addListDataListener(\r
415                         new ListDataListener() {\r
416                                 public void contentsChanged(ListDataEvent e) {\r
417                                         int current_channel = midiChComboboxModel.getSelectedChannel();\r
418                                         for( int n : channelNotes[current_channel] )\r
419                                                 if( autoScroll(n) ) break;\r
420                                         repaint();\r
421                                 }\r
422                                 public void intervalAdded(ListDataEvent e) {}\r
423                                 public void intervalRemoved(ListDataEvent e) {}\r
424                         }\r
425                 );\r
426         }\r
427         public void paint(Graphics g) {\r
428                 if( keys == null ) return;\r
429                 Graphics2D g2 = (Graphics2D) g;\r
430                 Dimension d = getSize();\r
431                 //\r
432                 // 鍵盤をクリア\r
433                 g2.setBackground( getBackground() );\r
434                 g2.clearRect( 0, 0, d.width, d.height );\r
435                 //\r
436                 // 白鍵を描画\r
437                 g2.setColor( isDark ? Color.gray : Color.white );\r
438                 for( PianoKey k : whiteKeys ) k.paintKey(g2);\r
439 \r
440                 NoteList notesArray[] = {\r
441                         (NoteList)selectedKeyNoteList.clone(),\r
442                         (NoteList)channelNotes[midiChComboboxModel.getSelectedChannel()].clone()\r
443                 };\r
444                 PianoKey key;\r
445                 //\r
446                 // ノートオン状態の白鍵を塗り重ねる\r
447                 for( int n : notesArray[1] )\r
448                         if( (key=getPianoKey(n)) != null && !(key.isBlack) )\r
449                                 key.paintKey(g2,true);\r
450                 //\r
451                 // 黒鍵を描画\r
452                 g2.setColor(getForeground());\r
453                 for( PianoKey k : blackKeys ) k.paintKey(g2);\r
454                 //\r
455                 // ノートオン状態の黒鍵を塗り重ねる\r
456                 g2.setColor( Color.gray );\r
457                 for( int n : notesArray[1] )\r
458                         if( (key=getPianoKey(n)) != null && key.isBlack )\r
459                                 key.paintKey(g2,true);\r
460                 //\r
461                 // インジケータの表示\r
462                 for( NoteList nl : notesArray ) {\r
463                         for( int n : nl ) {\r
464                                 if( (key=getPianoKey(n)) == null )\r
465                                         continue;\r
466                                 boolean isOnScale = (keySignature == null || keySignature.isOnScale(n));\r
467                                 int chordIndex;\r
468                                 if( chord != null && (chordIndex = chord.indexOf(n)) >=0 ) {\r
469                                         g2.setColor(Music.Chord.NOTE_INDEX_COLORS[chordIndex]);\r
470                                 }\r
471                                 else {\r
472                                         g2.setColor(isDark && isOnScale ? Color.pink : DARK_PINK);\r
473                                 }\r
474                                 key.paintIndicator(\r
475                                         g2, false,\r
476                                         pitchBendValues[midiChComboboxModel.getSelectedChannel()]\r
477                                 );\r
478                                 if( ! isOnScale ) {\r
479                                         g2.setColor(Color.white);\r
480                                         key.paintIndicator(g2, true);\r
481                                 }\r
482                         }\r
483                 }\r
484 \r
485                 if( isFocusOwner() ) {\r
486                         // Show PC-key binding\r
487                         for( PianoKey k : bindedKeys ) {\r
488                                 g2.setColor(\r
489                                         k.isBlack ? Color.gray.brighter() :\r
490                                         isDark ? getForeground() :\r
491                                         getForeground().brighter()\r
492                                 );\r
493                                 k.paintKeyBinding(g2);\r
494                         }\r
495                 }\r
496         }\r
497         //\r
498         protected void firePianoKeyPressed(int note_no, InputEvent event) {\r
499                 Object[] listeners = listenerList.getListenerList();\r
500                 for (int i = listeners.length-2; i>=0; i-=2) {\r
501                         if (listeners[i]==PianoKeyboardListener.class) {\r
502                                 ((PianoKeyboardListener)listeners[i+1]).pianoKeyPressed(note_no,event);\r
503                         }\r
504                 }\r
505         }\r
506         protected void firePianoKeyReleased(int note_no, InputEvent event) {\r
507                 Object[] listeners = listenerList.getListenerList();\r
508                 for (int i = listeners.length-2; i>=0; i-=2) {\r
509                         if (listeners[i]==PianoKeyboardListener.class) {\r
510                                 ((PianoKeyboardListener)listeners[i+1]).pianoKeyReleased(note_no,event);\r
511                         }\r
512                 }\r
513         }\r
514         protected void fireOctaveMoved(ChangeEvent event) {\r
515                 Object[] listeners = listenerList.getListenerList();\r
516                 for (int i = listeners.length-2; i>=0; i-=2) {\r
517                         if (listeners[i]==PianoKeyboardListener.class) {\r
518                                 ((PianoKeyboardListener)listeners[i+1]).octaveMoved(event);\r
519                         }\r
520                 }\r
521         }\r
522         protected void fireOctaveResized(ChangeEvent event) {\r
523                 Object[] listeners = listenerList.getListenerList();\r
524                 for (int i = listeners.length-2; i>=0; i-=2) {\r
525                         if (listeners[i]==PianoKeyboardListener.class) {\r
526                                 ((PianoKeyboardListener)listeners[i+1]).octaveResized(event);\r
527                         }\r
528                 }\r
529         }\r
530         public PianoKey getPianoKey(int note_no) {\r
531                 int i = note_no - octaveRangeModel.getValue() * 12 ;\r
532                 return i>=0 && i<keys.length ? keys[i]: null;\r
533         }\r
534         private int getNote(Point point) {\r
535                 PianoKey k = getPianoKey(point);\r
536                 return k==null ? -1 : k.getNote(getChromaticOffset());\r
537         }\r
538         private PianoKey getPianoKey(Point point) {\r
539                 int i_white_key = point.x / whiteKeySize.width;\r
540                 int i_octave = i_white_key / 7;\r
541                 int i = (i_white_key -= i_octave * 7) * 2 + i_octave * 12;\r
542                 if( i_white_key >= 3 ) i--;\r
543 \r
544                 if( i < 0 || i > keys.length-1 ) return null;\r
545 \r
546                 if( point.y > blackKeySize.height )\r
547                         return keys[i];\r
548 \r
549                 PianoKey k;\r
550                 if( i > 0 ) {\r
551                         k = keys[i-1];\r
552                         if( k.isBlack && !(k.out_of_bounds) && k.contains(point) ) return k;\r
553                 }\r
554                 if( i < keys.length-1 ) {\r
555                         k = keys[i+1];\r
556                         if( k.isBlack && !(k.out_of_bounds) && k.contains(point) ) return k;\r
557                 }\r
558                 return keys[i];\r
559         }\r
560 \r
561         PianoKey[]              bindedKeys;\r
562         private int             bindedKeyPosition;\r
563         private String  bindedKeyChars;\r
564         private PianoKey getPianoKey(KeyEvent e) {\r
565                 int i = bindedKeyChars.indexOf(e.getKeyChar());\r
566                 return i >= 0 ? keys[bindedKeyPosition + i] : null;\r
567         }\r
568         private int getNote(KeyEvent e) {\r
569                 PianoKey k = getPianoKey(e);\r
570                 return k==null ? -1 : k.getNote(getChromaticOffset());\r
571         }\r
572         void changeKeyBinding( int from, String key_chars ) {\r
573                 PianoKey k;\r
574                 bindedKeys = new PianoKey[(bindedKeyChars = key_chars).length()];\r
575                 bindedKeyPosition = from;\r
576                 for( int i = 0; i < bindedKeyChars.length(); i++ ) {\r
577                         bindedKeys[i] = k = keys[ bindedKeyPosition + i ];\r
578                         k.binded_key_char = bindedKeyChars.substring( i, i+1 );\r
579                 }\r
580                 repaint();\r
581         }\r
582 \r
583         private void checkOutOfBounds() {\r
584                 if( keys == null ) return;\r
585                 for( PianoKey k : keys ) k.getNote(getChromaticOffset());\r
586         }\r
587         void keyOff(int ch, int note_no) {\r
588                 if( note_no < 0 || ch < 0 || ch >= channelNotes.length ) return;\r
589                 channelNotes[ch].remove((Object)note_no);\r
590                 if( ch == midiChComboboxModel.getSelectedChannel() )\r
591                         repaint();\r
592         }\r
593         void keyOn(int ch, int note_no) {\r
594                 if( note_no < 0 || ch < 0 || ch >= channelNotes.length ) return;\r
595                 channelNotes[ch].add(note_no);\r
596                 setSelectedNote(ch,note_no);\r
597         }\r
598         boolean autoScroll(int note_no) {\r
599                 if( octaveRangeModel == null || keys == null )\r
600                         return false;\r
601                 int i = note_no - getChromaticOffset();\r
602                 if( i < 0 ) {\r
603                         octaveRangeModel.setValue(\r
604                                 octaveRangeModel.getValue() - (-i)/Music.SEMITONES_PER_OCTAVE - 1\r
605                         );\r
606                         return true;\r
607                 }\r
608                 if( i >= keys.length ) {\r
609                         octaveRangeModel.setValue(\r
610                                 octaveRangeModel.getValue() + (i-keys.length)/Music.SEMITONES_PER_OCTAVE + 1\r
611                         );\r
612                         return true;\r
613                 }\r
614                 return false;\r
615         }\r
616         void addPianoKeyboardListener(PianoKeyboardListener l) {\r
617                 listenerList.add(PianoKeyboardListener.class, l);\r
618         }\r
619         void removePianoKeyboardListener(PianoKeyboardListener l) {\r
620                 listenerList.remove(PianoKeyboardListener.class, l);\r
621         }\r
622         int countKeyOn() {\r
623                 return channelNotes[midiChComboboxModel.getSelectedChannel()].size();\r
624         }\r
625         int countKeyOn(int ch) {\r
626                 return channelNotes[ch].size();\r
627         }\r
628         void allKeysOff(int ch, int n_marks) {\r
629                 if( ! selectedKeyNoteList.isEmpty() ) return;\r
630                 switch(n_marks) {\r
631                 case -1:\r
632                         selectedKeyNoteList = (NoteList)(channelNotes[ch].clone());\r
633                         break;\r
634                 case  1:\r
635                         selectedKeyNoteList.add(\r
636                                 channelNotes[ch].get(channelNotes[ch].size()-1)\r
637                         );\r
638                         break;\r
639                 default: break;\r
640                 }\r
641                 channelNotes[ch].clear();\r
642                 if( midiChComboboxModel.getSelectedChannel() == ch )\r
643                         repaint();\r
644         }\r
645         void clear() {\r
646                 selectedKeyNoteList.clear();\r
647                 channelNotes[midiChComboboxModel.getSelectedChannel()].clear();\r
648                 chord = null;\r
649                 repaint();\r
650         }\r
651         int getNote() {\r
652                 int current_channel = midiChComboboxModel.getSelectedChannel();\r
653                 switch( channelNotes[current_channel].size() ) {\r
654                 case 1: return channelNotes[current_channel].get(0);\r
655                 case 0:\r
656                         if( selectedKeyNoteList.size() == 1 )\r
657                                 return selectedKeyNoteList.get(0);\r
658                         return -1;\r
659                 default:\r
660                         return -1;\r
661                 }\r
662         }\r
663         void setSelectedNote(int noteNumber) {\r
664                 setSelectedNote(midiChComboboxModel.getSelectedChannel(), noteNumber);\r
665         }\r
666         void setSelectedNote(int ch, int note_no) {\r
667                 if( ch != midiChComboboxModel.getSelectedChannel() )\r
668                         return;\r
669                 selectedKeyNoteList.add(note_no);\r
670                 int max_sel = (chord == null ? maxSelectable : chord.numberOfNotes());\r
671                 while( selectedKeyNoteList.size() > max_sel )\r
672                         selectedKeyNoteList.poll();\r
673                 if( !autoScroll(note_no) ) {\r
674                         // When autoScroll() returned false, stateChanged() not invoked - need repaint()\r
675                         repaint();\r
676                 }\r
677         }\r
678         Integer[] getSelectedNotes() {\r
679                 return selectedKeyNoteList.toArray(new Integer[0]);\r
680         }\r
681         //\r
682         Music.Chord getChord() { return chord; }\r
683         void setChord(Music.Chord c) {\r
684                 chordDisplay.setChord(chord = c);\r
685         }\r
686         void setKeySignature(Music.Key ks) {\r
687                 keySignature = ks;\r
688                 repaint();\r
689         }\r
690         //\r
691         private int     maxSelectable = 1;\r
692         void setMaxSelectable( int max_selectable ) {\r
693                 this.maxSelectable = max_selectable;\r
694         }\r
695         int getMaxSelectable() { return maxSelectable; }\r
696         //\r
697         int getChromaticOffset() {\r
698                 return octaveRangeModel.getValue() * 12 ;\r
699         }\r
700         int getOctaves() {\r
701                 return octaveSizeModel.getValue();\r
702         }\r
703         private int getPerferredOctaves() {\r
704                 int octaves = Math.round( (float)getWidth() / widthPerOctave );\r
705                 if( octaves > MAX_OCTAVES ) {\r
706                         octaves = MAX_OCTAVES;\r
707                 }\r
708                 else if( octaves < MIN_OCTAVES ) {\r
709                         octaves = MIN_OCTAVES;\r
710                 }\r
711                 return octaves;\r
712         }\r
713         private void octaveSizeChanged() {\r
714                 int octaves = octaveSizeModel.getValue();\r
715                 String defaultBindedKeyChars = "zsxdcvgbhnjm,l.;/\\]";\r
716                 Dimension keyboard_size = getSize();\r
717                 if( keyboard_size.width == 0 ) {\r
718                         return;\r
719                 }\r
720                 whiteKeySize = new Dimension(\r
721                         (keyboard_size.width - 1) / (octaves * 7 + 1),\r
722                         keyboard_size.height - 1\r
723                 );\r
724                 blackKeySize = new Dimension(\r
725                         whiteKeySize.width * 3 / 4,\r
726                         whiteKeySize.height * 3 / 5\r
727                 );\r
728                 Dimension indicatorSize = new Dimension(\r
729                         whiteKeySize.width / 2,\r
730                         whiteKeySize.height / 6\r
731                 );\r
732                 octaveRangeModel.setExtent( octaves );\r
733                 octaveRangeModel.setValue( (MAX_OCTAVES - octaves) / 2 );\r
734                 widthPerOctave = keyboard_size.width / octaves;\r
735                 //\r
736                 // Construct piano-keys\r
737                 //\r
738                 keys = new PianoKey[ octaves * 12 + 1 ];\r
739                 Vector<PianoKey> vBlackKeys = new Vector<PianoKey>();\r
740                 Vector<PianoKey> vWhiteKeys = new Vector<PianoKey>();\r
741                 Point keyPoint = new Point(1,1);\r
742                 PianoKey k;\r
743                 int i, i12;\r
744                 boolean is_CDE = true;\r
745                 for( i = i12 = 0; i < keys.length; i++, i12++ ) {\r
746                         switch(i12) {\r
747                         case 12: is_CDE = true; i12 = 0; break;\r
748                         case  5: is_CDE = false; break;\r
749                         default: break;\r
750                         }\r
751                         keyPoint.x = whiteKeySize.width * (\r
752                                 i / Music.SEMITONES_PER_OCTAVE * 7 + (i12+(is_CDE?1:2))/2\r
753                         );\r
754                         if( Music.isOnScale(i12,0) ) {\r
755                                 k = new PianoKey( keyPoint, whiteKeySize, indicatorSize );\r
756                                 k.isBlack = false;\r
757                                 vWhiteKeys.add(k);\r
758                         }\r
759                         else {\r
760                                 keyPoint.x -= ( (is_CDE?5:12) - i12 )/2 * blackKeySize.width / (is_CDE?3:4);\r
761                                 k = new PianoKey( keyPoint, blackKeySize, indicatorSize );\r
762                                 k.isBlack = true;\r
763                                 vBlackKeys.add(k);\r
764                         }\r
765                         (keys[i] = k).position = i;\r
766                 }\r
767                 whiteKeys = vWhiteKeys.toArray(new PianoKey[1]);\r
768                 blackKeys = vBlackKeys.toArray(new PianoKey[1]);\r
769                 changeKeyBinding(((octaves - 1) / 2) * 12, defaultBindedKeyChars);\r
770                 checkOutOfBounds();\r
771         }\r
772         //\r
773         void setDarkMode(boolean isDark) {\r
774                 this.isDark = isDark;\r
775                 setBackground( isDark ? Color.black : null );\r
776         }\r
777 }\r
778 \r
779 class PianoKeyboardPanel extends JPanel\r
780 {\r
781         PianoKeyboard keyboard = new PianoKeyboard();\r
782         private JSlider octaveSizeSlider = new JSlider() {\r
783                 {\r
784                         setToolTipText("Octave size");\r
785                 }\r
786         };\r
787         private JScrollBar octaveSelecter = new JScrollBar(JScrollBar.HORIZONTAL) {\r
788                 {\r
789                         setToolTipText("Octave position");\r
790                 }\r
791         };\r
792         private JPanel octaveBar = new JPanel() {\r
793                 {\r
794                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
795                 }\r
796         };\r
797         public PianoKeyboardPanel() {\r
798                 keyboard.addPianoKeyboardListener(\r
799                         new PianoKeyboardAdapter() {\r
800                                 public void octaveResized(ChangeEvent e) {\r
801                                         octaveSelecter.setBlockIncrement(keyboard.getOctaves());\r
802                                 }\r
803                         }\r
804                 );\r
805                 octaveSelecter.setModel( keyboard.octaveRangeModel );\r
806                 octaveSelecter.setBlockIncrement( keyboard.getOctaves() );\r
807                 octaveSizeSlider.setModel( keyboard.octaveSizeModel );\r
808                 octaveSizeSlider.setMinimumSize( new Dimension( 100, 18 ) );\r
809                 octaveSizeSlider.setMaximumSize( new Dimension( 100, 18 ) );\r
810                 octaveSizeSlider.setPreferredSize( new Dimension( 100, 18 ) );\r
811                 octaveBar.add(octaveSelecter);\r
812                 octaveBar.add(Box.createHorizontalStrut(5));\r
813                 octaveBar.add(octaveSizeSlider);\r
814                 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
815                 add(octaveBar);\r
816                 add(keyboard);\r
817                 setAlignmentX((float)0.5);\r
818         }\r
819         public void setDarkMode(boolean isDark) {\r
820                 Color col = isDark ? Color.black : null;\r
821                 octaveSelecter.setBackground( col );\r
822                 octaveSizeSlider.setBackground( col );\r
823                 octaveBar.setBackground( col );\r
824                 keyboard.setDarkMode( isDark );\r
825         }\r
826 }\r
827 \r
828 class MidiKeyboardPanel extends JPanel {\r
829         MidiEventDialog eventDialog;\r
830         Action sendSventAction = new AbstractAction() {\r
831                 {\r
832                         putValue(NAME,"Send");\r
833                 }\r
834                 public void actionPerformed(ActionEvent e) {\r
835                         keyboardCenterPanel.keyboard.midiDevice.sendMidiMessage(\r
836                                 eventDialog.midiMessageForm.getMessage()\r
837                         );\r
838                 }\r
839         };\r
840         JButton sendEventButton = new JButton(\r
841                 new AbstractAction() {\r
842                         {\r
843                                 putValue(NAME,"Send MIDI event");\r
844                         }\r
845                         public void actionPerformed(ActionEvent e) {\r
846                                 eventDialog.setTitle("Send MIDI event");\r
847                                 eventDialog.okButton.setAction(sendSventAction);\r
848                                 eventDialog.midiMessageForm.channelText.setSelectedChannel(\r
849                                         keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel()\r
850                                 );\r
851                                 eventDialog.openMessageForm();\r
852                         }\r
853                 }\r
854         );\r
855         JPanel keyboardChordPanel = new JPanel() {\r
856                 {\r
857                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
858                 }\r
859         };\r
860         JPanel keyboardSouthPanel = new JPanel() {\r
861                 {\r
862                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
863                 }\r
864         };\r
865         KeySignatureSelecter keySelecter = new KeySignatureSelecter(false);\r
866         PianoKeyboardPanel keyboardCenterPanel = new PianoKeyboardPanel();\r
867 \r
868         MidiChannelComboSelecter midiChannelCombobox =\r
869                 new MidiChannelComboSelecter(\r
870                         "MIDI Channel",\r
871                         keyboardCenterPanel.keyboard.midiChComboboxModel\r
872                 );\r
873         MidiChannelButtonSelecter midiChannelButtons =\r
874                 new MidiChannelButtonSelecter(keyboardCenterPanel.keyboard);\r
875         VelocitySelecter velocitySelecter =\r
876                 new VelocitySelecter(keyboardCenterPanel.keyboard.velocityModel);\r
877 \r
878         private static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
879 \r
880         public MidiKeyboardPanel( ChordMatrix chordMatrix ) {\r
881                 keyboardCenterPanel.keyboard.chord_matrix = chordMatrix;\r
882                 keyboardCenterPanel.keyboard.chordDisplay =\r
883                         new ChordDisplay(\r
884                                 "MIDI Keyboard", chordMatrix, keyboardCenterPanel.keyboard\r
885                         );\r
886                 keyboardChordPanel.add( Box.createHorizontalStrut(5) );\r
887                 keyboardChordPanel.add( velocitySelecter );\r
888                 keyboardChordPanel.add( keySelecter );\r
889                 keyboardChordPanel.add( keyboardCenterPanel.keyboard.chordDisplay );\r
890                 keyboardChordPanel.add( Box.createHorizontalStrut(5) );\r
891                 sendEventButton.setMargin(ZERO_INSETS);\r
892                 keyboardSouthPanel.add( midiChannelCombobox );\r
893                 keyboardSouthPanel.add( midiChannelButtons );\r
894                 keyboardSouthPanel.add( sendEventButton );\r
895                 //\r
896                 setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );\r
897                 add( keyboardChordPanel );\r
898                 add( keyboardCenterPanel );\r
899                 add( Box.createVerticalStrut(5) );\r
900                 add( keyboardSouthPanel );\r
901         }\r
902 \r
903         // Methods\r
904         //\r
905         public void setDarkMode(boolean isDark) {\r
906                 Color col = isDark ? Color.black : null;\r
907                 setBackground(col);\r
908                 keyboardCenterPanel.setDarkMode(isDark);\r
909                 keyboardChordPanel.setBackground(col);\r
910                 keyboardSouthPanel.setBackground(col);\r
911                 midiChannelButtons.setBackground(col);\r
912                 midiChannelCombobox.setBackground(col);\r
913                 midiChannelCombobox.comboBox.setBackground(col);\r
914                 keySelecter.setBackground(col);\r
915                 keySelecter.keysigCombobox.setBackground(col);\r
916                 velocitySelecter.setBackground(col);\r
917                 keyboardCenterPanel.keyboard.chordDisplay.setDarkMode(isDark);\r
918                 sendEventButton.setBackground(col);\r
919         }\r
920 \r
921 }\r