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.
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.
| Instrument | Character | Sound design |
|---|---|---|
| keys | soft, piano-like | decaying harmonic stack, slight inharmonicity |
| marimba | woody, percussive | bar partials (1 · 3.9 · 9.2×) with a soft mallet click |
| pad | warm, slow | two 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.
| Button | Action |
|---|---|
| D-pad ◀ ▶ | Switch instrument (keys / marimba / pad) |
| D-pad ▲ ▼ | Volume up / down |
| A | Test note — confirms sound without a keyboard attached |
| B | Quiet — instantly silences every voice |
| Start | Exit to the Ports menu |
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.
The obvious path — Godot's built-in MIDI API — is a dead end on this hardware, so notes take a scenic route:
/dev/snd/midiC1D0Two 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 entirely — OS.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:
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:
AUDIODEV, and restores its mixer. The hijacker can rewrite configs all it wants; the synth never consults them.0944) from the auto-switch, so games and menus also keep their sound while the keyboard is plugged in.deploy/ports/pocketsynth/game.pck.deploy/ports/* onto the SD card's ports/ folder. Delete the card's old pocketsynth/ first — cp -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
| Symptom | Fix |
|---|---|
| waiting for keyboard | Keyboard on the bottom (OTG) port, not the DC port. Replug with the charger attached; the app re-detects within ~2 s. |
| Console powers off | USB was hot-plugged on battery. Power back on with everything already connected, or keep the charger in the DC port. |
| Notes but no sound | Press A (test note), then D-pad ▲ a few times. If still silent, re-copy PocketSynth.sh — the audio pin lives there. |
| Games lost sound too | The system-wide hijack (see above). Run FixKorgAudio once from the Ports menu. |
| Old build keeps launching | The nested-copy gotcha: check the card for ports/pocketsynth/pocketsynth/, delete and re-copy. |