-
Notifications
You must be signed in to change notification settings - Fork 0
The Maimai FiNALE Chart Format (AKA SDT)
Important
This documentation owes its initial knowledge to Donmai's blog on the SDT format and previous iterations within Maimai "classic". If you're interested in knowing what the format looked like before FiNALE or otherwise want to read more about these formats from someone else, I highly recommend looking there.
The SDT format, like other parts of old Maimai's data formats, is a tabular format bearing some resemblance to a CSV file. Each column within the file relates to a single element of the note, with some variations depending on the type of note being described. Unlike more modern peers like simai which is dynamic and uses flags to describe each note, every note contains the same number of columns for a single note. They describe the note as follows.
| Column 1 | Column 2 | Column 3 | Column 4 | Column 5 | Column 6 | Column 7 | Column 8 | Column 9 |
|---|---|---|---|---|---|---|---|---|
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
It's also worth noting that all units of time in this format are in measures, assuming a 4/4 time signature. The BPM for the chart is not stored within the file, and is instead delegated to the mmMusic table.
Unfortunately, this format is a bit unintuitive, so it's easier to understand these once shown in action. Let's look at some examples to get an idea of how it works:
1.0000, 0.1250, 0.0625, 1, 1, 0, 0, 0, 0.0000,Let's split these into the columns described before:
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 1.0000 | 0.1250 | 0.0625 (N/A) | 1 | 1 (Tap) | 0 (N/A) | 0 (N/A) | 0 (N/A) | 0.0000 (N/A) |
Luckily for us, quite a few columns here don't apply to tap notes and so can be left for now.
Caution
Putting data where the game does not expect it has remifications ranging from doing nothing at all with any reasonable value (Duration) to crashing the game when reading the file (Slide ID). If you're not sure what to put in a certain case, feel free to copy from one of the examples here (as they are all taken from charts that have been confirmed to work with the game).
Let's start with the whole and fractional measure. Both of these columns combine to denote at what point in the chart a note should be hit. An easier way of reading these values is that the note starts at 1.1250 measures into the chart (AKA Whole Measure + Fractional Measure). While combining the values would have made for a simpler format, these values must remain separate and only show their respective parts of the measure (i.e whole measure must always represent an integer value, and fractional measure must satisfy 0 <= x < 1).
The location column represents where along the 8 outer zones the note should be positioned. This value is 0 indexed, and so ranges from 0-7 instead of the sensor's labels as A1-A8. In this example, the note would be successfully hit by sensor A2.
The note type refers to an enum of note types found within all charts, which can be found here.
Break notes are almost identical to taps, but offer extra score for the player and a critical perfect window within the perfect timing window for an extra 100 score (from 2500-2550-2600). This also applies to break stars. Let's have a look at a standard break for now:
13.0000, 0.0000, 0.0625, 0, 3, 0, 0, 0, 0.0000,| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 13.0000 | 0.0000 | 0.0625 (N/A) | 0 | 3 (Break) | 0 (N/A) | 0 (N/A) | 0 (N/A) | 0.0000 (N/A) |
As previously discussed with tap notes, we can see that the note would be hit by sensor A1 and is expected to be hit at exactly 13 measures into the chart. As with normal tap notes, the other columns are not necessary for the game to place the note and can be ignored/filled with default values.
When 2 taps are to be hit at the same timing, they are automatically converted into a 'both' note (name courtesy of SEGA), which recolours the notes as yellow instead of their normal pink. The same applies for holds and slides, but slides are a bit more complicated. Here's a simple example:
14.0000, 0.7500, 0.0625, 1, 1, 0, 0, 0, 0.0000,
14.0000, 0.7500, 0.0625, 4, 1, 0, 0, 0, 0.0000,Tap 1
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 14.0000 | 0.7500 | 0.0625 (N/A) | 1 | 1 (Tap) | 0 (N/A) | 0 (N/A) | 0 (N/A) | 0.0000 (N/A) |
Tap 2
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 14.0000 | 0.7500 | 0.0625 (N/A) | 4 | 1 (Tap) | 0 (N/A) | 0 (N/A) | 0 (N/A) | 0.0000 (N/A) |
As you can see, these notes are no different from a normal tap note. Since the game handles boths for us without us intervening, we have no need to worry about anything to keep things consistent.
Hold notes are mostly the same as taps, but now use the duration column in addition to the others used by the tap. Let's break one down and have a look:
2.0000, 0.0000, 0.3750, 0, 2, 0, 0, 0, 0.0000,| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 2.0000 | 0.0000 | 0.3750 | 0 | 2 (Hold) | 0 (N/A) | 0 (N/A) | 0 (N/A) | 0.0000 (N/A) |
The initial timing of the hold can be treated identically to taps - in this case, the note expects to start at sensor A1 at exactly 2 measures into the chart. After the player has started the hold, the note expects to be held for the amount of time specified in Duration. In this example, after the button or screen is held, the player should keep holding for 0.3750 measures before letting go (at 2.3750 measures into the chart).
Note
Unlike DX, The hold note will strictly judge both the start and end of the note, meaning that holding for too long or short of the end of the note will cause worse judgement in the same manner as hitting the start of the hold too early or late.
Slide notes are much more complicated than the other notes in the game, and are treated vastly different from other formats like simai. While simai keeps its flag system to separate slide taps from their screen moving counterparts, SDT treats slide notes as 3 distinct components - one component to denote the initial tap similar to normal taps and breaks, and a pair of components to denote where a slide starts and ends, along with some extra data in the slide start to specify pattern, delay and duration.
While slide stars are mostly similar to other tap types, slide stars use the duration column in a very unique way. Unlike normal tap notes, Duration is used for these notes and so needs to be taken into account. Specifically, it is proportional to the number of rotations a star does per measure. Unfortunately it isn't 1:1, but it is consistent for a specified BPM. It is also worth noting that while the rotation speed can be controlled by the duration, there is a cap to how fast the star will spin (which I have not calculated specifically, but exists regardless of BPM).
In vanilla charts, star spin speed tends to relate to how fast the player needs to move their hand to complete the slide. This means that the final value for the duration is proportional both to the duration of the slide (minus the delay) and the distance the slide covers. The speed is also identical between break and non-break variants of the star when given the same duration value.
Let's use some examples to show how it affects the note.
4.0000, 0.5000, 0.5000, 2, 4, 0, 0, 0, 0.0000,| Whole Measure | Fractional Measure | Duration (Rotation Time) | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 4.0000 | 0.5000 | 0.5000 | 2 | 4 (Star) | 0 (N/A) | 0 (N/A) | 0 | 0.0000 (N/A) |
As we can see above, the star has no relation to a slide that it is usually coupled with. This is why Utage charts are able to use stars as taps on their own, or have slides with no initial star (since they are internally entirely independant of one another). As well as this, the star is the only note to use the Slide Amount column. This denotes how many on-screen slide are going to "come from" the star, and as a result changes the sprite used to represent this (with versions where stars overlap each other). This allows for stars to show that they are for multiple slides while remaining as one note, which would make a chart otherwise impossible as they would be required to be hit separately.
Warning
While incorrectly declaring the slide amount for a star does not result in a crash, the result can lead to very ambiguous charts that are difficult to read.
Note
A star note with a Slide Amount of 0 looks identical to one with a Slide Amount of 1.
From my own calculations, BPM creates a variable constant that is applied to the duration to calculate how fast the star spins. For example, in a chart at 172 BPM a star set with a duration of 1.0000, rotates at a speed of 2.7222 seconds/rotation or 1.95099421 measures/rotation. Meanwhile, a chart at 90 BPM will have stars with 1.0000 duration spin at 4.75sec/rotation or 1.78102737 measures/rotation. While this constant does change relative to BPM, other duration values consistently scale relative to this constant (i.e a star with duration of 0.5000 measures always spins at double the speed of one at 1.0000 measures, given the speed cap isn't hit).
An example of how duration affects star spin speed can be seen below:
The on-screen component of a slide is split into 2 notes, a slide start and slide end. The slide start controls the majority of the things the slide will have (such as pattern, duration and delay), and the slide end matches those properties while also specifying where the final location of the slide will be.
Caution
Both components of the slide must be present and correct for a slide to work as intended. Failing to do so is very common grounds for the game to crash when attempting to load the chart.
Let's see an example with both components together:
6.0000, 0.0000, 0.3750, 2, 0, 1, 1, 0, 0.2500,
6.0000, 0.3750, 0.0000, 7, 128, 1, 1, 0, 0.0000,Slide Start
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 6.0000 | 0.0000 | 0.3750 | 2 | 0 (Slide Start) | 1 | 1 (Straight Line) | 0 (N/A) | 0.2500 |
Slide End
| Whole Measure | Fractional Measure | Duration | Location | Note Type | Slide ID | Slide Pattern | Slide Amount | Slide Delay |
|---|---|---|---|---|---|---|---|---|
| 6.0000 | 0.3750 | 0.0000 (N/A) | 7 | 128 (Slide End) | 1 | 1 (Straight Line) | 0 (N/A) | 0.0000 (N/A) |
In this example, the notes describe a slide that moves in a straight line from A3-A8, appearing at 6 measures into the chart and expecting to reach its end at 6.3750 measures into the chart.
It is important to note is how some elements between the two must match. Firstly, the time of the slide end must match the start of the slide start + its duration, as well the slide IDs and patterns being identical between the two. Failing to match these elements will cause a crash as mentioned before.
Another important detail is how the slide's movement is calculated. While a duration is included with the slide start, this duration includes the specified delay within itself. This means that the duration of time that the slide should be moving for is actually Duration - Slide Delay (in this case, 0.3750 - 0.2500 = 0.1250.
Like taps, slides can be merged into a both, which changes the colour of the slide arrow from blue to yellow to signify that they will start at the same time. Unfortunately, the implementation of boths means that the only parameter of the notes that is taken into account is when it starts - or more specifically the time that the slide start note appears. Because of this, slides can be marked as both in a way that makes for a very poor experience for the player.
I'm sure players would love to try to read this!
Warning
Most charters should avoid attempting to use this as the feature of a chart, as it is incredibly easy to create charts that are unintuitive and difficult to read.
In this example, both slides begin at 1 measure, but have differing delays and durations, causing a desync between the two.
1.0000, 0.0000, 0.0625, 7, 4, 0, 0, 1, 0.0000,
1.0000, 0.0000, 0.2500, 7, 0, 1, 1, 0, 0.2500,
1.0000, 0.0000, 0.5000, 7, 0, 2, 1, 0, 0.5000,
1.0000, 0.5000, 0.0000, 4, 128, 1, 1, 0, 0.0000,
2.0000, 0.0000, 0.0000, 2, 128, 2, 1, 0, 0.0000,Important
This section will cover the details of the requirements of the format outside of the definitions of each component within the file. Look here if you're interested in building an implementation for a parser, converter, SDT generator or something of similar description.
Unlike some other files within FiNALE, it is expected for these files to be encoded as ASCII. If your editor doesn't have easy access to ASCII as a character set (such as Notepad++), using UTF-8 should provide the same binary output given that you don't use any characters from outside the ASCII range.
Caution
While tables tend to be lenient on their encoding as long as they are a Unicode derivative (since only some tables use kanji that require the UTF-16 character set), the game doesn't check chart files anywhere near as thoroughly, and can crash if it finds something it doesn't expect (this extends to malformed notes).
In SDT files, all decimal values must be 4 d.p long, regardless of if all digits are actually used. As a result, many rows will include many entries of 0.0000 - this is expected.
Caution
Ignoring this stipulation will result in a crash.
As has been shown in every snippet up to this point, each row has a trailing comma after the last column's value, even though there is no value to follow. This is expected of the format and required, so ensure it is added in any SDT output.
Caution
Ignoring this stipulation will result in a crash.
Whitespace is ignored by FiNALE, to any degree. For example, Maimai is completely happy to use this chart (which is a snippet of one of the earlier test charts when initially investigating star spin speed as a dual test for spacing):
...
4.0000, 0.0000, 0.5000, 2, 4, 0, 0, 0, 0.0000,
4.0000, 0.5000, 0.5000, 2, 4, 0, 0, 0, 0.0000,
5.0000,0.0000,0.2500,2,4,0,0,0,0.0000,
5.0000, 0.5000, 0.2500, 2, 4, 0, 0, 0, 0.0000,
6.0000, 0.0000, 0.2500, 2, 4, 0, 0, 0, 0.0000,
...As you can see, both arbitrary amounts of spacing between columns, no spacing between columns and extra newlines between rows are all fair game in an SDT chart.
Tip
If you're planning to build an SDT generator, I'd highly recommend adding support for aligned columns to make your plain charts easier to read in case of debugging.
For example, SenDT at time of writing left justifies its columns to keep all spacing the same across the whole chart:
...
1.0000, 0.8569, 0.0625, 6, 4, 0, 0, 1, 0.0000,
1.0000, 0.8569, 0.1875, 6, 0, 1, 1, 0, 0.2500,
2.0000, 0.0444, 0.0625, 7, 1, 0, 0, 0, 0.0000,
2.0000, 0.1069, 0.0625, 6, 1, 0, 0, 0, 0.0000,
2.0000, 0.2319, 0.0625, 0, 1, 0, 0, 0, 0.0000,
2.0000, 0.2944, 0.0000, 2, 128, 1, 1, 0, 0.0000,
...
96.0000, 0.1073, 0.0000, 3, 128, 185, 3, 0, 0.0000,
96.0000, 0.3569, 0.0625, 0, 1, 0, 0, 0, 0.0000,
96.0000, 0.3573, 0.0000, 7, 128, 186, 1, 0, 0.0000,
96.0000, 0.7319, 0.0000, 0, 128, 187, 2, 0, 0.0000,
96.0000, 0.8569, 0.0625, 6, 1, 0, 0, 0, 0.0000,
97.0000, 0.0444, 0.1875, 5, 2, 0, 0, 0, 0.0000,
97.0000, 0.1069, 0.1250, 2, 2, 0, 0, 0, 0.0000,
97.0000, 0.3569, 0.2500, 6, 2, 0, 0, 0, 0.0000,
97.0000, 0.3569, 0.2500, 1, 2, 0, 0, 0, 0.0000,
97.0000, 0.8569, 0.4997, 7, 2, 0, 0, 0, 0.0000,
98.0000, 0.1069, 0.2500, 0, 2, 0, 0, 0, 0.0000,
...SEGA-made charts instead attempt to centre-align, which I always found interesting:
...
2.0000, 0.0000, 0.3750, 0, 2, 0, 0, 0, 0.0000,
2.0000, 0.0000, 0.3750, 4, 2, 0, 0, 0, 0.0000,
2.0000, 0.5000, 0.3750, 3, 2, 0, 0, 0, 0.0000,
2.0000, 0.5000, 0.3750, 6, 2, 0, 0, 0, 0.0000,
3.0000, 0.0000, 0.3750, 1, 2, 0, 0, 0, 0.0000,
3.0000, 0.0000, 0.3750, 4, 2, 0, 0, 0, 0.0000,
...
9.0000, 0.0000, 0.6250, 5, 0, 6, 11, 0, 0.2500,
9.0000, 0.6250, 0.0000, 0, 128, 5, 12, 0, 0.0000,
9.0000, 0.6250, 0.0000, 7, 128, 6, 11, 0, 0.0000,
10.0000, 0.0000, 0.9531, 7, 4, 0, 0, 1, 0.0000,
10.0000, 0.0000, 1.7500, 0, 2, 0, 0, 0, 0.0000,
10.0000, 0.0000, 3.5000, 7, 0, 7, 3, 0, 0.2500,
12.0000, 0.0000, 0.0625, 0, 3, 0, 0, 0, 0.0000,
12.0000, 0.5000, 0.0625, 1, 3, 0, 0, 0, 0.0000,
13.0000, 0.0000, 0.0625, 0, 3, 0, 0, 0, 0.0000,
13.0000, 0.2500, 0.0625, 1, 3, 0, 0, 0, 0.0000,
13.0000, 0.5000, 0.0000, 7, 128, 7, 3, 0, 0.0000,
13.0000, 0.5000, 0.0625, 2, 3, 0, 0, 0, 0.0000,
...
91.0000, 0.5000, 0.0625, 6, 1, 0, 0, 0, 0.0000,
91.0000, 0.7500, 0.0625, 5, 1, 0, 0, 0, 0.0000,
92.0000, 0.0000, 0.0000, 0, 128, 109, 5, 0, 0.0000,
92.0000, 0.0000, 0.0977, 4, 4, 0, 0, 1, 0.0000,
92.0000, 0.0000, 0.3750, 4, 0, 110, 3, 0, 0.2500,
92.0000, 0.2500, 0.0625, 2, 1, 0, 0, 0, 0.0000,
92.0000, 0.3750, 0.0000, 7, 128, 110, 3, 0, 0.0000,
92.0000, 0.5000, 0.0977, 3, 4, 0, 0, 1, 0.0000,
92.0000, 0.5000, 0.3750, 3, 0, 111, 2, 0, 0.2500,
92.0000, 0.7500, 0.0625, 5, 1, 0, 0, 0, 0.0000,
92.0000, 0.8750, 0.0000, 0, 128, 111, 2, 0, 0.0000,
...Because of the way the alignment is implemented, the column widths end up fluctuating quite a lot. Since debugging is a high possibility given a chart format that might still not be fully understood and many community members making charts, I felt it better to choose consistency over absolute faith to original formatting.
Newlines are canonically CR LF, but LF is confirmed to also cause no issues when used within charts. Since FiNALE is expecting to run on windows, I would personally recommend CR LF for consistency.
SDT files are read such that the start location of the notes are expected to be ordered. While this expectation is required, there is no specification for ordering within notes that have the same start time (and has not caused issues during testing when left without any such sorting).
Caution
Failing to order notes by start time may result in undefined behaviour, including but not limited to crashes.
While slide IDs follow the convention of incrementing from 1 to any desired upper bound for each new pair of slide start and end, this is not a requirement of the format. Charts will remain unchanged as long as all slide start/end pairs share the same ID.
Note
For ease of debugging and consitency, it is still highly recommended to use IDs incrementing from 1.
Slide IDs must be unique, or will otherwise make amalgamations of different slides that will ultimately ruin any intended pattern. If done with 2 slides of the same location and pattern, the slide will instead start at the beginning of the first slide as intended, but have its judgement end be taken from the later slide. The later slide between the two will also be "consumed", leaving only the slide tap with no on-screen follow-up. In this example, all slides were expected to be of the same speed and delay, but the 1st and 5th pair had matching IDs, and so are malformed:
Caution
This is the best case scenario for duplicate slide IDs. Mixing this with other unmatched data such as slide patterns or locations may result in a crash.
| Value | Note Type |
|---|---|
| 0 | Start Slide |
| 1 | Tap |
| 2 | Hold |
| 3 | Break |
| 4 | Star (AKA tap for slides) |
| 5 | Break Star |
| 128 | End Slide |
Important
These examples have been shamelessly copied from Donmai's blog.

Warning
Placing the end location of the slide less than 2 "zones" away from the start will cause the slide to default to 2 zones counter-clockwise.





Note
The end location for this pattern should be on the opposite side of the start location.

Warning
The end location for this pattern must be on the opposite side of the start location. Otherwise, the slide will default the the S variant of zigzag instead.




Caution
Setting the start and end location to the same location will crash the game on chart load.
Warning
Undefined behaviour occurs when the end location is in-between the start location and mid-point of the slide, but the game will not crash.

Caution
The same warnings apply to this pattern as the counter-clockwise variant.

Note
The slide will always move from one side to the other regardless of the specified slide end location.