mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
Added 4klang binary format construct.
This commit is contained in:
parent
7eb473e67e
commit
f58adab782
1
4kp-convert/.gitignore
vendored
Normal file
1
4kp-convert/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
__pycache__
|
0
4kp-convert/__init__.py
Normal file
0
4kp-convert/__init__.py
Normal file
356
4kp-convert/file_format.py
Normal file
356
4kp-convert/file_format.py
Normal file
@ -0,0 +1,356 @@
|
||||
import typing
|
||||
from construct import Struct, Array, Bytes, Int32ul, Enum, Int8ul, Const, Switch, this, Error, Padded, FlagsEnum, PaddedString, Container, ListContainer, EnumIntegerString, EnumInteger
|
||||
from enum import IntEnum, IntFlag
|
||||
|
||||
MAX_POLYPHONY = 2
|
||||
MAX_INSTRUMENTS = 16
|
||||
MAX_UNITS = 64
|
||||
MAX_UNIT_SLOTS = 16
|
||||
MAX_INSTRUMENT_NAME_LENGTH = 64
|
||||
|
||||
DEFAULT_GLOBAL_NAME = b'GlobalUnitsStoredAs.4ki '
|
||||
|
||||
class VersionTag(IntEnum or IntFlag):
|
||||
VERSION_TAG_10 = 0x30316b34 # 4k10
|
||||
VERSION_TAG_11 = 0x31316b34 # 4k11
|
||||
VERSION_TAG_12 = 0x32316b34 # 4k12
|
||||
VERSION_TAG_13 = 0x33316b34 # 4k13
|
||||
VERSION_TAG_CURRENT = 0x34316b34 # 4k14
|
||||
|
||||
class UnitId(IntEnum or IntFlag):
|
||||
M_NONE = 0x0
|
||||
M_ENV = 0x1
|
||||
M_VCO = 0x2
|
||||
M_VCF = 0x3
|
||||
M_DST = 0x4
|
||||
M_DLL = 0x5
|
||||
M_FOP = 0x6
|
||||
M_FST = 0x7
|
||||
M_PAN = 0x8
|
||||
M_OUT = 0x9
|
||||
M_ACC = 0xA
|
||||
M_FLD = 0xB
|
||||
M_GLITCH = 0xC
|
||||
NUM_MODULES = 0xD
|
||||
|
||||
class VCOFlags(IntEnum or IntFlag):
|
||||
VCO_SINE = 0x01
|
||||
VCO_TRISAW = 0x02
|
||||
VCO_PULSE = 0x04
|
||||
VCO_NOISE = 0x08
|
||||
VCO_LFO = 0x10
|
||||
VCO_GATE = 0x20
|
||||
VCO_STEREO = 0x40
|
||||
|
||||
class VCFType(IntEnum or IntFlag):
|
||||
VCF_LOWPASS = 0x1
|
||||
VCF_HIGHPASS = 0x2
|
||||
VCF_BANDPASS = 0x4
|
||||
VCF_BANDSTOP = 0x3
|
||||
VCF_ALLPASS = 0x7
|
||||
VCF_PEAK = 0x8
|
||||
VCF_STEREO = 0x10
|
||||
|
||||
class FOPFlags(IntEnum or IntFlag):
|
||||
FOP_POP = 0x1
|
||||
FOP_ADDP = 0x2
|
||||
FOP_MULP = 0x3
|
||||
FOP_PUSH = 0x4
|
||||
FOP_XCH = 0x5
|
||||
FOP_ADD = 0x6
|
||||
FOP_MUL = 0x7
|
||||
FOP_ADDP2 = 0x8
|
||||
FOP_LOADNOTE = 0x9
|
||||
FOP_MULP2 = 0xA
|
||||
|
||||
class FSTType(IntEnum or IntFlag):
|
||||
FST_SET = 0x00
|
||||
FST_ADD = 0x10
|
||||
FST_MUL = 0x20
|
||||
FST_POP = 0x40
|
||||
|
||||
class ACCFlags(IntEnum or IntFlag):
|
||||
ACC_OUT = 0x0
|
||||
ACC_AUX = 0x8
|
||||
|
||||
formatnone = Bytes(MAX_UNIT_SLOTS - 1)
|
||||
|
||||
formatenv = Struct(
|
||||
"attack" / Int8ul,
|
||||
"decay" / Int8ul,
|
||||
"sustain" / Int8ul,
|
||||
"release" / Int8ul,
|
||||
"gain" / Int8ul,
|
||||
)
|
||||
|
||||
formatvco = Struct(
|
||||
"transpose" / Int8ul,
|
||||
"detune" / Int8ul,
|
||||
"phhaseofs" / Int8ul,
|
||||
"gate" / Int8ul,
|
||||
"color" / Int8ul,
|
||||
"shape" / Int8ul,
|
||||
"gain" / Int8ul,
|
||||
"flags" / FlagsEnum(Int8ul, VCOFlags),
|
||||
)
|
||||
|
||||
# TODO: how do we add this conveniently?
|
||||
formatvco11 = Struct(
|
||||
"transpose" / Int8ul,
|
||||
"detune" / Int8ul,
|
||||
"phhaseofs" / Int8ul,
|
||||
"color" / Int8ul,
|
||||
"shape" / Int8ul,
|
||||
"gain" / Int8ul,
|
||||
"flags" / FlagsEnum(Int8ul, VCOFlags),
|
||||
)
|
||||
|
||||
formatvcf = Struct(
|
||||
"freq" / Int8ul,
|
||||
"res" / Int8ul,
|
||||
"type" / Enum(Int8ul, VCFType),
|
||||
)
|
||||
|
||||
formatdst = Struct(
|
||||
"drive" / Int8ul,
|
||||
"snhfreq" / Int8ul,
|
||||
"stereo" / Int8ul,
|
||||
)
|
||||
|
||||
formatdll = Struct(
|
||||
"pregain" / Int8ul,
|
||||
"dry" / Int8ul,
|
||||
"feedback" / Int8ul,
|
||||
"damp" / Int8ul,
|
||||
"freq" / Int8ul,
|
||||
"depth" / Int8ul,
|
||||
"delay" / Int8ul,
|
||||
"count" / Int8ul,
|
||||
"guidelay" / Int8ul,
|
||||
"synctype" / Int8ul, # TODO: Enum? Where?
|
||||
"leftreverb" / Int8ul,
|
||||
"reverb" / Int8ul,
|
||||
)
|
||||
|
||||
# TODO: Implement the migrations
|
||||
formatdll10 = Struct(
|
||||
"pregain" / Int8ul,
|
||||
"dry" / Int8ul,
|
||||
"feedback" / Int8ul,
|
||||
"damp" / Int8ul,
|
||||
"delay" / Int8ul,
|
||||
"count" / Int8ul,
|
||||
"guidelay" / Int8ul,
|
||||
"synctype" / Int8ul, # TODO: Enum? Where?
|
||||
"leftreverb" / Int8ul,
|
||||
"reverb" / Int8ul,
|
||||
)
|
||||
|
||||
formatfop = Struct(
|
||||
"flags"/ FlagsEnum(Int8ul, FOPFlags),
|
||||
)
|
||||
|
||||
formatfst = Struct(
|
||||
"amount" / Int8ul,
|
||||
"type" / Enum(Int8ul, FSTType),
|
||||
"dest_stack" / Int8ul,
|
||||
"dest_unit" / Int8ul,
|
||||
"dest_slot" / Int8ul,
|
||||
"dest_id" / Int8ul,
|
||||
)
|
||||
|
||||
formatpan = Struct(
|
||||
"panning" / Int8ul,
|
||||
)
|
||||
|
||||
formatout = Struct(
|
||||
"gain" / Int8ul,
|
||||
"auxsend" / Int8ul,
|
||||
)
|
||||
|
||||
formatacc = Struct(
|
||||
"flags" / FlagsEnum(Int8ul, ACCFlags),
|
||||
)
|
||||
|
||||
formatfld = Struct(
|
||||
"value" / Int8ul,
|
||||
)
|
||||
|
||||
formatglitch = Struct(
|
||||
"active" / Int8ul,
|
||||
"dry" / Int8ul,
|
||||
"dsize" / Int8ul,
|
||||
"dpitch" / Int8ul,
|
||||
"delay" / Int8ul,
|
||||
"guidelay" / Int8ul,
|
||||
)
|
||||
|
||||
# TODO: unclear what this does, find out.
|
||||
formatnummodules = Struct(
|
||||
"placeholder" / Int8ul,
|
||||
)
|
||||
|
||||
format4ku = Struct(
|
||||
"id" / Enum(Int8ul, UnitId),
|
||||
"slots" / Padded(
|
||||
MAX_UNIT_SLOTS - 1,
|
||||
Switch(
|
||||
keyfunc = this.id,
|
||||
cases = {
|
||||
UnitId.M_ENV.name: formatenv,
|
||||
UnitId.M_VCO.name: formatvco,
|
||||
UnitId.M_VCF.name: formatvcf,
|
||||
UnitId.M_DST.name: formatdst,
|
||||
UnitId.M_DLL.name: formatdll,
|
||||
UnitId.M_FOP.name: formatfop,
|
||||
UnitId.M_FST.name: formatfst,
|
||||
UnitId.M_PAN.name: formatpan,
|
||||
UnitId.M_OUT.name: formatout,
|
||||
UnitId.M_ACC.name: formatacc,
|
||||
UnitId.M_FLD.name: formatfld,
|
||||
UnitId.M_GLITCH.name: formatglitch,
|
||||
UnitId.NUM_MODULES.name: formatnummodules,
|
||||
UnitId.M_NONE.name: formatnone,
|
||||
},
|
||||
default = formatnone,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
format4ki = Struct(
|
||||
"versionTag" / Enum(Int32ul, VersionTag),
|
||||
"instrumentName" / PaddedString(MAX_INSTRUMENT_NAME_LENGTH, 'utf-8'),
|
||||
"units" / Array(MAX_UNITS, format4ku),
|
||||
)
|
||||
|
||||
format4kp = Struct(
|
||||
"versionTag" / Enum(Int32ul, VersionTag),
|
||||
"polyphony" / Int32ul,
|
||||
"instrumentNames" / Array(MAX_INSTRUMENTS, PaddedString(MAX_INSTRUMENT_NAME_LENGTH, 'utf-8')),
|
||||
"instrumentValues" / Array(MAX_INSTRUMENTS * MAX_UNITS, format4ku),
|
||||
"globalValues" / Array(MAX_UNITS, format4ku),
|
||||
)
|
||||
|
||||
class FormatConverter:
|
||||
builtins = ['copy', 'search', 'search_all', 'update']
|
||||
|
||||
@staticmethod
|
||||
def ConstructToDictTrivial(construct: typing.Any) -> dict:
|
||||
'''
|
||||
Convert a construct container to a dictionary for stupid-simple
|
||||
json and yaml dumps. Those do not work with sointu, as its json
|
||||
and yaml formats are simpler and more structured than 4klang's
|
||||
binary format.
|
||||
|
||||
Parameters:
|
||||
construct (typing.Any): Construct to convert to dictionary.
|
||||
|
||||
Returns:
|
||||
result (dict): json-serializable dictionary.
|
||||
|
||||
'''
|
||||
if type(construct) is Container:
|
||||
result = {}
|
||||
children = list(filter(lambda id: not id.startswith('_') and not id in FormatConverter.builtins, dir(construct)))
|
||||
for child in children:
|
||||
result[child] = FormatConverter.ConstructToDict(getattr(construct, child))
|
||||
return result
|
||||
elif type(construct) is ListContainer:
|
||||
result = []
|
||||
for child in construct:
|
||||
result.append(FormatConverter.ConstructToDict(child))
|
||||
return result
|
||||
elif type(construct) is EnumIntegerString:
|
||||
return str(construct)
|
||||
elif type(construct) is EnumInteger:
|
||||
return int(construct)
|
||||
elif type(construct) is int:
|
||||
return int(construct)
|
||||
elif type(construct) is bytes:
|
||||
return construct.decode('utf-8')
|
||||
elif type(construct) is bool:
|
||||
return construct
|
||||
elif type(construct) is str:
|
||||
return str(construct).split('\x00')[0]
|
||||
|
||||
raise Exception("Unrecognized construct type: {}".format(type(construct)))
|
||||
|
||||
# TODO: It needs to be verified whether those identifiers
|
||||
# are the correct ones for sointu.
|
||||
@staticmethod
|
||||
def UnitTypeName(id: UnitId) -> str:
|
||||
if id == UnitId.M_ACC.name:
|
||||
return "accumulate"
|
||||
elif id == UnitId.M_DLL.name:
|
||||
return "delay"
|
||||
elif id == UnitId.M_DST.name:
|
||||
return "distort"
|
||||
elif id == UnitId.M_ENV.name:
|
||||
return "envelope"
|
||||
elif id == UnitId.M_FLD.name:
|
||||
return "load"
|
||||
elif id == UnitId.M_FOP.name:
|
||||
# TODO: depending on the arithmetic handling in sointu,
|
||||
# this might not be the correct approach
|
||||
return "arithmetic"
|
||||
elif id == UnitId.M_FST.name:
|
||||
return "store"
|
||||
elif id == UnitId.M_GLITCH.name:
|
||||
return "glitch"
|
||||
elif id == UnitId.M_OUT.name:
|
||||
return "out"
|
||||
elif id == UnitId.M_PAN.name:
|
||||
return "pan"
|
||||
elif id == UnitId.M_VCF.name:
|
||||
return "filter"
|
||||
elif id == UnitId.M_VCO.name:
|
||||
return "oscillator"
|
||||
elif id == UnitId.M_NONE.name:
|
||||
return "none"
|
||||
else:
|
||||
return "unsupported"
|
||||
|
||||
@staticmethod
|
||||
def UnitContainerToDict(construct: typing.Any) -> dict:
|
||||
unitType = FormatConverter.UnitTypeName(construct.id)
|
||||
|
||||
slots = {}
|
||||
for (name, value) in construct.slots.items():
|
||||
if name == '_io':
|
||||
continue
|
||||
|
||||
slots[name] = value
|
||||
|
||||
return {
|
||||
"type": unitType,
|
||||
"parameters": slots,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def PatchContainerToDict(construct: typing.Any) -> dict:
|
||||
instruments = []
|
||||
for instrumentIndex in range(MAX_INSTRUMENTS):
|
||||
instrument = []
|
||||
for unitIndex in range(MAX_UNIT_SLOTS):
|
||||
valueIndex = instrumentIndex*MAX_UNIT_SLOTS + unitIndex
|
||||
|
||||
# Skip empty units.
|
||||
if construct.instrumentValues[valueIndex].id == UnitId.M_NONE.name:
|
||||
continue
|
||||
|
||||
unitDict = FormatConverter.UnitContainerToDict(construct.instrumentValues[valueIndex])
|
||||
instrument.append(unitDict)
|
||||
|
||||
if instrument != []:
|
||||
instruments.append({
|
||||
"numvoices": 1,
|
||||
"units": instrument,
|
||||
})
|
||||
|
||||
return {
|
||||
"patch": instruments
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def migrate(fromVersion: VersionTag, toVersion: VersionTag) -> typing.Any:
|
||||
return None
|
11
4kp-convert/tool.py
Normal file
11
4kp-convert/tool.py
Normal file
@ -0,0 +1,11 @@
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
from textwrap import indent
|
||||
from file_format import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
parsedFileContent = format4kp.parse_file(sys.argv[1])
|
||||
|
||||
sointuFormat = FormatConverter.PatchContainerToDict(parsedFileContent)
|
||||
print(yaml.dump(sointuFormat, indent=4))
|
Loading…
Reference in New Issue
Block a user