Struktura pliku MIDI: nagłówek i ścieżki
Pytanie
budowa pliku *.mid
Odpowiedź Sztucznej Inteligencji
Bezpośrednia odpowiedź na pytanie
- Plik *.mid to Standard MIDI File (SMF) – binarny kontener złożony z bloków (chunków): obowiązkowego nagłówka MThd i jednego lub wielu bloków ścieżek MTrk.
- Zdarzenia w ścieżkach są ułożone w czasie przyrostowym (delta-time, VLQ) i obejmują: komunikaty kanałowe MIDI (0x8n–0xEn), meta‑zdarzenia (0xFF) i SysEx (0xF0/0xF7).
- Wszystkie liczby wielobajtowe są w big‑endian. W nagłówku definiuje się format (0/1/2), liczbę ścieżek i podział czasu (PPQN lub SMPTE).
Kluczowe punkty
- Identyfikatory chunków: "MThd" i "MTrk" (ASCII).
- Długość danych chunku: 32‑bit big‑endian.
- Delta‑time: Variable Length Quantity (1–4 bajty).
- Obowiązkowe meta‑zdarzenie kończące każdą ścieżkę: FF 2F 00.
- Tempo: meta FF 51 03 t2 t1 t0 (µs/ćwierćnutę), domyślnie 500000 µs (120 BPM), jeśli nie ustawiono.
Szczegółowa analiza problemu
1) Ogólna struktura SMF
- Chunk (blok) = 4 bajty ID + 4 bajty długości + N bajtów danych.
- Kolejność w pliku:
- MThd (zawsze pierwszy i unikalny)
- MTrk × track_count (co najmniej jeden)
2) Nagłówek MThd (14 bajtów łącznie)
- "MThd" (0x4D 54 68 64)
- Długość danych: 00 00 00 06 (zawsze 6)
- Dane (6 bajtów):
- format (2 B): 0, 1 lub 2
- 0: jedna ścieżka (wszystkie kanały w MTrk)
- 1: wiele ścieżek startujących synchronicznie (najczęstszy)
- 2: wiele niezależnych sekwencji (rzadkie)
- liczba_ścieżek (2 B): ile MTrk występuje dalej
- division (2 B): podział czasu
- jeśli bit 15 = 0: PPQN (ticks per quarter note), np. 480, 960
- jeśli bit 15 = 1: format SMPTE:
- bajt wysok. = wartość ujemna: −24, −25, −29, −30 (29 ≈ 29.97 drop)
- bajt nisk. = ticks per frame (1–255)
- czas jednego ticka ≈ 1 / (|fps| × tpf)
Uwagi implementacyjne do division:
- PPQN: czas[µs] dla zdarzenia o deltcie D liczymy: D × (µs/ćwierćnutę) / PPQN.
- SMPTE: czas[s] ≈ D / (|fps| × tpf).
3) Blok ścieżki MTrk
- "MTrk" (0x4D 54 72 6B)
- Długość (4 B): rozmiar danych ścieżki
- Dane: sekwencja par [delta‑time][event], gdzie:
- delta‑time: VLQ (1–4 B), opóźnienie od poprzedniego zdarzenia w tikach
- event: dokładnie jeden z trzech typów:
- Zdarzenie kanałowe (Channel Voice): status 0x8n–0xEn + 1–2 bajty danych
- 0x8n Note Off (nota, vel)
- 0x9n Note On (nota, vel; vel=0 równoważne Note Off – powszechna optymalizacja)
- 0xAn Poly Aftertouch
- 0xBn Control Change (np. CC7 volume, CC10 pan, CC64 sustain)
- 0xCn Program Change (zmiana instrumentu)
- 0xDn Channel Aftertouch
- 0xEn Pitch Bend (LSB, MSB; środek 0x2000)
- SysEx:
- 0xF0 len(VLQ) … dane … (opcjonalnie kończące 0xF7 w danych)
- 0xF7 len(VLQ) … dane … (kontynuacja/escape)
- len to bezpośrednia długość bajtów danych po długości
- Meta (plikowe, nie wysyłane przez port MIDI):
- 0xFF typ (1 B) len(VLQ) dane
- Ważne typy:
- 0x2F End of Track (len=0) – wymagane
- 0x51 Set Tempo (len=3): t2 t1 t0 = µs/ćwierćnutę
- 0x58 Time Signature (len=4): nn (licznik), dd (wykładnik potęgi 2 dla mianownika), cc (MIDI clocks/ćwierćnuta, zwykle 24), bb (32‑ki na 24 clocks, zwykle 8)
- 0x59 Key Signature (len=2): sf (−7..+7), mi (0 dur, 1 moll)
- 0x01–0x07 teksty/nazwy; 0x20 kanał, 0x21 port, itd.
4) Delta‑time i Variable Length Quantity (VLQ)
- Każdy bajt: 7 bitów danych + MSB=1 oznacza „ciąg dalszy”.
- Składanie liczby (pseudokod):
- val=0; do { b=read8(); val=(val<<7) | (b & 0x7F); } while (b & 0x80);
- Przykłady:
- 0x00 → 00
- 0x7F → 7F
- 0x80 → 81 00
- 0x2000 → C0 00
- Maks. 4 bajty (28 bitów).
5) Running Status (kompresja strumienia)
- Jeśli kolejne zdarzenie kanałowe ma ten sam bajt statusu (0x8n–0xEn), można pominąć status i zapisać tylko bajty danych.
- Parser musi pamiętać „ostatni status kanałowy”. Zasady praktyczne:
- Running status dotyczy wyłącznie zdarzeń kanałowych.
- Nie stosuje się go do Meta (0xFF) ani SysEx (0xF0/0xF7).
- Po Meta/SysEx rozsądnie jest utrzymywać ostatni status kanałowy, ale wielu autorów plików go nie używa – parser powinien tolerować oba warianty.
6) Czas rzeczywisty, tempo i tempo‑mapa
- Jeśli brak FF 51, przyjmuje się 500000 µs/ćwierćnutę (120 BPM).
- Przeliczanie czasu (PPQN):
- czas_µs = Σ po segmentach: delta_ticks × (µs/ćwierćnutę_bieżące) / PPQN
- Przy zmianach tempa budujemy oś czasu przez kumulację między meta‑zdarzeniami FF 51.
- Długość nuty to różnica czasu między Note On a odpowiadającym Note Off (lub Note On z vel=0 tej samej nuty i kanału). Pedal sustain (CC64) wpływa na akustyczne „wybrzmienie”, ale nie na parowanie nut.
7) Minimalny przykład (format 1: tempo + jedna nuta)
Hex (komentarze po „//”):
- MThd
- 4D 54 68 64 // "MThd"
- 00 00 00 06
- 00 01 // format 1
- 00 02 // 2 ścieżki
- 01 E0 // 480 PPQN
- MTrk (tempo)
- 4D 54 72 6B
- 00 00 00 0B
- 00 FF 51 03 07 A1 20 // tempo 500000 µs/qn
- 00 FF 2F 00 // koniec ścieżki
- MTrk (nuta)
- 4D 54 72 6B
- 00 00 00 10
- 00 90 3C 64 // delta=0, Note On C4, vel=100
- 81 70 80 3C 00 // delta=240, Note Off C4
- 00 FF 2F 00
8) Pułapki implementacyjne
- Błędne dekodowanie VLQ (zapominanie o usunięciu bitu 7 i o przesunięciu o 7 bitów).
- Nieuwzględnienie running status (skutkuje „rozsynchronizowaniem” danych).
- Zamiana kolejności bajtów (endianness): wszystkie liczby wielobajtowe są MSB‑first.
- Niepoprawne długości chunków (liczby muszą dokładnie odpowiadać rzeczywistym bajtom).
- Brak FF 2F 00 w ścieżce – plik jest formalnie niepoprawny, choć niektóre odtwarzacze bazują tylko na długości MTrk.
- Parsowanie czasu przy SMPTE – pamiętaj o ujemnym bajcie fps i ticks‑per‑frame.
Aktualne informacje i trendy
- SMF (*.mid) w wersjach 0 i 1 pozostaje dominującym formatem wymiany danych MIDI między aplikacjami DAW i urządzeniami.
- Ekosystem MIDI 2.0 (Universal MIDI Packet – UMP, MIDI‑CI) rozwija się, lecz klasyczne pliki *.mid (SMF 1.x) są nadal powszechnie używane i wspierane. W wielu środowiskach produkcyjnych to wciąż „złoty standard” do archiwizacji i wymiany klipów MIDI.
Wspierające wyjaśnienia i detale
- Przeliczanie BPM z tempa:
- BPM = 60 000 000 / (µs/ćwierćnutę).
- Przykład running status:
- 90 3C 64 00 3E 64 00 40 64
- Tylko pierwszy komunikat ma status 0x90; kolejne dwa zdają się zaczynać od danych (MSB=0), ale dziedziczą 0x90.
- Pitch Bend:
- Wartość 14‑bit: value = (MSB<<7) | LSB; środek 8192 (0x2000), zakres ±8192.
Aspekty etyczne i prawne
- Plik *.mid nie zawiera sampli, ale utwór (melodia, aranżacja) podlega prawu autorskiemu. Dystrybucja cudzych plików MIDI może wymagać licencji.
- Zawartość SysEx bywa specyficzna dla producenta (np. mapy brzmień, konfiguracje) – publikacja takich danych może naruszać zastrzeżenia producenta.
Praktyczne wskazówki
- Narzędzia:
- Hex: HxD, 010 Editor (dobrze mieć szablon binarny).
- Analiza/parsowanie: Python mido/pretty_midi; w Javie javax.sound.midi; w C/C++ – RtMidi (runtime) + własny parser SMF.
- Podgląd: MIDI-OX, MIDITrail.
- Strategia parsera:
- Zweryfikuj MThd i długość=6; odczytaj format, tracks, division.
- Dla każdej MTrk: odczytuj delta‑time (VLQ), następnie event.
- Utrzymuj „last_status” dla running status; resetuj przy statusie 0x8n–0xEn odmiennym od poprzedniego; nie stosuj go do 0xF0/0xF7/0xFF.
- Buduj czas absolutny w tikach; przy PPQN zastosuj tempo‑mapę do przeliczeń na sekundy.
- Wymagaj FF 2F 00 i zgodności długości MTrk.
- Testy:
- Jednostkowo sprawdź dekoder VLQ na granicach (0x7F, 0x80, 0x2000).
- Weryfikuj, że suma długości zdarzeń równa się deklarowanej długości MTrk.
- Porównaj liczby Note On i Note Off dla każdej nuty/kanału (wykrywanie „wiszących” nut).
Ewentualne zastrzeżenia lub uwagi dodatkowe
- SMF nie definiuje brzmienia – jakość odtwarzania zależy od syntezatora/soundfontu (GM/GS/XG mogą się różnić).
- Nie wszystkie odtwarzacze poprawnie interpretują SMPTE division; jeśli to możliwe, używaj PPQN.
- Nadmierne poleganie na running status „przez” długie SysEx/Meta bywa źródłem niekompatybilności – jako autor pliku unikaj takich konstrukcji.
Sugestie dalszych badań
- Rozszerzenia producentów: GS (Roland), XG (Yamaha), GM2 – mapy instrumentów i SysEx.
- Kontrolery RPN/NRPN, modulacje MPE (MIDI Polyphonic Expression).
- MIDI 2.0 i UMP: reprezentacja zdarzeń 32‑/64‑bit, zgodność z MIDI 1.0.
- Budowa własnego parsera: automaty stanów dla strumienia MTrk, indeksowanie zdarzeń „tempo‑mapa + nuty”.
Krótkie podsumowanie
Plik .mid (SMF) to uporządkowany binarny kontener: nagłówek MThd definiuje format, liczbę ścieżek i podział czasu, a każda ścieżka MTrk zawiera chronologiczną sekwencję zdarzeń poprzedzonych delta‑time (VLQ). Zdarzenia obejmują komunikaty kanałowe MIDI, meta‑zdarzenia (tempo, metrum, nazwy) oraz SysEx. Kluczowe dla implementacji są poprawne dekodowanie VLQ, obsługa running status, zgodność długości chunków i big‑endian. Jeśli potrzebujesz, mogę przygotować przykładowy parser (np. w C lub Python) albo przeanalizować konkretny plik .mid krok po kroku.
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.
Zadaj dodatkowe pytanie Sztucznej Inteligencji