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