Implement full decoding, implement stable sorting routine to merge the tracks master
authorNick Downing <nick@ndcode.org>
Sun, 15 Sep 2024 03:14:04 +0000 (13:14 +1000)
committerNick Downing <nick@ndcode.org>
Sun, 15 Sep 2024 03:14:04 +0000 (13:14 +1000)
interleave.py [new file with mode: 0755]
midi.py

diff --git a/interleave.py b/interleave.py
new file mode 100755 (executable)
index 0000000..be99551
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# merge the tracks to create output sorted by ticks, track (otherwise stable)
+
+import re
+import sys
+
+re_chunk = re.compile('chunk 0x([0-9a-f]*) (.*)')
+re_ticks = re.compile('ticks 0x([0-9a-f]*) (.*)')
+
+items = []
+track = -1
+n = 0
+for line in sys.stdin:
+  line = line.rstrip()
+  match = re_chunk.match(line)
+  if match is not None:
+    chunk = int(match.group(1), 16)
+    if chunk == 0x4d54726b: # MTrk
+      track += 1
+      n = 0
+  else:
+    match = re_ticks.match(line)
+    if match is not None:
+      ticks = int(match.group(1), 16)
+      text = match.group(2)
+      items.append((ticks, track, n, text))
+    else:
+      print(line)
+  n += 1
+for ticks, track, _, text in sorted(items):
+  print('ticks', hex(ticks), 'track', hex(track), text)
diff --git a/midi.py b/midi.py
index c9ff0f0..a92e21c 100755 (executable)
--- a/midi.py
+++ b/midi.py
@@ -50,6 +50,311 @@ META_EVENT = {
   0x7f: 'Sequencer Specific Meta-Event',
 }
 
