White cubes fly at a 3×3 grid. Your cursor has to be on the cube when it lands. Where inside the cell you caught it decides the rank — timing barely matters, aim does.
Open Rhythm is a game, not a music player. The tracks are here so there is something to play to. If a song is good, go listen to it where the artist actually gets paid.
Grab a build for your platform from Releases. The game checks for a newer release when the menu opens and offers to update itself — one click, it downloads, installs and restarts. That check can be turned off in Settings → Video.
Or run it from source with Godot 4.7:
godot --path .What's in it
| Square mode | 3×3 grid, zone-based judging, PERFECT → BULLSHIT ranks |
| Hold notes | long cubes you have to carry to the end — nothing else is charted while one runs, because there is only one cursor |
| Click notes | cubes that have to be pressed, not just covered. Off by default; the CLICKS modifier turns on the ones a mapper put in |
| Modifiers | TARGET, BLACKOUT, CAGE, SPEED UP, SLOW DOWN, MIRROR, HIDDEN, CLICKS, CLICKY — each with its own score multiplier |
| Three stories | learn with Melly, then Night Drive, then Overdrive |
| Six OST tracks | written by tools/gen_media.py and tools/gen_pack2.py, with Easy / Normal / Hyper charts |
| Map editor | piano roll with a waveform and beat grid, snapping, undo, copy/paste, multi-select, hold dragging, and an onset-based auto-generator |
| Map scripting | a song can change the background colour, throw its own pictures behind the playfield, reskin the cubes and shake the camera |
| Imports | .sspm (Rhythia / Sound Space Plus, v1 and v2) and the legacy Sound Space map string |
| Versus | two players, one chart, higher score wins — a direct connection, no server |
| Offset calibration | tap along with a metronome and the game works out your audio delay |
| Replays | plus records, lifetime stats and 23 achievements |
| Accessibility | reduce motion, reduce flashes, cursor size, video off |
| Six languages | English, Russian, Chinese, German, Dutch, Spanish |
| Controls | mouse, touch, gamepad stick or keyboard, with rebindable keys |
| VR | the desktop build opens in a headset when one is running and in a window when it is not; Quest and Pico get their own packages. Aim with a laser pointer, or hold a saber in each hand and cut the cubes from any direction |
Runs on Linux, Windows, Android and the web.
Adding your own songs
Drop a folder or a .zip into the library folder shown on the Songs screen,
then press RESCAN. A song folder is just this:
my_song/
├── map.json required
├── audio.ogg .wav, .ogg or .mp3
├── video.ogv optional, played behind the playfield
└── events.json optional, see map scripting
The Songs screen also imports .sspm maps from Rhythia / Sound Space
directly — audio and cover art come along with them.
Full format reference: docs/MAP_FORMAT.md
Making maps
The 3×3 playfield on the left places notes at the playhead; the timeline at the bottom is where you actually shape the chart.
| Space | play / pause |
| ← → | seek by the snap step (Shift: a bar) |
| 1…8 | snap 1/1 … 1/16 |
| Ctrl+Z / Ctrl+Y | undo / redo |
| Ctrl+C / Ctrl+V | copy / paste at the playhead |
| Ctrl+D | duplicate the selection forward |
| Ctrl+A / Delete | select all / delete the selection |
| H | toggle a one-beat hold on the selection |
| C | toggle click notes on the selection |
| Ctrl+S / T | save / test the chart |
| drag | rubber-band select |
| Ctrl+click | add a note |
| drag right edge | set a hold length |
| wheel / Ctrl+wheel / middle drag | scroll / zoom / pan |
Saving a song that ships with the game forks it into your library first — it
asks what to call the copy. res:// lives inside the binary and cannot be
written to.
The waveform is drawn for .wav audio, or from a waveform.json if the song
ships one.
Versus
One player hosts, the other types their address. The connection is direct —
nothing goes through a server and nothing is uploaded anywhere, so both
machines have to be able to reach each other: the same network, or the host
forwarding port 27015.
Both sides must be on the same build and holding the same chart. The handshake compares a protocol number, the game version and a SHA-256 of the notes themselves, so custom maps work exactly like the bundled ones as long as the charts are identical. The audio file name and any video are left out of that hash — neither changes what you play.
VR
Headsets are not there yet. The desktop side is built and the room draws, but the Quest and Pico packages crash on startup inside the engine's own OpenXR - on a Pico 4 they do not survive half a second. What has been ruled out, and what it needs next, is written down in docs/VR_STATUS.md. The desktop build's VR path has never been run against a real runtime either, so treat it as untested.
You can look at the whole thing without a headset: Settings → VR → Look at the VR room on this screen.
There is no VR build of the desktop game and no switch to flip. It looks for an OpenXR runtime at startup: SteamVR running and a headset awake means it opens in VR, nothing there means it opens in a window. Settings → VR can say no, and picks how you hit the cubes.
Laser pointer puts the grid across the room and aims a beam at it; the trigger is a click, which is what click notes want. Sabers bring the grid to arm's length and put a blade in each hand. A cube counts the moment a blade sweeps through it, from any direction - the cubes have no side to cut them on, so swing the way that feels right. Grip + B recentres the room, and the menu button steps back the way Esc does.
Everything else is the flat game: the same charts, the same judging, the same menus and editor, running on a screen hanging in front of you. Only the cubes come out of it into the room.
Quest and Pico get their own packages. Building them needs the OpenXR loaders, which are not in this repository:
curl -LO https://github.com/GodotVR/godot_openxr_vendors/releases/download/5.1.0-stable/godotopenxrvendorsaddon.zip
unzip -q godotopenxrvendorsaddon.zip -d /tmp/vendors
cp -r /tmp/vendors/asset/addons/godotopenxrvendors addons/
godot --headless --path . --install-android-build-template --export-release "Android" build/OpenRhythm.apk
tools/build_headsets.shThose two are gradle builds, so they need the Android SDK and a JDK the same way the phone package does. They also need the Mobile renderer: OpenXR on Android binds to the graphics API when it creates its session and wants Vulkan, and on the OpenGL ES binding the interface loads while the session never does - which from inside the headset looks like the app hanging on its loading screen. The engine picks a renderer before it knows an export's custom features, so that cannot be a per-preset setting; the script flips it for the length of those two builds and puts it back afterwards.
Building
Godot 4.7 with the matching export templates installed.
godot --headless --path . --export-release "Linux" build/linux/OpenRhythm.x86_64
godot --headless --path . --export-release "Windows Desktop" build/windows/OpenRhythm.exe
godot --headless --path . --export-release "Web" build/web/index.htmlThe web export needs cross-origin isolation headers (COOP/COEP) on the
host; itch.io has a checkbox for it. Theora video is excluded from the web
build, so songs with a clip play without one.
Android signing. Release credentials are never stored in
export_presets.cfg — CI fails the build if they end up there. Pass them
through the environment instead:
export GODOT_ANDROID_KEYSTORE_RELEASE_PATH=/path/to/release.keystore
export GODOT_ANDROID_KEYSTORE_RELEASE_USER=your-alias
export GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD=...
godot --headless --path . --export-release "Android" build/android/OpenRhythm.apkCI builds the APK too, once the same key is stored as repository secrets
(ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_USER, ANDROID_KEYSTORE_PASSWORD);
without them that job simply skips.
Releases are cut by pushing a tag — CI builds every platform and attaches the archives:
git tag v0.3.1 && git push origin v0.3.1Music and rights
The bundled OST — Neon Drift, Hyper Drive, Midnight Pulse, Bass Rush, Crimson
Step, Afterburner and the tutorial track — is generated by the scripts in
tools/ and ships under the project licence.
Music by anyone else is not built into the game and is not in this repository. It travels as an installable song zip instead - drop the file in the songs folder and the game unpacks it on the next scan:
python3 tools/pack_song.py local_songs/my_songKeep those folders in local_songs/ with an empty .gdignore beside them, or
the engine will import and pack them anyway - that one file is the difference
between a 37 MB download and a 96 MB one.
That keeps someone else's recording out of the build, and out of every clone of this repo, while still being one file to hand a friend. The game shows a notice on first launch pointing players at the original releases.
If you are an artist and want a track out of the game, say so in Telegram or Discord and it goes.
Contributing
The most useful thing you can add is a map. Build one in the editor and open a pull request with the song folder — without the audio unless you own it.
Code is plain Godot 4 + GDScript, no plugins. There is a headless test harness:
godot --headless --path . res://tools/CoreTest.tscn # saves, judging, charts
godot --headless --path . res://tools/LayoutTest.tscn # every screen, six aspect ratiosSee CONTRIBUTING.md for house style, the translation workflow and the chart generators.
Code under MIT · fonts under SIL OFL 1.1 · music per track
Made by narezany




