private void parseArrangements(Song2014 xml, Sng2014File sng)
{
sng.Arrangements = new ArrangementSection();
sng.Arrangements.Count = getMaxDifficulty(xml) + 1;
sng.Arrangements.Arrangements = new Arrangement[sng.Arrangements.Count];
// not strictly necessary but more helpful than hash value
var note_id = new Dictionary<UInt32, UInt32>();
for (int i = 0; i < sng.Arrangements.Count; i++)
{
var level = xml.Levels[i];
var a = new Arrangement();
a.Difficulty = level.Difficulty;
var anchors = new AnchorSection();
anchors.Count = level.Anchors.Length;
anchors.Anchors = new Anchor[anchors.Count];
for (int j = 0; j < anchors.Count; j++)
{
var anchor = new Anchor();
anchor.StartBeatTime = level.Anchors[j].Time;
if (j + 1 < anchors.Count)
anchor.EndBeatTime = level.Anchors[j + 1].Time;
else
// last phrase iteration = noguitar/end
anchor.EndBeatTime = xml.PhraseIterations[xml.PhraseIterations.Length - 1].Time;
// TODO: not 100% clear
// times will be updated later
// these "garbage" values are everywhere!
//anchor.Unk3_FirstNoteTime = (float) 3.4028234663852886e+38;
//anchor.Unk4_LastNoteTime = (float) 1.1754943508222875e-38;
anchor.FretId = (byte)level.Anchors[j].Fret;
anchor.Width = (Int32)level.Anchors[j].Width;
anchor.PhraseIterationId = getPhraseIterationId(xml, anchor.StartBeatTime, false);
anchors.Anchors[j] = anchor;
}
a.Anchors = anchors;
// each slideTo will get anchor extension
a.AnchorExtensions = new AnchorExtensionSection();
foreach (var note in level.Notes)
if (note.SlideTo != -1)
++a.AnchorExtensions.Count;
a.AnchorExtensions.AnchorExtensions = new AnchorExtension[a.AnchorExtensions.Count];
// Fingerprints1 is for handshapes without "arp" displayName
a.Fingerprints1 = new FingerprintSection();
// Fingerprints2 is for handshapes with "arp" displayName
a.Fingerprints2 = new FingerprintSection();
var fp1 = new List<Fingerprint>();
var fp2 = new List<Fingerprint>();
foreach (var h in level.HandShapes)
{
if (h.ChordId < 0) continue;
var fp = new Fingerprint
{
ChordId = h.ChordId, StartTime = h.StartTime, EndTime = h.EndTime
// TODO: not always StartTime
//fp.Unk3_FirstNoteTime = fp.StartTime;
//fp.Unk4_LastNoteTime = fp.StartTime;
};
if (xml.ChordTemplates[fp.ChordId].DisplayName.EndsWith("arp"))
fp2.Add(fp);
else
fp1.Add(fp);
}
a.Fingerprints1.Count = fp1.Count;
a.Fingerprints1.Fingerprints = fp1.ToArray();
a.Fingerprints2.Count = fp2.Count;
a.Fingerprints2.Fingerprints = fp2.ToArray();
// calculated as we go through notes, seems to work
// NotesInIteration1 is count without ignore="1" notes
a.PhraseIterationCount1 = xml.PhraseIterations.Length;
a.NotesInIteration1 = new Int32[a.PhraseIterationCount1];
// NotesInIteration2 seems to be the full count
a.PhraseIterationCount2 = a.PhraseIterationCount1;
a.NotesInIteration2 = new Int32[a.PhraseIterationCount2];
// notes and chords sorted by time
List<Notes> notes = new List<Notes>();
int acent = 0;
foreach (var note in level.Notes)
{
var n = new Notes();
Notes prev = null;
if (notes.Count > 0)
prev = notes.Last();
parseNote(xml, note, n, prev);
notes.Add(n);
for (int j = 0; j < xml.PhraseIterations.Length; j++)
{
var piter = xml.PhraseIterations[j];
if (piter.Time > note.Time)
{
if (note.Ignore == 0)
++a.NotesInIteration1[j - 1];
++a.NotesInIteration2[j - 1];
break;
}
}
if (note.SlideTo != -1)
{
var ae = new AnchorExtension();
ae.FretId = (Byte)note.SlideTo;
ae.BeatTime = note.Time + note.Sustain;
a.AnchorExtensions.AnchorExtensions[acent++] = ae;
}
}
foreach (var chord in level.Chords)
{
var cn = new Notes();
Int32 id = -1;
if (chord.ChordNotes != null && chord.ChordNotes.Length > 0)
id = addChordNotes(sng, chord);
parseChord(xml, sng, chord, cn, id);
notes.Add(cn);
for (int j = 0; j < xml.PhraseIterations.Length; j++)
{
var piter = xml.PhraseIterations[j];
if (chord.Time >= piter.Time && piter.Time >= chord.Time)
{
if (chord.Ignore == 0)
++a.NotesInIteration1[j];
++a.NotesInIteration2[j]; // j-1 not safe with j=0
break;
}
}
}
// exception handler for some poorly formed RS1 CDLC
try
{
// need to be sorted before anchor note times are updated
notes.Sort((x, y) => x.Time.CompareTo(y.Time));
// check for RS1 CDLC note time errors
// if (notes.Count > 0) // alt method to deal with the exception
if ((int)first_note_time == 0 || first_note_time > notes[0].Time)
first_note_time = notes[0].Time;
}
catch (Exception)
{
// show error in convert2012CLI command window and continue
Console.WriteLine(@" -- CDLC contains note time errors and may not play properly"); // + ex.Message);
}
foreach (var n in notes)
{
for (Int16 id = 0; id < fp1.Count; id++) //FingerPrints 1st level (common handshapes?)
if (n.Time >= fp1[id].StartTime && n.Time < fp1[id].EndTime)
{
n.FingerPrintId[0] = id;
// add STRUM to chords if highDensity = 0
if (n.ChordId != -1 && (n.NoteMask & CON.NOTE_MASK_HIGHDENSITY) != CON.NOTE_MASK_HIGHDENSITY)
n.NoteMask |= CON.NOTE_MASK_STRUM;
if (fp1[id].Unk3_FirstNoteTime == 0)
fp1[id].Unk3_FirstNoteTime = n.Time;
float sustain = 0;
if (n.Time + n.Sustain < fp1[id].EndTime)
sustain = n.Sustain;
fp1[id].Unk4_LastNoteTime = n.Time + sustain;
break;
}
for (Int16 id = 0; id < fp2.Count; id++) //FingerPrints 2nd level (used for -arp(eggio) handshapes)
if (n.Time >= fp2[id].StartTime && n.Time < fp2[id].EndTime)
{
n.FingerPrintId[1] = id;
// add STRUM to chords
if (fp2[id].StartTime == n.Time && n.ChordId != -1)
n.NoteMask |= CON.NOTE_MASK_STRUM;
n.NoteMask |= CON.NOTE_MASK_ARPEGGIO;
if (fp2[id].Unk3_FirstNoteTime == 0)
fp2[id].Unk3_FirstNoteTime = n.Time;
float sustain = 0;
if (n.Time + n.Sustain < fp2[id].EndTime)
sustain = n.Sustain;
fp2[id].Unk4_LastNoteTime = n.Time + sustain;
break;
}
for (int j = 0; j < a.Anchors.Count; j++)
if (n.Time >= a.Anchors.Anchors[j].StartBeatTime && n.Time < a.Anchors.Anchors[j].EndBeatTime)
{
n.AnchorWidth = (Byte)a.Anchors.Anchors[j].Width;
// anchor fret
n.AnchorFretId = (Byte)a.Anchors.Anchors[j].FretId;
if (a.Anchors.Anchors[j].Unk3_FirstNoteTime == 0)
a.Anchors.Anchors[j].Unk3_FirstNoteTime = n.Time;
float sustain = 0;
if (n.Time + n.Sustain < a.Anchors.Anchors[j].EndBeatTime - 0.1)
sustain = n.Sustain;
a.Anchors.Anchors[j].Unk4_LastNoteTime = n.Time + sustain;
break;
}
}
// initialize times for empty anchors, based on 'lrocknroll'
foreach (var anchor in a.Anchors.Anchors)
if (anchor.Unk3_FirstNoteTime == 0)
{
anchor.Unk3_FirstNoteTime = anchor.StartBeatTime;
anchor.Unk4_LastNoteTime = anchor.StartBeatTime + (float)0.1;
}
a.Notes = new NotesSection();
a.Notes.Count = notes.Count;
a.Notes.Notes = notes.ToArray();
foreach (var piter in sng.PhraseIterations.PhraseIterations)
{
int count = 0;
int j = 0;
for (; j < a.Notes.Count; j++)
{
// skip notes outside of a phraseiteration
if (a.Notes.Notes[j].Time < piter.StartTime)
continue;
if (a.Notes.Notes[j].Time >= piter.NextPhraseTime)
{
break;
}
// set to next arrangement note
a.Notes.Notes[j].NextIterNote = (Int16)(j + 1);
// set all but first note to previous note
if (count > 0)
a.Notes.Notes[j].PrevIterNote = (Int16)(j - 1);
++count;
}
// fix last phrase note
if (count > 0)
a.Notes.Notes[j - 1].NextIterNote = -1;
}
for (int j = 1; j < a.Notes.Notes.Length; j++)
{
var n = a.Notes.Notes[j];
var p = a.Notes.Notes[j - 1];
int prvnote = 1; //set current + prev note + initialize prvnote variable
//do not do this searching for a parent, if the previous note timestamp != current time stamp
if (n.Time != p.Time) prvnote = 1;
else
{
for (int x = 1; x < (a.Notes.Notes.Length); x++) //search up till the beginning of iteration
{
if (j - x < 1) //don't search past the first note in iteration
{
prvnote = x;
x = a.Notes.Notes.Length + 2;
break; // stop searching for a match we reached the beginning
}
var prv = a.Notes.Notes[j - x]; // get the info for the note we are checking against
if (prv.Time != n.Time)
{
//now check the timestamp if its the same timestamp then keep looking
if (prv.ChordId != -1)
{
//check if its a chord
prvnote = x;
x = a.Notes.Notes.Length + 2;
break; //stop here, its a chord so don't need to check the strings
}
if (prv.StringIndex == n.StringIndex)
{
//check to see if we are looking at the same string
prvnote = x;
x = a.Notes.Notes.Length + 2;
break; //stop here we found the same string, at a different timestamp, thats not a chord
}
}
}
}
var prev = a.Notes.Notes[j - prvnote]; //this will be either the first note of piter, or the last note on the same string at previous timestamp
if ((prev.NoteMask & CON.NOTE_MASK_PARENT) != 0)
{
n.ParentPrevNote = (short)(prev.NextIterNote - 1);
n.NoteMask |= CON.NOTE_MASK_CHILD; //set the ParentPrevNote# = the matched Note#//add CHILD flag
}
}
a.PhraseCount = xml.Phrases.Length;
a.AverageNotesPerIteration = new float[a.PhraseCount];
var iter_count = new float[a.PhraseCount];
for (int j = 0; j < xml.PhraseIterations.Length; j++)
{
var piter = xml.PhraseIterations[j];
// using NotesInIteration2 to calculate
a.AverageNotesPerIteration[piter.PhraseId] += a.NotesInIteration2[j];
++iter_count[piter.PhraseId];
}
for (int j = 0; j < iter_count.Length; j++)
{
if (iter_count[j] > 0)
a.AverageNotesPerIteration[j] /= iter_count[j];
}
// this is some kind of optimization in RS2 where they
// hash all note data but their position in phrase iteration
// to mark otherwise unchanged notes
foreach (var n in a.Notes.Notes)
{
MemoryStream data = sng.CopyStruct(n);
var r = new EndianBinaryReader(EndianBitConverter.Little, data);
var ncopy = new Notes();
ncopy.read(r);
ncopy.NextIterNote = 0;
ncopy.PrevIterNote = 0;
ncopy.ParentPrevNote = 0;
UInt32 crc = sng.HashStruct(ncopy);
if (!note_id.ContainsKey(crc))
note_id[crc] = (UInt32)note_id.Count;
n.Hash = note_id[crc];
}
numberNotes(sng, a.Notes.Notes);
sng.Arrangements.Arrangements[i] = a;
}
}