J/Link Example: Piano

Code for the Piano application from section 1.3 of the J/Link User Guide.

This example demonstrates the versatility that J/Link brings to the Mathematica programing environment. Before J/Link, it was unthinkable that a program that displays a MIDI synthesizer keyboard and lets you play it could be written entirely in Mathematica, and that it would be completely portable.

This example uses the Java Sound API, which is a standard part of Java 1.3 and later. If you have an earlier version of Java (1.2.x), you can obtain the required components from the Java Sound home page (http://java.sun.com/products/java-media/sound/). This code requires some cooperation from your sound hardware. If you get a "ClassNotFoundException", then you do not have the Java Sound API corrrectly installed. If the keyboard appears but sounds don't come out, there is probably some hardware or software configuration issue that needs to be resolved. 

To try this example, evaluate all the initialization cells (you can do this with the menu command Kernel/Evaluation/Evaluate Initialization). Then go to the Example section.

Code

Needs["JLink`"]

(* This code could be simplified by using JButtons for keys instead of JPanels, but that
   doesn't work on Mac OSX, where the "Aquafication" of Swing makes it impossible to
   independently set button colors. On OSX if you use a Button, then the OS takes
   complete control of the look and feel of that Button.
*)

Piano[] := Piano[1]

Piano[instrument_Integer] :=
	JavaBlock[
		Module[{frm, keyPanel, label1, label2, label3, pianoKeyListener, synth, instMenu,
				instListener, volumeSlider, volumeListener, keyMap},
			InstallJava[];
			LoadJavaClass["java.awt.Color"];
			frm = JavaNew["com.wolfram.jlink.MathJFrame", "Mathematica Keyboard"];
			frm@setSize[2 (20 + $BorderWidth) + 15 ($WhiteKeyWidth + $KeyGap) - $KeyGap, 240];
			frm@getContentPane[]@setLayout[Null];
			keyPanel = JavaNew["javax.swing.JPanel"];
			keyPanel@setBounds[20, 50,
				2 $BorderWidth + 15 ($WhiteKeyWidth + $KeyGap) - $KeyGap,
				$WhiteKeyHeight + 2 $BorderWidth];
			keyPanel@setBackground[Color`black];
			keyPanel@setLayout[Null];
			frm@getContentPane[]@add[keyPanel];
			label1 = JavaNew["javax.swing.JLabel", "Click to play. Hold Shift key down to drag from"];
			label2 = JavaNew["javax.swing.JLabel", "key to key (shift must be down before clicking)."];
			label3 = JavaNew["javax.swing.JLabel", "Volume:"];
			frm@getContentPane[]@add[label1];
			frm@getContentPane[]@add[label2];
			frm@getContentPane[]@add[label3];
			label1@setBounds[20, 5, 300, 15];
			label2@setBounds[20, 20, 300, 15];
			label3@setBounds[590, 15, 60, 15];
			LoadJavaClass["javax.swing.JSlider"];
			volumeSlider = JavaNew["javax.swing.JSlider", JSlider`HORIZONTAL,  0, 255, 127];
			frm@getContentPane[]@add[volumeSlider];
			volumeSlider@setBounds[650, 15, 120, 24];
			volumeListener = ImplementJavaInterface["javax.swing.event.ChangeListener",
								{"stateChanged"->"volumeChanged"}];
			volumeSlider@addChangeListener[volumeListener];
			pianoKeyListener = JavaNew["com.wolfram.jlink.MathMouseListener"];
			pianoKeyListener@setHandler["mousePressed", "keyPressed"];
			pianoKeyListener@setHandler["mouseReleased", "keyReleased"];
			pianoKeyListener@setHandler["mouseEntered", "keyEntered"];
			pianoKeyListener@setHandler["mouseExited", "keyExited"];
			LoadJavaClass["javax.sound.midi.MidiSystem"];
			synth = MidiSystem`getSynthesizer[];
			synth@open[];
			instMenu = JavaNew["java.awt.Choice"];
			MapIndexed[instMenu@add[ToString[First[#2]] <> ". " <> #1@getName[]]&,
						synth@getAvailableInstruments[]];
			frm@getContentPane[]@add[instMenu];
			instMenu@setBounds[340, 15, 160, 24];
			instMenu@select[instrument - 1];  (* Convert to 0-based index. *)
			instListener = JavaNew["com.wolfram.jlink.MathItemListener", "instChanged"];
			instMenu@addItemListener[instListener];
			JavaShow[frm];
			keyMap =
				Map[
					createKey[keyPanel, pianoKeyListener, #]&,
					Transpose[{{"C", "C#", "D", "D#", "E", "F", "F#", "G",
						"G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F",
						"F#", "G", "G#", "A", "A#", "B", "C"}, Range[60, 84]}]
				];
			keyPanel@repaint[];
			frm@setModal[];
			Block[{$Channel, $IsShiftDown, $IsMouseDown = False,
					$KeyMap = keyMap, $KeyPanel = keyPanel},
				$Channel = First[synth@getChannels[]];
				$Channel@programChange[instrument - 1];
				DoModal[]
			];
			synth@close[];
		]
	]

$BorderWidth = 3;
$KeyGap = 1;
$WhiteKeyWidth = 50;
$WhiteKeyHeight = 150;
$BlackKeyWidth = 30;
$BlackKeyHeight = 90;
	
createKey[keyPanel_, listener_, {noteName_, pitch_}] :=
	Module[{key, lastWhiteKey, leftEdge},
		key = JavaNew["javax.swing.JPanel"];
		key@addMouseListener[listener];
		If[StringTake[noteName, -1] != "#",
			(* White keys *)
			If[keyPanel@getComponentCount[] == 0, (* First key. *)
				leftEdge = $BorderWidth,
			(* else *)
				lastWhiteKey = keyPanel@getComponent[keyPanel@getComponentCount[] - 1];
				leftEdge = lastWhiteKey@getLocation[]@x + $KeyGap + $WhiteKeyWidth
			];
			keyPanel@add[key];
			key@setBounds[leftEdge, $BorderWidth, $WhiteKeyWidth, $WhiteKeyHeight];
			key@setBackground[Color`white],
		(* else *)
			(* Black keys *)
			lastWhiteKey = keyPanel@getComponent[keyPanel@getComponentCount[] - 1];
			keyPanel@add[key, 0];
			leftEdge = lastWhiteKey@getLocation[]@x + $WhiteKeyWidth - $BlackKeyWidth/2;
			key@setBounds[leftEdge, $BorderWidth, $BlackKeyWidth, $BlackKeyHeight];
			key@setBackground[Color`black];
			key@setForeground[Color`white]
		];
		{leftEdge, noteName, pitch}
	]
	
