All publications
Audio & MIDI Interactive edition

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.

Sep 27, 2025 · 15 min read
Java MIDIJavaFXAudioSequencing

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:

A MIDI stream is a list of instructions on a timeline — not a single byte of recorded sound.

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?

Für Elise — opening theme
The opening of Für Elise, engraved on a grand staff — 3/4 time, an mp dynamic, the treble theme over a quiet bass. Press play to hear it.

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:

SymbolNameRelative lengthMultiplier
Each note is exactly half the one above it. Tap a row to hear it held for its duration at 120 BPM — toggle Dotted and watch the symbols and multipliers update.

Notice the mathematical relationship:

Durations · powers of 2
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)
3/4 Fill the measure — exactly 3 beats
0 / 3 beats
Stack durations into a 3/4 measure. The bar turns gold when it’s exactly full — and refuses to overflow, just like the rules above.

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 durations
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:

DurationNoteRestMultiplier
Whole1
Half1/2
Quarter1/4
Eighth1/8
Sixteenth1/16
Every note shape has a matching rest of the same length — sound and silence, measured identically.

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:

4/44 beats
3/43 beats
6/86 beats
2/42 beats

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!

A grand staff of ascending notes mapped down to a seven-octave piano keyboard, with note names (A–G), solfège (La, Si, Do, Re, Mi, Fa, Sol) and every C highlighted.
Source: https://nl.pinterest.com/pin/7881368093570865/

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

Java · 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)

Java · noteOn
// Press the Middle C key with maximum velocity (127)
synthChannel.noteOn(PianoKeys.C4, 127);

3. Releasing a Key (Note Off)

Java · noteOff
// Release the Middle C key
synthChannel.noteOff(PianoKeys.C4, 127);

We can build our hello world just by that! Here’s the output:

idle Click keys, drag across them, or use your computer keyboard (A–K)
The output — a real, playable keyboard. Every press fires a 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:

Java · sequencer
// 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
  1. 01 Sheet treble & bass, in musician terms
  2. 02 Events note on / off, tempo, velocity
  3. 03 Track ordered MidiEvents on ticks
  4. 04 Sequencer schedules events in time
  5. 05 Synthesizer turns events into sound
From musician language down to sound: each layer lowers the abstraction until the synthesizer makes air move.

Building Our PianoSequence Class

Instead of working with raw MIDI events, we’ll create a high-level PianoSequence class that speaks "musician language":

Java · PianoSequence
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:

Java · constructor
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.

10 ticks / quarter 5 / eighth 20 / half 40 / whole
A quarter note sliced into pulses. Slide PPQ up for finer resolution — every other duration scales with it.

How PPQ Affects Our Calculations

To calculate the number of ticks for a given note duration, I use the method getNoteTicks(double multiplier).

Java · getNoteTicks
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.

Java · getRestTicks
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):

Ticks · whole note vs whole rest
// 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:

Java · setTempo
Piano.getInstance().setTempo(120f); // 120 beats per minute
Same notes, same relative durations — only the clock changes. A quarter note is always twice an eighth, at 60 BPM or 200.

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:

Java · first measure
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);
The first measure, both ways: engraved on the staff and written in 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

Java · addNote
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

Java · addChord
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):

  1. Get velocity vel = velocity.getVelocity(false) → Gets MP velocity for treble
  2. Convert to ticks durationTicks = getNoteTicks(0.125) → 5 ticks (with PPQ=10)
  3. Convert to beats beats = 5 / (10 * 4.0 / 4) → 0.5 beats
  4. Schedule MIDI events
    • NOTE_ON at currentTick (start playing E5)
    • NOTE_OFF at currentTick + 5 (stop playing E5 after 5 ticks)
  5. Advance time Move forward 0.5 beats and 5 ticks

The Beat Conversion Formula Explained

Now let’s decode this crucial formula:

Java · beats
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:

Java · layout
double noteHeight = beats * pixelsPerBeat;
double notePosition = currentBeat * pixelsPerBeat;

The addRest Method: Silent but Important

Java · addRest
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:

Java · PianoVelocity
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
}
The same note, played soft to loud. Drag the slider and press play — the dynamic marking snaps to the nearest mark from the enum above.

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

Java · static velocity
// Set a constant volume for all following notes
p.setVelocity(PianoVelocity.MP);  // All notes will be "moderately soft"

Crescendo

Java · crescendo
// Gradually increase from current velocity to MF over the next 4 notes
p.setCrescendo(PianoVelocity.MF, 4);

Example

Java · crescendo 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
60 64 68 71 75
A crescendo from MP (60) to MF (75) across four notes — hear the hairpin open up.

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

Für Elise Ludwig van Beethoven · WoO 59
The opening of Für Elise.

Donyaye in rozaye man (Dariush)

Donyaye in rozaye man Dariush
Once the engine plays one sheet, it plays any sheet — Western classical or Persian pop alike.

12 — Your turn

Playground

Click & drag the keys · computer keys A W S E D F T G Y H U J K play C4–C5 · scroll for more octaves

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