[미디 분석 프로그램 만들기] 3. 미디 파일 열기 및 청크로 분할하기
안녕하세요. 언제나 휴일, 언휴예요.
이전 글에서 미디 분석 프로그램 프로젝트를 생성하고 자식 컨트롤을 배치했어요.
[미디 분석 프로그램 만들기] 2. 프로젝트 생성 및 Layout
[미디 분석 프로그램 만들기] 1. 구현할 프로그램 소개
이번에는 미디 파일을 열고 청크로 분할하는 작업을 하기로 할게요.
이미 앞에서 소개한 것처럼 미디 파일은 청크들로 이루어져 있습니다. [미디 파일] 미디 파일 구조 분석 및 프로그래밍 1 - 청크 목록
[그림] 청크 구조
먼저 파일 메뉴 아이템에 클릭 이벤트 핸들러를 등록하세요.
이제 파일 메뉴 클릭 이벤트 핸들러를 작성합시다.
private void fileMenuItem_Click(object sender, EventArgs e)
{
먼저 파일 열기 대화상자(OpenFileDialog) 개체를 생성한 후에 디폴트 확장자를 미디 파일로 명시하고 Filter 속성을 지정하세요.
OpenFileDialog ofd = new OpenFileDialog();
ofd.DefaultExt = "미디 파일";
ofd.Filter = "미디 파일|*.mid";
대화 상자를 띄운 후에 결과가 OK라면 파일을 선택한 것입니다.
if (ofd.ShowDialog() == DialogResult.OK)
{
Text = ofd.FileName;
선택한 파일을 입력 인자로 미디 파서(MidiParser) 개체를 생성하세요. 새 클래스 MidiParser를 추가하고 파일 명을 입력 인자로 받는 생성자를 추가하세요.
MidiParser mp = new MidiParser(ofd.FileName);
미디 파서을 분석하는 과정에 새로운 청크를 발견하면 이를 처리할 이벤트 핸들러를 등록합니다. 청크를 발견하였을 때 처리하는 이벤트 핸들러를 위해 대리자(FindChunkEventHandler)와 이벤트 인자 형식(FindChunkEventArgs)을 추가합니다.
mp.FindedChuck += Mp_FindedChuck;
미디 파서 개체에게 비동기 방식으로 분석할 것을 명령합니다. 이를 위해 MidiParser 클래스에 AsyncParser 메서드를 추가하세요.
mp.AsyncParse();
}
}
미디 파서에서 미디 파일을 분석하는 중에 청크를 발견하였을 때 이를 처리할 이벤트 핸들러는 다음과 같은 원형을 갖습니다. 이에 관한 코드는 다른 부분을 작성한 후에 구현하기로 할게요.
private void Mp_FindedChuck(object sender, FindChunkEventArgs e)
{
}
먼저 청크를 발견하였을 때의 처리하는 이벤트 핸들러를 위한 대리자와 이벤트 인자 형식을 추가하여 구현합니다.
using System;
namespace 미디_분석_프로그램
{
public delegate void FindChunkEventHandler(object sender, FindChunkEventArgs e);
이벤트 인자는 기반 형식인 EventArgs에서 파생 형식으로 정의하세요.
public class FindChunkEventArgs : EventArgs
{
이벤트 핸들러에서는 발견한 청크를 접근할 수 있어야 할 것입니다. 이를 위해 속성을 캡슐화합니다. 가져오기 속성은 노출하고 설정하기 속성은 내부에서 접근할 수 있게 정하세요. 물론 Chunk 이름의 새 클래스를 생성하세요.
public Chunk Chunk
{
get;
private set;
}
생성자에서는 발견한 청크를 입력 인자로 받아 속성을 설정합니다.
public FindChunkEventArgs(Chunk chuck)
{
Chunk = chuck;
}
}
}
현재 Chunk 클래스는 아무런 멤버도 캡슐화하지 않은 상태입니다.
namespace 미디_분석_프로그램
{
public class Chunk
{
}
}
이제 MidiParser 클래스를 구현합시다.
using System.IO;
using System.Threading;
namespace 미디_분석_프로그램
{
public class MidiParser
{
먼저 미디 파서 개체를 사용하는 곳에서 청크를 발견하였을 때의 이벤트 처리를 할 수 있게 이벤트 멤버를 캡슐화하세요.
public event FindChunkEventHandler FindedChuck;
생성할 때 전달받은 파일 이름을 설정하고 필요할 때 가져오기 할 수 있는 파일 이름 속성을 캡슐화하세요.
public string FileName
{
get;
private set;
}
생성자는 파일명을 입력인자로 받은 후에 속성을 설정합니다.
public MidiParser(string fname)
{
FileName = fname;
}
비동기적으로 분석하는 AsyncParse 메서드에서는 분석하는 Parse 메서드를 진입점으로 하는 스레드를 생성한 후에 스레드를 가동합니다.
public void AsyncParse()
{
Thread thread = new Thread(Parse);
thread.Start();
}
public void Parse()
{
먼저 파일 스트림을 생성합니다.
FileStream fs = new FileStream(FileName, FileMode.Open)
파일 스트림의 Postion 값이 파일 스트림 길이보다 작다면 반복합니다.
while (fs.Position < fs.Length)
{
반복해서 할 일은 파일 스트림에서 청크 하나를 구하는 것입니다. 이를 위해 Chunk 클래스에 Parse 정적 메서드를 추가하세요.
Chunk chunk = Chunk.Parse(fs);
그리고 이벤트 멤버가 null 이 아니면 발견한 청크를 이벤트 핸들러로 전달합니다.
if (FindedChuck != null)
{
FindedChuck(this, new FindChunkEventArgs(chunk));
}
}
}
}
}
using System;
using System.IO;
namespace 미디_분석_프로그램
{
public class Chunk
{
먼저 헤더 청크를 의미하는 magic과 트랙 청크를 의미하는 magic 상수를 캡슐화합니다.
const int magic_head = 0x4d546864;
const int magic_track = 0x4d54726b;
청크 유형을 위한 속성을 캡슐화하세요.
public int CT//청크 유형
{
get;
private set;
}
청크 길이를 위한 속성을 캡슐화하세요.
public int Length//청크 길이
{
get;
private set;
}
청크의 데이터를 위한 속성을 캡슐화하세요.
public byte[] Data//데이터
{
get;
private set;
}
사용자 편의성을 위해 청크 유형을 문자열로 접근할 수 있게 가져오기 속성을 제공합니다. 여기에서는 정수 형식에 있는 magic을 문자열로 변환하는 작업이 필요한데 앞으로 이러한 기능들은 MidiHelper라는 정적 클래스에 정적 메서드로 구현하기로 할게요.
public string CTString//청크 유형(문자열)
{
get
{
return MidiHelper.GetString(CT);
}
청크 유형과 길이, 데이터로 구성한 전체 청크의 데이터를 반환하는 속성도 제공합시다.
public byte[] Buffer
{
get
{
BitConverter 클래스의 정적 메서드 GetBytes를 통해 기본 형식을 byte 배열로 변환할 수 있습니다.
byte[] ct_buf = BitConverter.GetBytes(CT);
길이 부분은 호스트 정렬 방식으로 변환합니다.
int belen = MidiHelper.ConvertHostorder(Length);
마찬가지로 BitConverter 클래스의 정적 메서드 GetBytes를 통해 기본 형식을 byte 배열로 변환합니다.
byte[] len_buf = BitConverter.GetBytes(belen);
버퍼를 생성합니다.
byte[] buffer = new byte[ct_buf.Length + len_buf.Length + Data.Length];
생성한 버퍼에 Array클래스의 Copy 정적 메서드를 이용하여 값을 채우는 작업을 수행합니다.
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 개체를 생성하여 읽기 작업을 수행할게요.
BinaryReader br = new BinaryReader(stream);
먼저 4바이트 값을 얻습니다. 청크 유형(magic)이 있는 부분입니다.
int ctype = br.ReadInt32();
그리고 4바이트 값을 얻습니다. 여기는 청크 데이터 길이에 해당하는 부분인데 호스트 정렬 방식으로 변환하세요.
int length = br.ReadInt32();
length = MidiHelper.ConvertHostorder(length);
그리고 데이터 길이 만큼 읽어옵니다.
byte[] buffer = br.ReadBytes(length);
나중에 유형에 따라 헤더 청크 개체를 생성할 것인지 트랙 청크를 생성할 것인지 결정해야 하지만 지금은 그대로 청크 개체를 생성하여 반환합시다.
return new Chunk(ctype, length, buffer);
}
catch
{
return null;
}
}
public Chunk(int ctype, int length, byte[] buffer)
{
CT = ctype;
Length = length;
Data = buffer;
}
ToString 메서드를 재정의하여 청크 유형과 길이를 표현한 문자열을 반환하게 합시다.
public override string ToString()
{
return string.Format("{0}:{1} bytes", CTString, Length);
}
}
}
이번에는 MidiHelper 클래스를 구현합시다.
using System;
using System.Net;
using System.Text;
namespace 미디_분석_프로그램
{
public static class MidiHelper
{
호스트 정렬로 변환하는 메서드는 이미 IPAddress 클래스에서 제공하는 NetworkToHostOrder 메서드를 호출하는 형태로 래핑합니다.
public static int ConvertHostorder(int data)
{
return IPAddress.NetworkToHostOrder(data);
}
정수 형식에 있는 매직값을 문자열로 변환하는 부분은 BitConverter 클래스의 GetBytes 메서드로 byte 배열로 변환한 후에 Encoding 개체의 GetString 메서드를 호출합니다.
public static string GetString(int magic)
{
byte[] data = BitConverter.GetBytes(magic);
Encoding en = Encoding.Default;
return en.GetString(data);
}
}
}
이제 MainForm의 청크를 발견하였을 때의 이벤트 핸들러를 구현합시다.
private void Mp_FindedChuck(object sender, FindChunkEventArgs e)
{
AddNode(e.Chunk);
}
청크를 발견하는 것은 비동기 처리에서 수행하고 있습니다. 따라서 Main Form을 생성한 스레드와 다른 스레드에서 발견하여 크로스 스레드 문제가 발생하므로 다음처럼 크로스 스레드 문제를 해결하는 구조로 작성합니다.
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
{
lbox_chuck.Items.Add(chunk);
}
}
[그림] 현재까지 작성한 미디 분석 프로그램 실행 화면
▶ MainForm.cs
using System; using System.Windows.Forms;
namespace 미디_분석_프로그램 { public partial class MainForm : Form { public MainForm() { InitializeComponent(); }
private void fileMenuItem_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.DefaultExt = "미디 파일"; ofd.Filter = "미디 파일|*.mid";
if (ofd.ShowDialog() == DialogResult.OK) { Text = ofd.FileName; MidiParser mp = new MidiParser(ofd.FileName); mp.FindedChuck += Mp_FindedChuck; mp.AsyncParse(); } } private void Mp_FindedChuck(object sender, FindChunkEventArgs e) { AddNode(e.Chunk); }
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 { lbox_chuck.Items.Add(chunk); } } } }
|
▶ FindChunkEventArgs.cs
using System;
namespace 미디_분석_프로그램 { public delegate void FindChunkEventHandler(object sender, FindChunkEventArgs e); public class FindChunkEventArgs : EventArgs { public Chunk Chunk { get; private set; } public FindChunkEventArgs(Chunk chuck) { Chunk = chuck; } } }
|
▶ Chunk.cs
using System; using System.IO;
namespace 미디_분석_프로그램 { public class Chunk { const int magic_head = 0x4d546864; const int magic_track = 0x4d54726b; public int CT//청크 유형 { get; private set; } public int Length//청크 길이 { get; private set; } public byte[] Data//데이터 { get; private set; } public string CTString//청크 유형(문자열) { get { return MidiHelper.GetString(CT); } } public byte[] Buffer { get { byte[] ct_buf = BitConverter.GetBytes(CT); int belen = MidiHelper.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 = MidiHelper.ConvertHostorder(length); byte[] buffer = br.ReadBytes(length); int cval = MidiHelper.ConvertHostorder(ctype); //switch (cval) //{ // case magic_head: return new Header(ctype, length, buffer); // case magic_track: return new Track(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; } public override string ToString() { return string.Format("{0}:{1} bytes", CTString, Length); } } } |
▶ MidiHelper.cs
using System; using System.Net; using System.Text;
namespace 미디_분석_프로그램 { public class MidiHelper { public static int ConvertHostorder(int data) { return IPAddress.NetworkToHostOrder(data); }
public static string GetString(int magic) { byte[] data = BitConverter.GetBytes(magic); Encoding en = Encoding.Default; return en.GetString(data); } } } |
▶ MidiParser.cs
using System.IO; using System.Threading;
namespace 미디_분석_프로그램 { public class MidiParser { public event FindChunkEventHandler FindedChuck; public string FileName { get; private set; } public MidiParser(string fname) { FileName = fname; } public void Parse() { FileStream fs = new FileStream(FileName, FileMode.Open); while (fs.Position < fs.Length) { Chunk chunk = Chunk.Parse(fs); if (FindedChuck != null) { FindedChuck(this, new FindChunkEventArgs(chunk)); } } } public void AsyncParse() { Thread thread = new Thread(Parse); thread.Start(); } } } |
'프로그래밍 기술 > 미디 파일 구조 및 미디 분석 프로그램 만들기' 카테고리의 다른 글
[미디 분석 프로그램 만들기] 8. 트리 노드에 이벤트 정보 표시 (2) | 2018.05.28 |
---|---|
[미디 분석 프로그램 만들기] 7. 트랙 청크 분석하기 - 미디 이벤트 분석 (0) | 2018.05.03 |
[미디 분석 프로그램 만들기] 6. 트랙 청크 분석하기 - 메타 이벤트 분석 (0) | 2018.05.03 |
[미디 분석 프로그램 만들기] 5. 트랙 청크 분석하기 - delta time 구하기 (0) | 2018.05.03 |
[미디 분석 프로그램 만들기] 4. 헤더 청크 분석하기 (0) | 2018.05.02 |
[미디 분석 프로그램 만들기] 2. 프로젝트 생성 및 Layout (0) | 2018.05.01 |
[미디 분석 프로그램 만들기] 1. 구현할 프로그램 소개 (0) | 2018.05.01 |
[미디 파일] 미디 파일 구조 분석 및 프로그래밍 9 - 시스템 이벤트 (5) | 2018.04.19 |
[미디 파일] 미디 파일 구조 분석 및 프로그래밍 8 -악기 번호 (5) | 2018.04.18 |
[미디 파일] 미디 파일 구조 분석 및 프로그래밍 7 - 컨트롤 번호 (0) | 2018.04.18 |