Skip to content

Documentation for Devs

Page to document the details of how you can contribute with pd4web.

pd4web

pd4web is a collection of tools with a user interface provided through a Python module called pd4web. pd4web can be divided into three main parts:

  1. The main script, pd4web.py.
  2. The externals folder, consisting of the main file ExternalClass.py and files for each library.
  3. The lib folder, comprising the main file main.py and files for each library.

Adding a new PureData Library/External

To add a new PureData External you must add a new entry in the Externals.yaml. The .yaml file work with list of keys, the keys that you must specify is, name, repoUser, repoName, download_source or direct_link. We have some optional keys too: singleObject, dynamicLibraries, extraFunction and version.


Key Name Description
name With name you specify the name of the library, for example else, cyclone, timbreIdLib.
repoUser The name of the user where the repo is hosted. For example porres.
repoName The name of the repository where it is hosted. For example pd-else.
download_source The download source, it must be GITHUB_RELEASES or GITHUB_TAGS.
direct_link If you repository dev don't publish Releases or Tags you must use the direct link to a zip file.
singleObject When the library is just a single object (earplug~ for example) you must mark this as true.
dynamicLibraries It is a list of all dynamic libraries used, for example, convolve~ uses fftw3 so we need to set ["fftw3"]. It should be in the supported dynamic libraries..
extraFunction Name of the Python Function to copy headers and another files, add compilation flags, and others to the webpatch folder. Just used for complex libraries.


Here an example:

SupportedLibraries:
  - name: cyclone # name of the pd library
    download_source: GITHUB_RELEASES # source
    repoUser: porres # in this case, Github user name
    repoName: pd-cyclone # in this case, Github Repo
    version: cyclone_0.7-0 # version
    extraFunction: cyclone_extra # This library needs some extras steps, 
                                 # so we used a function called cyclone_extra, defined in externals.cyclone.

Writing the extraFunction

The extraFunction is responsible for making all extra configurations for a PureData External. The extraFunction is defined inside a file inside the externals folder. It will be automatically imported and used when you specify it on extraFunction key in the Externals.yaml file.

pd4web configures a .c file for compilation. To set up the compilation, we assume that:

  1. All header files (.h) are located within the webpatch/includes folder.
  2. All the files to be compiled (.c) are located within webpatch/externals.
  3. All additional files, such as external configurations, data files, and others, are situated within webpatch/data. Therefore, you need to place each file in its respective folder.
Example of Extra Function for pd-else
def else_extra(librarySelf: PureDataExternals):
    if not os.path.exists(os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "includes")):
        os.makedirs(os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "includes"))
    folder = os.path.join(librarySelf.folder, "Code_source", "shared")

    for file in os.listdir(folder):
        if file.endswith(".h"):
            shutil.copy(os.path.join(folder, file),
                        os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "includes"))

    for file in os.listdir(folder):
        if file.endswith(".c"):
            shutil.copy(os.path.join(folder, file),
                        os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "externals"))

    os.remove(os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "externals", "s_elseutf8.c")) # conflit with pd source.
    librarySelf.extraFuncExecuted = True
    if 'sfz~' in librarySelf.usedObjs:
        librarySelf.webpdPatch.print("sfz~ object is not supported yet", color="red")

    elif 'sfont~' in librarySelf.usedObjs:
        librarySelf.webpdPatch.print("sfont~ object is not supported yet", color="red")

    elif 'plaits~' in librarySelf.usedObjs:
        # inside the library folder, search recursively for the file plaits~.cpp
        plaitsFile = None
        for root, _, files in os.walk(librarySelf.folder):
            for file in files:
                if file.endswith("plaits~.cpp"):
                    plaitsFile = os.path.join(root, file)
                    plaitsFolder = os.path.dirname(plaitsFile)
                    file_names = [
                        "stmlib/dsp/units.cc",
                        "stmlib/utils/random.cc",
                        "stmlib/dsp/atan.cc",
                        "plaits/dsp/voice.cc",
                        "plaits/dsp/engine/additive_engine.cc",
                        "plaits/dsp/engine/bass_drum_engine.cc",
                        "plaits/dsp/engine/chord_engine.cc",
                        "plaits/dsp/engine/fm_engine.cc",
                        "plaits/dsp/engine/grain_engine.cc",
                        "plaits/dsp/engine/hi_hat_engine.cc",
                        "plaits/dsp/engine/modal_engine.cc",
                        "plaits/dsp/engine/noise_engine.cc",
                        "plaits/dsp/engine/particle_engine.cc",
                        "plaits/dsp/engine/snare_drum_engine.cc",
                        "plaits/dsp/engine/speech_engine.cc",
                        "plaits/dsp/engine/string_engine.cc",
                        "plaits/dsp/engine/swarm_engine.cc",
                        "plaits/dsp/engine/virtual_analog_engine.cc",
                        "plaits/dsp/engine/waveshaping_engine.cc",
                        "plaits/dsp/engine/wavetable_engine.cc",
                        "plaits/dsp/speech/lpc_speech_synth.cc",
                        "plaits/dsp/speech/lpc_speech_synth_controller.cc",
                        "plaits/dsp/speech/lpc_speech_synth_phonemes.cc",
                        "plaits/dsp/speech/lpc_speech_synth_words.cc",
                        "plaits/dsp/speech/naive_speech_synth.cc",
                        "plaits/dsp/speech/sam_speech_synth.cc",
                        "plaits/dsp/physical_modelling/modal_voice.cc",
                        "plaits/dsp/physical_modelling/resonator.cc",
                        "plaits/dsp/physical_modelling/string.cc",
                        "plaits/dsp/physical_modelling/string_voice.cc",
                        "plaits/dsp/engine2/chiptune_engine.cc",
                        "plaits/dsp/engine2/phase_distortion_engine.cc",
                        "plaits/dsp/engine2/six_op_engine.cc",
                        "plaits/dsp/engine2/string_machine_engine.cc",
                        "plaits/dsp/engine2/virtual_analog_vcf_engine.cc",
                        "plaits/dsp/engine2/wave_terrain_engine.cc",
                        "plaits/dsp/fm/algorithms.cc",
                        "plaits/dsp/fm/dx_units.cc",
                        "plaits/dsp/chords/chord_bank.cc",
                        "plaits/resources.cc"
                    ]
                    for plaitsFile in file_names:
                        librarySelf.webpdPatch.sortedSourceFiles.append(os.path.join(plaitsFolder, plaitsFile))


                    plaitsIncludes = os.path.join(plaitsFolder, "plaits")
                    shutil.copytree(plaitsIncludes, os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "includes", "plaits"))

                    stmlibrary = os.path.join(plaitsFolder, "stmlib")
                    shutil.copytree(stmlibrary, os.path.join(librarySelf.PROJECT_ROOT, "webpatch", "includes", "stmlib"))