private List <MidiEvent> GenerateBeatTrack(List <XmkTimeSignature> timeSigs, MidiEventCollection mid) { List <MidiEvent> track = new List <MidiEvent>(); track.Add(new NAudio.Midi.TextEvent("BEAT", MetaEventType.SequenceTrackName, 0)); long lastEventOffset = mid.SelectMany(x => x).Select(y => y.AbsoluteTime).Max(); long offset = 0; var tsEndOffsets = timeSigs.Skip(1).Select(x => (long)(x.Ticks / 2)).ToList(); tsEndOffsets.Add(lastEventOffset); int tsIdx = 0, currentBeat = 1; while (offset < lastEventOffset) { if (offset >= tsEndOffsets[tsIdx]) { // New time signature currentBeat = 1; tsIdx++; offset = timeSigs[tsIdx].Ticks / 2; } var ts = timeSigs[tsIdx]; int beatSize = DELTA_TICKS_PER_MEASURE / ts.Denominator; int eventSize = beatSize / 4; if (currentBeat > ts.Numerator) { currentBeat = 1; } int pitch = currentBeat == 1 ? 12 : 13; // 12 = down, 13 = up // Adds beat event track.Add(new NoteEvent(offset, 1, MidiCommandCode.NoteOn, pitch, 1)); track.Add(new NoteEvent(offset + eventSize, 1, MidiCommandCode.NoteOff, pitch, 1)); currentBeat++; offset += beatSize; } // Adds end track track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime)); return(track); }
private void init(byte[] bytes) { Stream s = new MemoryStream(bytes); MidiFile midiFIle = new MidiFile(s); mDeltaTicks = midiFIle.DeltaTicksPerQuarterNote; MidiEventCollection events = midiFIle.Events; IEnumerable <MidiEvent> flattened = events.SelectMany(i => i); noteOns = flattened .Where(midiEvent => midiEvent.CommandCode == MidiCommandCode.NoteOn) .Select(m => m as NoteOnEvent); metaEvents = flattened .Where(midiEvent => midiEvent.CommandCode == MidiCommandCode.MetaEvent) .Select(m => m as MetaEvent); tempos = metaEvents .Where(meta => meta.MetaEventType == MetaEventType.SetTempo) .Select(m => m as TempoEvent); //let's assume one tempo for now // TempoEvent firstTempo; if (tempos.Count() > 0) { mMicroSecondsPerQuarterNote = tempos.ToList()[0].MicrosecondsPerQuarterNote; } else { //120 bpm //minute / 120 quarter * microseconds/minute mMicroSecondsPerQuarterNote = 60000000 / 120; } }
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 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); }