2012/07/05(木)C#でMidiファイルを読む
C#でMidiファイルを読む。
とりあえず、読めればいいや版。
/* MidiFile.cs */ using System; using System.Collections.Generic; using System.Text; using System.IO; namespace Tsukikage.WinMM.MidiIO { public enum EventType { NoteOff = 0x80, NoteOn = 0x90, PolyKeyPress = 0xA0, ControlChange = 0xB0, ProgramChange = 0xC0, ChannelPress = 0xD0, PichBend = 0xE0, SystemExclusive = 0xF0, SequenceNumber = 0xFF00, TextEvent = 0xFF01, CopyrightNotice = 0xFF02, TrackName = 0xFF03, InstrumentName = 0xFF04, LyricText = 0xFF05, MarkerText = 0xFF06, CuePoint = 0xFF07, MidiChannelPrefix = 0xFF20, EndOfTrack = 0xFF2F, TempoSetting = 0xFF51, SMTPEOffset = 0xFF54, TimeSigunature = 0xFF58, KeySigunature = 0xFF59, SequencerSpecificEvent = 0xFF7F, } public class MidiSequence { #region StreamReader static int ReadInt2(Stream s) { int a = 0; a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); return a; } static int ReadInt3(Stream s) { int a = 0; a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); return a; } static int ReadInt4(Stream s) { int a = 0; a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); a = a << 8 | s.ReadByte(); return a; } static int ReadIntX(Stream s) { int a = 0; while (true) { int c = s.ReadByte(); a = a << 7 | (c & 0x7F); if ((c & 0x80) == 0) break; } return a; } static string ReadText(Stream s) { int length = ReadIntX(s); byte[] buf = new byte[length]; s.Read(buf, 0, length); return Encoding.GetEncoding(932).GetString(buf, 0, length); } static byte[] ReadSysEx(Stream s) { int length = ReadIntX(s); byte[] buf = new byte[length+1]; buf[0] = 0xF0; s.Read(buf, 1, length); return buf; } static void ReadUnknownMeta(Stream s) { int length = ReadIntX(s); byte[] buf = new byte[length]; s.Read(buf, 0, length); } #endregion public string Title = null, Copyright = null; public int Tracks; public int TimeBase; public MidiSequence(Stream source) { Load(source); } public MidiSequence(string path) { using (FileStream fs = File.OpenRead(path)) { Load(fs); } } void Load(Stream fs) { List<string> stringTable = new List<string>(256); List<byte[]> sysexTable = new List<byte[]>(256); List<MidiEvent> eventList = new List<MidiEvent>(65536); { // Read HEADER if (ReadInt4(fs) != 0x4D546864) /* MThd */ { byte[] aaa = new byte[256]; fs.Read(aaa, 0, 16); /* RIFF MIDI */ if (ReadInt4(fs) != 0x4D546864) /* MThd */ { fs.Read(aaa, 0, 108); /* MAC BINARY */ if (ReadInt4(fs) != 0x4D546864) /* MThd */ throw new ArgumentException("SMF/RMIじゃないっぽい", "path"); } } int len = ReadInt4(fs); int fmt = ReadInt2(fs); Tracks = ReadInt2(fs); TimeBase = ReadInt2(fs); } for (int track = 0; track < Tracks; track++) { if (ReadInt4(fs) != 0x4D54726B) /* MTrk */ throw new ArgumentException("SMFが壊れてるか対応してないっぽです。", "path"); int end = ReadInt4(fs) + (int)fs.Position; int currentPos = 0; int lastStatus = 0; while (fs.Position < end) { currentPos += ReadIntX(fs); // deltaTime int first = -1, second = -1, third = -1; first = fs.ReadByte(); if ((first & 0xF0) == 0) { // running status second = first; first = lastStatus; } lastStatus = first; int eventType = first & 0xF0; if (eventType != 0xF0) { // short message second = second != -1 ? second : fs.ReadByte(); if (eventType != 0xC0) third = fs.ReadByte(); else third = 0; eventList.Add(new MidiEvent(currentPos, (EventType)eventType, first, second, third)); } else if (first == 0xF0 || first == 0xF7) { // long message byte[] ex = ReadSysEx(fs); eventList.Add(new MidiEvent(currentPos, EventType.SystemExclusive, sysexTable.Count)); sysexTable.Add(ex); } else if (first == 0xFF) { // meta event EventType metatype = (EventType)(0xFF00 | fs.ReadByte()); switch (metatype) { case EventType.TextEvent: case EventType.CopyrightNotice: case EventType.TrackName: case EventType.LyricText: case EventType.MarkerText: case EventType.CuePoint: string text = ReadText(fs); eventList.Add(new MidiEvent(currentPos, metatype, stringTable.Count)); if ((EventType)metatype == EventType.CopyrightNotice) Copyright = text; if ((EventType)metatype == EventType.TrackName && Title == null) Title = text; stringTable.Add(text); break; case EventType.TempoSetting: ReadIntX(fs); eventList.Add(new MidiEvent(currentPos, metatype, ReadInt3(fs))); break; case EventType.TimeSigunature: ReadIntX(fs); eventList.Add(new MidiEvent(currentPos, metatype, ReadInt4(fs))); break; case EventType.KeySigunature: ReadIntX(fs); eventList.Add(new MidiEvent(currentPos, metatype, ReadInt2(fs))); break; default: ReadUnknownMeta(fs); break; } } } } // All Tracks was readed. Events = eventList.ToArray(); MargeSort(Events, delegate(MidiEvent x, MidiEvent y) { return x.Position - y.Position; }); StringTable = stringTable.ToArray(); SysExTable = sysexTable.ToArray(); } /// <summary> /// 入力されたノートオンイベントに対するノートオフの位置を検索する。 /// </summary> /// <param name="noteOnEvent">noteOnEvent</param> /// <returns>位置</returns> public int GetNoteOffEventIndex(int noteOnEvent) { int i = noteOnEvent + 1; if (Events[noteOnEvent].EventType != EventType.NoteOn) new ArgumentException("NoteOnイベントのインデックスではないようです。", "noteOnEvent"); int noteCh = Events[noteOnEvent].Channel; int noteNum = Events[noteOnEvent].NoteNumber; int noteVel = Events[noteOnEvent].NoteVelocity; for (; i < Events.Length; i++) { MidiEvent e = Events[i]; if (e.EventType == EventType.NoteOff && e.Channel == noteCh && e.NoteNumber == noteNum) return i; if (e.EventType == EventType.NoteOn && e.Channel == noteCh && e.NoteNumber == noteNum && e.NoteVelocity == 0) return i; } return -1; } public MidiEvent[] Events; public String[] StringTable; public byte[][] SysExTable; static void MargeSort<T>(T[] target, Comparison<T> comparer) { T[] work = new T[target.Length]; for (int i = 0; ; i++) { int n = 1 << i; int m = n * 2; for (int j = 0; ; j++) { int p = m * j; int p1 = p, e1 = p1 + n; int p2 = e1, e2 = Math.Min(p2 + n, target.Length); int seg = e2 - p1; if (seg <= n) break; for (int k = 0; k < seg; k++) { if (p2 >= e2) work[k] = target[p1++]; else if (p1 >= e1) work[k] = target[p2++]; else if (comparer(target[p1], target[p2]) <= 0) work[k] = target[p1++]; else work[k] = target[p2++]; } Array.Copy(work, 0, target, p, seg); } if (n >= target.Length) break; } } } public struct MidiEvent { public MidiEvent(int position, EventType type, int data) { this.position = position; this.type = type; this.data = data; this.UserData = 0; if (this.type == EventType.NoteOn && NoteVelocity == 0) { this.data ^= 0x10; this.type = EventType.NoteOff; } } public MidiEvent(int position, EventType type, int first, int second, int third) { this.position = position; this.type = type; this.data = first | second << 8 | third << 16; this.UserData = 0; if (this.type == EventType.NoteOn && NoteVelocity == 0) { this.data ^= 0x10; this.type = EventType.NoteOff; } } int position; EventType type; int data; public int UserData; public int Position { get { return position; } } public EventType EventType { get { return type; } } public int Channel { get { return data & 0x0F; } } public int NoteNumber { get { return data >> 8 & 0xFF; } } public int NoteVelocity { get { return data >> 16 & 0xFF; } } public int ControlNumber { get { return data >> 8 & 0xFF; } } public int ControlValue { get { return data >> 16 & 0xFF; } } public int ProgramNumber { get { return data >> 8 & 0xFF; } } public int PichBend { get { return (data >> 1 & 0x3F80) | (data >> 16 & 0x7F); } } public int Tempo { get { return data; } } public double BPM { get { return 60000000.0 / data; } } public int TimeNum { get { return data >> 24 & 0xFF; } } public int TimeDenom { get { return data >> 16 & 0xFF; } } public int TimeBeat { get { return data >> 8 & 0xFF; } } public int ScaleSharps { get { return data >> 8 & 0xFF; } } public int ScaleMajor { get { return data & 0xFF; } } public int SysExIndex { get { return data; } } public int TextIndex { get { return data; } } public uint RawData { get { return (uint)data; } } public override string ToString() { string pos = "" + (position / 1920 + 1).ToString("D4") + "." + (position % 1920 / 480 + 1) + "." + (position % 480).ToString("D3") + " > "; switch (EventType) { case EventType.NoteOn: return pos + "NoteOn : Ch." + Channel + " N" + NoteNumber + " V" + NoteVelocity; case EventType.NoteOff: return pos + "NoteOff : Ch." + Channel + " N" + NoteNumber; case EventType.ProgramChange: return pos + "ProgChg : Ch." + Channel + " Prog." + ProgramNumber; case EventType.ControlChange: return pos + "CtrlChg : Ch." + Channel + " CC" + ControlNumber + ">" + ControlValue; case EventType.PichBend: return pos + "PichBend : Ch." + Channel + " PB." + PichBend; case EventType.ChannelPress: return pos + "ChannelPress : Ch." + Channel; case EventType.PolyKeyPress: return pos + "PolyKeyPress : Ch." + Channel + " N" + NoteNumber + " V" + NoteVelocity; case EventType.SystemExclusive: return pos + "SysEx : " + data; case EventType.TempoSetting: return pos + "Tempo : " + data + "μs/beat = BPM. " + BPM + ")"; case EventType.TimeSigunature: return pos + "TimeSign : " + TimeNum + " / " + "2^" + TimeDenom; case EventType.KeySigunature: return pos + "KeySign : Sharps." + ScaleSharps + " " + (ScaleMajor == 1 ? "Major" : "Minor"); case EventType.TextEvent: return pos + "Text : " + data; case EventType.TrackName: return pos + "TrackName : " + data; case EventType.CopyrightNotice: return pos + "Copyright : " + data; case EventType.InstrumentName: return pos + "InstName : " + data; case EventType.LyricText: return pos + "Lyric : " + data; case EventType.MarkerText: return pos + "Marker : " + data; case EventType.CuePoint: return pos + "CuePoint : " + data; default: return pos + EventType.ToString() + " : " + data; } } } }