2024. 7. 15. 04:10ㆍDevelopers 공간 [Shorts]/Vision & Audio
<분류>
A. 수단
- OS/Platform/Tool : Linux, Kubernetes(k8s), Docker, AWS
- Package Manager : node.js, yarn, brew,
- Compiler/Transpillar : React, Nvcc, gcc/g++, Babel, Flutter
- Module Bundler : React, Webpack, Parcel
B. 언어
- C/C++, python, Javacsript, Typescript, Go-Lang, CUDA, Dart, HTML/CSS
C. 라이브러리 및 프레임워크 및 SDK
- OpenCV, OpenCL, FastAPI, PyTorch, Tensorflow, Nsight
1. What? (현상)
MIDI(Musical Instrument Digital Interface)는 전자악기가 데이터를 저장하거나 교환하기 위해 활용되는 프로토콜입니다.
이번 글에서는 mido라는 패키지를 활용해 .mid로 된 MIDI파일을 다루는 법에 대해 살펴보고자 합니다.
먼저 mido 패키지를 설치하고 시작합니다.
pip3 install mido
2. Why? (원인)
- X
3. How? (해결책)
0. 불러오기
먼저 기존의 미디파일을 불러오는 방법은 아래와 같으며, 예시 결과를 볼 수 있습니다.
** clip (default : False) : note나 velocity가 127을 넘어갈 때 clip할지 error 메시지를 낼지에 대한 옵션입니다.
mid = mido.MidiFile('myfile.mid', clip=True)
MidiFile(type=0, ticks_per_beat=480, tracks=[
MidTrack([
MetaMessage('channel_prefix', channel=15, time=0),
MetaMessage('track_name', name='the title of song', time=0),
MetaMessage('instrument_name', name='PIANO', time=0),
MetaMessage('time_signature', numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0),
MetaMessage('key_signature', key='Eb', time=0),
MetaMessage('key_signature', key='D', time=0),
MetaMessage('smpte_offset', frame_rate=25, hours=1, minutes=0, seconds=0, frames=0, sub_frames=0, time=0),
MetaMessage('set_tempo', tempo=600000, time=0),
MetaMessage('marker', text='38x385', time=303),
....
Message('note_on', channel=0, note=77, velocity=53, time=5),
Message('note_on', channel=0, note=44, velocity=24, time=7),
Message('note_on', channel=0, note=51, velocity=16, time=119),
Message('control_change', channel=0, control=64, value=127, time=200),
....
MetaMessage('end_of_track', time=5010),
])
])
위에서 보이는 것과 같이 기본적으로 MidiFile에는 파일에 대한 정보가 있고, 여러개의 MidTrack이 존재합니다.
1. MidiFile
먼저 파일에 대한 정보를 살펴보겠습니다.
위에서 얻은 오브젝트에서 해당 정보만을 뽑아보는 방법은 아래와 같습니다.
print("mid.type : {}".format(mid.type))
print("mid.ticks_per_beat : {}".format(mid.ticks_per_beat))
print("track len : {}".format(len(mid.tracks)))
print("track[0] message len : {}".format(len(mid.tracks[0])))
mid.type : 0
mid.ticks_per_beat : 480
track len : 1
track[0] message len : 1467
이들에 대한 정보는 아래와 같습니다.
- type (default : 1) : MIDI 파일의 포맷 타입입니다. 아래와 같은 종류가 있습니다.
- type 0 (single track): 하나의 track에 모든 메시지들이 저장되어있습니다.
- type 1 (synchronous): 두개 이상의 track을 포함하고 있으며, 첫번째 track chunk는 meta events 정보를 가지고 있고, 모든 트랙이 동시에 시작합니다.
- type 2 (asynchronous): 여러개의 track을 포함하고 있지만, 각각의 track chunk들은 독립된 sequence를 의미합니다.
- ticks_per_beat : 하나의 beat에 포함된 MIDI tick의 개수입니다.
** tick, beat 등 음악에 대한 용어가 궁금한 분은 아래 더보기를 참조하세요 - track 길이 : 몇 개의 track이 들어있는지를 의미합니다.
- message 길이 : 아래 설명하겠지만, 각각의 track에는 여러개의 Message가 포함되어있는데, 몇 개의 Message가 포함되어있는지를 의미합니다.
------------------------------------------------------------------
<음악 용어>
1. 음표(note)
음악에 쓰이는 기본 음의 길이를 의미합니다.
- 16분음표 : sixteenth note
- 8분음표 : eighth note
- 4분음표 : quarter note
- 점4분음표 : dotted qaurter note
- 2분음표 : half note
- 점2분음표 : dotted half note
- 온음표 : whole note
2. 쉼표(rest)
음표와 반대로 쉬는 음의 길이를 의미합니다.
- 16분쉼표 : sixteenth rest
- 8분쉼표 : eighth rest
- 4분쉼표 : quarter rest
- 점4분쉼표 : dotted qaurter rest
- 2분쉼표 : half rest
- 점2분쉼표 : dotted half rest
- 온쉼표 : whole rest
3. 박(beat)
beat는 음악내에서 “느껴지는” 시간의 단위입니다. 일정한 시간적인 질서를 의미하며, 리듬을 지정할 때도 사용합니다.
따라서 하나의 beat는 하나의 음악 내에서는 동일하겠지만, 다른 음악에서는 다른 시간적 길이를 가질 수도 있습니다.
쉽게 애기하면 4분음표 기준으로 박자를 쪼개면 4 beat, 8분음표를 기준으로 박자를 쪼개면 8비트, 16분음표를 기준으로 박자를 조개면 16비트입니다.
4. BPM(Beat Per Minute), Tempo
앞서 설명한 바와 같이 beat는 “느껴지는” pulse 단위이기 때문에, "1분간 비트수"인 BPM은 얼마나 노래가 빠르게 “느껴지는가”를 의미합니다.
따라서 BPM은 하나의 노래에서 beat가 어떻게 정의되는냐에 따라 다르게 측정될 수 있습니다.
예를 들어 120 BPM은 1분간 120회의 beat가 있음을 의미합니다.
5. 박자(meter) 박자표(time signature, meter signature)
여러개의 박(beat)를 모아 "심리적인 강점"을 주기적으로 설정해 박의 진행을 정리&통합하는 조직단위입니다.
예를 들어 4/4박자, 3/8박자 등이 있는데, 4 분모(denominator)를 가지는 박자는 보통 4비트를 가지고, 이 때 4분음표(quarter note)와 beat는 같은 말로 쓰입니다.
6. 마디(Bars, Measures)
하나의 chunk에 몇개의 beat가 chunk되는가에 대한 단위입니다.
7. 틱(tick)
MIDI에서 가장 작은 절대적인 시간 단위로, 하나의 beat는 여러개의 tick을 가지고 있습니다.
------------------------------------------------------------------
2. MidTrack
위에서 살펴본 바와 같이 MidTrack은 여러개의 MetaMessage와 Message로 이루어져있습니다.
이들을 다시 한번 살펴보겠습니다.
print(mid.tracks[0].sort(key=lambda message:message.time))
MidTrack([
MetaMessage('channel_prefix', channel=15, time=0),
MetaMessage('track_name', name='the title of song', time=0),
MetaMessage('instrument_name', name='PIANO', time=0),
MetaMessage('time_signature', numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0),
MetaMessage('key_signature', key='Eb', time=0),
MetaMessage('key_signature', key='D', time=0),
MetaMessage('smpte_offset', frame_rate=25, hours=1, minutes=0, seconds=0, frames=0, sub_frames=0, time=0),
MetaMessage('set_tempo', tempo=600000, time=0),
MetaMessage('marker', text='38x385', time=303),
....
Message('note_on', channel=0, note=77, velocity=53, time=5),
Message('note_on', channel=0, note=44, velocity=24, time=7),
Message('note_on', channel=0, note=51, velocity=16, time=119),
Message('control_change', channel=0, control=64, value=127, time=200),
....
MetaMessage('end_of_track', time=5010),
])
2-1. Meta Message
먼저 MetaMessage에 대해 살펴보려고 합니다. 아래는 meta만 골라 프린트해 본 코드입니다.
for msg in mid.tracks[0]:
if msg.is_meta:
print(msg)
else:
pass
각각에 대한 정보는 아래와 같습니다.
- channel_prefix : 16개의 MIDI 채널 prefix 중 어떤 채널을 위한 미디파일인지를 의미합니다.
- track_name : MIDI track의 이름입니다.
- instrument_name : 악기의 이름입니다.
- time_signature : 박자를 의미합니다. 4/4박자가 default입니다.
- numerator : 분자를 의미하며, 예를 들어 3/8박자인 경우 numerator 3입니다.
- denominator : 분모를 의미하며, 예를 들어 3/8박자인 경우 denominator 8입니다.
- clocks_per_click : 1 click당 MIDI clock tick(메트로놈 틱)이 몇개 필요한지를 의미합니다.
예를 들어 default인 24인 경우, quarter note에 대한 clock이 24개 필요하다면 “한개 quarter note”마다 한번의 click tick이 발생합니다. - notated_32nd_notes_per_beat : 32분음표가 몇개가 모여 beat를 만들어내는지를 의미합니다.
보통 32분음표가 8개모이면 4분음표를 만들어내므로 8개가 default이지만, 어떤 프로그램은 특이하게도 4분음표를 32분음표 10개로 구성하는 경우도 있습니다.
- key_signature : 키(key)를 의미하며, “A A#m Ab Abm Am B Bb Bbm Bm C C# C#m Cb Cm D D#m Db Dm E Eb Ebm Em F F# F#m Fm G G#m Gb Gm” 중 하나를 가집니다.
- smpte_offset : MIDI track이 시작되는 위치에 대한 정보를 SMPTE time code로 나타낸 결과 입니다.
** SMPTE(Society of Motion Picture and Television Engineers) time code : 00:00:00:00(몇시 몇분 몇초 몇프레임)을 의미하는 시간으로, 여러개의 데이터의 동기화를 위한 타임코드입니다. - set_tempo: 1박(beat)에 몇 microseconds를 가지는지 의미합니다.
** 위 정보를 활용해 BPM을 구하는 법이 궁금하다면 아래 더보기를 참조하세요
------------------------------------------------------------------
<BPM 구하기>
BPM(Beats Per Minute)은 1분 동안 beat가 등장하는 개수이므로 1분동안 등장하는 beat의 개수를 구해내면 됩니다.
하지만 MIDI의 정보에는 set_tempo라는 "하나의 quarter note의 시간적 길이 (microseconds)"로 제공됩니다.
** 500,000이 default입니다.
따라서 먼저 아래의 식을 통해 "1분당 quarter의 개수"를 구할 수 있습니다.
$$\frac{10^6\times 60}{set\_tempo}$$
이 때, 위에서 구한 "1분당 quarter의 개수"는 4분음표(quarter note)가 1 beat일 때를 기준으로 구한 값이지만, 아시다시피 1 beat는 박자표에 따라 다른 음표를 가질 수 있습니다.
** 4/4박자인 경우 quarter note 하나가 하나의 beat를 의미
** 2/2박자인 경우 half note 하나(두개의 quarter note)가 하나의 beat를 의미
따라서 박자의 denominator를 반영해 a/b 박자인 경우, "1분당 quarter의 개수"를 4로 나누고 denominator b를 곱해주면 "1분당 beat의 개수"를 구해낼 수 있습니다.
# Get Information
time_signature=None
set_tempo=None
for msg in mid.tracks[0]:
if msg.is_meta:
print(msg)
if msg.type=='time_signature':
time_signature=msg
if msg.type=='set_tempo':
set_tempo=msg
else:
pass
# Case1.
quarter_notes_per_min=1.0/(set_tempo.tempo/(10**6))*60.0
beats_per_min = quarter_notes_per_min*time_signature.denominator/4
print("Check this bpm : {}".format(beats_per_min))
아래와 같이 mido 패키지에서 bpm을 얻을 수 있는 함수를 제공하지만, 이때는 denominator를 반영해 결과를 내지 않습니다.
# Case2.
print("Check this bpm2 : {}".format(mido.tempo2bpm(set_tempo.tempo)))
------------------------------------------------------------------
2-2. Message
Message에는 여러가지 type이 있지만 아래와 같은 두가지만 살펴보겠습니다.
- note_on : note가 바뀌는 곳을 의미합니다.
- channel : 16개의 MIDI채널 중 어디로 가야할지를 의미합니다.
- note (0~127) : note를 의미한다. 예를 들어 0x3C(60)의 경우 중간 C를 의미한다.
- velocity (0~127) : note의 attack 혹은 release가 얼마나 빠른지를 나타내며 64를 default로 활용합니다. 아래와 같은 logarithmic decibel gain으로 바뀌어 사용됩니다.
$$G=20log_{10}(\frac{127^2}{V^2})$$
- control_change : volume control, pan control, legato pedal과 같은 a slider, knob, or a switch형태의 controller가 바뀌는 곳을 의미합니다.
- channel : 16개의 MIDI채널 중 어디로 가야할지를 의미합니다.
- control : 어떤 controller를 지정할지를 의미합니다. 예를 들어 0x07(7)의 경우 coarse volume controller를 의미합니다.
- value : 어떤 값을 적용할지를 의미합니다.
https://www.twilio.com/en-us/blog/working-with-midi-data-in-python-using-mido
https://www.recordingblogs.com/wiki/musical-instrument-digital-interface-midi
MIDI 관련
https://mido.readthedocs.io/en/stable/meta_message_types.html?highlight=channel_prefix#channel-prefix-0x20
https://www.recordingblogs.com/wiki/midi-meta-messages
https://m.blog.naver.com/mrland_net/223055500977
박자 관련
https://blog.naver.com/cityblue63/221897084971
https://learningmusic.ableton.com/ko/make-beats/backbeats.html
http://www.drawmusic.com/music-theory/time?ref=redmt
https://speckofdust.tistory.com/153
SMPTE
'Developers 공간 [Shorts] > Vision & Audio' 카테고리의 다른 글
[Audio] Python활용해 BPM 측정하기 (0) | 2024.08.08 |
---|---|
[Audio] Python에서 Audio 불러오기 (0) | 2024.07.15 |
[NLP] Python으로 Regular Expression 활용 예시 (0) | 2024.07.07 |
[NLP] Faiss와 CLIP을 이용한 간단한 Text Retrieval 구현하기 (0) | 2024.06.06 |
[Generative] Diffusion 모델 원하는 위치에 받기 (0) | 2024.03.28 |