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

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

언제나휴일 2018. 5. 3. 14:33
반응형

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



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


앞에서 트랙 청크를 구성하는 이벤트 중에 메타 이벤트 분석까지 구현하였습니다.

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

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

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

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

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

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


이번에는 미디 이벤트를 분석하는 것을 구현합시다.


트랙 청크의 이벤트는 delta 타임 뒤에 상태 바이트(Status Byte)가 오며 이 상태 바이트 값이 0~0xEF 사이에 오면 미디 이벤트, 0xF0~0xFE일 때 시스템 이벤트, 0xFF일 때 메타 이벤트라고 했었죠.


여기에서는 상태 바이트 값이 0~0xEF 사이일 때인 미디 이벤트를 분석하는 것을 할 거예요.


미디 이벤트에 관한 표준 문서들을 보면 0x80~0xEF 사이일 때에 관한 설명은 다음처럼 나와 있습니다.

8X

Note off (소리 끄기)

9X

Note on (소리 내기)

AX

Key after touch (건반을 누른 상태에서 다시 압력을 가함)

BX

Control Change (효과 바꾸기)

CX

Program change (악기 바꾸기)

DX

Channel after touch

EX

Pitch wheel change


상태 바이트의 상위 4비트(니블)의 값이 8일 때는 Note off, 9일 때는 Note on, ... 처럼 미디 이벤트 종류를 의미하고 하위 4비트(니블)은 채널 정보입니다. 

[미디 파일] 미디 파일 구조 분석 및 프로그래밍 6 - Track 청크 4, 미디 이벤트 분할



그런데 상태 바이트가 0~0x7F일 때는 상태 바이트가 없는 미디 이벤트이며 상태는 이전 이벤트와 같은 상태이며 이를 Running Status라고 부릅니다.


이러한 부분을 고려하여 코드를 작성합시다.


앞에서 작성한 미디 분석 프로그램의 MDEvent의 정적 메서드 Parsing의 맨 뒤에 상태 바이트가 0xF0보다 작을 때 미디 이벤트를 만들어 반환하는 코드를 추가하세요. 

 

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

        {

            int oldoffset = offset;

            int delta = MidiHelper.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, bef_event.EventType);

            }

            return null;

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

        }



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

using System;

 

namespace 미디_분석_프로그램

{

    public class MidiEvent : MDEvent

    {

먼저 미디 이벤트를 만드는 정적 메서드 MakeEvent를 구현합시다.

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

        {

미디 이벤트는 첫 번째 데이터만 있는 것과 두 번째 데이터가 있는 것이 있습니다. 

            byte fdata;

            byte sdata = 0;

만약 상태 바이트 값이 0x80보다 작다면 Running Status로 이전 미디 이벤트의 상태 정보와 같습니다. 여기에서는 입력 인자로 받은 be_evtype과 같습니다. 이 때는 입력 인자로 받은 etype이 상태 바이트 정보가 아니라 첫 번째 데이터로 사용합니다.

            if (etype < 0x80)

            {

                fdata = etype;

                etype = be_evtype;

            }

만약 etype이 0x80보다 작지 않다면 이 값은 상태 바이트입니다. 이 때는 현재 offset 위치에 있는 값으로 첫 번째 데이터를 설정합니다.

            else

            {

                fdata = buffer[offset++];

            }

 

그리고 상태 바이트의 상위 4비트(니블)의 값이 0xC와 0xD일 때는 두 번째 데이터가 없고 그 외에는 두 번째 데이터가 있습니다. 이에 해당하는 코드를 작성하세요.

            switch (etype >> 4)

            {

                case 0x8: //Note Off

                case 0x9: //Note On

                case 0xA: //Note after touch

                case 0xB: //Controller

                case 0xE: //Pitch Bend

                    sdata = buffer[offset++];

                    break;

                case 0xC: //Change Instrument

                case 0xD: //Channel after touch1

                    break;

                default: return null;

            }

 

현재 offset과 이전 offset의 차이에 해당하는 크기의 버퍼를 생성한 후 원본 버퍼에서 현재 이벤트 정보를 복사합니다.

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

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

미디 이벤트 중에 어떠한 종류인지, delta 타임, 그리고 첫 번째 데이터와 두 번째 데이터, 이벤트 데이터를 복사한 버퍼를 입력 인자로 미디 이벤트 개체를 생성한 후 반환하세요.

            return new MidiEvent(etype, delta, fdata, sdata, buffer2);

        }

 

첫 번째 데이터와 두 번째 데이터의 가져오기 속성을 캡슐화하세요.