+KEY_SIGNATURE_SF = {
+  0xf9: '7 flats',
+  0xfa: '6 flats',
+  0xfb: '5 flats',
+  0xfc: '4 flats',
+  0xfd: '3 flats',
+  0xfe: '2 flats',
+  0xff: '1 flat',
+  0: 'C',
+  1: '1 sharp',
+  2: '2 sharps',
+  3: '3 sharps',
+  4: '4 sharps',
+  5: '5 sharps',
+  6: '6 sharps',
+  7: '7 sharps',
+}
+KEY_SIGNATURE_MI = {
+  0: 'major',
+  1: 'minor',
+}
+
+NOTE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
+
+CONTROL = [
+  'Bank Select MSB', # 00 0-127
+  'Modulation wheel MSB', # 01 * 0-127
+  'Breath control MSB', # 02 0-127
+  'Undefined MSB', # 03 0-127
+  'Foot controller MSB', # 04 0-127
+  'Portamento time MSB', # 05 0-127
+  'Data Entry MSB', # 06 0-127
+  'Channel Volume MSB', # 07 * 0-127 (formerly Main Volume)
+  'Balance MSB', # 08 0-127
+  'Undefined MSB', # 09 0-127
+  'Pan MSB', # 0A * 0-127
+  'Expression Controller MSB', # 0B * 0-127
+  'Effect control 1 MSB', # 0C 0-127
+  'Effect control 2 MSB', # 0D 0-127
+  'Undefined MSB', # 0E 0-127
+  'Undefined MSB', # 0F 0-127
+  'General Purpose Controller #1 MSB', # 10 0-127
+  'General Purpose Controller #2 MSB', # 11 0-127
+  'General Purpose Controller #3 MSB', # 12 0-127
+  'General Purpose Controller #4 MSB', # 13 0-127
+  'Undefined MSB', # 14 0-127
+  'Undefined MSB', # 15 0-127
+  'Undefined MSB', # 16 0-127
+  'Undefined MSB', # 17 0-127
+  'Undefined MSB', # 18 0-127
+  'Undefined MSB', # 19 0-127
+  'Undefined MSB', # 1A 0-127
+  'Undefined MSB', # 1B 0-127
+  'Undefined MSB', # 1C 0-127
+  'Undefined MSB', # 1D 0-127
+  'Undefined MSB', # 1E 0-127
+  'Undefined MSB', # 1F 0-127
+  'Bank Select LSB', # 20 0-127
+  'Modulation wheel LSB', # 21 0-127
+  'Breath control LSB', # 22 0-127
+  'Undefined LSB', # 23 0-127
+  'Foot controller LSB', # 24 0-127
+  'Portamento time LSB', # 25 0-127
+  'Data entry LSB', # 26 0-127
+  'Channel Volume LSB', # 27 0-127 (formerly Main Volume)
+  'Balance LSB', # 28 0-127
+  'Undefined LSB', # 29 0-127
+  'Pan LSB', # 2A 0-127
+  'Expression Controller LSB', # 2B 0-127
+  'Effect control 1 LSB', # 2C 0-127
+  'Effect control 2 LSB', # 2D 0-127
+  'Undefined LSB', # 2E 0-127
+  'Undefined LSB', # 2F 0-127
+  'General Purpose Controller #1 LSB', # 30 0-127
+  'General Purpose Controller #2 LSB', # 31 0-127
+  'General Purpose Controller #3 LSB', # 32 0-127
+  'General Purpose Controller #4 LSB', # 33 0-127
+  'Undefined LSB', # 34 0-127
+  'Undefined LSB', # 35 0-127
+  'Undefined LSB', # 36 0-127
+  'Undefined LSB', # 37 0-127
+  'Undefined LSB', # 38 0-127
+  'Undefined LSB', # 39 0-127
+  'Undefined LSB', # 3A 0-127
+  'Undefined LSB', # 3B 0-127
+  'Undefined LSB', # 3C 0-127
+  'Undefined LSB', # 3D 0-127
+  'Undefined LSB', # 3E 0-127
+  'Undefined LSB', # 3F 0-127
+  'Damper pedal on/off (Sustain)', # 40 * <63=off >64=on
+  'Portamento on/off', # 41 <63=off >64=on
+  'Sustenuto on/off', # 42 <63=off >64=on
+  'Soft pedal on/off', # 43 <63=off >64=on
+  'Legato Footswitch', # 44 <63=off >64=on
+  'Hold 2', # 45 <63=off >64=on
+  'Sound Controller 1 (Sound Variation) LSB', # 46 0-127
+  'Sound Controller 2 (Timbre) LSB', # 47 0-127
+  'Sound Controller 3 (Release Time) LSB', # 48 0-127
+  'Sound Controller 4 (Attack Time) LSB', # 49 0-127
+  'Sound Controller 5 (Brightness) LSB', # 4A 0-127
+  'Sound Controller 6 LSB', # 4B 0-127
+  'Sound Controller 7 LSB', # 4C 0-127
+  'Sound Controller 8 LSB', # 4D 0-127
+  'Sound Controller 9 LSB', # 4E 0-127
+  'Sound Controller 10 LSB', # 4F 0-127
+  'General Purpose Controller #5 LSB', # 50 0-127
+  'General Purpose Controller #6 LSB', # 51 0-127
+  'General Purpose Controller #7 LSB', # 52 0-127
+  'General Purpose Controller #8 LSB', # 53 0-127
+  'Portamento Control', # 54 0-127 Source Note
+  'Undefined LSB', # 55 0-127
+  'Undefined LSB', # 56 0-127
+  'Undefined LSB', # 57 0-127
+  'Undefined LSB', # 58 0-127
+  'Undefined LSB', # 59 0-127
+  'Undefined LSB', # 5A 0-127
+  'Effects 1 Depth LSB', # 5B 0-127
+  'Effects 2 Depth LSB', # 5C 0-127
+  'Effects 3 Depth LSB', # 5D 0-127
+  'Effects 4 Depth LSB', # 5E 0-127
+  'Effects 5 Depth LSB', # 5F 0-127
+  'Data entry +1', # 60 N/A  
+  'Data entry -1', # 61 N/A  
+  'Non-Registered Parameter Number LSB', # 62 0-127
+  'Non-Registered Parameter Number MSB', # 63 0-127
+  'Registered Parameter Number LSB', # 64 * 0-127
+  'Registered Parameter Number MSB', # 65 * 0-127
+  'Undefined', # 66 ?  
+  'Undefined', # 67 ?  
+  'Undefined', # 68 ?  
+  'Undefined', # 69 ?  
+  'Undefined', # 6A ?  
+  'Undefined', # 6B ?  
+  'Undefined', # 6C ?  
+  'Undefined', # 6D ?  
+  'Undefined', # 6E ?  
+  'Undefined', # 6F ?  
+  'Undefined', # 70 ?  
+  'Undefined', # 71 ?  
+  'Undefined', # 72 ?  
+  'Undefined', # 73 ?  
+  'Undefined', # 74 ?  
+  'Undefined', # 75 ?  
+  'Undefined', # 76 ?  
+  'Undefined', # 77 ?  
+  'All Sound Off', # 78 0  
+  'Reset All Controllers', # 79 * 0  
+  'Local control on/off', # 7A 0=off 127=on
+  'All notes off', # 7B * 0  
+  'Omni mode off (+ all notes off)', # 7C 0  
+  'Omni mode on (+ all notes off)', # 7D 0  
+  'Poly mode on/off (+ all notes off)', # 7E **  
+  'Poly mode on (incl mono=off +all notes off)', # 7F 0  
+]
+
+INSTRUMENT_FAMILY = [
+  'Piano', # 1-8
+  'Chromatic Percussion', # 9-16
+  'Organ', # 17-24
+  'Guitar', # 25-32
+  'Bass', # 33-40
+  'Strings', # 41-48
+  'Ensemble', # 49-56
+  'Brass', # 57-64
+  'Reed', # 65-72
+  'Pipe', # 73-80
+  'Synth Lead', # 81-88
+  'Synth Pad', # 89-96
+  'Synth Effects', # 97-104
+  'Ethnic', # 105-112
+  'Percussive', # 113-120
+  'Sound Effects', # 121-128
+]
+
+INSTRUMENT = [
+  'Acoustic Grand Piano', # 1
+  'Bright Acoustic Piano', # 2
+  'Electric Grand Piano', # 3
+  'Honky-tonk Piano', # 4
+  'Electric Piano 1 (Rhodes Piano)', # 5
+  'Electric Piano 2 (Chorused Piano)', # 6
+  'Harpsichord', # 7
+  'Clavinet', # 8
+  'Celesta', # 9
+  'Glockenspiel', # 10
+  'Music Box', # 11
+  'Vibraphone', # 12
+  'Marimba', # 13
+  'Xylophone', # 14
+  'Tubular Bells', # 15
+  'Dulcimer (Santur)', # 16
+  'Drawbar Organ (Hammond)', # 17
+  'Percussive Organ', # 18
+  'Rock Organ', # 19
+  'Church Organ', # 20
+  'Reed Organ', # 21
+  'Accordion (French)', # 22
+  'Harmonica', # 23
+  'Tango Accordion (Band neon)', # 24
+  'Acoustic Guitar (nylon)', # 25
+  'Acoustic Guitar (steel)', # 26
+  'Electric Guitar (jazz)', # 27
+  'Electric Guitar (clean)', # 28
+  'Electric Guitar (muted)', # 29
+  'Overdriven Guitar', # 30
+  'Distortion Guitar', # 31
+  'Guitar harmonics', # 32
+  'Acoustic Bass', # 33
+  'Electric Bass (fingered)', # 34
+  'Electric Bass (picked)', # 35
+  'Fretless Bass', # 36
+  'Slap Bass 1', # 37
+  'Slap Bass 2', # 38
+  'Synth Bass 1', # 39
+  'Synth Bass 2', # 40
+  'Violin', # 41
+  'Viola', # 42
+  'Cello', # 43
+  'Contrabass', # 44
+  'Tremolo Strings', # 45
+  'Pizzicato Strings', # 46
+  'Orchestral Harp', # 47
+  'Timpani', # 48
+  'String Ensemble 1 (strings)', # 49
+  'String Ensemble 2 (slow strings)', # 50
+  'SynthStrings 1', # 51
+  'SynthStrings 2', # 52
+  'Choir Aahs', # 53
+  'Voice Oohs', # 54
+  'Synth Voice', # 55
+  'Orchestra Hit', # 56
+  'Trumpet', # 57
+  'Trombone', # 58
+  'Tuba', # 59
+  'Muted Trumpet', # 60
+  'French Horn', # 61
+  'Brass Section', # 62
+  'SynthBrass 1', # 63
+  'SynthBrass 2', # 64
+  'Soprano Sax', # 65
+  'Alto Sax', # 66
+  'Tenor Sax', # 67
+  'Baritone Sax', # 68
+  'Oboe', # 69
+  'English Horn', # 70
+  'Bassoon', # 71
+  'Clarinet', # 72
+  'Piccolo', # 73
+  'Flute', # 74
+  'Recorder', # 75
+  'Pan Flute', # 76
+  'Blown Bottle', # 77
+  'Shakuhachi', # 78
+  'Whistle', # 79
+  'Ocarina', # 80
+  'Lead 1 (square wave)', # 81
+  'Lead 2 (sawtooth wave)', # 82
+  'Lead 3 (calliope)', # 83
+  'Lead 4 (chiffer)', # 84
+  'Lead 5 (charang)', # 85
+  'Lead 6 (voice solo)', # 86
+  'Lead 7 (fifths)', # 87
+  'Lead 8 (bass + lead)', # 88
+  'Pad 1 (new age Fantasia)', # 89
+  'Pad 2 (warm)', # 90
+  'Pad 3 (polysynth)', # 91
+  'Pad 4 (choir space voice)', # 92
+  'Pad 5 (bowed glass)', # 93
+  'Pad 6 (metallic pro)', # 94
+  'Pad 7 (halo)', # 95
+  'Pad 8 (sweep)', # 96
+  'FX 1 (rain)', # 97
+  'FX 2 (soundtrack)', # 98
+  'FX 3 (crystal)', # 99
+  'FX 4 (atmosphere)', # 100
+  'FX 5 (brightness)', # 101
+  'FX 6 (goblins)', # 102
+  'FX 7 (echoes, drops)', # 103
+  'FX 8 (sci-fi, star theme)', # 104
+  'Sitar', # 105
+  'Banjo', # 106
+  'Shamisen', # 107
+  'Koto', # 108
+  'Kalimba', # 109
+  'Bag pipe', # 110
+  'Fiddle', # 111
+  'Shanai', # 112
+  'Tinkle Bell', # 113
+  'Agogo', # 114
+  'Steel Drums', # 115
+  'Woodblock', # 116
+  'Taiko Drum', # 117
+  'Melodic Tom', # 118
+  'Synth Drum', # 119
+  'Reverse Cymbal', # 120
+  'Guitar Fret Noise', # 121
+  'Breath Noise', # 122
+  'Seashore', # 123
+  'Bird Tweet', # 124
+  'Telephone Ring', # 125
+  'Helicopter', # 126
+  'Applause', # 127
+  'Gunshot', # 128
+]
+
 with open(
   sys.argv[1] if len(sys.argv) >= 2 else 'test/fur_Elise_WoO59.mid',
   'rb'
@@ -105,7 +410,25 @@ with open(
           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]))
