프로그래밍 기술/미디 파일 구조 및 미디 분석 프로그램 만들기

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 4 - Track 청크 2, Meta Event

언제나휴일 2018. 4. 12. 16:13
반응형

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 4 - Track 청크 2, Meta Event


 

 안녕하세요. 언제나 휴일, 언휴예요.

 

 

 이전 게시글에서 미디 파일에서 Track 청크의 기본 구조와 delta time을 구하는 것에 관하여 다루었어요.

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 1 - 청크 목록

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 2 - Head 청크, Mthd

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 3 - Track 청크 1

 

 이번에는 Track 청크의 Meta 이벤트에 관하여 알아보고 분석하는 소스 코드를 소개할게요.

 

 Track 청크의 이벤트는 delta time과 이벤트 정보로 이루어져 있다는 것을 이전 게시글에서 얘기했어요. 또한 이벤트 정보는 Meta 이벤트, System 이벤트, Midi 이벤트 중에 하나라는 것도 소개했었죠.

 

 이벤트 정보가 Meta 이벤트, System 이벤트, Midi 이벤트 중에 어떤 것인지를 확인하려면 delta time 뒤에 오는 첫 번째 바이트 정보의 값을 확인하여야 합니다.

 

 해당 값이 0xFF이면 Meta 이벤트이며 0xF0 ~ 0xFE사이라면 System 이벤트, 0x00~0xEF 사이라면 Midi 이벤트입니다. 

 

 여기에서는 Meta 이벤트를 다루고 다른 이벤트는 뒤에서 다루기로 할게요.

 

 Meta 이벤트는 delta time + 0xFF + Meta Data로 구성합니다. 그리고 Meta Data는 0xFF 뒤에 오는 1바이트의 값에 따라 의미가 달라집니다. 다음은 Meta 이벤트에서 delta time 뒤에 오는 값들에 관한 표입니다.

 

Status byte

Meta Data byte

2nd byte

Other bytes

Description

FF

00

02 ss ss

Set track’s sequence #

FF

01

nn tt…

Any Text user wants

FF

02

nn tt…

Text for copyright info.

FF

03

nn tt…

Track name

FF

04

nn tt…

Track instrument name

FF

05

nn tt…

Lyric

FF

06

nn tt…

Marker

FF

07

nn tt…

Cue point

FF

20

01 channel

Channel prefix

FF

21

01 pp

MIDI port

FF

2F

00

End of Track

FF

51

03 tt tt tt

Set tempo

(microseconds/quarter note)

FF

54

05 hh mm ss ff sf

hour/minute/second

/frame/subframe

SMPTE Offset

FF

58

04 nn dd cc bb

numerator/

denominator/

metronome ticks #/

32nd notes# per quarter note

Time signature

 

FF

59

02 sf mi

