A note occurs at a given absolute beat, which is its distance from the beginning of the chart, unaffected by time signatures.
Each note is quantized by two numbers: parts_per_beat and part_id.
parts_per_beat is how many parts a beat is divided into.
part_id is how many of those divisions are before this note in the beat.
A time signature has three relevant fields: start_beat, beats_per_measure, and note_value_per_beat.
start_beat is the beat the time signature starts on. This is distance from the beginning of the chart, unaffected by time signatures.
beats_per_measure is the number of beats in a measure.
note_value_per_beat is how many pennies a note that occurs once per beat can be exchanged for.
The time signature data is used with the note's beat value to calculate the parts_per_beat and part_id for the note like this:
local note_beat= 13 -- Fetched from note data.
local curr_time_signature= {start= 0, beats_per_measure= 4, note_value_per_beat= 4} -- Fetched from timing data.
local signature_multiple= curr_time_signature.note_value_per_beat / 4 -- Calculate relative to 4/4 time because it's the most common.
local dist_from_signature_start= (note_beat - curr_time_signature.start) * signature_multiple
local beat_in_curr_measure= dist_from_signature_start % curr_time_signature.beats_per_measure
local parts_per_beat, part_id= fractionate_number(beat_in_curr_measure % 1)
fractionate_number is a function that finds the closest fraction with a denominator less than or equal to 100 to fit the number.
Noteskins provide quantization data for a parts_per_beat value.
The quantization data can optionally be split to give each possible part_id value a different appearance.
When displaying a note, the notefield finds the data with a matching parts_per_beat and matching part_id. If there is no part_id match, then the first entry with a matching parts_per_beat is used.
If there is no parts_per_beat match, there is an entry for unknowns. The note's row_id is used instead of part_id (modulus by the number of part_id entries).
In a chart meant for multiple players, each note has a player number. That player number is used instead of parts_per_beat to pick the quantization for the note.
Mods can change the parts_per_beat or part_id values of a note before they are used to display the note.
parts_per_beat and part_id are fields in the mod input.
This replaces the state map used to quantize and animate notes in SM 5.1.-3.
{
unknown= {
{0, 1, 2, 3}, -- Use these frames to animate a note.
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15},
}
}
{
unknown= {
-- Translate the texture to this position and use the beat to set seconds into animation.
{trans_x= 0, trans_y= 0},
{trans_x= .25, trans_y= 0},
{trans_x= .5, trans_y= 0},
{trans_x= .75, trans_y= 0},
}
}
{
{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15},
}
}
{
{
{trans_x= 0, trans_y= 0},
{trans_x= .25, trans_y= 0},
{trans_x= .5, trans_y= 0},
{trans_x= .75, trans_y= 0},
}
}
{
{
{0, 1, 2, 3},
}
}
{
{
{trans_x= 0, trans_y= 0},
}
}
{
-- Notes that occur once per beat.
-- Also used for player 1.
[1]= {
{0, 1, 2, 3}, -- One per beat notes for player 1.
{4, 5, 6, 7}, -- Two per beat notes for player 1.
{8, 9, 10, 11}, -- Four per beat notes for player 1.
{12, 13, 14, 15}, -- Eight per beat notes for player 1.
},
-- Notes that occur two times per beat.
-- Also used for player 2.
[2]= {
{...}, -- One per beat notes for player 2.
{...}, -- Two per beat notes for player 2.
{...}, -- Four per beat notes for player 2.
{...}, -- Eight per beat notes for player 2.
},
-- Notes that occur four times per beat.
-- Also used for player 2.
[4]= {
[1]= {...}, -- The 16th that occurs after the 4th.
-- Also one per beat notes for player 3.
[3]= {...}, -- The 16th that occurs before the 4th.
-- Two per beat notes for player 3.
[5]= {...}, -- Four per beat notes for player 3.
[7]= {...}, -- Eight per beat notes for player 3.
},
-- Notes that occur eight times per beat.
-- Also used for player 2.
[8]= {
[1]= {...}, -- The 32nd that occurs after the 4th.
-- Also one per beat notes for player 4.
[3]= {...}, -- The 32nd that occurs after the first 16th.
-- Two per beat notes for player 4.
[5]= {...}, -- The 32nd that occurs after the 8th.
-- Four per beat notes for player 4.
[7]= {...}, -- The 32nd that occurs before the 4th.
-- Eight per beat notes for player 4.
},
unknown= {
{...},
{...},
{...},
{...},
},
}
{
-- Notes that occur once per beat.
[1]= {{0, 1, 2, 3}},
-- Notes that occur two times per beat.
[2]= {{4, 5, 6, 7}},
-- Notes that occur four times per beat.
[4]= {{8, 9, 10, 11}},
-- Notes that occur eight times per beat.
[8]= {{12, 13, 14, 15}},
unknown= {{...}},
player= {
-- Notes for player 1.
{
[1]= {16}, -- One per beat.
[2]= {17}, -- Two per beat.
[4]= {18}, -- Four per beat.
[8]= {19}, -- Eight per beat.
},
-- Notes for player 2.
{
[1]= {20}, -- One per beat.
[2]= {21}, -- Two per beat.
[4]= {22}, -- Four per beat.
[8]= {23}, -- Eight per beat.
},
-- Notes for player 3.
{
[1]= {24}, -- One per beat.
[2]= {25}, -- Two per beat.
[4]= {26}, -- Four per beat.
[8]= {27}, -- Eight per beat.
},
-- Notes for player 4.
{
[1]= {28}, -- One per beat.
[2]= {29}, -- Two per beat.
[4]= {30}, -- Four per beat.
[8]= {31}, -- Eight per beat.
},
},
}
The problem with separated playerization data is that it requires the noteskin author to plan for a specific number of players.
If normal quantization data is reinterpreted as playerization data, every noteskin supports as many players as it has quantizations.
So, separated playerization data is not going to be used because it would mean fewer noteskins for modes with more players.
All notes in a chart are fractionated before gameplay begins (ideally by loading the data from the simfile, but that's a problem for the future). This is not performed every frame for every note.
void fractionate_number(double number, int& numenator, int& denomerator)
{
double fraction_dist= 10.0;
double min_dist= 1.0/1000.0; // Try to cut it short, so not every number has to go through the loop 100 times.
for(int denom= 2; denom < 100; ++denom)
{
int numer= number * denom;
double dist= std::fabs(number - (double(numer) / double(denom)));
if(dist < fraction_dist)
{
numenator= numer;
denomerator= denom;
fraction_dist= dist;
if(dist < min_dist) { break; }
}
}
}
And when arbitrary quantizations get supported (ability to place a note on any row or beat or any millisecond) how are .sm files supposed to encode it? It would need like 600 lines for one measure, if you were looking to put the special 5 or 7/4 quants (20ths and 28ths)