Introduction

Plugins are an important aspect of Gwion's flexibility and extensibility. They allow you to customize behavior as well as develop novel functionality within Gwion itself.

Much of Gwion's features come from plugins. Audio generation, file I/O, image editing and more.

What is a Plugin?

Plugins are shared object files typically created from C source code. They consist of a few core functions required for the Gwion runtime to properly initialize and use it.

Simple Plugin

Let's create a simple plugin which provides a single function: add. This will take two ints and return their sum as an int.

First, open up your terminal and run the following shell commands within Gwion/plug:

# Create a new directory for our Adder plugin
mkdir Adder

# Navigate to the newly created directory
cd Adder

# Create a makefile for compilation
printf "include ../config.mk\ninclude ../config_post.mk\n" > Makefile

# Create an adder.c file with your favorite text editor
nano adder.c

We are greeted with any empty file. However, it won't be empty for long.

Let's add the following code to the top of our file:

#include "plugin_dev.h"

This will include all the necessary functions and macros to develop a plugin without much head scratching.

Next, we need the GWION_IMPORT function. This is extremely important since it is what the Gwion runtime uses to set up your plugin. GWION_IMPORT is a macro which takes the name of your plugin: in this case that is "Adder". Underneath the include let's add:

GWION_IMPORT(Adder) {
    // Init code here
}

Within this function we register the public API of our plugin to Gwion. In our case, we want an Adder class with a single static function add that takes two numbers and returns their sum.

Our first step is to create the Adder class. This is done with:

GWION_IMPORT(Adder) {
    // Begin our adder class
    DECL_OB(const Type, t_adder, = gwi_class_ini(gwi, "Adder", "Object"));
}

Now, whenever we ini a class, we must also end it:

GWION_IMPORT(Adder) {
    // Begin our adder class
    DECL_OB(const Type, t_adder, = gwi_class_ini(gwi, "Adder", "Object"));

    // End our adder class
    GWI_BB(gwi_class_end(gwi));
}

We also want to make sure to indicate that everything has gone OK. We do this by returning GW_OK:

GWION_IMPORT(Adder) {
    // Begin our adder class
    DECL_OB(const Type, t_adder, = gwi_class_ini(gwi, "Adder", "Object"));

    // End our adder class
    GWI_BB(gwi_class_end(gwi));

    return GW_OK;
}

Right now our code doesn't really do much; it creates an empty class and then exits. Let's expand this code to add a static function inside the class:

GWION_IMPORT(Adder) {
    // Begin our adder class
    DECL_OB(const Type, t_adder, = gwi_class_ini(gwi, "Adder", "Object"));

    // Create a new function named `add` with a return type of `int`
    // gwi_func_ini(gwi, return_type, name);
    GWI_BB(gwi_func_ini(gwi, "int", "add"));

    // Register our two args `a` and `b` of type `int`
    // gwi_func_arg(gwi, arg_type, name);
    GWI_BB(gwi_func_arg(gwi, "int", "a"));
    GWI_BB(gwi_func_arg(gwi, "int", "b"));

    // Mark the function as completely declared
    GWI_BB(gwi_func_end(gwi, adder_add, ae_flag_static));

    // End our adder class
    GWI_BB(gwi_class_end(gwi));

    return GW_OK;
}

Looks like we are good to go! Let's compile...and:

adder.c: In function 'import':
adder.c:22:28: error: 'adder_add' undeclared (first use in this function)
   22 |   GWI_BB(gwi_func_end(gwi, adder_add, ae_flag_static));
      |                            ^~~~~~~~~

When we marked the function as completely declared with gwi_func_end, we gave the function implementation adder_add as an argument. This tells Gwion that our Adder.add function in Gwion corresponds to the C function adder_add. However we haven't actually defined it! Let's do that. Above our GWION_IMPORT function, add:

// The `SFUN` macro defines a static function
// The `static` here is actually a C keyword, unrelated to Gwion
static SFUN(sfun) {
    // Function body
}

Let's recall what our Adder.add function does. It takes two int parameters and returns an int. Gwion provides facilities for doing this:

// The `SFUN` macro defines a static function
// The `static` here is actually a C keyword, unrelated to Gwion
static SFUN(sfun) {
    // Retrieve the arguments
    // `a` is in memory at offset 0
    const m_int a = *(m_int*)MEM(0);
    // `b` is in memory at offset `SZ_INT`
    // This is because `a` has size `SZ_INT` and `b` is after `a`
    const m_int b = *(m_int*)MEM(SZ_INT);

    // We now set the return value, given as a void pointer with the `RETURN` macro
    // We need to cast it to the pointer type we want to return and then assign the return value
    *(m_int*)RETURN = a + b;
}

Finally, we can test our plugin. After running make (which creates Adder.so) in the directory, create a new file called add.gw and insert the following code:

#! We import our newly created plugin
#import Adder

#! Let's call our add function
<<< Adder.add(1, 2) >>>;

If all goes well, running the following shell command should result in "3" being printed:

gwion -p. add.gw
built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground