private void parseNote(Song2014 xml, SongNote2014 note, Notes n, Notes prev)
        {
            n.NoteMask = parseNoteMask(note, true);
            // numbering (NoteFlags) will be set later
            n.Time = note.Time;
            n.StringIndex = note.String;
            // actual fret number
            n.FretId = (Byte)note.Fret;
            // anchor fret will be set later
            n.AnchorFretId = unchecked((Byte)(-1));
            // will be overwritten
            n.AnchorWidth = unchecked((Byte)(-1));
            n.ChordId = -1;
            n.ChordNotesId = -1;
            n.PhraseIterationId = getPhraseIterationId(xml, n.Time, false);
            n.PhraseId = xml.PhraseIterations[n.PhraseIterationId].PhraseId;
            // these will be overwritten
            n.FingerPrintId[0] = -1;
            n.FingerPrintId[1] = -1;
            // these will be overwritten
            n.NextIterNote = -1;
            n.PrevIterNote = -1;
            n.ParentPrevNote = -1;
            n.SlideTo = unchecked((Byte)note.SlideTo);
            n.SlideUnpitchTo = unchecked((Byte)note.SlideUnpitchTo);
            n.LeftHand = unchecked((Byte)note.LeftHand);
            // 'bvibrato' and 'rchords8' are using 0 value but without TAP mask
            if (note.Tap != 0)
                n.Tap = unchecked((Byte)note.Tap);
            else
                n.Tap = unchecked((Byte)(-1));

            n.PickDirection = (Byte)note.PickDirection;
            n.Slap = (Byte)note.Slap;
            n.Pluck = (Byte)note.Pluck;
            n.Vibrato = note.Vibrato;
            n.Sustain = note.Sustain;
            n.MaxBend = note.Bend;
            n.BendData = new BendDataSection();
            n.BendData.BendData = parseBendData(note, true);
            n.BendData.Count = n.BendData.BendData.Length;
        }
        private void parseChord(Song2014 xml, Sng2014File sng, SongChord2014 chord, Notes n, Int32 chordNotesId)
        {
            n.NoteMask |= CON.NOTE_MASK_CHORD;
            if (chordNotesId != -1)
            {
                // there should always be a STRUM too => handshape at chord time
                // probably even for chordNotes which are not exported to SNG
                n.NoteMask |= CON.NOTE_MASK_CHORDNOTES;
            }

            if (chord.LinkNext != 0)
                n.NoteMask |= CON.NOTE_MASK_PARENT;

            if (chord.Accent != 0)
                n.NoteMask |= CON.NOTE_MASK_ACCENT;
            if (chord.FretHandMute != 0)
                n.NoteMask |= CON.NOTE_MASK_FRETHANDMUTE;
            if (chord.HighDensity != 0)
                n.NoteMask |= CON.NOTE_MASK_HIGHDENSITY;
            if (chord.Ignore != 0)
                n.NoteMask |= CON.NOTE_MASK_IGNORE;
            if (chord.PalmMute != 0)
                n.NoteMask |= CON.NOTE_MASK_PALMMUTE;
            // TODO: does not seem to have a mask or any effect
            // if (chord.Hopo != 0)
            //     n.NoteMask |= ;

            // numbering will be set later
            //n.NoteFlags = CON.NOTE_FLAGS_NUMBERED;

            n.Time = chord.Time;
            n.StringIndex = unchecked((Byte)(-1));
            // always -1
            n.FretId = unchecked((Byte)(-1));
            // anchor fret will be set later
            n.AnchorFretId = unchecked((Byte)(-1));
            // will be overwritten
            n.AnchorWidth = unchecked((Byte)(-1));
            n.ChordId = chord.ChordId;
            n.ChordNotesId = chordNotesId;
            n.PhraseIterationId = getPhraseIterationId(xml, n.Time, false);
            n.PhraseId = xml.PhraseIterations[n.PhraseIterationId].PhraseId;
            // these will be overwritten
            n.FingerPrintId[0] = -1;
            n.FingerPrintId[1] = -1;
            // these will be overwritten
            n.NextIterNote = -1;
            n.PrevIterNote = -1;
            // seems to be unused for chords
            n.ParentPrevNote = -1;
            n.SlideTo = unchecked((Byte)(-1));
            n.SlideUnpitchTo = unchecked((Byte)(-1));
            n.LeftHand = unchecked((Byte)(-1));
            n.Tap = unchecked((Byte)(-1));
            n.PickDirection = unchecked((Byte)(-1));
            n.Slap = unchecked((Byte)(-1));
            n.Pluck = unchecked((Byte)(-1));
            if (chord.ChordNotes != null)
            {
                foreach (var cn in chord.ChordNotes)
                    if (cn.Sustain > n.Sustain)
                        n.Sustain = cn.Sustain;
            }

            if (n.Sustain > 0)
                n.NoteMask |= CON.NOTE_MASK_SUSTAIN;

            int cnt = 0;
            for (int str = 0; str < 6; str++)
                if (sng.Chords.Chords[chord.ChordId].Frets[str] != 255)
                    ++cnt;
            if (cnt == 2)
                n.NoteMask |= CON.NOTE_MASK_DOUBLESTOP;

            // there are only zeros for all chords in lessons
            //n.Vibrato = 0;
            //n.MaxBend = 0;
            n.BendData = new BendDataSection();
            n.BendData.Count = 0;
            n.BendData.BendData = new BendData32[n.BendData.Count];
        }
        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;
            }
        }
        /// <summary>
        /// Sign notes with fret number. (each 8 notes)
        /// </summary>
        /// <param name="sng"></param>
        /// <param name="notes"></param>
        private void numberNotes(Sng2014File sng, Notes[] notes)
        {
            // current phrase iteration
            int p = 0;
            for (int o = 0; o < notes.Length; o++)
            {
                var current = notes[o];

                // skip open strings
                if (current.FretId == 0)
                {
                    continue;
                }

                // are we past phrase iteration boundary?
                if (current.Time > sng.PhraseIterations.PhraseIterations[p].NextPhraseTime)
                {
                    // advance and re-run
                    // will be repeated through empty iterations
                    ++p;
                    o = o - 1;
                    continue;
                }

                bool repeat = false;
                int start = o - 8;
                if (start < 0)
                    start = 0;
                // search last 8 notes
                for (int i = o - 1; i >= start; i--)
                {
                    // ignore notes which are too far away
                    if (notes[i].Time + 2.0 < current.Time)
                        continue;
                    // ignore notes outside of iteration
                    if (notes[i].Time < sng.PhraseIterations.PhraseIterations[p].StartTime)
                        continue;

                    // count as repeat if this fret/chord was numbered recently
                    if ((current.ChordId == -1 && notes[i].FretId == current.FretId) ||
                        (current.ChordId != -1 && notes[i].ChordId == current.ChordId))
                    {
                        if ((notes[i].NoteFlags & CON.NOTE_FLAGS_NUMBERED) != 0)
                        {
                            repeat = true;
                            break;
                        }
                    }
                }

                // change
                if (!repeat)
                    current.NoteFlags |= CON.NOTE_FLAGS_NUMBERED;
            }
        }
 public void read(EndianBinaryReader r)
 {
     Count = r.ReadInt32();
     Notes = new Notes[Count]; for (int i = 0; i < Count; i++) { var obj = new Notes(); obj.read(r); Notes[i] = obj; }
 }
 public void read(BinaryReader r)
 {
     this.Count = r.ReadInt32();
     this.Notes = new Notes[this.Count]; for (int i=0; i<this.Count; i++) { Notes obj = new Notes(); obj.read(r); this.Notes[i] = obj; }
 }