        public byte Fdata

        {

            get;

            private set;

        }

        public byte Sdata

        {

            get;

            private set;

        }


만약 미디 이벤트가 Note on이거나 Note off일 때 Note 값을 문자열로 변환하여 반환하는 가져오기 속성을 제공합시다. Note 값을 문자열로 변환하는 것은 MidiHelper 클래스에 정적 메서드(GetNoteName)로 구현합시다.


        public string Note

        {

            get

            {

                return MidiHelper.GetNoteName(Fdata);

            }

        }

컨트롤 값을 문자열로 변환하여 반환하는 가져오기 속성도 제공합시다. 마찬가지로 변환하는 것은 MidiHelper 클래스에 정의합시다.

        public string ControlData

        {

            get

            {

                return MidiHelper.GetControlStr(Sdata);

            }

        }

악기 값을 악기 이름으로 변환하여 반환하는 가져오기 속성도 제공합시다. 마찬가지로 변환하는 것은 MidiHelper 클래스에 정의합시다.

        public string InstrumentName

        {

            get

            {

                return MidiHelper.GetInstrumentName(Fdata);

            }

        }

상태를 반환하는 가져오기 속성을 제공합시다.

        public string Status

        {

            get

            {

이벤트 타입이 0x80미만이면 Running Status입니다. 하지만 실제 여기서 작성하는 프로그램에서는 이전 미디 이벤트의 상태값으로 설정하고 있어서 실질적으로 이렇게 출력하는 부분은 없습니다.

                if (EventType < 0x80)

                {

                    return "Running Status";

                }

이벤트 타입에 따라 적절한 내용을 반환하게 합시다.

                switch (EventType >> 4)

                {

                    case 0x8: return "Note Off";//Note Off

                    case 0x9: return "Note On";//Note On

                    case 0xA: return "Note after touch";//Note after touch

                    case 0xB: return "Controller";//Controller

                    case 0xE: return "Pitch Bend";//Pitch Bend                      

                    case 0xC: return "Change Instrument";//Change Instrument

                    case 0xD: return "Channel after touch";//Channel after touch1

                }

                return string.Empty;

            }

        }

채널 값을 반환하는 가져오기 속성을 제공하세요. 채널은 EventType의 하위 4비트(니블)입니다.

        public int Channel

        {

            get

            {

                return EventType & 0x0F;

            }

        }

이벤트 타입에 따라 적절한 설명을 반환하는 가져오기 속성을 제공합시다.

        public string Description

        {

            get

            {

                switch (EventType >> 4)

                {

                    case 0x8: //Note Off

                    case 0x9: //Note On

                    case 0xA: //Note after touch

                        return MakeNoteVelocity();

                    case 0xB: return MakeControlChange();//Controller

                    case 0xE: return MakePitchBend();//Pitch Bend

                    case 0xC: return MakeInstrument();//Change Instrument

                    case 0xD: return MakeChannel();//Channel after touch1

                    default: return string.Empty;

                }

            }

        }

        private string MakeChannel()

        {

            return string.Format("{0}:{1}:{2}", Status, Delta, Fdata);

        }

        private string MakeInstrument()

        {

            return string.Format("{0}:{1}:{2}", Status, Delta, InstrumentName);

        }

        private string MakePitchBend()

        {

            return string.Format("{0}:{1}:{2}:{3}", Status, Delta, Fdata & 0x7F, Sdata >> 1);

        }

        private string MakeControlChange()

