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

[미디 분석 프로그램 만들기] 8. 트리 노드에 이벤트 정보 표시

언제나휴일 2018. 5. 28. 15:39
반응형

[미디 분석 프로그램 만들기] 8. 트리 노드에 이벤트 정보 표시



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


앞에서 미디 파일을 로딩하여 분석한 후 이를 트랙 별로 리스트 박스에 출력하는 부분까지 구현하였습니다.



이번에는 리스트 박스의 항목을 선택하면 해당 트랙의 이벤트 정보들을 트리 뷰에 나타나게 하는 부분과 트리 뷰의 항목을 선택하면 해당 항목의 이벤트 정보의 바이너리 정보를 헥사 스트링으로 출력한 데이터 그리드 뷰의 해당 쉘들을 선택하게 하는 부분을 구현합시다.


먼저 MainForm.cs 파일의 AddNode 메서드의 else 부분에 청크 정보로 트리 노드를 만드는 작업을 추가하세요. 

 

        delegate void AddNodeDele(Chunk chunk);

        private void AddNode(Chunk chunk)

        {

            if (tv_midi.InvokeRequired)

            {

                AddNodeDele dele = AddNode;

                tv_midi.Invoke(dele, new object[] { chunk });

            }

            else

            {

이 부분에 청크를 입력 인자로 트리 노드를 만드는 메서드를 호출하기로 합시다. 해당 메서드(MakeChunkNode)는 새롭게 추가할 메서드예요.

                TreeNode tn = MakeChuckNode(chunk);

그리고 해당 트리 노드에 청크의 데이터를 매핑하는 청크 노드 개체를 생성합니다. 청크 노드 형식은 새로운 클래스를 추가하여 구현합시다. 

                ChunkNode cn = new ChunkNode(chunk, tn);

그리고 해당 청크 노드를 리스트 박스에 추가하는 것으로 코드를 수정하세요.

                lbox_chuck.Items.Add(cn);

            }

        }


청크 노드 클래스는 단순히 청크 개체와 트리 노드 개체를 하나로 래핑한 형식일 뿐입니다. 이는 리스트 박에서 청크의 정보를 출력하게 하고 만약 리스트 박스의 항목을 선택하면 선택한 청크와 매핑한 트리 노드를 선택할 수 있게 하기 위해서입니다. 다음은 ChunkNode.cs 파일의 소스 코드 내용입니다.


using System.Windows.Forms;

 

namespace 미디_분석_프로그램

{

    public class ChunkNode

    {

        public TreeNode Node

        {

            get;

            private set;

        }

        public Chunk Chunk

        {

            get;

            private set;

        }

        public ChunkNode(Chunk chunk, TreeNode node)

        {

            Chunk = chunk;

            Node = node;

        }

        public override string ToString()

        {

            return Chunk.ToString();

        }

    }

}


 

계속 MainForm.cs 파일을 편집합시다.


MakeChunkNode 메서드를 작성합시다.

        private TreeNode MakeChuckNode(Chunk chunk)

        {

청크의 내용을 입력 인자로 트리 노드 개체를 생성합니다. 그리고 트리 노드의 Tag 속성에 매핑할 청크 개체를 설정합니다. 

            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;

        }

 

