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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1776 - (hide 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 iliev 1776 /*
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