private static void loadDirectly(string filePath, List <MidiTrack> midiTracks, ref ushort timeDivision) // returns the MIDI loaded in the List of all individual tracks { // FileStreams seem to have their own buffering layer so there is no need for an additional Buffered Stream FileStream midiFileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); BinaryReader midiBinaryStream = new BinaryReader(midiFileStream); midiFileStream.Position = 0xA; // seek to the amount of tracks in the MIDI file int numTracks = midiBinaryStream.ReadByte() << 8 | midiBinaryStream.ReadByte(); timeDivision = (ushort)(midiBinaryStream.ReadByte() << 8 | midiBinaryStream.ReadByte()); // finished reading the header data, now continue transscribing the tracks for (int currentTrack = 0; currentTrack < numTracks; currentTrack++) { MidiTrack cTrk = new MidiTrack(); midiTracks.Add(cTrk); // we have to create the object of the track first and we can add it later to out track list if the track was transscribed into it's objects long currentTick = 0; NormalType lastEventType = NormalType.NoteOFF; byte lastMidiChannel = 0; // check if the track doesn't begin like expected with an MTrk string byte[] textString = new byte[4]; midiBinaryStream.Read(textString, 0, 4); if (Encoding.ASCII.GetString(textString, 0, 4) != "MTrk") { throw new Exception("Track doesn't start with MTrk string!"); } byte[] intArray = new byte[4]; midiBinaryStream.Read(intArray, 0, 4); // read the track length // this value isn't even needed, so we don't do further processing with it; I left it in the code for some usage in the future; no specific plan??? // now do the event loop and load all the events #region EventLoop while (true) { // first thing that is done is getting the next delta length value and add the value to the current position to calculate the absolute position of the event currentTick += readVariableLengthValue(midiBinaryStream); // now check what event type is used and disassemble it byte eventTypeByte = midiBinaryStream.ReadByte(); // do a jumptable for each event type if (eventTypeByte == 0xFF) // if META Event { byte metaType = (byte)midiFileStream.ReadByte(); long metaLength = readVariableLengthValue(midiBinaryStream); byte[] metaData = new byte[metaLength]; midiBinaryStream.Read(metaData, 0, (int)metaLength); if (metaType == 0x2F) { break; // if end of track is reached, break out of the loop, End of Track Events aren't written into the objects } cTrk.midiEvents.Add(new MetaMidiEvent(currentTick, metaType, metaData)); } else if (eventTypeByte == 0xF0 || eventTypeByte == 0xF7) // if SysEx Event { long sysexLength = readVariableLengthValue(midiBinaryStream); byte[] sysexData = new byte[sysexLength]; midiBinaryStream.Read(sysexData, 0, (int)sysexLength); cTrk.midiEvents.Add(new SysExMidiEvent(currentTick, eventTypeByte, sysexData)); } else if (eventTypeByte >> 4 == 0x8) // if Note OFF command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.NoteOFF, par1, par2)); // save the last event type and channel lastEventType = NormalType.NoteOFF; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0x9) // if Note ON command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.NoteON, par1, par2)); // save the last event type and channel lastEventType = NormalType.NoteON; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0xA) // if Aftertouch command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.NoteAftertouch, par1, par2)); // save the last event type and channel lastEventType = NormalType.NoteAftertouch; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0xB) // if MIDI controller command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.Controller, par1, par2)); // save the last event type and channel lastEventType = NormalType.Controller; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0xC) // if Preset command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = 0x0; // unused cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.Program, par1, par2)); // save the last event type and channel lastEventType = NormalType.Program; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0xD) // if Channel Aftertouch command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = 0x0; // unused cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.ChannelAftertouch, par1, par2)); // save the last event type and channel lastEventType = NormalType.ChannelAftertouch; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 == 0xE) // if Pitch Bend command { byte par1 = midiBinaryStream.ReadByte(); byte par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, (byte)(eventTypeByte & 0xF), NormalType.PitchBend, par1, par2)); // save the last event type and channel lastEventType = NormalType.PitchBend; lastMidiChannel = (byte)(eventTypeByte & 0xF); } else if (eventTypeByte >> 4 < 0x8) { byte par1 = eventTypeByte; byte par2; switch (lastEventType) { case NormalType.NoteOFF: par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.NoteOFF, par1, par2)); break; case NormalType.NoteON: par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.NoteON, par1, par2)); break; case NormalType.NoteAftertouch: par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.NoteAftertouch, par1, par2)); break; case NormalType.Controller: par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.Controller, par1, par2)); break; case NormalType.Program: cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.Program, par1, 0x0)); break; case NormalType.ChannelAftertouch: cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.ChannelAftertouch, par1, 0x0)); break; case NormalType.PitchBend: par2 = midiBinaryStream.ReadByte(); cTrk.midiEvents.Add(new MessageMidiEvent(currentTick, lastMidiChannel, NormalType.PitchBend, par1, par2)); break; } } else { throw new Exception("Bad MIDI event at 0x" + midiBinaryStream.BaseStream.Position.ToString("X8") + ": 0x" + eventTypeByte.ToString("X2")); } } // end of the event transscribing loop #endregion } // end of the track loop midiBinaryStream.BaseStream.Close(); } // end of function loadDirectly
public static void saveToFile(string filePath, List <MidiTrack> midiTracks, ushort timeDivision) { Console.WriteLine("Saving MIDI to type 1 file..."); // first of all check if a file with the name already exists if (File.Exists(filePath)) { File.Delete(filePath); } FileStream midiFileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None); BinaryWriter midiWriter = new BinaryWriter(midiFileStream); Console.WriteLine("The new MIDI file has {0} tracks!", midiTracks.Count); // first of all write MIDI header string midiWriter.Write(Encoding.ASCII.GetBytes("MThd")); // writer the header chunk length (=6) midiWriter.Write(intToBigEndian(6)); // write the midi file type (=1) midiWriter.Write(ushortToBigEndian(1)); // write the amount of tracks midiWriter.Write(ushortToBigEndian((ushort)midiTracks.Count)); // write the time division midiWriter.Write(ushortToBigEndian(timeDivision)); // finished writing the header, now do the tracks long[] trackHeaderOffset = new long[midiTracks.Count]; long[] trackStartOffset = new long[midiTracks.Count]; long[] trackEndOffset = new long[midiTracks.Count]; for (int currentTrack = 0; currentTrack < midiTracks.Count; currentTrack++) { MidiTrack cTrk = midiTracks[currentTrack]; trackHeaderOffset[currentTrack] = midiWriter.BaseStream.Position; // save the offset to the track header // write the header info midiWriter.Write(Encoding.ASCII.GetBytes("MTrk")); midiWriter.Write((int)0); // write 0 into the chunk length slot; it'll get filled later; 0 is the same in Little Endian as in Big, so we can use the normal int32 writing trackStartOffset[currentTrack] = midiWriter.BaseStream.Position; // save the track beginning (doesn't point to the header, it points to the data) long currentTick = 0; // init the current tick to 0 to calculate Delta Time values for (int currentEvent = 0; currentEvent < cTrk.midiEvents.Count; currentEvent++) { // write the Delta time to the stream midiWriter.Write(VariableLength.ConvertToVariableLength(cTrk.midiEvents[currentEvent].absoluteTicks - currentTick)); // write the actual Event data midiWriter.Write(cTrk.midiEvents[currentEvent].getEventData()); currentTick = cTrk.midiEvents[currentEvent].absoluteTicks; } midiWriter.Write((byte)0x0); // write delta time for track end midiWriter.Write((byte)0xFF); // write META event byte midiWriter.Write((byte)0x2F); // write End of Track byte midiWriter.Write((byte)0x0); // the length of this META event data is 0 (no data follows) trackEndOffset[currentTrack] = midiWriter.BaseStream.Position; // calc the length of the event data and backup the position } // end of track loop midiWriter.BaseStream.Close(); // close the filestreams and create a new one to edit the file midiFileStream = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None); midiWriter = new BinaryWriter(midiFileStream); for (int currentTrack = 0; currentTrack < midiTracks.Count; currentTrack++) { midiWriter.BaseStream.Position = trackHeaderOffset[currentTrack] + 4; midiWriter.Write(intToBigEndian((int)(trackEndOffset[currentTrack] - trackStartOffset[currentTrack]))); } midiWriter.Close(); // close filestream and finish Console.WriteLine("Successfully finished creating MIDI file!"); } // end of function