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

[미디 분석 프로그램 만들기] 6. 트랙 청크 분석하기 - 메타 이벤트 분석

언제나휴일 2018. 5. 3. 11:41
반응형

[미디 분석 프로그램 만들기] 6. 트랙 청크 분석하기 - 메타 이벤트 분석



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


이전 글에서 트랙 청크의 delta 타임을 구하는 부분까지 구현했었죠.

[미디 분석 프로그램 만들기] 1. 구현할 프로그램 소개

[미디 분석 프로그램 만들기] 2. 프로젝트 생성 및 Layout

[미디 분석 프로그램 만들기] 3. 미디 파일 열기 및 청크로 분할하기

[미디 분석 프로그램 만들기] 4. 헤더 청크 분석하기

[미디 분석 프로그램 만들기] 5. 트랙 청크 분석하기 - delta time 구하기


이번에는 트랙 청크의 이벤트 중에 메타 이벤트를 분석하는 부분을 구현합시다. 메타 이벤트에 관한 자세한 정보는 다음 게시글을 참고하세요.

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

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 5 - Track 청크 3, 박자, 키 정보 등


트랙 청크의 이벤트는 delta 타임 뒤에 오는 1바이트 값에 따라 어떠한 이벤트인지 결정할 수 있어요. 만약 해당 값이 0xFF라면 메타 이벤트이며 0xF0에서 0xFE 사이라면 시스템 이벤트, 그 외의 값일 때 미디 이벤트입니다. 참고로 delta 타임 뒤에 오는 1 바이트를 Status Byte(상태 바이트)라고 부릅니다.


여기에서는 Meta 이벤트를 다룰 거예요.


Meta 이벤트는 상태 바이트 뒤에 어떠한 데이터인지 구분하는 정보가 옵니다. 그리고 그리고 각 정보에 따라 다른 의미의 데이터가 따라옵니다. 이 부분은 가변 길이여서 맨 처음에 길이가 오고 해당 길이만큼 데이터가 옵니다. 다음은 Meta 데이터가 어떠한 의미를 지니는지 설명한 것입니다.

Status byte

Meta Data byte

2ndbyte

Other bytes

Description

FF

0

02 ss ss

Set track’s sequence #

FF

1

nn tt…

Any Text user wants

FF

2

nn tt…

Text for copyright info.

FF

3

nn tt…

Track name

FF

4

nn tt…

Track instrument name

FF

5

nn tt

Lyric

FF

6

nn tt

Marker

FF

7

nn tt

Cue point

FF

20

01 channel

Channel prefix

FF

21

01 pp

MIDI port

FF

2F

0

End of Track

FF

51

03 tt tt tt

Set tempo

(microseconds/quarter note)

FF

54

05 hh mm ss ff sf

SMPTE Offset

hour/minute/second

/frame/subframe

FF

58

04 nn dd cc bb

Time signature

numerator/

denominator/

metronome ticks #/

32nd notes# per quarter note

FF

59

02 sf mi

Key signature

key(sharp/flat#)

(C when Key=0)

scale(0:major, 1:minor)

 

FF

7F

nn tt

Sequencer specific info.



먼저 MDEvent 클래스에 이벤트 하나를 분석하는 정적 메서드인 Parsing에 메타 이벤트를 분석하는 코드를 추가합니다.


 

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

        {

            int oldoffset = offset;

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

이 부분에 Status Byte의 값이 0xFF일 때 메타 이벤트를 분석하여 반환하는 코드를 추가하세요.

            if (buffer[offset] == 0xFF)

            {

                offset++;

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

            }

이 부분에 미디 이벤트와 시스템 이벤트를 분석하는 코드를 작성할 부분이며 뒤에서 다룰 거예요.

            return null;

            //if (buffer[offset] < 0xF0)

            //{

            //    return MidiEvent.MakeEvent(buffer[offset++], delta, buffer, ref offset, oldoffset, bef_event.EventType);

            //}

 

            //return SysEvent.MakeEvent(buffer[offset++], delta, buffer, ref offset, oldoffset);

        }



이제 MetaEvent 클래스를 구현합시다.

using System;

 

namespace 미디_분석_프로그램

{

    public class MetaEvent : MDEvent

    {

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

        {

메시지 종류가 맨 앞에 위치합니다.

            byte msg = buffer[offset++];

그리고 메시지 길이가 따라옵니다.

            byte len = buffer[offset++];

            byte[] data = null;

만약 메시지 값이 0x2F일 때는 트랙 종료를 의미합니다. 그렇지 않을 때는 메시지 길이만큼 데이터가 있으니 이를 보관할 버퍼를 생성하여 복사하세요.

            if (msg != 0x2F)

            {

                data = new byte[len];

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

                offset += len;

            }

전체 데이터의 크기는 현재 offset에서 이전 offset을 뺀 크기입니다. 이 부분을 복사할게요.

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

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

delta 값과 메타 이벤트 종류, 데이터 길이, 데이터, 이벤트 전체 내용이 있는 버퍼를 입력 인자로 메타 이벤트 개체를 생성하여 반환합니다.

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

        }

 

메타 이벤트에는 어떠한 종류의 메타 이벤트인지 판별하는 속성을 캡슐화하세요.

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

        {

            get;

            private set;

        }

메타 이벤트에서 데이터 길이가 다르며 이를 판별하는 속성을 캡슐화하세요.

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

        {

            get;

            private set;

        }

메타 데이터를 위한 속성을 캡슐화하세요.

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

        {

            get;

            private set;

        }

사용자 편의를 위해 메타 데이터를 문자열로 변환하여 반환하는 가져오기 속성을 제공합시다. 변환하는 메서드는 MidiHelper 클래스에 구현하기로 할게요.(GetString 메서드 중복 정의)

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

        {

            get

            {

                if (Data == null)

                {

                    return string.Empty;

                }

                return MidiHelper.GetString(Data);

            }

        }

메시지 종류에 따라 상세 정보를 문자열로 제공하는 가져오기 속성도 정의합시다.

        public string MetaDescription

        {

            get

            {

                switch (Msg)

                {

                    case 0x00: return string.Format("SeqNo:{0}" + BitConverter.ToInt16(Data, 0));

                    case 0x01: return DataString;

                    case 0x02: return "Copyright:" + DataString;

                    case 0x03: return "Track Name:" + DataString;

                    case 0x04: return "Instument:" + DataString;

                    case 0x05: return "Lyric:" + DataString;

                    case 0x06: return "Marker:" + DataString;

                    case 0x07: return "CuePoint:" + DataString;

                    case 0x08: return "ProgramName" + DataString;

                    case 0x09: return "DeviceName" + DataString;

                    case 0x20: return "Channel:" + Data[0].ToString();

                    case 0x21: return "Midi Port:" + Data[0].ToString();

                    case 0x2F: return "End of Track";

                    case 0x51: return "Tempo:" + MakeTempo();

                    case 0x54: return "SmpteOffSet";

                    case 0x58: return "TimeSignature:" + MakeTimeSig(); ;

                    case 0x59: return "KeySignature" + MakeKeySignature();

                    case 0x7F: return "SeqEvent";

                    default: return "ETC";

                }

            }

        }


템포 문자열을 만드는 메서드입니다.

        private string MakeTempo()

        {

            int tempo = Data[0] << 16 | Data[1] << 8 | Data[0];

            return tempo.ToString() + "microseconds/quarter note";

        }


장조의 키 진행입니다.

        static string[] keystr = new string[]

        {

            "C Flat","G Flat","D Flat","A Flat","E Flat","B Flat","F Flat",

            "C",

            "G","D","A","E","B","F Sharp","C Sharp"

        };

단조의 키 진행입니다.

        static string[] keystr2 = new string[]

        {

            "A Flat","E Flat","B Flat","F","C","G","D",

            "A",

            "E","B","F Sharp","C Sharp","G Sharp", "D Sharp", "A Sharp"

        };

키 문자열을 만드는 메서드입니다.

        private string MakeKeySignature()

        {

            byte ki = (byte)(Data[0] + 7);

            if (Data[1] == 0)

            {

                return keystr[ki] + " Major";

            }

            return keystr2[ki] + " minor";

        }

박자를 만드는 메서드입니다.

        private string MakeTimeSig()

        {

            return string.Format("{0}/{1}", Data[0], Math.Pow(2, Data[1]));

        }

메타 이벤트의 생성자입니다.

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

        {

            Msg = msg;

            Length = len;

            Data = data;

        }

 

    }

}



다음은 MidiHelper 클래스에 중복정의한 GetString 메서드입니다. 

 

        public static string GetString(byte[] data)

        {

            Encoding en = Encoding.Default;

            return en.GetString(data);

        }



반응형