fxp

VST2.x plugin preset parser.

Caution

Use this for parsing only presets (.fxp) and not soundbanks (.fxb).

Source code in fxp/__init__.py
class FXP:
    """VST2.x plugin preset parser.

    Caution:
        Use this for parsing only presets (.fxp) and not soundbanks (.fxb).
    """

    _fxp = c.Struct(
        "chunkMagic" / c.Const(b"CcnK"),
        "byteSize" / c.Int32ub,
        "fxMagic" / c.Enum(c.PaddedString(4, "ascii"), PresetType),  # type: ignore
        "version" / c.Const(1, c.Int32ub),
        "fxID" / c.PaddedString(4, "ascii"),
        "fxVersion" / c.Int32ub,
        "numParams" / c.Int32ub,
        "prgName" / c.PaddedString(28, "ascii"),
        "content"
        / c.IfThenElse(
            c.this["fxMagic"] == PresetType.REGULAR,
            c.Float32b[c.this["numParams"]],
            c.Struct(
                "size" / c.Int32ub,
                "data" / c.Int8ub[c.this["size"]],
            ),
        ),
    )

    def __init__(self, path: "str | os.PathLike") -> "None":
        """Create a new parser instance.

        Preset files using this format generally have .fxp extension although
        some plugins have their own file name extensions.

        Args:
            path (str | os.PathLike): The path of the preset file.
        """
        self._path = path
        self._struct = self._fxp.parse_file(path)  # type: ignore

    def is_opaque(self) -> "bool":
        """Whether preset data is stored in opaque chunks."""
        return self.preset_type == PresetType.OPAQUE

    def is_regular(self) -> "bool":
        """Whether preset data is stored in regular chunks."""
        return self.preset_type == PresetType.REGULAR

    def save(self, path: "str | os.PathLike | None" = None) -> "None":
        """Save the preset back into a file.

        Args:
            path (str | os.PathLike, optional): Defaults to None.
                The name or path of the file to be used for saving the preset.
                If None, the original file is overwritten.
        """
        self._fxp.build_file(self._struct, path or self._path)  # type: ignore

    def to_bytes(self) -> "bytes":
        """Dumps this object back as a stream of bytes."""
        return self._fxp.build(self._struct)

    @property
    def preset_type(self) -> "PresetType":
        """You most likely need `is_opaque` and `is_regular` instead of this."""
        return PresetType[self._struct["fxMagic"]]

    @preset_type.setter
    def preset_type(self, value: "PresetType") -> "None":
        self._struct["fxMagic"] = value

    @property
    def plugin_id(self) -> "str":
        """A four character unique ID identifying the plugin e.g. "XfsX".

        This is used by DAWs and plugins to verify the preset's integrity.

        Raises:
            ValueError: When set to a string whose length in ASCII
                representation is not equal to 4.
        """
        return self._struct["fxID"]

    @plugin_id.setter
    def plugin_id(self, value: "str") -> "None":
        if len(value.encode("ascii")) != 4:
            raise ValueError("Expected a string of size 4")
        self._struct["fxID"] = value

    @property
    def plugin_version(self) -> "int":
        """Plugin preset format version.

        This could be used by devs to create a backward incompatible preset
        format for future versions of a plugin, while still allowing newer
        versions to handle the legacy formats differently.
        """
        return self._struct["fxVersion"]

    @plugin_version.setter
    def plugin_version(self, value: "int") -> "None":
        self._struct["fxVersion"] = value

    @property
    def param_count(self) -> "int":
        """Number of parameters for which preset data is stored.

        Used only by regular presets.

        Raises:
            InvalidAttribute: When an opaque preset tries to set a value.
        """
        return self._struct["numParams"]

    @param_count.setter
    def param_count(self, value: "int") -> "None":
        if not self.is_regular():
            raise InvalidAttribute("param_count", PresetType.OPAQUE)
        self._struct["numParams"] = value

    @property
    def name(self) -> "str":
        """Name of the preset as saved by the plugin (in ASCII).

        This is not always the same as the name of the preset file.

        Raises:
            OverflowError: When it is set to a string whose length in ASCII
                representation exceeds 28 characters / bytes.
        """
        return self._struct["prgName"]

    @name.setter
    def name(self, value: "str") -> "None":
        if len(value.encode("ascii")) > 28:
            raise OverflowError("Length of encodeded string exceeds 28")
        self._struct["prgName"] = value

    @property
    def params(self) -> "list[float]":
        """Return the preset data used by presets using regular data chunks.

        Raises:
            InvalidAttribute: When preset doesn't use regular data chunks.

        Returns:
            list[float]: The list of parameter values (knobs, sliders etc.).
        """
        if not self.is_regular():
            raise InvalidAttribute("params", PresetType.OPAQUE)
        return list(self._struct["content"])  # TODO Test return type

    @params.setter
    def params(self, values: "list[float]") -> "None":
        if not self.is_regular():
            raise InvalidAttribute("params", PresetType.OPAQUE)
        if len(values) != self.param_count:
            raise ValueError(f"Expected a list of length {self.param_count}")
        self._struct["content"] = values

    @property
    def data(self) -> "bytes":
        """Return the preset data used by presets using opaque data chunks.

        Raises:
            InvalidAttribute: When preset doesn't use opaque data chunks.

        Returns:
            bytes: Plugin-specific representation of the preset data.
        """
        if not self.is_opaque():
            raise InvalidAttribute("data", PresetType.REGULAR)
        return bytes(self._struct["content"]["data"])

    @data.setter
    def data(self, value: "bytes") -> "None":
        if not self.is_opaque():
            raise InvalidAttribute("data", PresetType.REGULAR)
        content = self._struct["content"]
        content["size"] = len(value)
        content["data"] = value

