-
-
Save gekart/b187d3c16e6160571ccfcf6c597fea3f to your computer and use it in GitHub Desktop.
| # Use this file to parse the structure of your minilogue programs and libraries (sound banks) | |
| # this makes it easy to understand and document a finished sound | |
| # run "python mnlgxd.py test.mnlgxdprog" to print the sound in a program name test.mnlgxdprog | |
| # or "python mnlgxd.py test.mnlgxdlib 1" to print the second sound in the bank named test.mnlgxdlib | |
| import struct, sys, zipfile, fpdf | |
| fileStructure = [ | |
| ("MAGIC", "<4s"), | |
| ("PROGRAM NAME", "12s"), | |
| ("OCTAVE", "B", "val - 2"), | |
| ("PORTAMENTO", "B"), | |
| ("KEY TRIG", "B", "['Off','On'][val]"), | |
| ("VOICE MODE DEPTH", "H", "voice_mode_depth(val)"), | |
| ("VOICE MODE TYPE", "B", "['None', 'ARP', 'CHORD', 'UNISON','POLY'][val]"), | |
| ("VCO 1 WAVE", "B", '["SQR","TRI","SAW"][val]'), | |
| ("VCO 1 OCTAVE", "B", '["16\'","8\'","4\'","2\'"][val]'), | |
| ("VCO 1 PITCH", "H", "pitch_cents(val)"), | |
| ("VCO 1 SHAPE", "H"), | |
| ("VCO 2 WAVE", "B", '["SQR","TRI","SAW"][val]'), | |
| ("VCO 2 OCTAVE", "B", '["16\'","8\'","4\'","2\'"][val]'), | |
| ("VCO 2 PITCH", "H", "pitch_cents(val)"), | |
| ("VCO 2 SHAPE", "H"), | |
| ("SYNC", "B", "['Off', 'On'][val]"), | |
| ("RING", "B", "['Off', 'On'][val]"), | |
| ("CROSS MOD DEPTH", "H"), | |
| ("MULTI TYPE", "B", "['NOISE','VPM','USER'][val]"), | |
| ("SELECT NOISE", "B", "['HIGH','LOW','PEAK','DECIM'][val]"), | |
| ("SELECT VPM", "B","['SIN1','SIN2','SIN3','SIN4','SAW1','SAW2','SQU1','SQU2','FAT1','FAT2','AIR1','AIR2','DECAY1','DECAY2','CREEP','THROAT'][val]"), | |
| ("SELECT USER", "B", "'USER' + str(val + 1)"), | |
| ("SHAPE NOISE", "H"), | |
| ("SHAPE VPM", "H"), | |
| ("SHAPE USER", "H"), | |
| ("SHIFT SHAPE NOISE", "H"), | |
| ("SHIFT SHAPE VPM", "H"), | |
| ("SHIFT SHAPE USER", "H"), | |
| ("VCO 1 LEVEL", "H"), | |
| ("VCO 2 LEVEL", "H"), | |
| ("MULTI LEVEL", "H"), | |
| ("CUTOFF", "H"), | |
| ("RESONANCE", "H"), | |
| ("CUTOFF DRIVE", "B", '["0%", "50%", "100%"][val]'), | |
| ("CUTOFF KEYBOARD TRACK", "B", '["0%", "50%", "100%"][val]'), | |
| ("AMP EG ATTACK", "H"), | |
| ("AMP EG DECAY", "H"), | |
| ("AMP EG SUSTAIN", "H"), | |
| ("AMP EG RELEASE", "H"), | |
| ("EG ATTACK", "H"), | |
| ("EG DECAY", "H"), | |
| ("EG INT", "H", "eg_int(val)"), | |
| ("EG TARGET", "B", "['CUTOFF', 'PITCH2', 'PITCH'][val]"), | |
| ("LFO WAVE", "B", '["SQR","TRI","SAW"][val]'), | |
| ("LFO MODE", "B", "['1-SHOT','NORMAL','BPM'][val]"), | |
| ("LFO RATE", "H", "lfo_rate(val)"), | |
| ("LFO INT", "H"), | |
| ("LFO TARGET", "B", "['CUTOFF', 'SHAPE', 'PITCH'][val]"), | |
| ("MOD FX ON OFF", "B", "['Off', 'On'][val]"), | |
| ("MOD FX TYPE", "B", "['CHORUS','ENSEMBLE','PHASER','FLANGER','USER'][val]"), | |
| ("MOD FX CHORUS", "B", "['STEREO','LIGHT','DEEP','TRIPHASE','HARMONIC','MONO','FEEDBACK','VIBRATO'][val]"), | |
| ("MOD FX ENSEMBLE", "B", "['STEREO','LIGHT','MONO'][val]"), | |
| ("MOD FX PHASER", "B", "['STEREO','FAST','ORANGE','SMALL','SMALL RESO','BLACK','FORMANT','TWINKLE'][val]"), | |
| ("MOD FX FLANGER", "B", "['STEREO','LIGHT','MONO','HIGH SWEEP','MID SWEEP','PAN SWEEP','MONO SWEEP','TRIPHASE'][val]"), | |
| ("MOD FX USER", "B", "'USER' + str(val + 1)"), | |
| ("MOD FX TIME", "H"), | |
| ("MOD FX DEPTH", "H"), | |
| ("DELAY FX ON OFF", "B", "['Off', 'On'][val]"), | |
| ("DELAY SUB TYPE", "B", "['STEREO','MONO','PING PONG','HIPASS','TAPE','ONE TAP','STEREO BPM','MONO BPM','PING BPM','HIPASS BPM','TAPE BPM','DOUBLING','USER1','USER2','USER3','USER4','USER5','USER6','USER7','USER8'][val]"), | |
| ("DELAY TIME", "H"), | |
| ("DELAY DEPTH", "H"), | |
| ("REVERB FX ON OFF", "B", "['Off', 'On'][val]"), | |
| ("REVERB SUB TYPE", "B", "['HALL','SMOOTH','ARENA','PLATE','ROOM','EARLY REF','SPACE','RISER','SUBMARINE','HORROR','USER1','USER2','USER3','USER4','USER5','USER6','USER7','USER8'][val]"), | |
| ("REVERB TIME", "H"), | |
| ("REVERB DEPTH", "H"), | |
| ("X+ BEND RANGE", "B"), | |
| ("X- BEND RANGE", "B", "-val"), | |
| ("Y+ ASSIGN", "B", "assign_parameter(val)"), | |
| ("Y+ RANGE", "B", "str(val-100) + '%'"), | |
| ("Y- ASSIGN", "B", "assign_parameter(val)"), | |
| ("Y- RANGE", "B", "str(val-100) + '%'"), | |
| ("CV IN MODE", "B", "['Modulation','CV/Gate(+)','CV/Gate(-)'][val]"), | |
| ("CV IN1 ASSIGN", "B", "assign_parameter(val)"), | |
| ("CV IN1 RANGE", "B", "str(val-100) + '%'"), | |
| ("CV IN2 ASSIGN", "B", "assign_parameter(val)"), | |
| ("CV IN2 RANGE", "B", "str(val-100) + '%'"), | |
| ("MICRO TUNING", "B", "micro_tuning(val)"), | |
| ("SCALE KEY", "B", "('+' if val > 12 else '') + str(val - 12) + ' Note'"), | |
| ("PROGRAM TUNING", "B", "('+' if val > 50 else '') + str(val - 50) + ' Cent'"), | |
| ("LFO KEY SYNC", "B", "['Off', 'On'][val]"), | |
| ("LFO VOICE SYNC", "B", "['Off', 'On'][val]"), | |
| ("LFO TARGET OSC", "B", "['ALL','VCO1+VCO2','VCO2','MULTI'][val]"), | |
| ("CUTOFF VELOCITY", "B"), | |
| ("AMP VELOCITY", "B"), | |
| ("MULTI OCTAVE", "B", '["16\'","8\'","4\'","2\'"][val]'), | |
| ("MULTI ROUTING", "B", "['Pre VCF', 'Post VCF'][val]"), | |
| ("EG LEGATO", "B", "['Off', 'On'][val]"), | |
| ("PORTAMENTO MODE", "B", '["Auto","On"][val]'), | |
| ("PORTAMENTO BPM SYNC", "B", "['Off', 'On'][val]"), | |
| ("PROGRAM LEVEL", "B", "('+' if val > 102 else '') + '%.1f' % ((float(val)-12)/5 - 18) + 'dB'"), | |
| ("VPM PARAM1", "B", "str(val-100) + '%'"), | |
| ("VPM PARAM2", "B", "str(val-100) + '%'"), | |
| ("VPM PARAM3", "B", "str(val-100) + '%'"), | |
| ("VPM PARAM4", "B", "str(val-100) + '%'"), | |
| ("VPM PARAM5", "B", "str(val-100) + '%'"), | |
| ("VPM PARAM6", "B", "str(val-100) + '%'"), | |
| ("USER PARAM1", "B", "user_param(val, 1)"), | |
| ("USER PARAM2", "B", "user_param(val, 1)"), | |
| ("USER PARAM3", "B", "user_param(val, 1)"), | |
| ("USER PARAM4", "B", "user_param(val, 1)"), | |
| ("USER PARAM5", "B", "user_param(val, 1)"), | |
| ("USER PARAM6", "B", "user_param(val, 1)"), | |
| ("USER PARAM TYPE", "H"), | |
| ("PROGRAM TRANSPOSE", "B", "('+' if val > 13 else '') + str(val - 13) + ' Note'"), | |
| ("DELAY DRY WET", "H"), | |
| ("REVERB DRY WET", "H"), | |
| ("MIDI AFTER TOUCH ASSIGN", "B", "assign_parameter(val)"), | |
| ("PRED", "4s"), | |
| ("SQ", "2s"), # previously SEQD | |
| ("Active Step Off/On Steps 1-16","H", "bit_on_off(val) if result[108] == 'SQ' else 'No Active Steps Firmware 1.XX'"), | |
| ("BPM","H","'%.1f' % (float(val) / 10)"), | |
| ("Step Length","B"), | |
| ("Step Resolution","B",'("1/16","1/8","1/4","1/2","1/1")[val]'), | |
| ("Swing","B", "val-75"), | |
| ("Default Gate Time","B","str((val * 100) / 72) + '%'"), | |
| ("Step Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Step Motion Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Motion Slot 1 Parameter","H"), | |
| ("Motion Slot 2 Parameter","H"), | |
| ("Motion Slot 3 Parameter","H"), | |
| ("Motion Slot 4 Parameter","H"), | |
| ("Motion Slot 1 Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Motion Slot 2 Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Motion Slot 3 Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Motion Slot 4 Off/On Steps 1-16","H", "bit_on_off(val)"), | |
| ("Step 1 Event Data","52s"), | |
| ("Step 2 Event Data","52s"), | |
| ("Step 3 Event Data","52s"), | |
| ("Step 4 Event Data","52s"), | |
| ("Step 5 Event Data","52s"), | |
| ("Step 6 Event Data","52s"), | |
| ("Step 7 Event Data","52s"), | |
| ("Step 8 Event Data","52s"), | |
| ("Step 9 Event Data","52s"), | |
| ("Step 10 Event Data","52s"), | |
| ("Step 11 Event Data","52s"), | |
| ("Step 12 Event Data","52s"), | |
| ("Step 13 Event Data","52s"), | |
| ("Step 14 Event Data","52s"), | |
| ("Step 15 Event Data","52s"), | |
| ("Step 16 Event Data","52s"), | |
| ("ARP Gate Time","B","str((val * 100) / 72) + '%'"), | |
| ("ARP Rate","B") | |
| ] | |
| motion_parameters = { | |
| 0 : "None", | |
| 15 : "PORTAMENTO", | |
| 16 : "VOICE MODE DEPTH", | |
| 17 : "VOICE MODE TYPE", | |
| 18 : "VCO 1 WAVE", | |
| 19 : "VCO 1 OCTAVE", | |
| 20 : "VCO 1 PITCH", | |
| 21 : "VCO 1 SHAPE", | |
| 22 : "VCO 2 WAVE", | |
| 23 : "VCO 2 OCTAVE", | |
| 24 : "VCO 2 PITCH", | |
| 25 : "VCO 2 SHAPE", | |
| 26 : "SYNC", | |
| 27 : "RING", | |
| 28 : "CROSS MOD DEPTH", | |
| 29 : "MULTI ENGINE TYPE", | |
| 30 : "MULTI ENGINE NOISE TYPE", | |
| 31 : "MULTI ENGINE VPM TYPE", | |
| 33 : "MULTI SHAPE NOISE", | |
| 34 : "MULTI SHAPE VPM", | |
| 35 : "MULTI SHAPE USER", | |
| 36 : "MULTI SHIFT SHAPE NOISE", | |
| 37 : "MULTI SHIFT SHAPE VPM", | |
| 38 : "MULTI SHIFT SHAPE USER", | |
| 39 : "VCO 1 LEVEL", | |
| 40 : "VCO 2 LEVEL", | |
| 41 : "MULTI ENGINE LEVEL", | |
| 42 : "CUTOFF", | |
| 43 : "RESONANCE", | |
| 45 : "KEYTRACK", | |
| 46 : "AMP EG ATTACK", | |
| 47 : "AMP EG DECAY", | |
| 48 : "AMP EG SUSTAIN", | |
| 49 : "AMP EG RELEASE", | |
| 50 : "EG ATTACK", | |
| 51 : "EG DECAY", | |
| 52 : "EG INT", | |
| 53 : "EG TARGET", | |
| 54 : "LFO WAVE", | |
| 55 : "LFO MODE", | |
| 56 : "LFO RATE", | |
| 57 : "LFO INT", | |
| 58 : "LFO TARGET", | |
| 59 : "MOD FX ON/OFF", | |
| 66 : "MOD FX TIME", | |
| 67 : "MOD FX DEPTH", | |
| 68 : "DELAY ON/OFF", | |
| 70 : "DELAY TIME", | |
| 71 : "DELAY DEPTH", | |
| 72 : "REVERB ON/OFF", | |
| 74 : "REVERB TIME", | |
| 75 : "REVERB DEPTH", | |
| 126 : "PITCH BEND", | |
| 129 : "GATE TIME" | |
| } | |
| def user_param(val, i): | |
| param_type = (result[102] & (3 << (i - 1))) >> (i - 1) | |
| if param_type == 0: # Percent Type | |
| return str(val) + '%' | |
| if param_type == 1: # Bipolar | |
| return val - 100 | |
| return val # Select | |
| def assign_parameter(val): | |
| assign_parameters = { | |
| 0 : "GATE TIME", | |
| 1 : "PORTAMENTO", | |
| 2 : "V.M DEPTH", | |
| 3 : "VCO1 PITCH", | |
| 4 : "VCO1 SHAPE", | |
| 5 : "VCO2 PITCH", | |
| 6 : "VCO2 SHAPE", | |
| 7 : "CROSS MOD", | |
| 8 : "MULTI SHAPE", | |
| 9 : "VCO1 LEVEL", | |
| 10 : "VCO2 LEVEL", | |
| 11 : "MULTI LEVEL", | |
| 12 : "CUTOFF", | |
| 13 : "RESONANCE", | |
| 14 : "A.EG ATTACK", | |
| 15 : "A.EG DECAY", | |
| 16 : "A.EG SUSTAIN", | |
| 17 : "A.EG RELEASE", | |
| 18 : "EG ATTACK", | |
| 19 : "EG DECAY", | |
| 20 : "EG INT", | |
| 21 : "LFO RATE", | |
| 22 : "LFO INT", | |
| 23 : "MOD FX SPEED", | |
| 24 : "MOD FX DEPTH", | |
| 25 : "REVERB TIME", | |
| 26 : "REVERB DEPTH", | |
| 27 : "DELAY TIME", | |
| 28 : "DELAY DEPTH" | |
| } | |
| return assign_parameters[val] | |
| def micro_tuning(val): | |
| micro_tuning = { | |
| 0 : "Equal Temp", | |
| 1 : "Pure Major", | |
| 2 : "Pure Minor", | |
| 3 : "Pythagorean", | |
| 4 : "Werckmeister", | |
| 5 : "Kirnburger", | |
| 6 : "Slendro", | |
| 7 : "Pelog", | |
| 8 : "Ionian", | |
| 9 : "Dorian", | |
| 10 : "Aeolian", | |
| 11 : "Major Penta", | |
| 12 : "Minor Penta", | |
| 13 : "Reverse", | |
| 14 : "AFX001", | |
| 15 : "AFX002", | |
| 16 : "AFX003", | |
| 17 : "AFX004", | |
| 18 : "AFX005", | |
| 19 : "AFX006", | |
| 128 : "USER SCALE 1", | |
| 129 : "USER SCALE 2", | |
| 130 : "USER SCALE 3", | |
| 131 : "USER SCALE 4", | |
| 132 : "USER SCALE 5", | |
| 133 : "USER SCALE 6", | |
| 134 : "USER OCTAVE 1", | |
| 135 : "USER OCTAVE 2", | |
| 136 : "USER OCTAVE 3", | |
| 137 : "USER OCTAVE 4", | |
| 138 : "USER OCTAVE 5", | |
| 139 : "USER OCTAVE 6" | |
| } | |
| return micro_tuning[val] | |
| def lfo_rate(val): | |
| if result[44] == 2: # LFO MODE BPM | |
| if 0 <= val <= 63: | |
| return 4 | |
| if 64 <= val <= 127: | |
| return 2 | |
| if 128 <= val <= 191: | |
| return 1 | |
| if 192 <= val <= 255: | |
| return 3/4 | |
| if 256 <= val <= 319: | |
| return 1/2 | |
| if 320 <= val <= 383: | |
| return 3/8 | |
| if 384 <= val <= 447: | |
| return 1/3 | |
| if 448 <= val <= 511: | |
| return 1/4 | |
| if 512 <= val <= 575: | |
| return 3/16 | |
| if 576 <= val <= 639: | |
| return 1/6 | |
| if 640 <= val <= 703: | |
| return 1/8 | |
| if 704 <= val <= 767: | |
| return 1/12 | |
| if 768 <= val <= 831: | |
| return 1/16 | |
| if 832 <= val <= 895: | |
| return 1/24 | |
| if 896 <= val <= 959: | |
| return 1/32 | |
| if 960 <= val <= 1023: | |
| return 1/36 | |
| return val | |
| def eg_int(val): | |
| if 0 <= val <= 11: | |
| return '-100%' | |
| if 11 <= val <= 492: | |
| return str(- ((492 - val) * (492 - val) * 4641 * 100) / 0x40000000) + '%' | |
| if 492 <= val <= 532: | |
| return '0%' | |
| if 532 <= val <= 1013: | |
| return str(((val - 532) * (val - 532) * 4641 * 100) / 0x40000000) + '%' | |
| if 1013 <= val <= 1023: | |
| return '100%' | |
| def voice_mode_depth(val): | |
| if result[6] == 1: # ARP | |
| arp_range = (("0 <= val <= 78", "MANUAL 1"), | |
| ("79 <= val <= 156", "MANUAL 2"), | |
| ("157 <= val <= 234", "RISE 1"), | |
| ("235 <= val <= 312", "RISE 2"), | |
| ("313 <= val <= 390", "FALL 1"), | |
| ("391 <= val <= 468", "FALL 2"), | |
| ("469 <= val <= 546", "RISE FALL 1"), | |
| ("547 <= val <= 624", "RISE FALL 2"), | |
| ("625 <= val <= 702", "POLY 1"), | |
| ("703 <= val <= 780", "POLY 2"), | |
| ("781 <= val <= 858", "RANDOM 1"), | |
| ("859 <= val <= 936", "RANDOM 2"), | |
| ("937 <= val <= 1023", "RANDOM 3")) | |
| for e in arp_range: | |
| if eval(e[0]): | |
| return e[1] | |
| if result[6] == 2: # UNISON | |
| return str(val * 50 / 1023) + " Cent" | |
| if result[6] == 3: # CHORD | |
| chord_range = (("0 <= val <= 73", "5th"), | |
| ("74 <= val <= 146", "sus2"), | |
| ("147 <= val <= 219", "m"), | |
| ("220 <= val <= 292", "Maj"), | |
| ("293 <= val <= 365", "sus4"), | |
| ("366 <= val <= 438", "m7"), | |
| ("439 <= val <= 511", "7"), | |
| ("512 <= val <= 585", "7sus4"), | |
| ("586 <= val <= 658", "Maj7"), | |
| ("659 <= val <= 731", "aug"), | |
| ("732 <= val <= 804", "dim"), | |
| ("805 <= val <= 877", "m7b5"), | |
| ("878 <= val <= 950", "mMaj7"), | |
| ("951 <= val <= 1023", "Maj7b5")) | |
| for e in chord_range: | |
| if eval(e[0]): | |
| return e[1] | |
| if result[6] == 4: # POLY | |
| return 'Poly' if val < 256 else 'Duo ' + str(val * 50 / 1023) | |
| def pitch_cents(value): | |
| if 0 <= value <= 4: | |
| return '-1200C' | |
| if 4 <= value <= 356: #-1200 ~ -256 (Cent) | |
| return str((value - 356) * 944 / 352 - 256) + 'C' | |
| if 356 <= value <= 476: # -256 ~ -16 (Cent) | |
| return str((value - 476) * 2 - 16) + 'C' | |
| if 476 <= value <= 492: # -16 ~ 0 (Cent) | |
| return str(value - 492) + 'C' | |
| if 492 <= value <= 532: # 0 (Cent) | |
| return '0C' | |
| if 532 <= value <= 548: # 0 ~ 16 (Cent) | |
| return str(value - 532) + 'C' | |
| if 548 <= value <= 668: # 16 ~ 256 (Cent) | |
| return str((value - 548) * 2 + 16) + 'C' | |
| if 668 <= value <= 1020: # 256 ~ 1200 (Cent) | |
| return str((value - 668) * 944 / 352 + 256) + 'C' | |
| if 1020 <= value <= 1023: # 1200 (Cent) | |
| return '1200C' | |
| def bit_on_off(flags): | |
| return ("{:016b}".format(flags))[::-1] | |
| def motion_parameter(i): | |
| return motion_parameters[i >> 8] + ', Motion ' + ("Off", "On")[i & 1] + ", Smooth " + ("Off", "On")[(i & 2) >> 1] | |
| def step_event_data(data): | |
| event_structure = [ | |
| ("Note 1", "<B"), | |
| ("Note 2", "B"), | |
| ("Note 3", "B"), | |
| ("Note 4", "B"), | |
| ("Note 5", "B"), | |
| ("Note 6", "B"), | |
| ("Note 7", "B"), | |
| ("Note 8", "B"), | |
| ("Velocity 1", "B"), | |
| ("Velocity 2", "B"), | |
| ("Velocity 3", "B"), | |
| ("Velocity 4", "B"), | |
| ("Velocity 5", "B"), | |
| ("Velocity 6", "B"), | |
| ("Velocity 7", "B"), | |
| ("Velocity 8", "B"), | |
| ("Gate time 1", "B"), | |
| ("Gate time 2", "B"), | |
| ("Gate time 3", "B"), | |
| ("Gate time 4", "B"), | |
| ("Gate time 5", "B"), | |
| ("Gate time 6", "B"), | |
| ("Gate time 7", "B"), | |
| ("Gate time 8", "B"), | |
| ("Motion Slot 1 Data 1", "B"), | |
| ("Motion Slot 1 Data 2", "B"), | |
| ("Motion Slot 1 Data 3", "B"), | |
| ("Motion Slot 1 Data 4", "B"), | |
| ("Motion Slot 1 Data 5", "B"), | |
| ("Motion Slot 1 Data 6", "B"), | |
| ("Motion Slot 1 Data 7", "B"), | |
| ("Motion Slot 2 Data 1", "B"), | |
| ("Motion Slot 2 Data 2", "B"), | |
| ("Motion Slot 2 Data 3", "B"), | |
| ("Motion Slot 2 Data 4", "B"), | |
| ("Motion Slot 2 Data 5", "B"), | |
| ("Motion Slot 2 Data 6", "B"), | |
| ("Motion Slot 2 Data 7", "B"), | |
| ("Motion Slot 3 Data 1", "B"), | |
| ("Motion Slot 3 Data 2", "B"), | |
| ("Motion Slot 3 Data 3", "B"), | |
| ("Motion Slot 3 Data 4", "B"), | |
| ("Motion Slot 3 Data 5", "B"), | |
| ("Motion Slot 3 Data 6", "B"), | |
| ("Motion Slot 3 Data 7", "B"), | |
| ("Motion Slot 4 Data 1", "B"), | |
| ("Motion Slot 4 Data 2", "B"), | |
| ("Motion Slot 4 Data 3", "B"), | |
| ("Motion Slot 4 Data 4", "B"), | |
| ("Motion Slot 4 Data 5", "B"), | |
| ("Motion Slot 4 Data 6", "B"), | |
| ("Motion Slot 4 Data 7", "B"), | |
| ] | |
| # get the second value (the unpack structure type field) of all tuples in the list | |
| # and assemble a unpack structure | |
| unpackStructure = ''.join(map(lambda x: x[1], event_structure)) | |
| # parse binary using the assembled unpack string | |
| result = list(struct.unpack(unpackStructure, data)) | |
| result[0] = ("C","C#","D","D#","E","F","F#","G","G#","A","A#", "B")[result[0] % 12] + str(result[0] / 12 - 2) | |
| if result[4] >= 73: | |
| result[4] = 'TIE' | |
| else: | |
| result[4] = str((result[4] * 100) / 72) + '%' | |
| return result[0] + ", Velocity " + str(result[2]) + ", Gate time " + result[4]+ "," + str(result[6]) + str(result[7]) + str(result[8]) + str(result[9]) | |
| # check one argument is present | |
| if len(sys.argv) != 2 and len(sys.argv) != 3: | |
| print('Usage: molg.py filename [program number]') | |
| exit(-1) | |
| programNumber = 0 | |
| if len(sys.argv) == 3: | |
| programNumber = int(sys.argv[2]) | |
| # open and read the file | |
| with zipfile.ZipFile(sys.argv[1], mode='r') as file: | |
| try: | |
| fileContent = file.read('Prog_%03d.prog_bin' % (programNumber,)) | |
| except: | |
| print("Couldn't open file. Check program number and file name.") | |
| exit(-2) | |
| # get the second value (the unpack structure type field) of all tuples in the list | |
| # and assemble a unpack structure | |
| unpackStructure = ''.join(map(lambda x: x[1], fileStructure)) | |
| # parse binary using the assembled unpack string | |
| result = list(struct.unpack(unpackStructure, fileContent)) | |
| # print parameters | |
| for i, parameter in enumerate(fileStructure): | |
| if parameter[0] == "MAGIC" or parameter[0] == "PRED" or parameter[0] == "SQ" or parameter[0] == "END" or parameter[0] == "RESERVED" or parameter[0] == "USER PARAM TYPE": | |
| # don't print useless fields | |
| continue | |
| if parameter[0] == 'Motion Slot 1 Parameter': | |
| result[i] = motion_parameter(result[i]) | |
| if parameter[0] == 'Motion Slot 2 Parameter': | |
| result[i] = motion_parameter(result[i]) | |
| if parameter[0] == 'Motion Slot 3 Parameter': | |
| result[i] = motion_parameter(result[i]) | |
| if parameter[0] == 'Motion Slot 4 Parameter': | |
| result[i] = motion_parameter(result[i]) | |
| if parameter[0] == "Motion Slot 1 Steps Off/On": | |
| result[i] = bit_on_off(result[i]) | |
| if parameter[0] == "Motion Slot 2 Steps Off/On": | |
| result[i] = bit_on_off(result[i]) | |
| if parameter[0] == "Motion Slot 3 Steps Off/On": | |
| result[i] = bit_on_off(result[i]) | |
| if parameter[0] == "Motion Slot 4 Steps Off/On": | |
| result[i] = bit_on_off(result[i]) | |
| if parameter[0] == 'Step 1 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 2 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 3 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 4 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 5 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 6 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 7 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 8 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 9 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 10 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 11 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 12 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 13 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 14 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 15 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| if parameter[0] == 'Step 16 Event Data': | |
| result[i] = step_event_data(result[i]) | |
| # print parameter | |
| if len(parameter) < 3: # no transformation necessary | |
| print(parameter[0] + ': ' + str(result[i])) | |
| else: | |
| val = result[i] # make accessing the value from transforms easier | |
| print(parameter[0] + ': ' + str(eval(parameter[2]))) # transform | |
| """ pdf = fpdf.FPDF('L', 'mm', 'A4') | |
| pdf.add_page() | |
| pdf.image('monologue panel.png', 0, 0, 297) | |
| pdf.set_font('Arial', '', 10) | |
| pdf.set_xy(215, 70) | |
| pdf.cell(0, 0, result[1]) | |
| pdf.output('bla.pdf', 'F') """ |
Just wanted to post this here in case anyone is ever looking for it. https://github.com/isnotinvain/minilogue-xd-util
@isnotinvain Great work! Original idea here was to take the graphics from the front panel (manual has a nice one) and draw in the values so that you would have a patch print out as a PDF. Just in case you need another challenge.
cc @buenaonda who just reached out to me and wants to build a GUI like this.
I was thinking maybe https://ctrlr.org/ could be an easy way to do it though you could of course build from scratch. The good news is the MIDI spec for loading / dumping patches is basically the same as the file spec.
Thank you for creating and sharing this script!
I was able to run mnlgxd.py successfully on .mnlgxdprog files, however when running on a .mnlgpreset file, I received the following error:
struct.error: unpack requires a buffer of 1024 bytes
Looking at the .prog_bin files inside of the .mnlgpreset zip archive, I see that the files are only 448 bytes long; .prog_bin files typically decoded by mnlgxd.py are 1024 bytes long.
Are those two different kinds of .prog_bin files?
For reference, the .mnlgpreset file (which includes the 448 bytes .prog_bin files) came from here.
Thanks!
Thank you for creating and sharing this script!
I was able to run
mnlgxd.pysuccessfully on.mnlgxdprogfiles, however when running on a.mnlgpresetfile, I received the following error:
struct.error: unpack requires a buffer of 1024 bytesLooking at the
.prog_binfiles inside of the.mnlgpresetzip archive, I see that the files are only 448 bytes long;.prog_binfiles typically decoded bymnlgxd.pyare 1024 bytes long.Are those two different kinds of
.prog_binfiles?For reference, the
.mnlgpresetfile (which includes the 448 bytes.prog_binfiles) came from here.Thanks!
I suppose the file is for the classic minilogue, not the xd
I've been working on turning this script into something that can read + write patch files, mostly so I can move a user oscillator to a different slot with out ruing my patches. I'll post it here after I clean it up a bit. Thanks for this gist it's super helpful!