// chunk = <chunk_id> + <chunk_length> + <chunk_bytes> public Chunk(Stream stream) { Id = StreamHelperBe.ReadString(stream, 4); Length = (int)StreamHelperBe.ReadUInt32(stream); // DebugConsole.WriteLine("Padding " + (Length & 1)); Bytes = new byte[Length]; stream.Read(Bytes, 0, Length); }
public MidiFile(Stream stream) { // header_chunk = "MThd" + <header_length> + <format> + <n> + <division> var chunk = new Chunk(stream); if (chunk.Id != "MThd") { throw new FileFormatException("MidiFile.chunk.Id", chunk.Id, "MThd"); } using (var header = chunk.GetStream()) { Format = (MidiFileFormat)StreamHelperBe.ReadInt16(header); NumberOfTracks = StreamHelperBe.ReadUInt16(header); var division = StreamHelperBe.ReadInt16(header); if (division > 0) { TicksPerBeat = division; } else { throw new Exception("STMPE based time is not supported"); } } // track_chunk = "MTrk" + <track_length> [+ <delta_time> + <event> ...] for (int track = 0; track < NumberOfTracks; track++) { chunk = new Chunk(stream); if (chunk.Id != "MTrk") { throw new FileFormatException("MidiFile.chunk.Id", chunk.Id, "MTrk"); } using (var trackStream = chunk.GetStream()) { BuildTrack(trackStream, track); } } Sort(); Rebase(); Trim(); }
void BuildTrack(Stream stream, int track) { int tick = 0; byte runningStatus = 0x00; while (stream.Position < stream.Length) { tick += (int)StreamHelperBe.ReadVlv(stream); var statusByte = (byte)stream.ReadByte(); if (statusByte < 0xF0) // Midi events (status bytes 0x8n - 0xEn) { byte dataByte1; if (statusByte < 0x80) // If the first byte < 0x80, running status is in effect, and this byte is actually the first data byte. { dataByte1 = statusByte; statusByte = runningStatus; } else { dataByte1 = (byte)stream.ReadByte(); runningStatus = statusByte; } var midiEvent = new MidiEvent(track, tick, statusByte); switch (statusByte & 0xF0) { case 0x80: // Note Off case 0x90: // Note On case 0xA0: // Aftertouch case 0xB0: // Controller Change case 0xE0: // Pitch Bend midiEvent.DataByte1 = dataByte1; midiEvent.DataByte2 = (byte)stream.ReadByte(); break; case 0xC0: // Program Change case 0xD0: // Channel Aftertouch midiEvent.DataByte1 = dataByte1; break; default: throw new FileFormatException("MidiFile.statusByte", statusByte.ToString("X"), "[midi statusByte]"); } if (midiEvent.Type == MidiEventType.NoteOn && midiEvent.Velocity == 0) { midiEvent.StatusByte = (byte)(0x80 | midiEvent.Channel); } MidiEvents.Add(midiEvent); } else if (statusByte == 0xF0 || statusByte == 0xF7) // SysEx events (status bytes 0xF0 and 0xF7) { runningStatus = 0x00; var sysExEvent = new SysExEvent(track, tick, (int)StreamHelperBe.ReadVlv(stream)); stream.Read(sysExEvent.Bytes, 0, sysExEvent.Length); SysExEvents.Add(sysExEvent); } else if (statusByte == 0xFF) // Meta events (status byte 0xFF) { runningStatus = 0x00; var metaEvent = new MetaEvent(track, tick, (byte)stream.ReadByte(), (int)StreamHelperBe.ReadVlv(stream)); stream.Read(metaEvent.Data, 0, metaEvent.Length); MetaEvents.Add(metaEvent); } else { throw new FileFormatException("MidiFile.statusByte", statusByte.ToString("X"), "[midi statusByte]"); } } Length = Math.Max(Length, tick); }