Ho ho.
I made a small Python script to convert .aupreset files to .fxp format (VST presets).
Hopefully others may find it useful too.
Our use case for it was creating our factory presets for SurferEQ once in Logic and then converting them to other formats.
This script could be easily adapted to convert the other way around. (it uses the Construct library to describe the fxb format declaratively for both parsing and building)
Happy Holidays!
from construct import Array, BFloat32, Bytes, Const, Container, Enum, LazyBound, String, Struct, Switch, UBInt32, ULInt32
from os import path
import sys
from xml.dom import minidom
# fxp/fxb file format. (VST/Cubase's preset or "bank" files from before VST3 era)
# based on VST SDK's vst2.x/vstfxstore.h
# names as in the source
vst2preset = Struct('vst2preset',
Const(Bytes('chunkMagic', 4), 'CcnK'),
UBInt32('byteSize'),
Enum(Bytes('fxMagic', 4),
FXP_PARAMS = 'FxCk', FXP_OPAQUE_CHUNK = 'FPCh',
FXB_REGULAR = 'FxBk', FXB_OPAQUE_CHUNK = 'FBCh',
),
UBInt32('version'),
UBInt32('fxID'),
UBInt32('fxVersion'),
UBInt32('count'),
Switch('data', lambda ctx: ctx['fxMagic'], {
'FXP_PARAMS': Struct('data',
String('prgName', 28, padchar = '\0'),
Array(lambda ctx: ctx['_']['count'], BFloat32('params')),
),
'FXP_OPAQUE_CHUNK': Struct('data',
String('prgName', 28, padchar = '\0'),
UBInt32('size'),
Bytes('chunk', lambda ctx: ctx['size']),
),
'FXB_REGULAR': Struct('data',
Bytes('future', 128), # zeros
# Array of FXP_PARAMS vst2preset
Array(lambda ctx: ctx['_']['count'], LazyBound('presets', lambda: vst2preset)),
),
'FXB_OPAQUE_CHUNK': Struct('data',
Bytes('future', 128), # zeros
UBInt32('size'),
# Unknown format of internal chunk
Bytes('chunk', lambda ctx: ctx['size']),
),
}),
)
def get_aupreset_value_node_for_key(dom, key, value_tag):
for key_node in dom.getElementsByTagName('key'):
[key_data] = key_node.childNodes
if key_data.data == key:
break
else:
raise KeyError
# Advance to the value node.
node = key_node
while True:
node = node.nextSibling
if node.hasChildNodes():
value_node = node
break
assert value_node.tagName == value_tag
return value_node
def get_xml_node_data(node):
[data_node] = node.childNodes
return data_node.data
def get_aupreset_subtype_node(dom):
return get_aupreset_value_node_for_key(dom, 'subtype', 'integer')
def parse_aupreset(dom):
return {
'data': get_xml_node_data(get_aupreset_value_node_for_key(dom, 'jucePluginState', 'data')).decode('base64'),
'plugin_id_int': int(get_xml_node_data(get_aupreset_subtype_node(dom))),
}
[src_filename, dst_filename] = sys.argv[1:]
preset_name = path.split(src_filename)[1].rsplit('.', 1)[0]
au_preset = parse_aupreset(minidom.parseString(file(src_filename, 'rb').read()))
# Save to vst format
fxp_data = Container(
chunkMagic = 'CcnK',
byteSize = 0, # will fill later
fxMagic = 'FXP_OPAQUE_CHUNK',
version = 1,
fxID = au_preset['plugin_id_int'],
fxVersion = 1,
count = 0,
data = Container(
prgName = preset_name,
size = len(au_preset['data']),
chunk = au_preset['data'],
),
)
fxp_data.byteSize = len(vst2preset.build(fxp_data)) - 8
file(dst_filename, 'wb').write(vst2preset.build(fxp_data))
