Skip to content

Rhym v2 (JSON + Binary...?)

New version of the Rhythia Map format
For those that aren’t aware, if a word ends with / (for example, Insane/), then that means it is a folder

As the last version of .rhym, it will be formatted in a folder. This takes inspiration from osu, quaver, Beat Saber, etc.

Again, .rhym is just the compressed version of the map. It is just a zip, but with the filename extension renamed. So if a file is called Map.rhym, you can just rename it to and it will work COMPLETELY the same!

If I do any global caching for songs and videos and whatnot, it is to speed up parsing logically! To add on to this, difficulties no longer have their own folders (BY DEFAULT). BUT, users can still make their own folders if they would like! This gives mappers more creativity and more organization, which is completely up to the mapper. So an aspire map can have an assets/ folder if they would like, or a massive training map pack can have respective song folders. For example, “BlueZenith/”, “Kamikaze/”, or “Dangerous_Slot/”, anything you desire!

This enables mappers to have more freedom with how their map is structured. If it is of a user’s concern on map scanning parsing times, there will be tools available to automatically optimize parsing speeds! (Optimizer Tool that gets rid of all folders, and moves it all in the global folder to speed up parsing)

READ THIS HEADS UP!!
Anything used by a difficulty will be defined in the metadata.json!! I am moving from separate folders and metadata.jsons and object.jsons to a singular metadata.json to keep parsing fast, and making objects BINARY!! BoooOOoooo... (.rhyo) Don’t get scared... It will be WAAAY easier to interpret compared to SSPMv2! Just a reminder this also includes events, so events and note objects will be in the same file! (For example BPM events, modchart events such as moving notes around, etc etc.)

-Designed solely by fog! :)

.rhym/Map ID/Folder Name Example

Example #1
121 - Haxagon_Example_こんにちは/

Example #2
MapID - MapCreator_Artist_Title/

Folder Format Example (Default/Barebones)

1 - fog_Example_こんにちは1/
- cover.png
- song.mp3
- metadata.json
- TASUKETE.rhyo

Folder Format Example #2

121 - Haxagon_Example_こんにちは/
- assets/
- cover.png
- differentSong.mp3
- example_konnichiwa.mp3
- metadata.json
- Hard.rhyo
- Insane.rhyo
- DifferentSong.rhyo

Folder Format Example #3

13 - fogsaturate_Example_こんにちは2/
- Hard/
- hard.rhyo
- Insane/
- insane.json
- crazier_cover_to_show_this_diff_is_harder!!!.png
- Artist_DifferentSong/
- differentSong.mp3
- DifferentSong.json
- Differentvideo.mp4
- differentcover.png
- example_konnichiwa.mp3
- metadata.json
- globalcover.png
- globalvideo.mp4

metadata.json Key Legend

// ANYTHING WITH A ? NEXT TO THE TYPE IS OPTIONAL
// GLOBAL SECTION
version: int

artist: string
romanizedArtist: string? // romanized = multi-language characters into english alphabet
title: string
romanizedTitle: string?

colorset: string[]?
coverPath: string?
audioPath: string
videoPath: string?

difficulties: difficulty[]

// DIFFICULTY SECTION
// ANYTHING THAT IS THE SAME AS GLOBAL IS OPTIONAL,
// AND WILL DEFAULT TO GLOBAL IF NOT OVERWRITTEN!!!

rhyoPath: string
difficultyName: string
mappers: mapper[] // mapper: string, id: int?

artist: string?
romanizedArtist: string?
title: string?
romanizedTitle: string?

colorset: string[]?
coverPath: string?
audioPath: string?
videoPath: string?
Mapset's metadata.json Example #1 (barebones example)
key
value
version
1
artist
“テスト”
romanizedArtist
“Tesuto”
title
“こんにちは”
romanizedTitle
“Konnichiwa”
colorset
[“0000ff”]
coverPath
“cover.png”
audioPath
”audio.mp3"
videoPath
“video.mp3”
difficulties
[{"rhyoPath":"TASUKETE.rhyo","difficultyName":"TASUKETE?!?","mappers":[{"name":"fogsaturate","id":1}]}]
There are no rows in this table
{
"version":1,
"artist":"テスト",
"romanizedArtist":"Tesuto",
"title":"こんにちは",
"romanizedTitle":"Konnichiwa",
"colorset": ["0000ff"], // this can optionally have a #, for example ["#0000FF"]
"coverPath": "cover.png",
"audioPath": "audio.mp3",
"videoPath": "video.mp4",
"difficulties":[
{
"rhyoPath": "TASUKETE.rhyo",
"difficultyName": "TASUKETE?!?",
"mappers": [{ "name": "fogsaturate", "id": 1 }]
}
],
}

.rhyo Documentation (STILL MASSIVELY W.I.P)

This format is written in binary, which is done to conserve as MUCH file space as possible.
This couldn’t have done without the help of kermeet, so thanks to him for assisting me with this!

Order of .rhyo:
General Object Metadata → Event List → Note Object List

Let’s get started!
General Object Metadata
Name
Bit Mask
Notes
Magic Number
4 Bytes (0x7268796F)
rhyo in Hexadecimal (all lowercase)
.rhyo Version
1 Byte (0x00)
Version mismatch checking with .json version
Total Event Count
4 Bytes (0x00000000)
There can be modcharts, massively changing BPM events, even lyrics support which can lead to a high event list count
Total Note Count
4 Bytes (0x00000000)
Self Explanatory
Millisecond of Last Note
4 Bytes (0x00000000)
For calculating last second of map
There are no rows in this table

Event List

To be written

Note Object List

This format will be slightly hard to understand, but it will all piece together. I will provide numerous explanations at the end
We will be manipulating a SINGULAR BYTE to define a note. So in other words, we will be using bit masks to compress as much information for a note as possible!
Before we get into it, I will be using the word “nibble” a lot. What this means is 4 bits of a byte. So take 00010101 for example. The left nibble is 0001, and the right is 0101

Just a disclaimer, this format will be USING DELTA TIME! This means the milliseconds of an object will be subtracted from the last object. If it is the first object, then it will start out at 0.
The order of notes will go like this

Flag Byte/Note Position → Note Position Float (When applicable) → Time Delta

0000 0000
We will be focusing on the left 4 bits for the flag section, or in other words, the Left Nibble.

Flag Bits for Left Nibble (1111 0000)
Name
Bit Mask
Notes
Time Bit
0001 0000
This decides whether the time byte is 2 bytes or 4 bytes. (Unsigned Integer). If 0, then 2 bytes, and if 1 then 4 bytes.
Reserved
0010 0000
Reserved
0100 0000
Reserved
1000 0000
There are no rows in this table
Bits for Right Nibble (0000 1111)
Name
Bit Mask
X position bits
0000 0011
Y position bits
0000 1100
There are no rows in this table

Right Nibble Key Legend

00
Bottom, or left of the grid. So -1 on a grid plane
01
Middle of the grid. So 0 on a grid plane
10
Top, or right of the grid. So 1 on a grid plane
11
Quantum Flag. Will be assigned a signed 2 byte float.
There are no rows in this table

Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.