├── README.md ├── IByteReader.cs ├── MIDIEvents ├── NoteEvent.cs ├── TrackStartEvent.cs ├── TuneRequestEvent.cs ├── EndOfExclusiveEvent.cs ├── ChannelEvent.cs ├── MIDIPortEvent.cs ├── ChannelPrefixEvent.cs ├── CustomEvent.cs ├── UndefinedEvent.cs ├── SystemExclusiveMessageEvent.cs ├── SongSelectEvent.cs ├── KeySignatureEvent.cs ├── NoteOffEvent.cs ├── ProgramChangeEvent.cs ├── ChannelPressureEvent.cs ├── NoteOnEvent.cs ├── SongPositionPointerEvent.cs ├── ChannelModeMessageEvent.cs ├── TempoEvent.cs ├── PolyphonicKeyPressureEvent.cs ├── ControlChangeEvent.cs ├── TimeSignatureEvent.cs ├── SMPTEOffsetEvent.cs ├── PitchWheelChangeEvent.cs ├── MajorMidiMessageEvent.cs ├── MIDIEvent.cs ├── TextEvent.cs └── ColorEvent.cs ├── Generator ├── EventSequenceExtensions.cs ├── Loop.cs ├── MathHelper.cs ├── Keep.cs ├── Lines.cs ├── Fields.cs ├── TransformExtensions.cs └── NoteSequenceExtensions.cs ├── TrackNote.cs ├── MIDIModificationFramework.csproj ├── Note.cs ├── BatchBlockingCollection.cs ├── Extensions ├── NoteSequenceFunctions.cs ├── EventSequenceExtensions.cs └── ThreadedSequenceFunctions.cs ├── XZ.cs ├── Logic ├── SequenceFunctions.cs ├── NoteConversion.cs └── Mergers.cs ├── MidiFile.cs ├── MidiWriter.cs ├── FastList.cs ├── .gitignore ├── BufferByteReader.cs ├── EventParser.cs └── ParallelStream.cs /README.md: -------------------------------------------------------------------------------- 1 | # MIDIModificationFramework 2 | My framework for reading, writing and modifying MIDI 1.0 files 3 | -------------------------------------------------------------------------------- /IByteReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework 8 | { 9 | interface IByteReader : IDisposable 10 | { 11 | byte Read(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MIDIEvents/NoteEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public abstract class NoteEvent : ChannelEvent 10 | { 11 | public byte Key { get; set; } 12 | 13 | public NoteEvent(double delta, byte key, byte channel) : base(delta, channel) 14 | { 15 | Key = key; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Generator/EventSequenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class EventSequenceExtensions 10 | { 11 | public static IEnumerable CloneEvents(this IEnumerable seq) 12 | where T : MIDIEvent 13 | { 14 | foreach (var n in seq) yield return n.Clone() as T; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Generator/Loop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class Loop 10 | { 11 | public static IEnumerable For(int start, int count, Func action) 12 | { 13 | for(int i = start; i < start + count; i++) 14 | { 15 | yield return action(i); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Generator/MathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class MathHelper 10 | { 11 | public static void Rotate(ref double x, ref double y, double angle) 12 | { 13 | var newy = x * Math.Sin(angle) + y * Math.Cos(angle); 14 | x = x * Math.Cos(angle) - y * Math.Sin(angle); 15 | y = newy; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Generator/Keep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class Keep 10 | { 11 | public static T Value(V1 value, Func func) => func(value); 12 | public static T Value(V1 value, V2 value2, Func func) => func(value, value2); 13 | public static T Value(V1 value, V2 value2, V3 value3, Func func) => func(value, value2, value3); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MIDIEvents/TrackStartEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class TrackStartEvent : MIDIEvent 10 | { 11 | public TrackStartEvent() : base(0) { } 12 | 13 | public override MIDIEvent Clone() 14 | { 15 | return new TrackStartEvent(); 16 | } 17 | 18 | public override byte[] GetData() 19 | { 20 | return new byte[] { 0xFF, 0x00, 0x02 }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Generator/Lines.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class Lines 10 | { 11 | public static IEnumerable BasicLine(int startKey, int count, double offset, double length, int offsetPerKey) 12 | { 13 | for (int i = 0; i < count; i++) 14 | { 15 | yield return new Note(0, (byte)(startKey + offsetPerKey * i), 127, offset * i, offset * i + length); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MIDIEvents/TuneRequestEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class TuneRequestEvent : MIDIEvent 10 | { 11 | public TuneRequestEvent(double delta) : base(delta) { } 12 | 13 | public override MIDIEvent Clone() 14 | { 15 | return new TuneRequestEvent(DeltaTime); 16 | } 17 | 18 | public override byte[] GetData() 19 | { 20 | return new byte[] { 0b11110110 }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MIDIEvents/EndOfExclusiveEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class EndOfExclusiveEvent : MIDIEvent 10 | { 11 | public EndOfExclusiveEvent(double delta) : base(delta) { } 12 | 13 | public override MIDIEvent Clone() 14 | { 15 | return new EndOfExclusiveEvent(DeltaTime); 16 | } 17 | 18 | public override byte[] GetData() 19 | { 20 | return new byte[] { 0b11110111 }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MIDIEvents/ChannelEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public abstract class ChannelEvent : MIDIEvent 10 | { 11 | byte channel; 12 | public byte Channel 13 | { 14 | get { return channel; } 15 | set 16 | { 17 | channel = (byte)(value & 0x0F); 18 | } 19 | } 20 | 21 | public ChannelEvent(double delta, byte channel) : base(delta) 22 | { 23 | Channel = channel; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MIDIEvents/MIDIPortEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class MIDIPortEvent : ChannelEvent 10 | { 11 | public MIDIPortEvent(double delta, byte channel) : base(delta, channel) 12 | { 13 | } 14 | 15 | public override MIDIEvent Clone() 16 | { 17 | return new MIDIPortEvent(DeltaTime, Channel); 18 | } 19 | 20 | public override byte[] GetData() 21 | { 22 | return new byte[] { 0xFF, 0x20, 0x01, Channel }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MIDIEvents/ChannelPrefixEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ChannelPrefixEvent : ChannelEvent 10 | { 11 | public ChannelPrefixEvent(double delta, byte channel) : base(delta, channel) 12 | { 13 | } 14 | 15 | public override MIDIEvent Clone() 16 | { 17 | return new ChannelPrefixEvent(DeltaTime, Channel); 18 | } 19 | 20 | public override byte[] GetData() 21 | { 22 | return new byte[] { 0xFF, 0x20, 0x01, Channel }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MIDIEvents/CustomEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class CustomEvent : MIDIEvent 10 | { 11 | byte[] data; 12 | public CustomEvent(double delta, byte[] data) : base(delta) 13 | { 14 | this.data = data; 15 | } 16 | 17 | public override MIDIEvent Clone() 18 | { 19 | return new CustomEvent(DeltaTime, (byte[])data.Clone()); 20 | } 21 | 22 | public override byte[] GetData() 23 | { 24 | return data; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MIDIEvents/UndefinedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class UndefinedEvent : MIDIEvent 10 | { 11 | public byte Command { get; set; } 12 | 13 | public UndefinedEvent(double delta, byte command) : base(delta) 14 | { 15 | Command = command; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new UndefinedEvent(DeltaTime, Command); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] { Command }; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MIDIEvents/SystemExclusiveMessageEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class SystemExclusiveMessageEvent : MIDIEvent 10 | { 11 | byte[] data; 12 | 13 | public SystemExclusiveMessageEvent(double delta, byte[] data) : base(delta) 14 | { 15 | this.data = data; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new SystemExclusiveMessageEvent(DeltaTime, (byte[])data.Clone()); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return data; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TrackNote.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework 8 | { 9 | public class TrackNote : Note 10 | { 11 | public int Track { get; set; } 12 | 13 | public TrackNote(int track, byte channel, byte key, byte vel, double start, double end) : base(channel, key, vel, start, end) 14 | { 15 | Track = track; 16 | } 17 | 18 | public TrackNote(int track, Note n) : this(track, n.Channel, n.Key, n.Velocity, n.Start, n.End) 19 | { } 20 | 21 | public override Note Clone() 22 | { 23 | return new TrackNote(Track, Channel, Key, Velocity, Start, End); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MIDIEvents/SongSelectEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class SongSelectEvent : MIDIEvent 10 | { 11 | public byte Song { get; set; } 12 | 13 | public SongSelectEvent(double delta, byte song) : base(delta) 14 | { 15 | Song = song; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new SongSelectEvent(DeltaTime, Song); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] 26 | { 27 | 0b11110011, 28 | Song 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MIDIEvents/KeySignatureEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class KeySignatureEvent : MIDIEvent 10 | { 11 | public byte SF { get; set; } 12 | public byte MI { get; set; } 13 | 14 | public KeySignatureEvent(double delta, byte sf, byte mi) : base(delta) 15 | { 16 | SF = sf; 17 | MI = mi; 18 | } 19 | 20 | public override MIDIEvent Clone() 21 | { 22 | return new KeySignatureEvent(DeltaTime, SF, MI); 23 | } 24 | 25 | public override byte[] GetData() 26 | { 27 | return new byte[] { 0xFF, 0x59, 0x02, SF, MI }; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MIDIEvents/NoteOffEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class NoteOffEvent : NoteEvent 10 | { 11 | public byte Velocity { get; set; } 12 | 13 | public NoteOffEvent(double delta, byte channel, byte key) : base(delta, key, channel) 14 | { 15 | Key = key; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new NoteOffEvent(DeltaTime, Channel, Key); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] 26 | { 27 | (byte)(0b10000000 | Channel), 28 | Key, 29 | 0 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MIDIEvents/ProgramChangeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ProgramChangeEvent : ChannelEvent 10 | { 11 | public byte Program { get; set; } 12 | 13 | public ProgramChangeEvent(double delta, byte channel, byte program) : base(delta, channel) 14 | { 15 | Program = program; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new ProgramChangeEvent(DeltaTime, Channel, Program); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] 26 | { 27 | (byte)(0b11000000 | Channel), 28 | Program 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MIDIModificationFramework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netstandard2.0 6 | AnyCPU;x64 7 | 8 | 9 | 10 | true 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | true 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MIDIEvents/ChannelPressureEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ChannelPressureEvent : ChannelEvent 10 | { 11 | public byte Pressure { get; set; } 12 | 13 | public ChannelPressureEvent(double delta, byte channel, byte pressure) : base(delta, channel) 14 | { 15 | Pressure = pressure; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new ChannelPressureEvent(DeltaTime, Channel, Pressure); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] 26 | { 27 | (byte)(0b11010000 | Channel), 28 | Pressure 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MIDIEvents/NoteOnEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class NoteOnEvent : NoteEvent 10 | { 11 | public byte Velocity { get; set; } 12 | 13 | public NoteOnEvent(double delta, byte channel, byte key, byte velocity) : base(delta, key, channel) 14 | { 15 | Key = key; 16 | Velocity = velocity; 17 | } 18 | 19 | public override MIDIEvent Clone() 20 | { 21 | return new NoteOnEvent(DeltaTime, Channel, Key, Velocity); 22 | } 23 | 24 | public override byte[] GetData() 25 | { 26 | return new byte[] 27 | { 28 | (byte)(0b10010000 | Channel), 29 | Key, 30 | Velocity 31 | }; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MIDIEvents/SongPositionPointerEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class SongPositionPointerEvent : MIDIEvent 10 | { 11 | public ushort Location { get; set; } 12 | 13 | public SongPositionPointerEvent(double delta, ushort location) : base(delta) 14 | { 15 | Location = location; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new SongPositionPointerEvent(DeltaTime, Location); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | return new byte[] 26 | { 27 | 0b11110010, 28 | (byte)(Location & 0x7F), 29 | (byte)((Location >> 7) & 0x7F) 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MIDIEvents/ChannelModeMessageEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ChannelModeMessageEvent : ChannelEvent 10 | { 11 | public byte C { get; set; } 12 | public byte V { get; set; } 13 | 14 | public ChannelModeMessageEvent(double delta, byte channel, byte cc, byte vv) : base(delta, channel) 15 | { 16 | C = cc; 17 | V = vv; 18 | } 19 | 20 | public override MIDIEvent Clone() 21 | { 22 | return new ChannelModeMessageEvent(DeltaTime, Channel, C, V); 23 | } 24 | 25 | public override byte[] GetData() 26 | { 27 | return new byte[] 28 | { 29 | (byte)(0b10110000 | Channel), 30 | C, 31 | V 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MIDIEvents/TempoEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class TempoEvent : MIDIEvent 10 | { 11 | public int Tempo { get; set; } 12 | 13 | public TempoEvent(double delta, int tempo) : base(delta) 14 | { 15 | Tempo = tempo; 16 | } 17 | 18 | public override MIDIEvent Clone() 19 | { 20 | return new TempoEvent(DeltaTime, Tempo); 21 | } 22 | 23 | public override byte[] GetData() 24 | { 25 | byte[] data = new byte[6]; 26 | data[0] = 0xFF; 27 | data[1] = 0x51; 28 | data[2] = 0x03; 29 | data[3] = (byte)((Tempo >> 16) & 0xFF); 30 | data[4] = (byte)((Tempo >> 8) & 0xFF); 31 | data[5] = (byte)(Tempo & 0xFF); 32 | return data; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MIDIEvents/PolyphonicKeyPressureEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class PolyphonicKeyPressureEvent : NoteEvent 10 | { 11 | public byte Velocity { get; set; } 12 | 13 | public PolyphonicKeyPressureEvent(double delta, byte channel, byte key, byte velocity) : base(delta, key, channel) 14 | { 15 | Key = key; 16 | Velocity = velocity; 17 | } 18 | 19 | public override MIDIEvent Clone() 20 | { 21 | return new PolyphonicKeyPressureEvent(DeltaTime, Channel, Key, Velocity); 22 | } 23 | 24 | public override byte[] GetData() 25 | { 26 | return new byte[] 27 | { 28 | (byte)(0b10100000 | Channel), 29 | Key, 30 | Velocity 31 | }; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MIDIEvents/ControlChangeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ControlChangeEvent : ChannelEvent 10 | { 11 | public byte Controller { get; set; } 12 | public byte Value { get; set; } 13 | 14 | public ControlChangeEvent(double delta, byte channel, byte controller, byte value) : base(delta, channel) 15 | { 16 | Controller = controller; 17 | Value = value; 18 | } 19 | 20 | public override MIDIEvent Clone() 21 | { 22 | return new ControlChangeEvent(DeltaTime, Channel, Controller, Value); 23 | } 24 | 25 | public override byte[] GetData() 26 | { 27 | return new byte[] 28 | { 29 | (byte)(0b10110000 | Channel), 30 | Controller, 31 | Value 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MIDIEvents/TimeSignatureEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class TimeSignatureEvent : MIDIEvent 10 | { 11 | public byte Numerator { get; set; } 12 | public byte Denominator { get; set; } 13 | public byte TicksPerClick { get; set; } 14 | public byte BB { get; set; } 15 | 16 | public TimeSignatureEvent(double delta, byte nn, byte dd, byte cc, byte bb) : base(delta) 17 | { 18 | Numerator = nn; 19 | Denominator = dd; 20 | TicksPerClick = cc; 21 | BB = bb; 22 | } 23 | 24 | public override MIDIEvent Clone() 25 | { 26 | return new TimeSignatureEvent(DeltaTime, Numerator, Denominator, TicksPerClick, BB); 27 | } 28 | 29 | public override byte[] GetData() 30 | { 31 | return new byte[] 32 | { 33 | 0xFF, 0x58, 0x04, Numerator, Denominator, TicksPerClick, BB 34 | }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MIDIEvents/SMPTEOffsetEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class SMPTEOffsetEvent : MIDIEvent 10 | { 11 | public byte Hours { get; set; } 12 | public byte Minutes { get; set; } 13 | public byte Seconds { get; set; } 14 | public byte Frames { get; set; } 15 | public byte FractionalFrames { get; set; } 16 | 17 | public SMPTEOffsetEvent(double delta, byte hr, byte mn, byte se, byte fr, byte ff) : base(delta) 18 | { 19 | Hours = hr; 20 | Minutes = mn; 21 | Seconds = se; 22 | Frames = fr; 23 | FractionalFrames = ff; 24 | } 25 | 26 | public override MIDIEvent Clone() 27 | { 28 | return new SMPTEOffsetEvent(DeltaTime, Hours, Minutes, Seconds, Frames, FractionalFrames); 29 | } 30 | 31 | public override byte[] GetData() 32 | { 33 | return new byte[] 34 | { 35 | 0xFF, 0x54, 0x05, Hours, Minutes, Seconds, Frames, FractionalFrames 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MIDIEvents/PitchWheelChangeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class PitchWheelChangeEvent : ChannelEvent 10 | { 11 | short value; 12 | public short Value 13 | { 14 | get => value; 15 | set 16 | { 17 | if (value > 8191) this.value = 8191; 18 | else if (value < -8192) this.value = -8192; 19 | else this.value = value; 20 | } 21 | } 22 | 23 | public PitchWheelChangeEvent(double delta, byte channel, short value) : base(delta, channel) 24 | { 25 | Value = value; 26 | } 27 | 28 | public override MIDIEvent Clone() 29 | { 30 | return new PitchWheelChangeEvent(DeltaTime, Channel, Value); 31 | } 32 | 33 | public override byte[] GetData() 34 | { 35 | int val = value + 8192; 36 | return new byte[] 37 | { 38 | (byte)(0b11100000 | Channel), 39 | (byte)(val & 0x7F), 40 | (byte)((val >> 7) & 0x7F) 41 | }; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MIDIEvents/MajorMidiMessageEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class MajorMidiMessageEvent : MIDIEvent 10 | { 11 | byte[] data; 12 | 13 | public MajorMidiMessageEvent(double delta, byte command) : base(delta) 14 | { 15 | data = new byte[] { command }; 16 | } 17 | 18 | public MajorMidiMessageEvent(double delta, byte command, byte var1) : base(delta) 19 | { 20 | data = new byte[] { command, var1 }; 21 | } 22 | 23 | public MajorMidiMessageEvent(double delta, byte command, byte var1, byte var2) : base(delta) 24 | { 25 | data = new byte[] { command, var1, var2 }; 26 | } 27 | 28 | public override MIDIEvent Clone() 29 | { 30 | if (data.Length == 1) 31 | return new MajorMidiMessageEvent(DeltaTime, data[0]); 32 | if (data.Length == 2) 33 | return new MajorMidiMessageEvent(DeltaTime, data[0], data[1]); 34 | if (data.Length == 3) 35 | return new MajorMidiMessageEvent(DeltaTime, data[0], data[1], data[2]); 36 | else throw new Exception("Bad Things™ happened"); 37 | } 38 | 39 | public override byte[] GetData() 40 | { 41 | return data; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Note.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework 8 | { 9 | public class Note 10 | { 11 | double length; 12 | 13 | public double Start { get; set; } 14 | public byte Channel { get; set; } 15 | public byte Key { get; set; } 16 | public byte Velocity { get; set; } 17 | 18 | public double End 19 | { 20 | get => Start + length; 21 | set 22 | { 23 | Length = value - Start; 24 | } 25 | } 26 | 27 | public double Length 28 | { 29 | get => length; 30 | set 31 | { 32 | if (value < -0.00000001) 33 | throw new ArgumentException("Note can not have a negative length"); 34 | length = value; 35 | } 36 | } 37 | 38 | public void SetStartOnly(double newStart) 39 | { 40 | double newLength = End - newStart; 41 | Start = newStart; 42 | Length = newLength; 43 | } 44 | 45 | public Note(byte channel, byte key, byte vel, double start, double end) 46 | { 47 | Channel = channel; 48 | this.Start = start; 49 | this.Length = end - start; 50 | this.Key = key; 51 | this.Velocity = vel; 52 | } 53 | 54 | public virtual Note Clone() 55 | { 56 | return new Note(Channel, Key, Velocity, Start, End); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MIDIEvents/MIDIEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework 8 | { 9 | public abstract class MIDIEvent 10 | { 11 | double deltatime; 12 | public double DeltaTime 13 | { 14 | get => deltatime; 15 | set 16 | { 17 | if (value < -0.0000001) throw new ArgumentException("Negative delta time not allowed", "delta"); 18 | deltatime = value; 19 | } 20 | } 21 | 22 | public MIDIEvent(double delta) 23 | { 24 | DeltaTime = delta; 25 | } 26 | public abstract byte[] GetData(); 27 | 28 | IEnumerable MakeVariableLenFast(int i) 29 | { 30 | var b = new byte[5]; 31 | int len = 4; 32 | byte added = 0x00; 33 | while (true) 34 | { 35 | byte v = (byte)(i & 0x7F); 36 | i = i >> 7; 37 | v = (byte)(v | added); 38 | b[len--] = v; 39 | added = 0x80; 40 | if (i == 0) 41 | { 42 | break; 43 | } 44 | } 45 | return b.Skip(len + 1); 46 | } 47 | 48 | public byte[] MakeVariableLen(int i) 49 | { 50 | return MakeVariableLenFast(i).ToArray(); 51 | } 52 | 53 | public byte[] GetDataWithDelta() 54 | { 55 | if (DeltaTime < 0 || double.IsNaN(DeltaTime)) throw new Exception("Invalid delta time detected: " + DeltaTime); 56 | return MakeVariableLenFast((int)Math.Round(DeltaTime)).Concat(GetData()).ToArray(); 57 | } 58 | 59 | public abstract MIDIEvent Clone(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MIDIEvents/TextEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public enum TextEventType 10 | { 11 | TextEvent = 1, 12 | CopyrightNotice = 2, 13 | TrackName = 3, 14 | InstrumentName = 4, 15 | Lyric = 5, 16 | Marker = 6, 17 | CuePoint = 7, 18 | ProgramName = 8, 19 | DeviceName = 9, 20 | Undefined = 10, 21 | MetaEvent = 0x7F 22 | } 23 | 24 | public class TextEvent : MIDIEvent 25 | { 26 | byte[] data; 27 | 28 | public string Text 29 | { 30 | get => new string(data.Select(b => (char)b).ToArray()); 31 | set 32 | { 33 | data = value.ToArray().Select(c => (byte)c).ToArray(); 34 | } 35 | } 36 | 37 | public byte[] Bytes 38 | { 39 | get => data; 40 | set 41 | { 42 | data = value; 43 | } 44 | } 45 | 46 | public int Length => data.Length; 47 | 48 | public byte Type { get; set; } 49 | 50 | public TextEvent(double delta, byte type, byte[] data) : base(delta) 51 | { 52 | Type = type; 53 | Bytes = data; 54 | } 55 | 56 | public TextEvent(double delta, byte type, string text) : base(delta) 57 | { 58 | Type = type; 59 | Bytes = data; 60 | } 61 | 62 | public override MIDIEvent Clone() 63 | { 64 | return new TextEvent(DeltaTime, Type, (byte[])data.Clone()); 65 | } 66 | 67 | public override byte[] GetData() 68 | { 69 | byte[] len = MakeVariableLen(Length); 70 | return new byte[] { 0xFF, (byte)Type } 71 | .Concat( 72 | len 73 | ).Concat( 74 | data 75 | ).ToArray(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Generator/Fields.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class Fields 10 | { 11 | public static IEnumerable Block(double length, double noteDensity) => Basic(length, noteDensity, (a, b) => true); 12 | 13 | public static IEnumerable Basic(double length, double noteDensity, Func place) 14 | { 15 | double size = 1 / noteDensity; 16 | for(double i = 0; i < length; i += size) 17 | { 18 | var end = i + size; 19 | if (end > length) end = length; 20 | for (byte k = 0; k < 128; k++) 21 | { 22 | if (place(k, i)) 23 | yield return new Note(0, k, 1, i, end); 24 | } 25 | } 26 | } 27 | 28 | public static IEnumerable Basic(double length, double noteDensity, T val, Func place) => 29 | Basic(length, noteDensity, (a, b) => place(a, b, val)); 30 | public static IEnumerable Basic(double length, double noteDensity, T val, T2 val2, Func place) => 31 | Basic(length, noteDensity, (a, b) => place(a, b, val, val2)); 32 | public static IEnumerable Basic(double length, double noteDensity, T val, T2 val2, T3 val3, Func place) => 33 | Basic(length, noteDensity, (a, b) => place(a, b, val, val2, val3)); 34 | 35 | public static IEnumerable> RangeSplit(IEnumerable notes, int tracks, Func func) 36 | { 37 | return Loop.For(0, tracks, i => 38 | notes.Where(n => (func(n) % tracks) - i < 1) 39 | ); 40 | } 41 | 42 | public static IEnumerable> RangeSplit(IEnumerable notes, int tracks, Func func) => 43 | RangeSplit(notes, tracks, (n) => func(n.Key, n.Start)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BatchBlockingCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace MIDIModificationFramework 11 | { 12 | public class BatchBlockingCollection : IEnumerable 13 | { 14 | BlockingCollection> data; 15 | 16 | public int BatchCount => data.Count; 17 | 18 | public bool IsComplete { get; private set; } 19 | 20 | FastList buffer = new FastList(); 21 | int currentBatchSize = 0; 22 | 23 | int batchSize = 1000; 24 | 25 | public BatchBlockingCollection() : this(1000) 26 | { } 27 | 28 | public BatchBlockingCollection(int batchSize) : this(10, batchSize) 29 | { } 30 | 31 | public BatchBlockingCollection(int maxBatches, int batchSize) 32 | { 33 | this.batchSize = batchSize; 34 | data = new BlockingCollection>(maxBatches); 35 | } 36 | 37 | public void Add(T item, CancellationToken cancel) 38 | { 39 | if (IsComplete) 40 | throw new ObjectDisposedException("Already completed adding"); 41 | buffer.Add(item); 42 | currentBatchSize++; 43 | if (currentBatchSize >= batchSize) 44 | { 45 | data.Add(buffer, cancel); 46 | buffer = new FastList(); 47 | currentBatchSize = 0; 48 | } 49 | } 50 | 51 | public void Add(T item) 52 | { 53 | if (IsComplete) throw new ObjectDisposedException("Already completed adding"); 54 | buffer.Add(item); 55 | currentBatchSize++; 56 | if (currentBatchSize >= batchSize) 57 | { 58 | data.Add(buffer); 59 | buffer = new FastList(); 60 | currentBatchSize = 0; 61 | } 62 | } 63 | 64 | public void Complete() 65 | { 66 | if(currentBatchSize > 0) 67 | { 68 | data.Add(buffer); 69 | buffer = new FastList(); 70 | currentBatchSize = 0; 71 | } 72 | data.CompleteAdding(); 73 | IsComplete = true; 74 | } 75 | 76 | IEnumerable Enumerate() 77 | { 78 | foreach (var b in data.GetConsumingEnumerable()) 79 | { 80 | foreach (T e in b) 81 | { 82 | yield return e; 83 | } 84 | } 85 | } 86 | 87 | public IEnumerator GetEnumerator() 88 | { 89 | return Enumerate().GetEnumerator(); 90 | } 91 | 92 | IEnumerator IEnumerable.GetEnumerator() 93 | { 94 | return Enumerate().GetEnumerator(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Extensions/NoteSequenceFunctions.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public static class NoteSequenceFunctions 11 | { 12 | public static IEnumerable ExtractEvents(this IEnumerable seq) 13 | { 14 | return NoteConversion.EncodeNotes(seq); 15 | } 16 | 17 | public static IEnumerable ExtractEvents(this IEnumerable seq, FastList buffer) 18 | { 19 | return Mergers.MergeWithBuffer(NoteConversion.EncodeNotes(seq), buffer); 20 | } 21 | 22 | public static IEnumerable ToTrackNotes(this IEnumerable seq, int track) 23 | { 24 | return NoteConversion.ToTrackNotes(seq, track); 25 | } 26 | 27 | public static IEnumerable MergeAll(this IEnumerable> seq) 28 | where T : Note 29 | { 30 | return Mergers.MergeSequences(seq); 31 | } 32 | 33 | public static IEnumerable MergeAllMany(this IEnumerable> seq) 34 | where T : Note 35 | { 36 | return Mergers.MergeManySequences(seq); 37 | } 38 | 39 | public static IEnumerable MergeWith(this IEnumerable seq, IEnumerable seq2) 40 | where T : Note 41 | { 42 | return Mergers.MergeSequences(new[] { seq, seq2 }); 43 | } 44 | 45 | public static IEnumerable TrimStart(this IEnumerable seq) 46 | where T : Note => TrimStart(seq, 0); 47 | public static IEnumerable TrimStart(this IEnumerable seq, double time) 48 | where T : Note 49 | { 50 | foreach (var n in seq) 51 | { 52 | if (n.End < time) continue; 53 | if (n.Start < time) 54 | { 55 | var nc = n.Clone() as T; 56 | nc.SetStartOnly(time); 57 | if(nc.Length < 0.00000001) continue; 58 | yield return nc; 59 | } 60 | else 61 | { 62 | yield return n; 63 | } 64 | } 65 | } 66 | 67 | public static IEnumerable TrimEnd(this IEnumerable seq, double time) 68 | where T : Note 69 | { 70 | foreach (var n in seq) 71 | { 72 | if (n.Start > time) continue; 73 | if (n.End > time) 74 | { 75 | var nc = n.Clone() as T; 76 | nc.End = time; 77 | if(nc.Length < 0.00000001) continue; 78 | yield return nc; 79 | } 80 | else 81 | { 82 | yield return n; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Generator/TransformExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MIDIModificationFramework.MIDIEvents; 7 | 8 | namespace MIDIModificationFramework.Generator 9 | { 10 | public static class TransformExtensions 11 | { 12 | public static IEnumerable SetChannel(this IEnumerable seq, int channel) 13 | where T : Note 14 | { 15 | foreach (var n in seq.CloneNotes()) 16 | { 17 | n.Channel = (byte)channel; 18 | yield return n; 19 | } 20 | } 21 | 22 | public static IEnumerable> SetChannel(this IEnumerable> seq, int channel) 23 | where T : Note => seq.Select(s => s.SetChannel(channel)); 24 | 25 | public static IEnumerable SetEventsChannel(this IEnumerable seq, int channel) 26 | where T : MIDIEvent 27 | { 28 | foreach (var e in seq) 29 | { 30 | if (e is ChannelEvent) 31 | { 32 | var ce = e.Clone() as ChannelEvent; 33 | ce.Channel = (byte)channel; 34 | yield return ce as T; 35 | } 36 | else 37 | { 38 | yield return e; 39 | } 40 | } 41 | } 42 | 43 | public static IEnumerable> SetEventsChannel(this IEnumerable> seq, int channel) 44 | where T : MIDIEvent => seq.Select(s => s.SetEventsChannel(channel)); 45 | 46 | public static IEnumerable OffsetKeys(this IEnumerable seq, int keys) 47 | where T : Note 48 | { 49 | foreach (var n in seq.CloneNotes()) 50 | { 51 | int k = n.Key + keys; 52 | if (k < 0 || k > 127) continue; 53 | n.Key = (byte)k; 54 | yield return n; 55 | } 56 | } 57 | 58 | public static IEnumerable> OffsetKeys(this IEnumerable> seq, int keys) 59 | where T : Note => seq.Select(s => s.OffsetKeys(keys)); 60 | 61 | public static IEnumerable OffsetEventKeys(this IEnumerable seq, int keys) 62 | where T : MIDIEvent 63 | { 64 | foreach (var e in seq) 65 | { 66 | if (e is NoteEvent) 67 | { 68 | var ke = e.Clone() as NoteEvent; 69 | int k = ke.Key + keys; 70 | if (k < 0 || k > 127) continue; 71 | ke.Key = (byte)k; 72 | yield return ke as T; 73 | } 74 | else 75 | { 76 | yield return e; 77 | } 78 | } 79 | } 80 | 81 | public static IEnumerable> OffsetEventKeys(this IEnumerable> seq, int keys) 82 | where T : MIDIEvent => seq.Select(s => s.OffsetEventKeys(keys)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Generator/NoteSequenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.Generator 8 | { 9 | public static class NoteSequenceExtensions 10 | { 11 | public static IEnumerable CloneNotes(this IEnumerable seq) 12 | where T : Note 13 | { 14 | foreach (var n in seq) yield return n.Clone() as T; 15 | } 16 | 17 | public static IEnumerable OffsetTime(this IEnumerable seq, double ticks) 18 | where T : Note 19 | { 20 | foreach (var n in seq.CloneNotes()) 21 | { 22 | n.Start += ticks; 23 | yield return n; 24 | } 25 | } 26 | 27 | public static IEnumerable> OffsetTime(this IEnumerable> seq, double ticks) 28 | where T : Note => seq.Select(s => s.OffsetTime(ticks)); 29 | 30 | public static IEnumerable> ExtractEvents(this IEnumerable> seq) => 31 | seq.Select(s => s.ExtractEvents()); 32 | 33 | public static IEnumerable> TrimStart(this IEnumerable> seq) 34 | where T : Note => TrimStart(seq, 0); 35 | public static IEnumerable> TrimStart(this IEnumerable> seq, double start) 36 | where T : Note => seq.Select(s => s.TrimStart(start)); 37 | 38 | public static IEnumerable> TrimEnd(this IEnumerable> seq, double end) 39 | where T : Note => seq.Select(s => s.TrimEnd(end)); 40 | 41 | public static IEnumerable> Pack(this IEnumerable> seq, int tracks) 42 | where T : Note 43 | { 44 | var all = seq.ToArray(); 45 | 46 | IEnumerable> select(int offset) 47 | { 48 | for (int i = offset; i < all.Length; i += tracks) 49 | { 50 | yield return all[i]; 51 | } 52 | } 53 | 54 | for (int i = 0; i < tracks; i++) 55 | { 56 | yield return select(i).MergeAll(); 57 | } 58 | } 59 | 60 | public static IEnumerable Silhouette(this IEnumerable seq) 61 | { 62 | Queue[] unendedNotes = new Queue[256 * 16]; 63 | FastList notesQueue = new FastList(); 64 | for (int i = 0; i < unendedNotes.Length; i++) unendedNotes[i] = new Queue(); 65 | 66 | Queue queueFromNote(Note n) => unendedNotes[n.Key * 16 + n.Channel]; 67 | 68 | foreach (var n in seq) 69 | { 70 | var q = queueFromNote(n); 71 | if (q.Count == 0 || q.Last().End + 0.00000001 < n.Start) 72 | { 73 | var newNote = new Note(n.Channel, n.Key, 1, n.Start, n.End); 74 | q.Enqueue(newNote); 75 | notesQueue.Add(newNote); 76 | } 77 | else 78 | { 79 | q.Last().End = n.End; 80 | } 81 | if (!notesQueue.ZeroLen && queueFromNote(notesQueue.First).Count > 1) 82 | { 83 | queueFromNote(notesQueue.First).Dequeue(); 84 | yield return notesQueue.Pop(); 85 | } 86 | } 87 | foreach (Note n in notesQueue) 88 | { 89 | yield return n; 90 | } 91 | notesQueue.Unlink(); 92 | } 93 | 94 | public static IEnumerable> Silhouette(this IEnumerable> seq) => 95 | seq.Select(s => s.Silhouette()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /XZ.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public class XZStream : Stream 12 | { 13 | Process xz; 14 | Task copyTask; 15 | public XZStream(Stream write, bool autoClose = true, int threads = 0) 16 | { 17 | xz = new Process(); 18 | xz.StartInfo = new ProcessStartInfo("xz", "-zc --threads=" + threads) 19 | { 20 | RedirectStandardOutput = true, 21 | RedirectStandardInput = true, 22 | UseShellExecute = false 23 | }; 24 | xz.Start(); 25 | copyTask = Task.Run(() => 26 | { 27 | xz.StandardOutput.BaseStream.CopyTo(write); 28 | if (autoClose) write.Close(); 29 | }); 30 | stdin = xz.StandardInput.BaseStream; 31 | //xz.StandardInput.Close(); 32 | } 33 | 34 | public override bool CanRead => false; 35 | 36 | public override bool CanSeek => false; 37 | 38 | public override bool CanWrite => true; 39 | 40 | public override long Length => throw new NotImplementedException(); 41 | 42 | public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 43 | 44 | Stream stdin; 45 | 46 | public override void Flush() 47 | { 48 | stdin.Flush(); 49 | } 50 | 51 | public override int Read(byte[] buffer, int offset, int count) 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | public override long Seek(long offset, SeekOrigin origin) 57 | { 58 | throw new NotImplementedException(); 59 | } 60 | 61 | public override void SetLength(long value) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | public override void Write(byte[] buffer, int offset, int count) 67 | { 68 | stdin.Write(buffer, offset, count); 69 | } 70 | 71 | public override void Close() 72 | { 73 | xz.StandardInput.Close(); 74 | copyTask.GetAwaiter().GetResult(); 75 | //xz.WaitForExit(); 76 | base.Close(); 77 | } 78 | } 79 | 80 | public static class XZ 81 | { 82 | public static Stream RemoveXZCompressionLayer(Stream input, int threads = 0) 83 | { 84 | Process xz = new Process(); 85 | xz.StartInfo = new ProcessStartInfo("xz", "-dc --threads=" + threads) 86 | { 87 | RedirectStandardOutput = true, 88 | RedirectStandardInput = true, 89 | UseShellExecute = false 90 | }; 91 | xz.Start(); 92 | Task.Run(() => 93 | { 94 | input.CopyTo(xz.StandardInput.BaseStream); 95 | xz.StandardInput.Close(); 96 | }); 97 | return xz.StandardOutput.BaseStream; 98 | } 99 | 100 | public static Stream AddXZCompressionLayer(Stream input, int threads = 0) 101 | { 102 | Process xz = new Process(); 103 | xz.StartInfo = new ProcessStartInfo("xz", "-zc --threads=" + threads) 104 | { 105 | RedirectStandardOutput = true, 106 | RedirectStandardInput = true, 107 | UseShellExecute = false 108 | }; 109 | xz.Start(); 110 | Task.Run(() => 111 | { 112 | input.CopyTo(xz.StandardInput.BaseStream); 113 | xz.StandardInput.Close(); 114 | }); 115 | return xz.StandardOutput.BaseStream; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /MIDIEvents/ColorEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MIDIModificationFramework.MIDIEvents 8 | { 9 | public class ColorEvent : MIDIEvent 10 | { 11 | byte channel = 0x7f; 12 | public byte Channel { 13 | get => channel; 14 | set 15 | { 16 | if (value > 15 && value != 0x7f) throw new ArgumentException("Channel can only be between 0 and 16, or 7F for all channels"); 17 | channel = value; 18 | } 19 | } 20 | bool gradients; 21 | 22 | public byte R { get; set; } 23 | public byte G { get; set; } 24 | public byte B { get; set; } 25 | public byte A { get; set; } 26 | public byte R2 { get; set; } 27 | public byte G2 { get; set; } 28 | public byte B2 { get; set; } 29 | public byte A2 { get; set; } 30 | 31 | public ColorEvent(double delta, byte r, byte g, byte b, byte a) : base(delta) 32 | { 33 | R = r; 34 | G = g; 35 | B = b; 36 | A = a; 37 | 38 | gradients = false; 39 | } 40 | 41 | public ColorEvent(double delta, byte channel, byte r, byte g, byte b, byte a) : base(delta) 42 | { 43 | if (channel == 0x7f) Channel = 0x7f; 44 | else Channel = (byte)(channel % 16); 45 | R = r; 46 | G = g; 47 | B = b; 48 | A = a; 49 | 50 | gradients = false; 51 | } 52 | 53 | public ColorEvent(double delta, byte r, byte g, byte b, byte a, byte r2, byte g2, byte b2, byte a2) : base(delta) 54 | { 55 | R = r; 56 | G = g; 57 | B = b; 58 | A = a; 59 | R2 = r2; 60 | G2 = g2; 61 | B2 = b2; 62 | A2 = a2; 63 | 64 | gradients = true; 65 | } 66 | 67 | public ColorEvent(double delta, byte channel, byte r, byte g, byte b, byte a, byte r2, byte g2, byte b2, byte a2) : base(delta) 68 | { 69 | if (channel == 0x7f) Channel = 0x7f; 70 | else Channel = (byte)(channel % 16); 71 | 72 | R = r; 73 | G = g; 74 | B = b; 75 | A = a; 76 | R2 = r2; 77 | G2 = g2; 78 | B2 = b2; 79 | A2 = a2; 80 | 81 | gradients = true; 82 | } 83 | 84 | private ColorEvent(double delta, byte channel, byte r, byte g, byte b, byte a, byte r2, byte g2, byte b2, byte a2, bool gradients) : base(delta) 85 | { 86 | if (channel == 0x7f) Channel = 0x7f; 87 | else Channel = (byte)(channel % 16); 88 | 89 | R = r; 90 | G = g; 91 | B = b; 92 | A = a; 93 | R2 = r2; 94 | G2 = g2; 95 | B2 = b2; 96 | A2 = a2; 97 | 98 | this.gradients = gradients; 99 | } 100 | 101 | public override MIDIEvent Clone() 102 | { 103 | return new ColorEvent(DeltaTime, Channel, R, G, B, A, R2, G2, B2, A2, gradients); 104 | } 105 | 106 | public override byte[] GetData() 107 | { 108 | byte[] data; 109 | if (gradients) 110 | data = new byte[15]; 111 | else 112 | data = new byte[11]; 113 | data[0] = 0xFF; 114 | data[1] = 0x0A; 115 | if (gradients) 116 | data[2] = 0x0B; 117 | else 118 | data[2] = 0x08; 119 | data[3] = 0x00; 120 | data[4] = 0x0F; 121 | data[5] = Channel; 122 | data[6] = 0x00; 123 | 124 | data[7] = R; 125 | data[8] = G; 126 | data[9] = B; 127 | data[10] = A; 128 | 129 | if (gradients) 130 | { 131 | data[11] = R2; 132 | data[12] = G2; 133 | data[13] = B2; 134 | data[14] = A2; 135 | } 136 | 137 | return data; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Logic/SequenceFunctions.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public static class SequenceFunctions 11 | { 12 | public static IEnumerable CancelTempoEvents(IEnumerable sequence, double newTempo, bool returnTempos = false) 13 | { 14 | double extraTicks = 0; 15 | int tempo = 500000; 16 | double lastDiff = 0; 17 | 18 | newTempo = tempo * (tempo / newTempo); 19 | 20 | foreach (var _e in sequence) 21 | { 22 | var e = _e.Clone(); 23 | e.DeltaTime = e.DeltaTime / newTempo * tempo + extraTicks; 24 | extraTicks = 0; 25 | if (e is TempoEvent) 26 | { 27 | var ev = e as TempoEvent; 28 | tempo = ev.Tempo; 29 | extraTicks = e.DeltaTime + lastDiff; 30 | lastDiff = 0; 31 | if (returnTempos) 32 | { 33 | ev.Tempo = (int)newTempo; 34 | yield return ev; 35 | } 36 | continue; 37 | } 38 | yield return e; 39 | } 40 | } 41 | 42 | public static IEnumerable EventInjector(IEnumerable sequence, Func generator) 43 | { 44 | MIDIEvent nextGenerated = generator().Clone(); 45 | foreach (var _e in sequence) 46 | { 47 | var e = _e.Clone(); 48 | while (nextGenerated.DeltaTime < e.DeltaTime) 49 | { 50 | e.DeltaTime -= nextGenerated.DeltaTime; 51 | yield return nextGenerated; 52 | nextGenerated = generator().Clone(); 53 | } 54 | nextGenerated.DeltaTime -= e.DeltaTime; 55 | yield return e; 56 | } 57 | } 58 | 59 | public static IEnumerable ExtractEvent(IEnumerable sequence) 60 | where T : MIDIEvent 61 | { 62 | double delta = 0; 63 | foreach (var e in sequence) 64 | { 65 | delta += e.DeltaTime; 66 | if (e is T) 67 | { 68 | var _e = e.Clone() as T; 69 | _e.DeltaTime = delta; 70 | delta = 0; 71 | yield return _e; 72 | } 73 | } 74 | } 75 | 76 | public static IEnumerable PPQChange(IEnumerable sequence, double startPPQ, double endPPQ) 77 | where T : MIDIEvent 78 | { 79 | var ppqRatio = endPPQ / startPPQ; 80 | foreach (var _e in sequence) 81 | { 82 | var e = _e.Clone() as T; 83 | e.DeltaTime *= ppqRatio; 84 | yield return e; 85 | } 86 | } 87 | 88 | public static IEnumerable RoundDeltas(IEnumerable sequence) 89 | where T : MIDIEvent 90 | { 91 | double time = 0; 92 | double roundedtime = 0; 93 | foreach (var _e in sequence) 94 | { 95 | var e = _e.Clone() as T; 96 | time += e.DeltaTime; 97 | var round = Math.Round(time); 98 | e.DeltaTime = round - roundedtime; 99 | roundedtime = round; 100 | yield return e; 101 | } 102 | } 103 | 104 | public static IEnumerable FilterEvents(IEnumerable sequence, Func func) 105 | where T : MIDIEvent 106 | { 107 | double extraDelta = 0; 108 | foreach (var _e in sequence) 109 | { 110 | if (func(_e)) 111 | { 112 | var e = _e.Clone() as T; 113 | e.DeltaTime += extraDelta; 114 | extraDelta = 0; 115 | yield return e; 116 | } 117 | else 118 | { 119 | extraDelta += _e.DeltaTime; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /MidiFile.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public class MidiFile : IDisposable 12 | { 13 | internal class MidiChunkPointer 14 | { 15 | public long Start { get; set; } 16 | public uint Length { get; set; } 17 | } 18 | 19 | public ushort Format { get; private set; } 20 | public ushort PPQ { get; private set; } 21 | public int TrackCount { get; private set; } 22 | 23 | internal MidiChunkPointer[] TrackLocations { get; private set; } 24 | 25 | Stream reader; 26 | DiskReadProvider readProvider; 27 | 28 | int readBufferSize = 100000; 29 | 30 | public BufferByteReader GetTrackByteReader(int track) 31 | { 32 | return new BufferByteReader(readProvider, 100000, TrackLocations[track].Start, TrackLocations[track].Length); 33 | } 34 | 35 | public IEnumerable GetTrackUnsafe(int track) 36 | { 37 | var reader = new EventParser(GetTrackByteReader(track)); 38 | while (!reader.Ended) 39 | { 40 | MIDIEvent ev; 41 | ev = reader.ParseNextEvent(); 42 | if (ev == null) break; 43 | yield return ev; 44 | } 45 | reader.Dispose(); 46 | } 47 | 48 | public IEnumerable GetTrack(int track) 49 | { 50 | var reader = new EventParser(GetTrackByteReader(track)); 51 | while (!reader.Ended) 52 | { 53 | MIDIEvent ev; 54 | try 55 | { 56 | ev = reader.ParseNextEvent(); 57 | } 58 | catch 59 | { 60 | break; 61 | } 62 | if (ev == null) break; 63 | yield return ev; 64 | } 65 | reader.Dispose(); 66 | } 67 | 68 | public IEnumerable> IterateTracks() 69 | { 70 | for (int i = 0; i < TrackCount; i++) yield return GetTrack(i); 71 | } 72 | 73 | string filepath; 74 | 75 | public MidiFile(Stream stream, int readBufferSize) 76 | { 77 | reader = stream; 78 | ParseHeaderChunk(); 79 | List tracks = new List(); 80 | while (reader.Position < reader.Length) 81 | { 82 | ParseTrackChunk(tracks); 83 | } 84 | TrackLocations = tracks.ToArray(); 85 | TrackCount = TrackLocations.Length; 86 | readProvider = new DiskReadProvider(stream); 87 | } 88 | 89 | public MidiFile(Stream stream) : this(stream, 100000) 90 | { } 91 | 92 | public MidiFile(string filename, int readBufferSize) : this(File.Open(filename, FileMode.Open), readBufferSize) 93 | { } 94 | 95 | public MidiFile(string filename) : this(filename, 100000) 96 | { } 97 | 98 | void AssertText(string text) 99 | { 100 | foreach (char c in text) 101 | { 102 | if (reader.ReadByte() != c) 103 | { 104 | throw new Exception("Corrupt chunk headers"); 105 | } 106 | } 107 | } 108 | 109 | uint ReadInt32() 110 | { 111 | uint length = 0; 112 | for (int i = 0; i != 4; i++) 113 | length = (uint)((length << 8) | (byte)reader.ReadByte()); 114 | return length; 115 | } 116 | 117 | ushort ReadInt16() 118 | { 119 | ushort length = 0; 120 | for (int i = 0; i != 2; i++) 121 | length = (ushort)((length << 8) | (byte)reader.ReadByte()); 122 | return length; 123 | } 124 | 125 | void ParseHeaderChunk() 126 | { 127 | AssertText("MThd"); 128 | uint length = (uint)ReadInt32(); 129 | if (length != 6) throw new Exception("Header chunk size isn't 6"); 130 | Format = ReadInt16(); 131 | ReadInt16(); 132 | PPQ = ReadInt16(); 133 | } 134 | 135 | void ParseTrackChunk(List tracks) 136 | { 137 | AssertText("MTrk"); 138 | uint length = (uint)ReadInt32(); 139 | tracks.Add(new MidiChunkPointer() { Start = reader.Position, Length = length }); 140 | reader.Position += length; 141 | } 142 | 143 | public void Dispose() 144 | { 145 | reader.Dispose(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /MidiWriter.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public class MidiWriter 12 | { 13 | Stream writer; 14 | 15 | long chunkStart = 0; 16 | 17 | int tracks = 0; 18 | 19 | public MidiWriter(Stream writer) 20 | { 21 | this.writer = writer; 22 | } 23 | 24 | public MidiWriter(string output) : this(new BufferedStream(File.Open(output, FileMode.Create))) 25 | { } 26 | 27 | public void Write(string text) 28 | { 29 | for (int i = 0; i < text.Length; i++) writer.WriteByte((byte)text[i]); 30 | } 31 | 32 | public void Write(MIDIEvent e) 33 | { 34 | var data = e.GetDataWithDelta(); 35 | writer.Write(data, 0, data.Length); 36 | } 37 | 38 | public void Write(IEnumerable seq) 39 | { 40 | foreach (var e in seq) 41 | { 42 | var data = e.GetDataWithDelta(); 43 | writer.Write(data, 0, data.Length); 44 | } 45 | } 46 | 47 | public void Write(byte[] data) 48 | { 49 | writer.Write(data, 0, data.Length); 50 | } 51 | 52 | public void Write(Stream data) 53 | { 54 | var bytes = new byte[4096]; 55 | int read; 56 | 57 | while((read = data.Read(bytes, 0, bytes.Length)) != 0) 58 | { 59 | writer.Write(bytes, 0, read); 60 | } 61 | //data.CopyTo(writer); 62 | } 63 | 64 | public void Write(ushort v) 65 | { 66 | for (int i = 1; i >= 0; i--) writer.WriteByte((byte)((v >> (i * 8)) & 0xFF)); 67 | } 68 | 69 | public void Write(uint v) 70 | { 71 | for (int i = 3; i >= 0; i--) writer.WriteByte((byte)((v >> (i * 8)) & 0xFF)); 72 | } 73 | 74 | public void Write(byte v) 75 | { 76 | writer.WriteByte(v); 77 | } 78 | 79 | public void WriteTrack(IEnumerable seq) 80 | { 81 | InitTrack(); 82 | Write(seq); 83 | EndTrack(); 84 | } 85 | 86 | public void WriteTrack(byte[] data) 87 | { 88 | InitTrack(); 89 | Write(data); 90 | EndTrack(); 91 | } 92 | 93 | public void WriteTrack(Stream data) 94 | { 95 | InitTrack(); 96 | Write(data); 97 | EndTrack(); 98 | } 99 | 100 | public void WriteVariableLen(int i) 101 | { 102 | var b = new byte[5]; 103 | int len = 0; 104 | while (true) 105 | { 106 | byte v = (byte)(i & 0x7F); 107 | i = i >> 7; 108 | if (i != 0) 109 | { 110 | v = (byte)(v | 0x80); 111 | b[len++] = v; 112 | } 113 | else 114 | { 115 | b[len++] = v; 116 | break; 117 | } 118 | } 119 | Write(b.Take(len).ToArray()); 120 | } 121 | 122 | public void WriteFormat(ushort s) 123 | { 124 | long pos = writer.Position; 125 | writer.Position = 8; 126 | Write((ushort)s); 127 | writer.Position = pos; 128 | } 129 | 130 | void WriteNtrks(ushort s) 131 | { 132 | long pos = writer.Position; 133 | writer.Position = 10; 134 | Write((ushort)s); 135 | writer.Position = pos; 136 | } 137 | 138 | void WritePPQ(ushort s) 139 | { 140 | long pos = writer.Position; 141 | writer.Position = 12; 142 | Write((ushort)s); 143 | writer.Position = pos; 144 | } 145 | 146 | public void Init(ushort ppq) 147 | { 148 | writer.Position = 0; 149 | Write("MThd"); 150 | Write((uint)6); 151 | WriteFormat(1); 152 | WriteNtrks(0); 153 | WritePPQ(ppq); 154 | } 155 | 156 | public void InitTrack() 157 | { 158 | chunkStart = writer.Length; 159 | writer.Position = chunkStart; 160 | Write("MTrk"); 161 | Write((uint)0); 162 | } 163 | 164 | public void EndTrack() 165 | { 166 | Write(new byte[] { 0, 0xFF, 0x2F, 0x00 }); 167 | uint len = (uint)(writer.Position - chunkStart) - 8; 168 | writer.Position = chunkStart + 4; 169 | Write(len); 170 | writer.Position = writer.Length; 171 | tracks++; 172 | } 173 | 174 | public void Close() 175 | { 176 | if (tracks > 65535) tracks = 65535; 177 | WriteNtrks((ushort)tracks); 178 | writer.Flush(); 179 | writer.Close(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /FastList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public class FastList : IEnumerable 11 | { 12 | private class ListItem 13 | { 14 | public ListItem Next; 15 | public T item; 16 | } 17 | 18 | private ListItem root = new ListItem(); 19 | private ListItem last = null; 20 | 21 | public T First 22 | { 23 | get 24 | { 25 | if (root.Next != null) return root.Next.item; 26 | else return default(T); 27 | } 28 | } 29 | public class Iterator 30 | { 31 | FastList _ilist; 32 | 33 | private ListItem prev; 34 | private ListItem curr; 35 | 36 | internal Iterator(FastList ll) 37 | { 38 | _ilist = ll; 39 | Reset(); 40 | } 41 | 42 | public bool MoveNext(out T v) 43 | { 44 | ListItem ll = curr.Next; 45 | 46 | if (ll == null) 47 | { 48 | v = default(T); 49 | _ilist.last = curr; 50 | return false; 51 | } 52 | 53 | v = ll.item; 54 | 55 | prev = curr; 56 | curr = ll; 57 | 58 | return true; 59 | } 60 | 61 | public void Remove() 62 | { 63 | if (_ilist.last.Equals(curr)) _ilist.last = prev; 64 | prev.Next = curr.Next; 65 | } 66 | 67 | public void Insert(T item) 68 | { 69 | var i = new ListItem() 70 | { 71 | item = item, 72 | Next = curr 73 | }; 74 | if (prev == null) 75 | _ilist.root.Next = i; 76 | else 77 | prev.Next = i; 78 | //if (curr.Equals(_ilist.last)) 79 | //{ 80 | // _ilist.last = curr; 81 | //} 82 | } 83 | 84 | public void Reset() 85 | { 86 | this.prev = null; 87 | this.curr = _ilist.root; 88 | } 89 | } 90 | 91 | public class FastIterator : IEnumerator 92 | { 93 | FastList _ilist; 94 | 95 | private ListItem curr; 96 | 97 | internal FastIterator(FastList ll) 98 | { 99 | _ilist = ll; 100 | Reset(); 101 | } 102 | 103 | public object Current => curr.item; 104 | 105 | T IEnumerator.Current => curr.item; 106 | 107 | public void Dispose() 108 | { 109 | 110 | } 111 | 112 | public bool MoveNext() 113 | { 114 | try 115 | { 116 | curr = curr.Next; 117 | 118 | return curr != null; 119 | } 120 | catch { return false; } 121 | } 122 | 123 | public void Reset() 124 | { 125 | this.curr = _ilist.root; 126 | } 127 | } 128 | 129 | public void Add(T item) 130 | { 131 | ListItem li = new ListItem(); 132 | li.item = item; 133 | 134 | if (root.Next != null && last != null) 135 | { 136 | while (last.Next != null) last = last.Next; 137 | last.Next = li; 138 | } 139 | else 140 | root.Next = li; 141 | 142 | last = li; 143 | } 144 | 145 | public T Pop() 146 | { 147 | ListItem el = root.Next; 148 | root.Next = el.Next; 149 | return el.item; 150 | } 151 | 152 | public Iterator Iterate() 153 | { 154 | return new Iterator(this); 155 | } 156 | 157 | public bool ZeroLen => root.Next == null; 158 | 159 | public IEnumerator FastIterate() 160 | { 161 | return new FastIterator(this); 162 | } 163 | 164 | public void Unlink() 165 | { 166 | root.Next = null; 167 | last = null; 168 | } 169 | 170 | public int Count() 171 | { 172 | int cnt = 0; 173 | 174 | ListItem li = root.Next; 175 | while (li != null) 176 | { 177 | cnt++; 178 | li = li.Next; 179 | } 180 | 181 | return cnt; 182 | } 183 | 184 | public bool Any() 185 | { 186 | return root.Next != null; 187 | } 188 | 189 | public IEnumerator GetEnumerator() 190 | { 191 | return FastIterate(); 192 | } 193 | 194 | IEnumerator IEnumerable.GetEnumerator() 195 | { 196 | return FastIterate(); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Extensions/EventSequenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public static class EventSequenceExtensions 11 | { 12 | public static IEnumerable CancelTempoEvents(this IEnumerable seq, double newTempo) 13 | { 14 | return SequenceFunctions.CancelTempoEvents(seq, newTempo); 15 | } 16 | 17 | public static IEnumerable CancelTempoEvents(this IEnumerable seq, double newTempo, bool returnTempos) 18 | { 19 | return SequenceFunctions.CancelTempoEvents(seq, newTempo, returnTempos); 20 | } 21 | 22 | public static IEnumerable MakeTimeBased(this IEnumerable seq, double originalPPQ) 23 | { 24 | return SequenceFunctions.CancelTempoEvents(seq, 250000 / originalPPQ); 25 | } 26 | 27 | public static IEnumerable RoundDeltas(this IEnumerable seq) 28 | { 29 | return SequenceFunctions.RoundDeltas(seq); 30 | } 31 | 32 | public static IEnumerable InjectEvents(this IEnumerable seq, Func generator) 33 | { 34 | return SequenceFunctions.EventInjector(seq, generator); 35 | } 36 | 37 | public static IEnumerable FilterEvents(this IEnumerable seq) 38 | where T : MIDIEvent 39 | { 40 | double delta = 0; 41 | foreach (var e in seq) 42 | { 43 | if (e is T) 44 | { 45 | var ev = e.Clone() as T; 46 | ev.DeltaTime += delta; 47 | delta = 0; 48 | yield return ev; 49 | } 50 | else 51 | { 52 | delta += e.DeltaTime; 53 | } 54 | } 55 | } 56 | 57 | public static IEnumerable FilterEvents(this IEnumerable seq, IEnumerable types) 58 | { 59 | double delta = 0; 60 | foreach (var e in seq) 61 | { 62 | bool extends = false; 63 | foreach (var t in types) 64 | { 65 | if (t.IsInstanceOfType(e)) 66 | { 67 | extends = true; 68 | break; 69 | } 70 | } 71 | if (extends) 72 | { 73 | var ev = e.Clone(); 74 | ev.DeltaTime += delta; 75 | delta = 0; 76 | yield return ev; 77 | } 78 | else 79 | { 80 | delta += e.DeltaTime; 81 | } 82 | } 83 | } 84 | 85 | public static IEnumerable RemoveEvents(this IEnumerable seq, IEnumerable types) 86 | where T : MIDIEvent 87 | { 88 | double delta = 0; 89 | foreach (var e in seq) 90 | { 91 | bool extends = false; 92 | foreach (var t in types) 93 | { 94 | if (t.IsInstanceOfType(e)) 95 | { 96 | extends = true; 97 | break; 98 | } 99 | } 100 | if (!extends) 101 | { 102 | var ev = e.Clone() as T; 103 | ev.DeltaTime += delta; 104 | delta = 0; 105 | yield return ev; 106 | } 107 | else 108 | { 109 | delta += e.DeltaTime; 110 | } 111 | } 112 | } 113 | 114 | public static IEnumerable ChangePPQ(this IEnumerable seq, double startPPQ, double endPPQ) 115 | where T : MIDIEvent 116 | { 117 | return SequenceFunctions.PPQChange(seq, startPPQ, endPPQ); 118 | } 119 | 120 | public static IEnumerable ChangePPQ(this IEnumerable seq, double ppqMultiplier) 121 | where T : MIDIEvent 122 | { 123 | return SequenceFunctions.PPQChange(seq, 1, ppqMultiplier); 124 | } 125 | 126 | public static IEnumerable MergeWith(this IEnumerable seq, IEnumerable seq2) 127 | where T : MIDIEvent 128 | { 129 | return Mergers.MergeSequences(seq, seq2); 130 | } 131 | 132 | public static IEnumerable MergeAllTracks(this IEnumerable> seqs) 133 | where T : MIDIEvent 134 | { 135 | return Mergers.MergeSequences(seqs); 136 | } 137 | 138 | public static IEnumerable MergeBuffer(this IEnumerable seq, FastList buffer) 139 | { 140 | return Mergers.MergeWithBuffer(seq, buffer); 141 | } 142 | 143 | public static IEnumerable ExtractNotes(this IEnumerable seq) 144 | { 145 | return NoteConversion.ExtractNotes(seq); 146 | } 147 | 148 | public static IEnumerable ExtractNotes(this IEnumerable seq, FastList otherEvents) 149 | { 150 | return NoteConversion.ExtractNotes(seq, otherEvents); 151 | } 152 | 153 | public static IEnumerable FilterEvents(this IEnumerable seq, Func select) 154 | { 155 | return SequenceFunctions.FilterEvents(seq, select); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Logic/NoteConversion.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public static class NoteConversion 11 | { 12 | class DecodedNote 13 | { 14 | public Note note; 15 | public bool ended = false; 16 | } 17 | 18 | class UnplacedNoteOff 19 | { 20 | public NoteOffEvent e; 21 | public double time; 22 | } 23 | 24 | public static IEnumerable ExtractNotes(IEnumerable sequence, FastList otherEvents = null) 25 | { 26 | double time = 0; 27 | FastList[] unendedNotes = new FastList[256 * 16]; 28 | FastList notesQueue = new FastList(); 29 | for (int i = 0; i < unendedNotes.Length; i++) unendedNotes[i] = new FastList(); 30 | double delta = 0; 31 | foreach (var e in sequence) 32 | { 33 | time += e.DeltaTime; 34 | if (e is NoteOnEvent) 35 | { 36 | var n = e as NoteOnEvent; 37 | var note = new DecodedNote() 38 | { 39 | note = new Note(n.Channel, n.Key, n.Velocity, time, double.PositiveInfinity) 40 | }; 41 | unendedNotes[n.Key * 16 + n.Channel].Add(note); 42 | notesQueue.Add(note); 43 | delta += e.DeltaTime; 44 | } 45 | else if (e is NoteOffEvent) 46 | { 47 | var n = e as NoteOffEvent; 48 | var arr = unendedNotes[n.Key * 16 + n.Channel]; 49 | if (arr.ZeroLen) continue; 50 | var note = arr.Pop(); 51 | note.ended = true; 52 | note.note.End = time; 53 | delta += e.DeltaTime; 54 | } 55 | else 56 | { 57 | if (otherEvents != null) 58 | { 59 | var ev = e.Clone(); 60 | ev.DeltaTime += delta; 61 | delta = 0; 62 | otherEvents.Add(ev); 63 | } 64 | } 65 | if (notesQueue.First != null && notesQueue.First.ended) 66 | { 67 | yield return notesQueue.Pop().note; 68 | } 69 | } 70 | foreach (DecodedNote un in notesQueue) 71 | { 72 | if (!un.ended) 73 | { 74 | un.note.End = time; 75 | } 76 | yield return un.note; 77 | } 78 | notesQueue.Unlink(); 79 | foreach (var s in unendedNotes) s.Unlink(); 80 | } 81 | 82 | public static IEnumerable EncodeNotes(IEnumerable sequence) 83 | { 84 | List noteOffs = new List(); 85 | double prevTime = 0; 86 | 87 | foreach (var n in sequence) 88 | { 89 | while (noteOffs.Count != 0 && noteOffs[0].time <= n.Start) 90 | { 91 | var e = noteOffs[0]; 92 | noteOffs.RemoveAt(0); 93 | e.e.DeltaTime = e.time - prevTime; 94 | yield return e.e; 95 | prevTime = e.time; 96 | } 97 | 98 | yield return new NoteOnEvent(n.Start - prevTime, n.Channel, n.Key, n.Velocity); 99 | prevTime = n.Start; 100 | var time = n.End; 101 | var off = new UnplacedNoteOff() { e = new NoteOffEvent(0, n.Channel, n.Key), time = time }; 102 | var pos = noteOffs.Count / 2; 103 | if (noteOffs.Count == 0) noteOffs.Add(off); 104 | else 105 | { 106 | // binary search 107 | for (int jump = noteOffs.Count / 4; ; jump /= 2) 108 | { 109 | if (jump <= 0) jump = 1; 110 | if (pos < 0) pos = 0; 111 | if (pos >= noteOffs.Count) pos = noteOffs.Count - 1; 112 | var u = noteOffs[pos]; 113 | if (u.time >= time) 114 | { 115 | if (pos == 0 || noteOffs[pos - 1].time < time) 116 | { 117 | noteOffs.Insert(pos, off); 118 | break; 119 | } 120 | else pos -= jump; 121 | } 122 | else 123 | { 124 | if (pos == noteOffs.Count - 1) 125 | { 126 | noteOffs.Add(off); 127 | break; 128 | } 129 | else pos += jump; 130 | } 131 | } 132 | } 133 | } 134 | 135 | foreach (var e in noteOffs) 136 | { 137 | e.e.DeltaTime = e.time - prevTime; 138 | yield return e.e; 139 | prevTime = e.time; 140 | } 141 | } 142 | 143 | public static IEnumerable ToTrackNotes(IEnumerable notes, int track) 144 | { 145 | foreach (var n in notes) yield return new TrackNote(track, n); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /BufferByteReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public class BufferByteReader : IByteReader 12 | { 13 | long pos; 14 | int bufferSize; 15 | int bufferPos; 16 | int maxBufferPos; 17 | long streamStart; 18 | long streamLen; 19 | DiskReadProvider stream; 20 | byte[] buffer; 21 | byte[] bufferNext; 22 | BlockingCollection readReturn = new BlockingCollection(); 23 | bool sentRequest = false; 24 | 25 | public BufferByteReader(DiskReadProvider stream, int buffersize, long streamstart, long streamlen) 26 | { 27 | if (buffersize > streamlen) buffersize = (int)streamlen; 28 | this.bufferSize = buffersize; 29 | buffer = new byte[buffersize]; 30 | bufferNext = new byte[buffersize]; 31 | this.streamStart = streamstart; 32 | this.streamLen = streamlen; 33 | this.stream = stream; 34 | UpdateBuffer(pos, true); 35 | } 36 | 37 | void UpdateBuffer(long pos, bool first = false) 38 | { 39 | if (first) 40 | { 41 | if (sentRequest) 42 | { 43 | readReturn.Take(); 44 | } 45 | stream.Request(new ReadDiskRequest(pos + streamStart, bufferSize, bufferNext, readReturn)); 46 | sentRequest = true; 47 | } 48 | bufferNext = readReturn.Take(); 49 | sentRequest = false; 50 | Buffer.BlockCopy(bufferNext, 0, buffer, 0, bufferSize); 51 | stream.Request(new ReadDiskRequest(pos + streamStart + bufferSize, bufferSize, bufferNext, readReturn)); 52 | sentRequest = true; 53 | maxBufferPos = (int)Math.Min(streamLen - pos + 1, bufferSize); 54 | } 55 | 56 | public long Location => pos; 57 | public long Length => streamLen; 58 | 59 | public int Pushback = -1; 60 | 61 | public byte Read() 62 | { 63 | if (Pushback != -1) 64 | { 65 | byte _b = (byte)Pushback; 66 | Pushback = -1; 67 | return _b; 68 | } 69 | byte b = buffer[bufferPos++]; 70 | if (bufferPos < maxBufferPos) return b; 71 | else if (bufferPos >= bufferSize) 72 | { 73 | pos += bufferPos; 74 | bufferPos = 0; 75 | UpdateBuffer(pos); 76 | return b; 77 | } 78 | else throw new EndOfStreamException(); 79 | } 80 | 81 | public byte ReadFast() 82 | { 83 | byte b = buffer[bufferPos++]; 84 | if (bufferPos < maxBufferPos) return b; 85 | else if (bufferPos >= bufferSize) 86 | { 87 | pos += bufferPos; 88 | bufferPos = 0; 89 | UpdateBuffer(pos); 90 | return b; 91 | } 92 | else throw new EndOfStreamException(); 93 | } 94 | 95 | public void Reset() 96 | { 97 | pos = 0; 98 | bufferPos = 0; 99 | UpdateBuffer(pos, true); 100 | } 101 | 102 | public void ResetAndResize(int buffersize) 103 | { 104 | if (buffersize > streamLen) buffersize = (int)streamLen; 105 | this.bufferSize = buffersize; 106 | buffer = new byte[buffersize]; 107 | bufferNext = new byte[buffersize]; 108 | Reset(); 109 | } 110 | 111 | public void Skip(int count) 112 | { 113 | for (int i = 0; i < count; i++) 114 | { 115 | if (Pushback != -1) 116 | { 117 | Pushback = -1; 118 | continue; 119 | } 120 | bufferPos++; 121 | if (bufferPos < maxBufferPos) continue; 122 | if (bufferPos >= bufferSize) 123 | { 124 | pos += bufferPos; 125 | bufferPos = 0; 126 | UpdateBuffer(pos); 127 | } 128 | else throw new IndexOutOfRangeException(); 129 | } 130 | } 131 | 132 | public void Dispose() 133 | { 134 | buffer = null; 135 | } 136 | } 137 | 138 | public struct ReadDiskRequest 139 | { 140 | public long from; 141 | public int length; 142 | public byte[] buffer; 143 | public BlockingCollection output; 144 | 145 | public ReadDiskRequest(long from, int length, byte[] buffer, BlockingCollection output) 146 | { 147 | this.from = from; 148 | this.length = length; 149 | this.buffer = buffer; 150 | this.output = output; 151 | } 152 | } 153 | 154 | public class DiskReadProvider : IDisposable 155 | { 156 | BlockingCollection requests = new BlockingCollection(); 157 | Stream reader; 158 | 159 | public DiskReadProvider(Stream reader) 160 | { 161 | this.reader = reader; 162 | Task.Run(() => 163 | { 164 | foreach (var request in requests.GetConsumingEnumerable()) 165 | { 166 | if (reader.Position != request.from) 167 | { 168 | reader.Position = request.from; 169 | } 170 | reader.Read(request.buffer, 0, request.length); 171 | request.output.Add(request.buffer); 172 | } 173 | }); 174 | } 175 | 176 | public void Request(ReadDiskRequest req) 177 | { 178 | requests.Add(req); 179 | } 180 | 181 | public void Dispose() 182 | { 183 | requests.CompleteAdding(); 184 | reader.Dispose(); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Extensions/ThreadedSequenceFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Concurrent; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public static class ThreadedSequenceFunctions 12 | { 13 | class DisposeAction : IDisposable 14 | { 15 | Action disposeAction; 16 | 17 | public DisposeAction(Action action) 18 | { 19 | disposeAction = action; 20 | } 21 | 22 | public void Dispose() 23 | { 24 | disposeAction(); 25 | } 26 | } 27 | 28 | public static IEnumerable ConstantThreadedBuffer(this IEnumerable seq, int maxSize) 29 | { 30 | BlockingCollection buffer = new BlockingCollection(maxSize); 31 | 32 | CancellationTokenSource cancel = new CancellationTokenSource(); 33 | 34 | var runner = Task.Run(() => 35 | { 36 | foreach (var t in seq) 37 | { 38 | if (cancel.IsCancellationRequested) break; 39 | buffer.Add(t); 40 | } 41 | buffer.CompleteAdding(); 42 | }); 43 | 44 | void cancelAndWait() 45 | { 46 | cancel.Cancel(); 47 | runner.Wait(); 48 | } 49 | 50 | using (new DisposeAction(cancelAndWait)) 51 | { 52 | foreach (var t in buffer.GetConsumingEnumerable()) 53 | { 54 | yield return t; 55 | } 56 | } 57 | } 58 | public static IEnumerable ConstantThreadedBuffer(this IEnumerable seq, int maxBatches, int batchSize) 59 | { 60 | BatchBlockingCollection buffer = new BatchBlockingCollection(maxBatches, batchSize); 61 | 62 | CancellationTokenSource cancel = new CancellationTokenSource(); 63 | 64 | var runner = Task.Run(() => 65 | { 66 | foreach (var t in seq) 67 | { 68 | if (cancel.IsCancellationRequested) break; 69 | buffer.Add(t, cancel.Token); 70 | } 71 | buffer.Complete(); 72 | }); 73 | 74 | void cancelAndWait() 75 | { 76 | cancel.Cancel(); 77 | runner.Wait(); 78 | } 79 | 80 | using (new DisposeAction(cancelAndWait)) 81 | { 82 | foreach (var t in buffer) 83 | { 84 | yield return t; 85 | } 86 | } 87 | } 88 | 89 | public static IEnumerable TaskedThreadedBuffer(this IEnumerable seq, int maxSize) 90 | { 91 | BlockingCollection buffer = new BlockingCollection(); 92 | 93 | Task readerTask = null; 94 | 95 | bool completed = false; 96 | 97 | var en = seq.GetEnumerator(); 98 | 99 | CancellationTokenSource cancel = new CancellationTokenSource(); 100 | 101 | Exception e = null; 102 | 103 | Action runReader = () => 104 | { 105 | if (readerTask != null) readerTask.Wait(); 106 | if (completed) return; 107 | readerTask = Task.Run(() => 108 | { 109 | while (buffer.Count < maxSize) 110 | { 111 | if (cancel.IsCancellationRequested) break; 112 | try 113 | { 114 | if (!en.MoveNext()) 115 | { 116 | completed = true; 117 | buffer.CompleteAdding(); 118 | return; 119 | } 120 | buffer.Add(en.Current, cancel.Token); 121 | } 122 | catch (Exception ex) 123 | { 124 | buffer.CompleteAdding(); 125 | cancel.Cancel(); 126 | e = ex; 127 | } 128 | } 129 | readerTask = null; 130 | }); 131 | }; 132 | 133 | runReader(); 134 | 135 | void cancelAndWait() 136 | { 137 | cancel.Cancel(); 138 | buffer.CompleteAdding(); 139 | readerTask?.Wait(); 140 | } 141 | 142 | using (new DisposeAction(cancelAndWait)) 143 | { 144 | foreach (var t in buffer.GetConsumingEnumerable()) 145 | { 146 | yield return t; 147 | if (buffer.Count < maxSize / 2 || buffer.Count == 0) 148 | { 149 | if (!completed) runReader(); 150 | } 151 | } 152 | if (e != null) throw e; 153 | } 154 | } 155 | 156 | public static IEnumerable TaskedThreadedBuffer(this IEnumerable seq, int maxBatches, int batchSize) 157 | { 158 | BatchBlockingCollection buffer = new BatchBlockingCollection(batchSize); 159 | 160 | Task readerTask = null; 161 | 162 | bool completed = false; 163 | 164 | var en = seq.GetEnumerator(); 165 | 166 | CancellationTokenSource cancel = new CancellationTokenSource(); 167 | 168 | Exception e = null; 169 | 170 | Action runReader = () => 171 | { 172 | if (readerTask != null) readerTask.GetAwaiter().GetResult(); 173 | if (completed) return; 174 | readerTask = Task.Run(() => 175 | { 176 | while (buffer.BatchCount < maxBatches) 177 | { 178 | if (cancel.IsCancellationRequested) break; 179 | try 180 | { 181 | if (!en.MoveNext()) 182 | { 183 | completed = true; 184 | buffer.Complete(); 185 | return; 186 | } 187 | buffer.Add(en.Current, cancel.Token); 188 | } 189 | catch (Exception ex) 190 | { 191 | buffer.Complete(); 192 | cancel.Cancel(); 193 | e = ex; 194 | } 195 | } 196 | readerTask = null; 197 | }); 198 | }; 199 | 200 | runReader(); 201 | 202 | 203 | void cancelAndWait() 204 | { 205 | cancel.Cancel(); 206 | buffer.Complete(); 207 | readerTask?.Wait(); 208 | } 209 | 210 | using (new DisposeAction(cancelAndWait)) 211 | { 212 | foreach (var t in buffer) 213 | { 214 | yield return t; 215 | if (buffer.BatchCount < maxBatches / 2 || buffer.BatchCount == 0) 216 | { 217 | if (!completed) runReader(); 218 | } 219 | } 220 | if (e != null) throw e; 221 | } 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Logic/Mergers.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MIDIModificationFramework 9 | { 10 | public static class Mergers 11 | { 12 | public static IEnumerable MergeSequences(IEnumerable sequence1, IEnumerable sequence2, bool noClone = false) 13 | where T : MIDIEvent 14 | { 15 | var enum1 = sequence1.GetEnumerator(); 16 | var enum2 = sequence2.GetEnumerator(); 17 | T e1 = null; 18 | T e2 = null; 19 | if (noClone) 20 | { 21 | if (enum1.MoveNext()) e1 = enum1.Current; 22 | if (enum2.MoveNext()) e2 = enum2.Current; 23 | } 24 | else 25 | { 26 | if (enum1.MoveNext()) e1 = enum1.Current.Clone() as T; 27 | if (enum2.MoveNext()) e2 = enum2.Current.Clone() as T; 28 | } 29 | 30 | while (true) 31 | { 32 | if (e1 != null) 33 | { 34 | if (e2 != null) 35 | { 36 | if (e1.DeltaTime <= e2.DeltaTime) 37 | { 38 | e2.DeltaTime -= e1.DeltaTime; 39 | yield return e1; 40 | if (enum1.MoveNext()) 41 | { 42 | if (noClone) e1 = enum1.Current; 43 | else e1 = enum1.Current.Clone() as T; 44 | } 45 | else e1 = null; 46 | } 47 | else 48 | { 49 | e1.DeltaTime -= e2.DeltaTime; 50 | yield return e2; 51 | if (enum2.MoveNext()) 52 | { 53 | if (noClone) e2 = enum2.Current; 54 | else e2 = enum2.Current.Clone() as T; 55 | } 56 | else e2 = null; 57 | } 58 | } 59 | else 60 | { 61 | yield return e1; 62 | if (enum1.MoveNext()) 63 | { 64 | if (noClone) e1 = enum1.Current; 65 | else e1 = enum1.Current.Clone() as T; 66 | } 67 | else e1 = null; 68 | } 69 | } 70 | else 71 | { 72 | if (e2 == null) break; 73 | else yield return e2; 74 | if (enum2.MoveNext()) 75 | { 76 | if (noClone) e2 = enum2.Current; 77 | else e2 = enum2.Current.Clone() as T; 78 | } 79 | else e2 = null; 80 | } 81 | } 82 | } 83 | 84 | public static IEnumerable MergeSequences(IEnumerable sequence1, IEnumerable sequence2) 85 | where T : Note 86 | { 87 | var enum1 = sequence1.GetEnumerator(); 88 | var enum2 = sequence2.GetEnumerator(); 89 | T n1 = null; 90 | T n2 = null; 91 | if (enum1.MoveNext()) n1 = enum1.Current; 92 | if (enum2.MoveNext()) n2 = enum2.Current; 93 | 94 | while (true) 95 | { 96 | if (n1 != null) 97 | { 98 | if (n2 != null) 99 | { 100 | if (n1.Start < n2.Start) 101 | { 102 | yield return n1; 103 | if (enum1.MoveNext()) n1 = enum1.Current; 104 | else n1 = null; 105 | } 106 | else 107 | { 108 | yield return n2; 109 | if (enum2.MoveNext()) n2 = enum2.Current; 110 | else n2 = null; 111 | } 112 | } 113 | else 114 | { 115 | yield return n1; 116 | if (enum1.MoveNext()) n1 = enum1.Current; 117 | else n1 = null; 118 | } 119 | } 120 | else 121 | { 122 | if (n2 == null) break; 123 | else yield return n2; 124 | if (enum2.MoveNext()) n2 = enum2.Current; 125 | else n2 = null; 126 | } 127 | } 128 | } 129 | 130 | public static IEnumerable MergeSequences(IEnumerable> sequences, bool noClone = false) 131 | where T : MIDIEvent 132 | { 133 | var batch1 = new List>(); 134 | var batch2 = new List>(); 135 | foreach (var s in sequences) batch1.Add(s); 136 | if (batch1.Count == 0) return new T[0]; 137 | while (batch1.Count > 1) 138 | { 139 | int pos = 0; 140 | while (pos < batch1.Count) 141 | { 142 | if (batch1.Count - pos == 1) 143 | { 144 | batch2.Add(batch1[pos]); 145 | pos += 1; 146 | } 147 | else 148 | { 149 | batch2.Add(MergeSequences(batch1[pos], batch1[pos + 1], noClone)); 150 | pos += 2; 151 | } 152 | } 153 | batch1 = batch2; 154 | batch2 = new List>(); 155 | } 156 | return batch1[0]; 157 | } 158 | 159 | public static IEnumerable MergeSequences(IEnumerable> sequences) 160 | where T : Note 161 | { 162 | var batch1 = new List>(); 163 | var batch2 = new List>(); 164 | foreach (var s in sequences) batch1.Add(s); 165 | if (batch1.Count == 0) return new T[0]; 166 | while (batch1.Count > 1) 167 | { 168 | int pos = 0; 169 | while (pos < batch1.Count) 170 | { 171 | if (batch1.Count - pos == 1) 172 | { 173 | batch2.Add(batch1[pos]); 174 | pos += 1; 175 | } 176 | else 177 | { 178 | batch2.Add(MergeSequences(batch1[pos], batch1[pos + 1])); 179 | pos += 2; 180 | } 181 | } 182 | batch1 = batch2; 183 | batch2 = new List>(); 184 | } 185 | return batch1[0]; 186 | } 187 | 188 | public static IEnumerable MergeWithBuffer(IEnumerable sequence, FastList buffer) 189 | { 190 | double bdeltasub = 0; 191 | foreach (var _e in sequence) 192 | { 193 | var e = _e.Clone(); 194 | while (!buffer.ZeroLen && buffer.First.DeltaTime - bdeltasub < e.DeltaTime) 195 | { 196 | var be = buffer.Pop().Clone(); 197 | be.DeltaTime -= bdeltasub; 198 | bdeltasub = 0; 199 | e.DeltaTime -= be.DeltaTime; 200 | yield return be; 201 | } 202 | yield return e; 203 | bdeltasub += e.DeltaTime; 204 | } 205 | while (!buffer.ZeroLen) 206 | { 207 | var be = buffer.Pop().Clone(); 208 | be.DeltaTime -= bdeltasub; 209 | bdeltasub = 0; 210 | yield return be; 211 | } 212 | } 213 | 214 | public static IEnumerable MergeManySequences(IEnumerable> sequences) 215 | where T : Note 216 | { 217 | bool mainEnded = false; 218 | var mainIter = sequences.GetEnumerator(); 219 | var lists = new List>(); 220 | IEnumerator nextList = null; 221 | 222 | double prevstart = -1; 223 | 224 | void makeNextList() 225 | { 226 | while (!mainEnded) 227 | { 228 | if (!mainIter.MoveNext()) 229 | { 230 | mainEnded = true; 231 | break; 232 | } 233 | var seq = mainIter.Current; 234 | var it = seq.GetEnumerator(); 235 | if (it.MoveNext()) 236 | { 237 | nextList = it; 238 | break; 239 | } 240 | } 241 | if (mainEnded) nextList = null; 242 | } 243 | 244 | makeNextList(); 245 | if (nextList != null) 246 | { 247 | while (!(mainEnded && lists.Count == 0)) 248 | { 249 | double min = -1; 250 | int minid = -1; 251 | for (int i = 0; i < lists.Count; i++) 252 | { 253 | if (lists[i].Current.Start < min || i == 0) 254 | { 255 | min = lists[i].Current.Start; 256 | minid = i; 257 | } 258 | } 259 | if (minid == -1 || (nextList != null && nextList.Current.Start < lists[minid].Current.Start)) 260 | { 261 | lists.Add(nextList); 262 | minid = lists.Count - 1; 263 | makeNextList(); 264 | } 265 | 266 | if (prevstart > lists[minid].Current.Start) 267 | { } 268 | prevstart = lists[minid].Current.Start; 269 | 270 | yield return lists[minid].Current; 271 | if (!lists[minid].MoveNext()) lists.RemoveAt(minid); 272 | } 273 | } 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /EventParser.cs: -------------------------------------------------------------------------------- 1 | using MIDIModificationFramework.MIDIEvents; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | public class EventParser : IDisposable 12 | { 13 | class StreamByteReader : IByteReader 14 | { 15 | Stream stream; 16 | 17 | public StreamByteReader(Stream s) 18 | { 19 | stream = s; 20 | } 21 | 22 | public void Dispose() => stream.Dispose(); 23 | public byte Read() 24 | { 25 | int b = stream.ReadByte(); 26 | if (b == -1) throw new EndOfStreamException(); 27 | return (byte)b; 28 | } 29 | } 30 | 31 | IByteReader reader; 32 | long TrackTime { get; set; } = 0; 33 | 34 | public bool Ended { get; private set; } = false; 35 | 36 | internal EventParser(IByteReader reader) 37 | { 38 | this.reader = reader; 39 | } 40 | 41 | public EventParser(Stream reader) 42 | { 43 | this.reader = new StreamByteReader(reader); 44 | } 45 | 46 | uint ReadVariableLen() 47 | { 48 | long n = 0; 49 | while (true) 50 | { 51 | byte curByte = Read(); 52 | n = (n << 7) | (byte)(curByte & 0x7F); 53 | if ((curByte & 0x80) == 0) 54 | { 55 | break; 56 | } 57 | } 58 | return (uint)n; 59 | } 60 | 61 | int pushback = -1; 62 | byte Read() 63 | { 64 | if (pushback != -1) 65 | { 66 | byte p = (byte)pushback; 67 | pushback = -1; 68 | return p; 69 | } 70 | return reader.Read(); 71 | } 72 | 73 | byte prevCommand; 74 | public MIDIEvent ParseNextEvent() 75 | { 76 | if (Ended) return null; 77 | uint delta = ReadVariableLen(); 78 | TrackTime += delta; 79 | byte command = Read(); 80 | if (command < 0x80) 81 | { 82 | pushback = command; 83 | command = prevCommand; 84 | } 85 | prevCommand = command; 86 | byte comm = (byte)(command & 0b11110000); 87 | if (comm == 0b10010000) 88 | { 89 | byte channel = (byte)(command & 0b00001111); 90 | byte note = Read(); 91 | byte vel = Read(); 92 | if (vel == 0) return new NoteOffEvent(delta, channel, note); 93 | return new NoteOnEvent(delta, channel, note, vel); 94 | } 95 | else if (comm == 0b10000000) 96 | { 97 | byte channel = (byte)(command & 0b00001111); 98 | byte note = Read(); 99 | byte vel = Read(); 100 | return new NoteOffEvent(delta, channel, note); 101 | } 102 | else if (comm == 0b10100000) 103 | { 104 | byte channel = (byte)(command & 0b00001111); 105 | byte note = Read(); 106 | byte vel = Read(); 107 | return new PolyphonicKeyPressureEvent(delta, channel, note, vel); 108 | } 109 | else if (comm == 0b10110000) 110 | { 111 | byte channel = (byte)(command & 0b00001111); 112 | byte controller = Read(); 113 | byte value = Read(); 114 | return new ControlChangeEvent(delta, command, controller, value); 115 | } 116 | else if (comm == 0b11000000) 117 | { 118 | byte program = Read(); 119 | return new ProgramChangeEvent(delta, command, program); 120 | } 121 | else if (comm == 0b11010000) 122 | { 123 | byte pressure = Read(); 124 | return new ChannelPressureEvent(delta, command, pressure); 125 | } 126 | else if (comm == 0b11100000) 127 | { 128 | byte var1 = Read(); 129 | byte var2 = Read(); 130 | return new PitchWheelChangeEvent(delta, command, (short)(((var2 << 7) | var1) - 8192)); 131 | } 132 | else if (comm == 0b10110000) 133 | { 134 | byte cc = Read(); 135 | byte vv = Read(); 136 | return new ChannelModeMessageEvent(delta, command, cc, vv); 137 | } 138 | else if (command == 0b11110000) 139 | { 140 | List data = new List() { command }; 141 | byte b = 0; 142 | while (b != 0b11110111) 143 | { 144 | b = Read(); 145 | data.Add(b); 146 | } 147 | return new SystemExclusiveMessageEvent(delta, data.ToArray()); 148 | } 149 | else if (command == 0b11110100 || command == 0b11110001 || command == 0b11110101 || command == 0b11111001 || command == 0b11111101) 150 | { 151 | return new UndefinedEvent(delta, command); 152 | } 153 | else if (command == 0b11110010) 154 | { 155 | byte var1 = Read(); 156 | byte var2 = Read(); 157 | return new SongPositionPointerEvent(delta, (ushort)((var2 << 7) | var1)); 158 | } 159 | else if (command == 0b11110011) 160 | { 161 | byte pos = Read(); 162 | return new SongSelectEvent(delta, pos); 163 | } 164 | else if (command == 0b11110110) 165 | { 166 | return new TuneRequestEvent(delta); 167 | } 168 | else if (command == 0b11110111) 169 | { 170 | return new EndOfExclusiveEvent(delta); 171 | } 172 | else if (command == 0b11111000) 173 | { 174 | return new MajorMidiMessageEvent(delta, command); 175 | } 176 | else if (command == 0b11111010) 177 | { 178 | return new MajorMidiMessageEvent(delta, command); 179 | } 180 | else if (command == 0b11111100) 181 | { 182 | return new MajorMidiMessageEvent(delta, command); 183 | } 184 | else if (command == 0b11111110) 185 | { 186 | return new MajorMidiMessageEvent(delta, command); 187 | } 188 | else if (command == 0xFF) 189 | { 190 | command = Read(); 191 | if (command == 0x00) 192 | { 193 | if (Read() != 2) 194 | { 195 | throw new Exception("Corrupt Track"); 196 | } 197 | return new TrackStartEvent(); 198 | } 199 | else if ((command >= 0x01 && command <= 0x0A) || command == 0x7F) 200 | { 201 | int size = (int)ReadVariableLen(); 202 | var data = new byte[size]; 203 | for (int i = 0; i < size; i++) data[i] = Read(); 204 | if (command == 0x0A && 205 | (size == 8 || size == 12) && 206 | data[0] == 0x00 && data[1] == 0x0F && 207 | (data[2] < 16 || data[2] == 7F) && 208 | data[3] == 0) 209 | { 210 | if (data.Length == 8) 211 | { 212 | return new ColorEvent(delta, data[2], data[4], data[5], data[6], data[7]); 213 | } 214 | return new ColorEvent(delta, data[2], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]); 215 | } 216 | else 217 | return new TextEvent(delta, command, data); 218 | } 219 | else if (command == 0x20) 220 | { 221 | command = Read(); 222 | if (command != 1) 223 | { 224 | throw new Exception("Corrupt Track"); 225 | } 226 | return new ChannelPrefixEvent(delta, Read()); 227 | } 228 | else if (command == 0x21) 229 | { 230 | command = Read(); 231 | if (command != 1) 232 | { 233 | throw new Exception("Corrupt Track"); 234 | } 235 | return new MIDIPortEvent(delta, Read()); 236 | } 237 | else if (command == 0x2F) 238 | { 239 | command = Read(); 240 | if (command != 0) 241 | { 242 | throw new Exception("Corrupt Track"); 243 | } 244 | Ended = true; 245 | return null; 246 | } 247 | else if (command == 0x51) 248 | { 249 | command = Read(); 250 | if (command != 3) 251 | { 252 | throw new Exception("Corrupt Track"); 253 | } 254 | int btempo = 0; 255 | for (int i = 0; i != 3; i++) 256 | btempo = (int)((btempo << 8) | Read()); 257 | return new TempoEvent(delta, btempo); 258 | } 259 | else if (command == 0x54) 260 | { 261 | command = Read(); 262 | if (command != 5) 263 | { 264 | throw new Exception("Corrupt Track"); 265 | } 266 | byte hr = Read(); 267 | byte mn = Read(); 268 | byte se = Read(); 269 | byte fr = Read(); 270 | byte ff = Read(); 271 | return new SMPTEOffsetEvent(delta, hr, mn, se, fr, ff); 272 | } 273 | else if (command == 0x58) 274 | { 275 | command = Read(); 276 | if (command != 4) 277 | { 278 | throw new Exception("Corrupt Track"); 279 | } 280 | byte nn = Read(); 281 | byte dd = Read(); 282 | byte cc = Read(); 283 | byte bb = Read(); 284 | return new TimeSignatureEvent(delta, nn, dd, cc, bb); 285 | } 286 | else if (command == 0x59) 287 | { 288 | command = Read(); 289 | if (command != 2) 290 | { 291 | throw new Exception("Corrupt Track"); 292 | } 293 | byte sf = Read(); 294 | byte mi = Read(); 295 | return new KeySignatureEvent(delta, sf, mi); 296 | } 297 | else 298 | { 299 | throw new Exception("Corrupt Track"); 300 | } 301 | } 302 | else 303 | { 304 | throw new Exception("Corrupt Track"); 305 | } 306 | } 307 | 308 | public void Dispose() 309 | { 310 | reader.Dispose(); 311 | } 312 | } 313 | } -------------------------------------------------------------------------------- /ParallelStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace MIDIModificationFramework 10 | { 11 | internal class ParallelStreamIO : Stream 12 | { 13 | public override bool CanRead => true; 14 | 15 | public override bool CanSeek => true; 16 | 17 | public override bool CanWrite => true; 18 | 19 | long length = 0; 20 | public override long Length => length; 21 | 22 | public bool Closed = false; 23 | 24 | long position = 0; 25 | public override long Position 26 | { 27 | get => position; 28 | set 29 | { 30 | bool locked = false; 31 | if (!Monitor.IsEntered(stream)) 32 | { 33 | Monitor.Enter(stream); 34 | locked = true; 35 | } 36 | int offset = (int)(value % pstream.ChunkDataSize); 37 | currentChunk = (value - offset) / pstream.ChunkDataSize; 38 | if (currentChunk >= locations.Count) throw new ArgumentOutOfRangeException("Position", "Position was out of range"); 39 | currentChunkSize = ReadChunkSize(locations[(int)currentChunk]); 40 | currentChunkAvailableRead = currentChunkSize - offset; 41 | if (currentChunkAvailableRead < 0) throw new ArgumentOutOfRangeException("Position", "Position was out of range"); 42 | currentChunkAvailableWrite = pstream.ChunkDataSize - offset; 43 | position = value; 44 | streampos = pstream.HeaderChunkSize + pstream.ChunkSize * locations[(int)currentChunk] + 4 + offset; 45 | if (locked) Monitor.Exit(stream); 46 | } 47 | } 48 | 49 | List locations = new List(); 50 | 51 | long currentChunk = -1; 52 | int currentChunkSize = -1; 53 | int currentChunkAvailableRead = -1; 54 | int currentChunkAvailableWrite = -1; 55 | 56 | Func GetNewChunk; 57 | 58 | Stream stream; 59 | long streampos; 60 | ParallelStream pstream; 61 | 62 | bool locked = false; 63 | 64 | public ParallelStreamIO(long length, List locations, Func getNewChunk, Stream stream, ParallelStream pstream) 65 | { 66 | this.length = length; 67 | this.locations = locations; 68 | GetNewChunk = getNewChunk; 69 | this.stream = stream; 70 | this.pstream = pstream; 71 | Position = 0; 72 | } 73 | 74 | public override void Flush() 75 | { 76 | 77 | } 78 | 79 | void WriteChunkSize(long chunk, int length) 80 | { 81 | stream.Position = pstream.HeaderChunkSize + pstream.ChunkSize * chunk; 82 | byte[] ib = new byte[4]; 83 | int a = 0; 84 | for (int i = 3; i >= 0; i--) ib[a++] = (byte)((length >> (i * 8)) & 0xFF); 85 | stream.Write(ib, 0, 4); 86 | } 87 | 88 | int ReadChunkSize(long chunk) 89 | { 90 | stream.Position = pstream.HeaderChunkSize + pstream.ChunkSize * chunk; 91 | int v = 0; 92 | for (int j = 0; j < 4; j++) v = (v << 8) + stream.ReadByte(); 93 | return v; 94 | } 95 | 96 | public override int Read(byte[] buffer, int offset, int count) 97 | { 98 | lock (stream) 99 | { 100 | int read = 0; 101 | if (currentChunkAvailableRead == 0) return 0; 102 | while (read != count) 103 | { 104 | stream.Position = streampos; 105 | if (currentChunkAvailableRead <= count - read) 106 | { 107 | stream.Read(buffer, offset + read, currentChunkAvailableRead); 108 | read += currentChunkAvailableRead; 109 | Position += currentChunkAvailableRead; 110 | if (currentChunkAvailableRead == 0) 111 | { 112 | break; 113 | } 114 | } 115 | else 116 | { 117 | int r = stream.Read(buffer, offset + read, count - read); 118 | read = count; 119 | Position += r; 120 | } 121 | } 122 | return read; 123 | } 124 | } 125 | 126 | public override long Seek(long offset, SeekOrigin origin) 127 | { 128 | if (origin == SeekOrigin.Begin) Position = offset; 129 | if (origin == SeekOrigin.Current) Position += offset; 130 | if (origin == SeekOrigin.End) Position = length - offset; 131 | return position; 132 | } 133 | 134 | public override void SetLength(long value) 135 | { 136 | throw new NotImplementedException(); 137 | } 138 | 139 | public override void Write(byte[] buffer, int offset, int count) 140 | { 141 | lock (stream) 142 | { 143 | if (Closed) throw new Exception("Stream closed"); 144 | int written = 0; 145 | while (written != count) 146 | { 147 | int w = 0; 148 | stream.Position = streampos; 149 | if (currentChunkAvailableWrite <= count - written) 150 | { 151 | stream.Write(buffer, offset + written, currentChunkAvailableWrite); 152 | w = currentChunkAvailableWrite; 153 | } 154 | else 155 | { 156 | stream.Write(buffer, offset + written, count - written); 157 | w = count - written; 158 | } 159 | if (w > currentChunkAvailableRead) 160 | { 161 | currentChunkSize += w - currentChunkAvailableRead; 162 | if(Position + w < currentChunkSize) 163 | { } 164 | length += w - currentChunkAvailableRead; 165 | WriteChunkSize(locations[(int)currentChunk], currentChunkSize); 166 | if (w == currentChunkAvailableWrite && currentChunk == locations.Count - 1) 167 | { 168 | locations.Add(GetNewChunk()); 169 | } 170 | Position += w; 171 | written += w; 172 | } 173 | else 174 | { 175 | Position += w; 176 | written += w; 177 | } 178 | } 179 | } 180 | } 181 | 182 | public override void Close() 183 | { 184 | lock (stream) 185 | { 186 | Closed = true; 187 | } 188 | } 189 | } 190 | 191 | public class ParallelStream : IDisposable 192 | { 193 | public int ChunkSize { get; private set; } = 4096 * 4096; 194 | public int HeaderChunkSize { get; private set; } = 4096 * 4096; 195 | public int ChunkDataSize => ChunkSize - 4; 196 | 197 | Stream stream; 198 | 199 | long nextOpenChunk = 0; 200 | 201 | int[] headerData; 202 | 203 | List streams = new List(); 204 | 205 | public ParallelStream(Stream stream, int chunkSize = 4096 * 4096, int headerChunkSize = 4096 * 4096) 206 | { 207 | lock (stream) 208 | { 209 | this.stream = stream; 210 | ChunkSize = chunkSize; 211 | HeaderChunkSize = headerChunkSize; 212 | if (stream.Length < HeaderChunkSize) 213 | { 214 | stream.SetLength(HeaderChunkSize); 215 | } 216 | headerData = new int[headerChunkSize / 4]; 217 | stream.Position = 0; 218 | for (int i = 0; i < headerData.Length; i++) 219 | { 220 | int val = 0; 221 | for (int j = 0; j < 4; j++) val = (val << 8) + stream.ReadByte(); 222 | headerData[i] = val; 223 | } 224 | } 225 | } 226 | 227 | public Stream GetStream(int id, bool readOnly = false) 228 | { 229 | lock (stream) 230 | { 231 | id += 1; 232 | if (id == 0) throw new ArgumentOutOfRangeException("id", "id can't be -1"); 233 | List locations = new List(); 234 | long length = 0; 235 | lock (stream) 236 | { 237 | for (int i = 0; i < headerData.Length; i++) 238 | { 239 | if (headerData[i] == id) 240 | { 241 | locations.Add(i); 242 | length += GetChunkLen(i); 243 | } 244 | } 245 | } 246 | if (locations.Count == 0) locations.Add(GetEmptyChunk(id)); 247 | else 248 | { 249 | if (!readOnly) throw new Exception("Can't write to a closed write stream. Consider setting readOnly = true"); 250 | } 251 | var s = new ParallelStreamIO(length, locations, () => GetEmptyChunk(id), stream, this); 252 | if (readOnly) s.Close(); 253 | else streams.Add(s); 254 | s.Position = 0; 255 | return s; 256 | } 257 | } 258 | 259 | int GetChunkLen(long chunk) 260 | { 261 | stream.Position = HeaderChunkSize + chunk * ChunkSize; 262 | int l = 0; 263 | for (int i = 0; i < 4; i++) l = (l << 8) + stream.ReadByte(); 264 | return l; 265 | } 266 | 267 | void WriteInt(int v) 268 | { 269 | byte[] ib = new byte[4]; 270 | int a = 0; 271 | for (int i = 3; i >= 0; i--) ib[a++] = (byte)((v >> (i * 8)) & 0xFF); 272 | stream.Write(ib, 0, 4); 273 | } 274 | 275 | void WriteHeaderEntry(long i) 276 | { 277 | stream.Position = i * 4; 278 | WriteInt(headerData[i]); 279 | } 280 | 281 | public void CloseAllStreams() 282 | { 283 | foreach (var s in streams) if (!s.Closed) s.Close(); 284 | streams.Clear(); 285 | } 286 | 287 | public void DeleteStream(int stream) 288 | { 289 | lock (this.stream) 290 | { 291 | stream++; 292 | streams = streams.Where(s => !s.Closed).ToList(); 293 | if (streams.Count > 0) throw new Exception("All streams must be closed before using. Consider using ParallelStream.CloseAllStreams()"); 294 | for (long i = 0; i < headerData.Length; i++) 295 | { 296 | if (headerData[i] == stream) headerData[i] = 0; 297 | WriteHeaderEntry(i); 298 | } 299 | nextOpenChunk = -1; 300 | } 301 | } 302 | 303 | void WriteFullHeaderChunk() 304 | { 305 | int l = HeaderChunkSize / 4; 306 | byte[] raw = new byte[HeaderChunkSize]; 307 | int a = 0; 308 | for (int i = 0; i < l; i++) 309 | { 310 | int v = headerData[l]; 311 | for (int j = 3; j >= 0; j--) raw[a++] = (byte)((v >> (j * 8)) & 0xFF); 312 | } 313 | stream.Position = 0; 314 | stream.Write(raw, 0, HeaderChunkSize); 315 | } 316 | 317 | int[] ParseHeaderChunk() 318 | { 319 | int l = HeaderChunkSize / 4; 320 | byte[] raw = new byte[HeaderChunkSize]; 321 | int[] data = new int[l]; 322 | stream.Read(raw, 0, HeaderChunkSize); 323 | unsafe 324 | { 325 | for (int i = 0; i < l; i++) 326 | { 327 | int v = 0; 328 | for (int j = 0; j < 4; j++) v = (v << 8) + raw[l]; 329 | data[i] = v; 330 | } 331 | } 332 | return data; 333 | } 334 | 335 | long GetEmptyChunk(int stream) 336 | { 337 | if (nextOpenChunk == -1) 338 | { 339 | nextOpenChunk = 0; 340 | while (headerData[nextOpenChunk] != 0) nextOpenChunk++; 341 | } 342 | headerData[nextOpenChunk] = stream; 343 | WriteHeaderEntry(nextOpenChunk); 344 | long c = nextOpenChunk; 345 | if (HeaderChunkSize + (nextOpenChunk + 1) * ChunkSize > this.stream.Length) 346 | this.stream.SetLength(HeaderChunkSize + (nextOpenChunk + 1) * ChunkSize); 347 | this.stream.Position = HeaderChunkSize + nextOpenChunk * ChunkSize; 348 | while (headerData[nextOpenChunk] != 0) nextOpenChunk++; 349 | this.stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); 350 | return c; 351 | } 352 | 353 | public void Dispose() 354 | { 355 | stream.Close(); 356 | stream.Dispose(); 357 | } 358 | } 359 | } 360 | --------------------------------------------------------------------------------