public void ReadSong(Song2014 songXml, Sng2014File sngFile) { Int16[] tuning = { songXml.Tuning.String0, songXml.Tuning.String1, songXml.Tuning.String2, songXml.Tuning.String3, songXml.Tuning.String4, songXml.Tuning.String5, }; parseEbeats(songXml, sngFile); parsePhrases(songXml, sngFile); parseChords(songXml, sngFile, tuning, songXml.Arrangement == "Bass"); // vocals use different parse function sngFile.Vocals = new VocalSection { Vocals = new Vocal[0] }; parsePhraseIterations(songXml, sngFile); parsePhraseExtraInfo(songXml, sngFile); parseNLD(songXml, sngFile); parseActions(songXml, sngFile); parseEvents(songXml, sngFile); parseTones(songXml, sngFile); parseDNAs(songXml, sngFile); parseSections(songXml, sngFile); parseArrangements(songXml, sngFile); parseMetadata(songXml, sngFile, tuning); // this needs to be initialized after arrangements parseChordNotes(songXml, sngFile); }
private static void CreateNddArrangement(Sng2014File inp, int threshold) { var inputArrangements = inp.Arrangements.Arrangements; var maxDifficulty = inp.Arrangements.Arrangements.Max(a => a.Difficulty); inp.Arrangements.Arrangements = inp.Arrangements.Arrangements.Where(a => a.Difficulty == maxDifficulty).ToArray(); var outputArrangement = inp.Arrangements.Arrangements[0]; inp.Arrangements.Count = 1; foreach (var phrase in inp.Phrases.Phrases) { phrase.MaxDifficulty = 0; } outputArrangement.Notes.Notes = (from ia in inputArrangements from notes in ia.Notes.Notes select new {ia.Difficulty, notes} into dn group dn by dn.notes.Time into duplicateNotes let hardest = duplicateNotes.Max(x => x.Difficulty) select duplicateNotes.First(x => x.Difficulty == hardest) into hardestNotes orderby hardestNotes.notes.Time select hardestNotes.notes ).ToArray(); //inputArrangements.SelectMany(ia => ia.Notes.Notes.Select(notes => new {ia.Difficulty, notes}).GroupBy(ian => ian.notes.Time).Select(nn => nn.Max(n => n.Difficulty)).OrderBy(x => x.Time).ToArray(); outputArrangement.Notes.Count = outputArrangement.Notes.Notes.Length; outputArrangement.Anchors.Anchors = inputArrangements.SelectMany(ia => ia.Anchors.Anchors).Distinct(ANCHOR_COMPARER).OrderBy(x => x.StartBeatTime).ToArray(); outputArrangement.Anchors.Count = outputArrangement.Anchors.Anchors.Length; outputArrangement.AnchorExtensions.AnchorExtensions = inputArrangements.SelectMany(ia => ia.AnchorExtensions.AnchorExtensions).Distinct(ANCHOR_EXTENSION_COMPARER).ToArray(); outputArrangement.AnchorExtensions.Count = outputArrangement.AnchorExtensions.AnchorExtensions.Length; outputArrangement.Fingerprints1.Fingerprints = inputArrangements.SelectMany(ia => ia.Fingerprints1.Fingerprints).Distinct(FINGERPRINT_COMPARER).OrderBy(x => x.StartTime).ToArray(); outputArrangement.Fingerprints1.Count = outputArrangement.Fingerprints1.Fingerprints.Length; outputArrangement.Fingerprints2.Fingerprints = inputArrangements.SelectMany(ia => ia.Fingerprints2.Fingerprints).Distinct(FINGERPRINT_COMPARER).OrderBy(x => x.StartTime).ToArray(); outputArrangement.Fingerprints2.Count = outputArrangement.Fingerprints2.Fingerprints.Length; }
public static Sng2014File ConvertXML(string xmlPath, ArrangementType type, string cdata = null) { if (type == ArrangementType.Vocal) { return(Sng2014FileWriter.ReadVocals(xmlPath, cdata)); } return(Sng2014File.ConvertSong(xmlPath)); }
// this is platform independent SNG object public static Sng2014File ConvertSong(string xmlFile) { var song = Song2014.LoadFromFile(xmlFile); var parser = new Sng2014FileWriter(); var sng = new Sng2014File(); parser.ReadSong(song, sng); sng.NoteCount = parser.NoteCount; sng.DNACount = parser.DNACount; return sng; }
public static void RemoveDifficulty(Sng2014File sng, int overlap = -1) { if (sng.Vocals.Count <= 0) { //if (overlap < 0) // DifficultyRemover.CreateNddArrangement(sng); //else DifficultyRemover.CreateNddArrangement(sng, overlap); } }
/// <summary> /// Raw SNG data reader. /// </summary> /// <param name="inputFile">Packed and encrypted SNG file</param> /// <returns></returns> public static Sng2014File LoadFromFile(string inputFile, Platform platform) { Sng2014File sng = null; using (FileStream fs = new FileStream(inputFile, FileMode.Open)) { sng = ReadSng(fs, platform); } return(sng); }
// this is platform independent SNG object public static Sng2014File ConvertSong(string xmlFile) { var song = Song2014.LoadFromFile(xmlFile); var parser = new Sng2014FileWriter(); var sng = new Sng2014File(); parser.ReadSong(song, sng); sng.NoteCount = parser.NoteCount; sng.DNACount = parser.DNACount; return(sng); }
public static Sng2014File ConvertXML(string xmlPath, ArrangementType type) { if (type != ArrangementType.Vocal) { return(Sng2014File.ConvertSong(xmlPath)); } else { return(Sng2014FileWriter.ReadVocals(xmlPath)); } }
public Vocals(Sng2014File sngData) { Vocal = new Vocal[sngData.Vocals.Count]; for (var i = 0; i < sngData.Vocals.Count; i++) { var v = new Vocal(); v.Time = sngData.Vocals.Vocals[i].Time; v.Note = sngData.Vocals.Vocals[i].Note; v.Length = sngData.Vocals.Vocals[i].Length; v.Lyric = sngData.Vocals.Vocals[i].Lyric.ToNullTerminatedUTF8(); Vocal[i] = v; } Count = Vocal.Length; }
public static Sng2014File ReadVocals(string xmlFile, string cdata = null) { Sng2014File sng; if (!String.IsNullOrEmpty(cdata)) sng = new Sng2014File(new FileStream(cdata, FileMode.Open)); else sng = new Sng2014File(new MemoryStream(Resources.VOCALS_RS2)); var xml = Vocals.LoadFromFile(xmlFile); parseVocals(xml, sng); return sng; }
public static Sng2014File ReadSng(Stream input, Platform platform) { var sng = new Sng2014File(); using (var ms = new MemoryStream()) using (var r = new EndianBinaryReader(platform.GetBitConverter, ms)) { UnpackSng(input, ms, platform); ms.Flush(); ms.Seek(0, SeekOrigin.Begin); sng.Read(r); } return sng; }
public static Sng2014File ReadSng(Stream input, Platform platform) { Sng2014File sng = new Sng2014File(); using (var ms = new MemoryStream()) using (var r = new BinaryReader(ms)) { UnpackSng(input, ms, platform); ms.Flush(); ms.Seek(0, SeekOrigin.Begin); sng.Read(r); } return(sng); }
private Arrangement(Attributes2014 attr, Sng2014File song, Guid id) { this.ArrangementSort = attr.ArrangementSort; this.Sng2014 = song; this.Name = (ArrangementName)Enum.Parse(typeof(ArrangementName), attr.ArrangementName); this.ScrollSpeed = 20; this.Id = id; this.MasterId = ArrangementType == Sng.ArrangementType.Vocal ? 1 : RandomGenerator.NextInt(); if (this.ArrangementType == ArrangementType.Vocal) { this.Tones = new List<Tone2014>(); } else { ParseTuning(this, attr); ParseTones(this, attr); } }
private void parseChords(Song2014 xml, Sng2014File sng, Int16[] tuning, bool bass) { sng.Chords = new ChordSection(); sng.Chords.Count = xml.ChordTemplates.Length; sng.Chords.Chords = new Chord[sng.Chords.Count]; for (int i = 0; i < sng.Chords.Count; i++) { var chord = xml.ChordTemplates[i]; var c = new Chord(); // TODO: skip if DisplayName == null if (chord.DisplayName.EndsWith("arp")) c.Mask |= CON.CHORD_MASK_ARPEGGIO; else if (chord.DisplayName.EndsWith("nop")) c.Mask |= CON.CHORD_MASK_NOP; c.Frets[0] = (Byte)chord.Fret0; c.Frets[1] = (Byte)chord.Fret1; c.Frets[2] = (Byte)chord.Fret2; c.Frets[3] = (Byte)chord.Fret3; c.Frets[4] = (Byte)chord.Fret4; c.Frets[5] = (Byte)chord.Fret5; c.Fingers[0] = (Byte)chord.Finger0; c.Fingers[1] = (Byte)chord.Finger1; c.Fingers[2] = (Byte)chord.Finger2; c.Fingers[3] = (Byte)chord.Finger3; c.Fingers[4] = (Byte)chord.Finger4; c.Fingers[5] = (Byte)chord.Finger5; for (Byte s = 0; s < 6; s++) c.Notes[s] = GetMidiNote(tuning, s, c.Frets[s], bass, xml.Capo, template: true); readString(chord.ChordName, c.Name); sng.Chords.Chords[i] = c; } }
private void parseChordNotes(Song2014 xml, Sng2014File sng) { sng.ChordNotes = new ChordNotesSection(); sng.ChordNotes.ChordNotes = cns.ToArray(); sng.ChordNotes.Count = sng.ChordNotes.ChordNotes.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 parsePhraseExtraInfo(Song2014 xml, Sng2014File sng) { sng.PhraseExtraInfo = new PhraseExtraInfoByLevelSection(); sng.PhraseExtraInfo.Count = 0; sng.PhraseExtraInfo.PhraseExtraInfoByLevel = new PhraseExtraInfoByLevel[sng.PhraseExtraInfo.Count]; // probably leftover section from RS1 format, not used anymore // for (int i = 0; i < sng.PhraseExtraInfo.Count; i++) { // //var extra = xml.?[i]; // var e = new PhraseExtraInfoByLevel(); // //"PhraseId", // //"Difficulty", // //"Empty", // //"LevelJump", // //"Redundant", // //"Padding" // sng.PhraseExtraInfo.PhraseExtraInfoByLevel[i] = e; // } }
public static Sng2014File ReadVocals(string xmlFile) { var data = new MemoryStream(Resources.VOCALS_RS2); var sng = new Sng2014File(data); var xml = Vocals.LoadFromFile(xmlFile); Sng2014FileWriter.parseVocals(xml, sng); return sng; }
private void parseNLD(Song2014 xml, Sng2014File sng) { sng.NLD = new NLinkedDifficultySection(); sng.NLD.Count = xml.NewLinkedDiff.Length; sng.NLD.NLinkedDifficulties = new NLinkedDifficulty[sng.NLD.Count]; for (int i = 0; i < sng.NLD.Count; i++) { var nld = xml.NewLinkedDiff[i]; var n = new NLinkedDifficulty(); // TODO: Ratio attribute unused? n.LevelBreak = nld.LevelBreak; n.PhraseCount = nld.PhraseCount; n.NLD_Phrase = new Int32[n.PhraseCount]; for (int j = 0; j < n.PhraseCount; j++) { n.NLD_Phrase[j] = nld.Nld_phrase[j].Id; } sng.NLD.NLinkedDifficulties[i] = n; } }
private void parseEvents(Song2014 xml, Sng2014File sng) { sng.Events = new EventSection(); sng.Events.Count = xml.Events.Length; sng.Events.Events = new Event[sng.Events.Count]; for (int i = 0; i < sng.Events.Count; i++) { var evnt = xml.Events[i]; var e = new Event(); e.Time = evnt.Time; readString(evnt.Code, e.EventName); sng.Events.Events[i] = e; } }
private static void parseVocals(Vocals xml, Sng2014File sng) { sng.Vocals = new VocalSection(); sng.Vocals.Count = xml.Vocal.Length; sng.Vocals.Vocals = new Vocal[sng.Vocals.Count]; for (int i = 0; i < sng.Vocals.Count; i++) { var vcl = xml.Vocal[i]; var v = new Vocal(); v.Time = vcl.Time; v.Note = vcl.Note; v.Length = vcl.Length; readString(vcl.Lyric, v.Lyric); sng.Vocals.Vocals[i] = v; } }
public Int32 addChordNotes(Sng2014File sng, SongChord2014 chord) { var c = new ChordNotes(); for (int i = 0; i < 6; i++) { SongNote2014 n = null; foreach (var cn in chord.ChordNotes) { if (cn.String == i) { n = cn; break; } } // TODO: need to figure out which masks are not applied c.NoteMask[i] = parseNoteMask(n, false); c.BendData[i] = new BendData(); c.BendData[i].BendData32 = parseBendData(n, false); if (n != null && n.BendValues != null) c.BendData[i].UsedCount = n.BendValues.Length; if (n != null) { c.SlideTo[i] = (Byte)n.SlideTo; c.SlideUnpitchTo[i] = (Byte)n.SlideUnpitchTo; } else { c.SlideTo[i] = unchecked((Byte)(-1)); c.SlideUnpitchTo[i] = unchecked((Byte)(-1)); } if (n != null) c.Vibrato[i] = n.Vibrato; } UInt32 crc = sng.HashStruct(c); if (cnsId.ContainsKey(crc)) return cnsId[crc]; // don't export chordnotes if there are no techniques bool noTechniques = c.NoteMask.All(m => m == 0); if (noTechniques) return -1; // add new ChordNotes instance Int32 id = cns.Count; cnsId[crc] = id; cns.Add(c); return cnsId[crc]; }
private void parseTones(Song2014 xml, Sng2014File sng) { sng.Tones = new ToneSection(); if (xml.Tones != null) sng.Tones.Count = xml.Tones.Length; else sng.Tones.Count = 0; sng.Tones.Tones = new Tone[sng.Tones.Count]; for (int i = 0; i < sng.Tones.Count; i++) { var tn = xml.Tones[i]; var t = new Tone(); t.Time = tn.Time; if (tn.Name == xml.ToneA) t.ToneId = 0; else if (tn.Name == xml.ToneB) t.ToneId = 1; else if (tn.Name == xml.ToneC) t.ToneId = 2; else if (tn.Name == xml.ToneD) t.ToneId = 3; else throw new InvalidDataException("Undefined tone name: " + tn.Name); sng.Tones.Tones[i] = t; }; }
private void parseSections(Song2014 xml, Sng2014File sng) { sng.Sections = new SectionSection(); sng.Sections.Count = xml.Sections.Length; sng.Sections.Sections = new Section[sng.Sections.Count]; for (int i = 0; i < sng.Sections.Count; i++) { var section = xml.Sections[i]; var s = new Section(); readString(section.Name, s.Name); s.Number = section.Number; s.StartTime = section.StartTime; if (i + 1 < sng.Sections.Count) s.EndTime = xml.Sections[i + 1].StartTime; else s.EndTime = xml.SongLength; s.StartPhraseIterationId = getPhraseIterationId(xml, s.StartTime, false); s.EndPhraseIterationId = getPhraseIterationId(xml, s.EndTime, true); for (int j = getMaxDifficulty(xml); j >= 0; j--) { // used string mask for section at all difficulty j Byte mask = 0; foreach (var note in xml.Levels[j].Notes) if (note.Time >= s.StartTime && note.Time < s.EndTime) { mask |= (Byte)(1 << note.String); } foreach (var chord in xml.Levels[j].Chords) if (chord.Time >= s.StartTime && chord.Time < s.EndTime) { var ch = xml.ChordTemplates[chord.ChordId]; if (ch.Fret0 != -1) mask |= (Byte)(1 << 0); if (ch.Fret1 != -1) mask |= (Byte)(1 << 1); if (ch.Fret2 != -1) mask |= (Byte)(1 << 2); if (ch.Fret3 != -1) mask |= (Byte)(1 << 3); if (ch.Fret4 != -1) mask |= (Byte)(1 << 4); if (ch.Fret5 != -1) mask |= (Byte)(1 << 5); if (mask == 0x3F) break; } // use mask from next section if there are no notes if (mask == 0 && j < getMaxDifficulty(xml)) mask = s.StringMask[j + 1]; s.StringMask[j] = mask; } sng.Sections.Sections[i] = s; } }
private void parsePhrases(Song2014 xml, Sng2014File sng) { sng.Phrases = new PhraseSection(); sng.Phrases.Count = xml.Phrases.Length; sng.Phrases.Phrases = new Phrase[sng.Phrases.Count]; for (int i = 0; i < sng.Phrases.Count; i++) { var phrase = xml.Phrases[i]; var p = new Phrase(); p.Solo = phrase.Solo; p.Disparity = phrase.Disparity; p.Ignore = phrase.Ignore; p.MaxDifficulty = phrase.MaxDifficulty; p.PhraseIterationLinks = xml.PhraseIterations.Count(iter => iter.PhraseId == i); readString(phrase.Name, p.Name); sng.Phrases.Phrases[i] = p; } }
private void parsePhraseIterations(Song2014 xml, Sng2014File sng) { sng.PhraseIterations = new PhraseIterationSection(); sng.PhraseIterations.Count = xml.PhraseIterations.Length; sng.PhraseIterations.PhraseIterations = new PhraseIteration[sng.PhraseIterations.Count]; for (int i = 0; i < sng.PhraseIterations.Count; i++) { var piter = xml.PhraseIterations[i]; var p = new PhraseIteration(); p.PhraseId = piter.PhraseId; p.StartTime = piter.Time; if (i + 1 < sng.PhraseIterations.Count) p.NextPhraseTime = xml.PhraseIterations[i + 1].Time; else p.NextPhraseTime = xml.SongLength; // default to (0, 0, max) // they use Medium (previous) value if there is hero=3 missing p.Difficulty[2] = xml.Phrases[p.PhraseId].MaxDifficulty; if (piter.HeroLevels != null) foreach (var h in piter.HeroLevels) p.Difficulty[h.Hero - 1] = h.Difficulty; sng.PhraseIterations.PhraseIterations[i] = p; } }
private void parseDNAs(Song2014 xml, Sng2014File sng) { sng.DNAs = new DnaSection(); List<Dna> dnas = new List<Dna>(); DNACount = new int[4]; // based on events: dna_none=0, dna_solo=1, dna_riff=2, dna_chord=3 foreach (var e in xml.Events) { Int32 id = -1; switch (e.Code) { case "dna_none": id = 0; break; case "dna_solo": id = 1; break; case "dna_riff": id = 2; break; case "dna_chord": id = 3; break; } if (id != -1) { var dna = new Dna(); dna.Time = e.Time; dna.DnaId = id; DNACount[id] += 1; dnas.Add(dna); } } sng.DNAs.Dnas = dnas.ToArray(); sng.DNAs.Count = sng.DNAs.Dnas.Length; }
private void parseEbeats(Song2014 xml, Sng2014File sng) { sng.BPMs = new BpmSection(); sng.BPMs.Count = xml.Ebeats.Length; sng.BPMs.BPMs = new Bpm[sng.BPMs.Count]; Int16 measure = 0; Int16 beat = 0; for (int i = 0; i < sng.BPMs.Count; i++) { var ebeat = xml.Ebeats[i]; var bpm = new Bpm(); bpm.Time = ebeat.Time; if (ebeat.Measure >= 0) { measure = ebeat.Measure; beat = 0; } else { beat++; } bpm.Measure = measure; bpm.Beat = beat; bpm.PhraseIteration = getPhraseIterationId(xml, bpm.Time, true); if (beat == 0) { bpm.Mask |= 1; if (measure % 2 == 0) bpm.Mask |= 2; } sng.BPMs.BPMs[i] = bpm; } }
private int getNoteCount(Sng2014File sng, int Level) { // time => note count var notes = new Dictionary<float, int>(); var level = new Dictionary<float, int>(); for (int i = sng.Arrangements.Count - 1; i >= 0; i--) { var a = sng.Arrangements.Arrangements[i]; foreach (var n in a.Notes.Notes) { if (i > sng.PhraseIterations.PhraseIterations[n.PhraseIterationId].Difficulty[Level]) // this note is above requested level continue; if (!notes.ContainsKey(n.Time)) { // 1 note at difficulty i notes[n.Time] = 1; level[n.Time] = i; } else if (i == level[n.Time]) { // we can add notes while still in the same difficulty notes[n.Time] += 1; } } } int count = 0; foreach (var time_count in notes.Values) count += time_count; return count; }
private void parseMetadata(Song2014 xml, Sng2014File sng, Int16[] tuning) { // Easy, Medium, Hard NoteCount = new int[3]; NoteCount[0] = getNoteCount(sng, 0); NoteCount[1] = getNoteCount(sng, 1); NoteCount[2] = getNoteCount(sng, 2); sng.Metadata = new Metadata(); sng.Metadata.MaxScore = 100000; sng.Metadata.MaxDifficulty = getMaxDifficulty(xml); sng.Metadata.MaxNotesAndChords = NoteCount[2]; sng.Metadata.MaxNotesAndChords_Real = sng.Metadata.MaxNotesAndChords;//num unique notes+not ignored sng.Metadata.PointsPerNote = sng.Metadata.MaxScore / sng.Metadata.MaxNotesAndChords; sng.Metadata.FirstBeatLength = xml.Ebeats[1].Time - xml.Ebeats[0].Time; sng.Metadata.StartTime = xml.Offset * -1; sng.Metadata.CapoFretId = (xml.Capo == 0) ? unchecked((Byte)(-1)) : xml.Capo; readString(xml.LastConversionDateTime, sng.Metadata.LastConversionDateTime); sng.Metadata.Part = xml.Part; sng.Metadata.SongLength = xml.SongLength; sng.Metadata.StringCount = 6; sng.Metadata.Tuning = tuning ?? new Int16[sng.Metadata.StringCount]; // calculated when parsing arrangements sng.Metadata.Unk11_FirstNoteTime = first_note_time; sng.Metadata.Unk12_FirstNoteTime = first_note_time; }
/// <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; } }
private void parseActions(Song2014 xml, Sng2014File sng) { // there is no XML example, EOF does not support it either sng.Actions = new ActionSection(); sng.Actions.Count = 0; sng.Actions.Actions = new Action[sng.Actions.Count]; // no RS2 SNG is using this // for (int i = 0; i < sng.Actions.Count; i++) { // //var action = xml.?[i]; // var a = new Action(); // //a.Time = action.Time; // //read_string(action.ActionName, a.ActionName); // sng.Actions.Actions[i] = a; // } }
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; } }
private void parseTones(Song2014 xml, Sng2014File sng) { sng.Tones = new ToneSection(); if (xml.Tones != null) sng.Tones.Count = xml.Tones.Length; else sng.Tones.Count = 0; sng.Tones.Tones = new Tone[sng.Tones.Count]; for (int i = 0; i < sng.Tones.Count; i++) { var tn = xml.Tones[i]; var t = new Tone(); t.Time = tn.Time; try { // fix for undefined tone name (tone name should be shorter) if (xml.ToneBase.ToLower().Contains(tn.Name.ToLower())) t.ToneId = 0; if (xml.ToneA.ToLower().Contains(tn.Name.ToLower())) t.ToneId = 0; else if (xml.ToneB.ToLower().Contains(tn.Name.ToLower())) t.ToneId = 1; else if (xml.ToneC.ToLower().Contains(tn.Name.ToLower())) t.ToneId = 2; else if (xml.ToneD.ToLower().Contains(tn.Name.ToLower())) t.ToneId = 3; else throw new InvalidDataException("Undefined tone name: " + tn.Name); sng.Tones.Tones[i] = t; } catch (Exception) { throw new InvalidDataException(@"There is tone name error in XML Arrangement: " + xml.Arrangement + " " + tn.Name + " is not properly defined." + "Use EOF to re-author custom tones or Notepad to attempt manual repair." ); } } }
public static Sng2014File ReadVocals(Stream xmlData) { var data = new MemoryStream(Resources.VOCALS_RS2); var sng = new Sng2014File(data); var xml = new XmlStreamingDeserializer<Vocals>(new StreamReader(xmlData)).Deserialize(); Sng2014FileWriter.parseVocals(xml, sng); return sng; }