1
0
mirror of synced 2024-11-14 10:37:39 +01:00

Add docs for timing objects, Fixes #25

This commit is contained in:
Stepland 2021-12-20 01:45:25 +01:00
parent 5c1540fcdc
commit 3df99079d4
5 changed files with 200 additions and 103 deletions

View File

@ -1,3 +1,3 @@
Sphinx==3.3.1
sphinx-rtd-theme==1.0.0
myst-parser==0.15.2
Sphinx~=3.3
sphinx-rtd-theme~=1.0
myst-parser~=0.15

View File

@ -30,7 +30,6 @@ release = '1.0.0'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autosectionlabel',
'sphinx_rtd_theme',
'myst_parser'
]
@ -69,3 +68,7 @@ html_theme_options = {
'logo_only': False,
'collapse_navigation': False,
}
# Turn on Auto-generated header anchors so myst *actually* checks "doc.md#header" links for validity
# https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#auto-generated-header-anchors
myst_heading_anchors = 2

View File

@ -11,5 +11,5 @@ memon
.. toctree::
schema
validation
other-things
changelog

View File

@ -1,6 +1,8 @@
# Validation
# Other things to look out for
This page explains a few things to look out for when reading/writing memon files.
[JSON Schema](https://json-schema.org/) is great but by itself, a schema can't describe *everything* that makes a memon file "valid". The json schema file given in the github repository is meant to serve as a *reference* rather than a direct implementation, so some bits are left to be handled by the people adding memon compatibility into their software.
So, here are a few things to look out for or to keep in mind when reading or writing memon files :
## Notes
@ -25,9 +27,6 @@ All the numbers are within the intervals defined in the schema, however this wou
Notice the tail starting outside the screen.
Parsers should **reject** such notes
### Uniqueness
The schema itself does not prevent the following cases from happening in the array of notes that make up the chart :
@ -48,9 +47,6 @@ The schema itself does not prevent the following cases from happening in the arr
]
```
Parsers should only keep one note for each `(n, t)` couple
### Hitbox Overlap
*(This describes **hitbox** overlap, for marker animation overlap see {ref}`marker-animation-overlap`)*
@ -91,8 +87,6 @@ Here, two long notes overlap on the same square
To clarify, a long note *lasts* for the amount of ticks specified by its `L` key, this means there **cannot** be another note on the same square from `T` to `T+L`, inclusive.
Parsers should **reject** charts with these kinds of overlapping notes.
(marker-animation-overlap)=
### Marker Animation Overlap
@ -103,13 +97,62 @@ Since this is mostly an implicit rule that's not strictly followed by official c
## Decimal values
If possible, non-integer values like `BPM` or `offset` should be manipulated using a [Decimal Data Type](https://en.wikipedia.org/wiki/Decimal_data_type) to preserve their original decimal representation. I think no one likes to see the BPM they defined as a clean `195.3` in the editor be stored as a messy `195.3000030517578125` in the file. If doing this is too hard with your language / library of choice, keep in mind that `1.0.0` allows strings instead :
If possible, non-integer values like `bpm` or `offset` should be manipulated using a [Decimal Data Type](https://en.wikipedia.org/wiki/Decimal_data_type) to preserve their original decimal representation. I think no one likes to see the BPM they defined as a clean `195.3` in the editor be stored as a messy `195.3000030517578125` in the file. If doing this is too hard with your language / library of choice, keep in mind that `1.0.0` allows strings instead :
- **Valid**
```json
{ "BPM" : 195.3 }
```
```json
{ "bpm" : 195.3 }
```
- **Valid since 1.0.0**
```json
{ "BPM": "195.3" }
```
```json
{ "bpm": "195.3" }
```
## Multiple timing objects
memon version 1.0.0 introduced [timing objects](schema.md#timing) in two different places in the file, either at the root or in a chart.
All the keys in a timing object are optional, this is to allow for a chart to only redefine what is different from the default timing object.
In other words the timing object at the root acts as a fallback.
In general, when deciding what timing information applies to a given chart, timing objects should be searched in that order *for every key* :
- Chart-specific timing object
- Root timing object
- Default values defined by the schema
For instance in the following file :
```json
{
"version": "1.0.0",
"timing": {
"offset": 0.84
},
"data": {
"BSC": {
"timing": {
"bpms": [{"beat": 0, "bpm": 200}]
},
"notes": []
},
"ADV": {
"timing": {
"offset": 0.31,
"bpms": [{"beat": 0, "bpm": 100}]
},
"notes": []
},
}
}
```
Neither `BSC` nor `ADV` define `resolution` in their timing info so the implicit default is used instead for both charts.
`BSC` defines no chart-specific `offset` so it uses the value `0.84` from the timing object at the root of the file instead.
`ADV` defines its own `offset` so it gets used instead of all the others

View File

@ -8,6 +8,7 @@ This page gives a top-down view of how a memon file is structured
{
"version": "x.y.z",
"metadata": {},
"timing": {},
"data": {}
}
```
@ -19,14 +20,17 @@ It's a json object with the following keys :
- string, required
- Indicates the schema version this memon file uses, follows [semver](https://semver.org/). If a memon file does not have this key it's probably following the pre-semver format
- **metadata**
- object, required
- Contains the {ref}`metadata object <metadata>`
- object, optional
- Contains the [metadata object](#metadata)
- **timing**
- object, optional
- Contains the default / fallback [timing object](#timing)
- See [](other-things.md#multiple-timing-objects) for more details on the behavior this should have
- **data**
- object, required
- Contains the {ref}`data object <data>`
- Contains the [data object](#data)
(metadata)=
## Metadata
```json
@ -35,38 +39,27 @@ It's a json object with the following keys :
"artist": "",
"audio": "",
"jacket": "",
"BPM": 120.0,
"offset": 0.0,
"preview": {},
}
```
Contains information that applies to the whole set of charts
- **title**
- string, required
- string, optional
- Song title
- **artist**
- string, required
- string, optional
- **audio**
- string, optional
- Path to the music file, *relative* to the memon file.
- **jacket**
- string, optional
- Relative path to the jacket / album cover / album art to be shown in music select for example. usually a square image.
- **BPM**
- number or string, required
- Song tempo in Beats per Minute.
- Striclty positive
- Strings allowed for easier decimal representation preservation
- **offset**
- number or string, required
- In seconds, opposite of the time position of the first beat in the music file. For instance, if the first beat occurs at 0.15 seconds in the audio file, `offset̀` should be -0.15
- Strings allowed for easier decimal representation preservation
- **preview**
- object or string, optional
- Contains either a {ref}`preview object <preview>` or a path to a bms-style preview file
- Contains either a [preview object](#preview) or a path to a bms-style preview file
(preview)=
## Preview
```json
@ -89,7 +82,54 @@ Describes the part of the music file that's meant to be played on loop when prev
- Strictly positive
- Strings allowed for easier decimal representation preservation
(data)=
## Timing
```json
{
"offset": 0.0,
"resolution": 1,
"bpms": []
}
```
Describes the relationship between seconds in the audio file and symbolic time (time measured in beats)
- **offset**
- number or string, optional
- In seconds, time at which the first beat occurs in the music file.
For instance, if the first beat occurs at 0.15 seconds in the audio file, the offset should be the number literal `0.15`, or the string `"0.15"` if the tools used can't keep a clean decimal representation when using json number literals.
- **resolution**
- integer, optional
- Greater than 0, always an integer
- Number of ticks in a beat for the bpm events defined in this timing object, if some bpm events define a beat using a single integer, this is the implicit fraction denominator to use to convert the integer number of ticks to a fractional number of beats.
- **bpms**
- array, optional
- Array of [BPM events](#bpm)
## BPM
```json
{
"beat": 0,
"bpm": 120
}
```
Defines a change in tempo measured in beats per minutes happening at a specific symbolic time (measured in beats)
- **beat**
- [symbolic time](#symbolic-time), required
- Beat at which the tempo changes
- **bpm**
- number or string
- Song tempo at the given beat, in Beats per Minute.
- Striclty positive
- Strings allowed for easier decimal representation preservation
## Data
```json
@ -101,7 +141,7 @@ Describes the part of the music file that's meant to be played on loop when prev
}
```
The data object maps difficulty names to {ref}`chart objects <chart>`.
The data object maps difficulty names to [chart objects](#chart).
Keys in this object are not fixed, they can be any string.
@ -111,29 +151,34 @@ When sorting, difficulties may be presented in that order :
BSC ➔ ADV ➔ EXT ➔ (everything else in alphabetical order)
(chart)=
## Chart
```json
{
"level": 10.3,
"resolution": 240,
"notes" : []
"timing": {},
"notes": []
}
```
- **level**
- number, required
- Chart level, can be an integer or a decimal value
- number or string, optional
- Chart level, can be an integer or a decimal value, or even a string holding a decimal number
- **resolution**
- number, required
- integer, optional
- Greater than 0, always an integer
- Number of ticks in a beat, denominator of all beat fractions. Usually 240
- Number of ticks in a beat for all the notes in the chart, see [](#symbolic-time)
- **timing**
- [Timing object](#timing), optional
- Chart-specific timing information
- See [](other-things.md#multiple-timing-objects) for more details on the behavior this should have
- **notes**
- array, required
- Array of {ref}`tap notes <tap-note>` and {ref}`long notes <long-note>` that make up the chart
- Array of [tap notes](#tap-note) and [long notes](#long-note) that make up the chart
(tap-note)=
## Tap Note
```json
@ -145,15 +190,6 @@ When sorting, difficulties may be presented in that order :
A classic note.
**t** can also be defined this way :
```json
{
"n": 0,
"t": [1, 0, 1]
}
```
- **n**
- number, required
- Integer between 0 and 15 inclusive
@ -165,28 +201,10 @@ A classic note.
12 13 14 15
```
- **t**
- required
- as a number :
- Integer greater or equal to 0
- Note time in ticks.
- [Symbolic Time](#symbolic-time), required
- Beat at which the note occurs
Ticks are fractions of the beat.
The resolution defines how many ticks are in a beat for a given chart.
In other words if the resolution is 420, a tick lasts for 1/420th oLike for the BPM, sf a beat
For more info about measuring time in ticks, see [bmson's docs](https://bmson-spec.readthedocs.io/en/master/doc/index.html#terminologies) (their docs refers to ticks as *pulses*).
- as an array :
- The array MUST have length 3
- `t[0]` and `t[1]` are integers greater or equal to 0
- `t[2]` is an integer greater than 0
- Reprensents the note time in beats as a fraction, the value can be retrieved by the following computation :
```
t[0] + (t[1] / t[2])
```
For instance `[1, 2, 3]` means the note happens 2/3 of a beat after beat 1
(long-note)=
## Long Note
```json
@ -200,28 +218,13 @@ A classic note.
A classic long note, with a tail
**`l`** can also take the same 3-int tuple form as **`t`**, except it has to represent a non-zero duration :
```json
{
"n": 0,
"t": 3600,
"l": [0, 1, 10],
"p": 5
}
```
**n** and **t** are the same as in a {ref}`tap note <tap-note>`
- **n** : same as in a [tap note](#tap-note)
- **t** : same as in a [tap note](#tap-note)
- **l**
- required
- as a number :
- Integer greater than 0
- Long note duration ("l" as in length ?!), in ticks
- as an array:
- same constraints as the array-variant of **t**
- `t[0]` and `t[1]` cannot both be zero at the same time
- Long note duration as a beat fraction
- [Symbolic Time](#symbolic-time), required
- If a number, strictly positive
- If an array, `l[0]` and `l[1]` cannot both be zero at the same time
- Long note duration in ticks or beats
- **p**
- number, required
- Integer between 0 and 11 inclusive
@ -250,3 +253,51 @@ A classic long note, with a tail
|
```
## Symbolic Time
Either an integer :
```json
0
```
or an array :
```json
[0, 2, 3]
```
Represents a time point (or duration) measured in *beats*
### As a number
- integer greater or equal to 0
- Time measured in *ticks* :
Ticks are fractions of the beat.
Using ticks implies a *resolution* is defined somewhere else in the file.
The resolution defines how many ticks are in a beat.
In other words if the resolution is 240, a tick lasts for 1/240th of a beat.
For more info about measuring time in ticks, see [bmson's docs](https://bmson-spec.readthedocs.io/en/master/doc/index.html#terminologies) (their docs refers to ticks as *pulses*).
### As an array
- The array **MUST** have length 3
- The first and second elements are integers greater or equal to 0
- The third element is an integer greater than 0
- The array reprensents a time in beats as a [mixed number](https://en.wikipedia.org/wiki/Fraction#Mixed_numbers)
If `a` is the array in question and we use the bracket notation for array access, the value represented by the array is the following :
```
a[0] + (a[1] / a[2])
```
For instance `[1, 2, 3]` represents 1 + 2/3, `[0, 1, 20]` represents 0 + 1/20.
As it currently is, the schema allows for improper fractions (0 + 5/1) and non-reduced fractions (0 + 2/4)