Sviluppo plugins in PureData con HVCC e DPF¶
Un ambiente di sviluppo per trasformare patch PureData in plugin audio VST3 per macOS, Windows e Linux.
Introduzione¶
Questo post descrive come è strutturata e come funziona la repository dev_plugin, pensata per sviluppare plugin audio partendo da patch scritte in PureData o PlugData.
Il risultato finale sono tre plugin per la spazializzazione in formato VST3, disponibili per i tre sistemi operativi più diffusi: macOS universal, Windows 64 bit1 e Linux 64 bit1.
Chi ha fatto cosa
I comandi di compilazione e parte del codice C++ sono stati sviluppati con l'aiuto di Claude Code. La parte teorica e il DSP sono stati sviluppati personalmente.
L'ambiente è stato scritto e testato su macOS; le versioni per Windows e Linux sono ottenute tramite cross compilazione.
Obiettivi¶
L'obiettivo principale è creare facilmente plugin audio per DAW, nei vari formati disponibili. L'ambiente non è scritto da zero: si appoggia a progetti open source consolidati, soprattutto per la conversione della patch Pd in codice C++ e per la compilazione di DSP e GUI.
In sintesi, il percorso da patch a plugin è questo:
flowchart LR
P["patch .pd"] --> H{{"hvcc -g dpf"}}
J["plugin.json"] --> H
H --> C["codice C/C++<br/>+ wrapper DPF"]
C --> D{{"DPF (make)"}}
U["UI ImGui<br/>ui/ + common/"] --> D
D --> F["VST3 / CLAP / LV2"]
Framework¶
L'ambiente di lavoro è strutturato come segue:
.
├── README.md // informazioni sull'ambiente
├── config.sh // configurazione (autore, percorsi delle dipendenze)
├── new.sh // crea il template di un nuovo plugin
├── build.sh // compila i plugin
├── requirements.txt // librerie Python installate nel venv
├── dep/ // dipendenze e submodule
├── src/ // codice sorgente e patch di tutti i plugin
├── common/ // codice C++ condiviso (colori UI e funzioni grafiche)
├── templates/ // file di partenza di un nuovo plugin (*.pd, *.json, UI.cpp)
├── docker/ // configurazione per la cross compilazione su Linux
├── parse_max_pd/ // studio sulla conversione da Pd a Max
└── venv/ // virtual environment Python
Dipendenze¶
-
Heavy Compiler Collection (hvcc)
hvccè un compilatore scritto in Python che genera codice C/C++ e una serie di wrapper per diversi framework audio. Supporta molte piattaforme e framework. Prende una patch PureData e la traduce in codice per il framework scelto: qui usiamoDPF, che esporta plugin nei formatiLV2,VST2,VST3,CLAPeJACK. -
DPF - DISTRHO Plugin Framework
DPFè il cuore del progetto: prende il codice generato dahvcce lo compila nei formati elencati sopra. Fornisce anche un'interfaccia grafica di base, collegata al DSP in entrambe le direzioni tramite un'APIC++basata su parametri. -
Raccolta di widget della community, utile per costruire
UIpersonalizzate. È un submodule necessario alla compilazione.
Nella repository è inclusa anche la Heavy Lib, una collezione di abstraction per Pd scritte dagli autori di hvcc.
PlugData e hvcc
PlugData include già hvcc e permette di compilare direttamente dal software. È comodo per iniziare, ma limitato quando serve controllare in modo preciso grafica, dipendenze e opzioni di compilazione: per questo qui hvcc viene usato da terminale.
Versioni fissate
I link di hvcc e DPF puntano a commit precisi: sono le versioni con cui l'ambiente è stato testato. Aggiornare i submodule può rompere la compilazione o cambiare il codice generato.
Comunicazione tra UI e DSP¶
Ogni parametro esposto nella patch diventa un parametro del plugin. DPF lo mantiene sincronizzato tra interfaccia grafica, DSP e DAW:
sequenceDiagram
participant UI as UI (ImGui)
participant DSP as DSP (Heavy)
participant DAW
UI->>DSP: setParameterValue() quando muovi un knob
DAW->>DSP: automazione del parametro
DSP-->>UI: parameterChanged() e la UI si aggiorna
HVCC e DPF¶
Il primo passo è creare il virtual environment Python, attivarlo e installare i requirements.txt:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
deactivate # per uscire dal venv quando hai finito
A questo punto nel terminale è disponibile il comando hvcc, che converte una patch in codice C++. Le opzioni disponibili sono descritte nella documentazione di hvcc. Per questo progetto serve questo comando:
Indichiamo la patch da compilare, il framework (-g dpf) e la cartella di output. L'opzione -m plugin.json è disponibile solo compilando da terminale: permette di passare impostazioni che sostituiscono quelle di default di hvcc.
Template di plugin.json
{
"name": "@NAME@", // nome del plugin
"nosimd": true, // disattiva le ottimizzazioni SIMD (evita problemi di compilazione)
"dpf": { // opzioni di DPF
"dpf_path": "../../dep/", // percorso del submodule DPF
"enable_ui": true, // attiva la UI
"ui_size": {"width": 480, "height": 320}, // dimensione della finestra
"description": "@DESCRIPTION@", // descrizione
"maker": "@MAKER@", // sviluppatore
"brand_id": "@BRAND_ID@", // codice brand
"unique_id": "@UNIQUE_ID@", // codice univoco (4 caratteri)
"homepage": "@HOMEPAGE@", // sito web
"plugin_uri": "@URI@", // pagina web del plugin
"version": "1, 0, 0", // versione
"license": "@LICENSE@", // licenza
"midi_input": 0, // input MIDI
"midi_output": 0, // output MIDI
"plugin_formats": @FORMATS@ // array dei formati desiderati
}
}
Niente commenti nel JSON reale
I commenti // qui sopra servono solo a spiegare i campi: il JSON standard non li ammette, quindi il plugin.json vero deve esserne privo, altrimenti hvcc non riesce a leggerlo.
unique_id deve essere davvero unico
La DAW riconosce il plugin da brand_id e unique_id. Due plugin con lo stesso codice vanno in conflitto, e cambiarlo dopo la pubblicazione fa sì che le sessioni salvate non trovino più il plugin.
Creare un nuovo plugin¶
Il ciclo di lavoro tipico è questo:
flowchart LR
N["./new.sh nome"] --> P["modifica la patch<br/>in Pd / PlugData"]
P --> B["./build.sh nome --install"]
B --> T["test nella DAW"]
T -- "iterazione" --> P
Il comando ./new.sh <nome-plugin> avvia un prompt che chiede tutte le informazioni necessarie, compresi i formati, e compila il template di plugin.json visto sopra. Al termine, in src/ trovi il nuovo plugin con i file .pd e .json e la cartella ui/, dove i parametri vengono disegnati automaticamente come knob con la palette comune.
La patch di default generata da new.sh.
Un po' come in Max for Live, la patch di default è già pronta per la compilazione. Possiamo esporre al plugin altri parametri, come mostrato nell'immagine.
Esporre un parametro
In hvcc un parametro si dichiara con un oggetto receive seguito da @hv_param, il valore minimo, il massimo e quello di default, per esempio [r gain @hv_param 0 1 0.5]. Tutti i dettagli sono nella documentazione di hvcc.
Solo oggetti supportati da Heavy
hvcc supporta solo un sottoinsieme degli oggetti di Pd vanilla e nessun external. Se la patch usa un oggetto non supportato, la compilazione si interrompe: conviene controllare l'elenco degli oggetti supportati prima di scrivere patch complesse.
Il plugin è già compilabile. Con questo comando vediamo il risultato:
L'opzione --install copia automaticamente il plugin nella cartella indicata in config.sh.
Il plugin appena compilato, aperto nella DAW.
Personalizzare l'interfaccia¶
L'interfaccia grafica usa l'API di ImGui, che permette di aggiungere controlli di vario tipo in poco tempo. Modificando il plugin test-basic come segue otteniamo un'interfaccia diversa:
La patch con il nuovo parametro mode.
#include "PluginUIBase.hpp"
START_NAMESPACE_DISTRHO
class PluginUI : public PluginUIBase
{
protected:
void drawContent() override
{
const float pad = kPad * fZ;
const float width = getWidth() - 2.0f * pad;
ImGui::SetCursorPos(ImVec2(pad, pad));
drawTitle("test-basic", "test e info");
ImGui::SetCursorPosX(pad);
sectionHeader("PARAMETERS", width);
ImGui::SetCursorPosX(pad);
static const char* const modeItems[] = { "uno", "due", "tre" };
comboParam("mode", parammode, modeItems, 3, 140.0f * fZ);
ImGui::SetCursorPosX(pad);
knobParam("gain", paramgain, 0.0f, 1.0f, "%.2f");
}
};
UI* createUI()
{
return new PluginUI();
}
END_NAMESPACE_DISTRHO
Il risultato è questo:
La nuova interfaccia con il menu mode e il knob gain.
Compilazione e cross compilazione¶
Per compilare si usa ./build.sh <nome-plugin> [opzioni]:
| Opzione | Cosa fa |
|---|---|
(nessuna) o --native |
compila per il sistema su cui stai lavorando |
--universal |
macOS universal: Apple Silicon + Intel (solo da Mac) |
--win |
Windows 64 bit: da Mac con MinGW, nativo su Windows |
--linux |
Linux 64 bit: da Mac con Docker, nativo su Linux |
--all |
--universal + --win + --linux: un solo bundle per i tre sistemi |
--install |
copia il bundle nella cartella VST3 indicata in config.sh |
--clean |
cancella build* e bin del plugin prima di compilare |
flowchart LR
B["./build.sh nome"] --> N["--native<br/>sistema corrente"]
B --> U["--universal<br/>arm64 + x86_64"]
B --> W["--win<br/>MinGW-w64"]
B --> L["--linux<br/>container Docker"]
U & W & L --> A["--all<br/>bundle VST3 unico"]
Requisiti per la cross compilazione da macOS
--winrichiedemingw-w64, installabile conbrew install mingw-w64.--linuxrichiede Docker avviato: lo script crea al volo un ambiente Linux virtuale e compila al suo interno.
Windows e Linux: testati da macOS
Le build per Windows e Linux sono prodotte da macOS tramite cross compilazione. Prima di distribuirle conviene provarle su macchine reali: compilare senza errori non garantisce che il plugin si carichi correttamente in ogni DAW.
Output¶
Con questa infrastruttura sono stati scritti i plugin Orbita, Lontananza e Diffuser.
-
Vedi il capitolo Compilazione e cross compilazione per dettagli su compatibilità e possibili errori. ↩↩



