Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

heartopia-midi

Play Heartopia's in-game piano with a real MIDI keyboard.

This is a small Python bridge that listens for MIDI note events from a USB MIDI controller and sends the matching QWERTY keystrokes that Heartopia's piano expects. Plug in your keyboard, run the script, focus the piano UI in Heartopia, and play normally — middle C on your MIDI plays middle C in-game, sharps and all.

How it works

   MIDI keyboard          midi_piano.py             Heartopia
  ┌──────────────┐      ┌─────────────────┐      ┌─────────────┐
  │   press C4   │ ───► │ MIDI 60 → 'z'   │ ───► │ plays DO    │
  └──────────────┘      │ send keystroke  │      │ (middle C)  │
                        └─────────────────┘      └─────────────┘

The bridge:

  1. Reads MIDI note-on / note-off events with pygame.midi.
  2. Looks up the QWERTY key Heartopia expects for that MIDI note.
  3. Synthesizes a global key press / release with pynput so the game receives it as normal typing.

Requirements

  • Windows (works on macOS / Linux too in principle, but Heartopia is Windows)
  • Python 3.10+ — 3.12 is the most well-tested. 3.13 works with the current pygame.midi backend.
  • A USB MIDI keyboard that Windows recognizes
  • Heartopia installed and running

Install

git clone https://github.com/sp0oby/heartopia-midi.git
cd heartopia-midi
pip install -r requirements.txt

Dependencies:

  • pygame>=2.5.0 — MIDI input (and has Python 3.13 wheels, unlike python-rtmidi)
  • pynput>=1.7.6 — synthetic keystroke injection

Usage

  1. Plug in your MIDI keyboard. Wait a couple seconds for Windows to recognize it.

  2. Run the bridge:

    python midi_piano.py

    You should see:

    Using MIDI input: <your keyboard name>
    Focus Heartopia's piano UI, then play. Ctrl+C to quit.
    
  3. Launch Heartopia, open the piano, click the piano UI so it's focused.

  4. Play your MIDI keyboard. Notes play in-game in real time.

  5. Ctrl+C in the terminal to stop cleanly (all held keys are released).

Sanity check (no game required)

Want to confirm the bridge works before launching Heartopia?

  1. Start the script.
  2. Open Notepad and click into the blank area.
  3. Press middle C on your MIDI keyboard — the letter z should appear in Notepad.

If letters appear in Notepad, the bridge is fine and any issue is on Heartopia's side. If not, see Troubleshooting.

Configuration

All knobs live at the top of midi_piano.py.

OCTAVE_SHIFT

Shifts every incoming MIDI note up or down by N octaves before mapping. Useful for compact MIDI controllers (25/49-key) whose default range doesn't sit naturally inside Heartopia's C3 → C6.

Value Effect
-2 Drops 2 octaves
-1 Drops 1 octave
0 No shift (default)
+1 Raises 1 octave
+2 Raises 2 octaves

You can also just use the OCTAVE +/− buttons on the MIDI keyboard itself — that's usually the easier approach for live playing.

DEBUG

DEBUG = True   # prints every incoming MIDI note to the terminal
DEBUG = False  # silent

Leave it on while you dial in OCTAVE_SHIFT, then turn it off for clean play.

Heartopia key mapping

Heartopia covers a full chromatic 3-octave range. The bridge maps each MIDI note in C3–C6 to the QWERTY key the game expects.

High octave (C5 – C6)

Note MIDI Key Note MIDI Key
C5 72 Q C♯5 73 2
D5 74 W D♯5 75 3
E5 76 E
F5 77 R F♯5 78 5
G5 79 T G♯5 80 6
A5 81 Y A♯5 82 7
B5 83 U
C6 84 I

Middle octave (C4 – B4)

Note MIDI Key Note MIDI Key
C4 60 Z C♯4 61 S
D4 62 X D♯4 63 D
E4 64 C
F4 65 V F♯4 66 G
G4 67 B G♯4 68 H
A4 69 N A♯4 70 J
B4 71 M

Low octave (C3 – B3)

Note MIDI Key Note MIDI Key
C3 48 , C♯3 49 L
D3 50 . D♯3 51 ;
E3 52 /
F3 53 O F♯3 54 0
G3 55 P G♯3 56 -
A3 57 [ A♯3 58 =
B3 59 ]

Notes outside MIDI 48–84 are dropped silently. Use OCTAVE_SHIFT (or your controller's octave buttons) to bring them into range.

Troubleshooting

"No MIDI input devices found"

Windows doesn't see your keyboard. Check:

  • Is the USB cable a data cable, not power-only?
  • Does the MIDI keyboard show up in Device Manager under "Sound, video and game controllers"?
  • Some controllers need a vendor driver before they enumerate over MIDI — check the manufacturer's site.

The script exits immediately with no output

If you're on Python 3.13 and previously installed python-rtmidi, that package's C extension crashes silently on 3.13. This project uses pygame.midi instead specifically to avoid that. Make sure your requirements.txt is the current one and reinstall:

pip install -r requirements.txt

Letters appear in Notepad but Heartopia ignores them

Some Unity / Unreal games use raw or DirectInput and ignore pynput's synthetic keystrokes. The fix is to swap the input backend to pydirectinput, which sends scancode-level events most games accept. Open an issue if you hit this and I'll add a switch.

Notes are doubling in-game

Already handled. The bridge ignores repeat note_on events for a key it's already holding — some MIDI controllers re-trigger on aftertouch and this used to cause doubles.

MIDI input is detected but nothing plays in-game

  • Is the piano UI in Heartopia focused? Click on it.
  • Run with DEBUG = True and play a key. If key='z' (or whatever) prints, the bridge is working — it's a game-side focus issue.
  • If key=None prints, your note is outside the C3–C6 range. Adjust OCTAVE_SHIFT.

License

MIT.

About

Play Heartopia's in-game piano with a real MIDI keyboard. Python bridge that maps MIDI notes to the QWERTY keys Heartopia listens for - full 3-octave chromatic range.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages