From: Nick Downing Date: Sun, 15 Sep 2024 00:26:35 +0000 (+1000) Subject: Initial commit, basic parsing of MIDI files, doesn't parse event data yet X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=726d3fb588189594bafcb8724fd66108e4e36c9e;p=midi.git Initial commit, basic parsing of MIDI files, doesn't parse event data yet --- 726d3fb588189594bafcb8724fd66108e4e36c9e diff --git a/doc/Standard MIDI file format, updated.pdf b/doc/Standard MIDI file format, updated.pdf new file mode 100644 index 0000000..4aa0651 Binary files /dev/null and b/doc/Standard MIDI file format, updated.pdf differ diff --git a/midi.py b/midi.py new file mode 100755 index 0000000..c9ff0f0 --- /dev/null +++ b/midi.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +import struct +import sys + +CHANNEL_MESSAGE = [ + ('Note Off', 2), + ('Note On', 2), + ('Polyphonic Key Pressure', 2), + ('Control Change', 2), + ('Program Change', 1), + ('Channel Pressure', 1), + ('Pitch Wheel Change', 2), +] + +COMMON_MESSAGE = [ + ('System Exclusive', -1), + ('Undefined', 0), + ('Song Position Pointer', 2), + ('Song Select', 1), + ('Undefined', 0), + ('Undefined', 0), + ('Tune Request', 0), + ('End of Exclusive', -1), + ('Timing Clock', 0), + ('Undefined', 0), + ('Start', 0), + ('Continue', 0), + ('Stop', 0), + ('Undefined', 0), + ('Active Sensing', 0), + ('Meta Event', -1), +] + +META_EVENT = { + 0x00: 'Sequence Number', + 0x01: 'Text Event', + 0x02: 'Copyright Notice', + 0x03: 'Sequence/Track Name', + 0x04: 'Instrument Name', + 0x05: 'Lyric', + 0x06: 'Marker', + 0x07: 'Cue Point', + 0x20: 'MIDI Channel Prefix', + 0x2f: 'End of Track', + 0x51: 'Set Tempo', + 0x54: 'SMPTE Offset', + 0x58: 'Time Signature', + 0x59: 'Key Signature', + 0x7f: 'Sequencer Specific Meta-Event', +} + +with open( + sys.argv[1] if len(sys.argv) >= 2 else 'test/fur_Elise_WoO59.mid', + 'rb' +) as fin: + while True: + header = fin.read(8) + assert len(header) == 0 or len(header) == 8 + if len(header) == 0: + break + + chunk, length = struct.unpack('>II', header) + print('chunk', hex(chunk), 'length', hex(length)) + track = fin.read(length) + if chunk == 0x4d546864: # MThd + assert len(track) >= 6 + format, ntrks, division = struct.unpack('>HHH', track[:6]) + print( + 'format', + hex(format), + 'ntrks', + hex(ntrks), + 'division', + hex(division) + ) + assert format == 0 or format == 1 # tracks must be simultaneous + assert (division & 0x8000) == 0 # must be ticks per quarter note + elif chunk == 0x4d54726b: # MTrk + i = 0 + def variable(): + global i + v = 0 + while True: + b = track[i] + i += 1 + v = (v << 7) | (b & 0x7f) + if (b & 0x80) == 0: + break + return v + + s = 0 + ticks = 0 + while i < len(track): + ticks += variable() + if track[i] & 0x80: + s = track[i] + i += 1 + else: + assert s & 0x80 + + id = (s >> 4) & 7 # channel message ID (if < 7) + channel = s & 0xf # or common message ID (if id == 7) + if id < 7: + name, l = CHANNEL_MESSAGE[id] + d = track[i:i + l] + i += l + data = '[{0:s}]'.format(', '.join([f'0x{b:02x}' for b in d])) + print( + 'ticks', + hex(ticks), + 'channel', + hex(channel), + 'name', + f'"{name:s}"', + 'data', + data + ) + else: + name, l = COMMON_MESSAGE[channel] + if channel == 0xf: + t = track[i] + i += 1 + l = track[i] + i += 1 + d = track[i:i + l] + i += l + _type = META_EVENT.get(t, 'Unknown') + if t >= 1 and t < 0x10: + data = f'"{d.decode():s}"' + else: + data = '[{0:s}]'.format(', '.join([f'0x{b:02x}' for b in d])) + print( + 'ticks', + hex(ticks), + 'name', + f'"{name:s}"', + 'type', + f'"{_type:s}"', + 'data', + data + ) + else: + if channel == 0 or channel == 7: + l = track[i] + i += 1 + d = track[i:i + l] + i += l + data = '[{0:s}]'.format(', '.join([f'0x{b:02x}' for b in d])) + print( + 'ticks', + hex(ticks), + 'name', + f'"{name:s}"', + 'data', + data + ) + else: + print('unknown chunk', hex(chunk)) diff --git a/test/16739.mid b/test/16739.mid new file mode 100644 index 0000000..a6101f8 Binary files /dev/null and b/test/16739.mid differ diff --git a/test/28362.mid b/test/28362.mid new file mode 100644 index 0000000..7a160c1 Binary files /dev/null and b/test/28362.mid differ diff --git a/test/Bagatella Fur Elise.mid b/test/Bagatella Fur Elise.mid new file mode 100644 index 0000000..56a8438 Binary files /dev/null and b/test/Bagatella Fur Elise.mid differ diff --git a/test/Fur-Elise-1.mid b/test/Fur-Elise-1.mid new file mode 100644 index 0000000..889ab66 Binary files /dev/null and b/test/Fur-Elise-1.mid differ diff --git a/test/Fur-Elise-2.mid b/test/Fur-Elise-2.mid new file mode 100644 index 0000000..f4cde33 Binary files /dev/null and b/test/Fur-Elise-2.mid differ diff --git a/test/Fur-Elise-3.mid b/test/Fur-Elise-3.mid new file mode 100644 index 0000000..5c14541 Binary files /dev/null and b/test/Fur-Elise-3.mid differ diff --git a/test/Fur_Elise.mid b/test/Fur_Elise.mid new file mode 100644 index 0000000..7756823 Binary files /dev/null and b/test/Fur_Elise.mid differ diff --git a/test/Fur_Elise.mid.mid b/test/Fur_Elise.mid.mid new file mode 100644 index 0000000..41c7248 Binary files /dev/null and b/test/Fur_Elise.mid.mid differ diff --git a/test/for_elise_by_beethoven.mid b/test/for_elise_by_beethoven.mid new file mode 100644 index 0000000..b50146c Binary files /dev/null and b/test/for_elise_by_beethoven.mid differ diff --git a/test/fur-elise.mid b/test/fur-elise.mid new file mode 100644 index 0000000..0bb26f4 Binary files /dev/null and b/test/fur-elise.mid differ diff --git a/test/fur_Elise_WoO59-a4.pdf b/test/fur_Elise_WoO59-a4.pdf new file mode 100644 index 0000000..3e4d066 Binary files /dev/null and b/test/fur_Elise_WoO59-a4.pdf differ diff --git a/test/fur_Elise_WoO59.ly b/test/fur_Elise_WoO59.ly new file mode 100644 index 0000000..7b48b55 --- /dev/null +++ b/test/fur_Elise_WoO59.ly @@ -0,0 +1,203 @@ +\version "2.18.2" + + \header { + title = "Für Elise" + subtitle = "Clavierstuck in A Minor - WoO 59" + composer = "Ludwig van Beethoven" + mutopiatitle = "Für Elise" + mutopiacomposer = "BeethovenLv" + mutopiaopus = "WoO 59" + mutopiainstrument = "Piano" + date = "1810" + source = "Breitkopf & Härtel, 1888" + style = "Classical" + license = "Public Domain" + maintainer = "Stelios Samelis" + moreInfo = "keywords: fur elise, bagatelle no.25" + + footer = "Mutopia-2015/08/18-931" + copyright = \markup { \override #'(baseline-skip . 0 ) \right-column { \sans \bold \with-url #"http://www.MutopiaProject.org" { \abs-fontsize #9 "Mutopia " \concat { \abs-fontsize #12 \with-color #white \char ##x01C0 \abs-fontsize #9 "Project " } } } \override #'(baseline-skip . 0 ) \center-column { \abs-fontsize #11.9 \with-color #grey \bold { \char ##x01C0 \char ##x01C0 } } \override #'(baseline-skip . 0 ) \column { \abs-fontsize #8 \sans \concat { " Typeset using " \with-url #"http://www.lilypond.org" "LilyPond" " by " \maintainer " " \char ##x2014 " " \footer } \concat { \concat { \abs-fontsize #8 \sans{ " Placed in the " \with-url #"http://creativecommons.org/licenses/publicdomain" "public domain" " by the typesetter " \char ##x2014 " free to distribute, modify, and perform" } } \abs-fontsize #13 \with-color #white \char ##x01C0 } } } + tagline = ##f +} + +\paper { + top-margin = 8\mm + bottom-margin = 9\mm + top-system-spacing.basic-distance = #12 + ragged-last-bottom = ##f +} + +%----definitions +hideTupletNumber = \override TupletNumber.transparent = ##t +posPedal = \once \override SustainPedal.extra-offset = #'(0.4 . 0) +posDynTxt = \once \override DynamicText.extra-offset = #'(0.4 . 0) + +\score { + + \new PianoStaff + << + \new Staff = "up" { + \clef treble + \key a \minor + \time 3/8 + \override Score.MetronomeMark.transparent = ##t + \tempo 4 = 72 + \repeat volta 2 { + \partial 8 e''16\pp^\markup { \bold "Poco moto." } + dis'' e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' gis' b' + c''8 r16 e'_[ e'' dis''] e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b' } + \alternative { { a'4 } { a'8 \bar "" r16 b' \set Timing.measurePosition = #(ly:make-moment -1/8) c''16 d'' } + } + \repeat volta 2 { + e''8. g'16[ f'' e''] d''8. f'16[ e'' d''] c''8. e'16[ d'' c''] b'8 r16 e'_[ e''] r r e''[ e'''] r r dis'' + e''8 r16 dis'' e'' dis'' e''16 dis'' e'' b' d'' c'' + a'8 r16 c' e' a' b'8 r16 e' gis' b' c''8 r16 e'_[ e'' dis''] e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b'} + \alternative { { a'8 r16 b'[ c'' d''] } { a'8 r16 [ ] } } + + + \grace { f'16[ a'] } c''4 f''16. e''32 e''8([ d'']) bes''16. a''32 a''16( g'' f'' e'' d'' c'') + bes'8[ a'] \appoggiatura bes'32 a'32[ g' a' bes'] c''4 d''16[ dis''] e''8. e''16[ f'' a'] c''4 d''16. b'32 + c''32[ g'' g' g''] a'[ g'' b' g''] c''[ g'' d'' g''] e''[ g'' c''' b''] a''[ g'' f'' e''] d''[ g'' f'' d''] + c''32[ g'' g' g''] a'[ g'' b' g''] c''[ g'' d'' g''] e''[ g'' c''' b''] a''[ g'' f'' e''] d''[ g'' f'' d''] + e''32[ f'' e'' dis''] e''[ b' e'' dis''] e''[ b' e'' dis''] e''8. b'16[ e'' dis''] + e''8. b'16([ e'']) dis''( e'') dis''([ e'']) dis''([ e'']) dis''( e'') dis'' e'' b' d'' c'' + a'8 r16 c' e' a' b'8 r16 e' gis' b' c''8 r16 e'_[ e'' dis''] e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b' + a'8 r16 b'16 c'' d'' e''8. g'16[ f'' e''] d''8. f'16[ e'' d''] c''8. e'16[ d'' c''] b'8 r16 e'_[( e'']) r + r16 e''[( e''']) r r dis''( e'') r r dis''[ e'' dis''] e'' dis'' e'' b' d'' c'' + a'8 r16 c' e' a' b'8 r16 e' gis' b' c''8 r16 e' e'' dis'' e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b' + + a'8 r r 4. 4 16[ ] 4 8 4. + 4 16[ ] 4 8 8[ ] 4. + 4. 4 16[ ] 4 8 4. + 4 16[ ] 4 8 4 8 4 r8 8 r r + \tupletSpan 8 + \tuplet 3/2 { \posDynTxt a16\pp [ c' e'] a'[ c'' e''] d''[ c'' b'] \hideTupletNumber a'[ c'' e''] a''[ c''' e'''] d'''[ c''' b''] \ottava #1 \set Staff.ottavation = \markup {8} + a''[ c''' e'''] a'''[ c'''' e''''] d''''[ c'''' b'''] bes'''[ a''' gis'''] g''' [ \ottava #0 fis''' f'''] e'''[ dis''' d'''] + cis'''[ c''' b''] bes''[ a'' gis''] g''[ fis'' f''] } + + e''16 dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' gis' b' + c''8 r16 e'_[ e'' dis''] e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b' + a'8 r16 b'16 c'' d'' e''8. g'16[ f'' e''] d''8. f'16[ e'' d''] c''8. e'16[ d'' c''] b'8 r16 e'_[( e'']) r + r16 e''[( e''']) r r dis''( e'') r r dis''[ e'' dis''] e'' dis'' e'' b' d'' c'' + a'8 r16 c' e' a' b'8 r16 e' gis' b' c''8 r16 e'_[ e'' dis''] e'' dis'' e'' b' d'' c'' a'8 r16 c' e' a' b'8 r16 e' c'' b' + a'8 r \bar "|." +} + + \new Staff = "down" { + \clef bass + \key a \minor + \time 3/8 + \repeat volta 2 { + + \partial 8 r8\pp R4. a,16 e a r16 r8 e,16 e gis r r8 + a,16 e a r r8 R4. a,16 e a r r8 + e,16 e gis r r8 } + \alternative { { a,16 e a r } { a,16[ e \bar "" a16] r \set Timing.measurePosition = #(ly:make-moment -1/8) r8 } } + \repeat volta 2 { + c16 g c' r r8 g,16 g b r r8 + a,16 e a r r8 e,16 e e' r r \clef treble e'16_[( e'']) r r dis''[ e''] r r16 dis''[ e''] r r8 R4. + \clef bass a,16 e a r16 r8 e,16 e gis r r8 + a,16 e a r r8 R4. a,16 e a r r8 + e,16 e gis r r8 } + \alternative { { a,16 e a r r8 } { a,16[ e a] [ ] } } + + f16 a c' a c' a f bes d' bes d' bes f e' e' e' f a c' a c' a f a c' a c' a e a c' a f + g16 e' g e' g f' \clef treble 8 r16 [ ] 8 \clef bass 8[ ] + \clef treble c'8 r16 [ ] 8 \clef bass 8[ ] 8 r r R4. + R4. R4. R4. a,16 e a r16 r8 e,16 e gis r r8 a,16 e a r r8 + R4. a,16 e a r r8 e,16 e gis r r8 a,16 e a r r8 + c16 g c' r r8 g,16 g b r r8 a,16 e a r r8 e,16 e e' r r + \clef treble e'16(_[ e'']) r r dis''([ e'']) r r dis''([ e'']) r r8 R4. + \clef bass a,16 e a r16 r8 e,16 e gis r r8 a,16 e a r r8 R4. a,16 e a r r8 e,16 e gis r r8 + + a,16 a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, bes, + b,! b, b, b, b, b, c4 r8 8 r r + a,,8 r r r r r R4. + + R4. a,16 e a r r8 e,16 e gis r r8 + a,16 e a r r8 R4. + a,16 e a r r8 e,16 e gis r r8 a,16 e a r r8 + c16 g c' r r8 g,16 g b r r8 + a,16 e a r r8 e,16 e( e') r r + \clef treble e'16(_[ e'']) r r dis''([ e'']) r r dis''([ e'']) r r8 R4. + \clef bass a,16 e a r16 r8 e,16 e gis r r8 a,16 e a r r8 R4. + a,16 e a r r8 e,16 e gis r r8 8 r \bar "|." +} + +\new Dynamics = "pedalOne" { + \repeat volta 2 { + \partial 8 s8 + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + } + \alternative{ { s4 } { s4. } } + \repeat volta 2 { + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4.\sustainOn + s4. + s4 s16. s32\sustainOff + s4. + s4. + s4\sustainOn s16. s32\sustainOff + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + s4. + } + \alternative { { s4. } { s4. } } + \repeat unfold 17 { s4. } + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + s4. + s4. + s4.\sustainOn + s4 s16. s32\sustainOff + \repeat unfold 26 { s4. } + s4.\sustainOn + \repeat unfold 3 { s4. } + s4 s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + \posPedal s4.\sustainOn + s4 s16. s32\sustainOff + s4. + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4. + s4. + s4\sustainOn s16. s32\sustainOff + \posPedal s4\sustainOn s16. s32\sustainOff + s4 +} +>> + + \layout { } + + \midi { } + +} diff --git a/test/fur_Elise_WoO59.mid b/test/fur_Elise_WoO59.mid new file mode 100644 index 0000000..ac41827 Binary files /dev/null and b/test/fur_Elise_WoO59.mid differ