-
-
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') """ |
I am using this snippet to see which missing / unknown parameters are used by the KORG presets:
for i in {0..199}; do python mnlgxd.py orig.mnlgxdlib $i | grep -a UNKNOWN; echo $i; done | grep UNKNOWN | cut -c 1-10 | sort -u
UNKNOWN 02
UNKNOWN 03
UNKNOWN 05
UNKNOWN 07
UNKNOWN 09
UNKNOWN 10
The newest version uses the correct program data. Still some work to be done on the sequence data and for printing out on panel page.
This is awesome! Thank you so much
Glad you found it! ;)
Thanks very much for this. It's proving really handy when I'm experimenting with different sounds to have a reliable way of logging my settings. One thing that may have changed with the latest minilogue xd update 2.10 or because I don't have any user mods installed, when it outputs the MOD FX TYPE value it looks to be mismatched. As follows:
Device = Phaser, Script = Flanger.
Device = Flanger, Script = User.
Device = Chorus, Script = Ensemble.
Device = Ensemble, Script = Phaser.
Just in case this is helpful if it shows up for you or others.
Hello, I can second what @bensherlock found.
You can fix the script by changing this line:
("MOD FX TYPE", "B", "['CHORUS','ENSEMBLE','PHASER','FLANGER','USER'][val]"),
to
("MOD FX TYPE", "B", "['NONE','CHORUS','ENSEMBLE','PHASER','FLANGER','USER'][val]"),
(Add a 'NONE' string to position 0 of the mod fx type list which shifts everything over by 1 which is what's needed)
The 'NONE' is not just a placeholder, some patches have a 0 in this field (maybe patches that never enable mod fx and never select an effect -- some patches have a selected fx even though it's disabled)
@gekart is there a spec for these files you used as a reference? I'd love to look at that if it's published somewhere
Just for anyone else looking, the detailed spec can be found here: https://www.korg.com/us/support/download/manual/0/811/4440/
@isnoginvain yes, after trying to find the parameters myself, I found the midi implementation was almost a 1:1 guide
thanks for the feedback guys. hoping to find some time to finish off the pretty printer as PDF output.
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!
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.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 bymnlgxd.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!
I suppose the file is for the classic minilogue, not the xd
Use this script to quickly understand how patches / presets are built. One can extend it to pretty-print preset files, use it for searching presets from preset banks for specific traits (like find all patches using cross-modulation etc) or to automatically convert sounds from minilogue / monologue to minilogue xd.
Next steps: