원문

 

바이트 레벨에서 분석한 표준 미디(Standard MIDI) 사양 및 구조

이 가이드는 (바이트 수준에서) 원시(raw) MIDI 데이터 프로그래밍 및 조작에 대한 집중 매뉴얼입니다. 완전하지는 않지만 MIDI(SMF) 형식의 구조와 일부 주요 명령에 집중하므로 기본 사항을 매우 빠르게 배우는 데 도움이 될 것입니다! 이 페이지에 포함된 지식을 사용하여 내 MIDI 변환 애플릿과 같은 프로그램을 만드는 데 도움이 될 것입니다.

 

기본적인 MIDI 바이트 구조

MIDI에는 Type-0, Type-1 및 Type-2의 세 가지 유형이 있습니다. 그러나 Type-2는 매우 드물기 때문에 0과 1에 집중할 것입니다. Type-0과 Type-1은 모두 데이터가 저장되는 방식만 다르므로 Type-1에서 들을 수 있는 것들은 모두 Type-0에서도 들을 수 있습니다. 결과적으로 데이터 또는 정밀도의 손실 없이 유형 간 변환이 가능합니다.

Type-0은 모든 데이터가 하나의 ‘트랙’에만 저장되는 곳입니다. 트랙(‘채널’과 혼동하지 말 것)은 모든 바이트가 서로 옆에 있는 연속적인(중단되지 않는) 파일의  데이터 스트림입니다. Type-1은 채널 데이터를 1개 이상의 트랙(최대 65536개까지)으로 구분할 수 있으므로 더 편리합니다. 간단한 베이스 라인과 멜로디가 있다고 상상해 보세요. Type-0을 사용하면 서로 옆에 있는 베이스 라인과 멜로디의 음표를 교대로 인코딩합니다. Type-1에서 모든 멜로디 음표는 하나의 트랙/데이터스트림으로 이동하고 모든 베이스 음표는 선택적으로 다른 트랙으로 이동합니다. 또한 Type-1에서는 모든 트랙에서 Type-0의 ‘인터리빙(interleaving)’ 스타일을 모방할 수 있습니다.

 

MIDI는 최대 16개의 ‘채널’을 허용합니다. 각 채널은 코드를 만들기 위해 여러 개의 보이스(성부)를 가질 수 있지만 한 번에 하나의 악기(음악 이론의 ‘파트’와 유사)만 사용할 수 있습니다. 피아노와 기타를 동시에 사용하려면 2개의 채널을 사용해야 합니다. 아래 표에는 현재 하나의 트랙 덩어리(섹션 F, G, H, I)만 있습니다. 또한 하나의 보이스만 사용하므로 다른 악기의 보이스를 더 추가하려면 기존 트랙에 데이터를 삽입하거나 또는 다른 트랙을 추가할 수 있습니다 (둘 이상의 악기를 사용하는 경우 채널 번호를 계속 보간할 필요가 없기 때문에 메모리를 절약할 수 있습니다). 다음을 살펴보세요.

 

 

빨간색(및 녹색)으로 강조된 부분은 편집하려는 부분입니다. 많은 플레이어가 트랙의 나머지 부분에서 정확한 바이트 수를 명시해야 하므로 섹션 G에 특히 주의하세요!  “blah blah…..“는 실제 음악 데이터가 들어가는 곳입니다. (H & I)

 

다음은 다양한 섹션에 대한 요약입니다.

  • A = 맨 처음 4바이트(16진수 아스키코드로 MThd)는 파일이 MIDI임을 나타냅니다.
  • B = 다음 4바이트는 나머지 MIDI 헤더(C, D & E)의 크기를 나타냅니다. 표준 MIDI 파일(SMF)의 경우 항상 00 00 00 06입니다.
  • C = MIDI에는 하위 형식이 있습니다. 0000은 Type-0 MIDI 파일임을 의미합니다. 0001은 Type-1입니다.
  • D = MIDI의 트랙 수를 지정합니다. Type-0은 1트랙으로 제한됩니다.
  • E = 음악의 속도(speed). 표시된 16진수 값 80은 4분 음표당 128틱(ticks)을 의미합니다.
  • F = 4D 54 72 6B는 아스키 코드 MTrk의 16진수이며 트랙 데이터의 시작을 표시합니다.
  • G = 이것은 HI(Track data & Track Out)에 존재하는 데이터의 바이트 크기와 일치해야 합니다. 표시된 것은 00 00 00 0A이므로 10바이트가 있음을 의미합니다(16진수 A는 10진수로 10을 의미).
  • H = 실제 음악 데이터. 자세한 내용은 아래를 참조하세요.
  • I = 00 FF 2F 00은 트랙의 끝에 도달했음을 나타내기 위해 필요합니다.

 

 

