/[svn]/jsampler/trunk/src/org/jsampler/view/std/PianoRoll.java
ViewVC logotype

Contents of /jsampler/trunk/src/org/jsampler/view/std/PianoRoll.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1776 - (show annotations) (download)
Thu Sep 11 18:48:36 2008 UTC (15 years, 7 months ago) by iliev
File size: 18427 byte(s)
* Implemented virtual MIDI keyboard

1 /*
2 * JSampler - a java front-end for LinuxSampler
3 *
4 * Copyright (C) 2005-2008 Grigor Iliev <grigor@grigoriliev.com>
5 *
6 * This file is part of JSampler.
7 *
8 * JSampler is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2
10 * as published by the Free Software Foundation.
11 *
12 * JSampler is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with JSampler; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20 * MA 02111-1307 USA
21 */
22
23 package org.jsampler.view.std;
24
25 import java.awt.Color;
26 import java.awt.Font;
27 import java.awt.FontMetrics;
28 import java.awt.GradientPaint;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.RenderingHints;
34
35 import java.awt.event.MouseEvent;
36 import java.awt.event.MouseAdapter;
37
38 import java.awt.geom.RoundRectangle2D;
39
40 import java.util.Vector;
41
42 import javax.swing.JPanel;
43
44 import org.linuxsampler.lscp.event.MidiDataEvent;
45 import org.linuxsampler.lscp.event.MidiDataListener;
46
47 /**
48 *
49 * @author Grigor Iliev
50 */
51 public class PianoRoll extends JPanel implements MidiDataListener {
52 public class Key {
53 private boolean disabled = false;
54 private boolean pressed = false;
55 private boolean keyswitch = false;
56
57 public
58 Key() {
59
60 }
61
62 public boolean
63 isDisabled() { return disabled; }
64
65 public void
66 setDisabled(boolean b) { disabled = b; }
67
68 public boolean
69 isPressed() { return pressed; }
70
71 public void
72 setPressed(boolean b) { pressed = b; }
73
74 public boolean
75 isKeyswitch() { return keyswitch; }
76
77 public void
78 setKeyswitch(boolean b) { keyswitch = b; }
79
80 }
81
82
83 private final Vector<Key> keys = new Vector<Key>();
84
85 private boolean vertical;
86 private boolean mirror;
87 private boolean octaveLabelsVisible = true;
88 private boolean playingEnabled = true;
89
90 private int firstKey = -1;
91 private int lastKey = -1;
92 private int whiteKeyCount = 68;
93
94 private Color borderColor = Color.BLACK;
95 private Color keyColor = Color.WHITE;
96 private Color disabledKeyColor = Color.GRAY;
97 private Color pressedKeyColor = Color.GREEN;
98 private Color keySwitchColor = Color.PINK;
99 private Color blackKeyColor = Color.BLACK;
100
101 private final Vector<MidiDataListener> listeners = new Vector<MidiDataListener>();
102
103 private boolean shouldRepaint = false;
104
105 /**
106 * Creates a new horizontal, not mirrored <code>PianoRoll</code>.
107 */
108 public
109 PianoRoll() { this(false); }
110
111 /**
112 * Creates a new not mirrored <code>PianoRoll</code>.
113 * @param vertical Specifies whether the piano roll
114 * should be vertical or horizontal
115 */
116 public
117 PianoRoll(boolean vertical) { this(vertical, false); }
118
119 /**
120 * Creates a new instance of <code>PianoRoll</code>.
121 * @param vertical Specifies whether the piano roll
122 * should be vertical or horizontal.
123 * @param mirror Specifies whether to mirror the piano roll.
124 */
125 public
126 PianoRoll(boolean vertical, boolean mirror) {
127 this.vertical = vertical;
128 this.mirror = mirror;
129
130 this.addMouseListener(getHandler());
131 setKeyRange(0, 127);
132 }
133
134 /**
135 * Registers the specified listener to be notified when
136 * MIDI event occurs due to user input.
137 * @param l The <code>MidiDataListener</code> to register.
138 */
139 public void
140 addMidiDataListener(MidiDataListener l) { listeners.add(l); }
141
142 /**
143 * Removes the specified listener.
144 * @param l The <code>MidiDataListener</code> to remove.
145 */
146 public void
147 removeMidiDataListener(MidiDataListener l) { listeners.remove(l); }
148
149 private void
150 fireMidiDataEvent(MidiDataEvent e) {
151 for(MidiDataListener l : listeners) l.midiDataArrived(e);
152 }
153
154 /**
155 * Determines whether the user input is processed
156 * and respective MIDI data events are sent.
157 */
158 public boolean
159 isPlayingEnabled() { return playingEnabled; }
160
161 /**
162 * Sets whether the user input should be processed
163 * and respective MIDI data events should be sent.
164 * @see #isPlayingEnabled
165 */
166 public void
167 setPlayingEnabled(boolean b) { playingEnabled = b; }
168
169 public boolean
170 hasKey(int key) {
171 if(key >= firstKey && key <= lastKey) return true;
172 return false;
173 }
174
175 public Key
176 getKey(int key) { return keys.get(key - firstKey); }
177
178 public boolean
179 isKeyDisabled(int key) {
180 return getKey(key).isDisabled();
181 }
182
183 public void
184 setKeyDisabled(int key, boolean disable) {
185 Key k = getKey(key);
186 if(k.isDisabled() == disable) return;
187
188 getKey(key).setDisabled(disable);
189 if(getShouldRepaint()) repaint();
190 }
191
192 public boolean
193 isKeyPressed(int key) {
194 return getKey(key).isPressed();
195 }
196
197 public void
198 setKeyPressed(int key, boolean press) {
199 Key k = getKey(key);
200 if(k.isPressed() == press) return;
201
202 getKey(key).setPressed(press);
203 if(getShouldRepaint()) repaint(getKeyRectangle(key));
204 }
205
206 public boolean
207 isKeyswitch(int key) {
208 return getKey(key).isKeyswitch();
209 }
210
211 public void
212 setKeyswitch(int key, boolean keyswitch) {
213 Key k = getKey(key);
214 if(k.isKeyswitch() == keyswitch) return;
215
216 getKey(key).setKeyswitch(keyswitch);
217 if(getShouldRepaint()) repaint(getKeyRectangle(key));
218 }
219
220 public void
221 setAllKeysPressed(boolean b) {
222 boolean changed = false;
223 for(Key key : keys) {
224 if(key.isPressed() != b) {
225 key.setPressed(b);
226 changed = true;
227 }
228 }
229
230 if(changed && getShouldRepaint()) repaint();
231 }
232
233 public void
234 setAllKeysDisabled(boolean b) {
235 boolean changed = false;
236 for(Key key : keys) {
237 if(key.isDisabled() != b) {
238 key.setDisabled(b);
239 changed = true;
240 }
241 }
242
243 if(changed && getShouldRepaint()) repaint();
244 }
245
246 public Integer[]
247 getKeyswitches() {
248 Vector<Integer> v = new Vector<Integer>();
249 for(int i = 0; i < keys.size(); i++) {
250 if(keys.get(i).isKeyswitch()) v.add(firstKey + i);
251 }
252
253 return v.toArray(new Integer[v.size()]);
254 }
255
256 /**
257 * Keys outside the keyboard range are ignored.
258 * @param keys List of keys
259 */
260 public void
261 setKeyswitches(Integer[] keys, boolean keyswitches) {
262 boolean changed = false;
263 for(int k : keys) {
264 if(!hasKey(k)) continue;
265 if(getKey(k).isKeyswitch() != keyswitches) {
266 getKey(k).setKeyswitch(keyswitches);
267 changed = true;
268 }
269 }
270 if(changed && getShouldRepaint()) repaint();
271 }
272
273 public Integer[]
274 getEnabledKeys() {
275 Vector<Integer> v = new Vector<Integer>();
276 for(int i = 0; i < keys.size(); i++) {
277 if(!keys.get(i).isDisabled()) v.add(firstKey + i);
278 }
279
280 return v.toArray(new Integer[v.size()]);
281 }
282
283 /**
284 * Enables or disables the specified list of keys.
285 * Keys outside the keyboard range are ignored.
286 * @param keys List of keys
287 */
288 public void
289 setDisabled(Integer[] keys, boolean disable) {
290 boolean changed = false;
291 for(int k : keys) {
292 if(!hasKey(k)) continue;
293 if(getKey(k).isDisabled() != disable) {
294 getKey(k).setDisabled(disable);
295 changed = true;
296 }
297 }
298 if(changed && getShouldRepaint()) repaint();
299 }
300
301 public void
302 removeAllKeyswitches() {
303 boolean changed = false;
304 for(Key key : keys) {
305 if(key.isKeyswitch()) {
306 key.setKeyswitch(false);
307 changed = true;
308 }
309 }
310 if(changed && getShouldRepaint()) repaint();
311 }
312
313 /**
314 * Sets the piano key range which this piano roll will provide.
315 * Note that the specified last key is also included in the piano roll.
316 * Also, the first and the last key should be white keys. If the first key
317 * and/or the last key are not white keys then the range is extended automatically.
318 * @param firstKey Number between 0 and 127 (inclusive) as specified in the MIDI standard.
319 * @param lastKey Number between 0 and 127 (inclusive) as specified in the MIDI standard.
320 * @throws IllegalArgumentException if the specified range is invalid.
321 */
322 public void
323 setKeyRange(int firstKey, int lastKey) {
324 if(this.firstKey == firstKey && this.lastKey == lastKey) return;
325
326 if(firstKey < 0 || firstKey > 127 || lastKey < 0 || lastKey > 127 || firstKey >= lastKey) {
327 throw new IllegalArgumentException("Invalid range: " + firstKey + "-" + lastKey);
328 }
329
330 /*Integer[] enabledKeys = getEnabledKeys();
331 Integer[] keyswitches = getKeyswitches();*/
332
333 if(!isWhiteKey(firstKey)) firstKey--;
334 if(!isWhiteKey(lastKey)) lastKey++;
335 this.firstKey = firstKey;
336 this.lastKey = lastKey;
337
338 keys.removeAllElements();
339 for(int i = 0; i <= lastKey - firstKey; i++) keys.add(new Key());
340
341 whiteKeyCount = getWhiteKeyCount(firstKey, lastKey);
342
343 /*setAllKeysDisabled(true);
344 setDisabled(enabledKeys, false);
345 setKeyswitches(keyswitches, true);*/
346
347 if(getShouldRepaint()) repaint();
348 }
349
350 public boolean
351 getOctaveLabelsVisible() {
352 return octaveLabelsVisible;
353 }
354
355 public void
356 setOctaveLabelsVisible(boolean b) {
357 octaveLabelsVisible = b;
358 }
359
360 /**
361 * Gets the number of white keys int the specified range (inclusive).
362 * @see #setKeyRange
363 */
364 private static int
365 getWhiteKeyCount(int firstKey, int lastKey) {
366 int count = 0;
367 for(int j = firstKey; j <= lastKey; j++) { // FIXME: Stupid but works
368 if(isWhiteKey(j)) count++;
369 }
370
371 return count;
372 }
373
374 private int
375 getWhiteKeyByNumber(int whiteKey) {
376 int count = 0;
377 for(int j = firstKey; j <= lastKey; j++) { // FIXME: Stupid but works
378 if(isWhiteKey(j)) {
379 if(whiteKey == count) return j;
380 count++;
381 }
382 }
383
384 return -1;
385 }
386
387 private int
388 getWhiteKeyCount() {
389 return whiteKeyCount;
390 }
391
392 /**
393 * Determines whether the specified key is a white key.
394 * @param key Number between 0 and 127 (inclusive) as specified in the MIDI standard.
395 */
396 public static boolean
397 isWhiteKey(int key) {
398 if(key < 0 || key > 127) return false;
399
400 int k = key % 12;
401 if(k == 1 || k == 3 || k == 6 || k == 8 || k == 10) {
402 return false;
403 }
404
405 return true;
406 }
407
408 /**
409 * Returns the position (zero-based) of the specified white key in the octave.
410 * @param whiteKey Number between 0 and 127 (inclusive) as specified in the MIDI standard.
411 * @return Number between 0 and 6 (inclusive) and -1 if the
412 * specified key is not a white key.
413 */
414 public static int
415 getKeyOctaveIndex(int whiteKey) {
416 if(whiteKey < 0 || whiteKey > 127) return -1;
417
418 int k = whiteKey % 12;
419 if(k == 1 || k == 3 || k == 6 || k == 8 || k == 10) {
420 return -1;
421 }
422
423 return getWhiteKeyCount(0, k) - 1;
424 }
425
426 private Color
427 getKeyColor(int key) {
428 Key k = getKey(key);
429 if(isWhiteKey(key)) {
430 if(k.isPressed()) return pressedKeyColor;
431 if(k.isKeyswitch()) return keySwitchColor;
432 if(k.isDisabled()) return disabledKeyColor;
433 return keyColor;
434 } else {
435 if(k.isPressed()) return Color.GREEN;
436 return blackKeyColor;
437 }
438 }
439
440 /**
441 * Releases all pressed keys, enables all keys and removes all keyswitches.
442 */
443 public void
444 reset() { reset(false); }
445
446 /**
447 * Releases all pressed keys, enables/disables all keys and removes all keyswitches.
448 * @param dissable Specifies whether all keys should be enabled or disabled
449 */
450 public void
451 reset(boolean dissableAllKeys) {
452 boolean b = getShouldRepaint();
453 setShouldRepaint(false);
454 setAllKeysPressed(false);
455 removeAllKeyswitches();
456 setAllKeysDisabled(dissableAllKeys);
457 setShouldRepaint(b);
458 if(getShouldRepaint()) repaint();
459 }
460
461 public boolean
462 getShouldRepaint() { return shouldRepaint; }
463
464 public void
465 setShouldRepaint(boolean b) { shouldRepaint = b; }
466
467 private int
468 getKey(Point p) {
469 double w = getWhiteKeyWidth() + /* space between keys */ 1.0d;
470 if(w == 0) return -1;
471 int whiteKeyNumber = (int) (p.getX() / w);
472 double leftBorder = whiteKeyNumber * w;
473 int key = getWhiteKeyByNumber(whiteKeyNumber);
474 if(key == -1) return -1;
475
476 double bh = getBlackKeyHeight();
477 double blackKeyOffset = w / 4.0d;
478 if(p.getY() > bh) return key;
479 if(key != firstKey && !isWhiteKey(key - 1)) {
480 if(p.getX() <= leftBorder + blackKeyOffset) return key - 1;
481 }
482 if(key != lastKey && !isWhiteKey(key + 1)) {
483 if(p.getX() >= leftBorder + 3 * blackKeyOffset - 3) return key + 1;
484 }
485
486 return key;
487 }
488
489 private int
490 getVelocity(Point p, boolean whiteKey) {
491 double h = whiteKey ? getWhiteKeyHeight() : getBlackKeyHeight();
492 int velocity = (int) ((p.getY() / h) * 127.0d + 1);
493 if(velocity < 0) velocity = 0;
494 if(velocity > 127) velocity = 127;
495 return velocity;
496 }
497
498 private double
499 getWhiteKeyWidth() {
500 double w = getSize().getWidth();
501 return (w - getWhiteKeyCount()) / getWhiteKeyCount();
502 }
503
504 private double
505 getWhiteKeyHeight() {
506 return getSize().getHeight() - 3.0d;
507 }
508
509 private double
510 getBlackKeyWidth() {
511 return getWhiteKeyWidth() / 2.0d;
512 }
513
514 private double
515 getBlackKeyHeight() {
516 return getWhiteKeyHeight() / 1.5d;
517 }
518
519 protected void
520 paintOctaveLabel(Graphics2D g, int octave, int whiteKeyIndex) {
521 double h = getSize().getHeight();
522 double whiteKeyWidth = getWhiteKeyWidth();
523 g.setPaint(Color.BLACK);
524 int fsize = (int) (whiteKeyWidth / (1.5 + whiteKeyWidth / 50));
525 if(fsize < 8) fsize = 8;
526 g.setFont(g.getFont().deriveFont(Font.BOLD, fsize));
527
528 float x = (float) (whiteKeyWidth * whiteKeyIndex + whiteKeyIndex);
529 float y = (float) (h - 1);
530
531 String s = String.valueOf(octave);
532 FontMetrics fm = g.getFontMetrics();
533
534 // center text
535 int i = fm.stringWidth(s);
536 if(i < whiteKeyWidth) {
537 x += (whiteKeyWidth - i) / 2;
538 } else {
539 x += 2;
540 }
541
542 y -= (h / 12);
543
544 g.drawString(s, x, y);
545 }
546
547 /**
548 * Gets the rectangle in which the key is drawn and empty rectangle
549 * if the specified key is not shown on the piano roll or is invalid.
550 */
551 public Rectangle
552 getKeyRectangle(int key) {
553 Rectangle r = new Rectangle();
554 if(!hasKey(key)) return r;
555
556 int whiteKeyIndex = getWhiteKeyCount(firstKey, key) - 1;
557
558 if(isWhiteKey(key)) {
559 double whiteKeyWidth = getWhiteKeyWidth();
560 double whiteKeyHeight = getWhiteKeyHeight();
561 double x = whiteKeyWidth * whiteKeyIndex + whiteKeyIndex;
562 r.setRect(x, 0, whiteKeyWidth, whiteKeyHeight);
563 } else {
564 double blackKeyWidth = getBlackKeyWidth();
565 double blackKeyHeight = getBlackKeyHeight();
566 int i = whiteKeyIndex;
567 double x = blackKeyWidth * (2*(i + 1)) - blackKeyWidth * 0.5d + i;
568 r.setRect(x, 0, blackKeyWidth, blackKeyHeight);
569 }
570
571 return r;
572 }
573
574 @Override public void
575 paint(Graphics g) {
576 super.paint(g);
577 Graphics2D g2 = (Graphics2D) g;
578
579 double whiteKeyWidth = getWhiteKeyWidth();
580 double whiteKeyHeight = getWhiteKeyHeight();
581 double arcw = whiteKeyWidth/4.0d;
582 double arch = whiteKeyHeight/14.0d;
583
584 g2.setPaint(keyColor);
585
586 RoundRectangle2D.Double rect;
587
588 g2.setRenderingHint (
589 RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF
590 );
591
592 int i = 0;
593 for(int j = firstKey; j <= lastKey; j++) {
594 if(!isWhiteKey(j)) continue;
595
596 Color c = getKeyColor(j);
597 if(g2.getPaint() != c) g2.setPaint(c);
598
599 rect = new RoundRectangle2D.Double (
600 // If you change this you should also change getKeyRectangle()
601 whiteKeyWidth * i + i, 0,
602 whiteKeyWidth, whiteKeyHeight,
603 arcw, arch
604 );
605
606 g2.fill(rect);
607 i++;
608 }
609
610 g2.setRenderingHint (
611 RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
612 );
613
614 g2.setStroke(new java.awt.BasicStroke(1.5f));
615 g2.setPaint(borderColor);
616
617 i = 0;
618 for(int j = firstKey; j <= lastKey; j++) {
619 if(!isWhiteKey(j)) continue;
620
621 rect = new RoundRectangle2D.Double (
622 whiteKeyWidth * i + i, 0,
623 whiteKeyWidth, whiteKeyHeight,
624 arcw, arch
625 );
626
627 g2.draw(rect);
628 i++;
629 }
630
631
632 g2.setStroke(new java.awt.BasicStroke(2.5f));
633 double blackKeyWidth = getBlackKeyWidth();
634 double blackKeyHeight = getBlackKeyHeight();
635
636 i = 0;
637 for(int j = firstKey; j <= lastKey; j++) {
638 if(getOctaveLabelsVisible() && j % 12 == 0) {
639 int octave = j / 12 - 2;
640 paintOctaveLabel(g2, octave, i);
641 }
642 if(!isWhiteKey(j)) continue;
643
644 int k = (i + getKeyOctaveIndex(firstKey)) % 7;
645 if(k == 2 || k == 6) { i++; continue; }
646
647 Color c = (j == lastKey) ? blackKeyColor : getKeyColor(j + 1);
648 if(g2.getPaint() != c) g2.setPaint(c);
649
650 // If you change this you should also change getKeyRectangle()
651 double x = blackKeyWidth * (2*(i + 1)) - blackKeyWidth * 0.5d + i;
652 rect = new RoundRectangle2D.Double (
653 // If you change this you should also change getKeyRectangle()
654 x, 0,
655 blackKeyWidth, blackKeyHeight,
656 arcw, arch
657 );
658 ///////
659
660 boolean pressed = (j == lastKey) ? false : getKey(j + 1).isPressed();
661 if(!pressed) g2.fill(rect);
662
663 RoundRectangle2D.Double rect2;
664 rect2 = new RoundRectangle2D.Double (
665 x, 0,
666 blackKeyWidth, arch,
667 arcw, arch / 1.8d
668 );
669
670 g2.fill(rect2);
671 g2.setPaint(borderColor);
672 g2.draw(rect);
673
674 if(pressed) {
675 GradientPaint gr = new GradientPaint (
676 (float)(x + blackKeyWidth/2), (float)(blackKeyHeight/4), Color.BLACK,
677 (float)(x + blackKeyWidth/2), (float)blackKeyHeight, new Color(0x058a02)
678 );
679 g2.setPaint(gr);
680 g2.fill(rect);
681 }
682 i++;
683 }
684 }
685
686 @Override public void
687 midiDataArrived(MidiDataEvent e) {
688 switch(e.getType()) {
689 case NOTE_ON:
690 setKeyPressed(e.getNote(), true);
691 break;
692 case NOTE_OFF:
693 setKeyPressed(e.getNote(), false);
694 }
695 }
696
697 private final Handler handler = new Handler();
698
699 private Handler
700 getHandler() { return handler; }
701
702 private class Handler extends MouseAdapter {
703 private int pressedKey = -1;
704
705 @Override public void
706 mousePressed(MouseEvent e) {
707 if(!isPlayingEnabled()) return;
708 if(e.getButton() != MouseEvent.BUTTON1) return;
709
710 int key = getKey(e.getPoint());
711 if(key == -1) return;
712 pressedKey = key;
713 setKeyPressed(key, true);
714 int velocity = getVelocity(e.getPoint(), isWhiteKey(key));
715
716 MidiDataEvent evt = new MidiDataEvent (
717 PianoRoll.this, MidiDataEvent.Type.NOTE_ON, key, velocity
718 );
719
720 fireMidiDataEvent(evt);
721 }
722
723 @Override public void
724 mouseReleased(MouseEvent e) {
725 if(!isPlayingEnabled()) return;
726 if(e.getButton() != MouseEvent.BUTTON1) return;
727
728 if(pressedKey == -1) return;
729 setKeyPressed(pressedKey, false);
730
731 int velocity = getVelocity(e.getPoint(), isWhiteKey(pressedKey));
732 MidiDataEvent evt = new MidiDataEvent (
733 PianoRoll.this, MidiDataEvent.Type.NOTE_OFF, pressedKey, velocity
734 );
735
736 pressedKey = -1;
737
738 fireMidiDataEvent(evt);
739 }
740 }
741 }

  ViewVC Help
Powered by ViewVC