private static void UpdateTrackEnd(ICollection <MidiEvent> events, long?newTime = null) { var endTrack = events.SingleOrDefault(MidiEvent.IsEndTrack); long absoluteTime; if (newTime == null) { var lastEvent = events.Where(e => !MidiEvent.IsEndTrack(e)).OrderBy(e => e.AbsoluteTime).LastOrDefault(); absoluteTime = lastEvent?.AbsoluteTime ?? 0; } else { absoluteTime = newTime.Value; } if (endTrack == null) { events.Add(new MetaEvent(MetaEventType.EndTrack, 0, absoluteTime)); } else { events.Remove(endTrack); endTrack.AbsoluteTime = absoluteTime; events.Add(endTrack); } }
private bool RemoveTrackIfEmpty(MidiEventCollection midi, int trackNumber) { // If a track only has an EndTrack (and optionally a name) then it is "empty" if (midi[trackNumber].All(e => e.IsSequenceTrackName() || MidiEvent.IsEndTrack(e))) { midi.RemoveTrack(trackNumber); return(true); } return(false); }
public static MemoryStream ExportMidiToStream(MidiEventCollection events) { //Taken from https://github.com/naudio/NAudio/blob/master/NAudio/Midi/MidiFile.cs#L256 and edited to support MemoryStream export(thanks Mettra) if (events.MidiFileType == 0 && events.Tracks > 1) { throw new ArgumentException("Can't export more than one track to a type 0 file"); } using (MemoryStream stream = new MemoryStream()) { using (var writer = new BinaryWriter(stream)) { writer.Write(Encoding.UTF8.GetBytes("MThd")); writer.Write(SwapUInt32(6)); // chunk size writer.Write(SwapUInt16((ushort)events.MidiFileType)); writer.Write(SwapUInt16((ushort)events.Tracks)); writer.Write(SwapUInt16((ushort)events.DeltaTicksPerQuarterNote)); for (int track = 0; track < events.Tracks; track++) { IList <MidiEvent> eventList = events[track]; writer.Write(Encoding.UTF8.GetBytes("MTrk")); long trackSizePosition = writer.BaseStream.Position; writer.Write(SwapUInt32(0)); long absoluteTime = events.StartAbsoluteTime; // use a stable sort to preserve ordering of MIDI events whose // absolute times are the same MergeSort.Sort(eventList, new MidiEventComparer()); if (eventList.Count > 0) { System.Diagnostics.Debug.Assert(MidiEvent.IsEndTrack(eventList[eventList.Count - 1]), "Exporting a track with a missing end track"); } foreach (var midiEvent in eventList) { midiEvent.Export(ref absoluteTime, writer); } uint trackChunkLength = (uint)(writer.BaseStream.Position - trackSizePosition) - 4; writer.BaseStream.Position = trackSizePosition; writer.Write(SwapUInt32(trackChunkLength)); writer.BaseStream.Position += trackChunkLength; } } stream.Flush(); byte[] bytes = stream.GetBuffer(); return(new MemoryStream(bytes)); } }
public void ConsolidateTracks(MidiEventCollection midi) { // find all of the names var nameCounts = new Dictionary <string, int>(); for (var t = 0; t < midi.Tracks; t++) { var names = midi[t]. OfType <TextEvent>(). Where(e => e.MetaEventType == MetaEventType.SequenceTrackName). ToList(); if (names.Count > 1) { var detail = string.Join(", ", names.Select(n => $"'{n.Text}'")); throw new InvalidOperationException($"Multiple names {detail} on the same track."); } var name = names.FirstOrDefault()?.Text ?? ""; int count; nameCounts.TryGetValue(name, out count); nameCounts[name] = count + 1; } /* For all of the names that appear on more than one track * find all the other tracks that have this name and consolidate them. * We iterate multiple times because the track numbers will change every * time tracks are consolidated. */ foreach (var kvp in nameCounts.Where(kvp => kvp.Value > 1)) { var name = kvp.Key; var list = new List <MidiEvent>(); // iterate in reverse so track numbers don't change mid iteration for (var t = midi.Tracks - 1; t >= 0; t--) { if (!midi[t].OfType <TextEvent>().Any(e => e.IsSequenceTrackName() && e.Text == name)) { continue; } var events = midi[t].Where(e => !MidiEvent.IsEndTrack(e) && !e.IsSequenceTrackName()); list.AddRange(events); midi.RemoveTrack(t); } midi.AddNamedTrack(name, list.OrderBy(e => e.AbsoluteTime)); } }
/// <summary> /// Exports a MIDI file /// </summary> /// <param name="filename">Filename to export to</param> /// <param name="events">Events to export</param> public static void Export(string filename, MidiEventCollection events) { if (events.MidiFileType == 0 && events.Tracks > 1) { throw new ArgumentException("Can't export more than one track to a type 0 file"); } using (var writer = new BinaryWriter(File.Create(filename))) { writer.Write(Encoding.UTF8.GetBytes("MThd")); writer.Write(SwapUInt32((uint)6)); // chunk size writer.Write(SwapUInt16((ushort)events.MidiFileType)); writer.Write(SwapUInt16((ushort)events.Tracks)); writer.Write(SwapUInt16((ushort)events.DeltaTicksPerQuarterNote)); for (int track = 0; track < events.Tracks; track++) { IList <MidiEvent> eventList = events[track]; writer.Write(Encoding.UTF8.GetBytes("MTrk")); long trackSizePosition = writer.BaseStream.Position; writer.Write(SwapUInt32((uint)0)); long absoluteTime = events.StartAbsoluteTime; // use a stable sort to preserve ordering of MIDI events whose // absolute times are the same MergeSort.Sort(eventList, new MidiEventComparer()); if (eventList.Count > 0) { System.Diagnostics.Debug.Assert(MidiEvent.IsEndTrack(eventList[eventList.Count - 1]), "Exporting a track with a missing end track"); } foreach (MidiEvent midiEvent in eventList) { midiEvent.Export(ref absoluteTime, writer); } uint trackChunkLength = (uint)(writer.BaseStream.Position - trackSizePosition) - 4; writer.BaseStream.Position = trackSizePosition; writer.Write(SwapUInt32(trackChunkLength)); writer.BaseStream.Position += trackChunkLength; } } }
public void TestType0() { MidiEventCollection collection = new MidiEventCollection(0, 120); collection.AddEvent(new TextEvent("Test", MetaEventType.TextEvent, 0), 0); collection.AddEvent(new NoteOnEvent(0, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(15, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(30, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(0, 10, 60, 100, 15), 10); collection.AddEvent(new NoteOnEvent(15, 10, 60, 100, 15), 10); collection.AddEvent(new NoteOnEvent(30, 10, 60, 100, 15), 10); Assert.AreEqual(collection.Tracks, 1); collection.PrepareForExport(); Assert.AreEqual(collection.Tracks, 1); IList <MidiEvent> track0 = collection.GetTrackEvents(0); Assert.AreEqual(track0.Count, 8); Assert.IsTrue(MidiEvent.IsEndTrack(track0[track0.Count - 1])); }
public void TestType0ToType1() { MidiEventCollection collection = new MidiEventCollection(0, 120); collection.AddEvent(new TextEvent("Test", MetaEventType.TextEvent, 0), 0); collection.AddEvent(new NoteOnEvent(0, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(15, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(30, 1, 30, 100, 15), 1); collection.AddEvent(new NoteOnEvent(0, 10, 60, 100, 15), 10); collection.AddEvent(new NoteOnEvent(15, 10, 60, 100, 15), 10); collection.AddEvent(new NoteOnEvent(30, 10, 60, 100, 15), 10); Assert.AreEqual(collection.Tracks, 1); collection.MidiFileType = 1; collection.PrepareForExport(); Assert.AreEqual(3, collection.Tracks, "Wrong number of tracks"); IList <MidiEvent> track0 = collection.GetTrackEvents(0); Assert.AreEqual(track0.Count, 2); Assert.AreEqual(collection.GetTrackEvents(1).Count, 4); Assert.AreEqual(collection.GetTrackEvents(2).Count, 4); Assert.IsTrue(MidiEvent.IsEndTrack(track0[track0.Count - 1])); }
private static void ExportBinary(BinaryWriter writer, MidiEventCollection events) { writer.Write(System.Text.Encoding.UTF8.GetBytes("MThd")); writer.Write(SwapUInt32(6)); // chunk size writer.Write(SwapUInt16((ushort)events.MidiFileType)); writer.Write(SwapUInt16((ushort)events.Tracks)); writer.Write(SwapUInt16((ushort)events.DeltaTicksPerQuarterNote)); for (int track = 0; track < events.Tracks; track++) { IList <MidiEvent> eventList = events[track]; writer.Write(System.Text.Encoding.UTF8.GetBytes("MTrk")); long trackSizePosition = writer.BaseStream.Position; writer.Write(SwapUInt32(0)); long absoluteTime = events.StartAbsoluteTime; // use a stable sort to preserve ordering of MIDI events whose // absolute times are the same MergeSort.Sort(eventList, new MidiEventComparer()); if (eventList.Count > 0 && !MidiEvent.IsEndTrack(eventList[eventList.Count - 1])) { Memory.Log.WriteLine("Exporting a track with a missing end track"); } foreach (var midiEvent in eventList) { midiEvent.Export(ref absoluteTime, writer); } uint trackChunkLength = (uint)(writer.BaseStream.Position - trackSizePosition) - 4; writer.BaseStream.Position = trackSizePosition; writer.Write(SwapUInt32(trackChunkLength)); writer.BaseStream.Position += trackChunkLength; } }