- 2.1 Introducing JavaFX
- 2.2 Project GuitarTuner
- 2.3 JavaFX Program Structure
- 2.4 Key JavaFX Features
- 2.5 Making Things Look Good
- 2.6 Doing Things
- 2.7 Source Code for Project GuitarTuner
- Footnotes
2.7 Source Code for Project GuitarTuner
Listing 2.1 and Listing 2.2 show the code for class GuitarString in two parts. Listing 2.1 includes the class declarations, functions, class-level variables, and properties for class GuitarString. Note that several variables are declared public-init. This JavaFX keyword means that users of the class can provide initial values with object literals, but otherwise these properties are read-only. The default accessibility for all variables is script-private, making the remaining declarations private.
Use def for read-only variables and var for modifiable variables. The GuitarString class also provides utility functions that play a note (playNote) or stop playing a note (stopNote). Along with the sound, guitar strings vibrate on and off with vibrateOn and vibrateOff. These functions implement the behavior of the GuitarString class.
Listing 2.1 Class GuitarString—Properties, Variables, and Functions
package guitartuner; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.scene.Cursor; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.Text; import noteplayer.SingleNote; public class GuitarString extends CustomNode { // read-only variables def stringColor = Color.WHITESMOKE; // "Strings" are oriented sideways, so stringLength is the // Rectangle width and stringSize is the Rectangle height def stringLength = 300; def stringSize = 1; def stringMouseSize = 15; def timeline = Timeline { repeatCount: Timeline.INDEFINITE autoReverse: true keyFrames: [ at (0s) { vibrateH => 1.0 } at (.01s) { vibrateH => 3.0 tween Interpolator.LINEAR } at (0s) { vibrateY => 5.0 } at (.01s) { vibrateY => 4.0 tween Interpolator.LINEAR } ] }; // properties to be initialized public-init var synthNote: SingleNote; public-init var note: Integer; public-init var yOffset: Number; public-init var noteText: String; // class variables var vibrateH: Number; var vibrateY: Number; var play: Rectangle;
function vibrateOn(): Void { play.visible = true; timeline.play(); } function vibrateOff(): Void { play.visible = false; timeline.stop(); } function playNote(): Void { synthNote.noteOn(note); vibrateOn(); } function stopNote(): Void { synthNote.noteOff(note); vibrateOff(); }
Listing 2.2 shows the second part of the code for the GuitarString class.
Every class that extends CustomNode must define a function create that returns a Node object.[3] Often the node you return will be a Group, since Group is the most general Node type and can include subnodes. But, you can return other Node types, such as Rectangle (Shape) or HBox (horizontal box) layout node.
The scene graph for GuitarString is interesting because it actually consists of three Rectangle nodes and a Text node. The first Rectangle, used to detect mouse clicks, is completely translucent (its opacity is 0). This Rectangle is wider than the guitar string so the user can more easily select it with the mouse. Several properties implement its behavior: property cursor lets a user know the string is selected and property onMouseClicked provides the event handling code (play the note and vibrate the string).
The second Rectangle node defines the visible string. The third Rectangle node (assigned to variable play) “vibrates” by both moving and changing its height. This rectangle is only visible when a note is playing and provides the vibration effect of “plucking” a string. The movement and change in height are achieved with animation and binding. The Text node simply displays the letter (E, A, D, etc.) associated with the guitar string’s note.
Listing 2.2 Scene Graph for GuitarString
protected override function create(): Node { return Group { content: [ // Rectangle to detect mouse events for string plucking Rectangle { x: 0 y: yOffset width: stringLength height: stringMouseSize // Rectangle has to be "visible" or scene graph will // ignore mouse events. Therefore, we make it fully // translucent (opacity=0) so it is effectively invisible fill: Color.web("#FFFFF", 0) // translucent cursor: Cursor.HAND onMouseClicked: function(evt: MouseEvent): Void { if (evt.button == MouseButton.PRIMARY){ Timeline { keyFrames: [ KeyFrame { time: 0s action: playNote
} KeyFrame { time: 2.8s action: stopNote } ] // keyFrames }.play(); // start Timeline } // if } } // Rectangle // Rectangle to render the guitar string Rectangle { x: 0.0 y: 5 + yOffset width: stringLength height: stringSize fill: stringColor } // Special "string" that vibrates by changing its height // and location play = Rectangle { x: 0.0 y: yOffset width: stringLength height: bind vibrateH fill: stringColor visible: false translateY: bind vibrateY } Text { // Display guitar string note name x: stringLength + 8 y: 13 + yOffset font: Font { size: 20 } content: noteText } ] } // Group } } // GuitarString
Listing 2.3 shows the code for Main.fx, the main program for GuitarTuner.
Listing 2.3 Main.fx
package guitartuner; import guitartuner.GuitarString; import javafx.scene.effect.DropShadow; import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Stop; import javafx.scene.Scene; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import noteplayer.SingleNote; def noteValues = [ 40,45,50,55,59,64 ]; // numeric value required by midi def guitarNotes = [ "E","A","D","G","B","E" ]; // guitar note name def numberFrets = 2; def numberStrings = 6; var singleNote = SingleNote{}; singleNote.setInstrument(27); // "Clean Guitar" var scene: Scene; var group: Group; Stage { title: "Guitar Tuner" visible: true scene: scene = Scene { fill: LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 proportional: true stops: [ Stop { offset: 0.0 color: Color.LIGHTGRAY }, Stop { offset: 1.0 color: Color.GRAY } ] } width: 340 height: 200 content: [ group = Group { // Center the whole group vertically within the scene layoutY: bind (scene.height - group.layoutBounds.height) /
2 - group.layoutBounds.minY content: [ Rectangle { // guitar neck (fret board) effect: DropShadow { } x: 0 y: 0 width: 300 height: 121 fill: LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 proportional: true stops: [ Stop { offset: 0.0 color: Color.SADDLEBROWN }, Stop { offset: 1.0 color: Color.BLACK } ] } } // Rectangle for (i in [0..<numberFrets]) // two frets Line { startX: 100 * (i + 1) startY: 0 endX: 100 * (i + 1) endY: 120 stroke: Color.GRAY } for (i in [0..<numberStrings]) // six guitar strings GuitarString { yOffset: i * 20 + 5 note: noteValues[i] noteText: guitarNotes[i] synthNote: singleNote } ] } ] } }