+          if id < 3: # note on/off, polyphonic key pressure
+            k = d[0]
+            v = d[1]
+            data = f'{NOTE[k % 12]:s}{k // 12 - 1:d},{v / 127.:.3f}'
+          elif id == 3: # control change
+            c = d[0]
+            v = d[1]
+            data = f'"{CONTROL[c]:s}",0x{v:02x}'
+          elif id == 4: # program change
+            p = d[0]
+            data = f'"{INSTRUMENT_FAMILY[p >> 3]:s}","{INSTRUMENT[p]:s}"'
+          elif id == 5: # channel pressure
+            v = d[0]
+            data = f'{v / 127.:.3f}'
+          elif id == 6: # channel pressure
+            v = (d[0] | (d[1] << 7)) - 0x2000
+            data = hex(v)
+          else:
+            assert False #data = '[{0:s}]'.format(', '.join([f'0x{b:02x}' for b in d]))
           print(
             'ticks',
             hex(ticks),
@@ -127,7 +450,26 @@ with open(
             i += l
             _type = META_EVENT.get(t, 'Unknown')
             if t >= 1 and t < 0x10:
-              data = f'"{d.decode():s}"'
+              data = '"{0:s}"'.format(d.decode('iso-8859-1'))
+            elif t == 0x51: # set tempo
+              assert l == 3
+              tempo = (d[0] << 16) | (d[1] << 8) | d[2]
+              data = str(tempo) # usecs per MIDI quarter-note
+            elif t == 0x58: # time signature
+              assert l == 4
+              nn = d[0]
+              dd = d[1]
+              cc = d[2]
+              bb = d[3]
+              data = f'{nn:d}/{1 << dd:d},{cc:d},{bb:d}'
+            elif t == 0x59: # key signature
+              assert l == 2
+              sf = d[0]
+              mi = d[1]
+              data = '{0:s},{1:s}'.format(
+                KEY_SIGNATURE_SF.get(sf, 'unknown'),
+                KEY_SIGNATURE_MI.get(mi, 'unknown'),
+              )
             else:
               data = '[{0:s}]'.format(', '.join([f'0x{b:02x}' for b in d]))
             print(