data: bytes property writable

Return the preset data used by presets using opaque data chunks.

Exceptions:
  • InvalidAttribute – When preset doesn't use opaque data chunks.

Returns:
  • bytes – Plugin-specific representation of the preset data.

name: str property writable

Name of the preset as saved by the plugin (in ASCII).

This is not always the same as the name of the preset file.

Exceptions:
  • OverflowError – When it is set to a string whose length in ASCII representation exceeds 28 characters / bytes.

param_count: int property writable

Number of parameters for which preset data is stored.

Used only by regular presets.

Exceptions:
  • InvalidAttribute – When an opaque preset tries to set a value.

params: list[float] property writable

Return the preset data used by presets using regular data chunks.

Exceptions:
  • InvalidAttribute – When preset doesn't use regular data chunks.

Returns:
  • list[float] – The list of parameter values (knobs, sliders etc.).

plugin_id: str property writable

A four character unique ID identifying the plugin e.g. "XfsX".

This is used by DAWs and plugins to verify the preset's integrity.

Exceptions:
  • ValueError – When set to a string whose length in ASCII representation is not equal to 4.

plugin_version: int property writable

Plugin preset format version.

This could be used by devs to create a backward incompatible preset format for future versions of a plugin, while still allowing newer versions to handle the legacy formats differently.

preset_type: PresetType property writable

You most likely need is_opaque and is_regular instead of this.

__init__(self, path: str | os.PathLike) -> None special

Create a new parser instance.

Preset files using this format generally have .fxp extension although some plugins have their own file name extensions.

Parameters:
  • path (str | os.PathLike) – The path of the preset file.

Source code in fxp/__init__.py
def __init__(self, path: "str | os.PathLike") -> "None":
    """Create a new parser instance.

    Preset files using this format generally have .fxp extension although
    some plugins have their own file name extensions.

    Args:
        path (str | os.PathLike): The path of the preset file.
    """
    self._path = path
    self._struct = self._fxp.parse_file(path)  # type: ignore

is_opaque(self) -> bool

Whether preset data is stored in opaque chunks.

Source code in fxp/__init__.py
def is_opaque(self) -> "bool":
    """Whether preset data is stored in opaque chunks."""
    return self.preset_type == PresetType.OPAQUE

is_regular(self) -> bool

Whether preset data is stored in regular chunks.

Source code in fxp/__init__.py
def is_regular(self) -> "bool":
    """Whether preset data is stored in regular chunks."""
    return self.preset_type == PresetType.REGULAR

save(self, path: str | os.PathLike | None = None) -> None

Save the preset back into a file.

Parameters:
  • path (str | os.PathLike) – Defaults to None. The name or path of the file to be used for saving the preset. If None, the original file is overwritten.

Source code in fxp/__init__.py
def save(self, path: "str | os.PathLike | None" = None) -> "None":
    """Save the preset back into a file.

    Args:
        path (str | os.PathLike, optional): Defaults to None.
            The name or path of the file to be used for saving the preset.
            If None, the original file is overwritten.
    """
    self._fxp.build_file(self._struct, path or self._path)  # type: ignore

to_bytes(self) -> bytes

Dumps this object back as a stream of bytes.

Source code in fxp/__init__.py
def to_bytes(self) -> "bytes":
    """Dumps this object back as a stream of bytes."""
    return self._fxp.build(self._struct)