key(sharp/flat#)

scale(0:major, 1:minor)

Key signature

(C when Key=0)

FF

7F

nn tt…

Sequencer specific info.

 

표를 보면 0xFF 뒤에 0x2F가 오면 트랙의 끝을 의미하며 0x2F 뒤에 1바이트까지 Meta Data 필드임을 알 수 있습니다.

그 외에는 delta time + 0xFF + Meta Data 구분자(1바이트) + 길이(1바이트) + 데이터(가변)임을 알 수 있습니다. 

 

 다음 글에서는 메타 이벤트에 관해 보다 상세하게 알아야 할 부분을 살펴보기로 하고 여기에서는 먼저 현재까지의 내용을 분석할 수 있는 소스 코드를 소개하기로 할게요.

 

[StaticFuns.cs] 공통으로 사용할 함수를 래핑한 정적 클래스

using System;

using System.Net;

using System.Text;

 

namespace 트랙_청크_분석

{

    public static class StaticFuns

    {

        public static string GetString(int magic)

        {

            byte[] data = BitConverter.GetBytes(magic);

            ASCIIEncoding en = new ASCIIEncoding();

            return en.GetString(data);

        }

 

        public static short ConvertHostorderS(byte[] data, int offset)

        {

            return ConvertHostorder(BitConverter.ToInt16(data, offset));

        }

        public static short ConvertHostorder(short data)

        {

            return IPAddress.NetworkToHostOrder(data);

        }

 

        public static string GetString(byte[] data)

        {

            char[] buf = new char[data.Length];

            for (int i = 0; i < data.Length; i++)

            {

                buf[i] = (char)data[i];

            }

            //return new String(buf);

            ASCIIEncoding en = new ASCIIEncoding();

            return en.GetString(data);

        }

 

        public static int ConvertHostorder(int data)

        {

            return IPAddress.NetworkToHostOrder(data);

        }

 

        public static string HexaString(byte[] buffer)

        {

            string str = "";

            foreach (byte d in buffer)

            {

                str += string.Format(" ", d);

            }

            return str;

        }

        public static int ReadDeltaTime(byte[] buffer, ref int offset)

        {

            int time = 0;

            byte b;

            do

            {

                b = buffer[offset];

                offset++;

                time = (time<<7)|(b & 0x7F);

            } while (b > 127);

            return time;

        }

    }

}

 

 

[MDEvent.cs]

namespace 트랙_청크_분석

{

    public class MDEvent

    {

        public int Delta

        {

            get;

            private set;

        }

        public byte EventType

        {

            get;

            private set;

        }

        public byte[] Buffer

        {

            get;

            private set;

        }

 

        public MDEvent(byte evtype,int delta,byte[] buffer)

        {

            EventType = evtype;

            Delta = delta;

            Buffer = buffer;

        }

 

        public static MDEvent Parsing(byte[] buffer, ref int offset,MDEvent bef_event)

        {

            int oldoffset = offset;

            int delta = StaticFuns.ReadDeltaTime(buffer, ref offset);

            if(buffer[offset]==0xFF)

            {

                offset++;

                return MetaEvent.MakeEvent(delta, buffer,ref offset,oldoffset);

            }

            return null;//미디 이벤트와 시스템 이벤트 부분은 차후에 구현                      

        }

    }

}

 

 

[MetaEvent.cs]

using System;

 

namespace 트랙_청크_분석

{

    public class MetaEvent:MDEvent

    {

        public byte Msg //어떤 종류의 메타 이벤트인지를 판별

        {

            get;

            private set;

        }

        public byte Length //메타 데이터 길이

        {

            get;

            private set;

        }

        public byte[] Data//메타 데이터

        {

            get;

            private set;

        }

        public string DataString//메타 데이터를 문자열로 변환한 값

        {

            get

            {

                if(Data == null)

                {

                    return string.Empty;

                }

                return StaticFuns.GetString(Data);

            }

        }

        public MetaEvent(int delta,byte msg, byte len, byte[] data,byte[] orgbuffer):base(0xFF,delta,orgbuffer)

        {

            Msg = msg;

            Length = len;

            Data = data;

        }

 

        public static MDEvent MakeEvent(int delta, byte[] buffer, ref int offset, int oldoffset)

        {

            byte msg = buffer[offset++];

            byte len = buffer[offset++];

            byte[] data = null;

            if (msg != 0x2F)

            {

                data = new byte[len];

                Array.Copy(buffer, offset, data, 0, len);

                offset += len;

            }

            byte[] buffer2 = new byte[offset - oldoffset];

            Array.Copy(buffer, oldoffset, buffer2, 0, buffer2.Length);

            return new MetaEvent(delta, msg, len, data, buffer2);

        }

    }

 

}

 

[Track.cs]

using System.Collections;

using System.Collections.Generic;

 

namespace 트랙_청크_분석

{

    public class Track:Chunk,IEnumerable

    {

        List<MDEvent> events = new List<MDEvent>();

        public Track(int ctype, int length, byte[] buffer):base(ctype,length,buffer)

        {

            Parsing(buffer);

        }

 

        public IEnumerator GetEnumerator()

        {

            return events.GetEnumerator();

        }

 

        private void Parsing(byte[] buffer)

        {

            int offset = 0;

            MDEvent mdevent=null;

            while (offset<buffer.Length)

            {

                mdevent = MDEvent.Parsing(buffer,ref offset,mdevent);

                if(mdevent == null) //분석하지 못한 것이 오면 현재 트랙의 뒷 부분 분석은 Skip

                {

                    break;

                }

                events.Add(mdevent);

            }

        }

    }

}

 

[Chunk.cs]

using System;

using System.IO;

 

namespace 트랙_청크_분석

{

    public class Chunk

    {

        public int CT//청크 유형

        {

            get;

            private set;

        }

        public int Length//청크 길이

        {

            get;

            private set;

        }

        public byte[] Data//데이터

        {

            get;

            private set;

        }

        public string CTString//청크 유형(문자열)

        {

            get

            {

                return StaticFuns.GetString(CT);

            }

        }

        public byte[] Buffer

        {

            get

            {

                byte[] ct_buf = BitConverter.GetBytes(CT);

                int belen = StaticFuns.ConvertHostorder(Length);

                byte[] len_buf = BitConverter.GetBytes(belen);

                byte[] buffer = new byte[ct_buf.Length + len_buf.Length + Data.Length];

                Array.Copy(ct_buf, buffer, ct_buf.Length);

                Array.Copy(len_buf, 0, buffer, ct_buf.Length, len_buf.Length);

                Array.Copy(Data, 0, buffer, ct_buf.Length + len_buf.Length, Data.Length);

                return buffer;

            }

        }

        public static Chunk Parse(Stream stream)

        {

            try

            {

                BinaryReader br = new BinaryReader(stream);

                int ctype = br.ReadInt32();

                int length = br.ReadInt32();

                length = StaticFuns.ConvertHostorder(length);

                byte[] buffer = br.ReadBytes(length);

                int cval = StaticFuns.ConvertHostorder(ctype);

                switch (cval)

                {

                    case 0x4d546864: return new Header(ctype, length, buffer);

                    case 0x4d54726b: return new Track(ctype, length, buffer);

                }

                return new Chunk(ctype, length, buffer);

            }

            catch

            {

                return null;

            }

        }

        public Chunk(int ctype, int length, byte[] buffer)

        {

            CT = ctype;

            Length = length;

            Data = buffer;

        }

    }

}

 

[Header.cs]

namespace 트랙_청크_분석

{

    public class Header:Chunk

    {

        public int Format//포멧

        {

            get

            {

                return StaticFuns.ConvertHostorderS(Data, 0);

            }

        }

 

        public int TrackCount//트랙 개수

        {

            get

            {

                return StaticFuns.ConvertHostorderS(Data, 2);

            }

        }

 

        public int Division//Division

        {

            get

            {

                /*첫 번째 비트가 1일 때는 다른 코드가 필요함*/

                return StaticFuns.ConvertHostorderS(Data, 4);

            }

        }

        public Header(int ctype, int length, byte[] buffer):base(ctype,length,buffer)

        {

        }

    }

}

 

 

 

[Program.cs]

using System;

using System.IO;

 

namespace 트랙_청크_분석

{

    class Program

    {

        static string fname = "example.mid";

        static void Main(string[] args)

        {

            FileStream fs = new FileStream(fname, FileMode.Open);

            while (fs.Position < fs.Length)

            {

                Chunk chunk = Chunk.Parse(fs);

                if (chunk != null)

                {

                    Console.WriteLine(" : bytes", chunk.CTString, chunk.Length);

                }

                if (chunk is Header)

                {

                    ViewHeader(chunk as Header);

                }

                if (chunk is Track)

                {

                    ViewTrack(chunk as Track);

                }

            }

        }

 

        private static void ViewTrack(Track track)

        {

            Console.WriteLine("=== Track Chuck ===");

            int ecnt = 0;

            foreach (MDEvent mdevent in track)

            {

                ecnt++;

                Console.WriteLine(StaticFuns.HexaString(mdevent.Buffer));

                Console.WriteLine("th delta:", ecnt, mdevent.Delta);

 

                if (mdevent is MetaEvent)

                {

                   

                    Console.Write("<Meta>");

                    ViewMeta(mdevent as MetaEvent);

                }               

            }

        }

 

        private static void ViewMeta(MetaEvent metaevent)

        {

            Console.Write("메시지: ", metaevent.Msg);

            Console.Write("길이: ", metaevent.Length);

            Console.WriteLine(metaevent.DataString);

        }

 

        private static void ViewHeader(Header header)

        {

            Console.WriteLine("=== 헤더 Chuck ===");

            Console.WriteLine(StaticFuns.HexaString(header.Buffer));

            Console.WriteLine("Format:", header.Format);

            Console.WriteLine("Tracks:", header.TrackCount);

            Console.WriteLine("Division:", header.Division);

            Console.WriteLine();

        }

    }

}

 

사용한 미디파일:

미디 파일.zip
다운로드

 

미디 파일 Meta 이벤트 분석 실행 화면

 

반응형