public void read(EndianBinaryReader r)
 {
     Count = r.ReadInt32();
     Fingerprints = new Fingerprint[Count]; for (int i = 0; i < Count; i++) { var obj = new Fingerprint(); obj.read(r); Fingerprints[i] = obj; }
 }
        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;
            }
        }
 public void read(BinaryReader r)
 {
     this.Count = r.ReadInt32();
     this.Fingerprints = new Fingerprint[this.Count]; for (int i=0; i<this.Count; i++) { Fingerprint obj = new Fingerprint(); obj.read(r); this.Fingerprints[i] = obj; }
 }