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

Cannot load .fxp file for Serum #277

Open
turian opened this issue Nov 18, 2023 · 8 comments
Open

Cannot load .fxp file for Serum #277

turian opened this issue Nov 18, 2023 · 8 comments

Comments

@turian
Copy link

turian commented Nov 18, 2023

I am trying to load a factory FXP preset for Serum:

synth_plugin = load_plugin("/Library/Audio/Plug-Ins/Components/Serum.component")
synth_plugin.load_preset("/Library/Audio/Presets/Xfer Records/Serum Presets/Presets/Plucked/PL Crusty Pluck [LCV].fxp")

The problem is that synth_plugin is AudioUnitPlugin, not VST3Plugin, and AudioUnitPlugin doesn't have load_preset method.

If instead I try:

synth_plugin = load_plugin("/Library/Audio/Plug-Ins/VST/Serum.vst")

I get this error:

  File "/opt/homebrew/anaconda3/envs/faceswap/lib/python3.10/site-packages/pedalboard/_pedalboard.py", line 781, in load_plugin
    raise ImportError(
ImportError: Failed to load plugin as VST3Plugin or AudioUnitPlugin. Errors were:
	VST3Plugin: Unable to load plugin /Library/Audio/Plug-Ins/VST/Serum.vst: unsupported plugin format or load failure.
	AudioUnitPlugin: Unable to load plugin /Library/Audio/Plug-Ins/VST/Serum.vst: unsupported plugin format or load failure. macOS requires plugin files to be moved to /Library/Audio/Plug-Ins/Components/ or ~/Library/Audio/Plug-Ins/Components/ before loading.

How can I load a particular Serum preset in pedalboard, to get a particular instrument sound?

@0xdevalias
Copy link

0xdevalias commented Dec 2, 2023

@zfarrell13
Copy link

@turian did you ever find a way to do this? ive been trying for the past few days with all of the methods and workarounds from above but no luck with serum sepcifically

@0xdevalias
Copy link

did you ever find a way to do this?

These 2 PR's look super useful, assuming one or the other (or both) eventually lands:

@Ma5onic
Copy link

Ma5onic commented Apr 27, 2024

Serum and its presets are in vst2 format. VST3 support is apparently in testing and not released to the public yet.
VST3 plugins usually have .vst3 file extensions, yours is just .vst.

The current pedalboard code only accepts loading for VST3 presets:
https://github.com/spotify/pedalboard/pull/67/files

      .def("load_preset",
           &ExternalPlugin<juce::VST3PluginFormat>::loadPresetData,
           "Load a VST3 preset file in .vstpreset format.",
           py::arg("preset_file_path"))

In my opinion, load_preset should be be used to load fxb/fxp formats, and the current load_preset implementation should be renamed to load_vst3_preset or something similar, to distinguish between the two.

relevant vst2 juce preset code to load fxb/fxp formats:

    //=======================================================
    void getStateInformation (MemoryBlock& mb) override                  { saveToFXBFile (mb, true); }
    void getCurrentProgramStateInformation (MemoryBlock& mb) override    { saveToFXBFile (mb, false); }

    void setStateInformation (const void* data, int size) override               { loadFromFXBFile (data, (size_t) size); }
    void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, (size_t) size); }

    //=======================================================

header:

    /** Attempts to reload a VST plugin's state from some FXB or FXP data. */
    static bool loadFromFXBFile (AudioPluginInstance* plugin, const void* data, size_t dataSize);

    /** Attempts to save a VST's state to some FXP or FXB data. */
    static bool saveToFXBFile (AudioPluginInstance* plugin, MemoryBlock& result, bool asFXB);

@0xdevalias I like your writeup/RE notes...
You could take a look at https://github.com/DBraun/DawDreamer in the meantime. It supports serum preset loading, but serum hasn't been tested on mac yet, nor does it support saving presets yet.

Usage: https://github.com/DBraun/DawDreamer/wiki/Plugin-Processor

DawDreamer Example:

import dawdreamer as daw

SAMPLE_RATE = 44100
BUFFER_SIZE = 128 # Parameters will undergo automation at this buffer/block size.
SYNTH_PLUGIN = "/path/to/synth.dll"  # extensions: .dll, .vst3, .vst, .component

engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE)

# Make a processor and give it the unique name "my_synth", for later use.
synth = engine.make_plugin_processor("my_synth", SYNTH_PLUGIN)
assert synth.get_name() == "my_synth"

