private List <MidiEvent> CreateTrack(Xmk xmk, int index) { List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent(!string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {index}", MetaEventType.SequenceTrackName, 0)); foreach (var entry in xmk.Entries) { long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); int velocity = entry.Unknown2 == 0 ? 100 : entry.Unknown2 % 128; // Text event? if (!string.IsNullOrEmpty(entry.Text)) { track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start)); } if ((end - start) <= 0 || entry.Pitch > 127) { continue; } track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, entry.Pitch, velocity)); track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, entry.Pitch, velocity)); } // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
private List <MidiEvent> ParseGuitar6(Xmk xmk, bool guitar = true) { MidiMapping map = _guitarTouchMap; List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent(guitar ? "PART GUITAR" : "PART BASS", MetaEventType.SequenceTrackName, 0)); foreach (var entry in xmk.Entries) { long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); int velocity = 100; // Text event? if (!string.IsNullOrEmpty(entry.Text)) { continue; // Don't write text events } //track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start)); if ((end - start) <= 0 || entry.Pitch > 127) { continue; } int pitchRemap = map[entry.Pitch]; if (pitchRemap == -1) { continue; } track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitchRemap, velocity)); track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitchRemap, velocity)); } // Adds play event var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault(); if (firstNote != null) { var idx = track.IndexOf(firstNote); track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime)); } // Adds idle event (end) var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault(); if (lastNote != null) { var idx = track.IndexOf(lastNote); track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime)); } // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
private List <MidiEvent> ParseEvents(Xmk xmk) { MidiMapping map = _guitarMap; List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent("EVENTS", MetaEventType.SequenceTrackName, 0)); foreach (var entry in xmk.Entries) { if (string.IsNullOrEmpty(entry.Text) || entry.Unknown3 != 3) { continue; // Practice section } long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); string text = GetPracticeName(entry.Text); track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.TextEvent, start)); } // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
private List <MidiEvent> ParseVocals(Xmk xmk) { List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent("PART VOCALS", MetaEventType.SequenceTrackName, 0)); const int VOCALS_PHRASE = 105; //const int VOCALS_MAX_PITCH = 84; const int VOCALS_MIN_PITCH = 36; var phraseEvents = new List <NoteEvent>(); foreach (var entry in xmk.Entries) { long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); int velocity = 100; if (!string.IsNullOrEmpty(entry.Text) && entry.Unknown3 == 57) { // Lyric + pitch event string text = entry.Text; int pitch = entry.Pitch; text = text.Replace("=", string.Empty); text = text.Replace("@", "+"); if (entry.Pitch < VOCALS_MIN_PITCH) { text = text + "#"; pitch = 60; // Middle C } track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.Lyric, start)); track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitch, velocity)); track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitch, velocity)); } else if (entry.Unknown3 == 1 && entry.Pitch == 129) { // Vocal phrase if ((end - start) <= 0) { end = start + DELTA_TICKS_PER_QUARTER / 4; // 1/16 note } phraseEvents.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, velocity)); phraseEvents.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, velocity)); } } // Extends phrases for (int i = 1; i < phraseEvents.Count - 1; i += 2) { // i = end, i+1 = start var endTime = phraseEvents[i + 1].AbsoluteTime; phraseEvents[i].AbsoluteTime = endTime; } if (phraseEvents.Count > 0) { // Extends first phrase phraseEvents.First().AbsoluteTime = 0; // Extends last phrase phraseEvents.Last().AbsoluteTime = Math.Max(phraseEvents.Last().AbsoluteTime, track.Max(x => x.AbsoluteTime)) + DELTA_TICKS_PER_QUARTER / 8; } else { // No phrases found (Create one) phraseEvents.Add(new NoteEvent(0, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, 100)); phraseEvents.Add(new NoteEvent(track.Max(x => x.AbsoluteTime) + DELTA_TICKS_PER_QUARTER / 8, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, 100)); } // Shrinks phrases for (int i = 0; i < phraseEvents.Count; i += 2) { NoteEvent start = phraseEvents[i]; NoteEvent end = phraseEvents[i + 1]; var betweenNotes = track.Where(x => x is NoteEvent && ((NoteEvent)x).NoteNumber != VOCALS_PHRASE && x.AbsoluteTime >= start.AbsoluteTime && x.AbsoluteTime <= end.AbsoluteTime).ToList(); if (betweenNotes.Count <= 0) { continue; // No notes between phrases } var startEvent = betweenNotes .Where(x => ((NoteEvent)x).CommandCode == MidiCommandCode.NoteOn) .OrderBy(y => y.AbsoluteTime).FirstOrDefault(); var endEvent = betweenNotes .Where(x => ((NoteEvent)x).CommandCode == MidiCommandCode.NoteOff) .OrderBy(y => y.AbsoluteTime).LastOrDefault(); if (startEvent == null || endEvent == null || startEvent.AbsoluteTime > endEvent.AbsoluteTime) { continue; // No full notes between phrases } // 1/128th note start.AbsoluteTime = startEvent.AbsoluteTime - (DELTA_TICKS_PER_QUARTER / 32); end.AbsoluteTime = endEvent.AbsoluteTime + (DELTA_TICKS_PER_QUARTER / 32); track.Add(start); track.Add(end); } // Adds play event var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault(); if (firstNote != null) { var idx = track.IndexOf(firstNote); track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime)); } // Adds idle event (end) var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault(); if (lastNote != null) { var idx = track.IndexOf(lastNote); track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime)); } // Sort by absolute time (And ensure track name is first event) track.Sort((x, y) => (int)(x is NAudio.Midi.TextEvent && ((NAudio.Midi.TextEvent)x).MetaEventType == MetaEventType.SequenceTrackName ? int.MinValue : x.AbsoluteTime - y.AbsoluteTime)); // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
private List <MidiEvent> ParseGuitar3(Xmk xmk, bool guitar = true) { MidiMapping map = _guitarMap; List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent(guitar ? "PART GUITAR GHL" : "PART BASS GHL", MetaEventType.SequenceTrackName, 0)); // Tracks HOPO on/off events List <NoteOnEvent> hopoNotes = new List <NoteOnEvent>(); foreach (var entry in xmk.Entries) { long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); int velocity = 100; // Text event? if (!string.IsNullOrEmpty(entry.Text)) { continue; // Don't write text events } //track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start)); if ((end - start) <= 0 || entry.Pitch > 127) { continue; } if ((entry.Unknown2 & 2) == 2) // Observed 0x02, 0xCA { // Barre chord int shift = (entry.Pitch % 2 == 1) ? 1 : -1; track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, map[entry.Pitch + shift], velocity)); track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, map[entry.Pitch + shift], velocity)); } int pitchRemap = map[entry.Pitch]; if (pitchRemap == -1) { continue; } track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitchRemap, velocity)); track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitchRemap, velocity)); int hopoPitch; // Sets forced HOPO off pitch if (pitchRemap >= 94 && pitchRemap <= 100) { hopoPitch = 102; // Expert } else if (pitchRemap >= 82 && pitchRemap <= 88) { hopoPitch = 90; // Hard } else if (pitchRemap >= 70 && pitchRemap <= 76) { hopoPitch = 78; // Medium } else if (pitchRemap >= 58 && pitchRemap <= 64) { hopoPitch = 66; // Easy } else { continue; } hopoPitch -= (entry.Unknown3 & 0x80) >> 7; // 1 = Forced HOPO hopoNotes.Add(new NoteOnEvent(start, 1, hopoPitch, velocity, (int)(end - start))); } // Flattens HOPO events var groupedNotes = hopoNotes.GroupBy(x => x.NoteNumber); foreach (var group in groupedNotes) { // Selects longest note at each absolute time offset var notes = group.GroupBy(x => x.AbsoluteTime).Select(y => y.OrderByDescending(z => z.NoteLength).First()).OrderBy(q => q.AbsoluteTime); NoteOnEvent prevNote = notes.First(); track.Add(prevNote); foreach (var note in notes.Skip(1)) { if (note.AbsoluteTime >= prevNote.OffEvent.AbsoluteTime) { track.Add(prevNote.OffEvent); } else { // Overlap detected, insert note off event track.Add(new NoteEvent(note.AbsoluteTime, note.Channel, MidiCommandCode.NoteOff, note.NoteNumber, note.Velocity)); } track.Add(note); prevNote = note; } // Adds last note off event track.Add(prevNote.OffEvent); } // Adds play event var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault(); if (firstNote != null) { var idx = track.IndexOf(firstNote); track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime)); } // Adds idle event (end) var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault(); if (lastNote != null) { var idx = track.IndexOf(lastNote); track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime)); } // Sorts by absolute time track.Sort((x, y) => { if (x.AbsoluteTime < y.AbsoluteTime) { return(-1); } else if (x.AbsoluteTime > y.AbsoluteTime) { return(1); } // Same abs time, note off goes first if (x.CommandCode == MidiCommandCode.NoteOff && y.CommandCode == MidiCommandCode.NoteOn) { return(-1); } else if (x.CommandCode == MidiCommandCode.NoteOn && y.CommandCode == MidiCommandCode.NoteOff) { return(1); } else { return(0); } }); // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
public void Export(string path, bool remap = false) { MidiEventCollection mid = new MidiEventCollection(1, DELTA_TICKS_PER_QUARTER); _xmks.Sort((x, y) => GetSortNumber(x.Name) - GetSortNumber(y.Name)); int GetSortNumber(string name) { switch (name) { case "touchdrums": return(1); case "touchguitar": return(2); case "guitar_3x2": return(3); case "vocals": return(4); case "control": return(5); default: return(100); } } Xmk firstXmk = _xmks.FirstOrDefault(); mid.AddTrack(CreateTempoTrack(firstXmk.TempoEntries, firstXmk.TimeSignatureEntries)); if (!remap) { for (int i = 0; i < _xmks.Count; i++) { mid.AddTrack(CreateTrack(_xmks[i], i)); } } else { var eventsIdx = -1; for (int i = 0; i < _xmks.Count; i++) { Xmk xmk = _xmks[i]; string trackName = !string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {i}"; // Sets remapping and track name switch (trackName.ToLower()) { case "control": // EVENTS mid.AddTrack(ParseEvents(xmk)); eventsIdx = i + 1; continue; case "guitar_3x2": // PART GUITAR GHL mid.AddTrack(ParseGuitar3(xmk)); continue; case "touchdrums": // PART DRUMS mid.AddTrack(ParseDrums(xmk)); continue; case "touchguitar": // PART GUITAR mid.AddTrack(ParseGuitar6(xmk)); continue; case "vocals": // PART VOCALS mid.AddTrack(ParseVocals(xmk)); continue; } mid.AddTrack(CreateTrack(xmk, i)); } // Updates EVENTS track var firstPlay = mid.SelectMany(x => x) .Where(y => y is TextEvent && ((TextEvent)y).Text == "[play]") .OrderBy(z => z.AbsoluteTime) .FirstOrDefault(); var lastIdle = mid.SelectMany(x => x) .Where(y => y is TextEvent && ((TextEvent)y).Text == "[idle]") .OrderByDescending(z => z.AbsoluteTime) .FirstOrDefault(); if (eventsIdx != -1 && firstPlay != null) { var events = mid[eventsIdx]; var nextEvent = events.OrderBy(x => x.AbsoluteTime).First(y => y.AbsoluteTime > firstPlay.AbsoluteTime); var nextEventIdx = events.IndexOf(nextEvent); events.Insert(nextEventIdx, new TextEvent("[music_start]", MetaEventType.TextEvent, firstPlay.AbsoluteTime)); } if (eventsIdx != -1 && lastIdle != null) { var events = mid[eventsIdx]; var trackEnd = events.Last(); trackEnd.AbsoluteTime = lastIdle.AbsoluteTime; // Updates end track position events.Remove(trackEnd); events.Add(new TextEvent("[music_end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime)); events.Add(new TextEvent("[end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime)); events.Add(trackEnd); // Adds end track back } // Generates up/down events for BEAT track (Needed for beat markers in CH and OD in RB) mid.AddTrack(GenerateBeatTrack(firstXmk.TimeSignatureEntries, mid)); } MidiFile.Export(path, mid); }
public XmkExport(Xmk xmk) { _xmks = new List <Xmk>(); _xmks.Add(xmk); }
private List <MidiEvent> ParseVocals(Xmk xmk) { string nameApp = ""; // Gets name suffix if (_voxRegex.IsMatch(xmk.Name)) { var match = _voxRegex.Match(xmk.Name); nameApp = Regex.Replace($"{match.Groups[2]}", @"[-]+", " ").ToUpper(); } List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent($"PART VOCALS{nameApp}", MetaEventType.SequenceTrackName, 0)); const int VOCALS_PHRASE = 105; const int DEFAULT_VELOCITY = 100; const int DEFAULT_CHANNEL = 1; //const int VOCALS_MAX_PITCH = 84; const int VOCALS_MIN_PITCH = 36; var phraseEvents = new List <NoteEvent>(); var notes = new List <(long Start, long End, string Lyric)>(); foreach (var entry in xmk.Entries) { long start = GetAbsoluteTime(entry.Start * 1000); long end = GetAbsoluteTime(entry.End * 1000); if (!string.IsNullOrEmpty(entry.Text) && (entry.Unknown3 == 57 || (xmk.Version == 5 && entry.Unknown3 == 16))) { // Lyric + pitch event string text = entry.Text; int pitch = entry.Pitch; // Get RB lyric syntax text = FSGLyricToRB(text); // If pitch is out of range, make unpitched - Problem solved! if (entry.Pitch < VOCALS_MIN_PITCH) { text = text + "#"; pitch = 60; // Middle C } track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.Lyric, start)); track.Add(new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, pitch, DEFAULT_VELOCITY)); track.Add(new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, pitch, DEFAULT_VELOCITY)); notes.Add((start, end, text)); // Keep track of vox notes } else if ((entry.Unknown3 == 1 && entry.Pitch == 129) || (xmk.Version == 5 && entry.Unknown3 == 17 && entry.Pitch == 20)) { // Vocal phrase if ((end - start) <= 0) { end = start + DELTA_TICKS_PER_QUARTER / 4; // 1/16 note } phraseEvents.Add(new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, VOCALS_PHRASE, DEFAULT_VELOCITY)); phraseEvents.Add(new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, VOCALS_PHRASE, DEFAULT_VELOCITY)); } } // Extends phrases for (int i = 1; i < phraseEvents.Count - 1; i += 2) { // i = end, i+1 = start var endTime = phraseEvents[i + 1].AbsoluteTime; phraseEvents[i].AbsoluteTime = endTime; } if (phraseEvents.Count > 0) { var firstPhrase = phraseEvents.First(); var firstLyric = track .Where(x => x is TextEvent && (x as TextEvent).MetaEventType == MetaEventType.Lyric) .OrderBy(x => x.AbsoluteTime) .FirstOrDefault(); // If first lyric is placed at least a quarter note before first phrase, then add additional phrase before to fill gap if (!(firstLyric is null) && (firstPhrase.AbsoluteTime - firstLyric.AbsoluteTime) > DELTA_TICKS_PER_QUARTER) { long start = firstLyric.AbsoluteTime; long end = firstPhrase.AbsoluteTime; phraseEvents.Insert(0, new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, VOCALS_PHRASE, DEFAULT_VELOCITY)); phraseEvents.Insert(1, new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, VOCALS_PHRASE, DEFAULT_VELOCITY)); } // Extends first phrase phraseEvents.First().AbsoluteTime = 0; // Extends last phrase phraseEvents.Last().AbsoluteTime = Math.Max(phraseEvents.Last().AbsoluteTime, track.Max(x => x.AbsoluteTime)) + DELTA_TICKS_PER_QUARTER / 8; } else { // No phrases found (Create one) phraseEvents.Add(new NoteEvent(0, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, 100)); phraseEvents.Add(new NoteEvent(track.Max(x => x.AbsoluteTime) + DELTA_TICKS_PER_QUARTER / 8, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, 100)); } // Shrinks phrases for (int i = 0; i < phraseEvents.Count; i += 2) { NoteEvent start = phraseEvents[i]; NoteEvent end = phraseEvents[i + 1]; var betweenNotes = notes .Where(x => x.Start >= start.AbsoluteTime && x.Start < end.AbsoluteTime) .OrderBy(x => x.Start) // Should already be sorted but you never know .ToList(); if (betweenNotes.Count <= 0) { continue; // No notes between phrases } var startNoteTime = betweenNotes .Select(x => x.Start) .First(); var endNoteTime = betweenNotes .Select(x => x.End) .Last(); // 1/128th note start.AbsoluteTime = startNoteTime - (DELTA_TICKS_PER_QUARTER / 32); end.AbsoluteTime = endNoteTime + (DELTA_TICKS_PER_QUARTER / 32); track.Add(start); track.Add(end); } // Adds play event var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault(); if (firstNote != null) { var idx = track.IndexOf(firstNote); track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime)); } // Adds idle event (end) var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault(); if (lastNote != null) { var idx = track.IndexOf(lastNote); track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime)); } // Sort by absolute time (And ensure track name is first event) track.Sort((x, y) => (int)(x is NAudio.Midi.TextEvent && ((NAudio.Midi.TextEvent)x).MetaEventType == MetaEventType.SequenceTrackName ? int.MinValue : x.AbsoluteTime - y.AbsoluteTime)); // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
public void Export(string path) { MidiEventCollection mid = new MidiEventCollection(1, DELTA_TICKS_PER_QUARTER); _xmks.Sort((x, y) => GetSortNumber(x.Name) - GetSortNumber(y.Name)); int GetSortNumber(string name) { switch (name) { case "touchdrums": return(1); case "touchguitar": return(2); case "guitar_3x2": return(3); case "vocals": return(4); case "control": return(5); default: return(100); } } // Get xmk track to use for tempo map + generating BEAT track // Note: Avoiding "touchdrums" because it was an under-developed feature so it may not consistently be as accurate compared to 6 fret var tempoTrackPreference = new[] { "guitar_3x2", "touchguitar", "vocals", "control" }; Xmk firstXmk = _xmks .Select(x => { var idx = Array.FindIndex(tempoTrackPreference, y => y == x.Name); if (idx == -1) { idx = 100; } return(Xmk: x, Order: idx); }) .OrderBy(x => x.Order) .Select(x => x.Xmk) .First(); mid.AddTrack(CreateTempoTrack(firstXmk.TempoEntries, firstXmk.TimeSignatureEntries)); if (!_exportOptions.Remap) { for (int i = 0; i < _xmks.Count; i++) { mid.AddTrack(CreateTrack(_xmks[i], i)); } } else { var eventsIdx = -1; for (int i = 0; i < _xmks.Count; i++) { Xmk xmk = _xmks[i]; string trackName = !string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {i}"; // Sets remapping and track name switch (trackName.ToLower()) { case "control": // EVENTS mid.AddTrack(ParseEvents(xmk)); eventsIdx = i + 1; continue; case "guitar_3x2": // PART GUITAR GHL mid.AddTrack(ParseGuitar3(xmk)); continue; case "touchdrums": // PART DRUMS mid.AddTrack(ParseDrums(xmk)); continue; case "touchguitar": // PART GUITAR mid.AddTrack(ParseGuitar5(xmk)); continue; case "vocals": // PART VOCALS mid.AddTrack(ParseVocals(xmk)); continue; } // TODO: Refactor to be more elegant if (_voxRegex.IsMatch(trackName)) { mid.AddTrack(ParseVocals(xmk)); continue; } mid.AddTrack(CreateTrack(xmk, i)); } // Updates EVENTS track var firstPlay = mid.SelectMany(x => x) .Where(y => y is TextEvent && ((TextEvent)y).Text == "[play]") .OrderBy(z => z.AbsoluteTime) .FirstOrDefault(); var lastIdle = mid.SelectMany(x => x) .Where(y => y is TextEvent && ((TextEvent)y).Text == "[idle]") .OrderByDescending(z => z.AbsoluteTime) .FirstOrDefault(); if (eventsIdx != -1 && firstPlay != null) { var events = mid[eventsIdx]; var nextEvent = events .OrderBy(y => y.AbsoluteTime) .FirstOrDefault(z => z.AbsoluteTime > firstPlay.AbsoluteTime); // If nextEvent is null, assume no text events exist var nextEventIdx = (nextEvent is null) ? 1 : events.IndexOf(nextEvent); events.Insert(nextEventIdx, new TextEvent("[music_start]", MetaEventType.TextEvent, firstPlay.AbsoluteTime)); } if (eventsIdx != -1 && lastIdle != null) { var events = mid[eventsIdx]; var trackEnd = events.Last(); trackEnd.AbsoluteTime = lastIdle.AbsoluteTime; // Updates end track position events.Remove(trackEnd); events.Add(new TextEvent("[music_end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime)); events.Add(new TextEvent("[end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime)); events.Add(trackEnd); // Adds end track back } // Generates up/down events for BEAT track (Needed for beat markers in CH and OD in RB) mid.AddTrack(GenerateBeatTrack(firstXmk.TimeSignatureEntries, mid)); } MidiFile.Export(path, mid); }
public static Xmk FromStream(Stream stream, string filePath = "") { AwesomeReader ar = new AwesomeReader(stream, true); Xmk xmk = new Xmk(); xmk._filePath = filePath; // Reads header info xmk.Version = ar.ReadInt32(); xmk.Hash = ar.ReadInt32(); int entryCount = ar.ReadInt32(); int blobSize = ar.ReadInt32(); xmk.Unknown1 = ar.ReadUInt32(); int tempoCount = ar.ReadInt32(); int tsCount = ar.ReadInt32(); // Parses tempo map for (int i = 0; i < tempoCount; i++) { XmkTempo entry = new XmkTempo() { Ticks = ar.ReadUInt32(), Start = ar.ReadSingle(), MicroPerQuarter = ar.ReadUInt32() }; xmk.TempoEntries.Add(entry); } // Parse time signatures for (int i = 0; i < tsCount; i++) { XmkTimeSignature ts = new XmkTimeSignature() { Ticks = ar.ReadUInt32(), Measure = ar.ReadInt32(), Numerator = ar.ReadInt32(), Denominator = ar.ReadInt32() }; xmk.TimeSignatureEntries.Add(ts); } // Reads in strings long startOffset = ar.BaseStream.Position; ar.BaseStream.Seek(entryCount * 24, SeekOrigin.Current); xmk.StringBlob = ar.ReadBytes(blobSize); Dictionary <long, string> words = ParseBlob(xmk.StringBlob); ar.BaseStream.Seek(startOffset, SeekOrigin.Begin); // Parses events for (int i = 0; i < entryCount; i++) { XmkEvent entry = new XmkEvent() { Unknown1 = ar.ReadUInt32(), Unknown2 = ar.ReadUInt16(), Unknown3 = ar.ReadByte(), Pitch = ar.ReadByte(), Start = ar.ReadSingle(), End = ar.ReadSingle(), Unknown4 = ar.ReadUInt32() }; // Adds text int offset = ar.ReadInt32() - (entryCount * 24); if (offset >= 0 && words.ContainsKey(offset)) { entry.Text = words[offset]; } xmk.Entries.Add(entry); } return(xmk); }