Skip to content

Instantly share code, notes, and snippets.

@gekart
Last active July 13, 2024 17:01
Show Gist options
  • Save gekart/9f850a14640a4103695c7da16f41f278 to your computer and use it in GitHub Desktop.
Save gekart/9f850a14640a4103695c7da16f41f278 to your computer and use it in GitHub Desktop.
KORG monologue Preset Pretty Printer
# 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]))
@mojozezozo
Copy link

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.

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