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

Replace metadata.BPM and metadata.offset with default/fallback + per-chart timing info objects that support bpm changes, Fixes #7

Flip the sign of the offset, Fixes #1
Allow data.<chart>.level to be a decimal number
Relax the requirements for some properties in charts and the root object
Rename some internal definitions in the schema
Sort test cases by file name
This commit is contained in:
Stepland 2021-12-19 03:42:10 +01:00
parent 514c01dc3f
commit 5c1540fcdc
37 changed files with 252 additions and 198 deletions

View File

@ -11,6 +11,7 @@
"metadata": {
"description": "Contains information that applies to the whole set of charts",
"type": "object",
"additionalProperties": false,
"properties": {
"title": {
"description": "The title of the song",
@ -28,14 +29,6 @@
"description": "Path to the album cover, relative to the memon file",
"type": "string"
},
"BPM": {
"description": "Song tempo in Beats per Minute",
"$ref": "#/$defs/strictlyPositiveDecimal"
},
"offset": {
"description": "In seconds, opposite of the time position of the first beat in the music file",
"$ref": "#/$defs/decimal"
},
"preview": {
"description": "Describes the part of the music file that's to be played on loop when previewing this song",
"oneOf": [{
@ -59,24 +52,31 @@
}
]
}
},
"required": ["title", "artist", "BPM", "offset"]
}
},
"timing": {
"description": "default timing, applies by default to every chart in the file",
"$ref": "#/$defs/timingObject"
},
"data": {
"description": "Charts with difficulty names used as keys",
"description": "Mapping that associates difficulty names to charts",
"type": "object",
"additionalProperties": {
"description": "A chart",
"type": "object",
"properties": {
"level": {
"description": "Level rating of the chart, typically goes from 0 to 10 in jubeat",
"type": "integer"
"description": "Level rating of the chart, typically goes from 1 to 10.9 in jubeat",
"$ref": "#/$defs/positiveDecimal"
},
"resolution": {
"description": "Tempo resolution, number of \"ticks\" in a beat",
"description": "Number of ticks in a beat for the notes",
"type": "integer",
"exclusiveMinimum": 0
"minimum": 1
},
"timing": {
"description": "Chart-specific timing to be used instead of the default timing info",
"$ref": "#/$defs/timingObject"
},
"notes": {
"description": "The array of notes",
@ -92,28 +92,12 @@
"maximum": 15
},
"t": {
"description": "Note time",
"oneOf": [{
"description": "Time measured in \"ticks\" as specified with the resolution",
"type": "integer",
"minimum": 0
},
{
"$ref": "#/$defs/timeFraction"
}
]
"description": "Note time, either in ticks or as a standalone fraction",
"$ref": "#/$defs/timeInBeats"
},
"l": {
"description": "Long note duration",
"oneOf": [{
"description": "Long Note Lenght in ticks",
"type": "integer",
"minimum": 1
},
{
"$ref": "#/$defs/nonZeroTimeFraction"
}
]
"description": "Long note duration, either in ticks or as a standalone fraction",
"$ref": "#/$defs/nonZeroTimeInBeats"
},
"p": {
"description": "Tail starting position, relative to note position, counting from 0 to 11 clockwise and expanding out starting one square above the note",
@ -131,14 +115,38 @@
}
}
},
"required": ["level", "resolution", "notes"]
"required": ["notes"]
}
}
},
"required": ["version", "metadata", "data"],
"required": ["version", "data"],
"$defs": {
"timeFraction": {
"description": "Time represented as a fraction",
"timeInBeats": {
"description": "Time measured as a fraction of beats, either in ticks or as a standalone fraction",
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"$ref": "#/$defs/positiveTimeFraction"
}
]
},
"nonZeroTimeInBeats": {
"description": "Strictly positive time measured as a fraction of beats, either in ticks or as a standalone fraction",
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"$ref": "#/$defs/strictlyPositiveTimeFraction"
}
]
},
"positiveTimeFraction": {
"description": "Time in beats represented as a fraction",
"type": "array",
"minItems": 3,
"maxItems": 3,
@ -159,10 +167,10 @@
}
]
},
"nonZeroTimeFraction": {
"description": "Non-zero time represented as a fraction",
"strictlyPositiveTimeFraction": {
"description": "Non-zero time in beats represented as a fraction",
"allOf": [{
"$ref": "#/$defs/timeFraction"
"$ref": "#/$defs/positiveTimeFraction"
},
{
"not": {
@ -190,7 +198,7 @@
]
},
"positiveDecimal": {
"description": "a decimal number as either a number literal or a string",
"description": "a positive decimal number as either a number literal or a string",
"oneOf": [{
"type": "number"
},
@ -204,13 +212,47 @@
"description": "a strictly positive decimal number as either a number literal or a string",
"oneOf": [{
"type": "number",
"exclusiveMinimum": 0
"minimum": 1
},
{
"type": "string",
"pattern": "^(0\\.\\d*[1-9]\\d*|\\d*[1-9]\\d*(\\.\\d+)?)$"
}
]
},
"timingObject": {
"type": "object",
"properties": {
"offset": {
"description": "In seconds, time at which the first beat occurs in the music file",
"$ref": "#/$defs/decimal"
},
"resolution": {
"description": "Number of ticks in a beat for the bpm events",
"type": "integer",
"minimum": 1
},
"bpms": {
"description": "Array of BPM events",
"type": "array",
"minItems": 1,
"items": {
"description": "A single BPM event",
"type": "object",
"properties": {
"beat": {
"description": "Time at which the bpm changes",
"$ref": "#/$defs/timeInBeats"
},
"bpm": {
"description": "Tempo measured in Beats Per Minute",
"$ref": "#/$defs/strictlyPositiveDecimal"
}
},
"required": ["beat", "bpm"]
}
}
}
}
}
}

View File

@ -32,10 +32,10 @@ class ExamplesFolder(pytest.Collector):
class ExampleSubfolder(pytest.Collector):
def collect(self):
path = Path(self.fspath)
for file in (path / "pass").glob("*.json"):
for file in sorted((path / "pass").glob("*.json")):
yield ValidExample.from_parent(self, name=file.stem, path=file)
for file in (path / "fail").glob("*.json"):
for file in sorted((path / "fail").glob("*.json")):
yield InvalidExample.from_parent(self, name=file.stem, path=file)

View File

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

View File

@ -1,10 +1,6 @@
{
"version": "1.0.0",
"metadata": {
"song title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": []
},
"data": {}

View File

@ -1,10 +1,6 @@
{
"version": "1.0.0",
"metadata": {
"song title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"position": 0,
"duration": 0

View File

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

View File

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

View File

@ -1,10 +1,6 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": 0,
"duration": 1

View File

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

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,11 +1,5 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0
},
"data": {
"BSC": {
"level": 0,

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": -1,
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": -1
}
]
},
"data": {}
}

View File

@ -0,0 +1,12 @@
{
"version": "1.0.0",
"timing": {
"bpms": [
{
"beat": 0,
"bpm": "-1"
}
]
},
"data": {}
}

View File

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

View File

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

View File

@ -1,10 +1,6 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 1,
"offset": 0,
"preview": {
"start": "0",
"duration": "0"

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0.000",
"offset": 0
"timing": {
"bpms": [
{
"beat": "0.000",
"bpm": -1
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 0,
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": 0
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0",
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": "0"
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "0.1",
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": "0.1"
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "123.456",
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": "123.456"
}
]
},
"data": {}
}

View File

@ -1,10 +1,13 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": "-0.24"
"timing": {
"offset": "-0.24",
"bpms": [
{
"beat": 0,
"bpm": 123.456
}
]
},
"data": {}
}

View File

@ -1,10 +1,13 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": "12.34"
"timing": {
"offset": "12.34",
"bpms": [
{
"beat": 0,
"bpm": 123.456
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 125,
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": 125
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": "123",
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": "123"
}
]
},
"data": {}
}

View File

@ -1,10 +1,12 @@
{
"version": "1.0.0",
"metadata": {
"title": "",
"artist": "",
"BPM": 123.456,
"offset": 0
"timing": {
"bpms": [
{
"beat": 0,
"bpm": 123.456
}
]
},
"data": {}
}

View File

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

View File

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

View File

@ -0,0 +1,9 @@
{
"version": "1.0.0",
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"data": {}
}

View File

@ -0,0 +1,29 @@
{
"version": "1.0.0",
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"data": {
"BSC": {
"timing": {
"bpms": [{"beat": 0, "bpm": 1}]
},
"notes": []
},
"ADV": {
"timing": {
"bpms": [{"beat": 0, "bpm": 2}]
},
"notes": []
},
"EXT": {
"timing": {
"resolution": 2,
"bpms": [{"beat": 0, "bpm": 3}]
},
"notes": []
}
}
}

View File

@ -0,0 +1,34 @@
{
"version": "1.0.0",
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"data": {
"BSC": {
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"notes": []
},
"ADV": {
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"notes": []
},
"EXT": {
"timing": {
"offset": 0,
"resolution": 1,
"bpms": [{"beat": 0, "bpm": 1}]
},
"notes": []
}
}
}