1
0
mirror of synced 2024-11-13 18:20:51 +01:00

Allow decimal numbers to be expressed as strings

Fixes #24
This commit is contained in:
Stepland 2021-12-16 19:56:37 +01:00
parent 06d23501ca
commit 514c01dc3f
21 changed files with 232 additions and 24 deletions

View File

@ -54,11 +54,14 @@ Contains information that applies to the whole set of charts
- 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, required
- number or string, required
- Song tempo in Beats per Minute.
- Striclty positive
- Strings allowed for easier decimal representation preservation
- **offset**
- number, required
- 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
@ -76,11 +79,15 @@ Contains information that applies to the whole set of charts
Describes the part of the music file that's meant to be played on loop when previewing this song at the music select screen
- **start**
- number, required
- number or string, required
- In seconds, start of the loop
- Positive
- Strings allowed for easier decimal representation preservation
- **duration**
- number, required
- number or string, required
- In seconds, duration of the loop
- Strictly positive
- Strings allowed for easier decimal representation preservation
(data)=
## Data
@ -165,7 +172,7 @@ A classic note.
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 of a beat
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 :

View File

@ -103,19 +103,13 @@ 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 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 :
Be careful that the serialized values in the resulting file must still be *number litterals*, not strings.
- **Good**
- **Valid**
```json
{ "BPM" : 195.3 }
```
- **Bad**
- **Valid since 1.0.0**
```json
{ "BPM": "195.3" }
```
This is not that easy. Python's standard module `json`, for instance, allows *deserializing* (reading) numbers in a json file as `decimal.Decimal` instances, but has no easy way of *serializing* (writing) `decimal.Decimal` instances as a json number litteral that respects the original representation.
A possible solution is to use the [simplejson](https://pypi.org/project/simplejson/) module as a near drop-in replacement for `json` and use the `use_decimal` options
```

View File

@ -30,12 +30,11 @@
},
"BPM": {
"description": "Song tempo in Beats per Minute",
"type": "number",
"exclusiveMinimum": 0
"$ref": "#/$defs/strictlyPositiveDecimal"
},
"offset": {
"description": "In seconds, opposite of the time position of the first beat in the music file",
"type": "number"
"$ref": "#/$defs/decimal"
},
"preview": {
"description": "Describes the part of the music file that's to be played on loop when previewing this song",
@ -45,13 +44,11 @@
"properties": {
"start": {
"description": "In seconds, Time at which preview should start",
"type": "number",
"minimum": 0
"$ref": "#/$defs/positiveDecimal"
},
"duration": {
"description": "In seconds, for how long should the preview be played past the starting point",
"type": "number",
"minimum": 0
"$ref": "#/$defs/strictlyPositiveDecimal"
}
},
"required": ["start", "duration"]
@ -180,6 +177,40 @@
}
}
]
},
"decimal": {
"description": "a decimal number as either a number literal or a string",
"oneOf": [{
"type": "number"
},
{
"type": "string",
"pattern": "^-?\\d+(\\.\\d+)?$"
}
]
},
"positiveDecimal": {
"description": "a decimal number as either a number literal or a string",
"oneOf": [{
"type": "number"
},
{
"type": "string",
"pattern": "^\\d+(\\.\\d+)?$"
}
]
},
"strictlyPositiveDecimal": {
"description": "a strictly positive decimal number as either a number literal or a string",
"oneOf": [{
"type": "number",
"exclusiveMinimum": 0
},
{
"type": "string",
"pattern": "^(0\\.\\d*[1-9]\\d*|\\d*[1-9]\\d*(\\.\\d+)?)$"
}
]
}
}
}

View File

@ -7,7 +7,7 @@
"offset": 0,
"preview": {
"start": 0,
"duration": 0
"duration": 1
}
},
"data": {}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": -1,
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "-1",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,14 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": "0",
"duration": "0.000"
}
},
"data": {}
}

View File

@ -0,0 +1,14 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": "0",
"duration": "-10.000"
}
},
"data": {}
}

View File

@ -0,0 +1,14 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": "0",
"duration": "0"
}
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0.000",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 0,
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0.1",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "123.456",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": "-0.24"
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": "12.34"
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 125,
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "123",
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": 0
},
"data": {}
}

View File

@ -0,0 +1,14 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": "8346.9346",
"duration": "35.936"
}
},
"data": {}
}

View File

@ -7,7 +7,7 @@ from jschon.jsonschema import Scope
from jschon import create_catalog, JSONSchema, JSON
import pytest
_catalog = create_catalog('2020-12')
_catalog = create_catalog("2020-12")
SCHEMA = JSONSchema.loadf("schema.json", catalog=_catalog)
def prettify_path(json_path: str) -> str: