[카테고리:] <span>미디 분석 프로그램</span>

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

1. 해야 할 일

드디어 미디 분석 프로그램 마지막 강의입니다.

이번 강의에서는 TreeView에 청크의 내용을 상세하게 보여주는 부분과 노드를 선택하였을 때 대응하는 바이너리를 DataGridView에서 확인하기 쉽게 선택해서 보여주는 부분을 구현합니다.

2. MakeChunkNode 메서드 수정

미디 분석기 프로그램 MainForm.cs에서 MakeChunkNode 메서드를 수정합시다.

chunk가 헤더인지 트랙인지에 따라 상세 노드를 만들어 매다는 메서드를 만들어 호출합시다.

        private TreeNode MakeChunkNode(Chunk chunk)
        {
            TreeNode tn = new TreeNode(chunk.ToString());
            tn.Tag = chunk;
            if(chunk is Header)
            {
                HangHeaderNode(tn, chunk as Header);
            }
            if(chunk is Track)
            {
                HangTrackNode(tn, chunk as Track);
            }
            return tn;
        }

다음은 트랙 노드의 상세 정보를 매다는 메서드입니다.

트랙의 이벤트 목록을 순차 방문하여 메타 이벤트인지 미디 이벤트인지 판별하여 매달면 되겠죠.

시스템 이벤트는 생략할게요.

        private void HangTrackNode(TreeNode tn, Track track)
        {
            foreach(MDEvent mdevent in track)
            {
                if(mdevent is MetaEvent)
                {
                    HangMetaEventNode(tn, mdevent as MetaEvent);
                }
                if(mdevent is MidiEvent)
                {
                    HangMidiEventNode(tn, mdevent as MidiEvent);
                }
            }
        }

다음은 미디 이벤트 노드를 메다는 메서드입니다.

미디 이벤트의 상세 정보를 노드로 만들어 입력 인자로 전달받은 노드의 자식 노드 컬렉션에 보관합니다.

        private void HangMidiEventNode(TreeNode tn, MidiEvent mevent)
        {
            TreeNode cn = new TreeNode("<미디이벤트>" + StaticFuns.HexaString(mevent.Buffer));
            cn.Tag = mevent;
            cn.Nodes.Add(string.Format("델타 타임:{0}", mevent.Delta));
            cn.Nodes.Add(string.Format("Status Byte:{0:X2}", mevent.EventType));
            cn.Nodes.Add(mevent.Description);
            tn.Nodes.Add(cn);
        }

다음은 메타 이벤트 노드를 메다는 메서드입니다.

메타 이벤트의 상세 정보를 노드로 만들어 입력 인자로 전달받은 노드의 자식 노드 컬렉션에 보관합니다.

        private void HangMetaEventNode(TreeNode tn, MetaEvent mevent)
        {
            TreeNode cn = new TreeNode("<메타이벤트>" + StaticFuns.HexaString(mevent.Buffer));
            cn.Tag = mevent;
            cn.Nodes.Add(string.Format("Status Byte:{0:X2}", mevent.EventType));
            cn.Nodes.Add(mevent.MetaDescription);
            cn.Nodes.Add(string.Format("종류:{0:X2}", mevent.Msg));
            cn.Nodes.Add(string.Format("길이:{0:X2}", mevent.Length));
            cn.Nodes.Add(StaticFuns.HexaString(mevent.Data));
            tn.Nodes.Add(cn);
        }

다음은 헤더 노드를 매다는 메서드 입니다.

포켓과 트랙 갯수 및 Division 정보를 자식 노드로 만들어 매답니다.

        private void HangHeaderNode(TreeNode tn, Header header)
        {
            string str = string.Format("포멧:{0}, [{1:X2}{2:X2}]", header.Format, header.Data[0], header.Data[1]);
            tn.Nodes.Add(new TreeNode(str));
            str = string.Format("트랙 갯수:{0}, [{1:X2}{2:X2}]",header.TrackCount,header.Data[2],header.Data[3]);
            tn.Nodes.Add(new TreeNode(str));
            if(header.IsTicks)
            {
                str = string.Format("1delta time : 1/{0} sec ,[{1:X2}{2:X2}]",
                    header.Division, header.Data[4], header.Data[5]);
                tn.Nodes.Add(new TreeNode(str));
            }
            else
            {
                str = string.Format("1delta time : 4분음표 길이/{0}  ,[{1:X2}{2:X2}]",
                    header.Division, header.Data[4], header.Data[5]);
                tn.Nodes.Add(new TreeNode(str));
            }
        }

