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; } }