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
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
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
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
55 * Piano Keyboard class for MIDI Chord Helper
\r
58 * Copyright (C) 2004-2013 Akiyoshi Kamide
\r
59 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
61 public class PianoKeyboard extends JComponent {
\r
65 public static final int MIN_OCTAVES = 3;
\r
69 public static final int MAX_OCTAVES = MIDISpec.MAX_NOTE_NO / 12 + 1;
\r
73 public static final Color DARK_PINK = new Color(0xFF,0x50,0x80);
\r
76 Dimension whiteKeySize;
\r
78 Dimension blackKeySize;
\r
80 boolean isDark = false;
\r
82 float widthPerOctave = Music.SEMITONES_PER_OCTAVE * 10;
\r
87 PianoKey[] blackKeys;
\r
89 PianoKey[] whiteKeys;
\r
91 DefaultBoundedRangeModel octaveRangeModel;
\r
92 DefaultBoundedRangeModel octaveSizeModel;
\r
93 VelocityModel velocityModel = new VelocityModel();
\r
94 DefaultMidiChannelComboBoxModel
\r
95 midiChComboboxModel = new DefaultMidiChannelComboBoxModel();
\r
97 NoteList selectedKeyNoteList = new NoteList();
\r
98 Music.Key keySignature = null;
\r
99 Music.Chord chord = null;
\r
101 class NoteList extends LinkedList<Integer> { }
\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
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
114 info = new MyInfo();
\r
115 setReceiver( new AbstractMidiStatus() {
\r
117 for( int i=0; i<MIDISpec.MAX_CHANNELS; i++ )
\r
118 add(new MidiChannelStatus(i));
\r
122 class MyInfo extends Info {
\r
123 protected MyInfo() {
\r
127 "Software MIDI keyboard",
\r
133 class MidiChannelStatus extends AbstractMidiChannelStatus {
\r
134 public MidiChannelStatus(int channel) {
\r
136 channelNotes[channel] = new NoteList();
\r
137 pitch_bend_sensitivities[channel] = 2; // Default is wholetone = 2 semitones
\r
139 public void fireRpnChanged() {
\r
140 if( data_for != DATA_FOR_RPN ) return;
\r
142 // RPN (MSB) - Accept 0x00 only
\r
143 if( controller_values[0x65] != 0x00 ) return;
\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
154 // MidiChannel interface
\r
156 public void noteOff( int note_no, int velocity ) {
\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
165 if( midi_ch_button_selecter != null ) {
\r
166 midi_ch_button_selecter.repaint();
\r
169 public void noteOn( int note_no, int velocity ) {
\r
170 if( velocity <= 0 ) {
\r
171 noteOff(note_no); return;
\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
179 chordDisplay.setNote( note_no, isRhythmPart() );
\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
189 if( chord_matrix != null ) {
\r
190 if( ! isRhythmPart() )
\r
191 chord_matrix.note(true, note_no);
\r
193 if( midi_ch_button_selecter != null ) {
\r
194 midi_ch_button_selecter.repaint();
\r
197 public void allNotesOff() {
\r
198 allKeysOff( channel, -1 );
\r
199 if( chord_matrix != null )
\r
200 chord_matrix.clearIndicators();
\r
202 public void setPitchBend(int bend) {
\r
203 super.setPitchBend(bend);
\r
204 pitchBendValues[channel] = bend;
\r
207 public void resetAllControllers() {
\r
208 super.resetAllControllers();
\r
210 // See also: Response to Reset All Controllers
\r
211 // http://www.midi.org/about-midi/rp15.shtml
\r
213 pitchBendValues[channel] = MIDISpec.PITCH_BEND_NONE;
\r
214 modulations[channel] = 0;
\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
226 private void repaintNotes() {
\r
227 if( midiChComboboxModel.getSelectedChannel() != channel
\r
228 || channelNotes[channel] == null
\r
231 if( channelNotes[channel].size() > 0 || selectedKeyNoteList.size() > 0 )
\r
235 public MidiChannel getSelectedChannel() {
\r
236 return midiDevice.getChannels()[midiChComboboxModel.getSelectedChannel()];
\r
238 public void note(boolean is_on, int note_no) {
\r
239 MidiChannel ch = getSelectedChannel();
\r
240 int velocity = velocityModel.getValue();
\r
242 ch.noteOn(note_no,velocity);
\r
244 ch.noteOff(note_no,velocity);
\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
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
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
261 indicator = new Rectangle( indicator_position, indicator_size );
\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
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
272 boolean paintKey(Graphics2D g2) {
\r
273 return paintKey(g2,false);
\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
280 boolean paintIndicator(Graphics2D g2, boolean is_small, int pitch_bend_value) {
\r
283 indicator.x + indicator.width/4,
\r
284 indicator.y + indicator.height/4 + 1,
\r
290 int current_channel = midiChComboboxModel.getSelectedChannel();
\r
291 int sens = pitch_bend_sensitivities[current_channel];
\r
296 7 * whiteKeySize.width * sens * (pitch_bend_value - MIDISpec.PITCH_BEND_NONE)
\r
298 int additional_height = indicator.height * modulations[current_channel] / 256 ;
\r
299 int y_offset = additional_height / 2 ;
\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
309 boolean paintIndicator(Graphics2D g2, boolean is_small) {
\r
310 return paintIndicator( g2, is_small, 0 );
\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
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
329 keyOn( currentChannel, n );
\r
331 firePianoKeyPressed( n, e );
\r
332 requestFocusInWindow();
\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
341 firePianoKeyReleased( n, e );
\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
353 firePianoKeyReleased( old_n, e );
\r
355 keyOn( current_channel, n );
\r
357 firePianoKeyPressed( n, e );
\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
367 else if( key_code == KeyEvent.VK_RIGHT || key_code == KeyEvent.VK_KP_RIGHT ) {
\r
368 octaveRangeModel.setValue( octaveRangeModel.getValue() + 1 );
\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
375 keyOn( current_channel, n );
\r
377 firePianoKeyPressed( n, e );
\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
385 firePianoKeyReleased( n, e );
\r
388 int octaves = getPerferredOctaves();
\r
389 octaveSizeModel = new DefaultBoundedRangeModel(
\r
390 octaves, 0, MIN_OCTAVES, MAX_OCTAVES
\r
392 octaveSizeModel.addChangeListener( new ChangeListener() {
\r
393 public void stateChanged(ChangeEvent e) {
\r
394 fireOctaveResized(e);
\r
395 octaveSizeChanged();
\r
398 octaveRangeModel = new DefaultBoundedRangeModel(
\r
399 (MAX_OCTAVES - octaves) / 2, octaves, 0, MAX_OCTAVES
\r
401 octaveRangeModel.addChangeListener( new ChangeListener() {
\r
402 public void stateChanged(ChangeEvent e) {
\r
403 fireOctaveMoved(e);
\r
404 checkOutOfBounds();
\r
408 addComponentListener( new ComponentAdapter() {
\r
409 public void componentResized(ComponentEvent e) {
\r
410 octaveSizeModel.setValue( getPerferredOctaves() );
\r
411 octaveSizeChanged();
\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
422 public void intervalAdded(ListDataEvent e) {}
\r
423 public void intervalRemoved(ListDataEvent e) {}
\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
433 g2.setBackground( getBackground() );
\r
434 g2.clearRect( 0, 0, d.width, d.height );
\r
437 g2.setColor( isDark ? Color.gray : Color.white );
\r
438 for( PianoKey k : whiteKeys ) k.paintKey(g2);
\r
440 NoteList notesArray[] = {
\r
441 (NoteList)selectedKeyNoteList.clone(),
\r
442 (NoteList)channelNotes[midiChComboboxModel.getSelectedChannel()].clone()
\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
452 g2.setColor(getForeground());
\r
453 for( PianoKey k : blackKeys ) k.paintKey(g2);
\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
462 for( NoteList nl : notesArray ) {
\r
463 for( int n : nl ) {
\r
464 if( (key=getPianoKey(n)) == null )
\r
466 boolean isOnScale = (keySignature == null || keySignature.isOnScale(n));
\r
468 if( chord != null && (chordIndex = chord.indexOf(n)) >=0 ) {
\r
469 g2.setColor(Music.Chord.NOTE_INDEX_COLORS[chordIndex]);
\r
472 g2.setColor(isDark && isOnScale ? Color.pink : DARK_PINK);
\r
474 key.paintIndicator(
\r
476 pitchBendValues[midiChComboboxModel.getSelectedChannel()]
\r
478 if( ! isOnScale ) {
\r
479 g2.setColor(Color.white);
\r
480 key.paintIndicator(g2, true);
\r
485 if( isFocusOwner() ) {
\r
486 // Show PC-key binding
\r
487 for( PianoKey k : bindedKeys ) {
\r
489 k.isBlack ? Color.gray.brighter() :
\r
490 isDark ? getForeground() :
\r
491 getForeground().brighter()
\r
493 k.paintKeyBinding(g2);
\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
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
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
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
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
534 private int getNote(Point point) {
\r
535 PianoKey k = getPianoKey(point);
\r
536 return k==null ? -1 : k.getNote(getChromaticOffset());
\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
544 if( i < 0 || i > keys.length-1 ) return null;
\r
546 if( point.y > blackKeySize.height )
\r
552 if( k.isBlack && !(k.out_of_bounds) && k.contains(point) ) return k;
\r
554 if( i < keys.length-1 ) {
\r
556 if( k.isBlack && !(k.out_of_bounds) && k.contains(point) ) return k;
\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
568 private int getNote(KeyEvent e) {
\r
569 PianoKey k = getPianoKey(e);
\r
570 return k==null ? -1 : k.getNote(getChromaticOffset());
\r
572 void changeKeyBinding( int from, String key_chars ) {
\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
583 private void checkOutOfBounds() {
\r
584 if( keys == null ) return;
\r
585 for( PianoKey k : keys ) k.getNote(getChromaticOffset());
\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
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
598 boolean autoScroll(int note_no) {
\r
599 if( octaveRangeModel == null || keys == null )
\r
601 int i = note_no - getChromaticOffset();
\r
603 octaveRangeModel.setValue(
\r
604 octaveRangeModel.getValue() - (-i)/Music.SEMITONES_PER_OCTAVE - 1
\r
608 if( i >= keys.length ) {
\r
609 octaveRangeModel.setValue(
\r
610 octaveRangeModel.getValue() + (i-keys.length)/Music.SEMITONES_PER_OCTAVE + 1
\r
616 void addPianoKeyboardListener(PianoKeyboardListener l) {
\r
617 listenerList.add(PianoKeyboardListener.class, l);
\r
619 void removePianoKeyboardListener(PianoKeyboardListener l) {
\r
620 listenerList.remove(PianoKeyboardListener.class, l);
\r
623 return channelNotes[midiChComboboxModel.getSelectedChannel()].size();
\r
625 int countKeyOn(int ch) {
\r
626 return channelNotes[ch].size();
\r
628 void allKeysOff(int ch, int n_marks) {
\r
629 if( ! selectedKeyNoteList.isEmpty() ) return;
\r
632 selectedKeyNoteList = (NoteList)(channelNotes[ch].clone());
\r
635 selectedKeyNoteList.add(
\r
636 channelNotes[ch].get(channelNotes[ch].size()-1)
\r
641 channelNotes[ch].clear();
\r
642 if( midiChComboboxModel.getSelectedChannel() == ch )
\r
646 selectedKeyNoteList.clear();
\r
647 channelNotes[midiChComboboxModel.getSelectedChannel()].clear();
\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
656 if( selectedKeyNoteList.size() == 1 )
\r
657 return selectedKeyNoteList.get(0);
\r
663 void setSelectedNote(int noteNumber) {
\r
664 setSelectedNote(midiChComboboxModel.getSelectedChannel(), noteNumber);
\r
666 void setSelectedNote(int ch, int note_no) {
\r
667 if( ch != midiChComboboxModel.getSelectedChannel() )
\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
678 Integer[] getSelectedNotes() {
\r
679 return selectedKeyNoteList.toArray(new Integer[0]);
\r
682 Music.Chord getChord() { return chord; }
\r
683 void setChord(Music.Chord c) {
\r
684 chordDisplay.setChord(chord = c);
\r
686 void setKeySignature(Music.Key ks) {
\r
691 private int maxSelectable = 1;
\r
692 void setMaxSelectable( int max_selectable ) {
\r
693 this.maxSelectable = max_selectable;
\r
695 int getMaxSelectable() { return maxSelectable; }
\r
697 int getChromaticOffset() {
\r
698 return octaveRangeModel.getValue() * 12 ;
\r
701 return octaveSizeModel.getValue();
\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
708 else if( octaves < MIN_OCTAVES ) {
\r
709 octaves = MIN_OCTAVES;
\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
720 whiteKeySize = new Dimension(
\r
721 (keyboard_size.width - 1) / (octaves * 7 + 1),
\r
722 keyboard_size.height - 1
\r
724 blackKeySize = new Dimension(
\r
725 whiteKeySize.width * 3 / 4,
\r
726 whiteKeySize.height * 3 / 5
\r
728 Dimension indicatorSize = new Dimension(
\r
729 whiteKeySize.width / 2,
\r
730 whiteKeySize.height / 6
\r
732 octaveRangeModel.setExtent( octaves );
\r
733 octaveRangeModel.setValue( (MAX_OCTAVES - octaves) / 2 );
\r
734 widthPerOctave = keyboard_size.width / octaves;
\r
736 // Construct piano-keys
\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
744 boolean is_CDE = true;
\r
745 for( i = i12 = 0; i < keys.length; i++, i12++ ) {
\r
747 case 12: is_CDE = true; i12 = 0; break;
\r
748 case 5: is_CDE = false; break;
\r
751 keyPoint.x = whiteKeySize.width * (
\r
752 i / Music.SEMITONES_PER_OCTAVE * 7 + (i12+(is_CDE?1:2))/2
\r
754 if( Music.isOnScale(i12,0) ) {
\r
755 k = new PianoKey( keyPoint, whiteKeySize, indicatorSize );
\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
765 (keys[i] = k).position = i;
\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
773 void setDarkMode(boolean isDark) {
\r
774 this.isDark = isDark;
\r
775 setBackground( isDark ? Color.black : null );
\r
779 class PianoKeyboardPanel extends JPanel
\r
781 PianoKeyboard keyboard = new PianoKeyboard();
\r
782 private JSlider octaveSizeSlider = new JSlider() {
\r
784 setToolTipText("Octave size");
\r
787 private JScrollBar octaveSelecter = new JScrollBar(JScrollBar.HORIZONTAL) {
\r
789 setToolTipText("Octave position");
\r
792 private JPanel octaveBar = new JPanel() {
\r
794 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\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
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
817 setAlignmentX((float)0.5);
\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
828 class MidiKeyboardPanel extends JPanel {
\r
829 MidiEventDialog eventDialog;
\r
830 Action sendSventAction = new AbstractAction() {
\r
832 putValue(NAME,"Send");
\r
834 public void actionPerformed(ActionEvent e) {
\r
835 keyboardCenterPanel.keyboard.midiDevice.sendMidiMessage(
\r
836 eventDialog.midiMessageForm.getMessage()
\r
840 JButton sendEventButton = new JButton(
\r
841 new AbstractAction() {
\r
843 putValue(NAME,"Send MIDI event");
\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
851 eventDialog.openMessageForm();
\r
855 JPanel keyboardChordPanel = new JPanel() {
\r
857 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\r
860 JPanel keyboardSouthPanel = new JPanel() {
\r
862 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\r
865 KeySignatureSelecter keySelecter = new KeySignatureSelecter(false);
\r
866 PianoKeyboardPanel keyboardCenterPanel = new PianoKeyboardPanel();
\r
868 MidiChannelComboSelecter midiChannelCombobox =
\r
869 new MidiChannelComboSelecter(
\r
871 keyboardCenterPanel.keyboard.midiChComboboxModel
\r
873 MidiChannelButtonSelecter midiChannelButtons =
\r
874 new MidiChannelButtonSelecter(keyboardCenterPanel.keyboard);
\r
875 VelocitySelecter velocitySelecter =
\r
876 new VelocitySelecter(keyboardCenterPanel.keyboard.velocityModel);
\r
878 private static final Insets ZERO_INSETS = new Insets(0,0,0,0);
\r
880 public MidiKeyboardPanel( ChordMatrix chordMatrix ) {
\r
881 keyboardCenterPanel.keyboard.chord_matrix = chordMatrix;
\r
882 keyboardCenterPanel.keyboard.chordDisplay =
\r
884 "MIDI Keyboard", chordMatrix, keyboardCenterPanel.keyboard
\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
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
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