Where to Start:
Building a Piano App
Code your own MIDI piano — play notes, craft sequences, master timing, and bring Für Elise to life.
Hello everyone! In this article, we’re going to build a simple “Hello World” Piano App using Java MIDI and JavaFX. However, our focus is more on understanding how a digital piano system works fundamentally, so you can use this tutorial as a foundation for building a piano application on any platform.
Whether you’re a developer curious about music programming, a musician wanting to understand the technical side, or someone who simply loves the intersection of code and creativity, this guide will take you from complete beginner to having a working piano that can play everything from single notes to classical masterpieces like Für Elise.
What we’ll build together
By the end of this tutorial, you’ll have created:
- A functional digital piano that can play individual notes
- A sequence system for composing and playing our music sheets
- Support for musical concepts like tempo, dynamics, and time signatures
- A simple implementation of Beethoven’s Für Elise
Before we begin
You’ll need:
- Basic Java knowledge (classes, methods, enums)
- A Java development environment set up
- No musical background required, we’ll explain everything!
But more importantly, you’ll understand the core concepts that power any digital piano system, regardless of the programming language or platform you choose to work with.
02 — The language of instruments
What is MIDI?
MIDI (Musical Instrument Digital Interface) is a technical standard that allows electronic musical instruments, computers, and audio equipment to communicate with each other. Think of it as a “language” for musical devices.
MIDI doesn’t contain actual audio, instead, it contains instructions like:
This makes MIDI files incredibly small and flexible, you can change instruments, tempo, or individual notes without affecting audio quality.
The toolbox
What is javax.sound.midi Package?
javax.sound.midi is Java's built-in
package for working with MIDI data. It provides classes and
interfaces for:
- Sequencer
- Plays MIDI sequences with precise timing
- Synthesizer
- Converts MIDI instructions into actual sound
- MidiChannel
- Controls individual instrument voices
- Sequence & Track
- Store and organize MIDI events
- MidiEvent & ShortMessage
- Represent individual MIDI instructions
This package is what makes our piano possible. it handles the complex timing, audio synthesis, and MIDI protocol details, letting us focus on the musical logic.
03 — From sheet to code
Understanding Music: From Sheet to Code
Let’s take a look at this beautiful piece of music, the Für Elise music sheet! We’re going to learn how we can play this on our digital (hand-made) piano. But first, we need to understand the fundamentals of music. What are notes? What are those lines? What are clefs?
Breaking Down What We See
Looking at this music sheet, let’s identify the key elements that our digital piano needs to understand:
1. The Staff Lines
Those five horizontal lines you see? That’s called a staff (plural: staves). Think of it as a coordinate system for musical pitch, the higher a note sits on the staff, the higher the sound it represents.
2. Clefs
Notice those curly symbols at the beginning of each staff?
Treble Clef (top): This is for higher-pitched notes (right hand on piano)
Bass Clef (bottom): This is for lower-pitched notes (left hand on piano)
3. Time Signature
See that “3/4” at the beginning? That’s our time signature:
- 3 = Three beats per measure
- 4 = Each beat is a quarter note
So every “measure” (the sections divided by vertical lines) contains exactly 3 quarter-note beats.
4. Notes
Those black and white oval shapes are notes . Each note has two main properties:
- Pitch: Where it sits on the staff (which key to press)
- Duration: The shape of the note (how long to hold the key)
5. Dynamic Markings: Volume/Velocity Control
Notice “mp” and “mf” in the sheet? These are dynamic markings:
- mp = “mezzo-piano” = medium soft
- mf = “mezzo-forte” = medium loud
These are like velocity settings for our digital piano!
04 — Timing
Understanding Note Durations: The Timing System
Let’s talk more about the notes, specifically, how long each note should be played. Every note in music has two essential properties: which key to press (pitch) and how long to hold it (duration).
The Mathematical Beauty of Note Durations
Looking at our PianoNotes class, you can
see that musical timing follows a beautiful mathematical pattern -
it's all based on powers of 2 , just like binary in computer
science!
Here’s how the note duration system works:
Notice the mathematical relationship:
WHOLE = 1 // 1/1
HALF = WHOLE ÷ 2 // 1/2
QUARTER = HALF ÷ 2 // 1/4
EIGHTH = QUARTER ÷ 2 // 1/8
SIXTEENTH = EIGHTH ÷ 2 // 1/16 Understanding Time Signatures and Beats
Now let’s understand how these note durations work within time signatures , this is crucial for building our digital piano system correctly.
The Beat Calculation Formula
When I say we’re in a time signature of ?/4 (like 3/4 or 4/4), the 4
in the denominator means that each quarter note equals one beat .
Here’s the key formula:
Notation beats = Multiplier × Denominator_Of_Time_Signiture
For a quarter note in 3/4 time:
Notation beats = 1/4 × 4 = 1And for a
quarter note in 6/8 time we have:
Notation beats = 1/4 × 8 = 2
In 3/4 time (like our Für Elise), each measure can hold exactly 3 beats . This means:
Valid combinations for one measure in 3/4:
- 3 Quarter Notes:
1 + 1 + 1 = 3 notation beats - 6 Eighth Notes:
0.5 + 0.5 + 0.5 + 0.5 + 0.5 + 0.5 = 3 notation beats - 1 Half Note + 1 Quarter Note:
2 + 1 = 3 notation beats
Invalid (doesn’t fit):
- 1 Whole Note:
4 notation beats(too big for a 3-beat measure!) - 4 Quarter Notes:
1 + 1 + 1 + 1 = 4 notation beats(exceeds the limit)
The Whole Note Problem
Here’s something important: A whole note in ?/4 time signatures is
always 4 beats (4 × 1 = 4 beats).
So in 3/4 time , we can never have a whole note , it simply doesn’t fit inside a measure! Instead, for a 3-beat duration in 3/4 time, we use a Dotted Half Note to fill the whole measure.
Dotted Notes: Adding 50% More Time
The “dotted” versions add half of the original note’s multiplier:
DOTTED_WHOLE = WHOLE + HALF; // 1 + 0.5 = 1.5
DOTTED_HALF = HALF + QUARTER; // 0.5 + 0.25 = 0.75
DOTTED_QUARTER = QUARTER + EIGHTH; // 0.25 + 0.125 = 0.375
DOTTED_EIGHTH = EIGHTH + SIXTEENTH; // 0.125 + 0.0625 = 0.1875
So, a Dotted half note in a ?/4 time, is 3 beats. (0.75 × 4 = 3)
05 — Silence
Understanding Rests: The Silent Beats
Now let’s talk about rests, an equally important concept in music. According to Wikipedia:
A rest is the absence of a sound for a defined period of time in music, or one of the musical notation signs used to indicate that.
Rests are not empty time, they’re intentional silence that’s just as important as the notes themselves!
Rest Duration Table
For every note duration, we have a corresponding rest with exactly the same multiplier. The naming follows the same pattern as notes:
Dotted Rests
Dotted rests work exactly like dotted notes. Adding a dot to a rest increases its duration by half of its original value.
Although dotted rests follow the same rule as dotted notes, they are used less often because combinations of regular rests can make the beat structure easier to read.
The Whole Rest Exception
The whole rest has a special behavior: When a whole rest appears by itself in a measure, it represents a full measure of silence regardless of the time signature.
This means:
06 — Which key to press
Understanding Pitch: Which Key to Press
Now comes the most important part about notes: pitch, which button (key) we want to press on our piano!
The image above shows the relationship between the musical staff, the piano keyboard, and the note names. Notice how each position on the staff corresponds to a specific key on the piano keyboard.
Looking at the keyboard, you’ll see a repeating pattern:
- White keys : C, D, E, F, G, A, B (then the pattern repeats)
- Black keys : The sharps/flats (C#/D♭, D#/E♭, F#/G♭, G#/A♭, A#/B♭)
The Middle C (C4 — C in octave 4) is our reference point. It’s like the “origin point” in a coordinate system for musical pitch.
07 — Hello, world
Playing Your First Note: The Code
Now let’s see how to make our computer actually produce sound! Here’s how we set up our digital piano and play a single note:
1. Setting Up the Synthesizer
// Create and open the synthesizer (our sound generator)
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
MidiChannel[] channels = synth.getChannels();
synthChannel = channels[channels.length - 1];
// Set the instrument - here 0 means Grand Piano
synthChannel.programChange(0); 2. Pressing a Key (Note On)
// Press the Middle C key with maximum velocity (127)
synthChannel.noteOn(PianoKeys.C4, 127); 3. Releasing a Key (Note Off)
// Release the Middle C key
synthChannel.noteOff(PianoKeys.C4, 127); We can build our hello world just by that! Here’s the output:
noteOn; every release a noteOff, synthesized live in your browser with the Web Audio API.
Note: MIDI also supports pedal functionality if you need to implement sustain, soft, or sostenuto pedals in your piano.
08 — Where the magic happens
Creating Musical Sequences: Where the Magic Happens
A sequence is like a “musical program”. a set of instructions that tells our piano exactly what to play and when. Think of it as a playlist, but instead of just song names, it contains every single note, rest, and timing detail.
Three Ways to Create Sequences
You can create MIDI sequences in several ways:
- Record from a real digital piano — Connect your MIDI keyboard and capture your performance
- Record from your hand-made digital piano — Capture what users play on your app
- Create sequences with code — Write music programmatically (what we’re going to do!)
The beauty of the third approach is that later we can scan music sheets and use this same implementation to play the sheets!
Setting Up the Sequencer
Here’s how we set up our sequence player:
// Create and open the sequencer (our "music player")
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
// Load our sequence and play it
sequencer.setSequence(THE_SEQUENCE_WE_SHOULD_CREATE);
sequencer.setLoopCount(0); // 0 = play once, -1 = loop forever
sequencer.start(); The Sequence Creation Process
Now comes the exciting part, creating
THE_SEQUENCE_WE_SHOULD_CREATE! This is
where we transform our musical knowledge into code.
A MIDI sequence consists of:
- Tracks — Think of these as different “voices” or “hands”, we use just one track in this implementation.
- Events — Individual instructions like “start note”, “stop note”, “change tempo”. May refer as MetaData.
- Timing — Precise scheduling of when each event happens
- 01 Sheet treble & bass, in musician terms
- 02 Events note on / off, tempo, velocity
- 03 Track ordered MidiEvents on ticks
- 04 Sequencer schedules events in time
- 05 Synthesizer turns events into sound
Building Our PianoSequence Class
Instead of working with raw MIDI events, we’ll create a high-level
PianoSequence class that speaks "musician
language":
public class PianoSequence {
private static final int PPQ = 100; // Pulses Per Quarter note
final Sequence sequence;
private final Track track;
// Two "hands" for piano playing
public final Sheet treble = new Sheet(false); // Right hand
public final Sheet bass = new Sheet(true); // Left hand
public PianoSequence() throws InvalidMidiDataException {
// Create the underlying MIDI sequence
sequence = new Sequence(Sequence.PPQ, PPQ);
track = sequence.createTrack();
}
} 09 — Ticks & tempo
Understanding the Sequence Constructor: The Foundation of MIDI Timing
Let’s break down this crucial line of code:
sequence = new Sequence(Sequence.PPQ, PPQ); This single line sets up the entire timing foundation for our musical sequence. Let’s understand each part:
First Parameter: Sequence.PPQ (Division Type)
Second Parameter: PPQ (Resolution - our constant set to 10)
The first parameter Sequence.PPQ tells
MIDI what type of timing system to use. We use
Sequence.PPQ because we're making music,
not syncing to video. PPQ-based timing is perfect for musical
applications.
What is PPQ?
PPQ stands for “Pulses Per Quarter Note” — it’s the fundamental unit of musical timing in MIDI.
Think of it like this:
- If PPQ = 10, then every quarter note is divided into 10 tiny time slices
- If PPQ = 100, then every quarter note is divided into 100 tiny time slices
- Higher PPQ = more precise timing, but more memory usage
We’ve defined a constant PPQ with a value of 10, which sets the resolution for timing calculations. This means a quarter note is represented by 10 ticks, an eighth note by 5 ticks, a half note by 20 ticks, and a whole note by 40 ticks.
How PPQ Affects Our Calculations
To calculate the number of ticks for a given note duration, I use
the method getNoteTicks(double multiplier).
private long getNoteTicks(double multiplier) {
return Math.round(multiplier * PPQ / PianoNotes.QUARTER);
}
For example, with PPQ = 10, a quarter note
(multiplier = 0.25) yields
0.25 * 10 / 0.25 = 10 ticks, an eighth
note (duration = 0.125) yields
0.125 * 10 / 0.25 = 5 ticks, and a half
note (duration = 0.5) yields
0.5 * 10 / 0.25 = 20 ticks.
private long getRestTicks(double duration) {
if (duration == PianoNotes.WHOLE) {
// Special case: whole rest fills the measure
return getNoteTicks((double) numerator / denominator);
}
return getNoteTicks(duration);
} Notice how getRestTicks() has special logic for whole rests, as we explained earlier:
In 3/4 time signature (numerator = 3, denominator = 4):
// Whole Note (regular): Always 4 beats = 40 ticks
getNoteTicks(PianoNotes.WHOLE) → 40 ticks
// Whole Rest (special): Fills measure = 3 beats = 30 ticks
getRestTicks(PianoNotes.WHOLE) → getNoteTicks(3.0/4.0) → 30 ticks What is Tempo?
Tempo is the speed of music, measured in BPM (Beats Per Minute) . It determines how fast or slow the music plays:
- 60 BPM = One beat per second (very slow)
- 120 BPM = Two beats per second (moderate, like our Für Elise)
- 180 BPM = Three beats per second (fast)
In our code, we control tempo with:
Piano.getInstance().setTempo(120f); // 120 beats per minute The thing about our PPQ-based system is that changing tempo doesn’t affect the musical relationships. notes still have the same relative durations, they just play faster or slower. A quarter note is always twice as long as an eighth note, regardless of whether the tempo is 60 BPM or 200 BPM.
The Sheet System
The Sheet class lets us write music using musical terms instead of raw MIDI:
p.setTimeSignature(3, 4);
p.setVelocity(PianoVelocity.MP);
p.treble.addRest(PianoNotes.HALF);
p.treble.addNote(PianoKeys.E5, PianoNotes.EIGHTH);
p.treble.addNote(PianoKeys.D_SHARP5, PianoNotes.EIGHTH);
p.bass.addRest(PianoNotes.WHOLE); Sheet terms. Press play to hear those six lines.
As seen in the first measure of the Für Elise music sheet, the time signature is set to 3/4, with a dynamic marking of mezzo-piano (mp). The treble clef begins with a half rest, followed by an eighth note on E5 and another eighth note on D#5. The bass clef, in contrast, contains only a whole rest.
That’s it! We can now play the first measure of Für Elise. But what’s happening behind the scenes?
Understanding addNote and addChord Methods
Let’s break down the magic happening under the hood:
The addNote Method
public void addNote(int note, double duration) {
addChord(duration, note);
}
This is beautifully simple — addNote is
just a convenience method that calls
addChord with a single note. This design
allows us to handle both individual notes and chords with the same
underlying system.
The addChord Method: Where the Real Work Happens
public void addChord(double duration, int... notes) {
int vel = velocity.getVelocity(isBass);
long durationTicks = getNoteTicks(duration);
double beats = durationTicks / (PPQ * 4.0 / denominator);
for (int note : notes) {
events.add(new PianoEvent.Note(currentBeat, beats, note, isBass, vel));
track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, DEFAULT_CHANNEL, note, vel), currentTick));
track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_OFF, DEFAULT_CHANNEL, note, 0), currentTick + durationTicks));
}
currentBeat += beats;
currentTick += durationTicks;
} Let’s trace through what happens when we call addNote(PianoKeys.E5, PianoNotes.EIGHTH):
- Get velocity
vel = velocity.getVelocity(false)→ Gets MP velocity for treble - Convert to ticks
durationTicks = getNoteTicks(0.125)→ 5 ticks (with PPQ=10) - Convert to beats
beats = 5 / (10 * 4.0 / 4)→ 0.5 beats - Schedule MIDI events
- NOTE_ON at
currentTick(start playing E5) - NOTE_OFF at
currentTick + 5(stop playing E5 after 5 ticks)
- NOTE_ON at
- Advance time Move forward 0.5 beats and 5 ticks
The Beat Conversion Formula Explained
Now let’s decode this crucial formula:
double beats = durationTicks / (PPQ * 4.0 / denominator); This converts MIDI ticks (internal timing) to notation beats.
Breaking Down the Formula
PPQ * 4.0 / denominator calculates "ticks per beat" for our time signature:
- PPQ = 10 (our pulses per quarter note)
- 4.0 represents a quarter note (the standard beat reference)
- denominator is from our time signature (4 in “3/4”)
Why This Beat Calculation Matters
This beat conversion is crucial for UI development ! Here’s why:
double noteHeight = beats * pixelsPerBeat;
double notePosition = currentBeat * pixelsPerBeat; The addRest Method: Silent but Important
public void addRest(double duration) {
long durationTicks = getRestTicks(duration);
double beats = durationTicks / (PPQ * 4.0 / denominator);
currentBeat += beats;
currentTick += getRestTicks(duration);
} Rests don’t generate sound, but they’re essential for:
- Proper timing: Maintaining the musical rhythm
- UI spacing: Creating visual gaps in our sequencer
10 — Expression
Understanding Velocity
Velocity represents how much pressure we put on the key when pressing it. More velocity equals a greater and louder sound. just like pressing a real piano key harder produces a louder, more intense tone.
In MIDI, velocity ranges from 0 to 127:
- 0 = Silent (no sound)
- 1–127 = Varying degrees of loudness and intensity
- 127 = Maximum volume and force
Dynamic Markings: From Sheet Music to Numbers
From the music sheets we’ve seen, we encounter dynamic markings like mp , p , f , and others. Each of these musical symbols maps to a specific velocity number:
public enum PianoVelocity {
PPP(15), // Pianississimo - extremely soft
PP(30), // Pianissimo - very soft
P(45), // Piano - soft
MP(60), // Mezzo-piano - moderately soft
MF(75), // Mezzo-forte - moderately loud
F(90), // Forte - loud
FF(110), // Fortissimo - very loud
FFF(120); // Fortississimo - extremely loud
} How These Look on Sheet Music
On a music sheet, you’ll see these as text markings:
- p = soft, gentle playing
- mp = moderately soft
- mf = moderately loud
- f = loud, strong playing
- ff = very loud
- fff = extremely loud
Crescendo and Decrescendo: Dynamic Changes Over Time
But music isn’t just about static volume levels, it’s about change and expression. This is where crescendo and decrescendo come in:
Crescendo (Getting Louder)
Crescendo means “growing” in Italian, the music gradually gets louder over time.
On sheet music , crescendo appears as:
- Hairpin symbol :
<(starts narrow, opens wide) - Text marking : “cresc.” or “crescendo”
- Combined : The hairpin with “cresc.” underneath
Decrescendo/Diminuendo (Getting Softer)
Decrescendo (or diminuendo ) means the music gradually gets softer.
On sheet music , it appears as:
- Hairpin symbol :
>(starts wide, narrows down) - Text marking : “decresc.” or “dim.”
- Combined : The hairpin with text
Implementing Dynamic Changes in Code
Our system supports both static velocities and dynamic changes:
Static Velocity
// Set a constant volume for all following notes
p.setVelocity(PianoVelocity.MP); // All notes will be "moderately soft" Crescendo
// Gradually increase from current velocity to MF over the next 4 notes
p.setCrescendo(PianoVelocity.MF, 4); Example
// Start with moderate soft volume
p.setVelocity(PianoVelocity.MP); // velocity = 60
// Play some notes at constant volume...
p.treble.addNote(PianoKeys.A4, PianoNotes.QUARTER); // velocity = 60
// Then start a crescendo to MF over the next 4 notes
p.setCrescendo(PianoVelocity.MF, 4); // from 60 to 75
p.treble.addNote(PianoKeys.B4, PianoNotes.EIGHTH); // velocity ≈ 64
p.treble.addNote(PianoKeys.C5, PianoNotes.EIGHTH); // velocity ≈ 68
p.treble.addNote(PianoKeys.D5, PianoNotes.EIGHTH); // velocity ≈ 71
p.treble.addNote(PianoKeys.E5, PianoNotes.EIGHTH); // velocity = 75 11 — Bringing it to life
Outputs: Bringing Our Piano to Life
Now let’s see our digital piano system in action! I’ve created a simple UI for our MIDI sequencer to visualize how our code translates into actual musical output.
Für Elise
Donyaye in rozaye man (Dariush)
12 — Your turn
Playground
Coda
Conclusion
This article has demonstrated the systematic development of a digital piano application using Java’s MIDI capabilities, progressing from fundamental musical concepts to a complete implementation capable of rendering complex compositions. Successfully translated core musical elements — notes, rests, time signatures, clefs, and dynamic markings — into computational structures and algorithms.
GitHub Repository Aghajari / Piano https://github.com/Aghajari/Piano