음악 데이터

앞서 언급했듯이 섹션 H는 모든 음악 데이터가 저장되는 곳입니다. 가장 간단한 시나리오부터 시작하겠습니다. 미들(middle) C, D 및 E의 3개 음표를 연주하고 싶다고 가정해 보겠습니다. 이렇게 하면 됩니다.

(주의: 종종 미디는 FF 바이트를 사용하여 실제 노트 데이터 이전에 여러 바이트의 메타 이벤트 데이터를 갖습니다. 이에 대해서는 나중에 자세히 설명합니다.)

 

여기에서 각각 4바이트(각 바이트는 2개의 ‘nibble‘ – 2개의 16진수 숫자로 구성됨)를 포함하고 각각 바이트 1(빨간색)의 타임스탬프로 시작하는 4개의 이벤트를 볼 수 있습니다. 이벤트는 여러 가지가 될 수 있습니다. 음을 연주하거나 중지하라는 메시지가 될 수도 있고, 비브라토를 추가하거나 악기를 변경하라는 메시지가 될 수도 있습니다. 하지만 모든 이벤트는 타임스탬프로 시작됩니다. 표시된 예에서 처음 세 이벤트(W, X, Y)는 각각 중간 C, D, E의 음표를 연주합니다. 네 번째 이벤트인 Z는 모든 음표를 무음 처리합니다. 이제 다음은 다양한 바이트 번호에 대한 요약입니다.

 

바이트 1은 각 이벤트의 타임스탬프입니다. 시간은 각 타임스탬프에 대해 누적되므로(accumulates) 꾸준한 리듬을 나타내기 위해 00, 10, 20, 30, 40, 50과 같은 타임스탬프가 아니라  00, 10, 10, 10 , 10, 10 와 같이 사용해야 합니다. 표의 예를 보면 00은 시간이 경과하지 않았음을 의미합니다. 다음 7F(Dec:127)는 앞의 이벤트 다음 7F의 시간을 기다리는 것을 의미합니다. 이보다 더 오래 대기하려면 MIDI가 특별한 작업을 수행해야 합니다. 80(Dec:128) 대신 2바이트인 81 00을 사용합니다.

 

81 007F보다 세밀한 대기시간 단위입니다.. 81 0181 0281 03…………………… 81 7F까지 가고 다음은 82 00 (256시간 단위), 그리고 결국 83 00…. 84 00…., FF 7F…., 81 80 00…., 81 80 80 00 을 지나 거대한 FF FF FF 7F까지 진행됩니다.

마지막 바이트가 항상 80보다 작기 때문에 타임스탬프가 종료된 시간을 알 수 있지만 이러한 표기법이 가능한 선행 바이트/초는 항상 80 이상입니다. Java 변환 알고리즘은 아래에 있는 코드를 참조하세요. 참고로, 시간 간격은 MIDI 헤더(섹션 E)에 정의된 음악 속도에 따라 다릅니다.

// Converting from the MIDI
// timestamp value to decimal in Java

int midiDecTime2normalTime(int[] n) {
  int l=n.length;    int t=0;
  for (int i=0 ; i<l-1 ; i++) {
    t += (n[i]-128) * Math.pow(2,7 * (l-i-1)) ;
  }
  t += n[l-1];
  return t;
}

 

위의 예제를 다시 보겠습니다.

