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

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

언제나휴일 2018. 4. 9. 15:32
반응형

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


 

참고할 파일: 

미디 파일.zip
다운로드

 

 

 

 안녕하세요. 언제나 휴일, 언휴입니다.

 

(이 글은 동영상 강의와 내용이 다소 차이가 있을 수 있습니다. 본문 바로가기)

 이전 글에서 미디 파일은 청크들의 집합이라는 얘기와 함께 이를 확인하는 C# 소스 코드를 소개하였습니다.

 

 이번에는 미디 파일의 청크 중에 Head 청크의 구조를 알아보고 이를 분석하는 C# 소스 코드를 소개할게요.

미디 파일 head 청크 구조

[미디 파일 head 청크 구조]

 

 미디 파일의 head 청크는 14 바이트로 구성합니다. 

청크 타입부분의 값은 ASCII 코드에서 Mthd에 해당하는 값(16진수로 4D 54 68 64)이 옵니다.

헤드 청크의 길이는 6바이트입니다.

Head 청크의 데이터는 포멧, 트랙 개수, division으로 구성합니다.

포멧은 2바이트 차지하며 0,1,2 중에 하나입니다.

    0: 단일 트랙,  1: 다중 트랙(여러 악기를 표현하기 쉬움), 2: 다중 트랙(트랙마다 시퀀스를 포함할 수 없는 제약이 있음)

트랙 개수는 2바이트를 차지합니다.

division은 2바이트를 차지합니다. division은 1delta time이 어느 정도의 길이를 의미하는지를 나타냅니다.

   양수(맨 앞 비트가 0)일 때는 1개의 4분 음표를 나눈 값을 말합니다. 만약 값이 20일 때 1delta는 = 4분 음표 길이/20입니다.

 

   음수일 때는 하위 1바이트 값은 프레임 당 Ticks, 상위 1바이트 중 7비트(부호 비트 제외)는 -(1초당 Frames)을 의미합니다. 

   그리고 1delta time은 1Ticks와 같습니다.

 

   예를 들어, division 값이 0x0034(0000 0000 0011 0100, 십진수로 52)라는 것은 1delta time이 4분 음표 길이의 1/52이라는 것입니다. 

   1delta time = 4분 음표 길이/52

   division 값이 0xE728(1110 0111 0010 1000)는 하위 1바이트 값이 0x28(10진수 40)이므로 1프레임 당 40Ticks을 의미합니다.

   상위 1바이트 중 7비트(110 0111)를 정수로 계산한다면 음수(첫비트가 1)

   2진 보수 값이(0011001)이며 10진수로 25입니다.

   이는 1초에 25Frames을 의미합니다.

   즉, 40*25Ticks/sec = 1000Ticks/sec = 1Ticks/ms입니다. 그리고 이 때의 1 delta time은 1초/1000입니다.

 

[그림] Head Chunk 헥사 코드 및 각 항목의 의미

 

다음은 앞에서 작성한 소스 코드를 변경하여 Head Chunk를 분석하는 부분을 추가한 것입니다.

[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 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;

        }

    }

}

 

 

[Chunk.cs]

using System;

using System.IO;

using System.Net;

 

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);

                switch(StaticFuns.ConvertHostorder(ctype))

                {

                    case 0x4d546864: return new Header(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);

                }

            }

        }

        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();

        }

    }

 

}

 

 

 

미디 파일 분석(헤더 청크) 실행화면

[미디 파일 분석(헤더 청크) 실행화면]

 

참고 파일:

Chunk.cs
다운로드

 

Header.cs
다운로드
Program.cs
다운로드
StaticFuns.cs
다운로드

 

 

 

반응형