2022년 9월 12일 월요일

python으로 mid file 분석, MThd

 

MThd 분석

앞에서 mid file은 chunk의 연속이라고 하였습니다. 그중에 MThd라고 불리는 chunk가 있는데 헤더를 저장하고 있는 chunk입니다.

내용은 간단하지만 그중에 ticks_per_beat (또는 time division)이라고 불리는 개념이 있어서 설명하려고 합니다.


Header chunk (of a MIDI file)

내용은 아래와 같습니다.

DescriptionLength in bytesStarts at byteValue
chunk ID40x00The character string "MThd"
size40x046
format type20x080, 1, or 2
number of tracks20x0A1 - 65,535
time division20x0CVarious as described below

여기서 생소한 트랙이라는 개념이 나오는데 트랙이라는 건 다른 연주자의 악보(또는 다른 악기의 악보)라고 생각 하면 됩니다. 

format type : 0,1,2인데 0은 트랙이 하나일때 1은 트랙이 여러개 이면서 동기적일때, 2는 트랙이 여러개이나 비동기적으로 play가 될때 사용합니다. 보통은 1이라고 생각하면 됩니다.

number of tracks : 전체 트랙의 숫자 입니다.

time division : 이 값은 4분 음표(4분 음표를 1박자라고 표현)를 여기 에서는 몇개의 틱으로 나타내는지를 나타내는 숫자 입니다.

(여기 값이 480이라고 한다면 나중에 음표를 연주할때 480틱이라는 시간 동안 연주를 하라는 명령이 내려오면 1박자 동안 연주하면 됩니다. )


MidFile 클래스

기존 소스를 변형하여 chunk 단위를 분석할 수 있는 process_chunk() 함수를 만들었습니다.

코드를 약간 정리하였고 주석을 넣었습니다.


소스

import numpy as np


class MidFile:

    # https://faydoc.tripod.com/formats/mid.htm
    # https://github.com/mido/mido/blob/main/mido/midifiles/midifiles.py
    def __init__(self):
        self.ticks_per_beat = None
        self.trackcount = None
        self.type = None

    def process_chunk(self, type, length, body):
        if type == 'MThd':
            ''' 
            * type (2)
              0:single-track
              1:multiple tracks, synchronous
              2:multiple tracks, asynchronous
            * trackcount (2)
            * ticks_per_beat (2)
              is the number of delta-time ticks per quarter note.
              4분 노트(음표)당 델타-타임 틱들의 갯수
              https://www.recordingblogs.com/wiki/header-chunk-of-a-midi-file
              16비트의 상위 비트가 0이면 "박자당 틱"(또는 "4분음표당 펄스(틱수)")입니다. 
              상위 비트가 1이면 시간 분할은 "초당 프레임 수"입니다.
              - BPM = 1분당 bit수 
                120BPM은 1분에 120Bit로 표현 = 4분 음표(1박)가 1분에 120번 나올 수 있음(1번 나오는데 0.5초)
                ticks_per_beat = 480 이면, time delta가 480인 경우 4분 음표로 표현됨  
            '''
            self.type = int.from_bytes(body[0:2].tobytes(), 'big')
            self.trackcount = int.from_bytes(body[2:4].tobytes(), 'big')
            self.ticks_per_beat = int.from_bytes(body[4:6].tobytes(), 'big')
            print(self.type, self.trackcount, self.ticks_per_beat)

    def read(self, filename):
        with open(filename) as f:
            rectype = np.dtype(np.uint8)
            bdata = np.fromfile(f, dtype=rectype)
        # MID 파일은 chunk의 연속으로 되어 있습니다.
        # Chunk Type(4), chunk body length (4), body(chunk body length)
        read_pos = 0
        while True:
            chunk_type = bdata[read_pos:read_pos + 4].tobytes()
            chunk_type = chunk_type.decode('utf-8')
            chunk_length = int.from_bytes(bdata[read_pos + 4:read_pos + 8].tobytes(), 'big')

            print(chunk_type, chunk_length)

            self.process_chunk(chunk_type, chunk_length, bdata[read_pos + 8:read_pos + 8 + chunk_length])

            read_pos = read_pos + chunk_length + 8
            if len(bdata) <= read_pos:
                break


if __name__ == "__main__":
    mid = MidFile()
    mid.read("../00_data/tests_for-elise.mid")


실행결과

MThd 6
1 2 480
MTrk 3909
MTrk 3034




https://signal.vercel.app 이라는 site에 tests_for-elise.mid mid 파일을 올려서 분석해본 내용입니다.
헤더 분석으로 통하여 ticks_per_beat (또는 time division) 값이 480이라는 숫치를 알았는데요
120BPM으로 연주를 하면 (120BPM은 1분에 120bit이니 120bit/60sec = 2 bit/sec 여기서 1bit라는 것은 쿵쿵(120bpm이므로 음악에서 0.5초당 쿵쿵.. )거리는 것을 의미하게 됩니다. 1bit / 0.5sec 가 됩니다. 

1박=1bit=480 이 엘리제를 위하여라는 mid파일에서 설정한 값입니다. 480tick 동안 특정음을 낸다면 그건 한박자 짜리(4분 음표)가 된다는 의미가 됩니다.











댓글 없음:

댓글 쓰기