From: Nick Downing Date: Sun, 15 Sep 2024 03:14:04 +0000 (+1000) Subject: Implement full decoding, implement stable sorting routine to merge the tracks X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=HEAD;p=midi.git Implement full decoding, implement stable sorting routine to merge the tracks --- diff --git a/interleave.py b/interleave.py new file mode 100755 index 0000000..be99551 --- /dev/null +++ b/interleave.py @@ -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 --- 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(