Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameters with the same names overwrite each other in VST3Plugin.parameters #292

Open
pcmbs opened this issue Feb 27, 2024 · 0 comments
Open

Comments

@pcmbs
Copy link

pcmbs commented Feb 27, 2024

Hi,

thanks for the nice library!

There is a problem arising from implementing VST3Plugin.parameters as a python dict to store the parameters of a VST3 plugin. In fact, as the parameter names are used as keys, if a synthesizer has several parameters with the same name (e.g., Diva envelope 1 and 2 have the same names), they overwrite each other.

This is not a major problem since parameters can still be accessed with the _parameters attribute since this one is implemented as list, but I guess it would be good to find a workaround, e.g., modifying normalize_python_parameter_name() to take an additional index argument (passed to the method by to_python_parameter_name()) which would be prepended to the parameter name. This would generate keys of the form <parameter-index>-<normalized-name> and would ensure that each synthesizer parameter generates a unique key for the dict. This could be implemented as:

def to_python_parameter_name(parameter: _AudioProcessorParameter) -> Optional[str]:
    if not parameter.name and not parameter.label:
        return None

    name = parameter.name
    if parameter.label and not parameter.label.startswith(":"):
        name = "{} {}".format(name, parameter.label)
    return normalize_python_parameter_name(name, index)


def normalize_python_parameter_name(name: str, index: int) -> str:
    name = name.lower().strip()
    # Special case: some plugins expose parameters with "#"/"♯" or "b"/"♭" in their names.
    name = name.replace("#", "_sharp").replace("♯", "_sharp").replace("♭", "_flat")
    # Replace all non-alphanumeric characters with underscores
    name_chars = [
        c if (c.isalpha() or c.isnumeric()) and c.isprintable() and ord(c) < 128 else "_"
        for c in name
    ]
    # Remove any double-underscores:
    name_chars = [a for a, b in zip(name_chars, name_chars[1:]) if a != b or b != "_"] + [
        name_chars[-1]
    ]
    # Remove any leading or trailing underscores:
    name = "".join(name_chars).strip("_")
    # Prepend parameter index to the name
    name = "".join((str(index), "-", name))
    return name

Here is an example of the issue using Diva:

from pedalboard import load_plugin

synth = load_plugin(
    path_to_plugin_file="C:/Program Files/Audio Plugins/VST3/u-he/Diva(x64).vst3",
    plugin_name="Diva",
)

### Get Diva parameters (excl. MIDI CC) using the synth.parameters dictionary {param_name: param_object}
params_from_dict = [param for name, param in synth.parameters.items() if not name.startswith("cc_")]

### Get Diva parameters (excl. MIDI CC) using the synth._parameters list [param_object]
params_from_list = [param for param in synth._parameters if not param.name.startswith("CC ")]

# print number of parameters
print("Number of parameters in params dict: ", len(params_from_dict))  # >> 191
print("Number of parameters in params list: ", len(params_from_list))  # >> 282

# So let print out the parameter names for the first and second envelopes for both synth.parameters and synth._parameters
# We can see in the `from list` column that the parameters of the second envelop have the same names as the parameters of the first one,
# and since the parameter names are used as keys in synth.parameters(), they overwrite each other in the dict.
print(f"{'':<14}{'from list':<14}{'from dict':<18}{'same?'}")
for i in range(33, 55):
    print(
        f"Parameter {i}: {params_from_list[i].name:10} -> {params_from_dict[i].name:15} "
        f"| {params_from_list[i].name == params_from_dict[i].name}"
    )

which output

Number of parameters in params dict:  191
Number of parameters in params list:  282
              from list     from dict         same?
Parameter 33: Attack     -> Attack          | True
Parameter 34: Decay      -> Decay           | True
Parameter 35: Sustain    -> Sustain         | True
Parameter 36: Release    -> Release         | True
Parameter 37: Velocity   -> Velocity        | True
Parameter 38: Model      -> Model           | True
Parameter 39: Trigger    -> Trigger         | True
Parameter 40: Quantise   -> Quantise        | True
Parameter 41: Curve      -> Curve           | True
Parameter 42: Release On -> Release On      | True
Parameter 43: KeyFollow  -> KeyFollow       | True
Parameter 44: Attack     -> Sync            | False
Parameter 45: Decay      -> Restart         | False
Parameter 46: Sustain    -> Waveform        | False
Parameter 47: Release    -> Phase           | False
Parameter 48: Velocity   -> Delay           | False
Parameter 49: Model      -> DepthMod Src1   | False
Parameter 50: Trigger    -> DepthMod Dpt1   | False
Parameter 51: Quantise   -> Rate            | False
Parameter 52: Curve      -> FreqMod Src1    | False
Parameter 53: Release On -> FreqMod Dpt     | False
Parameter 54: KeyFollow  -> Polarity        | False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant