Compare commits

...

22 Commits

Author SHA1 Message Date
6bd42c9862 space at the end of file 2025-07-15 09:35:46 +02:00
17a77fba9e fix lv2 manifest.ttl.in 2025-07-14 10:20:21 +02:00
378bbe9518 vst3: typo bug fix 2025-07-08 12:01:43 +02:00
ff48361165 use tinycthread since msys2 doesn't have threads.h 2025-07-07 12:06:50 +02:00
Paolo Marrone
9759b715d0 vst3: connectionpoint on ui gets called before view attachment - handled 2025-06-30 16:03:38 +02:00
531868ef84 api: ifdef TEMPLATE_SUPPORTS_MESSAGING 2025-06-16 15:05:17 +02:00
Paolo
4fbce1ac7b lv2 messaging: error checking, ifdefs, code cleaning and better names 2025-06-16 14:42:46 +02:00
Paolo
e7af05eead lv2 messaging: send/receive work 2025-06-16 12:05:29 +02:00
Paolo Marrone
996e32f74d lv2 messaging: some breaking progress 2025-06-12 13:34:17 +02:00
Paolo Marrone
622b2ba169 vst3 messaging: more thread safeness and error handling 2025-06-11 16:54:34 +02:00
Paolo Marrone
010a8e314b vst3 messaging: minor fixes 2025-06-11 15:35:55 +02:00
Paolo
7d9f2a0c3a lv2 messaging: fix proper ports order 2025-06-11 10:12:10 +02:00
Paolo Marrone
f7b8b434c8 lv2: messaging wip (actually broken) 2025-06-10 18:18:18 +02:00
Paolo
3bdff1ea8f vst3: support 1 view only 2025-06-09 14:04:54 +02:00
Paolo
ef4fef7d4e Imessage releases 2025-06-09 13:58:19 +02:00
Paolo
b2e7efa0ad vst3 messaging: fix ifdefs 2025-06-09 10:47:22 +02:00
Paolo
e0fe69e3e9 vst3 messaging: cleaning, error checking, TODOs 2025-06-09 10:41:48 +02:00
Paolo Marrone
0aa6d08288 vst3: dsp->ui communication using a separated thread to handle allocation and notification 2025-06-04 18:31:31 +02:00
Paolo
5f693136a8 vst3 beginning of a more solid implementation. 2025-06-04 11:53:30 +02:00
Paolo
c025a95f58 test functions to receive data 2025-06-04 11:52:41 +02:00
Paolo
f5097b14cb api callbacks for sending messages 2025-06-04 11:51:15 +02:00
Paolo
269b9b606f working example for vst3 UI->Audio binary data transfer 2025-06-03 17:22:29 +02:00
14 changed files with 2062 additions and 61 deletions

View File

@ -26,6 +26,11 @@ typedef struct {
const char * format;
const char * (*get_bindir)(void *handle);
const char * (*get_datadir)(void *handle);
{{?it.product.messaging}}
#ifdef TEMPLATE_SUPPORTS_MESSAGING
char (*send_to_ui)(void *handle, const void *data, size_t bytes);
#endif
{{?}}
} plugin_callbacks;
{{?it.product.state && it.product.state.dspCustom}}
@ -50,6 +55,11 @@ typedef struct {
void (*set_parameter)(void *handle, size_t index, float value);
void (*set_parameter_end)(void *handle, size_t index, float value);
{{?}}
{{?it.product.messaging}}
#ifdef TEMPLATE_SUPPORTS_MESSAGING
char (*send_to_dsp)(void *handle, const void *data, size_t bytes);
#endif
{{?}}
} plugin_ui_callbacks;
{{?it.product.parameters.length > 0}}

View File

@ -69,7 +69,7 @@
lv2:portProperty lv2:integer ;
lv2:portProperty lv2:reportsLatency ;
{{??}}
a {{?p.type == "control"}}lv2:ControlPort{{??p.type == "midi"}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} ,
a {{?p.type == "control"}}lv2:ControlPort{{??p.type == "midi"}}atom:AtomPort{{??p.type == "message"}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} ,
{{?p.direction == "input"}}lv2:InputPort{{??}}lv2:OutputPort{{?}} ;
lv2:name "{{=p.name}}" ;
{{?"shortName" in p}}
@ -83,6 +83,11 @@
{{??p.type == "midi"}}
atom:bufferType atom:Sequence ;
atom:supports midi:MidiEvent ;
{{??p.type == "message"}}
atom:bufferType atom:Sequence ;
{{?}}
{{?p.maxSize}}
rsz:minimumSize {{=p.maxSize}};
{{?}}
{{?p.sidechain}}
lv2:portProperty lv2:isSideChain ;
@ -140,5 +145,13 @@
{{?}}
{{?}}
lv2:requiredFeature ui:idleInterface ;
lv2:extensionData ui:idleInterface .
lv2:extensionData ui:idleInterface ;
{{?it.product.messaging}}
ui:portNotification [
ui:ui {{=it.tibia.lv2.ttlURI(it.lv2.uri)}} ;
lv2:symbol "notify" ;
ui:notifyType atom:Blank
]
{{?}}
.
{{?}}

View File