        {

            return string.Format("{0}:{1}:{2}:{3}", Status, Delta, Fdata, ControlData);

        }

        private string MakeNoteVelocity()

        {

            return string.Format("{0}:{1}:{2}:건반 누르기 속도(세기){3}", Status, Delta, Note, Sdata);

        }

        public MidiEvent(byte etype, int delta, byte fdata, byte sdata, byte[] buffer) : base(etype, delta, buffer)

        {

            Fdata = fdata;

            Sdata = sdata;

        } 

    }

}



다음은 MidiHelper 클래스에 새로운 기능을 추가한 코드입니다.

 using System;

using System.Net;

using System.Text;

 

namespace 미디_분석_프로그램

{

    public class MidiHelper

    {

        public static int ConvertHostorder(int data)

        {

            return IPAddress.NetworkToHostOrder(data);

        }

        public static short ConvertHostorder(short data)

        {

            return IPAddress.NetworkToHostOrder(data);

        }

 

        public static short ConvertHostorderS(byte[] data, int offset)

        {

            return ConvertHostorder(BitConverter.ToInt16(data, offset));

        }

 

        public static string GetString(int magic)

        {

            byte[] data = BitConverter.GetBytes(magic);

            Encoding en = Encoding.Default;

            return en.GetString(data);

        }

 

        public static string GetString(byte[] data)

        {

            char[] buf = new char[data.Length];

            for (int i = 0; i < data.Length; i++)

            {

                buf[i] = (char)data[i];

            }

            Encoding en = Encoding.Default;

            return en.GetString(data);

        }

 

        public static int ReadDeltaTime(byte[] buffer, ref int offset)

        {

            int time = 0;

            byte b;

            do

            {

                b = buffer[offset];

                offset++;

                time = (time << 7) | (b & 0x7F);

            } while (b > 127);

            return time;

        }

 

        public static string[] note_names = new string[]