바이트 2는 이벤트 유형(상태) 바이트입니다. W, X, Y의 경우 이벤트 유형은 ‘Note On’입니다. ‘90‘의 9 부분은 ‘Note On’ 메시지이고 ‘0‘ 자리는 이것이 적용되는 채널입니다. 가장 자주 사용되는 이 이벤트 유형은 바이트 3바이트 4로 볼 수 있는 두 개의 매개변수를 사용합니다.

바이트 3은 음의 피치(3C = 미들(middle) C)이고 바이트 4는 음의 볼륨입니다(둘 다 00 – 7F 범위).

요약하면, X7F 90 3E 60은 먼저 7F 시간 단위를 기다린 다음 채널 0에서 볼륨 60의 음표 C의 음을 재생함을 의미합니다.

 

9B가 유일한 이벤트 유형은 아닙니다. 8에서 E까지의 범위가 있습니다. 주요 목록은 다음과 같습니다.

  • 8 = 노트 꺼짐 (Note off)
  • 9 = 노트 켜짐 (Note on)
  • A = 애프터 터치 (AfterTouch). 즉, 키 프레셔(key pressure)
  • B = 컨트롤 변경 (Control change)
  • C = 프로그램(패치/악기) 변경 (Program (patch/instrument) change)
  • D = 채널 프래셔 (Channel pressure)
  • E = 피치 휠 (Pitch wheel)

 

이전에 언급한 것처럼 한 자리 숫자는 메시지가 실행되는 채널을 나타내기 위해 위의 유형을 따릅니다. 예를 들어, 92채널 2에 대한 참고 사항입니다. AB채널 B(Dec:11)에 애프터터치를 적용합니다. 주의하십시오. 제가 제공한 예에서 각 이벤트에 4바이트가 할당되어 있었지만 일부 이벤트 유형은 이보다 적거나 더 많이 사용합니다.

 

실행 상태 (Running Status)

상태 바이트의 후속 반복을 잘라내는 ‘실행 상태’라는 깔끔한 ‘트릭’을 사용할 수 있습니다.

이런 식으로 이벤트 유형(상태) 바이트 ‘90‘은 첫 번째 타임스탬프 이후 처음에 한 번만 사용됩니다. 클라이언트가 이를 ‘기억’하고 후속 이벤트에 적용하기 때문입니다.

 

<--- Z ---> 
7F B0 7B 00

마지막 이벤트(이벤트 Z)의 경우 이벤트 유형(바이트 2)은 채널 0에 적용된 ‘컨트롤 변경'(“B“)이고, 바이트 3(“7B“)은 ‘All Notes Off’ 컨트롤러입니다. 바이트 4는 무시되므로 00으로 채우면 됩니다.

그러나 실행 상태는 제어 바이트 8x ~ Ex에만 적용됩니다. F0 ~ F8는 제어 바이트가 취소되고 F9~ FF는 제어 바이트가 일시적으로 무시됩니다(따라서 이전의 실행 상태를 가져옴).

 

모든 부분 조합하기

실제로 실제 MIDI 파일을 생성하고 있음을 증명하기 위해 위의 데이터를 사용하여 페이지 상단 근처에 있는 원본 템플릿의 섹션 H에 삽입하겠습니다! (G 섹션에서 바이트 21을 편집해야 함). 이 코드를 복사하여 자주 사용하는 16진수 에디터에 붙여넣고 MIDI 파일로 저장하여 작동하는지 확인할 수 있습니다.

 

지금까지는 아주 좋은데 한 가지 약간의 문제가 있습니다. 모든 노트가 서로 겹칩니다. 다음 음이 시작되기 전에 각 음표가 중지되기를 원하면 어떻게 합니까? 90 ~ 9F 명령을 사용하여 음표를 시작하는 것처럼 808F 이벤트 유형을 사용하여 채널에서 음표를 중지할 수 있습니다. 또는 90 명령을 사용하지만 새 볼륨을 0으로 설정하는 것입니다. 이것은 마치 음이 멈춘 것처럼 (내부적으로도) 작동합니다. 이 메모 ‘소음기(silencer)’를 이미 가지고 있는 항목에 추가해 보겠습니다.