restoreKeyColor[key_] :=
	Module[{leftEdge, noteName},
		leftEdge = key@getLocation[]@x;
		noteName = Select[$KeyMap, #[[1]] == leftEdge &][[1, 2]];
		If[StringTake[noteName, -1] == "#",
			key@setBackground[Color`black],
		(* else *)
			key@setBackground[Color`white]
		];
		key@getParent[]@repaint[];
	]
	
(**************************  Event Handlers  ***************************)

keyPressed[evt_, _, _, _] := 
	JavaBlock[
		Module[{key, leftEdge, pitch},
			key = evt@getSource[];
			leftEdge = key@getLocation[]@x;
			pitch = Select[$KeyMap, #[[1]] == leftEdge &][[1, 3]];
			$Channel@noteOn[pitch, 80];
			$IsMouseDown = True;
			$IsShiftDown = evt@isShiftDown[];
			key@setBackground[Color`gray];
			key@getParent[]@repaint[];
			ReleaseJavaObject[evt]
		]
	]
	
keyReleased[evt_, _, _, _] := 
	JavaBlock[
		Module[{pt, srcPos, mouseDownKey, mouseUpKey},
			$Channel@allNotesOff[];
			$IsMouseDown = False;
			mouseDownKey = evt@getSource[];
			srcPos = mouseDownKey@getLocation[];
			pt = evt@getPoint[];
			pt@translate[srcPos@x, srcPos@y];
			mouseUpKey = $KeyPanel@getComponentAt[pt];
			If[mouseUpKey =!= Null && !SameObjectQ[mouseUpKey, $KeyPanel],
				restoreKeyColor[mouseUpKey]
			];
			If[mouseDownKey =!= mouseUpKey,
				restoreKeyColor[mouseDownKey]
			];
			ReleaseJavaObject[evt]
		]
	]
	
keyEntered[evt_, _, _, _] := 
	JavaBlock[
		Module[{key, leftEdge, pitch},
			If[$IsMouseDown && $IsShiftDown,
				$Channel@allNotesOff[];
				key = evt@getSource[];
				leftEdge = key@getLocation[]@x;
				pitch = Select[$KeyMap, #[[1]] == leftEdge &][[1, 3]];
				$Channel@noteOn[pitch, 80];
				key@setBackground[Color`gray];
				key@getParent[]@repaint[];
			]
		];
		ReleaseJavaObject[evt]
	]
	
keyExited[evt_, _, _, _] := 
	JavaBlock[
		If[$IsMouseDown && $IsShiftDown,
			restoreKeyColor[evt@getSource[]]
		];
		ReleaseJavaObject[evt]
	]

instChanged[evt_, state_] :=
	JavaBlock[
		If[state === ItemEvent`SELECTED,
			$Channel@programChange[evt@getSource[]@getSelectedIndex[]]
		];
		ReleaseJavaObject[evt]
	]

volumeChanged[evt_] :=
	JavaBlock[
		(* Controller 7 is the volume controller. *)
		$Channel@controlChange[7, evt@getSource[]@getValue[]];
		ReleaseJavaObject[evt]
	]

Example

Click the keys to play notes. Hold the Shift key down to drag from note to note (the Shift key must be down before you click). Close the window when you are done. Change instruments via the popup menu.

Piano[]

You can also provide the number of a specific MIDI instrument you want to start with (you can see the numbers next to the names in the instrument popup menu). This uses the church organ sound on most systems:

Piano[20]