├── requirements.txt ├── .gitignore ├── vst2preset.py ├── README.md └── fxp2aupreset.py /requirements.txt: -------------------------------------------------------------------------------- 1 | construct>=2.8.11 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Python 2 | bin/ 3 | include/ 4 | lib/ 5 | *.pyc 6 | -------------------------------------------------------------------------------- /vst2preset.py: -------------------------------------------------------------------------------- 1 | # fxp/fxb file format. (VST/Cubase's preset or "bank" files from before VST3 2 | # era) 3 | # based on VST SDK's vst2.x/vstfxstore.h 4 | # names as in the source 5 | 6 | import construct 7 | from construct import Array, Float32b, Bytes, Const, Container, Enum, \ 8 | LazyBound, Struct, Switch, Int32ub, Int32ul 9 | 10 | def getString(): 11 | try: 12 | # construct 2.8.11 13 | stringCtor = getattr(construct, 'String') 14 | return stringCtor(28, padchar='\0') 15 | except: 16 | # construct2.9.45 17 | stringCtor = getattr(construct, 'PaddedString') 18 | return stringCtor(28, 'ascii') 19 | 20 | vst2preset = Struct( 21 | "chunkMagic" / Const(b"CcnK"), 22 | "byteSize" / Int32ub, 23 | "fxMagic" / Enum(Bytes(4), 24 | FXP_PARAMS = b'FxCk', FXP_OPAQUE_CHUNK = b'FPCh', 25 | FXB_REGULAR = b'FxBk', FXB_OPAQUE_CHUNK = b'FBCh', 26 | ), 27 | "version" / Int32ub, 28 | "fxID" / Int32ub, 29 | "fxVersion" / Int32ub, 30 | "count" / Int32ub, 31 | "data" / Switch(lambda ctx: ctx.fxMagic, { 32 | 'FXP_PARAMS': "data" / Struct( 33 | "prgName" / getString(), 34 | Array(lambda ctx: ctx['_']['count'], "params" / Float32b), 35 | ), 36 | 'FXP_OPAQUE_CHUNK': "data" / Struct( 37 | "prgName" / getString(), 38 | "size" / Int32ub, 39 | "chunk" / Bytes(lambda ctx: ctx['size']), 40 | ), 41 | 'FXB_REGULAR': "data" / Struct( 42 | "future" / Bytes(128), # zeros 43 | # Array of FXP_PARAMS vst2preset 44 | Array(lambda ctx: ctx['_']['count'], "presets" / LazyBound(lambda: vst2preset)), 45 | ), 46 | 'FXB_OPAQUE_CHUNK': "data" / Struct( 47 | "future" / Bytes(128), # zeros 48 | "size" / Int32ub, 49 | # Unknown format of internal chunk 50 | "chunk" / Bytes(lambda ctx: ctx['size']), 51 | ), 52 | }), 53 | ) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fxp2aupreset 2 | === 3 | 4 | `fxp2aupreset` is a tool for converting VST presets to AU presets. 5 | 6 | Installation 7 | --- 8 | 9 | `fxp2aupreset` has modest requirements; it just needs the [construct][construct] library. 10 | 11 | Running 12 | --- 13 | 14 | `fxp2aupreset` requires information about the instrument whose presets are to 15 | be converted. You can retrieve that information by finding your instrument using 16 | 17 | `auval -a` 18 | 19 | You'll see a bunch of lines that look like this: 20 | 21 | ... 22 | aufx mcmp appl - Apple: AUMultibandCompressor 23 | aufx mrev appl - Apple: AUMatrixReverb 24 | aufx nbeq appl - Apple: AUNBandEQ 25 | aufx nsnd appl - Apple: AUNetSend 26 | ... 27 | 28 | The first three fields in this output (before the `-`) are the _type_, 29 | _subtype_ and _manufacturer_ of the instrument. Each of these three fields are 30 | four characters long. Additionally, you'll need to figure out which key is used 31 | for the preset data in aupreset files generated by the instrument. 32 | 33 | These four parameters can be specified manually: 34 | 35 | ./fxp2aupreset.py --type aufx --subtype mcmp --manufacturer aapl --state_key vstdata 36 | 37 | or (and this is the recommended method) you can give `fxp2aupreset` an example 38 | `.aupreset` file generated by the instrument, and it'll figure out the rest: 39 | 40 | ./fxp2aupreset.py --example 41 | 42 | License 43 | --- 44 | 45 | This software is released under a 46 | [Creative Commons Attribution-ShareAlike 4.0 International][cc-license] 47 | license. 48 | 49 | Original version by Colin Barry (colin@loomer.co.uk), based on `aupreset` to 50 | `fxp` converter found [here][script-bbpost]. 51 | 52 | [construct]: https://pypi.python.org/pypi/construct/2.5.2 53 | [cc-license]: http://creativecommons.org/licenses/by-sa/4.0/legalcode 54 | [script-bbpost]: http://www.rawmaterialsoftware.com/viewtopic.php?f=8&t=8337 55 | -------------------------------------------------------------------------------- /fxp2aupreset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # vst to au preset convert 4 | # Colin Barry / colin@loomer.co.uk / www.loomer.co.uk 5 | # 6 | # Based on the aupreset to fxp converter found here 7 | # http://www.rawmaterialsoftware.com/viewtopic.php?f=8&t=8337 8 | 9 | import argparse 10 | import re 11 | from os import path, listdir, getcwd, chdir 12 | import sys 13 | import fnmatch 14 | from xml.dom import minidom 15 | from base64 import b64encode 16 | from vst2preset import vst2preset 17 | 18 | # converts a four character identifier to an integer 19 | def id_to_integer(id): 20 | if (len(id) != 4): 21 | print id, "should be exactly 4 characters length." 22 | sys.exit(1) 23 | 24 | return str((ord(id[0]) << 24) + (ord(id[1]) << 16) + (ord(id[2]) << 8) + ord(id[3])) 25 | 26 | # converts an integer to a four character identifier 27 | def integer_to_id(integer): 28 | 29 | integer = int(integer) 30 | 31 | return chr(integer>>24 & 0x7F) + chr(integer>>16 & 0x7F) + chr(integer>> 8 & 0x7F) + chr(integer & 0x7F) 32 | 33 | # adds an element to an xml dom tree 34 | def add_key_and_value(doc, keyname, value, value_type, parent_element): 35 | key_element = doc.createElement("key") 36 | key_element_text = doc.createTextNode(keyname); 37 | key_element.appendChild(key_element_text) 38 | parent_element.appendChild(key_element) 39 | 40 | data_element = doc.createElement(value_type) 41 | data_element_text = doc.createTextNode(value); 42 | data_element.appendChild(data_element_text) 43 | parent_element.appendChild(data_element) 44 | 45 | 46 | # converts the passed fxp file, creating the equivalent aupreset. 47 | def convert(filename, manufacturer, subtype, type, state_key): 48 | print "Opening fxp preset file", path.abspath(filename) 49 | 50 | # extract fxp structure 51 | f = open(filename, 'rb') 52 | fxp = vst2preset.parse(f.read()) 53 | f.close() 54 | 55 | EXPECTED = 'FXP_OPAQUE_CHUNK' 56 | if (fxp['fxMagic'] != EXPECTED): 57 | print ".fxp preset is not in opaque chunk format {} (but {}), and so can not be converted.".format(EXPECTED, fxp['fxMagic']) 58 | return 59 | 60 | preset_name = path.splitext(filename)[0] 61 | 62 | # convert data chunk to base64 63 | base64data = b64encode(fxp['data']['chunk']) 64 | 65 | # create the aupreset dom 66 | # set the DOCTYPE 67 | imp = minidom.DOMImplementation() 68 | doctype = imp.createDocumentType( 69 | qualifiedName='plist', 70 | publicId="-//Apple//DTD PLIST 1.0//EN", 71 | systemId="http://www.apple.com/DTDs/PropertyList-1.0.dtd", 72 | ) 73 | 74 | doc = imp.createDocument(None, 'plist', doctype) 75 | 76 | # create the plist element 77 | nodeList = doc.getElementsByTagName("plist") 78 | plist = nodeList.item(0) 79 | plist.setAttribute("version", "1.0"); 80 | 81 | dict = doc.createElement("dict") 82 | plist.appendChild(dict) 83 | 84 | # create document nodes 85 | add_key_and_value(doc, state_key, base64data, "data", dict); 86 | add_key_and_value(doc, "manufacturer", manufacturer, "integer", dict); 87 | add_key_and_value(doc, "name", preset_name, "string", dict); 88 | add_key_and_value(doc, "subtype", subtype, "integer", dict); 89 | add_key_and_value(doc, "type", type, "integer", dict); 90 | add_key_and_value(doc, "version", "0", "integer", dict); 91 | 92 | aupreset_name = preset_name + ".aupreset" 93 | f = open(aupreset_name, "wb") 94 | f.write(doc.toxml("utf-8")) 95 | f.close() 96 | print "Created", path.abspath(aupreset_name) 97 | print 98 | 99 | def au_param_from_preset_dict(param, d): 100 | return d[param].childNodes[0].data 101 | 102 | def au_parameters_from_example(example_file): 103 | with open(example_file, 'r') as fp: 104 | parsed_example = minidom.parse(fp) 105 | 106 | # Convert list of XML keys and values into proper dict for easier handling 107 | aupreset_keys = filter( 108 | lambda x: x.nodeType == minidom.Node.ELEMENT_NODE, 109 | parsed_example.documentElement.getElementsByTagName('dict')[0] 110 | .getElementsByTagName('key')) 111 | 112 | # nextSibling twice to skip over newline text nodes 113 | aupreset_dict = dict({ (k.childNodes[0].data, k.nextSibling.nextSibling) 114 | for k in aupreset_keys }) 115 | 116 | au_type = au_param_from_preset_dict('type', aupreset_dict) 117 | au_subtype = au_param_from_preset_dict('subtype', aupreset_dict) 118 | au_manufacturer = au_param_from_preset_dict('manufacturer', aupreset_dict) 119 | 120 | data_elements = (parsed_example.documentElement 121 | .getElementsByTagName('dict')[0] 122 | .getElementsByTagName('data')) 123 | 124 | state_key = None 125 | 126 | if len(data_elements) > 1: 127 | # previousSibling twice to skip over newline text nodes 128 | state_key = (data_elements[1].previousSibling.previousSibling 129 | .childNodes[0].data) 130 | print ("Guessing '%s' for state key, use --state_key to override" % 131 | (state_key)) 132 | else: 133 | print "Couldn't infer state key from example" 134 | 135 | return (au_type, au_subtype, au_manufacturer, state_key) 136 | 137 | DESCRIPTION=""" 138 | .fxp to .aupreset converter" 139 | Original By: Colin Barry / colin@loomer.co.uk 140 | Modifications By: Alex Rasmussen / alexras@acm.org 141 | """ 142 | 143 | def get_arguments(parser): 144 | args = parser.parse_args() 145 | 146 | if (args.example is not None): 147 | args.type, args.subtype, args.manufacturer, args.state_key = ( 148 | au_parameters_from_example(args.example)) 149 | 150 | print "\ngiven example \"{}\" corresponds to:\n".format(args.example) 151 | print "{} --type {} --subtype {} --manufacturer {} --state_key {} \"{}\"\n".format(sys.argv[0], integer_to_id(args.type), integer_to_id(args.subtype), integer_to_id(args.manufacturer), args.state_key, args.path); 152 | 153 | else: 154 | for attr in ['type', 'subtype', 'manufacturer']: 155 | if getattr(args, attr) is None: 156 | sys.exit("ERROR: Must provide a %s or an example aupreset for " 157 | "the instrument" % (attr)) 158 | 159 | args.type = id_to_integer(args.type) 160 | args.subtype = id_to_integer(args.subtype) 161 | args.manufacturer = id_to_integer(args.manufacturer) 162 | 163 | if args.state_key is None: 164 | sys.exit("ERROR: Must provide a state key or an example aupreset for " 165 | "the instrument from which we can infer one") 166 | 167 | return args 168 | 169 | def main(): 170 | parser = argparse.ArgumentParser(description=DESCRIPTION) 171 | parser.add_argument('path', help='path to the directory of .fxp ' 172 | 'and .fxb files to convert') 173 | 174 | parser.add_argument('--type', '-t', help="the four-character type code for " 175 | "the preset's audio unit") 176 | parser.add_argument('--subtype', '-s', help="the four character subtype " 177 | "code for the preset's audio unit file") 178 | parser.add_argument('--manufacturer', '-m', help="the four character " 179 | "manufacturer code for the preset's audio unit file") 180 | parser.add_argument("--state_key", '-k', help="the key in the aupreset that " 181 | "stores the preset's state") 182 | parser.add_argument('--example', '-x', help='an example .aupreset file ' 183 | 'from which to infer type, subtype, manufacturer and ' 184 | 'state key') 185 | 186 | args = get_arguments(parser) 187 | 188 | # enumerate all the .fxp files in the current directory 189 | chdir(args.path) 190 | 191 | fxpFileList = fnmatch.filter(listdir(args.path), '*.[Ff][Xx][BbPp]') 192 | 193 | if (len(fxpFileList) == 0): 194 | print "No .fxp or .fxb files found in '%s'" % (getcwd()) 195 | 196 | for fname in fxpFileList: 197 | convert(fname, args.manufacturer, args.subtype, args.type, 198 | args.state_key) 199 | 200 | if __name__ == "__main__": 201 | sys.exit(main()) 202 | --------------------------------------------------------------------------------