From: Chris Dreher Date: Fri, 19 Aug 2016 17:26:02 +0000 (-0700) Subject: midi: Substantially improve decoding of MIDI messages. X-Git-Tag: libsigrokdecode-0.5.0~156 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=b0fc934add3394f7e753db6bcac23a2362bf7149;hp=d66d47eda43be865ca6775fe4f02cdd0824b6abb midi: Substantially improve decoding of MIDI messages. * Decode note names and percussion names (ex: 'G2', 'Tambourine'). * Decode instrument names and drum_kit names (ex: 'Flute', 'GS Orchestra Kit'). * Handle Polyphonic Pressure / Aftertouch (message 0xAn). * Handle Program Change (message 0xCn). * Handle Channel Pressure / Aftertouch (message 0xDn). * Handle Channel Mode (message 0xBn mm where mm is 120 through 127). * Handle System Common messages (message 0xF1 through 0xF6), including full time code decoding. * SysEx decoding now decodes the 1-3 byte manufacturer field, payload is now displayed as hex. * 'undefined' fields now display the value (ex: 'undefined 0xf4'). * Add 'MSB' and 'LSB' to many control_functions entries. * Fix "trapped in state X" bug with handle_channel_msg_generic(), though this might be dead code. * Fix bug in sysex_manufacturer_ids; 1-byte manufacturers were not tuples due to missing comma. * Fix bug in SysEx decoding state machine; 0xF7 now sent to handle_sysex_msg(). --- diff --git a/decoders/midi/lists.py b/decoders/midi/lists.py index c72f5c9..dc728a9 100644 --- a/decoders/midi/lists.py +++ b/decoders/midi/lists.py @@ -38,18 +38,18 @@ status_bytes = { 0xf1: 'MIDI time code quarter frame', 0xf2: 'song position pointer', 0xf3: 'song select', - 0xf4: 'undefined', - 0xf5: 'undefined', + 0xf4: 'undefined 0xf4', + 0xf5: 'undefined 0xf5', 0xf6: 'tune request', 0xf7: 'end of system exclusive (EOX)', # System real time messages 0xf8: 'timing clock', - 0xf9: 'undefined', + 0xf9: 'undefined 0xf9', 0xfa: 'start', 0xfb: 'continue', 0xfc: 'stop', - 0xfd: 'undefined', + 0xfd: 'undefined 0xfd', 0xfe: 'active sensing', 0xff: 'system reset', } @@ -156,37 +156,37 @@ universal_sysex_realtime = { # Note: Not all IDs are used/listed, i.e. there are some "holes". sysex_manufacturer_ids = { - # American group - (0x01): 'Sequential', - (0x02): 'IDP', - (0x03): 'Voyetra/Octave-Plateau', - (0x04): 'Moog', - (0x05): 'Passport Designs', - (0x06): 'Lexicon', - (0x07): 'Kurzweil', - (0x08): 'Fender', - (0x09): 'Gulbransen', - (0x0a): 'AKG Acoustics', - (0x0b): 'Voyce Music', - (0x0c): 'Waveframe Corp', - (0x0d): 'ADA Signal Processors', - (0x0e): 'Garfield Electronics', - (0x0f): 'Ensoniq', - (0x10): 'Oberheim', - (0x11): 'Apple Computer', - (0x12): 'Grey Matter Response', - (0x13): 'Digidesign', - (0x14): 'Palm Tree Instruments', - (0x15): 'JLCooper Electronics', - (0x16): 'Lowrey', - (0x17): 'Adams-Smith', - (0x18): 'Emu Systems', - (0x19): 'Harmony Systems', - (0x1a): 'ART', - (0x1b): 'Baldwin', - (0x1c): 'Eventide', - (0x1d): 'Inventronics', - (0x1f): 'Clarity', + # American group (range 01-1f, 000001-001f7f) + (0x01,): 'Sequential', + (0x02,): 'IDP', + (0x03,): 'Voyetra/Octave-Plateau', + (0x04,): 'Moog', + (0x05,): 'Passport Designs', + (0x06,): 'Lexicon', + (0x07,): 'Kurzweil', + (0x08,): 'Fender', + (0x09,): 'Gulbransen', + (0x0a,): 'AKG Acoustics', + (0x0b,): 'Voyce Music', + (0x0c,): 'Waveframe Corp', + (0x0d,): 'ADA Signal Processors', + (0x0e,): 'Garfield Electronics', + (0x0f,): 'Ensoniq', + (0x10,): 'Oberheim', + (0x11,): 'Apple Computer', + (0x12,): 'Grey Matter Response', + (0x13,): 'Digidesign', + (0x14,): 'Palm Tree Instruments', + (0x15,): 'JLCooper Electronics', + (0x16,): 'Lowrey', + (0x17,): 'Adams-Smith', + (0x18,): 'Emu Systems', + (0x19,): 'Harmony Systems', + (0x1a,): 'ART', + (0x1b,): 'Baldwin', + (0x1c,): 'Eventide', + (0x1d,): 'Inventronics', + (0x1f,): 'Clarity', (0x00, 0x00, 0x01): 'Time Warner Interactive', (0x00, 0x00, 0x07): 'Digital Music Corp.', @@ -306,32 +306,32 @@ sysex_manufacturer_ids = { (0x00, 0x01, 0x02): 'Crystal Semiconductor', (0x00, 0x01, 0x03): 'Rockwell Semiconductor', - # European group - (0x20): 'Passac', - (0x21): 'SIEL', - (0x22): 'Synthaxe', - (0x24): 'Hohner', - (0x25): 'Twister', - (0x26): 'Solton', - (0x27): 'Jellinghaus MS', - (0x28): 'Southworth Music Systems', - (0x29): 'PPG', - (0x2a): 'JEN', - (0x2b): 'SSL Limited', - (0x2c): 'Audio Veritrieb', - (0x2f): 'Elka', - - (0x30): 'Dynacord', - (0x31): 'Viscount', - (0x33): 'Clavia Digital Instruments', - (0x34): 'Audio Architecture', - (0x35): 'GeneralMusic Corp.', - (0x39): 'Soundcraft Electronics', - (0x3b): 'Wersi', - (0x3c): 'Avab Elektronik Ab', - (0x3d): 'Digigram', - (0x3e): 'Waldorf Electronics', - (0x3f): 'Quasimidi', + # European group (range 20-3f, 002000-003f7f) + (0x20,): 'Passac', + (0x21,): 'SIEL', + (0x22,): 'Synthaxe', + (0x24,): 'Hohner', + (0x25,): 'Twister', + (0x26,): 'Solton', + (0x27,): 'Jellinghaus MS', + (0x28,): 'Southworth Music Systems', + (0x29,): 'PPG', + (0x2a,): 'JEN', + (0x2b,): 'SSL Limited', + (0x2c,): 'Audio Veritrieb', + (0x2f,): 'Elka', + + (0x30,): 'Dynacord', + (0x31,): 'Viscount', + (0x33,): 'Clavia Digital Instruments', + (0x34,): 'Audio Architecture', + (0x35,): 'GeneralMusic Corp.', + (0x39,): 'Soundcraft Electronics', + (0x3b,): 'Wersi', + (0x3c,): 'Avab Elektronik Ab', + (0x3d,): 'Digigram', + (0x3e,): 'Waldorf Electronics', + (0x3f,): 'Quasimidi', (0x00, 0x20, 0x00): 'Dream', (0x00, 0x20, 0x01): 'Strand Lighting', @@ -378,51 +378,77 @@ sysex_manufacturer_ids = { (0x00, 0x20, 0x2d): 'Blue Chip Music Tech', (0x00, 0x20, 0x2e): 'BEE OH Corp', - # Japanese group - (0x40): 'Kawai', - (0x41): 'Roland', - (0x42): 'Korg', - (0x43): 'Yamaha', - (0x44): 'Casio', - (0x46): 'Kamiya Studio', - (0x47): 'Akai', - (0x48): 'Japan Victor', - (0x49): 'Mesosha', - (0x4a): 'Hoshino Gakki', - (0x4b): 'Fujitsu Elect', - (0x4c): 'Sony', - (0x4d): 'Nisshin Onpa', - (0x4e): 'TEAC', - (0x50): 'Matsushita Electric', - (0x51): 'Fostex', - (0x52): 'Zoom', - (0x53): 'Midori Electronics', - (0x54): 'Matsushita Communication Industrial', - (0x55): 'Suzuki Musical Inst. Mfg.', + # Japanese group (range 40-5f, 004000-005f7f) + (0x40,): 'Kawai', + (0x41,): 'Roland', + (0x42,): 'Korg', + (0x43,): 'Yamaha', + (0x44,): 'Casio', + (0x46,): 'Kamiya Studio', + (0x47,): 'Akai', + (0x48,): 'Japan Victor', + (0x49,): 'Mesosha', + (0x4a,): 'Hoshino Gakki', + (0x4b,): 'Fujitsu Elect', + (0x4c,): 'Sony', + (0x4d,): 'Nisshin Onpa', + (0x4e,): 'TEAC', + (0x50,): 'Matsushita Electric', + (0x51,): 'Fostex', + (0x52,): 'Zoom', + (0x53,): 'Midori Electronics', + (0x54,): 'Matsushita Communication Industrial', + (0x55,): 'Suzuki Musical Inst. Mfg.', + + # Other (range 60-7c, 006000-007f7f) + + # Special (7d-7f) + (0x7d,): 'Non-Commercial', + (0x7e,): 'Universal Non-Realtime', + (0x7f,): 'Universal Realtime', } control_functions = { - 0x00: 'bank select', - 0x01: 'modulation wheel/lever', - 0x02: 'breath controller', - # 0x03: undefined - 0x04: 'foot controller', - 0x05: 'portamento time', + 0x00: 'bank select MSB', + 0x01: 'modulation wheel/lever MSB', + 0x02: 'breath controller MSB', + # 0x03: undefined MSB + 0x04: 'foot controller MSB', + 0x05: 'portamento time MSB', 0x06: 'data entry MSB', - 0x07: 'channel volume (formerly main volume)', - 0x08: 'balance', - # 0x09: undefined - 0x0a: 'pan', - 0x0b: 'expression controller', - 0x0c: 'effect control 1', - 0x0d: 'effect control 2', - # 0x0e-0x0f: undefined - 0x10: 'general purpose controller 1', - 0x11: 'general purpose controller 2', - 0x12: 'general purpose controller 3', - 0x13: 'general purpose controller 4', - # 0x14-0x1f: undefined - # 0x20-0x3f: LSB for values 0x00-0x1f + 0x07: 'channel volume MSB (formerly main volume)', + 0x08: 'balance MSB', + # 0x09: undefined MSB + 0x0a: 'pan MSB', + 0x0b: 'expression controller MSB', + 0x0c: 'effect control 1 MSB', + 0x0d: 'effect control 2 MSB', + # 0x0e-0x0f: undefined MSB + 0x10: 'general purpose controller 1 MSB', + 0x11: 'general purpose controller 2 MSB', + 0x12: 'general purpose controller 3 MSB', + 0x13: 'general purpose controller 4 MSB', + # 0x14-0x1f: undefined MSB + 0x20: 'bank select LSB', + 0x21: 'modulation wheel/lever LSB', + 0x22: 'breath controller LSB', + # 0x23: undefined LSB + 0x24: 'foot controller LSB', + 0x25: 'portamento time LSB', + 0x26: 'data entry LSB', + 0x27: 'channel volume LSB (formerly main volume)', + 0x28: 'balance LSB', + # 0x29: undefined LSB + 0x2a: 'pan LSB', + 0x2b: 'expression controller LSB', + 0x2c: 'effect control 1 LSB', + 0x2d: 'effect control 2 LSB', + # 0x2e-0x2f: undefined LSB + 0x30: 'general purpose controller 1 LSB', + 0x31: 'general purpose controller 2 LSB', + 0x32: 'general purpose controller 3 LSB', + 0x33: 'general purpose controller 4 LSB', + # 0x34-0x3f: undefined LSB 0x40: 'damper pedal (sustain)', 0x41: 'portamento on/off', 0x42: 'sostenuto', @@ -452,18 +478,361 @@ control_functions = { 0x5f: 'effects 5 depth (formerly phaser depth)', 0x60: 'data increment', 0x61: 'data decrement', - 0x62: 'non-registered parameter number LSB', - 0x63: 'non-registered parameter number MSB', - 0x64: 'registered parameter number LSB', - 0x65: 'registered parameter number MSB', + 0x62: 'Non-Registered Parameter Number LSB', + 0x63: 'Non-Registered Parameter Number MSB', + 0x64: 'Registered Parameter Number LSB', + 0x65: 'Registered Parameter Number MSB', # 0x66-0x77: undefined # 0x78-0x7f: reserved for channel mode messages 0x78: 'all sound off', 0x79: 'reset all controllers', - 0x7a: 'local control on/off', + 0x7a: 'local control', 0x7b: 'all notes off', 0x7c: 'omni mode off', # all notes off 0x7d: 'omni mode on', # all notes off - 0x7e: 'poly mode off', # mono mode on, all notes off + 0x7e: 'mono mode on', # mono mode on, all notes off 0x7f: 'poly mode on', # mono mode off, all notes off } + +gm_instruments = { + 1: 'Acoustic Grand Piano', + 2: 'Bright Acoustic Piano', + 3: 'Electric Grand Piano', + 4: 'Honky-tonk Piano', + 5: 'Electric Piano 1', + 6: 'Electric Piano 2', + 7: 'Harpsichord', + 8: 'Clavi', + 9: 'Celesta', + 10: 'Glockenspiel', + 11: 'Music Box', + 12: 'Vibraphone', + 13: 'Marimba', + 14: 'Xylophone', + 15: 'Tubular Bells', + 16: 'Dulcimer', + 17: 'Drawbar Organ', + 18: 'Percussive Organ', + 19: 'Rock Organ', + 20: 'Church Organ', + 21: 'Reed Organ', + 22: 'Accordion', + 23: 'Harmonica', + 24: 'Tango Accordion', + 25: 'Acoustic Guitar (nylon)', + 26: 'Acoustic Guitar (steel)', + 27: 'Electric Guitar (jazz)', + 28: 'Electric Guitar (clean)', + 29: 'Electric Guitar (muted)', + 30: 'Overdriven Guitar', + 31: 'Distortion Guitar', + 32: 'Guitar harmonics', + 33: 'Acoustic Bass', + 34: 'Electric Bass (finger)', + 35: 'Electric Bass (pick)', + 36: 'Fretless Bass', + 37: 'Slap Bass 1', + 38: 'Slap Bass 2', + 39: 'Synth Bass 1', + 40: 'Synth Bass 2', + 41: 'Violin', + 42: 'Viola', + 43: 'Cello', + 44: 'Contrabass', + 45: 'Tremolo Strings', + 46: 'Pizzicato Strings', + 47: 'Orchestral Harp', + 48: 'Timpani', + 49: 'String Ensemble 1', + 50: 'String Ensemble 2', + 51: 'SynthStrings 1', + 52: 'SynthStrings 2', + 53: 'Choir Aahs', + 54: 'Voice Oohs', + 55: 'Synth Voice', + 56: 'Orchestra Hit', + 57: 'Trumpet', + 58: 'Trombone', + 59: 'Tuba', + 60: 'Muted Trumpet', + 61: 'French Horn', + 62: 'Brass Section', + 63: 'SynthBrass 1', + 64: 'SynthBrass 2', + 65: 'Soprano Sax', + 66: 'Alto Sax', + 67: 'Tenor Sax', + 68: 'Baritone Sax', + 69: 'Oboe', + 70: 'English Horn', + 71: 'Bassoon', + 72: 'Clarinet', + 73: 'Piccolo', + 74: 'Flute', + 75: 'Recorder', + 76: 'Pan Flute', + 77: 'Blown Bottle', + 78: 'Shakuhachi', + 79: 'Whistle', + 80: 'Ocarina', + 81: 'Lead 1 (square)', + 82: 'Lead 2 (sawtooth)', + 83: 'Lead 3 (calliope)', + 84: 'Lead 4 (chiff)', + 85: 'Lead 5 (charang)', + 86: 'Lead 6 (voice)', + 87: 'Lead 7 (fifths)', + 88: 'Lead 8 (bass + lead)', + 89: 'Pad 1 (new age)', + 90: 'Pad 2 (warm)', + 91: 'Pad 3 (polysynth)', + 92: 'Pad 4 (choir)', + 93: 'Pad 5 (bowed)', + 94: 'Pad 6 (metallic)', + 95: 'Pad 7 (halo)', + 96: 'Pad 8 (sweep)', + 97: 'FX 1 (rain)', + 98: 'FX 2 (soundtrack)', + 99: 'FX 3 (crystal)', + 100: 'FX 4 (atmosphere)', + 101: 'FX 5 (brightness)', + 102: 'FX 6 (goblins)', + 103: 'FX 7 (echoes)', + 104: 'FX 8 (sci-fi)', + 105: 'Sitar', + 106: 'Banjo', + 107: 'Shamisen', + 108: 'Koto', + 109: 'Kalimba', + 110: 'Bag pipe', + 111: 'Fiddle', + 112: 'Shanai', + 113: 'Tinkle Bell', + 114: 'Agogo', + 115: 'Steel Drums', + 116: 'Woodblock', + 117: 'Taiko Drum', + 118: 'Melodic Tom', + 119: 'Synth Drum', + 120: 'Reverse Cymbal', + 121: 'Guitar Fret Noise', + 122: 'Breath Noise', + 123: 'Seashore', + 124: 'Bird Tweet', + 125: 'Telephone Ring', + 126: 'Helicopter', + 127: 'Applause', + 128: 'Gunshot', +} + +drum_kit = { + 1: 'GM Standard Kit', + 9: 'GS Room Kit', + 17: 'GS Power Kit', + 25: 'GS Power Kit', + 26: 'GS TR-808 Kit', + 33: 'GS Jazz Kit', + 41: 'GS Brush Kit', + 49: 'GS Orchestra Kit', + 57: 'GS Sound FX Kit', + 128: 'GS CM-64/CM-32 Kit', +} + +quarter_frame_type = { + 0: 'frame count LS nibble', + 1: 'frame count MS nibble', + 2: 'seconds count LS nibble', + 3: 'seconds count MS nibble', + 4: 'minutes count LS nibble', + 5: 'minutes count MS nibble', + 6: 'hours count LS nibble', + 7: 'hours count MS nibble and SMPTE type', +} + +smpte_type = { + 0: '24 frames/second', + 1: '25 frames/second', + 2: '30 frames/second (drop-frame)', + 3: '30 frames/second (non-drop)', +} + +chromatic_notes = { + 0: 'C-2', + 1: 'C#-2', + 2: 'D-2', + 3: 'D#-2', + 4: 'E-2', + 5: 'F-2', + 6: 'F#-2', + 7: 'G-2', + 8: 'G#-2', + 9: 'A-2', + 10: 'A#-2', + 11: 'B-2', + 12: 'C-1', + 13: 'C#-1', + 14: 'D-1', + 15: 'D#-1', + 16: 'E-1', + 17: 'F-1', + 18: 'F#-1', + 19: 'G-1', + 20: 'G#-1', + 21: 'A-1', + 22: 'A#-1', + 23: 'B-1', + 24: 'C0', + 25: 'C#0', + 26: 'D0', + 27: 'D#0', + 28: 'E0', + 29: 'F0', + 30: 'F#0', + 31: 'G0', + 32: 'G#0', + 33: 'A0', + 34: 'A#0', + 35: 'B0', + 36: 'C1', + 37: 'C#1', + 38: 'D1', + 39: 'D#1', + 40: 'E1', + 41: 'F1', + 42: 'F#1', + 43: 'G1', + 44: 'G#1', + 45: 'A1', + 46: 'A#1', + 47: 'B1', + 48: 'C2', + 49: 'C#2', + 50: 'D2', + 51: 'D#2', + 52: 'E2', + 53: 'F2', + 54: 'F#2', + 55: 'G2', + 56: 'G#2', + 57: 'A2', + 58: 'A#2', + 59: 'B2', + 60: 'C3', + 61: 'C#3', + 62: 'D3', + 63: 'D#3', + 64: 'E3', + 65: 'F3', + 66: 'F#3', + 67: 'G3', + 68: 'G#3', + 69: 'A3', + 70: 'A#3', + 71: 'B3', + 72: 'C4', + 73: 'C#4', + 74: 'D4', + 75: 'D#4', + 76: 'E4', + 77: 'F4', + 78: 'F#4', + 79: 'G4', + 80: 'G#4', + 81: 'A4', + 82: 'A#4', + 83: 'B4', + 84: 'C5', + 85: 'C#5', + 86: 'D5', + 87: 'D#5', + 88: 'E5', + 89: 'F5', + 90: 'F#5', + 91: 'G5', + 92: 'G#5', + 93: 'A5', + 94: 'A#5', + 95: 'B5', + 96: 'C6', + 97: 'C#6', + 98: 'D6', + 99: 'D#6', + 100: 'E6', + 101: 'F6', + 102: 'F#6', + 103: 'G6', + 104: 'G#6', + 105: 'A6', + 106: 'A#6', + 107: 'B6', + 108: 'C7', + 109: 'C#7', + 110: 'D7', + 111: 'D#7', + 112: 'E7', + 113: 'F7', + 114: 'F#7', + 115: 'G7', + 116: 'G#7', + 117: 'A7', + 118: 'A#7', + 119: 'B7', + 120: 'C8', + 121: 'C#8', + 122: 'D8', + 123: 'D#8', + 124: 'E8', + 125: 'F8', + 126: 'F#8', + 127: 'G8', +} + +percussion_notes = { + 35: 'Acoustic Bass Drum', + 36: 'Bass Drum 1', + 37: 'Side Stick', + 38: 'Acoustic Snare', + 39: 'Hand Clap', + 40: 'Electric Snare', + 41: 'Low Floor Tom', + 42: 'Closed Hi Hat', + 43: 'High Floor Tom', + 44: 'Pedal Hi-Hat', + 45: 'Low Tom', + 46: 'Open Hi-Hat', + 47: 'Low-Mid Tom', + 48: 'Hi Mid Tom', + 49: 'Crash Cymbal 1', + 50: 'High Tom', + 51: 'Ride Cymbal 1', + 52: 'Chinese Cymbal', + 53: 'Ride Bell', + 54: 'Tambourine', + 55: 'Splash Cymbal', + 56: 'Cowbell', + 57: 'Crash Cymbal 2', + 58: 'Vibraslap', + 59: 'Ride Cymbal 2', + 60: 'Hi Bongo', + 61: 'Low Bongo', + 62: 'Mute Hi Conga', + 63: 'Open Hi Conga', + 64: 'Low Conga', + 65: 'High Timbale', + 66: 'Low Timbale', + 67: 'High Agogo', + 68: 'Low Agogo', + 69: 'Cabasa', + 70: 'Maracas', + 71: 'Short Whistle', + 72: 'Long Whistle', + 73: 'Short Guiro', + 74: 'Long Guiro', + 75: 'Claves', + 76: 'Hi Wood Block', + 77: 'Low Wood Block', + 78: 'Mute Cuica', + 79: 'Open Cuica', + 80: 'Mute Triangle', + 81: 'Open Triangle', +} diff --git a/decoders/midi/pd.py b/decoders/midi/pd.py index 1616af1..ccbca34 100644 --- a/decoders/midi/pd.py +++ b/decoders/midi/pd.py @@ -51,6 +51,12 @@ class Decoder(srd.Decoder): def putx(self, data): self.put(self.ss_block, self.es_block, self.out_ann, data) + def get_note_name(self, channel, note): + if channel != 10: + return chromatic_notes[note] + else: + return 'assuming ' + percussion_notes.get(note, 'undefined') + def handle_channel_msg_0x80(self): # Note off: 8n kk vv # n = channel, kk = note, vv = velocity @@ -59,8 +65,9 @@ class Decoder(srd.Decoder): return self.es_block = self.es msg, chan, note, velocity = c[0] & 0xf0, (c[0] & 0x0f) + 1, c[1], c[2] - self.putx([0, ['Channel %d: %s (note = %d, velocity = %d)' % \ - (chan, status_bytes[msg], note, velocity)]]) + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s (note = %d \'%s\', velocity = %d)' % \ + (chan, status_bytes[msg], note, note_name, velocity)]]) self.cmd, self.state = [], 'IDLE' def handle_channel_msg_0x90(self): @@ -73,14 +80,23 @@ class Decoder(srd.Decoder): self.es_block = self.es msg, chan, note, velocity = c[0] & 0xf0, (c[0] & 0x0f) + 1, c[1], c[2] s = 'note off' if (velocity == 0) else status_bytes[msg] - self.putx([0, ['Channel %d: %s (note = %d, velocity = %d)' % \ - (chan, s, note, velocity)]]) + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s (note = %d \'%s\', velocity = %d)' % \ + (chan, s, note, note_name, velocity)]]) self.cmd, self.state = [], 'IDLE' def handle_channel_msg_0xa0(self): # Polyphonic key pressure / aftertouch: An kk vv # n = channel, kk = polyphonic key pressure, vv = pressure value - pass # TODO + c = self.cmd + if len(c) < 3: + return + self.es_block = self.es + msg, chan, note, pressure = c[0] & 0xf0, (c[0] & 0x0f) + 1, c[1], c[2] + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s (note = %d \'%s\', pressure = %d)' % \ + (chan, status_bytes[msg], note, note_name, pressure)]]) + self.cmd, self.state = [], 'IDLE' def handle_controller_0x44(self): # Legato footswitch: Bn 44 vv @@ -94,27 +110,57 @@ class Decoder(srd.Decoder): # Portamento control (PTC): Bn 54 kk # n = channel, kk = source note for pitch reference chan, kk = (self.cmd[0] & 0x0f) + 1, self.cmd[2] + kk_name = self.get_note_name(chan, kk) self.putx([0, ['Channel %d: control function \'%s\' (source note ' \ - '= %d)' % (chan, control_functions[0x54], kk)]]) + '= %d / %s)' % \ + (chan, control_functions[0x54], kk, kk_name)]]) def handle_controller_generic(self): c = self.cmd chan, fn, param = (c[0] & 0x0f) + 1, c[1], c[2] - ctrl_fn = control_functions.get(fn, 'undefined') + default_name = 'undefined' + ctrl_fn = control_functions.get(fn, default_name) + if ctrl_fn == default_name: + ctrl_fn = '%s 0x%02x' % (default_name, fn) self.putx([0, ['Channel %d: control change to function \'%s\' ' \ '(param = 0x%02x)' % (chan, ctrl_fn, param)]]) + def handle_channel_mode(self): + # Channel Mode: Bn mm vv + # n = channel, mm = mode number (120 - 127), vv = value + c = self.cmd + chan, mm, vv = (c[0] & 0x0f) + 1, c[1], c[2] + mode_fn = control_functions.get(mm, 'undefined') + # Decode the value based on the mode number. + vv_string = '' + if mm == 122: # mode = local control? + if vv == 0: + vv_string = 'off' + elif vv == 127: # mode = poly mode on? + vv_string = 'on' + else: + vv_string = '(non-standard param value of 0x%02x)' % vv + elif mm == 126: # mode = mono mode on? + if vv != 0: + vv_string = '(%d channels)' % vv + else: + vv_string = '(channels \'basic\' through 16)' + elif vv != 0: # All other channel mode messages expect vv == 0. + vv_string = '(non-standard param value of 0x%02x)' % vv + self.putx([0, ['Channel %d: mode message \'%s\' %s' % \ + (chan, mode_fn, vv_string)]]) + self.cmd, self.state = [], 'IDLE' + def handle_channel_msg_0xb0(self): # Control change (or channel mode messages): Bn cc vv # n = channel, cc = control number (0 - 119), vv = control value c = self.cmd - if (len(c) >= 2) and (c[1] in range(0x78, 0x7f + 1)): - # This is not a control change, but rather a channel mode message. - # TODO: Handle channel mode messages. - return if len(c) < 3: return self.es_block = self.es + if c[1] in range(0x78, 0x7f + 1): + self.handle_channel_mode() + return handle_ctrl = getattr(self, 'handle_controller_0x%02x' % c[1], self.handle_controller_generic) handle_ctrl() @@ -123,22 +169,55 @@ class Decoder(srd.Decoder): def handle_channel_msg_0xc0(self): # Program change: Cn pp # n = channel, pp = program number (0 - 127) - pass # TODO + c = self.cmd + if len(c) < 2: + return + self.es_block = self.es + msg, chan, pp = self.cmd[0] & 0xf0, (self.cmd[0] & 0x0f) + 1, \ + self.cmd[1] + 1 + change_type = 'instrument' + name = '' + if chan != 10: # channel != percussion + name = gm_instruments.get(pp, 'undefined') + else: + change_type = 'drum kit' + name = drum_kit.get(pp, 'undefined') + self.putx([0, ['Channel %d: %s to %s %d (assuming %s)' % \ + (chan, status_bytes[msg], change_type, pp, name)]]) + self.cmd, self.state = [], 'IDLE' def handle_channel_msg_0xd0(self): # Channel pressure / aftertouch: Dn vv # n = channel, vv = pressure value - pass # TODO + c = self.cmd + if len(c) < 2: + return + self.es_block = self.es + msg, chan, vv = self.cmd[0] & 0xf0, (self.cmd[0] & 0x0f) + 1, self.cmd[1] + self.putx([0, ['Channel %d: %s %d' % (chan, status_bytes[msg], vv)]]) + self.cmd, self.state = [], 'IDLE' def handle_channel_msg_0xe0(self): # Pitch bend change: En ll mm # n = channel, ll = pitch bend change LSB, mm = pitch bend change MSB - pass # TODO + c = self.cmd + if len(c) < 3: + return + self.es_block = self.es + msg, chan, ll, mm = self.cmd[0] & 0xf0, (self.cmd[0] & 0x0f) + 1, \ + self.cmd[1], self.cmd[2] + decimal = (mm << 7) + ll + self.putx([0, ['Channel %d: %s 0x%02x 0x%02x (%d)' % \ + (chan, status_bytes[msg], ll, mm, decimal)]]) + self.cmd, self.state = [], 'IDLE' def handle_channel_msg_generic(self): + # TODO: It should not be possible to hit this code. + # It currently can not be unit tested. msg_type = self.cmd[0] & 0xf0 + self.es_block = self.es self.putx([0, ['Unknown channel message type: 0x%02x' % msg_type]]) - # TODO: Handle properly. + self.cmd, self.state = [], 'IDLE' def handle_channel_msg(self, newbyte): self.cmd.append(newbyte) @@ -148,17 +227,108 @@ class Decoder(srd.Decoder): handle_msg() def handle_sysex_msg(self, newbyte): - # SysEx message: 1 status byte, x data bytes, EOX byte + # SysEx message: 1 status byte, 1-3 manuf. bytes, x data bytes, EOX byte self.cmd.append(newbyte) if newbyte != 0xf7: # EOX return self.es_block = self.es - # TODO: Get message ID, vendor ID, message contents, etc. - self.putx([0, ['SysEx message']]) + # Note: Unlike other methods, this code pops bytes out of self.cmd + # to isolate the data. + msg, eox = self.cmd.pop(0), self.cmd.pop() + if len(self.cmd) < 1: + self.putx([0, ['SysEx: truncated manufacturer code (<1 bytes)']]) + self.cmd, self.state = [], 'IDLE' + return + # Extract the manufacturer name (or SysEx realtime or non-realtime). + m1 = self.cmd.pop(0) + manu = (m1,) + if m1 == 0x00: # If byte == 0, then 2 more manufacturer bytes follow. + if len(self.cmd) < 2: + self.putx([0, ['SysEx: truncated manufacturer code (<3 bytes)']]) + self.cmd, self.state = [], 'IDLE' + return + manu = (m1, self.cmd.pop(0), self.cmd.pop(0)) + default_name = 'undefined' + manu_name = sysex_manufacturer_ids.get(manu, default_name) + if manu_name == default_name: + if len(manu) == 3: + manu_name = '%s (0x%02x 0x%02x 0x%02x)' % \ + (default_name, manu[0], manu[1], manu[2]) + else: + manu_name = '%s (0x%02x)' % (default_name, manu[0]) + # Extract the payload. + # TODO: Write methods to decode SysEx realtime & non-realtime payloads. + payload = '' + while len(self.cmd) > 0: + payload += '0x%02x ' % (self.cmd.pop(0)) + if payload == '': + payload = '' + self.putx([0, ['SysEx: for \'%s\' with payload %s' % \ + (manu_name, payload)]]) + self.cmd, self.state = [], 'IDLE' + + def handle_syscommon_midi_time_code_quarter_frame_msg(self, newbyte): + # MIDI time code quarter frame: F1 nd + # n = message type + # d = values + c = self.cmd + if len(c) < 2: + return + msg = self.cmd[0] + nn, dd = (self.cmd[1] & 0x70) >> 4, self.cmd[1] & 0x0f + group = 'System Common' + self.es_block = self.es + if nn != 7: # If message type does not contain SMPTE type. + self.putx([0, ['%s: %s of %s, value 0x%01x' % \ + (group, status_bytes[msg], quarter_frame_type[nn], dd)]]) + self.cmd, self.state = [], 'IDLE' + return + tt = (dd & 0x6) >> 1 + self.putx([0, ['%s: %s of %s, value 0x%01x for %s' % \ + (group, status_bytes[msg], quarter_frame_type[nn], \ + dd, smpte_type[tt])]]) self.cmd, self.state = [], 'IDLE' def handle_syscommon_msg(self, newbyte): - pass # TODO + # System common messages + # + # There are 5 simple formats (which are directly handled here) and + # 1 complex one called MIDI time code quarter frame. + # + # Note: While the MIDI lists 0xf7 as a "system common" message, it + # is actually only used with SysEx messages so it is processed there. + self.cmd.append(newbyte) + msg = self.cmd[0] + c = self.cmd + group = 'System Common' + if msg == 0xf1: + # MIDI time code quarter frame + self.handle_syscommon_midi_time_code_quarter_frame_msg(newbyte) + return + elif msg == 0xf2: + # Song position pointer: F2 ll mm + # ll = LSB position, mm = MSB position + if len(c) < 3: + return + ll, mm = self.cmd[1], self.cmd[2] + decimal = (mm << 7) + ll + self.es_block = self.es + self.putx([0, ['%s: %s 0x%02x 0x%02x (%d)' % \ + (group, status_bytes[msg], ll, mm, decimal)]]) + elif msg == 0xf3: + # Song select: F3 ss + # ss = song selection number + if len(c) < 2: + return + ss = self.cmd[1] + self.es_block = self.es + self.putx([0, ['%s: %s number %d' % (group, status_bytes[msg], ss)]]) + elif msg == 0xf4 or msg == 0xf5 or msg == 0xf6: + # Undefined 0xf4, Undefined 0xf5, and Tune Request (respectively). + # All are only 1 byte long with no data bytes. + self.es_block = self.es + self.putx([0, ['%s: %s' % (group, status_bytes[msg])]]) + self.cmd, self.state = [], 'IDLE' def handle_sysrealtime_msg(self, newbyte): # System realtime message: 0b11111ttt (t = message type) @@ -193,9 +363,9 @@ class Decoder(srd.Decoder): self.ss_block = ss if pdata in range(0x80, 0xef + 1): self.state = 'HANDLE CHANNEL MSG' - elif pdata == 0xf0: + elif pdata == 0xf0 or pdata == 0xf7: self.state = 'HANDLE SYSEX MSG' - elif pdata in range(0xf1, 0xf7 + 1): + elif pdata in range(0xf1, 0xf7): self.state = 'HANDLE SYSCOMMON MSG' elif pdata in range(0xf8, 0xff + 1): self.state = 'HANDLE SYSREALTIME MSG'