diff --git a/Makefile b/Makefile index 140d9ca..de95fb7 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ V_SRC = $(wildcard $(SOURCEDIR)/*.v) DEBUG ?= 0 RELEASE ?= 0 ifeq ($(DEBUG),1) - V_FLAGS = -cg -show-c-output + V_FLAGS = -cg -show-c-output -trace-calls else ifeq ($(RELEASE),1) V_FLAGS = -prod -skip-unused -cflags -fvisibility=hidden endif diff --git a/src/c_abi.v b/src/c_abi.v index 1548e94..f807ac5 100644 --- a/src/c_abi.v +++ b/src/c_abi.v @@ -4,13 +4,15 @@ module plugin #include "clap/clap.h" enum ClapProcessStatus { - clap_process_error = 0 - clap_process_continue = 1 - clap_process_continue_if_not_quiet = 2 - clap_process_tail = 3 - clap_process_sleep = 4 + error = 0 + @continue = 1 + continue_if_not_quiet = 2 + tail = 3 + sleep = 4 } +type C.clap_id = u32 + @[typedef] struct C.clap_version_t { major u32 = u32(C.CLAP_VERSION_MAJOR) @@ -37,10 +39,10 @@ struct C.clap_plugin_t { desc &C.clap_plugin_descriptor_t @[required] plugin_data voidptr @[required] - init fn (&C.clap_plugin_t) bool @[required] - destroy fn (&C.clap_plugin_t) @[required] - activate fn (&C.clap_plugin_t, f64, u32, u32) bool @[required] - deactivate fn (&C.clap_plugin_t) @[required] + init fn (&C.clap_plugin_t) bool @[required] + destroy fn (&C.clap_plugin_t) @[required] + activate fn (&C.clap_plugin_t, f64, u32, u32) bool @[required] + deactivate fn (&C.clap_plugin_t) @[required] start_processing fn (&C.clap_plugin_t) bool stop_processing fn (&C.clap_plugin_t) reset fn (&C.clap_plugin_t) @@ -49,9 +51,10 @@ struct C.clap_plugin_t { on_main_thread fn (&C.clap_plugin_t) } -@[typedef] +@[heap; typedef] struct C.clap_host_t { - clap_version C.clap_version_t + clap_version C.clap_version_t + get_extension fn (&C.clap_host_t, &char) voidptr } @[typedef] @@ -70,8 +73,79 @@ struct C.clap_plugin_entry_t { } @[typedef] -struct C.clap_process_t +struct C.clap_process_t { + steady_time i64 + frames_count u32 + transport &C.clap_event_transport_t + audio_inputs &C.clap_audio_buffer_t + audio_inputs_count u32 + audio_outputs_count u32 + in_events &C.clap_input_events_t + out_events &C.clap_output_events_t +mut: + audio_outputs &C.clap_audio_buffer_t +} + +@[typedef] +struct C.clap_event_transport_t {} + +@[typedef] +struct C.clap_audio_buffer_t { + channel_count u32 + latency u32 + constant_mask u64 +mut: + data32 &&f32 + data64 &&f64 +} + +@[typedef] +struct C.clap_input_events_t { + ctx voidptr + size fn (&C.clap_input_events_t) u32 + // TODO: How to avoid `voidptr` and use header/event structs instead? + get fn (&C.clap_input_events_t, u32) voidptr +} + +@[typedef] +struct C.clap_event_header_t { + size u32 + time u32 + space_id u16 + @type u16 + flags u32 +} + +// TODO: Why doesn't typedef C structs work here? +struct ClapEventNote { + C.clap_event_header_t + note_id int + port_index i16 + channel i16 + key i16 + velocity f64 +} + +enum ClapEventType as u16 { + note_on = 0 + note_off = 1 + note_choke = 2 + note_end = 3 + note_expression = 4 + param_value = 5 + param_mod = 6 + param_gesture_begin = 7 + param_gesture_end = 8 + transport = 9 + midi = 10 + midi_sysex = 11 + midi2 = 12 +} + +@[typedef] +struct C.clap_output_events_t {} const clap_plugin_factory_id = unsafe { (&char(C.CLAP_PLUGIN_FACTORY_ID)).vstring() } +const clap_core_event_space_id = u16(C.CLAP_CORE_EVENT_SPACE_ID) fn C.clap_version_is_compatible(C.clap_version_t) bool diff --git a/src/ext_audio_ports.v b/src/ext_audio_ports.v new file mode 100644 index 0000000..14ec446 --- /dev/null +++ b/src/ext_audio_ports.v @@ -0,0 +1,29 @@ +module plugin + +const clap_ext_audio_ports = unsafe { (&char(C.CLAP_EXT_AUDIO_PORTS)).vstring() } +const clap_port_stereo = unsafe { (&char(C.CLAP_PORT_STEREO)).vstring() } +const clap_port_mono = unsafe { (&char(C.CLAP_PORT_MONO)).vstring() } + +@[typedef] +struct C.clap_audio_port_info_t { +mut: + id C.clap_id + name [256]char + flags u32 + channel_count u32 + port_type &char + in_place_pair C.clap_id +} + +@[typedef] +struct C.clap_plugin_audio_ports_t { + count fn (&C.clap_plugin_t, bool) u32 + get fn (&C.clap_plugin_t, u32, bool, &C.clap_audio_port_info_t) bool +} + +enum ClapAudioPortFlags as u32 { + is_main = 1 << 0 + supports_64_bits = 1 << 1 + prefers_64_bits = 1 << 2 + requires_common_sample_size = 1 << 3 +} diff --git a/src/ext_latency.v b/src/ext_latency.v new file mode 100644 index 0000000..07b39e3 --- /dev/null +++ b/src/ext_latency.v @@ -0,0 +1,8 @@ +module plugin + +const clap_ext_latency = unsafe { (&char(C.CLAP_EXT_LATENCY)).vstring() } + +@[typedef] +struct C.clap_plugin_latency_t { + get fn (&C.clap_plugin_t) u32 +} diff --git a/src/ext_note_ports.v b/src/ext_note_ports.v new file mode 100644 index 0000000..749542a --- /dev/null +++ b/src/ext_note_ports.v @@ -0,0 +1,25 @@ +module plugin + +const clap_ext_note_ports = unsafe { (&char(C.CLAP_EXT_NOTE_PORTS)).vstring() } + +@[typedef] +struct C.clap_note_port_info_t { +mut: + id C.clap_id + supported_dialects u32 + preferred_dialect u32 + name [256]char +} + +@[typedef] +struct C.clap_plugin_note_ports_t { + count fn (&C.clap_plugin_t, bool) u32 + get fn (&C.clap_plugin_t, u32, bool, &C.clap_note_port_info_t) bool +} + +enum ClapNoteDialect as u32 { + clap = 1 << 0 + midi = 1 << 1 + midi_mpe = 1 << 2 + midi2 = 1 << 3 +} diff --git a/src/main.v b/src/main.v index 146d850..f4a80aa 100644 --- a/src/main.v +++ b/src/main.v @@ -6,4 +6,4 @@ module plugin // Remove "const" so you get: // CLAP_EXPORT extern clap_plugin_entry_t clap_entry; @[markused] -__global clap_entry = _plugin_entry +__global clap_entry = plugin_entry diff --git a/src/plugin.v b/src/plugin.v index d483677..830295e 100644 --- a/src/plugin.v +++ b/src/plugin.v @@ -3,44 +3,187 @@ module plugin // This should be the actual implementation of the plugin with // all the interesting logic like DSP, UI, etc. struct MinimalPlugin { + host &C.clap_host_t +mut: + sample_rate f64 + latency u32 +} + +// Extract our actual pluging from CLAP plugin wrapper. +fn from_clap(clap_plugin &C.clap_plugin_t) &MinimalPlugin { + return unsafe { &MinimalPlugin(clap_plugin.plugin_data) } } -// TODO: Consider mapping to struct's methods? fn MinimalPlugin.init(clap_plugin &C.clap_plugin_t) bool { - // TODO: Actually init. - return true + return true } fn MinimalPlugin.destroy(clap_plugin &C.clap_plugin_t) { - // TODO: Ensure memory clean. + // Cleanup plugin members here. } fn MinimalPlugin.activate(clap_plugin &C.clap_plugin_t, sample_rate f64, min_frames_count u32, max_frames_count u32) bool { - // TODO: Store on plugin: sample_rate. - return false + mut p := from_clap(clap_plugin) + p.sample_rate = sample_rate + return true } fn MinimalPlugin.deactivate(clap_plugin &C.clap_plugin_t) { } fn MinimalPlugin.start_processing(clap_plugin &C.clap_plugin_t) bool { - return true + return true } fn MinimalPlugin.stop_processing(clap_plugin &C.clap_plugin_t) { } -fn MinimalPlugin.reset(clap_plugin &C.clap_plugin_t) { +fn MinimalPlugin.reset(clap_plugin &C.clap_plugin_t) { + // Cleanup plugin members here. +} + +fn (mp MinimalPlugin) process_event (raw_event voidptr) { + header := unsafe { &C.clap_event_header_t(raw_event) } + + if header.space_id != clap_core_event_space_id { + return + } + + match header.@type { + u16(ClapEventType.note_on) { + // Handle note playing. + event := unsafe { &ClapEventNote(raw_event) } + println("Note ON: ${event.note_id}") + } + u16(ClapEventType.note_off) { + // Handle note stop playing. + event := unsafe { &ClapEventNote(raw_event) } + println("Note OFF: ${event.note_id}") + } + // And so on... + else { + t := unsafe { ClapEventType(header.@type) } + println("Unsupported event type: ${t}") + } + } } -fn MinimalPlugin.process(clap_plugin &C.clap_plugin_t, process &C.clap_process_t) ClapProcessStatus { - return ClapProcessStatus.clap_process_continue +fn MinimalPlugin.process(clap_plugin &C.clap_plugin_t, mut process &C.clap_process_t) ClapProcessStatus { + p := from_clap(clap_plugin) + + nframes := process.frames_count + nev := process.in_events.size(process.in_events) + + mut ev_index := u32(0) + mut next_ev_frame := if nev > 0 { 0 } else { nframes } + + for i := 0 ; i < nframes ; { + for ev_index < nev && next_ev_frame == i { + event := process.in_events.get(process.in_events, ev_index) + header := unsafe { &C.clap_event_header_t(event) } + + if header.time != i { + next_ev_frame = header.time + break + } + + p.process_event(event) + ev_index++ + + if ev_index == nev { + next_ev_frame = nframes + break + } + } + + for ; i < next_ev_frame ; i++ { + in_l := unsafe { process.audio_inputs[0].data32[0][i] } + in_r := unsafe { process.audio_inputs[0].data32[1][i] } + // Swap left and right channels. + out_l := in_r + out_r := in_l + unsafe { process.audio_outputs[0].data32[0][i] = out_l } + unsafe { process.audio_outputs[0].data32[1][i] = out_r } + } + } + + return ClapProcessStatus.@continue } fn MinimalPlugin.get_extension(clap_plugin &C.clap_plugin_t, id &char) voidptr { - // TODO - return voidptr(0) + v_id := unsafe { cstring_to_vstring(id) } + + match v_id { + clap_ext_latency { + return &C.clap_plugin_latency_t{ + get: fn (clap_plugin &C.clap_plugin_t) u32 { + p := from_clap(clap_plugin) + return p.latency + } + } + } + + clap_ext_audio_ports { + return &C.clap_plugin_audio_ports_t{ + count: fn (clap_plugin &C.clap_plugin_t, is_input bool) u32 { + return 1 + } + get: fn (clap_plugin &C.clap_plugin_t, index u32, is_input bool, mut info C.clap_audio_port_info_t) bool { + // Just one port. + if index > 0 { + return false + } + + info.id = 0 + info.channel_count = 2 + info.flags = u32(ClapAudioPortFlags.is_main) + info.port_type = clap_port_stereo.str + info.in_place_pair = C.CLAP_INVALID_ID + + // Translate string to constant array. + port_name := 'Example audio port' + for i, chr in port_name { + info.name[i] = chr + } + info.name[port_name.len] = char(0) + + return true + } + } + } + clap_ext_note_ports { + return &C.clap_plugin_note_ports_t{ + count: fn (clap_plugin &C.clap_plugin_t, is_input bool) u32 { + return 1 + } + get: fn (clap_plugin &C.clap_plugin_t, index u32, is_input bool, mut info C.clap_note_port_info_t) bool { + if index > 0 { + return false + } + + info.id = 0 + info.supported_dialects = ( + u32(ClapNoteDialect.clap) | + u32(ClapNoteDialect.midi_mpe) | + u32(ClapNoteDialect.midi2) + ) + info.preferred_dialect = u32(ClapNoteDialect.clap) + + port_name := 'Example note port' + for i, chr in port_name { + info.name[i] = chr + } + info.name[port_name.len] = char(0) + + return true + } + } + } + else { + return unsafe { nil } + } + } } -fn MinimalPlugin.on_main_thread(clap_plugin &C.clap_plugin_t) { +fn MinimalPlugin.on_main_thread(clap_plugin &C.clap_plugin_t) { } diff --git a/src/setup.v b/src/setup.v index f6e34f5..9ae8105 100644 --- a/src/setup.v +++ b/src/setup.v @@ -3,7 +3,9 @@ module plugin // Features of the CLAP plugin. // Have to be defined separately here, otherwise wrong C is generated. const _plugin_features = [ - c'instrument', + c'audio-effect', + c'note-effect', + c'stereo', unsafe { nil }, ]! @@ -21,19 +23,8 @@ const _plugin_descriptor = C.clap_plugin_descriptor_t{ }) } -const _plugin_entry = C.clap_plugin_entry_t{ - clap_version: C.clap_version_t{} - init: entry_init - deinit: entry_deinit - get_factory: entry_get_factory -} - -fn get_plugin_count(factory &C.clap_plugin_factory_t) u32 { - return 1 -} fn create_plugin(factory &C.clap_plugin_factory_t, host &C.clap_host_t, plugin_id &char) &C.clap_plugin_t { - // Sanity checks for lib version and correct plugin expected. if !C.clap_version_is_compatible(host.clap_version) { return unsafe { nil } @@ -44,7 +35,9 @@ fn create_plugin(factory &C.clap_plugin_factory_t, host &C.clap_host_t, plugin_i } // Build actual plugin -- our custom structure. - main_plugin := &MinimalPlugin{} + main_plugin := &MinimalPlugin{ + host: host + } // This is the "official" plugin. clap_plugin := &C.clap_plugin_t{ desc: &_plugin_descriptor @@ -65,28 +58,21 @@ fn create_plugin(factory &C.clap_plugin_factory_t, host &C.clap_host_t, plugin_i return clap_plugin } -fn get_plugin_descriptor(factory &C.clap_plugin_factory_t, index u32) &C.clap_plugin_descriptor_t { - if index == 0 { - return &_plugin_descriptor - } else { - return unsafe { nil } - } -} - -fn entry_init(plugin_path &char) bool { - return true -} - -fn entry_deinit() { -} - fn entry_get_factory(factory_id &char) voidptr { factory_id_v := unsafe { factory_id.vstring() } if factory_id_v == clap_plugin_factory_id { factory := C.clap_plugin_factory_t{ - get_plugin_count: get_plugin_count - get_plugin_descriptor: get_plugin_descriptor + get_plugin_count: fn (factory &C.clap_plugin_factory_t) u32 { + return 1 + } + get_plugin_descriptor: fn (factory &C.clap_plugin_factory_t, index u32) &C.clap_plugin_descriptor_t { + if index == 0 { + return &_plugin_descriptor + } else { + return unsafe { nil } + } + } create_plugin: create_plugin } return voidptr(&factory) @@ -94,3 +80,14 @@ fn entry_get_factory(factory_id &char) voidptr { return unsafe { nil } } + + +const plugin_entry = C.clap_plugin_entry_t{ + clap_version: C.clap_version_t{} + init: fn (plugin_path &char) bool { + return true + } + deinit: fn () {} + + get_factory: entry_get_factory +}