Field manual · Harmonic Systems

PocketSynth

Pocket Synth  ·  keyboard connected
C4
< keys >volume ########--

A velocity-sensitive sampler synthesizer that lives in a pocket: a Korg microKEY2 plugged into a $30 R36S game handheld, playing through a Godot 3.5 app.

Built in one day as a music-reinforcement tool for school speech-therapy sessions — no laptop required.

What it is

PocketSynth turns the pink R36S (a Rockchip RK3326 retro-gaming handheld running ArkOS) into a small instrument. Plug a class-compliant USB MIDI keyboard into the OTG port and play: three instruments, ten-voice polyphony, velocity-sensitive volume, and a calm, low-stimulation display — a big note name and soft circles that drift and fade as you play.

InstrumentCharacterSound design
keyssoft, piano-likedecaying harmonic stack, slight inharmonicity
marimbawoody, percussivebar partials (1 · 3.9 · 9.2×) with a soft mallet click
padwarm, slowtwo detuned layers, 80 ms attack, long release

Each instrument ships 14 samples — one WAV every six semitones from MIDI 24 to 102, generated by tools/make_samples.py (numpy additive synthesis). The engine picks the nearest sample and pitch-shifts at most ±3 semitones, so stretching stays inaudible.

Controls

ButtonAction
D-pad ◀ ▶Switch instrument (keys / marimba / pad)
D-pad ▲ ▼Volume up / down
ATest note — confirms sound without a keyboard attached
BQuiet — instantly silences every voice
StartExit to the Ports menu

The school routine

  1. Connect the keyboard first, then power on. Hot-plugging USB while the console runs on battery browns out the power rail and the device shuts off instantly.
  2. If you must replug mid-session, put the charger in the DC port first (the top USB-C). With wall power attached, hot-plugging is safe. The bottom port marked OTG is the data port.
  3. Launch PocketSynth from the Ports menu. The corner status reads waiting for keyboardkeyboard connected within a couple of seconds.
  4. Press A once for the test note — your two-second sound check.

The system volume display lies. While the Korg is plugged in, ArkOS's volume UI may show 0%. Ignore it — that gauge is reading the keyboard's phantom sound card (see the audio hijack). Your real volume is D-pad ▲ ▼ inside the app.

How it works

The obvious path — Godot's built-in MIDI API — is a dead end on this hardware, so notes take a scenic route:

Why the bridge exists

Two verified engine limitations force the detour. The FRT runtime (the ARM build of Godot 3.5 that PortMaster ships for these handhelds) compiles out the ALSA MIDI driver entirelyOS.get_connected_midi_inputs() returns empty forever. And Godot 3 refuses to open character devices: its file layer stat-checks every path and rejects anything that isn't a regular file, so reading /dev/snd/midiC1D0 directly is impossible. A 40-line bash script and one UDP socket solve both.

This is the moment it first worked — the diagnostic build showing the kernel had seen the keyboard all along:

[/dev/snd]
by-id by-path controlC0 controlC1 midiC1D0 pcmC0D0c pcmC0D0p seq timer
[cards]
 0 [rockchiprk817 ]: rockchip-rk817
 1 [microKEY2     ]: USB-Audio - microKEY2
   KORG INC. microKEY2 at usb-ff300000.usb-1, full speed

NOTE_ON  ch=0 pitch=48 vel=72
NOTE_ON  ch=0 pitch=50 vel=74
NOTE_OFF ch=0 pitch=48

The audio hijack, explained

The strangest bug of the build: the keyboard worked, the app worked, and the speaker stayed silent while the volume kept "resetting to 0%."

Because USB MIDI is a subclass of USB Audio, the microKEY2 registers as a sound card. ArkOS ships USB-DAC auto-switching with no check for whether the new card can actually play audio: a udev rule tags any USB sound card, and a hidden root script (polling every second) rewrites the system-wide audio default to point at it. Result: the entire OS tries to play sound through a keyboard with no speakers.

Two fixes, both in the repo:

One-day build log

Build & deploy runbook

  1. Open the project in Godot 3.5.2 — never Godot 4, which silently converts and breaks it.
  2. Project → Export → Linux/X11 → Export PCK/Zip → save as deploy/ports/pocketsynth/game.pck.
  3. Copy deploy/ports/* onto the SD card's ports/ folder. Delete the card's old pocketsynth/ firstcp -R nests into an existing directory and the device will silently boot the stale build. Then sync before ejecting.
ports/
├── PocketSynth.sh          launcher: audio pin + bridge + FRT
├── FixKorgAudio.sh         optional one-time system fix
└── pocketsynth/
    ├── game.pck            the exported Godot app (~6 MB)
    ├── frt_3.5.2.squashfs  PortMaster Godot runtime
    ├── midi_bridge.sh      rawmidi → UDP bridge
    └── godot.gptk          button → key mapping

Troubleshooting

SymptomFix
waiting for keyboardKeyboard on the bottom (OTG) port, not the DC port. Replug with the charger attached; the app re-detects within ~2 s.
Console powers offUSB was hot-plugged on battery. Power back on with everything already connected, or keep the charger in the DC port.
Notes but no soundPress A (test note), then D-pad ▲ a few times. If still silent, re-copy PocketSynth.sh — the audio pin lives there.
Games lost sound tooThe system-wide hijack (see above). Run FixKorgAudio once from the Ports menu.
Old build keeps launchingThe nested-copy gotcha: check the card for ports/pocketsynth/pocketsynth/, delete and re-copy.