@ -86,3 +86,13 @@ static uint32_t index_to_param[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONT
{{?it.product.state && it.product.state.dspCustom}}
#define DATA_STATE_DSP_CUSTOM
{{?}}
{{?it.product.messaging}}
#define DATA_MESSAGING 1
#define DATA_MESSAGING_PORTS_N 2
#define DATA_MESSAGING_MAX {{=it.product.messaging.maxSize}}
#define DATA_MESSAGING_PORT_IN {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.id == 'message_in'))}}
#define DATA_MESSAGING_PORT_OUT {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.id == 'message_out'))}}
{{??}}
#define DATA_MESSAGING_PORTS_N 0
{{?}}

View File

@ -15,12 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with Tibia. If not, see <http://www.gnu.org/licenses/>.
*
* File author: Stefano D'Angelo
* File authors: Stefano D'Angelo, Paolo Marrone
*/
#include <stdlib.h>
#include <stdint.h>
#define TEMPLATE_SUPPORTS_MESSAGING 1
#include "data.h"
#include "plugin_api.h"
#pragma GCC diagnostic push
@ -49,7 +51,9 @@
#ifdef DATA_STATE_DSP_CUSTOM
# include <lv2/state/state.h>
#endif
#ifdef DATA_MESSAGING
#include <lv2/atom/forge.h>
#endif
#include <string.h>
#if defined(__i386__) || defined(__x86_64__)
@ -72,7 +76,8 @@
DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N \
+ DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N \
+ DATA_PRODUCT_MIDI_INPUTS_N \
+ DATA_PRODUCT_MIDI_OUTPUTS_N )
+ DATA_PRODUCT_MIDI_OUTPUTS_N \
+ DATA_MESSAGING_PORTS_N )
#define CONTROL_OUTPUT_INDEX_OFFSET (CONTROL_INPUT_INDEX_OFFSET + DATA_PRODUCT_CONTROL_INPUTS_N)
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
@ -91,6 +96,22 @@ static float adjust_param(size_t index, float value) {
}
#endif
#ifdef DATA_MESSAGING
typedef struct {
LV2_URID eventTransfer;
LV2_URID message;
LV2_URID message_length;
LV2_URID message_body;
} messaing_URIs;
static inline void messaing_URIs_map(LV2_URID_Map* map, messaing_URIs* uris) {
uris->eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer);
uris->message = map->map(map->handle, DATA_LV2_URI"#message");
uris->message_length = map->map(map->handle, LV2_ATOM__Int);
uris->message_body = map->map(map->handle, DATA_LV2_URI"#message_body");
}
#endif
typedef struct {
plugin p;
float sample_rate;
@ -132,6 +153,15 @@ typedef struct {
LV2_State_Store_Function state_store;
LV2_State_Handle state_handle;
#endif
#ifdef DATA_MESSAGING
struct {
messaing_URIs URIs;
const LV2_Atom_Sequence *control;
LV2_Atom_Sequence *notify;
LV2_Atom_Forge forge;
LV2_Atom_Forge_Frame frame;
} messaging;
#endif
} plugin_instance;
static const char * get_bundle_path_cb(void *handle) {
@ -168,6 +198,35 @@ static void state_set_parameter_cb(void *handle, size_t index, float value) {
# endif
#endif
#ifdef DATA_MESSAGING
static char send_to_ui (void *handle, const void *data, size_t bytes) {
plugin_instance *i = (plugin_instance *) handle;
if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) {
return 1;
}
const uint32_t space = i->messaging.notify->atom.size;
lv2_atom_forge_set_buffer(&i->messaging.forge, (uint8_t*)i->messaging.notify, space);
if (!lv2_atom_forge_sequence_head(&i->messaging.forge, &i->messaging.frame, 0)) return 1;
LV2_Atom_Forge_Frame frame;
if (!lv2_atom_forge_frame_time(&i->messaging.forge, 0)) return 1;
if (!lv2_atom_forge_object(&i->messaging.forge, &frame, 0, i->messaging.URIs.message)) return 1;
if (!lv2_atom_forge_key(&i->messaging.forge, i->messaging.URIs.message_length)) return 1;
if (!lv2_atom_forge_int(&i->messaging.forge, (int32_t)bytes)) return 1;
if (!lv2_atom_forge_key(&i->messaging.forge, i->messaging.URIs.message_body)) return 1;
if (!lv2_atom_forge_write(&i->messaging.forge, data, bytes)) return 1;
lv2_atom_forge_pop(&i->messaging.forge, &frame);
return 0;
}
#endif
static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double sample_rate, const char * bundle_path, const LV2_Feature * const * features) {
(void)descriptor;
(void)bundle_path;
@ -209,7 +268,10 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s
/* .handle = */ (void *)instance,
/* .format = */ "lv2",
/* .get_bindir = */ get_bundle_path_cb,
/* .get_datadir = */ get_bundle_path_cb
/* .get_datadir = */ get_bundle_path_cb,
#ifdef DATA_MESSAGING
/* .send_to_ui = */ send_to_ui
#endif
};
plugin_init(&instance->p, &cbs);
@ -246,6 +308,10 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s
for (uint32_t i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++)
instance->c[i] = NULL;
#endif
#ifdef DATA_MESSAGING
lv2_atom_forge_init(&instance->messaging.forge, instance->map);
messaing_URIs_map(instance->map, &instance->messaging.URIs);
#endif
return instance;
@ -263,6 +329,7 @@ err_instance:
static void connect_port(LV2_Handle instance, uint32_t port, void * data_location) {
plugin_instance * i = (plugin_instance *)instance;
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
if (port < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N) {
i->x[port] = data_location;
@ -291,6 +358,19 @@ static void connect_port(LV2_Handle instance, uint32_t port, void * data_locatio
}
port -= DATA_PRODUCT_MIDI_OUTPUTS_N;
#endif
#ifdef DATA_MESSAGING
if (port < DATA_MESSAGING_PORTS_N) {
if (port == 0) {
i->messaging.control = (const LV2_Atom_Sequence*)data_location;
return;
}
if (port == 1) {
i->messaging.notify = (LV2_Atom_Sequence*)data_location;
return;
}
}
port -= DATA_MESSAGING_PORTS_N;
#endif
#if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
i->c[port] = data_location;
#endif
@ -319,6 +399,35 @@ static void activate(LV2_Handle instance) {
static void run(LV2_Handle instance, uint32_t sample_count) {
plugin_instance * i = (plugin_instance *)instance;
#ifdef DATA_MESSAGING
LV2_ATOM_SEQUENCE_FOREACH(i->messaging.control, ev) {
if (!lv2_atom_forge_is_object_type(&i->messaging.forge, ev->body.type))
continue;
const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
if (obj->body.otype != i->messaging.URIs.message)
continue;
const LV2_Atom* msg_length = NULL;
const LV2_Atom* msg_data = NULL;
lv2_atom_object_get(obj,
i->messaging.URIs.message_length,
&msg_length,
i->messaging.URIs.message_body,
&msg_data, 0);
if (!msg_length || !msg_data)
continue;
size_t *length = (size_t*) LV2_ATOM_BODY(msg_length);
void *data = (void*) msg_data; // LV2_ATOM_BODY(msg_data);
plugin_receive_from_ui(&i->p, data, *length);
}
#endif
#if defined(__aarch64__)
uint64_t fpcr;
__asm__ __volatile__ ("mrs %0, fpcr" : "=r"(fpcr));
@ -521,6 +630,15 @@ typedef struct {
char has_touch;
LV2UI_Touch touch;
# endif
# ifdef DATA_MESSAGING
struct {
messaing_URIs URIs;
LV2_URID_Map *map;
LV2_Atom_Forge forge;
LV2_Atom_Forge_Frame frame;
uint8_t obj_buf[DATA_MESSAGING_MAX];
} messaging;
# endif
} ui_instance;
static const char * ui_get_bundle_path_cb(void *handle) {
@ -557,6 +675,33 @@ static void ui_set_parameter_end_cb(void *handle, size_t index, float value) {
}
# endif
# ifdef DATA_MESSAGING
static char send_to_dsp (void *handle, const void *data, size_t bytes) {
ui_instance* instance = (ui_instance*) handle;
if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) {
return 1;
}
lv2_atom_forge_set_buffer(&instance->messaging.forge, instance->messaging.obj_buf, sizeof(instance->messaging.obj_buf));
LV2_Atom* msg = (LV2_Atom*) lv2_atom_forge_object(&instance->messaging.forge, &instance->messaging.frame, 0, instance->messaging.URIs.message);
if (!msg) return 1;
if (!lv2_atom_forge_key(&instance->messaging.forge, instance->messaging.URIs.message_length)) return 1;
if (!lv2_atom_forge_int(&instance->messaging.forge, (int32_t)bytes)) return 1;
if (!lv2_atom_forge_key(&instance->messaging.forge, instance->messaging.URIs.message_body)) return 1;
if (!lv2_atom_forge_write(&instance->messaging.forge, data, bytes)) return 1;
lv2_atom_forge_pop(&instance->messaging.forge, &instance->messaging.frame);
instance->write(instance->controller, DATA_MESSAGING_PORT_IN, lv2_atom_total_size(msg), instance->messaging.URIs.eventTransfer, msg);
return 0;
}
# endif
static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const char * plugin_uri, const char * bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget * widget, const LV2_Feature * const * features) {
(void)descriptor;
(void)plugin_uri;
@ -596,6 +741,9 @@ static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const ch
/* .set_parameter_begin = */ ui_set_parameter_begin_cb,
/* .set_parameter = */ ui_set_parameter_cb,
/* .set_parameter_end = */ ui_set_parameter_end_cb,
# endif
# ifdef DATA_MESSAGING
/* .send_to_dsp = */ send_to_dsp
# endif
};
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
@ -610,6 +758,17 @@ static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const ch
goto err_create;
*widget = instance->ui->widget;
# ifdef DATA_MESSAGING
for (int i = 0; features[i]; ++i) {
if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) {
instance->messaging.map = (LV2_URID_Map*)features[i]->data;
}
}
lv2_atom_forge_init(&instance->messaging.forge, instance->messaging.map);
messaing_URIs_map(instance->messaging.map, &instance->messaging.URIs);
# endif
return instance;
err_create:
@ -628,12 +787,11 @@ static void ui_cleanup(LV2UI_Handle handle) {
free(instance);
}
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0 || defined(DATA_MESSAGING)
static void ui_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void * buffer) {
(void)buffer_size;
(void)format;
ui_instance *instance = (ui_instance *)handle;
if (format == 0 && buffer_size == sizeof(float)) {
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
if (port_index < CONTROL_OUTPUT_INDEX_OFFSET) {
size_t index = port_index - CONTROL_INPUT_INDEX_OFFSET;
@ -647,6 +805,29 @@ static void ui_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buf
plugin_ui_set_parameter(instance->ui, param_out_index[port_index - CONTROL_OUTPUT_INDEX_OFFSET], *((float *)buffer));
# endif
}
# ifdef DATA_MESSAGING
else if (format == instance->messaging.URIs.eventTransfer) {
const LV2_Atom* atom = (const LV2_Atom*)buffer;
if (lv2_atom_forge_is_object_type(&instance->messaging.forge, atom->type)) {
const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
if (obj->body.otype == instance->messaging.URIs.message) {
const LV2_Atom* len_a = NULL;
const LV2_Atom* data_a = NULL;
lv2_atom_object_get(obj,
instance->messaging.URIs.message_length, &len_a,
instance->messaging.URIs.message_body, &data_a, NULL);
if (!len_a || !data_a)
return;
const int32_t len = ((const LV2_Atom_Int*) len_a)->body;
const uint8_t* data = (uint8_t*) data_a;
plugin_ui_receive_from_dsp(instance->ui, data, len);
}
}
}
# endif
}
# endif
static int ui_idle(LV2UI_Handle handle) {
@ -666,7 +847,7 @@ static const LV2UI_Descriptor ui_descriptor = {
/* .URI = */ DATA_LV2_UI_URI,
/* .instantiate = */ ui_instantiate,
/* .cleanup = */ ui_cleanup,
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0 || defined(DATA_MESSAGING)
/* .port_event = */ ui_port_event,
# else
/* .port_event = */ NULL,

View File

@ -37,7 +37,8 @@ module.exports = function (data, api, outputCommon, outputData) {
{ id: "state", uri: "http://lv2plug.in/ns/extensions/state#" },
{ id: "ui", uri: "http://lv2plug.in/ns/extensions/ui#" },
{ id: "units", uri: "http://lv2plug.in/ns/extensions/units#" },
{ id: "urid", uri: "http://lv2plug.in/ns/ext/urid#" }
{ id: "urid", uri: "http://lv2plug.in/ns/ext/urid#" },
{ id: "rsz", uri: "http://lv2plug.in/ns/ext/resize-port#" }
],
units: {
"bar": "@units:bar",
@ -113,6 +114,27 @@ module.exports = function (data, api, outputCommon, outputData) {
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, audioPorts);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, midiPorts);
if (data.product.messaging) {
const ps = [
{
type: "message",
direction: "input",
control: true,
id: "message_in",
name: "message_in"
},
{
type: "message",
direction: "output",
control: true,
id: "message_out",
name: "message_out",
maxSize: data.product.messaging.maxSize
}
];
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, ps);
}
var ports = [];
for (var i = 0; i < data.product.parameters.length; i++) {
var p = data.product.parameters[i];

View File

@ -68,7 +68,7 @@ BUILD_DATA_DIR := build/$(BUNDLE_DIR)/Contents/Resources
-include $(MKINC_DIR)/vars-pre.mk
CFLAGS := -O3 -Wall -Wpedantic -Wextra
CFLAGS := -O3 -std=c11 -Wall -Wpedantic -Wextra
CFLAGS_ALL := -I$(DATA_DIR)/src -I$(PLUGIN_DIR) -I$(API_DIR) -fPIC -fvisibility=hidden $(CFLAGS_EXTRA) $(CFLAGS)
LDFLAGS :=
@ -97,7 +97,7 @@ endif
DLL_FILE := $(DLL_DIR)/$(BUNDLE_NAME)$(DLL_SUFFIX)
C_SRCS := $(COMMON_DIR)/src/vst3.c $(C_SRCS_EXTRA)
C_SRCS := $(COMMON_DIR)/src/vst3.c $(COMMON_DIR)/src/tinycthread.c $(C_SRCS_EXTRA)
C_OBJS := $(addprefix build/obj/, $(notdir $(C_SRCS:.c=.o)))
M_SRCS := $(M_SRCS_EXTRA)

View File

@ -267,3 +267,8 @@ static size_t parameterInfoToDataIndex[DATA_PRODUCT_PARAMETERS_N] = {
{{?it.product.state && it.product.state.dspCustom}}
#define DATA_STATE_DSP_CUSTOM
{{?}}
{{?it.product.messaging}}
#define DATA_MESSAGING 1
#define DATA_MESSAGING_MAX {{=it.product.messaging.maxSize}}
{{?}}

View File

@ -0,0 +1,931 @@
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
Copyright (c) 2012 Marcus Geelnard
Copyright (c) 2013-2016 Evan Nemerson
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "tinycthread.h"
#include <stdlib.h>
/* Platform specific includes */
#if defined(_TTHREAD_POSIX_)
#include <signal.h>
#include <sched.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
#elif defined(_TTHREAD_WIN32_)
#include <process.h>
#include <sys/timeb.h>
#endif
/* Standard, good-to-have defines */
#ifndef NULL
#define NULL (void*)0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
int mtx_init(mtx_t *mtx, int type)
{
#if defined(_TTHREAD_WIN32_)
mtx->mAlreadyLocked = FALSE;
mtx->mRecursive = type & mtx_recursive;
mtx->mTimed = type & mtx_timed;
if (!mtx->mTimed)
{
InitializeCriticalSection(&(mtx->mHandle.cs));
}
else
{
mtx->mHandle.mut = CreateMutex(NULL, FALSE, NULL);
if (mtx->mHandle.mut == NULL)
{
return thrd_error;
}
}
return thrd_success;
#else
int ret;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
if (type & mtx_recursive)
{
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
}
ret = pthread_mutex_init(mtx, &attr);
pthread_mutexattr_destroy(&attr);
return ret == 0 ? thrd_success : thrd_error;
#endif
}
void mtx_destroy(mtx_t *mtx)
{
#if defined(_TTHREAD_WIN32_)
if (!mtx->mTimed)
{
DeleteCriticalSection(&(mtx->mHandle.cs));
}
else
{
CloseHandle(mtx->mHandle.mut);
}
#else
pthread_mutex_destroy(mtx);
#endif
}
int mtx_lock(mtx_t *mtx)
{
#if defined(_TTHREAD_WIN32_)
if (!mtx->mTimed)
{
EnterCriticalSection(&(mtx->mHandle.cs));
}
else
{
switch (WaitForSingleObject(mtx->mHandle.mut, INFINITE))
{
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
default:
return thrd_error;
}
}
if (!mtx->mRecursive)
{
while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */
mtx->mAlreadyLocked = TRUE;
}
return thrd_success;
#else
return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error;
#endif
}
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts)
{
#if defined(_TTHREAD_WIN32_)
struct timespec current_ts;
DWORD timeoutMs;
if (!mtx->mTimed)
{
return thrd_error;
}
timespec_get(&current_ts, TIME_UTC);
if ((current_ts.tv_sec > ts->tv_sec) || ((current_ts.tv_sec == ts->tv_sec) && (current_ts.tv_nsec >= ts->tv_nsec)))
{
timeoutMs = 0;
}
else
{
timeoutMs = (DWORD)(ts->tv_sec - current_ts.tv_sec) * 1000;
timeoutMs += (ts->tv_nsec - current_ts.tv_nsec) / 1000000;
timeoutMs += 1;
}
/* TODO: the timeout for WaitForSingleObject doesn't include time
while the computer is asleep. */
switch (WaitForSingleObject(mtx->mHandle.mut, timeoutMs))
{
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
return thrd_timedout;
case WAIT_ABANDONED:
default:
return thrd_error;
}
if (!mtx->mRecursive)
{
while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */
mtx->mAlreadyLocked = TRUE;
}
return thrd_success;
#elif defined(_POSIX_TIMEOUTS) && (_POSIX_TIMEOUTS >= 200112L) && defined(_POSIX_THREADS) && (_POSIX_THREADS >= 200112L)
switch (pthread_mutex_timedlock(mtx, ts)) {
case 0:
return thrd_success;
case ETIMEDOUT:
return thrd_timedout;
default:
return thrd_error;
}
#else
int rc;
struct timespec cur, dur;
/* Try to acquire the lock and, if we fail, sleep for 5ms. */
while ((rc = pthread_mutex_trylock (mtx)) == EBUSY) {
timespec_get(&cur, TIME_UTC);
if ((cur.tv_sec > ts->tv_sec) || ((cur.tv_sec == ts->tv_sec) && (cur.tv_nsec >= ts->tv_nsec)))
{
break;
}
dur.tv_sec = ts->tv_sec - cur.tv_sec;
dur.tv_nsec = ts->tv_nsec - cur.tv_nsec;
if (dur.tv_nsec < 0)
{
dur.tv_sec--;
dur.tv_nsec += 1000000000;
}
if ((dur.tv_sec != 0) || (dur.tv_nsec > 5000000))
{
dur.tv_sec = 0;
dur.tv_nsec = 5000000;
}
nanosleep(&dur, NULL);
}
switch (rc) {
case 0:
return thrd_success;
case ETIMEDOUT:
case EBUSY:
return thrd_timedout;
default:
return thrd_error;
}
#endif
}
int mtx_trylock(mtx_t *mtx)
{
#if defined(_TTHREAD_WIN32_)
int ret;
if (!mtx->mTimed)
{
ret = TryEnterCriticalSection(&(mtx->mHandle.cs)) ? thrd_success : thrd_busy;
}
else
{
ret = (WaitForSingleObject(mtx->mHandle.mut, 0) == WAIT_OBJECT_0) ? thrd_success : thrd_busy;
}
if ((!mtx->mRecursive) && (ret == thrd_success))
{
if (mtx->mAlreadyLocked)
{
LeaveCriticalSection(&(mtx->mHandle.cs));
ret = thrd_busy;
}
else
{
mtx->mAlreadyLocked = TRUE;
}
}
return ret;
#else
return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy;
#endif
}
int mtx_unlock(mtx_t *mtx)
{
#if defined(_TTHREAD_WIN32_)
mtx->mAlreadyLocked = FALSE;
if (!mtx->mTimed)
{
LeaveCriticalSection(&(mtx->mHandle.cs));
}
else
{
if (!ReleaseMutex(mtx->mHandle.mut))
{
return thrd_error;
}
}
return thrd_success;
#else
return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;;
#endif
}
#if defined(_TTHREAD_WIN32_)
#define _CONDITION_EVENT_ONE 0
#define _CONDITION_EVENT_ALL 1
#endif
int cnd_init(cnd_t *cond)
{
#if defined(_TTHREAD_WIN32_)
cond->mWaitersCount = 0;
/* Init critical section */
InitializeCriticalSection(&cond->mWaitersCountLock);
/* Init events */
cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL)
{
cond->mEvents[_CONDITION_EVENT_ALL] = NULL;
return thrd_error;
}
cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL);
if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL)
{
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
cond->mEvents[_CONDITION_EVENT_ONE] = NULL;
return thrd_error;
}
return thrd_success;
#else
return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error;
#endif
}
void cnd_destroy(cnd_t *cond)
{
#if defined(_TTHREAD_WIN32_)
if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL)
{
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
}
if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL)
{
CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]);
}
DeleteCriticalSection(&cond->mWaitersCountLock);
#else
pthread_cond_destroy(cond);
#endif
}
int cnd_signal(cnd_t *cond)
{
#if defined(_TTHREAD_WIN32_)
int haveWaiters;
/* Are there any waiters? */
EnterCriticalSection(&cond->mWaitersCountLock);
haveWaiters = (cond->mWaitersCount > 0);
LeaveCriticalSection(&cond->mWaitersCountLock);
/* If we have any waiting threads, send them a signal */
if(haveWaiters)
{
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0)
{
return thrd_error;
}
}
return thrd_success;
#else
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error;
#endif
}
int cnd_broadcast(cnd_t *cond)
{
#if defined(_TTHREAD_WIN32_)
int haveWaiters;
/* Are there any waiters? */
EnterCriticalSection(&cond->mWaitersCountLock);
haveWaiters = (cond->mWaitersCount > 0);
LeaveCriticalSection(&cond->mWaitersCountLock);
/* If we have any waiting threads, send them a signal */
if(haveWaiters)
{
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
{
return thrd_error;
}
}
return thrd_success;
#else
return pthread_cond_broadcast(cond) == 0 ? thrd_success : thrd_error;
#endif
}
#if defined(_TTHREAD_WIN32_)
static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout)
{
DWORD result;
int lastWaiter;
/* Increment number of waiters */
EnterCriticalSection(&cond->mWaitersCountLock);
++ cond->mWaitersCount;
LeaveCriticalSection(&cond->mWaitersCountLock);
/* Release the mutex while waiting for the condition (will decrease
the number of waiters when done)... */
mtx_unlock(mtx);
/* Wait for either event to become signaled due to cnd_signal() or
cnd_broadcast() being called */
result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout);
if (result == WAIT_TIMEOUT)
{
/* The mutex is locked again before the function returns, even if an error occurred */
mtx_lock(mtx);
return thrd_timedout;
}
else if (result == WAIT_FAILED)
{
/* The mutex is locked again before the function returns, even if an error occurred */
mtx_lock(mtx);
return thrd_error;
}
/* Check if we are the last waiter */
EnterCriticalSection(&cond->mWaitersCountLock);
-- cond->mWaitersCount;
lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) &&
(cond->mWaitersCount == 0);
LeaveCriticalSection(&cond->mWaitersCountLock);
/* If we are the last waiter to be notified to stop waiting, reset the event */
if (lastWaiter)
{
if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
{
/* The mutex is locked again before the function returns, even if an error occurred */
mtx_lock(mtx);
return thrd_error;
}
}
/* Re-acquire the mutex */
mtx_lock(mtx);
return thrd_success;
}
#endif
int cnd_wait(cnd_t *cond, mtx_t *mtx)
{
#if defined(_TTHREAD_WIN32_)
return _cnd_timedwait_win32(cond, mtx, INFINITE);
#else
return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error;
#endif
}
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts)
{
#if defined(_TTHREAD_WIN32_)
struct timespec now;
if (timespec_get(&now, TIME_UTC) == TIME_UTC)
{
unsigned long long nowInMilliseconds = now.tv_sec * 1000 + now.tv_nsec / 1000000;
unsigned long long tsInMilliseconds = ts->tv_sec * 1000 + ts->tv_nsec / 1000000;
DWORD delta = (tsInMilliseconds > nowInMilliseconds) ?
(DWORD)(tsInMilliseconds - nowInMilliseconds) : 0;
return _cnd_timedwait_win32(cond, mtx, delta);
}
else
return thrd_error;
#else
int ret;
ret = pthread_cond_timedwait(cond, mtx, ts);
if (ret == ETIMEDOUT)
{
return thrd_timedout;
}
return ret == 0 ? thrd_success : thrd_error;
#endif
}
#if defined(_TTHREAD_WIN32_)
struct TinyCThreadTSSData {
void* value;
tss_t key;
struct TinyCThreadTSSData* next;
};
static tss_dtor_t _tinycthread_tss_dtors[1088] = { NULL, };
static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_head = NULL;
static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_tail = NULL;
static void _tinycthread_tss_cleanup (void);
static void _tinycthread_tss_cleanup (void) {
struct TinyCThreadTSSData* data;
int iteration;
unsigned int again = 1;
void* value;
for (iteration = 0 ; iteration < TSS_DTOR_ITERATIONS && again > 0 ; iteration++)
{
again = 0;
for (data = _tinycthread_tss_head ; data != NULL ; data = data->next)
{
if (data->value != NULL)
{
value = data->value;
data->value = NULL;
if (_tinycthread_tss_dtors[data->key] != NULL)
{
again = 1;
_tinycthread_tss_dtors[data->key](value);
}
}
}
}
while (_tinycthread_tss_head != NULL) {
data = _tinycthread_tss_head->next;
free (_tinycthread_tss_head);
_tinycthread_tss_head = data;
}
_tinycthread_tss_head = NULL;
_tinycthread_tss_tail = NULL;
}
static void NTAPI _tinycthread_tss_callback(PVOID h, DWORD dwReason, PVOID pv)
{
(void)h;
(void)pv;
if (_tinycthread_tss_head != NULL && (dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH))
{
_tinycthread_tss_cleanup();
}
}
#if defined(_MSC_VER)
#ifdef _M_X64
#pragma const_seg(".CRT$XLB")
#else
#pragma data_seg(".CRT$XLB")
#endif
PIMAGE_TLS_CALLBACK p_thread_callback = _tinycthread_tss_callback;
#ifdef _M_X64
#pragma data_seg()
#else
#pragma const_seg()
#endif
#else
PIMAGE_TLS_CALLBACK p_thread_callback __attribute__((section(".CRT$XLB"))) = _tinycthread_tss_callback;
#endif
#endif /* defined(_TTHREAD_WIN32_) */
/** Information to pass to the new thread (what to run). */
typedef struct {
thrd_start_t mFunction; /**< Pointer to the function to be executed. */
void * mArg; /**< Function argument for the thread function. */
} _thread_start_info;
/* Thread wrapper function. */
#if defined(_TTHREAD_WIN32_)
static DWORD WINAPI _thrd_wrapper_function(LPVOID aArg)
#elif defined(_TTHREAD_POSIX_)
static void * _thrd_wrapper_function(void * aArg)
#endif
{
thrd_start_t fun;
void *arg;
int res;
/* Get thread startup information */
_thread_start_info *ti = (_thread_start_info *) aArg;
fun = ti->mFunction;
arg = ti->mArg;
/* The thread is responsible for freeing the startup information */
free((void *)ti);
/* Call the actual client thread function */
res = fun(arg);
#if defined(_TTHREAD_WIN32_)
if (_tinycthread_tss_head != NULL)
{
_tinycthread_tss_cleanup();
}
return (DWORD)res;
#else
return (void*)(intptr_t)res;
#endif
}
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg)
{
/* Fill out the thread startup information (passed to the thread wrapper,
which will eventually free it) */
_thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info));
if (ti == NULL)
{
return thrd_nomem;
}
ti->mFunction = func;
ti->mArg = arg;
/* Create the thread */
#if defined(_TTHREAD_WIN32_)
*thr = CreateThread(NULL, 0, _thrd_wrapper_function, (LPVOID) ti, 0, NULL);
#elif defined(_TTHREAD_POSIX_)
if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0)
{
*thr = 0;
}
#endif
/* Did we fail to create the thread? */
if(!*thr)
{
free(ti);
return thrd_error;
}
return thrd_success;
}
thrd_t thrd_current(void)
{
#if defined(_TTHREAD_WIN32_)
return GetCurrentThread();
#else
return pthread_self();
#endif
}
int thrd_detach(thrd_t thr)
{
#if defined(_TTHREAD_WIN32_)
/* https://stackoverflow.com/questions/12744324/how-to-detach-a-thread-on-windows-c#answer-12746081 */
return CloseHandle(thr) != 0 ? thrd_success : thrd_error;
#else
return pthread_detach(thr) == 0 ? thrd_success : thrd_error;
#endif
}
int thrd_equal(thrd_t thr0, thrd_t thr1)
{
#if defined(_TTHREAD_WIN32_)
return GetThreadId(thr0) == GetThreadId(thr1);
#else
return pthread_equal(thr0, thr1);
#endif
}
void thrd_exit(int res)
{
#if defined(_TTHREAD_WIN32_)
if (_tinycthread_tss_head != NULL)
{
_tinycthread_tss_cleanup();
}
ExitThread((DWORD)res);
#else
pthread_exit((void*)(intptr_t)res);
#endif
}
int thrd_join(thrd_t thr, int *res)
{
#if defined(_TTHREAD_WIN32_)
DWORD dwRes;
if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED)
{
return thrd_error;
}
if (res != NULL)
{
if (GetExitCodeThread(thr, &dwRes) != 0)
{
*res = (int) dwRes;
}
else
{
return thrd_error;
}
}
CloseHandle(thr);
#elif defined(_TTHREAD_POSIX_)
void *pres;
if (pthread_join(thr, &pres) != 0)
{
return thrd_error;
}
if (res != NULL)
{
*res = (int)(intptr_t)pres;
}
#endif
return thrd_success;
}
int thrd_sleep(const struct timespec *duration, struct timespec *remaining)
{
#if !defined(_TTHREAD_WIN32_)
int res = nanosleep(duration, remaining);
if (res == 0) {
return 0;
} else if (errno == EINTR) {
return -1;
} else {
return -2;
}
#else
struct timespec start;
DWORD t;
timespec_get(&start, TIME_UTC);
t = SleepEx((DWORD)(duration->tv_sec * 1000 +
duration->tv_nsec / 1000000 +
(((duration->tv_nsec % 1000000) == 0) ? 0 : 1)),
TRUE);
if (t == 0) {
return 0;
} else {
if (remaining != NULL) {
timespec_get(remaining, TIME_UTC);
remaining->tv_sec -= start.tv_sec;
remaining->tv_nsec -= start.tv_nsec;
if (remaining->tv_nsec < 0)
{
remaining->tv_nsec += 1000000000;
remaining->tv_sec -= 1;
}
}
return (t == WAIT_IO_COMPLETION) ? -1 : -2;
}
#endif
}
void thrd_yield(void)
{
#if defined(_TTHREAD_WIN32_)
Sleep(0);
#else
sched_yield();
#endif
}
int tss_create(tss_t *key, tss_dtor_t dtor)
{
#if defined(_TTHREAD_WIN32_)
*key = TlsAlloc();
if (*key == TLS_OUT_OF_INDEXES)
{
return thrd_error;
}
_tinycthread_tss_dtors[*key] = dtor;
#else
if (pthread_key_create(key, dtor) != 0)
{
return thrd_error;
}
#endif
return thrd_success;
}
void tss_delete(tss_t key)
{
#if defined(_TTHREAD_WIN32_)
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*) TlsGetValue (key);
struct TinyCThreadTSSData* prev = NULL;
if (data != NULL)
{
if (data == _tinycthread_tss_head)
{
_tinycthread_tss_head = data->next;
}
else
{
prev = _tinycthread_tss_head;
if (prev != NULL)
{
while (prev->next != data)
{
prev = prev->next;
}
}
}
if (data == _tinycthread_tss_tail)
{
_tinycthread_tss_tail = prev;
}
free (data);
}
_tinycthread_tss_dtors[key] = NULL;
TlsFree(key);
#else
pthread_key_delete(key);
#endif
}
void *tss_get(tss_t key)
{
#if defined(_TTHREAD_WIN32_)
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key);
if (data == NULL)
{
return NULL;
}
return data->value;
#else
return pthread_getspecific(key);
#endif
}
int tss_set(tss_t key, void *val)
{
#if defined(_TTHREAD_WIN32_)
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key);
if (data == NULL)
{
data = (struct TinyCThreadTSSData*)malloc(sizeof(struct TinyCThreadTSSData));
if (data == NULL)
{
return thrd_error;
}
data->value = NULL;
data->key = key;
data->next = NULL;
if (_tinycthread_tss_tail != NULL)
{
_tinycthread_tss_tail->next = data;
}
else
{
_tinycthread_tss_tail = data;
}
if (_tinycthread_tss_head == NULL)
{
_tinycthread_tss_head = data;
}
if (!TlsSetValue(key, data))
{
free (data);
return thrd_error;
}
}
data->value = val;
#else
if (pthread_setspecific(key, val) != 0)
{
return thrd_error;
}
#endif
return thrd_success;
}
#if defined(_TTHREAD_EMULATE_TIMESPEC_GET_)
int _tthread_timespec_get(struct timespec *ts, int base)
{
#if defined(_TTHREAD_WIN32_)
struct _timeb tb;
#elif !defined(CLOCK_REALTIME)
struct timeval tv;
#endif
if (base != TIME_UTC)
{
return 0;
}
#if defined(_TTHREAD_WIN32_)
_ftime_s(&tb);
ts->tv_sec = (time_t)tb.time;
ts->tv_nsec = 1000000L * (long)tb.millitm;
#elif defined(CLOCK_REALTIME)
base = (clock_gettime(CLOCK_REALTIME, ts) == 0) ? base : 0;
#else
gettimeofday(&tv, NULL);
ts->tv_sec = (time_t)tv.tv_sec;
ts->tv_nsec = 1000L * (long)tv.tv_usec;
#endif
return base;
}
#endif /* _TTHREAD_EMULATE_TIMESPEC_GET_ */
#if defined(_TTHREAD_WIN32_)
void call_once(once_flag *flag, void (*func)(void))
{
/* The idea here is that we use a spin lock (via the
InterlockedCompareExchange function) to restrict access to the
critical section until we have initialized it, then we use the
critical section to block until the callback has completed
execution. */
while (flag->status < 3)
{
switch (flag->status)
{
case 0:
if (InterlockedCompareExchange (&(flag->status), 1, 0) == 0) {
InitializeCriticalSection(&(flag->lock));
EnterCriticalSection(&(flag->lock));
flag->status = 2;
func();
flag->status = 3;
LeaveCriticalSection(&(flag->lock));
return;
}
break;
case 1:
break;
case 2:
EnterCriticalSection(&(flag->lock));
LeaveCriticalSection(&(flag->lock));
break;
}
}
}
#endif /* defined(_TTHREAD_WIN32_) */
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,479 @@
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
Copyright (c) 2012 Marcus Geelnard
Copyright (c) 2013-2016 Evan Nemerson
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#ifndef _TINYCTHREAD_H_
#define _TINYCTHREAD_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file
* @mainpage TinyCThread API Reference
*
* @section intro_sec Introduction
* TinyCThread is a minimal, portable implementation of basic threading
* classes for C.
*
* They closely mimic the functionality and naming of the C11 standard, and
* should be easily replaceable with the corresponding standard variants.
*
* @section port_sec Portability
* The Win32 variant uses the native Win32 API for implementing the thread
* classes, while for other systems, the POSIX threads API (pthread) is used.
*
* @section misc_sec Miscellaneous
* The following special keywords are available: #_Thread_local.
*
* For more detailed information, browse the different sections of this
* documentation. A good place to start is:
* tinycthread.h.
*/
/* Which platform are we on? */
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
#define _TTHREAD_WIN32_
#else
#define _TTHREAD_POSIX_
#endif
#define _TTHREAD_PLATFORM_DEFINED_
#endif
/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */
#if defined(_TTHREAD_POSIX_)
#undef _FEATURES_H
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L)
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199309L
#endif
#if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500)
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#define _XPG6
#endif
/* Generic includes */
#include <time.h>
/* Platform specific includes */
#if defined(_TTHREAD_POSIX_)
#include <pthread.h>
#elif defined(_TTHREAD_WIN32_)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#define __UNDEF_LEAN_AND_MEAN
#endif
#include <windows.h>
#ifdef __UNDEF_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#undef __UNDEF_LEAN_AND_MEAN
#endif
#endif
/* Compiler-specific information */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define TTHREAD_NORETURN _Noreturn
#elif defined(__GNUC__)
#define TTHREAD_NORETURN __attribute__((__noreturn__))
#else
#define TTHREAD_NORETURN
#endif
/* If TIME_UTC is missing, provide it and provide a wrapper for
timespec_get. */
#ifndef TIME_UTC
#define TIME_UTC 1
#define _TTHREAD_EMULATE_TIMESPEC_GET_
#if defined(_TTHREAD_WIN32_)
struct _tthread_timespec {
time_t tv_sec;
long tv_nsec;
};
#define timespec _tthread_timespec
#endif
int _tthread_timespec_get(struct timespec *ts, int base);
#define timespec_get _tthread_timespec_get
#endif
/** TinyCThread version (major number). */
#define TINYCTHREAD_VERSION_MAJOR 1
/** TinyCThread version (minor number). */
#define TINYCTHREAD_VERSION_MINOR 2
/** TinyCThread version (full version). */
#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR)
/**
* @def _Thread_local
* Thread local storage keyword.
* A variable that is declared with the @c _Thread_local keyword makes the
* value of the variable local to each thread (known as thread-local storage,
* or TLS). Example usage:
* @code
* // This variable is local to each thread.
* _Thread_local int variable;
* @endcode
* @note The @c _Thread_local keyword is a macro that maps to the corresponding
* compiler directive (e.g. @c __declspec(thread)).
* @note This directive is currently not supported on Mac OS X (it will give
* a compiler error), since compile-time TLS is not supported in the Mac OS X
* executable format. Also, some older versions of MinGW (before GCC 4.x) do
* not support this directive, nor does the Tiny C Compiler.
* @hideinitializer
*/
#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local)
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
#define _Thread_local __thread
#else
#define _Thread_local __declspec(thread)
#endif
#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && (((__GNUC__ << 8) | __GNUC_MINOR__) < ((4 << 8) | 9))
#define _Thread_local __thread
#endif
/* Macros */
#if defined(_TTHREAD_WIN32_)
#define TSS_DTOR_ITERATIONS (4)
#else
#define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS
#endif
/* Function return values */
#define thrd_error 0 /**< The requested operation failed */
#define thrd_success 1 /**< The requested operation succeeded */
#define thrd_timedout 2 /**< The time specified in the call was reached without acquiring the requested resource */
#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */
#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */
/* Mutex types */
#define mtx_plain 0
#define mtx_timed 1
#define mtx_recursive 2
/* Mutex */
#if defined(_TTHREAD_WIN32_)
typedef struct {
union {
CRITICAL_SECTION cs; /* Critical section handle (used for non-timed mutexes) */
HANDLE mut; /* Mutex handle (used for timed mutex) */
} mHandle; /* Mutex handle */
int mAlreadyLocked; /* TRUE if the mutex is already locked */
int mRecursive; /* TRUE if the mutex is recursive */
int mTimed; /* TRUE if the mutex is timed */
} mtx_t;
#else
typedef pthread_mutex_t mtx_t;
#endif
/** Create a mutex object.
* @param mtx A mutex object.
* @param type Bit-mask that must have one of the following six values:
* @li @c mtx_plain for a simple non-recursive mutex
* @li @c mtx_timed for a non-recursive mutex that supports timeout
* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive)
* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive)
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int mtx_init(mtx_t *mtx, int type);
/** Release any resources used by the given mutex.
* @param mtx A mutex object.
*/
void mtx_destroy(mtx_t *mtx);
/** Lock the given mutex.
* Blocks until the given mutex can be locked. If the mutex is non-recursive, and
* the calling thread already has a lock on the mutex, this call will block
* forever.
* @param mtx A mutex object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int mtx_lock(mtx_t *mtx);
/** Lock the given mutex, or block until a specific point in time.
* Blocks until either the given mutex can be locked, or the specified TIME_UTC
* based time.
* @param mtx A mutex object.
* @param ts A UTC based calendar time
* @return @ref The mtx_timedlock function returns thrd_success on success, or
* thrd_timedout if the time specified was reached without acquiring the
* requested resource, or thrd_error if the request could not be honored.
*/
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts);
/** Try to lock the given mutex.
* The specified mutex shall support either test and return or timeout. If the
* mutex is already locked, the function returns without blocking.
* @param mtx A mutex object.
* @return @ref thrd_success on success, or @ref thrd_busy if the resource
* requested is already in use, or @ref thrd_error if the request could not be
* honored.
*/
int mtx_trylock(mtx_t *mtx);
/** Unlock the given mutex.
* @param mtx A mutex object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int mtx_unlock(mtx_t *mtx);
/* Condition variable */
#if defined(_TTHREAD_WIN32_)
typedef struct {
HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */
unsigned int mWaitersCount; /* Count of the number of waiters. */
CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */
} cnd_t;
#else
typedef pthread_cond_t cnd_t;
#endif
/** Create a condition variable object.
* @param cond A condition variable object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int cnd_init(cnd_t *cond);
/** Release any resources used by the given condition variable.
* @param cond A condition variable object.
*/
void cnd_destroy(cnd_t *cond);
/** Signal a condition variable.
* Unblocks one of the threads that are blocked on the given condition variable
* at the time of the call. If no threads are blocked on the condition variable
* at the time of the call, the function does nothing and return success.
* @param cond A condition variable object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int cnd_signal(cnd_t *cond);
/** Broadcast a condition variable.
* Unblocks all of the threads that are blocked on the given condition variable
* at the time of the call. If no threads are blocked on the condition variable
* at the time of the call, the function does nothing and return success.
* @param cond A condition variable object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int cnd_broadcast(cnd_t *cond);
/** Wait for a condition variable to become signaled.
* The function atomically unlocks the given mutex and endeavors to block until
* the given condition variable is signaled by a call to cnd_signal or to
* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex
* before it returns.
* @param cond A condition variable object.
* @param mtx A mutex object.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int cnd_wait(cnd_t *cond, mtx_t *mtx);
/** Wait for a condition variable to become signaled.
* The function atomically unlocks the given mutex and endeavors to block until
* the given condition variable is signaled by a call to cnd_signal or to
* cnd_broadcast, or until after the specified time. When the calling thread
* becomes unblocked it locks the mutex before it returns.
* @param cond A condition variable object.
* @param mtx A mutex object.
* @param xt A point in time at which the request will time out (absolute time).
* @return @ref thrd_success upon success, or @ref thrd_timeout if the time
* specified in the call was reached without acquiring the requested resource, or
* @ref thrd_error if the request could not be honored.
*/
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts);
/* Thread */
#if defined(_TTHREAD_WIN32_)
typedef HANDLE thrd_t;
#else
typedef pthread_t thrd_t;
#endif
/** Thread start function.
* Any thread that is started with the @ref thrd_create() function must be
* started through a function of this type.
* @param arg The thread argument (the @c arg argument of the corresponding
* @ref thrd_create() call).
* @return The thread return value, which can be obtained by another thread
* by using the @ref thrd_join() function.
*/
typedef int (*thrd_start_t)(void *arg);
/** Create a new thread.
* @param thr Identifier of the newly created thread.
* @param func A function pointer to the function that will be executed in
* the new thread.
* @param arg An argument to the thread function.
* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could
* be allocated for the thread requested, or @ref thrd_error if the request
* could not be honored.
* @note A threads identifier may be reused for a different thread once the
* original thread has exited and either been detached or joined to another
* thread.
*/
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
/** Identify the calling thread.
* @return The identifier of the calling thread.
*/
thrd_t thrd_current(void);
/** Dispose of any resources allocated to the thread when that thread exits.
* @return thrd_success, or thrd_error on error
*/
int thrd_detach(thrd_t thr);
/** Compare two thread identifiers.
* The function determines if two thread identifiers refer to the same thread.
* @return Zero if the two thread identifiers refer to different threads.
* Otherwise a nonzero value is returned.
*/
int thrd_equal(thrd_t thr0, thrd_t thr1);
/** Terminate execution of the calling thread.
* @param res Result code of the calling thread.
*/
TTHREAD_NORETURN void thrd_exit(int res);
/** Wait for a thread to terminate.
* The function joins the given thread with the current thread by blocking
* until the other thread has terminated.
* @param thr The thread to join with.
* @param res If this pointer is not NULL, the function will store the result
* code of the given thread in the integer pointed to by @c res.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int thrd_join(thrd_t thr, int *res);
/** Put the calling thread to sleep.
* Suspend execution of the calling thread.
* @param duration Interval to sleep for
* @param remaining If non-NULL, this parameter will hold the remaining
* time until time_point upon return. This will
* typically be zero, but if the thread was woken up
* by a signal that is not ignored before duration was
* reached @c remaining will hold a positive time.
* @return 0 (zero) on successful sleep, -1 if an interrupt occurred,
* or a negative value if the operation fails.
*/
int thrd_sleep(const struct timespec *duration, struct timespec *remaining);
/** Yield execution to another thread.
* Permit other threads to run, even if the current thread would ordinarily
* continue to run.
*/
void thrd_yield(void);
/* Thread local storage */
#if defined(_TTHREAD_WIN32_)
typedef DWORD tss_t;
#else
typedef pthread_key_t tss_t;
#endif
/** Destructor function for a thread-specific storage.
* @param val The value of the destructed thread-specific storage.
*/
typedef void (*tss_dtor_t)(void *val);
/** Create a thread-specific storage.
* @param key The unique key identifier that will be set if the function is
* successful.
* @param dtor Destructor function. This can be NULL.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
* @note On Windows, the @c dtor will definitely be called when
* appropriate for threads created with @ref thrd_create. It will be
* called for other threads in most cases, the possible exception being
* for DLLs loaded with LoadLibraryEx. In order to be certain, you
* should use @ref thrd_create whenever possible.
*/
int tss_create(tss_t *key, tss_dtor_t dtor);
/** Delete a thread-specific storage.
* The function releases any resources used by the given thread-specific
* storage.
* @param key The key that shall be deleted.
*/
void tss_delete(tss_t key);
/** Get the value for a thread-specific storage.
* @param key The thread-specific storage identifier.
* @return The value for the current thread held in the given thread-specific
* storage.
*/
void *tss_get(tss_t key);
/** Set the value for a thread-specific storage.
* @param key The thread-specific storage identifier.
* @param val The value of the thread-specific storage to set for the current
* thread.
* @return @ref thrd_success on success, or @ref thrd_error if the request could
* not be honored.
*/
int tss_set(tss_t key, void *val);
#if defined(_TTHREAD_WIN32_)
typedef struct {
LONG volatile status;
CRITICAL_SECTION lock;
} once_flag;
#define ONCE_FLAG_INIT {0,}
#else
#define once_flag pthread_once_t
#define ONCE_FLAG_INIT PTHREAD_ONCE_INIT
#endif
/** Invoke a callback exactly once
* @param flag Flag used to ensure the callback is invoked exactly
* once.
* @param func Callback to invoke.
*/
#if defined(_TTHREAD_WIN32_)
void call_once(once_flag *flag, void (*func)(void));
#else
#define call_once(flag,func) pthread_once(flag,func)
#endif
#ifdef __cplusplus
}
#endif
#endif /* _TINYTHREAD_H_ */