두 번째 줄에서 볼 수 있듯이 각 소음기 이벤트(2번째, 4번째, 6번째 이벤트)는 81 00의 딜레이를 가졌기 때문에 이후에 울리는 음(3번째 및 5번째 이벤트)에 대한 타임스탬프는 00이어야 합니다. ‘대기(wait)’가 이미 이전 이벤트에 의해 수행되었기 때문입니다. 또한 모든 메모가 이미 중지되었으므로 마지막 이벤트(All notes off)가 2라인의 예제에서 삭제되었습니다! 마지막으로, 두 라인의 모든 이벤트는 앞에서 설명한 ‘실행 상태’ 트릭으로 인해 암시적인 상태 90을 가집니다.

 

MIDI 메타 데이터

트랙 데이터(다이어그램의 섹션 H)에서 노트 데이터로 바로 들어가기 전에 MIDI에 음악 메타 이벤트가 포함되는 경우가 많으며 FF 바이트가 사용되기 때문에 이를 알 수 있습니다. FF 이벤트의 특별한 경우는 트랙이 완료되었지만(FF 2F 00) 강제 종료되었음을 나타내기 위해 사용됩니다(아래 데이터의 ‘footers‘ 참조). 그 외에도 메타 이벤트는 일반적으로 악기, 트랙 및 가사 등의 이름을 제공하는 것 외에는 아무 것도 하지 않습니다. 형식은 “FF XX YY“이며, 여기서 XX는 메타 이벤트 유형이고 YY는 방법입니다. 많은 바이트를 메타 이벤트가 차지합니다. 예를 들어 “FF 03 07“은 다음 7바이트가 트랙 이름을 지정함을 의미합니다.

메타데이터 자세히 보기 (영문)

 

보이스, 악기 동시에 연주

두 개 이상의 보이스를 사용하는 방법에 대해 논의하고 Type-0 및 Type-1 기술을 모두 사용하여 이를 구현합니다. 우리는 C, D, E라는 새로운 음표를 추가하여 이미 수행한 작업을 기반으로 C를 연주할 때 동시에 낮은 G를 연주하고 E를 연주할 때 동시에 낮은 A를 연주합니다.

먼저 Type-1 버전이 어떻게 생겼는지 보여줍니다. C, D, E는 트랙 1로, G와 A는 트랙 2로 이동합니다. 기본적으로 트랙 2는 트랙 1이 재생되는 동안 재생되므로 서로 효과적으로 중첩됩니다. 또한 트랙 헤더와 ‘푸터’, 전체 MIDI 파일을 완성하기 위한 MIDI 헤더가 포함됩니다. 시각화하기 쉽도록 데이터를 정렬했습니다. (실제 파일에서 바이트는 모두 바로 옆에 있습니다).

 

여기에서 고려해야 할 몇 가지 사항이 있습니다.

  • 먼저 MIDI 헤더의 마지막 바이트에서 세 번째 바이트를 살펴보세요.
    02 – 두 개의 트랙을 의미합니다. 또한 마지막에서 다섯 번째는 ‘01‘로 Type-1 MIDI임을 의미합니다.
  • 다음으로 Trk 1 헤더의 마지막 바이트를 살펴보세요. 이것은 1A이고 나머지 트랙 1의 바이트 수를 나타냅니다(트랙 2의 헤더 마지막 16도 동일합니다).
  • 이제 Trk 2의 데이터를 살펴보세요. 첫 번째 이벤트는 상태 바이트에 대해 숫자 C1을 사용합니다! 즉, 채널 1의 기기를 기기 ‘18‘(다음 바이트)로 변경합니다. 이것은 어쿠스틱 기타입니다.
  • 트랙 2 데이터의 두 번째 이벤트는 채널 1(‘91‘의 ‘1‘에 주의)에서 낮은 G(피치 37) 음표를 재생합니다. 다음 이벤트가 중요합니다. 일반적인 81 00 대신 82 00이 있습니다. 즉, G 음표를 두 배 더 길게 유지합니다.

 

 

다음은 동일한 내용의 Type-0 MIDI입니다.

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Media Art


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다