Inheritance: RocksmithToolkitLib.DLCPackage.Manifest2014.Header.AttributesHeader2014, IAttributes
        internal static SongTone2014[] Parse(Sng2014HSL.ToneSection toneSection, Attributes2014 attr = null)
        {
            var tones = new SongTone2014[toneSection.Count];
            for (var i = 0; i < toneSection.Count; i++)
            {
                var tone = new SongTone2014
                {
                    Id = toneSection.Tones[i].ToneId,
                    Time = toneSection.Tones[i].Time
                };

                if (attr != null)
                {
                    // Get tone name
                    switch (tone.Id)
                    {
                        case 0:
                            tone.Name = attr.Tone_A;
                            break;
                        case 1:
                            tone.Name = attr.Tone_B;
                            break;
                        case 2:
                            tone.Name = attr.Tone_C;
                            break;
                        case 3:
                            tone.Name = attr.Tone_D;
                            break;
                        default:
                            tone.Name = "importedtone_" + tone.Id;
                            break;
                    }
                }
                else
                    tone.Name = "importedtone_" + tone.Id;

                tones[i] = tone;
            }
            return tones;
        }
        public Song2014(Sng2014HSL.Sng sngData, Attributes2014 attr = null)
        {
            Version = "7";
            CrowdSpeed = "1";

            if (attr != null)
            {
                // If manifest is passed, fill general song information
                Title = attr.SongName;
                Arrangement = ((ArrangementName)attr.ArrangementType).ToString();
                Part = (short)attr.SongPartition;
                Offset = (float)attr.SongOffset;
                CentOffset = Convert.ToString(attr.CentOffset);
                SongLength = (float)attr.SongLength;
                SongNameSort = attr.SongNameSort;
                AverageTempo = attr.SongAverageTempo;
                Tuning = attr.Tuning;
                Capo = Convert.ToByte(attr.CapoFret);
                ArtistName = attr.ArtistName;
                ArtistNameSort = attr.ArtistNameSort;
                AlbumName = attr.AlbumName;
                AlbumNameSort = attr.AlbumNameSort;
                AlbumYear = Convert.ToString(attr.SongYear) ?? "";
                AlbumArt = attr.AlbumArt;
                ArrangementProperties = attr.ArrangementProperties;
                LastConversionDateTime = attr.LastConversionDateTime;

                ToneBase = attr.Tone_Base;
                ToneA = attr.Tone_A;
                ToneB = attr.Tone_B;
                ToneC = attr.Tone_C;
                ToneD = attr.Tone_D;
            }
            else
            {
                Part = sngData.Metadata.Part;
                SongLength = sngData.Metadata.SongLength;
                Tuning = new TuningStrings(sngData.Metadata.Tuning);
                Capo = (sngData.Metadata.CapoFretId >= 0) ? sngData.Metadata.CapoFretId : (byte)0;
                LastConversionDateTime = sngData.Metadata.LastConversionDateTime.ToNullTerminatedAscii();
            }

            Tones = (attr != null) ? SongTone2014.Parse(sngData.Tones, attr) : SongTone2014.Parse(sngData.Tones);
            if (attr == null)
            {
                // Fix tones slots for fake tone names if manifest was not entered
                foreach (var tone in Tones)
                {
                    if (tone.Name.EndsWith("_0"))
                        ToneBase = tone.Name;
                    if (tone.Name.EndsWith("_1"))
                    {
                        ToneA = ToneBase;
                        ToneB = tone.Name;
                    }
                    if (tone.Name.EndsWith("_2"))
                        ToneC = tone.Name;
                    if (tone.Name.EndsWith("_3"))
                        ToneD = tone.Name;
                }
            }

            //Sections can be obtained from manifest or sng file (manifest preferred)
            Sections = (attr != null) ? SongSection.Parse(attr.Sections) : SongSection.Parse(sngData.Sections);

            //Can be obtained from manifest or sng file (sng preferred)
            Phrases = SongPhrase.Parse(sngData.Phrases);
            PhraseIterations = SongPhraseIteration2014.Parse(sngData.PhraseIterations);

            //Can be obtained from manifest or sng file (combined preferred)
            ChordTemplates = SongChordTemplate2014.Parse(sngData.Chords); // Only SNG have all ChordTemplates, manifest have only chord templates with name
            if (attr != null)
            {
                SongChordTemplate2014.AddChordIds(ChordTemplates, attr.ChordTemplates); // Only manifest has chordIds
            }

            //Only in SNG
            Ebeats = SongEbeat.Parse(sngData.BPMs);
            StartBeat = sngData.BPMs.BPMs[0].Time;
            Events = SongEvent.Parse(sngData.Events);
            Levels = SongLevel2014.Parse(sngData);

            //Not used in RS2014 customs at this time. Need to check official files
            NewLinkedDiff = SongNewLinkedDiff.Parse(sngData.NLD);
            PhraseProperties = SongPhraseProperty.Parse(sngData.PhraseExtraInfo);
            LinkedDiffs = new SongLinkedDiff[0];
            FretHandMuteTemplates = new SongFretHandMuteTemplate[0];
            //ddc
            TranscriptionTrack = TranscriptionTrack2014.GetDefault();
        }
        public void GenerateTuningData(Attributes2014 attribute, dynamic song)
        {
            if (song.Tuning == null)
                return;

            attribute.Tuning = song.Tuning;
            var tuning = new TuningDefinition();
            var tuningName = tuning.NameFromStrings(attribute.Tuning);

            if (tuningName == "E Standard")
                attribute.ArrangementProperties.StandardTuning = 1;
            else
                attribute.ArrangementProperties.StandardTuning = 0;
        }
        public void GenerateTechniques(Attributes2014 attribute, Song2014 song)
        {
            // results from this method do not match ODLC but are workable
            //
            //"Techniques" : {
            //     "DiffLevelID" : {//used to display which techs are set at current lvl.
            //         "SectionNumber" : [// > 0
            //             TechID, //required base tech for extended tech(?)
            //             TechID
            //         ]
            //     },
            // }

            if (song.Sections == null)
                return;

            attribute.Techniques = new Dictionary<string, Dictionary<string, List<int>>>();
            for (int difficulty = 0; difficulty < song.Levels.Length; difficulty++)
            {
                var notes = song.Levels[difficulty].Notes;
                var sectionId = new Dictionary<string, List<int>>();
                var techId = new List<int>();

                for (int section = 0; section < song.Sections.Length; section++)
                {
                    var sectionNumber = song.Sections[section].Number;
                    var starTime = song.Sections[section].StartTime;
                    var endTime = song.Sections[Math.Min(section + 1, song.Sections.Length - 1)].StartTime;

                    // iterate through notes in section in the difficulty level
                    foreach (var note in notes)
                    {
                        if (note.Time >= starTime && note.Time < endTime) //in range
                        {
                            var noteTech = getNoteTech(note); // needs tweaking
                            techId.AddRange(noteTech);
                        }
                    }

                    if (techId.Count > 0)
                    {
                        // TODO: needs more tweaking
                        //  techId.Add(35); // try adding dumby data for now
                        List<int> distinctTechIds = techId.Distinct().OrderBy(x => x).ToList();
                        // sometimes sectionNumbers are not unique so duplicate key throws an error if not checked
                        if (sectionId.ContainsKey(sectionNumber.ToString()))
                        {
                            // get the current values and make sure all combined values are distinct
                            var techIdValue = sectionId[sectionNumber.ToString()];
                            techIdValue.AddRange(distinctTechIds);
                            distinctTechIds = techIdValue.Distinct().OrderBy(x => x).ToList();
                            sectionId.Remove(sectionNumber.ToString());
                        }

                        sectionId.Add(sectionNumber.ToString(), distinctTechIds);
                    }

                    techId = new List<int>();
                }
                /*
                "5": {
                   "1": [
                       13,
                       35 <- missing
                       ],
               */

                if (sectionId.Keys.Count > 0)
                    attribute.Techniques.Add(difficulty.ToString(), sectionId);
            }
        }
        public void GenerateChords(Attributes2014 attribute, Song2014 song)
        {
            // Some ODLC contain JSON Chords errors, this method is producing workable results
            //
            // USING song.Levels[difficulty].HandShapes METHOD
            // the handshape data can be used to obtain chordIds
            // (more efficient less data to iterate through)
            //
            //"Chords" : {
            //     "DiffLevelID" : {//used to display which chord is set at current lvl.
            //         "SectionID" : [// >= 0
            //             ChordID,
            //             ChordID
            //         ]
            //     },
            // }

            if (song.Sections == null)
                return;

            attribute.Chords = new Dictionary<string, Dictionary<string, List<int>>>();
            for (int difficulty = 0; difficulty < song.Levels.Length; difficulty++)
            {
                var chords = song.Levels[difficulty].HandShapes;
                var sectionId = new Dictionary<string, List<int>>();
                var chordId = new List<int>();

                for (int section = 0; section < song.Sections.Length; section++)
                {
                    var sectionNumber = song.Sections[section].Number;
                    var starTime = song.Sections[section].StartTime;
                    var endTime = song.Sections[Math.Min(section + 1, song.Sections.Length - 1)].StartTime;

                    // iterate through chords in handshapes in the difficulty level
                    foreach (var chord in chords)
                    {
                        if (chord.StartTime >= starTime && chord.EndTime < endTime) //in range
                        {
                            chordId.Add(chord.ChordId);
                        }
                    }

                    if (chordId.Count > 0)
                    {
                        // always ordered in ODLC
                        List<int> distinctChordIds = chordId.Distinct().OrderBy(x => x).ToList();
                        sectionId.Add(section.ToString(), distinctChordIds);
                    }

                    chordId = new List<int>();
                }

                if (sectionId.Keys.Count > 0)
                    attribute.Chords.Add(difficulty.ToString(), sectionId);
            }
        }
        /// <summary>
        /// Generates the techniques.
        /// </summary>
        /// <remarks>
        /// "Techniques" : {
        ///     "DiffLevelID" : {//used to display which techs are set at current lvl.
        ///         "SetionID" : [// > 0
        ///             TechID, //required base tech for extended tech(?)
        ///             TechID
        ///         ]
        ///     },
        /// }
        /// </remarks>
        /// <param name="attribute">Attribute.</param>
        /// <param name="song">Song.</param>
        public void GenerateTechniques(Attributes2014 attribute, Song2014 song)
        {
            if (song.Sections == null)
                return;

            attribute.Techniques = new Dictionary<string, Dictionary<string, List<int>>>();
            for( int l = 0, s = 0, sectionsL = song.Sections.Length, levelsL = song.Levels.Length; l < levelsL; l++, s = 0 )
            {
//                var shapes = song.Levels[l].HandShapes;
//                var chords = song.Levels[l].Chords;
                var notes = song.Levels[l].Notes;
                var t = new List<int>();
                var techs = new Dictionary<string, List<int>>();

                foreach( var n in notes )
                {//note should be in section range
                    var starTime = song.Sections[s].StartTime;
                    var endTime = song.Sections[Math.Min(s + 1, sectionsL - 1)].StartTime;

                    if(n.Time > starTime && n.Time <= endTime)//in range
                    {
                        t.AddRange(getNoteTech(n));
                    }
                    else if(n.Time > endTime)//at next section
                    {
                        s++;
                        if(t.Count > 0){
                            techs.Add(s.ToString(), t.Distinct().ToList());
                            t = new List<int>();
                        }
                    }
                }
                if(techs.Values.Count > 0)
                    attribute.Techniques.Add(l.ToString(), techs.Distinct().ToDictionary(x => x.Key, x => x.Value));
            }
        }