Last active
July 13, 2024 17:01
-
-
Save gekart/9f850a14640a4103695c7da16f41f278 to your computer and use it in GitHub Desktop.
KORG monologue Preset Pretty Printer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Use this file to parse the structure of your monologue programs and libraries (sound banks) | |
# this makes it easy to understand and document a finished sound | |
# run "python molg.py test.moldprog" to print the sound in a program name test.moldprog | |
# or "python molg.py test.molglib 1" to print the second sound in the bank named test.molglib | |
import struct, sys, zipfile | |
""" def bit_on_off(flags): | |
bit_list = [] | |
for i in range(15): | |
if flags & 1 == 1: | |
bit_list.append((i,'On')) | |
else: | |
bit_list.append((i,'Off')) | |
flags >>= 2 | |
return bit_list | |
""" | |
def bit_on_off(flags): | |
return ("{:016b}".format(flags))[::-1] | |
def motion_parameter(i): | |
motion_parameters = { | |
0 : "None", | |
13 : "VCO 1 PITCH", | |
14 : "VCO 1 SHAPE", | |
15 : "VCO 1 OCTAVE", | |
16 : "VCO 1 WAVE", | |
17 : "VCO 2 PITCH", | |
18 : "VCO 2 SHAPE", | |
19 : "VCO 2 OCTAVE", | |
20 : "VCO 2 WAVE", | |
21 : "VCO 1 LEVEL", | |
22 : "VCO 2 LEVEL", | |
23 : "CUTOFF", | |
24 : "RESONANCE", | |
25 : "SYNC/RING", | |
26 : "ATTACK", | |
27 : "DECAY", | |
28 : "EG INT", | |
29 : "EG TYPE", | |
30 : "EG TARGET", | |
31 : "LFO RATE", | |
32 : "LFO INT", | |
33 : "LFO TARGET", | |
34 : "LFO TYPE", | |
35 : "LFO MODE", | |
37 : "DRIVE", | |
40 : "PORTAMENTO", | |
55 : "VCO 2 OCTAVE", | |
56 : "PITCH BEND", | |
57 : "GATE TIME" | |
} | |
return motion_parameters[i >> 8] + ', Motion ' + ("Off", "On")[i & 1] + ", Smooth " + ("Off", "On")[(i & 2) >> 1] | |
def step_event_data(data): | |
event_structure = [ | |
("Note", "<B"), | |
("Reserved", "B"), | |
("Velocity", "B"), | |
("Reserved", "B"), | |
("Gate time", "B"), | |
("Reserved", "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 2 Data 1", "B"), | |
("Motion Slot 2 Data 2", "B"), | |
("Motion Slot 2 Data 3", "B"), | |
("Motion Slot 2 Data 4", "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 4 Data 1", "B"), | |
("Motion Slot 4 Data 2", "B"), | |
("Motion Slot 4 Data 3", "B"), | |
("Motion Slot 4 Data 4", "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]) | |
fileStructure = [ | |
("MAGIC", "<4s"), | |
("PROGRAM NAME", "12s"), | |
("VCO 1 PITCH", "B"), | |
("VCO 1 SHAPE", "B"), | |
("VCO 2 PITCH", "B"), | |
("VCO 2 SHAPE", "B"), | |
("VCO 1 LEVEL", "B"), | |
("VCO 2 LEVEL", "B"), | |
("CUTOFF", "B"), | |
("RESONANCE", "B"), | |
("EG ATTACK", "B"), | |
("EG DECAY", "B"), | |
("EG INT", "B"), | |
("LFO RATE", "B"), | |
("LFO INT", "B"), | |
("DRIVE", "B"), | |
("30 VCO 1 PITCH, SHAPE, OCTAVE, WAVE", "B"), | |
("31 VCO 2 PITCH, SHAPE, OCTAVE, WAVE", "B"), | |
("32 SYNC/RING, KEYBOARD OCTAVE", "B"), | |
("33 VCO 1 LEVEL, VCO 2 LEVEL, CUTOFF, RESONANCE", "B"), | |
("34 EG TYPE, EG ATTACK, EG DECAY, EG TARGET", "B"), | |
("35 EG INT, LFO RATE, LFO INT, DRIVE", "B"), | |
("36 LFO TYPE, LFO MODE, LFO TARGET, SEQ TRIG", "B"), | |
("PROGRAM TUNING", "B"), | |
("MICROTUNING", "B"), | |
("SCALE KEY", "B"), | |
("SLIDE TIME", "B"), | |
("PORTAMENTO TIME", "B"), | |
("SLIDER ASSIGN", "B"), | |
("43 BEND RANGE+/-", "B"), | |
("44 PORTAMENTO MODE, LFO BPM SYNC, CUTOFF VELOCITY, CUTOFF KEY TRACK", "B"), | |
("PROGRAM LEVEL", "B"), | |
("AMP VELOCITY", "B"), | |
("RESERVED", "B"), | |
("SEQD", "4s"), | |
("BPM", "H"), | |
("Step Length", "B"), | |
("Step Resolution", "B"), | |
("Swing", "B"), | |
("Default Gate Time", "B"), | |
("RESERVED", "6s"), | |
("Steps Off/On", "H"), | |
("Steps Motion Off/On", "H"), | |
("Steps Slide Off/On", "H"), | |
("RESERVED", "2s"), | |
("Motion Slot 1 Parameter", "H"), | |
("Motion Slot 2 Parameter", "H"), | |
("Motion Slot 3 Parameter", "H"), | |
("Motion Slot 4 Parameter", "H"), | |
("Motion Slot 1 Steps Off/On", "H"), | |
("Motion Slot 2 Steps Off/On", "H"), | |
("Motion Slot 3 Steps Off/On", "H"), | |
("Motion Slot 4 Steps Off/On", "H"), | |
("RESERVED", "8s"), | |
("Step 1 Event Data", "22s"), | |
("Step 2 Event Data", "22s"), | |
("Step 3 Event Data", "22s"), | |
("Step 4 Event Data", "22s"), | |
("Step 5 Event Data", "22s"), | |
("Step 6 Event Data", "22s"), | |
("Step 7 Event Data", "22s"), | |
("Step 8 Event Data", "22s"), | |
("Step 9 Event Data", "22s"), | |
("Step 10 Event Data", "22s"), | |
("Step 11 Event Data", "22s"), | |
("Step 12 Event Data", "22s"), | |
("Step 13 Event Data", "22s"), | |
("Step 14 Event Data", "22s"), | |
("Step 15 Event Data", "22s"), | |
("Step 16 Event Data", "22s") | |
] | |
# 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)) | |
# preprocess bits | |
for i, parameter in enumerate(fileStructure): | |
# check if parameter is compound (then it starts with a number) | |
compoundParameter = parameter[0][:2] | |
if compoundParameter.isdigit(): | |
# paramter is compound, now handle the details | |
if compoundParameter == '30': | |
# ("30 VCO 1 PITCH, SHAPE, OCTAVE, WAVE", "B"), | |
result[2] = (result[2] << 2) + (result[i] & 3) # VCO 1 PITCH | |
result[3] = (result[3] << 2) + ((result[i] & 12) >> 2) # VCO 1 SHAPE | |
fileStructure.append(("VCO 1 OCTAVE", "")) | |
result.append(["16'","8'","4'","2'"][(result[i] & 48) >> 4]) # VCO 1 OCTAVE | |
fileStructure.append(("VCO 1 WAVE", "")) | |
result.append(["SQR","TRI","SAW"][(result[i] & 192) >> 6]) # VCO 1 WAVE | |
if compoundParameter == '31': | |
# ("31 VCO 2 PITCH, SHAPE, OCTAVE, WAVE", "B"), | |
result[4] = (result[4] << 2) + (result[i] & 3) # VCO 2 PITCH | |
result[5] = (result[5] << 2) + ((result[i] & 12) >> 2) # VCO 2 SHAPE | |
fileStructure.append(("VCO 2 OCTAVE", "")) | |
result.append(["16'","8'","4'","2'"][(result[i] & 48) >> 4]) # VCO 2 OCTAVE | |
fileStructure.append(("VCO 2 WAVE", "")) | |
result.append(["NOISE","TRI","SAW"][(result[i] & 192) >> 6]) # VCO 2 WAVE | |
if compoundParameter == '32': | |
# ("32 SYNC/RING, KEYBOARD OCTAVE", "B"), | |
fileStructure.append(("SYNC/RING", "")) | |
result.append(["RING","OFF","SYNC"][(result[i] & 3)]) # SYNC/RING | |
fileStructure.append(("KEYBOARD OCTAVE", "")) | |
result.append(["-2","-1","0","1","2"][(result[i] & 28) >> 2]) # KEYBOARD OCTAVE | |
# ("33 VCO 1 LEVEL, VCO 2 LEVEL, CUTOFF, RESONANCE", "B"), | |
if compoundParameter == '33': | |
result[6] = (result[6] << 2) + (result[i] & 3) # VCO 1 LEVEL | |
result[7] = (result[7] << 2) + ((result[i] & 12) >> 2) # VCO 2 LEVEL | |
result[8] = (result[8] << 2) + ((result[i] & 48) >> 4) # CUTOFF | |
result[9] = (result[9] << 2) + ((result[i] & 192) >> 6) # RESONANCE | |
# ("34 EG TYPE, EG ATTACK, EG DECAY, EG TARGET", "B"), | |
if compoundParameter == '34': | |
fileStructure.append(("EG TYPE", "")) | |
result.append(["GATE","A/G/D","A/D"][(result[i] & 3)]) # EG TYPE | |
result[10] = (result[10] << 2) + ((result[i] & 12) >> 2) # EG ATTACK | |
result[11] = (result[11] << 2) + ((result[i] & 48) >> 4) # EG DECAY | |
fileStructure.append(("EG TARGET", "")) | |
result.append(["CUTOFF","PITCH 2","PITCH"][(result[i] & 192) >> 6]) # EG TARGET | |
# ("35 EG INT, LFO RATE, LFO RATE, DRIVE", "B"), | |
if compoundParameter == '35': | |
result[12] = (result[12] << 2) + (result[i] & 3) # EG INT | |
result[13] = (result[13] << 2) + ((result[i] & 12) >> 2) # LFO RATE | |
result[14] = (result[14] << 2) + ((result[i] & 48) >> 4) # LFO RATE | |
result[15] = (result[15] << 2) + ((result[i] & 192) >> 6) # DRIVE | |
# ("36 LFO TYPE, LFO MODE, LFO TARGET, SEQ TRIG", "B"), | |
if compoundParameter == '36': | |
fileStructure.append(("LFO TYPE", "")) | |
result.append(["SQR","TRI","SAW"][(result[i] & 3)]) # LFO TYPE | |
fileStructure.append(("LFO MODE", "")) | |
result.append(["1-SHOT","SLOW","FAST"][(result[i] & 12) >> 2]) # LFO MODE | |
fileStructure.append(("LFO TARGET", "")) | |
result.append(["CUTOFF","SHAPE","PITCH"][(result[i] & 48) >> 4]) # LFO TARGET | |
fileStructure.append(("SEQ TRIG", "")) | |
result.append(["Off","On"][(result[i] & 64) >> 6]) # SEQ TRIG | |
# ("43 BEND RANGE+/-", "B"), | |
if compoundParameter == '43': | |
fileStructure.append(("BEND RANGE+", "")) | |
result.append(result[i] & 15) # BEND RANGE+ | |
fileStructure.append(("BEND RANGE-", "")) | |
result.append((result[i] & 240) >> 4) # BEND RANGE- | |
# ("44 PORTAMENTO MODE, LFO BPM SYNC, CUTOFF VELOCITY, CUTOFF KEY TRACK", "B"), | |
if compoundParameter == '44': | |
fileStructure.append(("PORTAMENTO MODE", "")) | |
result.append(["Auto","On"][result[i] & 1]) # PORTAMENTO MODE | |
fileStructure.append(("LFO BPM SYNC", "")) | |
result.append(["Off","On"][(result[i] & 8) >> 3]) # LFO BPM SYNC | |
fileStructure.append(("CUTOFF VELOCITY", "")) | |
result.append(["0%", "50%", "100%"][(result[i] & 48) >> 4]) # CUTOFF VELOCITY | |
fileStructure.append(("CUTOFF KEY TRACK", "")) | |
result.append(["0%", "50%", "100%"][(result[i] & 192) >> 6]) # KEY TRACK | |
# print parameters | |
for i, parameter in enumerate(fileStructure): | |
if parameter[0] == "MAGIC" or parameter[0] == "SEQD" or parameter[0] == "END" or parameter[0] == "RESERVED": | |
# don't print useless fields | |
continue | |
if parameter[0] == 'VCO 1 PITCH': | |
if 0 <= result[i] <= 4: | |
result[i] = '-1200C' | |
if 4 <= result[i] <= 356: #-1200 ~ -256 (Cent) | |
result[i] = (result[i] - 356) * 944 / 352 - 256 | |
if 356 <= result[i] <= 476: # -256 ~ -16 (Cent) | |
result[i] = (result[i] - 476) * 2 - 16 | |
if 476 <= result[i] <= 492: # -16 ~ 0 (Cent) | |
result[i] = result[i] - 492 | |
if 492 <= result[i] <= 532: # 0 (Cent) | |
result[i] = '0C' | |
if 532 <= result[i] <= 548: # 0 ~ 16 (Cent) | |
result[i] = result[i] - 532 | |
if 548 <= result[i] <= 668: # 16 ~ 256 (Cent) | |
result[i] = (result[i] - 548) * 2 + 16 | |
if 668 <= result[i] <= 1020: # 256 ~ 1200 (Cent) | |
result[i] = (result[i] - 668) * 944 / 352 + 256 | |
if 1020 <= result[i] <= 1023: # 1200 (Cent) | |
result[i] = '1200C' | |
if parameter[0] == 'VCO 2 PITCH': | |
if 0 <= result[i] <= 4: | |
result[i] = -1200 | |
if 4 <= result[i] <= 356: #-1200 ~ -256 (Cent) | |
result[i] = (result[i] - 356) * 944 / 352 - 256 | |
if 356 <= result[i] <= 476: # -256 ~ -16 (Cent) | |
result[i] = (result[i] - 476) * 2 - 16 | |
if 476 <= result[i] <= 492: # -16 ~ 0 (Cent) | |
result[i] = result[i] - 492 | |
if 492 <= result[i] <= 532: # 0 (Cent) | |
result[i] = 0 | |
if 532 <= result[i] <= 548: # 0 ~ 16 (Cent) | |
result[i] = result[i] - 532 | |
if 548 <= result[i] <= 668: # 16 ~ 256 (Cent) | |
result[i] = (result[i] - 548) * 2 + 16 | |
if 668 <= result[i] <= 1020: # 256 ~ 1200 (Cent) | |
result[i] = (result[i] - 668) * 944 / 352 + 256 | |
if 1020 <= result[i] <= 1023: # 1200 (Cent) | |
result[i] = 1200 | |
result[i] = str(result[i]) + 'C' | |
if parameter[0] == 'LFO RATE': | |
if result[50] == 'On': | |
result[i] = ("4","2","1","3/4","1/2","3/8","1/3","1/4","3/16", | |
"1/6","1/8","1/12","1/16","1/24","1/32","1/36")[result[i] / 64] | |
if parameter[0] == 'Step Resolution': | |
result[i] = ("1/16","1/8","1/4","1/2","1/1")[result[i]] | |
if parameter[0] == 'LFO INT': | |
result[i] = result[i] - 512 | |
if parameter[0] == 'EG INT': | |
result[i] = result[i] - 512 | |
if parameter[0] == 'PROGRAM LEVEL': | |
result[i] -= 102 | |
if parameter[0] == 'PROGRAM TUNING': | |
result[i] = str(result[i] - 50) + "Cent" | |
if parameter[0] == 'SLIDE TIME': | |
result[i] = str((result[i] * 100) / 72) + '%' | |
if parameter[0] == 'SCALE KEY': | |
result[i] = result[i] - 12 | |
if parameter[0] == 'Default Gate Time': | |
result[i] = str((result[i] * 100) / 72) + '%' | |
if parameter[0] == 'PORTAMENTO TIME': | |
result[i] -= 1 | |
if result[i] < 0: | |
result[i] = 'Off' | |
if parameter[0] == "Steps Off/On": | |
result[i] = bit_on_off(result[i]) | |
if parameter[0] == "Steps Motion Off/On": | |
result[i] = bit_on_off(result[i]) | |
if parameter[0] == "Steps Slide Off/On": | |
result[i] = bit_on_off(result[i]) | |
if parameter[0] == 'SLIDER ASSIGN': | |
slider_assign = { | |
"13" : "VCO 1 PITCH", | |
"14" : "VCO 1 SHAPE", | |
"17" : "VCO 2 PITCH", | |
"18" : "VCO 2 SHAPE", | |
"21" : "VCO 1 LEVEL", | |
"22" : "VCO 1 LEVEL", | |
"23" : "CUTOFF", | |
"24" : "RESONANCE", | |
"26" : "ATTACK", | |
"27" : "DECAY", | |
"28" : "EG INT", | |
"31" : "LFO RATE", | |
"32" : "LFO INT", | |
"40" : "PORTAMENT", | |
"56" : "PITCH BEND", | |
"57" : "GATE TIME"} | |
result[i] = slider_assign[str(result[i])] | |
if parameter[0] == 'MICROTUNING': | |
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" | |
} | |
result[i] = micro_tuning[result[i]] | |
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] == 'BPM': | |
result[i] = '%.1f' % (float(result[i]) / 10) | |
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 | |
compoundParameter = parameter[0][:2] | |
if not compoundParameter.isdigit(): # don't print compound parameters | |
print(parameter[0] + ': ' + str(result[i])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,
Thanks for this script. I'm new to both monologue and python. The script does not work for me (Python 3). I suggest some changes (around line 278) to get it to work:
print parameters
for i, parameter in enumerate(fileStructure):
if parameter[0] == "MAGIC" or parameter[0] == "SEQD" or parameter[0] == "END" or parameter[0] == "RESERVED":
# don't print useless fields
continue
if parameter[0] == "PROGRAM NAME":
result[i] = result[i].split(b'\x00',1)[0].decode('utf-8')
if parameter[0] == 'VCO 1 PITCH':
if 0 <= result[i] <= 4:
result[i] = '-1200C'
elif 4 <= result[i] <= 356: #-1200 ~ -256 (Cent)
result[i] = (result[i] - 356) * 944 / 352 - 256
The first change is to pretty-print the program name. The second changes are to use "elif" for VCO 1 and VCO 2 pitch cases.