# Plugins can show their UI.
synth.open_editor()  # Open the editor, make changes, and close
synth.save_state('/path/to/state1')
# Next time, we can load_state without using open_editor.
synth.load_state('/path/to/state1')

# For some plugins, it's possible to load presets:
synth.load_preset('/path/to/preset.fxp')
synth.load_vst3_preset('/path/to/preset.vstpreset')

@0xdevalias
Copy link

0xdevalias commented Apr 29, 2024

VST3 support is apparently in testing and not released to the public yet.

Serum version 136b8 seems to come with a VST3 version.

I like your writeup/RE notes...

@Ma5onic Thanks :) 🖤 Skimming your GH repo's/forks looks like a bunch of interesting stuff too. Kind of interested what you're working on.. (can DM me on twitter if you're open to chatting more)

You could take a look at DBraun/DawDreamer in the meantime. It supports serum preset loading, but serum hasn't been tested on mac yet, nor does it support saving presets yet.

Oo, nice. I'd heard mention of DawDreamer but never looked closer at it; will definitely be bumping it up my list :)

@0xdevalias
Copy link

0xdevalias commented May 20, 2024

Just wanted to note that #297 just got merged, which may provide one potential angle of a solution here:

Apologies that this took so long - I've just made a couple changes and merged this, and it should be available as of v0.9.6 (released later today).

I did rename this property to raw_state instead of just state, as the state data is often (but not always) encoded in a format that I hope we can parse and expose as a .state parameter later. (i.e.: if the state of a VST3 is valid XML, Pedalboard could unwrap and parse that XML directly to make the client code simpler.)

Originally posted by @psobot in #297 (comment)

Docs:

@0xdevalias
Copy link

As an example of what the XML part of the .raw_state output from #297 looks like, when loading a fresh instance of Serum, I used the following helpers/code:

def is_vst3_xml(raw_state):
    """
    Check if the raw state looks like VST3 XML.

    This function checks if the 9th to 13th bytes are '<?xml' and the last byte is a null byte (0x00).

    Args:
      raw_state (bytes): The raw state to check.

    Returns:
      bool: True if the raw state looks like VST3 XML, False otherwise.
    """
    return (
            len(raw_state) > 13 and
            raw_state[8:13] == b'<?xml' and
            raw_state[-1] == 0x00
    )


def extract_vst3_xml(raw_state):
    """
    Extract the XML content from the VST3 raw state if it is valid.

    Args:
      raw_state (bytes): The raw state to extract XML from.

    Returns:
      str or None: The extracted XML content if valid, otherwise None.
    """
    if is_vst3_xml(raw_state):
        try:
            # Extract the bytes between the 8-byte header and the null byte
            xml_part = raw_state[8:-1].decode('utf-8')
            return xml_part
        except UnicodeDecodeError:
            pass
    return None

Along with this code to write out the XML part to a file:

# Check and extract XML from initial state
if is_vst3_xml(initial_synth_raw_state):
    initial_synth_state_xml = extract_vst3_xml(initial_synth_raw_state)
    with open(args.out_state_file_initial_xml, 'w') as f:
        f.write(initial_synth_state_xml)
        print(f"Initial raw state XML written to {args.out_state_file_initial_xml}")
else:
    print("Initial state does not look like VST3 XML.")

# Check and extract XML from new state
if is_vst3_xml(new_synth_raw_state):
    new_synth_state_xml = extract_vst3_xml(new_synth_raw_state)
    with open(args.out_state_file_new_xml, 'w') as f:
        f.write(new_synth_state_xml)
        print(f"New raw state XML written to {args.out_state_file_new_xml}")
else:
    print("New state does not look like VST3 XML.")

Which, after formatting it, gave me output like this for the initial state after loading Serum:

<?xml version="1.0" encoding="UTF-8"?>
<VST3PluginState>
    <IComponent>6365.3EP6c1.bUTcEGeShDBQEBAMHnBgXEzTEDRKNP4cOg3HRnhH5TKRasDnkuZM.AET5T5iIi.p0VnLpfLsUZFasfefCSsXssBSzQpFTnnCXYnPlLVvfLhkoEKNNo2O18kMQRxBCiDd6ucl6d28dO26t6+8b9Qd2bxCOOOwytkzu1c1y5MpLLGkLoWStV5H6xHh1kYDsKqHZ24EQ65RDsK6HZWWinc4DQ65VDsK2HZ24GQ6tfHZ2EFQ65dDsqGA146dQEJ.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.JvYCEnjRJQ77JUWr+8CEt1b6Dzm+wlpj91b5LljJ2bl5ZYlP8latdtap9DtyM8mT9uW02VaeRy0K01uXAWynzmHaut5BrUep0F4jM9SValw2ph9TaalZu8r1jJSYg+0lTmXwEKlRacrYbautaVURIaxVrSfWR6wl1OwhWucNdh0zGwTL16WblZu2s5h+4g6Oo+ytoe6w9iMb6N60Wqf4MzXr8om2jA5k9dXM12AN6ClmB0iwT77z5ZqdGUpcL9iy+dLXbd9WK+lsZgY7IUWY44GzWPcJiZ+CLycRYCqOWcoQy8R3MaesTKr2CguF91XZ2LWeQWaulgumaqiCeOGXyIqsf9n9TSA7e+2hXqVOCA5cPso+Vcbp2ml1C0Wp18myVbcBYmYLNaCEi5OF67Ex11aNr1ZhOMbOyX7GWxPi2Lss8bDDC6GiqssP8XME6bY3XAyUPsYBE6enolir1Yud59KUWRdRXCt4xYqcL9iq4i0WGeVYRUyGmp+Vbfgq5e8aw8j1nvmGdLl1srVCuMf+5aP3wnO19rz5wF97fia03BZt8dVaw6g.tp244FZp4y7rkotjktX5qK5R15RW0kbzktoKrE6U.i6.awXEXBUMiJpbVSa9CYz2Pgy7du6gN3EVwBhwxQr6Quo1XyJDYlwn7xbIixq5spqyHwJJa.Ixq5QmXE0VQhbxbIIFaQqLwxKacI11TegD4T8VSL1Z1YhkWa8I1QCert+LTWU9cSM1h5oZlCqOpkW1.TO8sc0psM0gpNTUiPkS0iVcMqpb0MWyDU+vMcmpeZsUndlcMKUcMLO0Gdr6WkclKQUPtOfZf4+fpg22GQMlhVoZRE+n54ZMp6aj+R87sN0pG2SomyeuZyS9Y0y6Kn1yr+C549kTexh9K54eqp99vulp3U82TibsaWMtZ1oZxa3cUydSumZQu79TOTs0qdh278UqeWef5Os2indiF9X0+3v+GUiG6+oNwm9Y5qeFR2yNKofb6hzut2UYf42MYvEb9xv66EJp90CYLE0S4VFXuj6n3KVtqA2aY5CqORkC+RkELxKWVrp+xRKa.xOaLWg7Xi6Jke0sLH42daWs7b2wWVdwIesxqbWCQd8oNT4smdIxtm8WU1ekWubvpFg7QK3qIGeQIjlVrHcs5QK8XY2fz6G9Fk9+yuIYPqpbYHO9WWt90Ndoze8DjwVyDka82c6xj1v2Plxy+MkYto6Tl6e7aI22K+cjjux2UVdsUHq30mlr5276KO4aOC4o20rjMt6efr48d2xV1+bjs0v7jcbv4K64v2qbfOZgxgN18KG83+H4S9zersv6+X+6+CzTS627ySRsSGB8uMbT+eN6C7EbcnaA6gmsuOBt9A2Wmsziy92GYj5CyZ9relh+Vg5ZSwqvqsvxqbV2itxb1meaY4rwrFTp0kpvB+7VzdsbJZd6MUmQ5a7ULsplSgCsMmKW+CqC5ujNn+uRa1utif2CmEqM2CoJs9dMz8UaZSqGSGcd3qW6cbGMOmJ8atNs2Vp9eqQLCyZ3ZOeCqu3DlR6MPissb83aeqoWT.T.T.eEvm633MApRye+FlWSQytdFQ6xOh10qHZ2EEQ6t3HZWAQztdGQ6tjHZWehnc8Mh1coQztKKh1c4Qzt9EQ65efcAtbsacxCszbL+ni8tT8uw7RJIqcrzkYOO4G329TltYK6i52tWi9suk5LaWPdKy29f1cWs7LU1ejzC2h4uW2Zf8As6l+Blqtc8uwfqyMb12ZE35dPmN6cLmd1GstYr4I66ya+8XU9dpaQ6a9+So0iiyQAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPAPA5Lq.CXiUMyUdj2QVVAu2ac7a+fR8K5hzek40nLj88kxdIOZCxPy4YtrI+81kLm7qdKuw5dMogmZsuZga7eI+jY+mumUcIup7gSby86ANb8RYS482QQ68Hx+9uO9ol2jdIY0UZFvNkFGnYFZTlWhb2cMy8ckeyM9hG6ydmMKiZ+S3Qd7JazjG972sN+86ie.wAvAfC.G.N.b.3.vAfC.G.NPbfCDZ4ANf44UuE78jE0nG3OP7.b.3.vAfC.G.N.b.3.vAfCjNxAbuUI9l3ahuSGiuwuF+Z7q4eeCN.b.3.vAfC.GHdyAre2uqk.y+GvZ9u3u5cM3+6A14c3um7gf7gHNjOD3mieN94j+evAfC.G.N.b.3.vAfCD5CCSdR6DCV+LzAiBfe.9A3GPb.b.3.vAfC.G.N.bfzWNfy6N884C9E7K3WDeCG.N.b.3.vAfC.G.N.bf3JGf7jlumrah7fg7fg7fg7gCN.b.3.vAfC.G.N.b.3.g4.tkHvtm7jNttdI7baT.VuP7CvOf3.3.vAfC.G.N.b.3.wGNfyaO977BeC9F7Mh2gC.G.N.b.3.vAfC.G.NPbgCPdRSdRSdRu+v4CA4GC4GC9CjmbvAfC.G.N.b.3.vAfC3VR.6dxS53x5ivyoQAX8.wO.+.hCfC.G.N.b.3.vAfCDe4.Nu+36yO7O3ev+H9GN.b.3.vAfC.G.N.b.3.oqb.xSZxSZxSZxSZMei7Ai7Ai7Ai3.3.vAfC.G.N.b.3.MyAbKAfcO4Ic555gvykQAX89vO.+.hCfC.G.N.b.3.vAfC.GHfC3hFPOBzCpgOBeDd.b.3.vAfC.G.N.b.3.vARW3.jmzjenjmzjmzZdF4EE4EE4EEwAvAfC.G.N.b.3.vAZlC39H+18jmzoKq+AOGFEf0yC+.7CHN.N.b.3.vAfC.G.N.bf1hC3hNPeZK8g1geB+D9.b.3.vAfC.G.N.b.3.vANWkCPdRS9gRdRSdRq4WjWTjWTjWTDG.G.N.b.3.vAfC.GnYNf6i3a2SdRet55cv8sQAX85vO.+.hCfC.G.N.b.3.vAfC.GHpb.WzB5UT0KrC9J7U3EvAfC.G.N.b.3.vAfC.G3bEN.4IM4GJ4IM4IslWQdQQdQQdQQb.b.3.vAfC.G.N.bfl4.tORucO4I84JquA2mFEf0iC+.7CHN.N.b.3.vAfC.G.N.bfSWNfK5A86zU+Xbveg+B+.N.b.3.vAfC.G.N.b.3.cV4.jmzjenjmzjmzZ9D4EE4EE4EEwAvAfC.G.N.b.3.vAZlC39H718rdFcVWOCtuLJ.9m3Gfe.wAvAfC.G.N.b.3.vAfCblhC3hlPOOSomLOvmgOCOAN.b.3.vAfC.G.N.b.3.ct3.ddA4Ks695jtm7lf7lf7ln47lf3AhGHdf3A3.vAfC.G.N.b.3.oubfPenXV+hNWqeAuO38gQAvO.+.7CHN.N.b.3.vAfC.G.NvYdNfyq5L+7R7JwqDuRbEb.3.vAfC.G.N.b.3.vAfCzYfCPdRqeKPdtPdtPdtPb.b.3.vAfC.G.N.b.3.vAfCX3.g1XcabhA5.5fQAvO.+.7CHN.N.b.3.vAfC.G.NP5KGv4cm997A+B9E7KhugC.G.N.b.3.vAfC.G.NP7lCPdRqe+S9PP9PPdQQb.b.3.vAfC.G.N.b.3.vAfCPdRaWfDVmn385Dw6ed+aT.7CvO.+.hCfC.G.N.b.3.wKNf6oE+d76iW9879l22FE.+.7CvOf3.3.vAfC.G.NP7fCzw4I8+GfqgV0d3EP6Yu+9eHW+GG+IFFFlyy4ggggg47r8hc974yGYyrSrggggmIIIIIokjVRRRRRRRaOSRRRRRRRKIIIIII0W88lt99ew2O6551d7491t1O755806e4YDu2uxiyuc7QzHYMTrdh9FQWo6zaZLVlKKmjqfqgUyZ313t39n3Q3I3YX87x7571DmPDchNSWnqr8zM1M5N8fdRun2zGNF5KMFHCkQxXYhLUlIyk4yBYorbVAqjUQxkxkwkyUvUxUwUy0v0x0w0yp4F3F4lXMbybKbqbab6bGbmbWb2bObubeb+7.rVJdPdHdXdDdTdLdbdBdRdJdZdFdVdNddVOu.uHuDuLuBuJuFuNuAuIuEuMuC+adWh9EwFvFxFQmXiYSXSoyrYr4rEzE1R1J1Z5JaCaKaGaO6.6H6Dciclcgckcicm8f8jtydwdy9POXeY+X+omb.bfbPzKNXNDNT5MGFGNGA8gijihiligikiiim9xIP+n+z3D4jX.LPFDClgvPYXLbFAijQwnYLLVFGimIvDYRLYlBSkowzYFLSlEyl4vb4j4TXdLeNUV.mFKjEwhYIrTNcNCVFKmyjyhylUv4v4x4wJ474B3BYUbQbwbIj8KRh2mk3RsDueKwkYI9.VhK2R7AsDWgk3CYItRKwG1RbUVhOhk3psDeTKw0XI9XVhq0R7wsDWmk3SXItdKwmzRrZKwmxRbCVhOsk3FsDeFKwMYI9rVh0XI9bVha1R74sD2hk3KXItUKwWzRbaVhujk31sDeYKwcXI9JVh6zR7UsD2kk3qYItaKwW2RbOVhugk3dsDeSKw8YI9VVh62R7ssDOfk36XIVqkXcVhxR7csDOnk36YIdHKw22R7vVhefk3QrD+PKwiZI9QVhGyR7isDOtk3mXIdBKwO0R7jVhelk3orD+bKwSaI9EVhmwR7KsDOqk3WYIdNKwu1R77VheikX8Vheqk3ErD+NKwKZI98VhWxR7GrDurk3OZIdEKwexR7pVh+rk30rD+EKwqaI9qVh2vR72rDuok3uaIdKKw+vR71Vh+ok3crD+KKw+1R7+XIdWKw+whuDLZjTDafljhXC0jTDajljhnSZRJhMVSRQrIZRJhMUSRQzYMIEwloIoH1bMIEwVnIoH5hljhXK0jTDakljhXq0jTDcUSRQrMZRJhsUSRQrcZRJhsWSRQrCZRJhcTSRQrSZRJhtoIoH1YMIEwtnIoH1UMIEwtoIoH1cMIEwdnIoH1SMIEQ20jTD6kljhXu0jTD6iljhnGZRJh8USRQreZRJh8WSRQzSMIEwAnIoHNPMIEwAoIoH5kljh3f0jTDGhljh3P0jTD8VSRQbXZRJhCWSRQbDZRJh9nIoHNRMIEwQoIoHNZMIEwwnIoHNVMIEwwoIoHNdMIEQe0jTDmfljhneZRJh9qIoPDMRJhSTSRQbRZRJhAnIoHFnljhXPZRJhAqIoHFhljhXnZRJhgoIoHFtljhXDZRJhQpIoHFkljhXzZRJhwnIoHFqljhXbZRJhwqIoHlfljhXhZRJhIoIoHlrljhXJZRJhopIoHllljhX5ZRJhYnIoHloljhXVZRJhYqIoHliljhXtZRJhSVSRQbJZRJh4oIoHluljh3T0jTDKPSRQbZZRJhEpIoHVjljhXwZRJhknIoHVpljh3z0jTDmgljhXYZRJhkqIoHNSMIEwYoIoHNaMIEwJzjTDmiljh3b0jTDmmljhXkZRJhyWSRQbAZRJhKTSRQrJMIEwEoIoHtXMIEwknIo9uVmedc9yVm+t04eilFA0EooQPsJMMBpKTSif5BzzHnNeMMBpUpoQPcdZZDTmqlFA04noQPsBMMBpyVSif5rzzHnNSMMBpkqoQPsLMMBpyPSif5z0zHnVplFA0RzzHnVrlFA0hzzHnVnlFA0oooQPs.MMBpSUSifZ9ZZDTySSif5TzzHnNYMMBp4poQPMGMMBpYqoQPMKMMBpYpoQPMCMMBpoqoQPMMMMBpopoQPMEMMBpIqoQPMIMMBpIpoQPMAMMBpwqoQPMNMMBpwpoQPMFMMBpQqoQPMJMMBpQpoQPMBMMBpgqoQPMLMMBpgpoQPMDMMBpAqoQPMHMMBpApoQPM.MMBpSRSif5D0zHnZZZDT8WSifpeZZDTmflFAUe0zHnNdMMBpiSSif5X0zHnNFMMBpiVSif5nzzHnNRMMBp9noQPcDZZDTGtlFA0gooQP0aMMBpCUSif5PzzHnNXMMBpdooQPcPZZDTGnlFA0AnoQP0SMMBp8WSifZ+zzHn1WMMBpdnoQPsOZZDT6slFA0dooQP0cMMBp8TSifZOzzHn1cMMBpcSSifZW0zHn1EMMBpcVSifpaZZDT6jlFA0NpoQPsCZZDTaulFA01ooQPssZZDTailFAUW0zHn1ZMMBpsRSifZK0zHn5hlFA0VnoQPs4ZZDTallFAUm0zHn1TMMBpMQSifZi0zHn5jlFA0FooQPsgZZDTaflFAUnoQ7d+7+YsQP9tVx+GK4+1R9urjuik7eZIeaK4+vR9VVx+tk7Msj+MK4aXI+qVxW2R9Wrjulk7OaIeUK4exR9JVx+nk7ksj+AK4KYI+8VxWzR96rjufk72ZIWuk72XIedK4u1R9bVxekk7Ysj+RK4yXI+EVxm1R9ysjOkk7mYIeRK4O0R9DVxehk7wsj+XK4iYI+QVxG0R9CsjOhk7GXIeXK422R9PVxumk7AsjeWKYYIWmkbsVxuik7ArjeaK48aI+VVx6yR9Msj2qk7aXIuGK4W2Rd2Vxulk7trjeUK4cZI+JVx6vR9ksj2tk7KYIuMK4WzRdqVxufk7VrjedK4MaI+bVx0XI+rVxaxR9Yrj2nk7SaIuAK4mxRtZK4mzRd8VxOgk75rjebK40ZI+XVxqwR9QsjWsk7iXIuJK4G1RdkVxOjk7JrjePK4kaI+.VxKyR99sjWpk78YIS664R3h4hXUbgbAb9rRNONWNGVAmMmEmIKmkwYvoyRYIrXVDKjSiEvox7YdbJbxLWlCylYwLYFLclFSkovjYRLQl.imwwXYLLZFEijQvvYXLTFBClAw.Y.bRbhzn+zONA5KGOGGGKGCGMGEGI8gifCmCidygxgvASu3f3.4.nmr+reruzC1G1a1K5N6I6A6N6F6J6B6LcichcjcfsmsisksgtxVyVwVRWXKXyYynyrorIrwzI1H1P1.Bd2uiumm2g2l2h2j2fWmWiWkWgWlWhWjWf0yyyywyxyvSySwSxSviyiwixivCyCwCRwZ4A39493d4d3t4t3N4N31413V4V3lYMbSbibCrZtdtNtVtFtZtJtRtBtbtLtTRVEqjUvxYorPlOykYxTYhLVFICkARi9xwPen2zK5I8ftytQ2X6oqzE5Lchf29A7NyKy54Y3I3Qn393t31XMrZtFtBRVNykwRidS2oqDr9us+eXMjzHdue99s+e+pi6+2w8+2v90w8+Gf2fAxfXvLDFJCigyHXjLJFMigwx3X7LAlHShIyTXpLMlNyfYxrX1LGlKmLmByi4yoxB3zXgrHVLKgkxoyYvxX4blbVb1rBNGNWNOVImOW.WHqhKhKlKgrecb++03c3y4cfNt+u2gGz6v2yRG2+26vK5c32aoi6+2+Nt++10+Nt++9z+Nt++Qz+Nt++.88AjTDCRSRQLXMIEwPzjTDCUSRQLLMIEwv0jTDiPSRQLRMIEwnzjTDiVSRQLFMIEwX0jTDiSSRQLdMIEwDzjTDSTSRQLIMIEwj0jTDSQSRQLUMIEwzzjTDSWSRQLCMIEwL0jTDyRSRQLaMIEwbzjTDyUSRQbxZRJhSQSRQLOMIEw70jTDmpljhXAZRJhSSSRQrPMIEwhzjTDKVSRQrDMIEwR0jTDmtljh3LzjTDKSSRQrbMIEwYpIoHNKMIEwYqIoHVgljh3bzjTDmqljh37zjTDqTSRQb9ZRJhKPSRQbgZRJhUoIoHtHMIEwEqIoHtDMI0+05539+Kaccb++4rNe2v57cD97.MBpYooQPMSMMBpYnoQPMcMMBpoooQPMUMMBponoQPMYMMBpIooQPMQMMBpInoQPMdMMBpwooQPMVMMBpwnoQPMZMMBpQooQPMRMMBpQnoQPMbMMBpgooQPMTMMBpgnoQPMXMMBpAooQPMPMMBpAnoQPcRZZDTmnlFAUSSifNt+u2iC06Acb+eedXO7dPG2+2mG1JuGao2C539+qsi6++rqsi6++s74.539+dGVs2gOokNt+u2gkxRXwrHVHmFKfSk4y73T3jYtLGlMyhYxLX5LMlJSgIyjXhLAFOiiwxXXzLJFIifgyvXnLDFLChAx.3j3DoQ+oebBzWNdNNNVNFNZNJNR5CGAGNGF8lCkCgCldwAwAxAPOY+Y+XeoGrOr2rWzc1S1C1c1M1U1E1Y5F6D6H6.aOaGaKaCckslshsjtvVvlylQmYSYSXioSrQrgrADzw8+++y2+++EpTze+c7B...</IComponent>
</VST3PluginState>

A note from my earlier deepdive into this:

Specifically, it seems like the getStateInformation / setStateInformation functions implemented in #297 as the plugin.raw_state look like they will save/restore both forms of VST3 state (IComponent, IEditController) in the one XML'ish file (assuming the VST plugin implements/has state for both)

Which you can see the deeper dive in the following + linked comments:

Those saved values are applied to the VST plugin in terms of its effect on the audio. But the plugin interface doesn't show the custom values. Therefore, the updates by setattr are not reflected in the UI. Is there a more early phase to init a VST plugin? Or the my_plugin.state bytes object will cure this all?

@famzah I was just reading a bit more about how this works in VST3, and it seems there are technically 2 separate types of state; the state that affects the audio, and the rest of the plugin GUI/etc state.

I documented some of this + reference links on another issue in this comment:

So while I'm not 100% certain, I suspect that may be why you're seeing the difference between the audio effect and GUI.


Edit: With regards to this bit:

Or the my_plugin.state bytes object will cure this all?

The following snippet from my other post seems like it can answer it:

Looking at JUCE's code we can see the implementations for:

This tutorial may also be interesting for futher/deeper reading:

Originally posted by @0xdevalias in #289 (comment)

Specifically, it seems like the getStateInformation / setStateInformation functions implemented in #297 as the plugin.raw_state look like they will save/restore both forms of VST3 state (IComponent, IEditController) in the one XML'ish file (assuming the VST plugin implements/has state for both)

Originally posted by @0xdevalias in #187 (comment)

I haven't yet looked any deeper into the specific encoded output format in the XML -> VST3PluginState -> IComponent yet; nor attempted to compare/contrast it with the specific .fxp patch format.

For my more general notes on reversing the serum format, you can see this gist:

@0xdevalias
Copy link

0xdevalias commented May 23, 2024

Here's some example code of using the new .raw_state feature to save/restore presets:

I can confirm that the new plugin.raw_state property fixes my issue and I can store/load the VST3 plugin state completely -- both the audio and UI settings.

Here is an example:

effect = load_plugin(r'C:\Program Files\Common Files\VST3\MeldaProduction\Stereo\MSpectralPan.vst3')

try:
	with open('params.bin', 'rb') as file:
		effect.raw_state = file.read()
except FileNotFoundError:
	pass

effect.show_editor()

with open('params.bin', 'wb') as file:
	file.write(effect.raw_state)

board = Pedalboard([effect])

Originally posted by @famzah in #187 (comment)


It would be interesting to see what the output looks like for Serum's .raw_state when a .fxp is manually loaded within Serum; and then compare/contrast the 'built in preset load' with what we have access to via .raw-state. That may allow us to figure a way to implement an 'external load function' for it.

Edit: I express a similar idea (but for Arturia Mini V3) in this comment: #245 (comment)

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

4 participants