프로젝트/미디 분석 프로그램

[C#] 미디 분석 프로그램 만들기 – 4. 트랙 청크 4.6 System Event

언휴 2024. 1. 17. 08:23

1. 유튜브 동영상 강의

미디 분석 프로그램 - 트랙 청크 분석, System Event

2. System Event Message

현재 미디 파일에 헤드 청크와 트랙 청크를 분석하는 작업을 진행하고 있습니다.

현재 헤드 청크는 상세 분석하였고 트랙 청크도 메타 이벤트와 미디 이벤트는 분석하는 기능까지 구현했어요.

이번 강의에서는 시스템 이벤트를 분석하는 작업을 진행할게요.

앞에서 얘기했듯이 상태 바이트가 0xF0~FE까지가 시스템 이벤트입니다.

다음은 상태 바이트에 따른 시스템 이벤트에 관한 설명입니다.

  • 0xF0 … 0xF7 ☞ System Exclusive Messages

상태 바이트 뒤에 제조사 ID가 오며 그 뒤에 오는 메시지는 가변적이며 맨 뒤에 0xF7이 옵니다.

  • 0xF1 DD ☞ MTC Quater Frame

상태 바이트 뒤에 MTC Quater Frame 데이타(1바이트)가 따라옵니다.

  • 0xF2 pp pp ☞ Song position pointer

상태 바이트 뒤에 2개의 바이트가 오며 두 개의 바이트에 상위 1비트는 0입니다. 두 개의 바이트의 하위 7비트 두 개를 합한 14비트의 값으로 큐 포인트를 표시합니다.

  • 0xF3 ss ☞ Song request

상태 바이트 뒤에 노래 요청 데이터(1바이트, 0~127)가 따라옵니다.

  • 0xF4 ☞ Undefined

상태 바이트만 있습니다.

  • 0xF5 ☞ Undefined

상태 바이트만 있습니다.

  • 0xF6 ☞ Tune request

상태 바이트만 있습니다.

  • 0xF7 ☞ Undefined

상태 바이트만 있습니다.

  • 0xF8 ☞ Timing Clock for Synchronization

상태 바이트만 있습니다. 다른 미디 장치와 동기화를 위해 사용합니다.

  • 0xF9 ☞ Undefined

상태 바이트만 있습니다.

  • 0xFA ☞ Start current sequence

상태 바이트만 있습니다. 미디 플레이를 시작하라는 신호입니다.

  • 0xFB ☞ Continue a stopped sequence

상태 바이트만 있습니다. 미디 플레이를 재개하라는 신호입니다.

  • 0xFC ☞ Stop a sequence

상태 바이트만 있습니다. 미디 플레이를 멈추라는 신호입니다.

  • 0xFD ☞ Undefined

상태 바이트만 있습니다.

  • 0xFE ☞ Active Sensing

상태 바이트만 있습니다. 미디 장치에 계속 연결 상태임을 알려줍니다.

다음은 앞에서 작성한 프로그램에 시스템 이벤트 분석 부분을 비롯하여 미디 이벤트를 보다 상세하게 분석한 소스 코드입니다.

3. MDEvent 클래스 추가 구현

ehmidilib의 MDEvent 클래스에 시스템 이벤트를 분석하여 개체를 생성하는 코드를 추가합시다.

MDEvent 클래스의 Parsing 메서드 맨 끝에 있는 SysEvent.MakeEvent 부분입니다.

namespace ehmidi
{
    public class MDEvent
    {
        public int Delta
        {
            get;            
        }
        public byte EventType
        {
            get;            
        }
        public byte[] Buffer
        {
            get;
        }
        public MDEvent(byte evtype, int delta, byte[] buffer)
        {
            EventType = evtype;
            Delta = delta;
            Buffer = buffer;
        }
        public static MDEvent Parsing(byte[] buffer, ref int offset, MDEvent mdevent)
        {
            int oldoffset = offset;
            int delta = StaticFuns.ReadDeltaTime(buffer, ref offset);
            if(buffer[offset] == 0xFF)
            {
                offset++;
                return MetaEvent.MakeEvent(delta, buffer, ref offset, oldoffset);
            }
            if(buffer[offset]<0xF0)
            {
                return MidiEvent.MakeEvent(buffer[offset++], delta, buffer, ref offset, oldoffset, mdevent.EventType);
            }
            return SysEvent.MakeEvent(buffer[offset++], delta, buffer, ref offset, oldoffset);
        }
    }
}

4. SysEvent 클래스 정의

System Event Message는 SysEvent 클래스로 정의할게요.

    public class SysEvent:MDEvent

상태 바이트 뒤에 데이터를 FData라고 부르고 그 외에 데이터를 Data라고 부를게요.

그런데 System Event에는 상태 바이트만 있는 것이 있어서 FData가 null을 허용할 수 있어야 합니다.

byte? Fdata처럼 ?가 있으면 null을 허용합니다.

배열은 참조 형식이므로 원래 null을 허용합니다.

        byte? Fdata
        {
            get;
        }
        byte[] Data
        {
            get;
        }

생성자 메서드에서는 전달받은 fdata와 data를 속성에 설정하고 기반 형식 초기화를 진행합니다.

        public SysEvent(byte msg, int delta, byte? fdata, byte[] data, byte[] orgbuffer):base(msg,delta,orgbuffer)
        {
            Fdata = fdata;
            Data = data;
        }

정적 메서드 MakeEvent에서는 이벤트 종류에 따라 fdata와 data를 분석하여 SysEvent 개체를 생성하여 반환합니다.

        public static MDEvent MakeEvent(byte msg, int delta, byte[] buffer, ref int offset, int oldoffset)
        {
            byte? fdata = null;
            byte[] data = null;
            if(msg == 0xF0)
            {
                int nowoffset = offset;
                while(buffer[offset]!=0x7F)
                {
                    offset++;
                }
                offset++;
                int len = offset - nowoffset;
                data = new byte[len];
                Array.Copy(buffer, nowoffset, data, offset, len);
            }
            if((msg == 0xF1)||(msg == 0xF3))
            {
                fdata = buffer[offset++];
            }
            if(msg == 0xF2)
            {
                fdata = buffer[offset++];
                data = new byte[1];
                data[0] = buffer[offset++];
            }
            byte[] buffer2 = new byte[offset - oldoffset];
            Array.Copy(buffer, oldoffset, buffer2, 0, buffer2.Length);
            return new SysEvent(msg, delta, fdata, data, buffer2);
        }

이벤트 타입에 따라 상세 정보를 제공하는 Description 속성을 제공합시다.

        public string Description
        {
            get
            {
                switch(EventType)
                {
                    case 0xF0: return "Sytem Exclusive Message:" + StaticFuns.HexaString(Data);
                    case 0xF1: return string.Format("MTC Quater Frame:", Fdata);
                    case 0xF2: return string.Format("Song position pointer:{0}", (Data[0] << 7) | (Data[1]));
                    case 0xF3: return string.Format("Song request:{0}", Fdata);
                    case 0xF6: return "Tune request";
                    case 0xF8: return "MIDI clock";
                    case 0xFA: return "MIDI Start";
                    case 0xFB: return "MIDI Continue";
                    case 0xFC: return "MIDI Stop";
                    case 0xFE: return "Active Sensing";
                }
                return "Not supported" + string.Format("{0:X2}", EventType);
            }
        }

다음은 SysEvent.cs 소스 코드 내용입니다.

using System;
namespace ehmidi
{
    public class SysEvent:MDEvent
    {
        byte? Fdata
        {
            get;
        }
        byte[] Data
        {
            get;
        }
        public string Description
        {
            get
            {
                switch(EventType)
                {
                    case 0xF0: return "Sytem Exclusive Message:" + StaticFuns.HexaString(Data);
                    case 0xF1: return string.Format("MTC Quater Frame:", Fdata);
                    case 0xF2: return string.Format("Song position pointer:{0}", (Data[0] << 7) | (Data[1]));
                    case 0xF3: return string.Format("Song request:{0}", Fdata);
                    case 0xF6: return "Tune request";
                    case 0xF8: return "MIDI clock";
                    case 0xFA: return "MIDI Start";
                    case 0xFB: return "MIDI Continue";
                    case 0xFC: return "MIDI Stop";
                    case 0xFE: return "Active Sensing";
                }
                return "Not supported" + string.Format("{0:X2}", EventType);
            }
        }

        public SysEvent(byte msg, int delta, byte? fdata, byte[] data, byte[] orgbuffer):base(msg,delta,orgbuffer)
        {
            Fdata = fdata;
            Data = data;
        }

        public static MDEvent MakeEvent(byte msg, int delta, byte[] buffer, ref int offset, int oldoffset)
        {
            byte? fdata = null;
            byte[] data = null;
            if(msg == 0xF0)
            {
                int nowoffset = offset;
                while(buffer[offset]!=0x7F)
                {
                    offset++;
                }
                offset++;
                int len = offset - nowoffset;
                data = new byte[len];
                Array.Copy(buffer, nowoffset, data, offset, len);
            }
            if((msg == 0xF1)||(msg == 0xF3))
            {
                fdata = buffer[offset++];
            }
            if(msg == 0xF2)
            {
                fdata = buffer[offset++];
                data = new byte[1];
                data[0] = buffer[offset++];
            }
            byte[] buffer2 = new byte[offset - oldoffset];
            Array.Copy(buffer, oldoffset, buffer2, 0, buffer2.Length);
            return new SysEvent(msg, delta, fdata, data, buffer2);
        }
    }
}