        {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"};

        public static string GetNoteName(int num)

        {

            return string.Format("{0}옥타브{1}", num / 12, note_names[num % 12]);

        }

 

        static string[] control_str = new string[]

        {

            "0 Bank Select",

            "1 Modulation Wheel",

            "2 Breath controller",

            "3 Undefined",

            "4 Foot Pedal",

            "5 Portamento Time",

            "6 Data Entry",

            "7 Volume",

            "8 Balance",

            "9 Undefined",

            "10 Pan position",

            "11 Expression",

            "12 Effect Control 1",

            "13 Effect Control 2",

            "14 Undefined",

            "15 Undefined",

            "16 Ribbon Controller or General Purpose Slider 1",

            "17 Knob 1 or General Purpose Slider 2",

            "18 General Purpose Slider 3",

            "19 Knob 2 General Purpose Slider 4",

            "20 Knob 3 or Undefined",

            "21 Knob 4 or Undefined",

            "22 Undefined",

            "23 Undefined",

            "24 Undefined",

            "25 Undefined",

            "26 Undefined",

            "27 Undefined",

            "28 Undefined",

            "29 Undefined",

            "30 Undefined",

            "31 Undefined",

            "32 Bank Select",

            "33 Modulation Wheel",

            "34 Breath controller",

            "35 Undefined",

            "36 Foot Pedal",

            "37 Portamento Time",

            "38 Data Entry",

            "39 Volume",

            "40 Balance",

            "41 Undefined",

            "42 Pan position",

            "43 Expression",

            "44 Effect Control 1",

            "45 Effect Control 2",

            "46 Undefined",

            "47 Undefined",

            "48 Undefined",

            "49 Undefined",

            "50 Undefined",

            "51 Undefined",

            "52 Undefined",

            "53 Undefined",

            "54 Undefined",

            "55 Undefined",

            "56 Undefined",

            "57 Undefined",

            "58 Undefined",

            "59 Undefined",

            "60 Undefined",

            "61 Undefined",

            "62 Undefined",

            "63 Undefined",

            "64 Hold Pedal(on/off)",

            "65 Portamento(on/off)",

            "66 Sustenuto Pedal(on/off)",

            "67 Soft Pedal(on/off)",

            "68 Legato Pedal(on/off)",

            "69 Hold 2 Pedal(on/off)",

            "70 Sound Variation",

            "71 Resonance(aka Timbre)",

            "72 Sound Release Time",

            "73 Sound Attack Time",

            "74 Frequency Cutoff(aka Brightness)",

            "75 Sound Control 6",

            "76 Sound Control 7",

            "77 Sound Control 8",

            "78 Sound Control 9",

            "79 Sound Control 10",

            "80 Decay or General Purpose Button 1 (on/off)",

            "81 Hi Pass Filter Frequency or General Purpose Button 2 (on/off)",

            "82 General Purpose Button 3 (on/off) Roland Tone level 3",

            "83 General Purpose Button 4 (on/off) Roland Tone level 4",

            "84 Undefined",

            "85 Undefined",

            "86 Undefined",

            "87 Undefined",

            "88 Undefined",

            "89 Undefined",

            "90 Undefined",

            "91 Reverb Level",

            "92 Tremolo Level",

            "93 Chorus Level",

            "94 Celeste Level or Detune",

            "95 Phaser Level",

            "96 Data Button increment",

            "97 Data Button decrement",

            "98 Non-registered Parameter",

            "99 Non-registered Parameter",

            "100 Registered Parameter",

            "101 Registered Parameter",

            "102 Undefined",

            "103 Undefined",

            "104 Undefined",

            "105 Undefined",

            "106 Undefined",

            "107 Undefined",

            "108 Undefined",

            "109 Undefined",

            "110 Undefined",

            "111 Undefined",

            "112 Undefined",

            "113 Undefined",

            "114 Undefined",

            "115 Undefined",

            "116 Undefined",

            "117 Undefined",

            "118 Undefined",

            "119 Undefined",

            "120 All Sound Off",

            "121 All Controllers Off",

            "122 Local Keyboard(on/off)",

            "123 All Notes Off",

            "124 Omni Mode Off",

            "125 Omni Mode On",

            "126 Mono Operation",

            "127 Poly Operation"

        };

 

        public static string GetControlStr(byte index)

        {

            return control_str[index & 0x7F];

        }

 

 

        public static string GetInstrumentName(int insnum)

        {

            switch (insnum)

            {

                //PIANO

                case 0: return "Acoustic Grand";

                case 1: return "Bright Acoustic";

                case 2: return "Electric Grand";

                case 3: return "Honky-Tonk";

                case 4: return "Electric Piano 1";

                case 5: return "Electric Piano 2";

                case 6: return "Harpsichord";

                case 7: return "Clavinet";

                //CHROMATIC

                case 8: return "elesta";

                case 9: return "Glockenspiel";

                case 10: return "Music Box";

                case 11: return "Vibraphone";

                case 12: return "Marimba";

                case 13: return "Xylophone";

                case 14: return "Tubular Bells";

                case 15: return "Dulcimer";

                //ORGAN

                case 16: return "Drawbar Organ";

                case 17: return "Percussive Organ";

                case 18: return "Rock Organ";

                case 19: return "Church Organ";

                case 20: return "Reed Organ";

                case 21: return "Accordian";

                case 22: return "Harmonica";

                case 23: return "Tango Accordian";

                //GUITAR

                case 24: return "Acoustic Guitar(nylon)";

                case 25: return "Acoustic Guitar(steel)";

                case 26: return "Electric Guitar(jazz)";

                case 27: return "Electric Guitar(clean)";

                case 28: return "Electric Guitar(muted)";

                case 29: return "Overdriven Guitar";

                case 30: return "Distortion Guitar";

                case 31: return "Guitar Harmonics";

                //BASS

                case 32: return "Acoustic Bass";

                case 33: return "Electric Bass (finger)";

                case 34: return "Electric Bass (pick)";

                case 35: return "Fretless Bass";

                case 36: return "Slap Bass 1";

                case 37: return "Slap Bass 2";

                case 38: return "Synth Bass 1";

                case 39: return "Synth Bass 2";

                //STRINGS

                case 40: return "Violin";

                case 41: return "Viola";

                case 42: return "Cello";

                case 43: return "Contrabass";

                case 44: return "Tremolo Strings";

                case 45: return "Pissicato Strings";

                case 46: return "Orchestral Strings";

                case 47: return "Timpani";

                //ENSEMBLE

                case 48: return "String Ensemble 1";

                case 49: return "String Ensemble 2";

                case 50: return "SynthStrings 1";

                case 51: return "SynthStrings 2";

                case 52: return "Choir Aahs";

                case 53: return "Voice Oohs";

                case 54: return "Synth Voice";

                case 55: return "Orchestra Hit";

                //BRASS

                case 56: return "Trumpet";

                case 57: return "Trombone";

                case 58: return "Tuba";

                case 59: return "Muted Trumpet";

                case 60: return "French Horn";

                case 61: return "Brass Section";

                case 62: return "SynthBrass 1";

                case 63: return "SynthBrass 2";

                //REED

                case 64: return "Soprano Sax";

                case 65: return "Alto Sax";

                case 66: return "Tenor Sax";

                case 67: return "Baritone Sax";

                case 68: return "Oboe";

                case 69: return "English Horn";

                case 70: return "Bassoon";

                case 71: return "Clarinet";

                //PIPE

                case 72: return "Piccolo";

                case 73: return "Flute";

                case 74: return "Recorder";

                case 75: return "Pan Flute";

                case 76: return "Blown Bottle";

                case 77: return "Skakuhachi";

                case 78: return "Whistle";

                case 79: return "Ocarina";

                //SYNTH LEAD

                case 80: return "Lead 1 (square)";

                case 81: return "Lead 2 (sawtooth)";

                case 82: return "Lead 3 (calliope)";

                case 83: return "Lead 4 (chiff)";

                case 84: return "Lead 5 (charang)";

                case 85: return "Lead 6 (voice)";

                case 86: return "Lead 7 (fifths)";

                case 87: return "Lead 8 (bass+lead)";

                //SYNYH PAD

                case 88: return "Pad 1 (new age)";

                case 89: return "Pad 2 (warm)";

                case 90: return "Pad 3 (polysynth)";

                case 91: return "Pad 4 (choir)";

                case 92: return "Pad 5 (bowed)";

                case 93: return "Pad 6 (metallic)";

                case 94: return "Pad 7 (halo)";

                case 95: return "Pad 8 (sweep)";

                //SYNTH EFFECTS

                case 96: return "FX 1 (rain)";

                case 97: return "FX 2 (soundtrack)";

                case 98: return "FX 3 (crystal)";

                case 99: return "FX 4 (atomosphere)";

                case 100: return "FX 5 (brightness)";

                case 101: return "FX 6 (goblins)";

                case 102: return "FX 7 (echoes)";

                case 103: return "FX 8 (sci-fi)";

                //ETHNIC

                case 104: return "Sitar";

                case 105: return "Banjo";

                case 106: return "Shamisen";

                case 107: return "Koto";

                case 108: return "Kalimba";

                case 109: return "Bagpipe";

                case 110: return "Fiddle";

                case 111: return "Shanai";

                //PERCUSSIVE

                case 112: return "Tinkle Bell";

                case 113: return "Agogo";

                case 114: return "Steel Drums";

                case 115: return "Woodblock";

                case 116: return "Taiko Drum";

                case 117: return "Melodic Tom";

                case 118: return "Synth Drum";

                case 119: return "Reverse Cymbal";

                //SOUND

                case 120: return "Guitar Fret Noise";

                case 121: return "Breath Noise";

                case 122: return "Seashore";

                case 123: return "Bird Tweet";

                case 124: return "Telephone Ring";

                case 125: return "Helicopter";

                case 126: return "Applause";

                case 127: return "Gunshot";

                default: return "알 수 없음";

            }

        }

    }

}




반응형