├── plugin ├── .clang-format ├── utils.v ├── gui │ ├── xcb.v │ └── gui.v ├── setup.v └── plugin.v ├── assets └── running.png ├── .editorconfig ├── .gitattributes ├── v.mod ├── .bumpversion.cfg ├── main.v ├── .gitignore ├── README.md ├── LICENSE ├── CHANGELOG.md └── Makefile /plugin/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | IndentWidth: 4 3 | -------------------------------------------------------------------------------- /assets/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-foss/vclap-plugin/HEAD/assets/running.png -------------------------------------------------------------------------------- /plugin/utils.v: -------------------------------------------------------------------------------- 1 | module plugin 2 | 3 | @[if debug] 4 | fn debug(s string) { 5 | eprintln(s) 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.v] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bat eol=crlf 3 | 4 | **/*.v linguist-language=V 5 | **/*.vv linguist-language=V 6 | **/*.vsh linguist-language=V 7 | **/v.mod linguist-language=V 8 | -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'clap-plugin' 3 | description: 'Demonstration of a CLAP audio plugin in V.' 4 | version: '0.3.0' 5 | license: 'MIT' 6 | dependencies: [ 7 | 'odiroot.clap' 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.3.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:v.mod] 7 | 8 | [bumpversion:file:CHANGELOG.md] 9 | search = [Unreleased] 10 | replace = [{new_version}] - {utcnow:%Y-%m-%d} 11 | -------------------------------------------------------------------------------- /main.v: -------------------------------------------------------------------------------- 1 | import plugin 2 | // Exposes the plugin to the host (DAW). 3 | 4 | @[markused] 5 | __global clap_entry = plugin.entry 6 | // This requires modification to `clap/entry.h`. 7 | // Remove "const" so you get: 8 | // CLAP_EXPORT extern clap_plugin_entry_t clap_entry; 9 | 10 | fn init() { 11 | $if debug { 12 | eprintln('VCLAP in debug mode') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # V-specific 31 | c2v_output/ 32 | 33 | # Build artifacts 34 | build/ 35 | *.clap 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLAP plugin in V 2 | Demonstration of a [CLAP](https://github.com/free-audio/clap) audio plugin in V. 3 | 4 | Built on top of [clap](https://github.com/odiroot/clap-lib) library for V language. 5 | 6 | Current features: 7 | 8 | - Builds a spec-compliant CLAP plugin. 9 | - Shows a barebones GUI based on XCB/X11 (no controls). 10 | - Runs under Bitwig Studio on Linux. 11 | 12 | ## Quickstart 13 | 14 | Ensure you have a working [V language](https://vlang.io/) environment. 15 | 16 | On top of that you'd need: 17 | 18 | - GNU Make 19 | - GCC 20 | - `libxcb` 21 | - `libxmdcp` 22 | - `libxau` 23 | 24 | Start with: 25 | ```sh 26 | git clone https://github.com/mo-foss/vclap.git 27 | cd vclap 28 | v install 29 | make 30 | ``` 31 | 32 | To confirm the plugin was built correctly you can use 33 | [this tool](https://github.com/free-audio/clap-info/): 34 | ```sh 35 | clap-info build/hello_world.clap 36 | ``` 37 | 38 | To test with your DAW, you have to make it discoverable: 39 | ``` 40 | make install 41 | ``` 42 | 43 | Here is the plugin being correctly loaded in Bitwig Studio 5.1: 44 | ![](./assets/running.png) 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MO FOSS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.3.0] - 2024-02-28 9 | ### Changed 10 | * Use clap library from external package. 11 | 12 | ## [0.2.0] - 2024-02-14 13 | ### Added 14 | * Demonstration of plugin GUI using raw XCB calls. 15 | The GUI is currently totally non-functional. 16 | 17 | ## [0.1.3] - 2024-01-24 18 | ### Changed 19 | * Plugin version is now source from V mod file. 20 | * CLAP plugin functions are directly V plugin structs methods (closures). 21 | 22 | ## [0.1.2] - 2024-01-20 23 | ### Changed 24 | * Using proper struct casting for event header when processing. 25 | * Less copying in the example audio buffer processing. 26 | 27 | ### Added 28 | * Logging. 29 | * Build target for generating intermediary C form. 30 | * Extended DEBUG flag for tracing all calls. 31 | 32 | ## [0.1.1] - 2024-01-17 33 | 34 | ### Changed 35 | - Aligned the minimal plugin example with the template from CLAP repository. 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCEDIR = . 2 | BUILDDIR = build 3 | TARGET = $(BUILDDIR)/hello_world.clap 4 | V_CMD = v -cc gcc -shared -enable-globals 5 | V_SRC := $(shell find $(SOURCEDIR) -name '*.v' -o -name '*.c') 6 | 7 | # Debug and Release build options 8 | DEBUG ?= 0 9 | RELEASE ?= 0 10 | ifeq ($(DEBUG),1) 11 | V_FLAGS = -cg 12 | else ifeq ($(DEBUG),2) 13 | V_FLAGS = -cg -show-c-output 14 | else ifeq ($(DEBUG),3) 15 | # Even more debug! Very noisy. 16 | V_FLAGS = -cg -show-c-output -trace-calls 17 | else ifeq ($(RELEASE),1) 18 | V_FLAGS = -prod -skip-unused -cflags -fvisibility=hidden 19 | endif 20 | 21 | 22 | all: $(TARGET) 23 | 24 | # Use dependency on V source files to avoid recompilation 25 | $(TARGET): $(V_SRC) | dir 26 | $(V_CMD) $(V_FLAGS) $(SOURCEDIR) -o $@.so 27 | ifeq ($(RELEASE),1) 28 | strip $@.so 29 | endif 30 | mv $@.so $@ 31 | 32 | dir: 33 | mkdir -p $(BUILDDIR) 34 | 35 | clean: 36 | rm -rf $(BUILDDIR) 37 | 38 | info: $(TARGET) 39 | clap-info $< 40 | 41 | genc: $(V_SRC) | dir 42 | $(V_CMD) $(V_FLAGS) $(SOURCEDIR) -o $(TARGET).c 43 | 44 | install: $(TARGET) 45 | mkdir -p ~/.clap 46 | cp $(TARGET) ~/.clap/ 47 | 48 | .PHONY: all clean dir info install 49 | -------------------------------------------------------------------------------- /plugin/gui/xcb.v: -------------------------------------------------------------------------------- 1 | module gui 2 | 3 | #pkgconfig --libs xcb 4 | 5 | #include 6 | 7 | // XCB library 8 | type C.xcb_window_t = u32 9 | type C.xcb_gcontext_t = u32 10 | type C.xcb_visualid_t = u32 11 | type C.xcb_atom_t = u32 12 | 13 | @[typedef] 14 | struct C.xcb_connection_t {} 15 | 16 | @[typedef] 17 | struct C.xcb_setup_t {} 18 | 19 | @[typedef] 20 | struct C.xcb_screen_t { 21 | root C.xcb_window_t 22 | root_visual C.xcb_visualid_t 23 | white_pixel u32 24 | black_pixel u32 25 | } 26 | 27 | @[typedef] 28 | struct C.xcb_screen_iterator_t { 29 | data &C.xcb_screen_t 30 | } 31 | 32 | @[typedef] 33 | struct C.xcb_gcontext_t {} 34 | 35 | @[typedef] 36 | struct C.xcb_rectangle_t { 37 | x i16 38 | y i16 39 | width u16 40 | height u16 41 | } 42 | 43 | @[typedef] 44 | struct C.xcb_generic_event_t { 45 | response_type u8 46 | } 47 | 48 | @[typedef] 49 | struct C.xcb_expose_event_t { 50 | window C.xcb_window_t 51 | } 52 | 53 | @[typedef] 54 | struct C.xcb_request_error_t { 55 | error_code u8 56 | sequence u16 57 | bad_value u32 58 | minor_opcode u16 59 | major_opcode u16 60 | } 61 | 62 | fn C.xcb_connect(&char, &int) &C.xcb_connection_t 63 | fn C.xcb_disconnect(&C.xcb_connection_t) 64 | fn C.xcb_flush(&C.xcb_connection_t) 65 | 66 | fn C.xcb_get_setup(&C.xcb_connection_t) &C.xcb_setup_t 67 | fn C.xcb_setup_roots_iterator(&C.xcb_setup_t) C.xcb_screen_iterator_t 68 | fn C.xcb_generate_id(&C.xcb_connection_t) u32 69 | fn C.xcb_get_file_descriptor(&C.xcb_connection_t) int 70 | 71 | fn C.xcb_create_window(&C.xcb_connection_t, u8, C.xcb_window_t, C.xcb_window_t, i16, i16, u16, u16, u16, u16, C.xcb_visualid_t, u32, voidptr) 72 | fn C.xcb_destroy_window(&C.xcb_connection_t, C.xcb_window_t) 73 | fn C.xcb_configure_window(&C.xcb_connection_t, C.xcb_window_t, u16, voidptr) 74 | fn C.xcb_change_window_attributes(&C.xcb_connection_t, C.xcb_window_t, u32, voidptr) 75 | fn C.xcb_reparent_window(&C.xcb_connection_t, C.xcb_window_t, C.xcb_window_t, i16, i16) 76 | fn C.xcb_map_window(&C.xcb_connection_t, C.xcb_window_t) 77 | fn C.xcb_unmap_window(&C.xcb_connection_t, C.xcb_window_t) 78 | 79 | fn C.xcb_create_gc(&C.xcb_connection_t, C.xcb_gcontext_t, C.xcb_window_t, u32, voidptr) 80 | fn C.xcb_free_gc(&C.xcb_connection_t, C.xcb_gcontext_t) 81 | fn C.xcb_change_gc(&C.xcb_connection_t, C.xcb_gcontext_t, u32, voidptr) 82 | 83 | fn C.xcb_clear_area(&C.xcb_connection_t, u8, C.xcb_window_t, i16, i16, u16, u16) 84 | fn C.xcb_poly_rectangle(&C.xcb_connection_t, C.xcb_window_t, C.xcb_gcontext_t, u32, &C.xcb_rectangle_t) 85 | fn C.xcb_poly_fill_rectangle(&C.xcb_connection_t, C.xcb_window_t, C.xcb_gcontext_t, u32, &C.xcb_rectangle_t) 86 | 87 | fn C.xcb_poll_for_event(&C.xcb_connection_t) &C.xcb_generic_event_t 88 | 89 | -------------------------------------------------------------------------------- /plugin/setup.v: -------------------------------------------------------------------------------- 1 | module plugin 2 | 3 | import v.vmod 4 | import odiroot.clap 5 | import odiroot.clap.factory as cfactory 6 | 7 | // Only need to change the mod file to update the version. 8 | const manifest = vmod.decode(@VMOD_FILE) or { panic(err) } 9 | const current_version = manifest.version 10 | const plugin_name = 'CLAP V Hello World' 11 | 12 | // Features of the CLAP plugin. 13 | // Have to be defined separately here, otherwise wrong C is generated. 14 | const _plugin_features = [ 15 | c'audio-effect', 16 | c'note-effect', 17 | c'stereo', 18 | unsafe { nil }, 19 | ]! 20 | 21 | // Plugin information, extracted at load time. 22 | const _id = 'example.hello.world' 23 | const _descriptor = clap.PluginDescriptor{ 24 | id: _id.str 25 | name: plugin_name.str 26 | vendor: c'MOFOSS' 27 | version: current_version.str 28 | description: c'MVP of a CLAP plugin in V.' 29 | // voidptr is to fix warning about const char**. 30 | features: voidptr(unsafe { 31 | &&char(&_plugin_features[0]) 32 | }) 33 | } 34 | 35 | fn create_plugin(factory &cfactory.PluginFactory, host &clap.Host, plugin_id &char) &clap.Plugin { 36 | // Sanity checks for lib version and correct plugin expected. 37 | if !clap.version_is_compatible(host.clap_version) { 38 | return unsafe { nil } 39 | } 40 | v_plugin_id := unsafe { cstring_to_vstring(plugin_id) } 41 | if v_plugin_id != _id { 42 | return unsafe { nil } 43 | } 44 | 45 | // Build actual plugin -- our custom structure. 46 | main_plugin := &MinimalPlugin{ 47 | host: host 48 | } 49 | 50 | // This is the "official" plugin. 51 | clap_plugin := &clap.Plugin{ 52 | desc: &plugin._descriptor 53 | // It always carries a pointer to our custom structure. 54 | plugin_data: main_plugin 55 | init: main_plugin.init 56 | destroy: main_plugin.destroy 57 | activate: main_plugin.activate 58 | deactivate: main_plugin.deactivate 59 | start_processing: main_plugin.start_processing 60 | stop_processing: main_plugin.stop_processing 61 | reset: main_plugin.reset 62 | process: main_plugin.process 63 | get_extension: main_plugin.get_extension 64 | on_main_thread: main_plugin.on_main_thread 65 | } 66 | 67 | return clap_plugin 68 | } 69 | 70 | fn entry_get_factory(factory_id &char) voidptr { 71 | factory_id_v := unsafe { factory_id.vstring() } 72 | if factory_id_v == cfactory.plugin_factory_id { 73 | factory := cfactory.PluginFactory{ 74 | get_plugin_count: fn (factory &cfactory.PluginFactory) u32 { 75 | return 1 76 | } 77 | get_plugin_descriptor: fn (factory &cfactory.PluginFactory, index u32) &clap.PluginDescriptor { 78 | if index == 0 { 79 | return &plugin._descriptor 80 | } else { 81 | return unsafe { nil } 82 | } 83 | } 84 | create_plugin: create_plugin 85 | } 86 | return voidptr(&factory) 87 | } 88 | 89 | return unsafe { nil } 90 | } 91 | 92 | pub const entry = clap.PluginEntry{ 93 | clap_version: clap.Version{} 94 | init: fn (plugin_path &char) bool { 95 | return true 96 | } 97 | deinit: fn () {} 98 | get_factory: entry_get_factory 99 | } 100 | -------------------------------------------------------------------------------- /plugin/gui/gui.v: -------------------------------------------------------------------------------- 1 | module gui 2 | 3 | @[heap] 4 | pub struct GUI { 5 | width u32 6 | height u32 7 | connection &C.xcb_connection_t 8 | window C.xcb_window_t 9 | gc C.xcb_gcontext_t 10 | pub: 11 | fd int 12 | } 13 | 14 | pub fn GUI.create(width u32, height u32, title string) &GUI { 15 | // Open the connection to the X server. 16 | conn := unsafe { C.xcb_connect(nil, nil) } 17 | if isnil(conn) { 18 | panic('Unable to open XCB connection.') 19 | } 20 | 21 | // Get the first screen. 22 | screen := C.xcb_setup_roots_iterator(C.xcb_get_setup(conn)).data 23 | // Create a window. 24 | window := C.xcb_generate_id(conn) 25 | C.xcb_create_window(conn, C.XCB_COPY_FROM_PARENT, window, screen.root, 0, 0, width, 26 | height, 1, C.XCB_WINDOW_CLASS_INPUT_OUTPUT, screen.root_visual, C.XCB_CW_BACK_PIXEL, 27 | &screen.white_pixel) 28 | 29 | // Select the events the window will receive. 30 | event_mask := C.XCB_EVENT_MASK_EXPOSURE | C.XCB_EVENT_MASK_POINTER_MOTION | C.XCB_EVENT_MASK_BUTTON_PRESS | C.XCB_EVENT_MASK_BUTTON_RELEASE | C.XCB_EVENT_MASK_KEY_PRESS | C.XCB_EVENT_MASK_KEY_RELEASE | C.XCB_EVENT_MASK_ENTER_WINDOW | C.XCB_EVENT_MASK_LEAVE_WINDOW | C.XCB_EVENT_MASK_BUTTON_MOTION | C.XCB_EVENT_MASK_KEYMAP_STATE | C.XCB_EVENT_MASK_FOCUS_CHANGE 31 | C.xcb_change_window_attributes(conn, window, C.XCB_CW_EVENT_MASK, &event_mask) 32 | 33 | // Inform a window manager not to tamper with the window 34 | // Note: doesn't work in standalone. 35 | C.xcb_change_window_attributes(conn, window, C.XCB_CW_OVERRIDE_REDIRECT, &[1]!) 36 | 37 | // Create a graphic context to paint on. 38 | gc := C.xcb_generate_id(conn) 39 | gc_values := [screen.black_pixel, 0]! 40 | C.xcb_create_gc(conn, gc, window, C.XCB_GC_FOREGROUND | C.XCB_GC_GRAPHICS_EXPOSURES, 41 | &gc_values) 42 | 43 | C.xcb_flush(conn) 44 | 45 | return &GUI{ 46 | width: width 47 | height: height 48 | connection: conn 49 | window: window 50 | gc: gc 51 | fd: C.xcb_get_file_descriptor(conn) 52 | } 53 | } 54 | 55 | pub fn (g &GUI) destroy() { 56 | C.xcb_free_gc(g.connection, g.gc) 57 | C.xcb_destroy_window(g.connection, g.window) 58 | C.xcb_disconnect(g.connection) 59 | } 60 | 61 | pub fn (g &GUI) set_parent(parent u32) { 62 | C.xcb_reparent_window(g.connection, g.window, parent, 0, 0) 63 | C.xcb_flush(g.connection) 64 | } 65 | 66 | pub fn (g &GUI) set_visible(visible bool) { 67 | if visible { 68 | C.xcb_configure_window(g.connection, g.window, C.XCB_CONFIG_WINDOW_STACK_MODE, 69 | &[C.XCB_STACK_MODE_ABOVE]!) 70 | C.xcb_map_window(g.connection, g.window) 71 | } else { 72 | C.xcb_unmap_window(g.connection, g.window) 73 | } 74 | C.xcb_flush(g.connection) 75 | } 76 | 77 | fn (g &GUI) paint() { 78 | C.xcb_clear_area(g.connection, 0, g.window, 0, 0, g.width, g.height) 79 | // Parameter "slider". 80 | C.xcb_change_gc(g.connection, g.gc, C.XCB_GC_FOREGROUND, &[0]!) 81 | C.xcb_poly_rectangle(g.connection, g.window, g.gc, 1, &C.xcb_rectangle_t{50, 50, 25, 100}) 82 | C.xcb_poly_fill_rectangle(g.connection, g.window, g.gc, 1, &C.xcb_rectangle_t{50, 50, 25, 40}) 83 | } 84 | 85 | pub fn (g &GUI) on_posix_fd() { 86 | C.xcb_flush(g.connection) 87 | 88 | for { 89 | event := C.xcb_poll_for_event(g.connection) 90 | if isnil(event) { 91 | break 92 | } 93 | t := event.response_type & ~0x80 94 | match t { 95 | // Error 96 | 0 { 97 | error_ev := &C.xcb_request_error_t(event) 98 | eprintln('Received X11 error: ${error_ev.error_code}') 99 | } 100 | u8(C.XCB_EXPOSE) { 101 | expose_ev := &C.xcb_expose_event_t(event) 102 | if expose_ev.window == g.window { 103 | g.paint() 104 | } 105 | } 106 | else {} 107 | } 108 | 109 | C.xcb_flush(g.connection) 110 | C.free(event) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /plugin/plugin.v: -------------------------------------------------------------------------------- 1 | module plugin 2 | 3 | import odiroot.clap 4 | import odiroot.clap.ext as cext 5 | import plugin.gui { GUI } 6 | 7 | const gui_width = 640 8 | const gui_height = 480 9 | 10 | // This should be the actual implementation of the plugin with 11 | // all the interesting logic like DSP, UI, etc. 12 | @[heap] 13 | struct MinimalPlugin { 14 | host &clap.Host 15 | mut: 16 | sample_rate f64 17 | latency u32 18 | host_posix_fd &cext.HostPosixFdSupport = unsafe { nil } 19 | gui &gui.GUI = unsafe { nil } 20 | } 21 | 22 | // Extract our actual pluging from CLAP plugin wrapper. 23 | fn from_clap(clap_plugin &clap.Plugin) &MinimalPlugin { 24 | return unsafe { &MinimalPlugin(clap_plugin.plugin_data) } 25 | } 26 | 27 | fn (mut mp MinimalPlugin) init(clap_plugin &clap.Plugin) bool { 28 | mp.host_posix_fd = mp.host.get_extension(mp.host, cext.ext_posix_fd_support.str) 29 | return true 30 | } 31 | 32 | fn (mp MinimalPlugin) destroy(clap_plugin &clap.Plugin) { 33 | // Cleanup plugin members here. 34 | } 35 | 36 | fn (mut mp MinimalPlugin) activate(clap_plugin &clap.Plugin, sample_rate f64, min_frames_count u32, max_frames_count u32) bool { 37 | mp.sample_rate = sample_rate 38 | return true 39 | } 40 | 41 | fn (mp MinimalPlugin) deactivate(clap_plugin &clap.Plugin) { 42 | } 43 | 44 | fn (mp MinimalPlugin) start_processing(clap_plugin &clap.Plugin) bool { 45 | return true 46 | } 47 | 48 | fn (mp MinimalPlugin) stop_processing(clap_plugin &clap.Plugin) { 49 | } 50 | 51 | fn (mp MinimalPlugin) reset(clap_plugin &clap.Plugin) { 52 | // Cleanup plugin members here. 53 | } 54 | 55 | fn (mp MinimalPlugin) process_event(header &clap.EventHeader) { 56 | if header.space_id != clap.core_event_space_id { 57 | return 58 | } 59 | 60 | match header.@type { 61 | u16(clap.event_note_on) { 62 | // Handle note playing. 63 | event := unsafe { &clap.EventNote(header) } 64 | debug('Note ON: ${event.note_id}') 65 | } 66 | u16(clap.event_note_off) { 67 | // Handle note stop playing. 68 | event := unsafe { &clap.EventNote(header) } 69 | debug('Note OFF: ${event.note_id}') 70 | } 71 | // And so on... 72 | else { 73 | t := unsafe { clap.EventType(header.@type) } 74 | debug('Unsupported event type: ${t}') 75 | } 76 | } 77 | } 78 | 79 | fn (mp MinimalPlugin) process(clap_plugin &clap.Plugin, mut process clap.Process) clap.ProcessStatus { 80 | frame_count := process.frames_count 81 | event_count := process.in_events.size(process.in_events) 82 | 83 | mut event_index := u32(0) 84 | mut next_frame := if event_count > 0 { 0 } else { frame_count } 85 | 86 | for i := 0; i < frame_count; { 87 | for event_index < event_count { 88 | // Handle every event at frame i. 89 | if next_frame != i { 90 | break 91 | } 92 | 93 | header := process.in_events.get(process.in_events, event_index) 94 | 95 | if header.time != i { 96 | next_frame = header.time 97 | break 98 | } 99 | 100 | mp.process_event(header) 101 | event_index++ 102 | 103 | // Event list exhausted. 104 | if event_index == event_count { 105 | next_frame = frame_count 106 | break 107 | } 108 | } 109 | 110 | for ; i < next_frame; i++ { 111 | // In general: 112 | // mut inputs := []AudioBuffer{cap: int(process.audio_inputs_count)} 113 | // for k := 0; k < process.audio_inputs_count; k++ { 114 | // inputs << unsafe{ process.audio_inputs[k] } 115 | // } 116 | input_left := unsafe { process.audio_inputs[0].data32[0][i] } 117 | input_right := unsafe { process.audio_inputs[0].data32[1][i] } 118 | 119 | // Swap left and right channels. 120 | unsafe { 121 | process.audio_outputs[0].data32[0][i] = input_right 122 | } 123 | unsafe { 124 | process.audio_outputs[0].data32[1][i] = input_left 125 | } 126 | } 127 | } 128 | 129 | return clap.process_continue 130 | } 131 | 132 | fn (mut mp MinimalPlugin) get_extension(clap_plugin &clap.Plugin, id &char) voidptr { 133 | v_id := unsafe { cstring_to_vstring(id) } 134 | 135 | match v_id { 136 | cext.ext_latency { 137 | return &cext.PluginLatency{ 138 | get: fn [mp] (clap_plugin &clap.Plugin) u32 { 139 | return mp.latency 140 | } 141 | } 142 | } 143 | cext.ext_audio_ports { 144 | return &cext.PluginAudioPorts{ 145 | count: fn (clap_plugin &clap.Plugin, is_input bool) u32 { 146 | return 1 147 | } 148 | get: fn (clap_plugin &clap.Plugin, index u32, is_input bool, mut info cext.AudioPortInfo) bool { 149 | // Just one port. 150 | if index > 0 { 151 | return false 152 | } 153 | 154 | info.id = 0 155 | info.channel_count = 2 156 | info.flags = cext.audio_port_is_main 157 | info.port_type = cext.port_stereo.str 158 | info.in_place_pair = C.CLAP_INVALID_ID 159 | 160 | // Translate string to constant array. 161 | port_name := 'Example audio port' 162 | for i, chr in port_name { 163 | info.name[i] = chr 164 | } 165 | info.name[port_name.len] = char(0) 166 | 167 | return true 168 | } 169 | } 170 | } 171 | cext.ext_note_ports { 172 | return &cext.PluginNotePorts{ 173 | count: fn (clap_plugin &clap.Plugin, is_input bool) u32 { 174 | return 1 175 | } 176 | get: fn (clap_plugin &clap.Plugin, index u32, is_input bool, mut info &cext.NotePortInfo) bool { 177 | if index > 0 { 178 | return false 179 | } 180 | 181 | info.id = 0 182 | // vfmt off 183 | info.supported_dialects = ( 184 | u32(cext.note_dialect_clap) | 185 | u32(cext.note_dialect_midi_mpe) | 186 | u32(cext.note_dialect_midi2) 187 | ) 188 | // vfmt on 189 | info.preferred_dialect = u32(cext.note_dialect_clap) 190 | 191 | port_name := 'Example note port' 192 | for i, chr in port_name { 193 | info.name[i] = chr 194 | } 195 | info.name[port_name.len] = char(0) 196 | 197 | return true 198 | } 199 | } 200 | } 201 | cext.ext_gui { 202 | return &cext.PluginGUI{ 203 | is_api_supported: fn (clap_plugin &clap.Plugin, api &char, is_floating bool) bool { 204 | if is_floating { 205 | return false 206 | } 207 | v_api := unsafe { cstring_to_vstring(api) } 208 | // Only X11 for now. 209 | return v_api == cext.window_api_x11 210 | } 211 | get_preferred_api: fn (clap_plugin &clap.Plugin, mut api voidptr, mut is_floating &bool) bool { 212 | is_floating = false 213 | api = &cext.window_api_x11.str 214 | return true 215 | } 216 | create: fn [mut mp] (clap_plugin &clap.Plugin, api &char, is_floating bool) bool { 217 | v_api := unsafe { cstring_to_vstring(api) } 218 | if v_api != cext.window_api_x11 || is_floating { 219 | return false 220 | } 221 | if !isnil(mp.gui) { 222 | panic('GUI already initialised!') 223 | } 224 | mp.gui = GUI.create(gui_width, gui_height, plugin_name) 225 | // Register the file descriptor we'll receive events from. 226 | if !isnil(mp.host_posix_fd) && !isnil(mp.host_posix_fd.register_fd) { 227 | mp.host_posix_fd.register_fd(mp.host, mp.gui.fd, cext.posix_fd_read) 228 | } 229 | 230 | return true 231 | } 232 | destroy: fn [mut mp] (clap_plugin &clap.Plugin) { 233 | if !isnil(mp.host_posix_fd) && !isnil(mp.host_posix_fd.unregister_fd) { 234 | mp.host_posix_fd.unregister_fd(mp.host, mp.gui.fd) 235 | } 236 | if isnil(mp.gui) { 237 | panic('GUI not initialised!') 238 | } 239 | mp.gui.destroy() 240 | 241 | unsafe { 242 | mp.gui = nil 243 | } 244 | } 245 | set_scale: fn (clap_plugin &clap.Plugin, scale f64) bool { 246 | return false 247 | } 248 | get_size: fn (clap_plugin &clap.Plugin, mut width &u32, mut height &u32) bool { 249 | width = gui_width 250 | height = gui_height 251 | return true 252 | } 253 | can_resize: fn (clap_plugin &clap.Plugin) bool { 254 | return false 255 | } 256 | get_resize_hints: fn (clap_plugin &clap.Plugin, hints &cext.GUIResizeHints) bool { 257 | return false 258 | } 259 | adjust_size: fn (clap_plugin &clap.Plugin, mut width &u32, mut height &u32) bool { 260 | width = gui_width 261 | height = gui_height 262 | return true 263 | } 264 | set_size: fn (clap_plugin &clap.Plugin, width &u32, height &u32) bool { 265 | return true 266 | } 267 | set_parent: fn [mut mp] (clap_plugin &clap.Plugin, window &cext.Window) bool { 268 | v_wapi := unsafe { (&char(window.api)).vstring() } 269 | assert v_wapi == cext.window_api_x11, 'Bad GUI API' 270 | 271 | mp.gui.set_parent(window.x11) 272 | return true 273 | } 274 | set_transient: fn (clap_plugin &clap.Plugin, window &cext.Window) bool { 275 | return false 276 | } 277 | suggest_title: fn (clap_plugin &clap.Plugin, title &char) {} 278 | show: fn [mp] (clap_plugin &clap.Plugin) bool { 279 | mp.gui.set_visible(true) 280 | return true 281 | } 282 | hide: fn [mp] (clap_plugin &clap.Plugin) bool { 283 | mp.gui.set_visible(false) 284 | return true 285 | } 286 | } 287 | } 288 | cext.ext_posix_fd_support { 289 | return &cext.PluginPosixFdSupport{ 290 | on_fd: fn [mp] (clap_plugin &clap.Plugin, fd int, flags cext.PosixFdFlags) { 291 | mp.gui.on_posix_fd() 292 | } 293 | } 294 | } 295 | else { 296 | return unsafe { nil } 297 | } 298 | } 299 | } 300 | 301 | fn (mp MinimalPlugin) on_main_thread(clap_plugin &clap.Plugin) { 302 | } 303 | --------------------------------------------------------------------------------