3. TreeView 선택 후 이벤트 핸들러 구현

속성 창에서 tv_midi의 AfterSelect 이벤트 핸들러를 등록하세요.

        private void tv_midi_AfterSelect(object sender, TreeViewEventArgs e)
        {

선택 노드가 Chunk일 때는 dgv_hexa에 전체 영역을 선택합니다.

            TreeNode tn = e.Node;
            if(tn.Tag is Chunk)
            {
                dgv_hexa.SelectAll();
                return;
            }

선택 노드가 이벤트 일 때는 해당 이벤트가 트랙의 어느 부분인지 조사하여 해당 부분만 선택합니다.

            if(tn.Tag is MDEvent)
            {
                dgv_hexa.ClearSelection();
                MDEvent md = tn.Tag as MDEvent;
                Track track = tv_midi.Nodes[0].Tag as Track;
                Pair pair = track.FindPair(md);
                if(pair !=null)
                {
                    int offset = pair.Offset;
                    int length = pair.Length;
                    int row = offset / 16;
                    int col = offset % 16;
                    int i = 0;
                    for(i=0;i<length;i++)
                    {
                        if(col==16)
                        {
                            col = 0;
                            row++;
                        }
                        dgv_hexa.Rows[row].Cells[col + 1].Selected = true;
                        col++;
                    }
                    dgv_hexa.FirstDisplayedScrollingRowIndex = row;
                }
            }

4. Pair 클래스 정의

ehmidilib에 Pair 클래스를 추가합니다.

Pair 클래스는 이벤트가 청크의 어느 부분에서 어느 길이만큼인지 정보를 갖는 개체입니다.

namespace ehmidi
{
    public class Pair
    {
        public int Offset
        {
            get;
        }
        public int Length
        {
            get;
        }
        public Pair(int offset, int endoffset)
        {
            Offset = offset + 8;
            Length = endoffset - offset;
        }
    }
}

5. Track 클래스 수정

ehmidilib에 Track 클래스를 수정합시다.

Track에 이벤트를 보관하는 컬렉션은 List였는데 이제는 이벤트를 Key, Pair를 값으로 하는 Dictionary로 바꿉니다.

이에 따라 GetEnumerator 메서드도 수정합니다.

그리고 FindPair 메서드에서 이벤트에 대응하는 Pair를 반환하게 구현합니다.

다음은 수정한 Track.cs 소스 코드 내용입니다.

using System.Collections;
using System.Collections.Generic;

namespace ehmidi
{
    public class Track : Chunk,IEnumerable
    {
        Dictionary<MDEvent, Pair> mdic = new Dictionary<MDEvent, Pair>();
        public IEnumerator GetEnumerator()
        {
            return mdic.Keys.GetEnumerator();
        }
        public Track(int ctype, int length, byte[] buffer) : base(ctype, length, buffer)
        {
            Parsing(buffer);
        }

        private void Parsing(byte[] buffer)
        {
            int offset = 0;
            int old;
            MDEvent mdevent = null;
            while(offset < buffer.Length)
            {
                old = offset;
                mdevent = MDEvent.Parsing(buffer, ref offset, mdevent);
                Pair pair = new Pair(old, offset);
                if(mdevent == null)
                {
                    break;
                }
                mdic[mdevent] = pair;
            }
        }

        public Pair FindPair(MDEvent md)
        {
            if(mdic.ContainsKey(md))
            {
                return mdic[md];
            }
            return null;
        }
    }
}

6. StaticFuns 코드 수정

현재 작성한 것으로 동작하면 StaticFuns의 HexaString에서 예외가 발생합니다.

입력 인자가 null일 때 처리를 하지 않았습니다. 이를 반영하세요.

        public static string HexaString(byte[] buffer)
        {
            string str = "";
            if(buffer == null)
            {
                return str;
            }
            foreach(byte d in buffer)
            {
                str += string.Format("{0:X2} ", d);
            }
            return str;
        }

드디어 미디 분석 프로그램을 완성하였습니다.

다음에 미디를 악보로 보여주고 미디를 연주하는 등의 프로그램도 만들 수 있었으면 좋겠네요.

모두 수고하셨습니다.

미디 분석 프로그램