View File

@ -26,6 +26,8 @@
#include "vst3_c_api.h"
#pragma GCC diagnostic pop
#define TEMPLATE_SUPPORTS_MESSAGING 1
#include "data.h"
#include "plugin_api.h"
#include "plugin.h"
@ -64,10 +66,15 @@
# endif
#endif
#ifdef DATA_MESSAGING
# include <tinycthread.h>
#endif
// COM in C doc:
// https://github.com/rubberduck-vba/Rubberduck/wiki/COM-in-plain-C
// https://devblogs.microsoft.com/oldnewthing/20040205-00/?p=40733
//#define TIBIA_TRACE
#ifdef TIBIA_TRACE
# define TRACE(...) printf(__VA_ARGS__); fflush(stdout);
#else
@ -308,6 +315,21 @@ typedef struct pluginInstance {
#endif
void * mem;
struct Steinberg_IBStream * state;
#ifdef DATA_MESSAGING
Steinberg_Vst_IConnectionPointVtbl *vtblIConnectionPoint;
Steinberg_Vst_IConnectionPoint *connectedPoint;
uint8_t message_data_in[DATA_MESSAGING_MAX];
size_t message_data_in_size;
uint8_t message_data_out[DATA_MESSAGING_MAX];
size_t message_data_out_size;
char message_data_out_tosend; // This is to tell message_thread to send data
thrd_t message_thread;
int message_cmd;
mtx_t message_mutex;
cnd_t message_cond;
#endif
} pluginInstance;
static Steinberg_Vst_IComponentVtbl pluginVtblIComponent;
@ -364,10 +386,15 @@ static Steinberg_tresult pluginQueryInterface(pluginInstance *p, const Steinberg
offset = offsetof(pluginInstance, vtblIAudioProcessor);
else if (memcmp(iid, Steinberg_Vst_IProcessContextRequirements_iid, sizeof(Steinberg_TUID)) == 0)
offset = offsetof(pluginInstance, vtblIProcessContextRequirements);
#ifdef DATA_MESSAGING
else if (memcmp(iid, Steinberg_Vst_IConnectionPoint_iid, sizeof(Steinberg_TUID)) == 0)
offset = offsetof(pluginInstance, vtblIConnectionPoint);
#endif
else {
TRACE(" not supported\n");
for (int i = 0; i < 16; i++)
for (int i = 0; i < 16; i++) {
TRACE(" %x", iid[i]);
}
TRACE("\n");
*obj = NULL;
return Steinberg_kNoInterface;
@ -392,6 +419,97 @@ static Steinberg_uint32 pluginRelease(pluginInstance *p) {
return p->refs;
}
#ifdef DATA_MESSAGING
static Steinberg_tresult pluginIConnectionPointQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) {
TRACE("plugin IConnectionPoint queryInterface %p\n", thisInterface);
return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)), iid, obj);
}
static Steinberg_uint32 pluginIConnectionPointAddRef(void* thisInterface) {
TRACE("plugin IConnectionPoint addRef %p\n", thisInterface);
return pluginAddRef((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)));
}
static Steinberg_uint32 pluginIConnectionPointRelease(void* thisInterface) {
TRACE("plugin IConnectionPoint release %p\n", thisInterface);
return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)));
}
static Steinberg_tresult pluginIConnectionPointConnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) {
pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint));
if (!other) return Steinberg_kInvalidArgument;
if (p->connectedPoint) return Steinberg_kResultFalse;
mtx_lock(&p->message_mutex);
p->connectedPoint = other;
mtx_unlock(&p->message_mutex);
return Steinberg_kResultOk;
}
static Steinberg_tresult pluginIConnectionPointDisconnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) {
pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint));
if (p->connectedPoint && other == p->connectedPoint) {
mtx_lock(&p->message_mutex);
p->connectedPoint = NULL;
mtx_unlock(&p->message_mutex);
return Steinberg_kResultOk;
}
return Steinberg_kResultFalse;
}
static Steinberg_tresult pluginIConnectionPointNotify(void* thisInterface, struct Steinberg_Vst_IMessage* message) {
pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint));
if (!message)
return Steinberg_kResultFalse;
Steinberg_Vst_IAttributeList *alist = message->lpVtbl->getAttributes(message);
if (!alist)
return Steinberg_kResultFalse;
const void *data = NULL;
unsigned int size = 0;
alist->lpVtbl->getBinary(alist, "message_data", &data, &size);
if (!data || size == 0)
return Steinberg_kResultFalse;
mtx_lock(&p->message_mutex);
memcpy(&p->message_data_in, data, size);
p->message_data_in_size = size;
mtx_unlock(&p->message_mutex);
return Steinberg_kResultOk;
}
static Steinberg_Vst_IConnectionPointVtbl pluginVtblIConnectionPoint = {
/* FUnknown */
/* .queryInterface = */ pluginIConnectionPointQueryInterface,
/* .addRef = */ pluginIConnectionPointAddRef,
/* .release = */ pluginIConnectionPointRelease,
/* IConnectionPoint */
/* .connect = */ pluginIConnectionPointConnect,
/* .disconnect = */ pluginIConnectionPointDisconnect,
/* .notify = */ pluginIConnectionPointNotify
};
// Assuming this gets called by audio thread only
static char send_to_ui (void *handle, const void *data, size_t bytes) {
pluginInstance *p = (pluginInstance *)handle;
if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) {
return 1;
}
memcpy(p->message_data_out, data, bytes);
p->message_data_out_size = bytes;
p->message_data_out_tosend = 1;
return 0;
}
# endif
static Steinberg_tresult pluginIComponentQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) {
TRACE("plugin IComponent queryInterface %p\n", thisInterface);
return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)), iid, obj);
@ -407,6 +525,68 @@ static Steinberg_uint32 pluginIComponentRelease(void *thisInterface) {
return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)));
}
#ifdef DATA_MESSAGING
int message_thread_f(void *arg) {
pluginInstance *p = (pluginInstance *) arg;
while (1) {
mtx_lock(&p->message_mutex);
if (p->message_cmd == 0) { // Wait
cnd_wait(&p->message_cond, &p->message_mutex);
}
if (p->message_cmd == 1) { // do work
if (!p->connectedPoint || ! p->context) {
mtx_unlock(&p->message_mutex);
thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec
continue;
}
Steinberg_Vst_IConnectionPointVtbl *ov = (Steinberg_Vst_IConnectionPointVtbl*) p->connectedPoint->lpVtbl;
Steinberg_Vst_IHostApplication *app = (Steinberg_Vst_IHostApplication*) p->context;
Steinberg_Vst_IMessage *msg = NULL;
app->lpVtbl->createInstance(app, (char*) Steinberg_Vst_IMessage_iid, (char*) Steinberg_Vst_IMessage_iid, (void**)&msg);
if (!msg) {
mtx_unlock(&p->message_mutex);
thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec
continue;
}
Steinberg_Vst_IAttributeList *alist = msg->lpVtbl->getAttributes(msg);
if (!alist) {
msg->lpVtbl->release(msg);
mtx_unlock(&p->message_mutex);
thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec
continue;
}
alist->lpVtbl->setBinary(alist, "message_data", p->message_data_out, p->message_data_out_size);
Steinberg_tresult r = ov->notify(p->connectedPoint, msg);
if (r == Steinberg_kResultFalse) {
msg->lpVtbl->release(msg);
mtx_unlock(&p->message_mutex);
thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec
continue;
}
msg->lpVtbl->release(msg);
p->message_cmd = 0;
} else if (p->message_cmd == 2) { // exit
mtx_unlock(&p->message_mutex);
break;
}
mtx_unlock(&p->message_mutex);
}
return 0;
}
#endif
static Steinberg_tresult pluginInitialize(void *thisInterface, struct Steinberg_FUnknown *context) {
TRACE("plugin initialize\n");
@ -422,7 +602,10 @@ static Steinberg_tresult pluginInitialize(void *thisInterface, struct Steinberg_
/* .handle = */ (void *)p,
/* .format = */ "vst3",
/* .get_bindir = */ getBindirCb,
/* .get_datadir = */ getDatadirCb
/* .get_datadir = */ getDatadirCb,
#ifdef DATA_MESSAGING
/* .send_to_ui = */ send_to_ui
#endif
};
plugin_init(&p->p, &cbs);
#if DATA_PRODUCT_PARAMETERS_IN_N > 0
@ -460,6 +643,15 @@ static Steinberg_tresult pluginInitialize(void *thisInterface, struct Steinberg_
# endif
#endif
p->mem = NULL;
#ifdef DATA_MESSAGING
mtx_init(&p->message_mutex, mtx_plain);
cnd_init(&p->message_cond);
if (thrd_create(&p->message_thread, message_thread_f, p) != thrd_success) {
return Steinberg_kResultFalse;
}
#endif
return Steinberg_kResultOk;
}
@ -470,6 +662,17 @@ static Steinberg_tresult pluginTerminate(void *thisInterface) {
plugin_fini(&p->p);
if (p->mem)
free(p->mem);
#ifdef DATA_MESSAGING
mtx_lock(&p->message_mutex);
p->message_cmd = 2;
cnd_signal(&p->message_cond);
mtx_unlock(&p->message_mutex);
thrd_join(p->message_thread, NULL);
mtx_destroy(&p->message_mutex);
cnd_destroy(&p->message_cond);
#endif
return Steinberg_kResultOk;
}
@ -1086,6 +1289,24 @@ static Steinberg_tresult pluginProcess(void* thisInterface, struct Steinberg_Vst
_MM_SET_DENORMALS_ZERO_MODE(denormals_zero_mode);
#endif
#ifdef DATA_MESSAGING
if (p->message_data_in_size > 0 || p->message_data_out_tosend) {
if (mtx_trylock(&p->message_mutex) == thrd_success) {
if (p->message_data_in_size > 0) {
plugin_receive_from_ui(&(p->p), p->message_data_in, p->message_data_in_size);
p->message_data_in_size = 0;
}
if (p->message_data_out_tosend) { // message_data_out_tosend is manipulated by audio thread only
p->message_cmd = 1;
cnd_signal(&p->message_cond);
p->message_data_out_tosend = 0;
}
mtx_unlock(&p->message_mutex);
}
}
#endif
return Steinberg_kResultOk;
}
@ -1151,8 +1372,9 @@ typedef struct plugView plugView;
typedef struct controller {
Steinberg_Vst_IEditControllerVtbl * vtblIEditController; // must stay first
Steinberg_Vst_IMidiMappingVtbl * vtblIMidiMapping;
#ifdef DATA_UI
//Steinberg_Vst_IConnectionPointVtbl * vtblIConnectionPoint;
#ifdef DATA_MESSAGING
Steinberg_Vst_IConnectionPointVtbl *vtblIConnectionPoint;
Steinberg_Vst_IConnectionPoint *connectedPoint;
#endif
Steinberg_uint32 refs;
Steinberg_FUnknown * context;
@ -1322,8 +1544,9 @@ static Steinberg_tresult plugViewQueryInterface(void *thisInterface, const Stein
offset = offsetof(plugView, vtblIPlugView);
else {
TRACE(" not supported\n");
for (int i = 0; i < 16; i++)
for (int i = 0; i < 16; i++) {
TRACE(" %x", iid[i]);
}
TRACE("\n");
*obj = NULL;
return Steinberg_kNoInterface;
@ -1458,6 +1681,45 @@ static void plugViewSetParameterEndCb(void *handle, size_t index, float value) {
# endif
# ifdef DATA_MESSAGING
static char send_to_dsp (void *handle, const void *data, size_t bytes) {
plugView *v = (plugView *)handle;
if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) {
return 1;
}
if (!v->ctrl->connectedPoint || !v->ctrl->context) {
return 1;
}
Steinberg_Vst_IConnectionPointVtbl *ov = (Steinberg_Vst_IConnectionPointVtbl*) v->ctrl->connectedPoint->lpVtbl;
Steinberg_Vst_IHostApplication *app = (Steinberg_Vst_IHostApplication*) v->ctrl->context;
Steinberg_Vst_IMessage *msg = NULL;
app->lpVtbl->createInstance(app, (char*) Steinberg_Vst_IMessage_iid, (char*) Steinberg_Vst_IMessage_iid, (void**)&(msg));
if (!msg)
return 1;
Steinberg_Vst_IAttributeList *alist = msg->lpVtbl->getAttributes(msg);
if (!alist) {
msg->lpVtbl->release(msg);
return 1;
}
alist->lpVtbl->setBinary(alist, "message_data", data, bytes);
Steinberg_tresult r = ov->notify(v->ctrl->connectedPoint, msg);
if (r == Steinberg_kResultFalse) {
msg->lpVtbl->release(msg);
return 1;
}
msg->lpVtbl->release(msg);
return 0;
}
# endif
# ifdef __APPLE__
static void plugViewTimerCb(CFRunLoopTimerRef timer, void *info) {
(void)timer;
@ -1493,7 +1755,10 @@ static Steinberg_tresult plugViewAttached(void* thisInterface, void* parent, Ste
# if DATA_PRODUCT_PARAMETERS_N > 0
/* .set_parameter_begin = */ plugViewSetParameterBeginCb,
/* .set_parameter = */ plugViewSetParameterCb,
/* .set_parameter_end = */ plugViewSetParameterEndCb
/* .set_parameter_end = */ plugViewSetParameterEndCb,
# endif
# ifdef DATA_MESSAGING
/* .send_to_dsp = */ send_to_dsp
# endif
};
v->ui = plugin_ui_create(1, parent, &cbs);
@ -1746,9 +2011,6 @@ static Steinberg_IPlugViewVtbl plugViewVtblIPlugView = {
#endif
static Steinberg_Vst_IMidiMappingVtbl controllerVtblIMidiMapping;
#ifdef DATA_UI
//static Steinberg_Vst_IConnectionPointVtbl controllerVtblIConnectionPoint;
#endif
static Steinberg_tresult controllerQueryInterface(controller *c, const Steinberg_TUID iid, void ** obj) {
// Same as above (pluginQueryInterface)
@ -1759,14 +2021,15 @@ static Steinberg_tresult controllerQueryInterface(controller *c, const Steinberg
offset = offsetof(controller, vtblIEditController);
else if (memcmp(iid, Steinberg_Vst_IMidiMapping_iid, sizeof(Steinberg_TUID)) == 0)
offset = offsetof(controller, vtblIMidiMapping);
#ifdef DATA_UI
/*else if (memcmp(iid, Steinberg_Vst_IConnectionPoint_iid, sizeof(Steinberg_TUID)) == 0)
offset = offsetof(controller, vtblIConnectionPoint);*/
#ifdef DATA_MESSAGING
else if (memcmp(iid, Steinberg_Vst_IConnectionPoint_iid, sizeof(Steinberg_TUID)) == 0)
offset = offsetof(controller, vtblIConnectionPoint);
#endif
else {
TRACE(" not supported\n");
for (int i = 0; i < 16; i++)
for (int i = 0; i < 16; i++) {
TRACE(" %x", iid[i]);
}
TRACE("\n");
*obj = NULL;
return Steinberg_kNoInterface;
@ -2171,13 +2434,20 @@ static struct Steinberg_IPlugView* controllerCreateView(void* thisInterface, Ste
if (strcmp(name, "editor"))
return NULL;
controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController));
size_t i;
for (i = 0; i < c->viewsCount; i++) {
if (c->views[i] != NULL) {
TRACE("controllerCreateView: trying to create another view - not supported \n");
return NULL;
}
}
plugView *view = malloc(sizeof(plugView));
if (view == NULL)
return NULL;
controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController));
size_t i;
for (i = 0; i < c->viewsCount; c++)
for (i = 0; i < c->viewsCount; i++)
if (c->views[i] == NULL)
break;
if (i == c->viewsCount) {
@ -2290,8 +2560,8 @@ static Steinberg_Vst_IMidiMappingVtbl controllerVtblIMidiMapping = {
/* .getMidiControllerAssignment = */ controllerGetMidiControllerAssignment
};
#ifdef DATA_UI
# if 0
#ifdef DATA_MESSAGING
static Steinberg_tresult controllerIConnectionPointQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) {
TRACE("controller IConnectionPoint queryInterface %p\n", thisInterface);
return controllerQueryInterface((controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint)), iid, obj);
@ -2308,21 +2578,48 @@ static Steinberg_uint32 controllerIConnectionPointRelease(void* thisInterface) {
}
static Steinberg_tresult controllerIConnectionPointConnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) {
(void)thisInterface;
return other ? Steinberg_kResultOk : Steinberg_kInvalidArgument;
}
static Steinberg_tresult controllerIConnectionPointDisconnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) {
(void)thisInterface;
(void)other;
controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint));
if (!other) return Steinberg_kInvalidArgument;
if (c->connectedPoint) return Steinberg_kResultFalse;
c->connectedPoint = other;
return Steinberg_kResultOk;
}
static Steinberg_tresult controllerIConnectionPointDisconnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) {
controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint));
if (c->connectedPoint && other == c->connectedPoint) {
c->connectedPoint = NULL;
return Steinberg_kResultOk;
}
return Steinberg_kResultFalse;
}
// This can get called after plugView creation but before attachment, so that ui is not ready
static Steinberg_tresult controllerIConnectionPointNotify(void* thisInterface, struct Steinberg_Vst_IMessage* message) {
(void)thisInterface;
(void)message;
controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint));
if (!message)
return Steinberg_kResultFalse;
Steinberg_Vst_IAttributeList *alist = message->lpVtbl->getAttributes(message);
if (!alist)
return Steinberg_kResultFalse;
const void *data = NULL;
unsigned int size = 0;
alist->lpVtbl->getBinary(alist, "message_data", &data, &size);
if (!data || size == 0)
return Steinberg_kResultFalse;
for (size_t i = 0; i < c->viewsCount; i++) {
plugView *v = c->views[i];
if (!v || !v->ui)
continue;
plugin_ui_receive_from_dsp(v->ui, data, size);
break; // Assuming there is only 1 view
}
return Steinberg_kResultOk;
}
@ -2338,7 +2635,7 @@ static Steinberg_Vst_IConnectionPointVtbl controllerVtblIConnectionPoint = {
/* .disconnect = */ controllerIConnectionPointDisconnect,
/* .notify = */ controllerIConnectionPointNotify
};
# endif
#endif
static Steinberg_tresult factoryQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) {
@ -2431,6 +2728,12 @@ static Steinberg_tresult factoryCreateInstance(void *thisInterface, Steinberg_FI
p->vtblIProcessContextRequirements = &pluginVtblIProcessContextRequirements;
p->refs = 1;
p->context = NULL;
#ifdef DATA_MESSAGING
p->vtblIConnectionPoint = &pluginVtblIConnectionPoint;
p->connectedPoint = NULL;
p->message_data_out_tosend = 0;
p->message_data_in_size = 0;
#endif
*obj = p;
TRACE(" instance: %p\n", (void *)p);
} else if (memcmp(cid, dataControllerCID, sizeof(Steinberg_TUID)) == 0) {
@ -2444,8 +2747,9 @@ static Steinberg_tresult factoryCreateInstance(void *thisInterface, Steinberg_FI
return Steinberg_kOutOfMemory;
c->vtblIEditController = &controllerVtblIEditController;
c->vtblIMidiMapping = &controllerVtblIMidiMapping;
#ifdef DATA_UI
//c->vtblIConnectionPoint = &controllerVtblIConnectionPoint;
#ifdef DATA_MESSAGING
c->vtblIConnectionPoint = &controllerVtblIConnectionPoint;
c->connectedPoint = NULL;
#endif
c->refs = 1;
c->context = NULL;

View File

@ -73,4 +73,6 @@ module.exports = function (data, api, outputCommon, outputData) {
api.generateFileFromTemplateFile(`data${sep}Info.plist`, `data${sep}Info.plist`, data);
api.copyFile(`src${sep}vst3.c`, `src${sep}vst3.c`);
api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data);
api.copyFile(`src${sep}tinycthread.h`, `src${sep}tinycthread.h`);
api.copyFile(`src${sep}tinycthread.c`, `src${sep}tinycthread.c`);
};

View File

@ -34,11 +34,30 @@ typedef struct plugin {
float z1;
float cutoff_k;
float yz1;
int communication_state;
plugin_callbacks cbs;
} plugin;
#if TEMPLATE_SUPPORTS_MESSAGING
#include <stdio.h>
static void plugin_receive_from_ui (plugin *instance, const void *data, size_t bytes) {
printf("plugin_receive_from_ui %lld bytes at %p: ", bytes, data);
for (size_t i = 0; i < bytes; i++) {
printf("%c", ((uint8_t*) data)[i]);
}
instance->communication_state = 1;
printf("\nplugin_receive_from_ui END - going to reply at next proc \n");
}
#define RANDOM_DATA_SIZE 7
const uint8_t random_data[RANDOM_DATA_SIZE] = { 'w', 'o', 'r', 'l', 'd', '!', 0 };
#endif
static void plugin_init(plugin *instance, const plugin_callbacks *cbs) {
(void)instance;
(void)cbs;
instance->cbs = *cbs;
instance->communication_state = 0;
}
static void plugin_fini(plugin *instance) {
@ -111,6 +130,12 @@ static void plugin_process(plugin *instance, const float **inputs, float **outpu
outputs[0][i] = instance->bypass ? inputs[0][i] : gain * y;
instance->yz1 = outputs[0][i];
}
#ifdef TEMPLATE_SUPPORTS_MESSAGING
if (instance->communication_state == 1) {
instance->cbs.send_to_ui(instance->cbs.handle, random_data, RANDOM_DATA_SIZE);
instance->communication_state = 2;
}
#endif
}
static void plugin_midi_msg_in(plugin *instance, size_t index, const uint8_t * data) {

View File

@ -38,6 +38,19 @@ typedef struct {
plugin_ui_callbacks cbs;
} plugin_ui;
#ifdef TEMPLATE_SUPPORTS_MESSAGING
static void plugin_ui_receive_from_dsp (plugin_ui *instance, const void *data, size_t bytes) {
(void) instance;
printf("plugin_ui_receive_from_dsp %lld bytes at %p: ", bytes, data);
for (size_t i = 0; i < bytes; i++) {
printf("%c", ((uint8_t*) data)[i]);
}
printf("\nplugin_ui_receive_from_dsp END \n");
}
#define RANDOM_UI_DATA_SIZE 6
const uint8_t random_ui_data[RANDOM_UI_DATA_SIZE] = { 'h', 'e', 'l', 'l', 'o', 0 };
#endif
#define WIDTH 600.0
#define HEIGHT 400.0
@ -112,6 +125,9 @@ static void on_mouse_release (window *win, int32_t x, int32_t y, uint32_t state)
pui->bypass = !pui->bypass;
pui->cbs.set_parameter(pui->cbs.handle, 3, pui->bypass ? 1.f : 0.f);
draw_button(pui, 3, pui->bypass);
#if TEMPLATE_SUPPORTS_MESSAGING
pui->cbs.send_to_dsp(pui->cbs.handle, (const void*) random_ui_data, RANDOM_UI_DATA_SIZE);
#endif
}
if (pui->param_down != -1) {

View File

@ -138,6 +138,9 @@
"userResizable": true,
"selfResizable": false
},
"messaging": {
"maxSize": 10240
},
"state": {
"dspCustom": true
}