트랙 노드를 매다는 작업이 MainForm을 생성한 스레드가 아닌 스레드에서 비동기적으로 호출하여 크로스 스레드가 발생하였을 때의 처리를 위해 HangDele 대리자를 정의할게요.

        delegate void HangDele(TreeNode tn, Track track);

        private void HangTrackNode(TreeNode tn, Track track)

        {

크로스 스레드가 발생하였을 때 적절한 처리를 해 줍니다. (UI 컨트롤의 InvokeRequired가 참이라는 것은 현재 수행하는 스레드가 UI 컨트롤을 소유하는 스레드와 달라 크로스 스레드 문제가 발생하였다는 것을 의미합니다.)

            if (tv_midi.InvokeRequired)

            {

                HangDele hdele = HangTrackNode;

                tv_midi.Invoke(hdele, new object[] { tn, track });

            }

            else

            {

트랙에 있는 이벤트 정보를 순차적으로 접근하여 메타 이벤트일 때는 메타 이벤트 정보를 매달고 미디 이벤트일 때는 미디 이벤트 정보를 매달게 구현합니다. 현재 시스템 이벤트에 관한 처리를 하지 않았으며 만약 이에 관한 처리를 한다면 비슷한 형태의 코드를 추가 작성해야 합니다.

                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 HangMetaEventNode(TreeNode tn, MetaEvent mdevent)

        {

메타 이벤트라는 것과 해당 이벤트의 바이너리 정보를 헥사 문자열로 만들어 트리 노드에 표시합니다. 이를 위해 MidiHelper 클래스에 정적 메서드 HexaString을 추가합니다.

            TreeNode cn = new TreeNode("<메타이벤트>" + MidiHelper.HexaString(mdevent.Buffer));

트리 노드와 매핑하는 메타 이벤트 개체 정보를 Tag 속성에 설정합니다.

            cn.Tag = mdevent;

메타 이벤트와 설명, 종류, 길이 등을 자식 노드로 매달게 합니다.

            cn.Nodes.Add(string.Format("Status Byte:{0:X2}", mdevent.EventType));

            cn.Nodes.Add(mdevent.MetaDescription);

            cn.Nodes.Add(string.Format("종류:{0:X2}", mdevent.Msg));

            cn.Nodes.Add(string.Format("길이:{0:X2}", mdevent.Length));

            cn.Nodes.Add(MidiHelper.HexaString(mdevent.Data));

메타 정보를 매단 트리 노드를 입력 인자로 받은 트리 노드의 자식으로 매답니다. 

            tn.Nodes.Add(cn);

        }

 

같은 방법으로 미디 이벤트를 자식 노드로 매다는 메서드도 구현하세요.

        private void HangMidiEventNode(TreeNode tn, MidiEvent mevent)

        {

            TreeNode cn = new TreeNode("<미디이벤트>" + MidiHelper.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);

        }

다음은 MidiHelper 클래스의 정적 메서드 HexaString입니다.


헤더 노드의 정보를 메다는 메서드도 비슷한 방법으로 구현하세요.

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

            str = string.Format("Division 개수:{0}, [{1:X2} {2:X2}]", header.Division, header.Data[4], header.Data[5]);

            TreeNode dn = new TreeNode(str);

            tn.Nodes.Add(dn);

 

            if (header.IsTicks)

            {

                str = string.Format("1delta time 1/{0} sec", header.Division);

            }

            else

            {

                str = string.Format("1delta time 4분 음표길이/{0}", header.Division);

            }

            dn.Nodes.Add(new TreeNode(str));

        }

 

이제 리스트 박스의 선택 항목 변경 이벤트 핸들러를 추가하세요.

        private void lbox_chuck_SelectedIndexChanged(object sender, EventArgs e)

        {

트리 뷰의 항목을 지워줍니다.

            tv_midi.Nodes.Clear();

선택 항목이 없을 때 아무 작업도 하지 않습니다.

            if (lbox_chuck.SelectedIndex == -1)

            {

                return;

            }


선택 항목을 청크 노드 개체로 참조합니다.

            ChunkNode cn = lbox_chuck.SelectedItem as ChunkNode;

 

선택한 청크 노드 개체 정보르 트리 노드와 헥사 뷰 정보를 설정합니다.

            SetTreeNode(cn.Node);

            SetHexaView(cn.Chunk.Buffer);

        }

 

다음은 헥사 정보를 데이터 그리드 뷰에 표시하는 메서드입니다.

        private void SetHexaView(byte[] buffer)

        {

            dgv_hexa.Rows.Clear();

            int len = buffer.Length - 16;

            int i, n;

            for (i = 0, n = 1; i < len; i += 16, n++)

            {

                MakeDataGridRow(buffer, i, 16, n);

 

            }

            MakeDataGridRow(buffer, i, buffer.Length - i, n);

        }

 

버퍼의 특정 offset에서 n번째 행 정보를 DataGridViewRow 개체로 생성하여 추가하는 메서드를 구현하세요.

        private void MakeDataGridRow(byte[] buffer, int offset, int length, int n)

        {

먼저 새로운 DataGridViewRow를 추가합니다.

            int index = dgv_hexa.Rows.Add();

추가한 개체 정보를 참조합니다.

            DataGridViewRow dr = dgv_hexa.Rows[index];

 

0번 인덱스 항목에는 행 번호를 설정합니다.

            dr.Cells[0].Value = n.ToString();

나머지 컬럼 정보를 설정합니다.

            for (int i = 0; i < length; i++)

            {

                dr.Cells[i + 1].Value = string.Format("{0:X2}", buffer[offset + i]);

            }

        }

 

트리 노드를 설정하는 메서드를 구현하세요. 단순히 트리 뷰의 자식 노드로 추가한 후에 펼쳐줍니다.

        private void SetTreeNode(TreeNode node)

        {

            tv_midi.Nodes.Add(node);

            node.Expand();

        }

 

이제 트리 뷰의 항목을 선택 변경하였을 때의 코드를 작성합시다.

        private void tv_midi_AfterSelect(object sender, TreeViewEventArgs e)

        {

            TreeNode tn = e.Node;

만약 현재 트리 노드와 대응하는 정보가 청크일 때는 헥사 정보를 보여주는 DataGridView의 전체 쉘을 선택합니다.

            if (tn.Tag is Chunk)

            {

                dgv_hexa.SelectAll();

            }

만약 미디 이벤트일 때의 작업을 구현합시다.

            if (tn.Tag is MDEvent)

            {

현재 DataGridView의 선택 영역을 지워줍니다.

                dgv_hexa.ClearSelection();

                MDEvent md = tn.Tag as MDEvent;

                Track track = tv_midi.Nodes[0].Tag as Track;

해당 트랙의 Pair 정보를 얻어옵니다.

                Pair pair = track.FindPair(md);

                if (pair != null)

                {

Pair 정보를 통해 DataGridView의 어느 영역을 선택해야 하는지 계산합니다. 물론 Pair 클래스를 먼저 구현해야겠죠.

                    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 + i == 16)

                        {

                            break;

                        }

                        dgv_hexa.Rows[row].Cells[col + i + 1].Selected = true;

                    }

                    for (col = 0; i < length; i++, col++)

                    {

                        dgv_hexa.Rows[row + 1].Cells[col].Selected = true;

                    }

                    dgv_hexa.FirstDisplayedScrollingRowIndex = row;

                }

            }

        }

    }

}


다음은 Pair클래스의 소스 코드입니다.

 


이상으로 미디 분석 프로그램 만들기를 마치기로 할게요.

참고 파일)

미디 분석 프로그램.zip


반응형