Skip to content

Instantly share code, notes, and snippets.

@gekart
Last active March 2, 2023 06:27
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gekart/b187d3c16e6160571ccfcf6c597fea3f to your computer and use it in GitHub Desktop.
Save gekart/b187d3c16e6160571ccfcf6c597fea3f to your computer and use it in GitHub Desktop.
KORG minilogue xd PROG file parser and pretty printer
# 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') """
@epezent
Copy link

epezent commented Apr 17, 2020

This is awesome! Thank you so much

@gekart
Copy link
Author

gekart commented Apr 21, 2020

Glad you found it! ;)

@bensherlock
Copy link

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.

@isnotinvain
Copy link

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)

@isnotinvain
Copy link

@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

@isnotinvain
Copy link

Just for anyone else looking, the detailed spec can be found here: https://www.korg.com/us/support/download/manual/0/811/4440/

@gekart
Copy link
Author

gekart commented Nov 9, 2020

@isnoginvain yes, after trying to find the parameters myself, I found the midi implementation was almost a 1:1 guide

@gekart
Copy link
Author

gekart commented Nov 9, 2020

thanks for the feedback guys. hoping to find some time to finish off the pretty printer as PDF output.

@isnotinvain
Copy link

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!

@isnotinvain
Copy link

Just wanted to post this here in case anyone is ever looking for it. https://github.com/isnotinvain/minilogue-xd-util

@gekart
Copy link
Author

gekart commented Jul 17, 2021

@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.

@isnotinvain
Copy link

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.

@gregsadetsky
Copy link

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!

@gekart
Copy link
Author

gekart commented May 9, 2022

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!

I suppose the file is for the classic minilogue, not the xd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment