aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeCommon.h
blob: 405bd9bec1a3127544cccc2be90b5a92f29bde8c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
/*
 * BridgeCommon.h
 * --------------
 * Purpose: Declarations of stuff that's common between the VST bridge and bridge wrapper.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#pragma once

#include "openmpt/all/BuildSettings.hpp"

#include <vector>

#if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
#include <intrin.h>
#endif


OPENMPT_NAMESPACE_BEGIN

// Insert some object at the end of a char vector.
template <typename T>
static void PushToVector(std::vector<char> &data, const T &obj, size_t writeSize = sizeof(T))
{
	static_assert(!std::is_pointer<T>::value, "Won't push pointers to data vectors.");
	const char *objC = reinterpret_cast<const char *>(&obj);
	data.insert(data.end(), objC, objC + writeSize);
}

static void PushZStringToVector(std::vector<char> &data, const char *str)
{
	if(str != nullptr)
		data.insert(data.end(), str, str + strlen(str));
	PushToVector(data, char(0));
}

OPENMPT_NAMESPACE_END

#include "AEffectWrapper.h"
#include "BridgeOpCodes.h"

OPENMPT_NAMESPACE_BEGIN


// Internal data structures


// Event to notify other threads
class Event
{
private:
	HANDLE handle = nullptr;

public:
	Event() = default;
	~Event() { Close(); }

	// Create a new event
	bool Create(bool manual = false, const wchar_t *name = nullptr)
	{
		Close();
		handle = CreateEventW(nullptr, manual ? TRUE : FALSE, FALSE, name);
		return handle != nullptr;
	}

	// Duplicate a local event
	bool DuplicateFrom(HANDLE source)
	{
		Close();
		return DuplicateHandle(GetCurrentProcess(), source, GetCurrentProcess(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS) != FALSE;
	}

	void Close()
	{
		CloseHandle(handle);
	}

	void Trigger()
	{
		SetEvent(handle);
	}

	void Reset()
	{
		ResetEvent(handle);
	}

	void operator=(const HANDLE other) { handle = other; }
	operator const HANDLE() const { return handle; }
};


// Two-way event
class Signal
{
public:
	Event send, confirm;

	// Create new (local) signal
	bool Create()
	{
		return send.Create() && confirm.Create();
	}

	// Create signal from name (for inter-process communication)
	bool Create(const wchar_t *name, const wchar_t *addendum)
	{
		wchar_t fullName[64 + 1];
		wcscpy(fullName, name);
		wcscat(fullName, addendum);
		fullName[std::size(fullName) - 1] = L'\0';
		size_t nameLen = wcslen(fullName);
		wcscpy(fullName + nameLen, L"-s");

		bool success = send.Create(false, fullName);
		wcscpy(fullName + nameLen, L"-a");
		return success && confirm.Create(false, fullName);
	}

	// Create signal from other signal
	bool DuplicateFrom(const Signal &other)
	{
		return send.DuplicateFrom(other.send)
		       && confirm.DuplicateFrom(other.confirm);
	}

	void Send()
	{
		send.Trigger();
	}

	void Confirm()
	{
		confirm.Trigger();
	}
};


// Memory that can be shared between processes
class MappedMemory
{
protected:
	struct Header
	{
		uint32 size;
	};

	HANDLE mapFile = nullptr;
	Header *view = nullptr;

public:
	MappedMemory() = default;
	~MappedMemory() { Close(); }

	// Create a shared memory object.
	bool Create(const wchar_t *name, uint32 size)
	{
		Close();

		mapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size + sizeof(Header), name);
		if(!mapFile)
		{
			return false;
		}
		view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
		if(!view)
		{
			return false;
		}
		view->size = size;
		return Good();
	}

	// Open an existing shared memory object.
	bool Open(const wchar_t *name)
	{
		Close();

		mapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, name);
		if(!mapFile)
		{
			return false;
		}
		view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
		if(!view)
		{
			return false;
		}
		return Good();
	}

	// Close this shared memory object.
	void Close()
	{
		if(mapFile)
		{
			if(view)
			{
				UnmapViewOfFile(view);
				view = nullptr;
			}
			CloseHandle(mapFile);
			mapFile = nullptr;
		}
	}

	template <typename T = void>
	T *Data() const
	{
		if(view == nullptr)
			return nullptr;
		else
			return reinterpret_cast<T *>(view + 1);
	}

	size_t Size() const
	{
		if(!view)
			return 0;
		else
			return view->size;
	}

	bool Good() const { return view != nullptr; }

	// Make a copy and detach it from the other object
	void CopyFrom(MappedMemory &other)
	{
		Close();
		mapFile = other.mapFile;
		view = other.view;
		other.mapFile = nullptr;
		other.view = nullptr;
	}
};


// Bridge communication data

#pragma pack(push, 8)

// Simple atomic value that has a guaranteed size and layout for the shared memory
template <typename T>
struct BridgeAtomic
{
public:
	BridgeAtomic() = default;
	BridgeAtomic<T> &operator=(const T value)
	{
		static_assert(sizeof(m_value) >= sizeof(T));
		MPT_ASSERT((intptr_t(&m_value) & 3) == 0);
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
		InterlockedExchange(&m_value, static_cast<LONG>(value));
#else
		_InterlockedExchange(&m_value, static_cast<LONG>(value));
#endif
		return *this;
	}
	operator T() const
	{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
		return static_cast<T>(InterlockedAdd(&m_value, 0));
#else
		return static_cast<T>(_InterlockedExchangeAdd(&m_value, 0));
#endif
	}

	T exchange(T desired)
	{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
		return static_cast<T>(InterlockedExchange(&m_value, static_cast<LONG>(desired)));
#else
		return static_cast<T>(_InterlockedExchange(&m_value, static_cast<LONG>(desired)));
#endif
	}

	T fetch_add(T arg)
	{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
		return static_cast<T>(InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
#else
		return static_cast<T>(_InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
#endif
	}

	bool compare_exchange_strong(T &expected, T desired)
	{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
		return InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
#else
		return _InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
#endif
	}

private:
	mutable LONG m_value;
};


// Host-to-bridge parameter automation message
struct AutomationQueue
{
	struct Parameter
	{
		uint32 index;
		float value;
	};

	BridgeAtomic<int32> pendingEvents;  // Number of pending automation events
	Parameter params[64];               // Automation events
};


// Host-to-bridge message to initiate a process call.
struct ProcessMsg
{
	enum ProcessType : int32
	{
		process = 0,
		processReplacing,
		processDoubleReplacing,
	};

	int32 processType;
	int32 numInputs;
	int32 numOutputs;
	int32 sampleFrames;
	// Input and output buffers follow
};


// General message header
struct MsgHeader
{
	enum BridgeMessageType : uint32
	{
		// Management messages, host to bridge
		newInstance,
		init,
		// Management messages, bridge to host
		errorMsg,
		exceptionMsg,

		// VST messages, common
		dispatch,
		// VST messages, host to bridge
		setParameter,
		getParameter,
		automate,
	};

	BridgeAtomic<bool> isUsed;
	uint32 size;  // Size of complete message, including this header
	uint32 type;  // See BridgeMessageType
};


// Host-to-bridge new instance message
struct NewInstanceMsg : public MsgHeader
{
	wchar_t memName[64];  // Shared memory object name;
};


// Host-to-bridge initialization message
struct InitMsg : public MsgHeader
{
	int32 result;
	int32 hostPtrSize;       // Size of VstIntPtr in host
	uint32 mixBufSize;       // Interal mix buffer size (for shared memory audio buffers)
	int32 pluginID;          // ID to use when sending messages to host
	uint32 fullMemDump;      // When crashing, create full memory dumps instead of stack dumps
	wchar_t str[_MAX_PATH];  // Plugin file to load. Out: Error message if result != 0.
};


// Host-to-bridge or bridge-to-host VST dispatch message
struct DispatchMsg : public MsgHeader
{
	int32 opcode;
	int32 index;
	int64 value;
	int64 ptr;  // Usually, this will be the size of whatever ptr points to. In that case, the data itself is stored after this struct.
	float opt;
	int32 result;
};


// Host-to-bridge VST setParameter / getParameter message
struct ParameterMsg : public MsgHeader
{
	int32 index;  // Parameter ID
	float value;  // Parameter value (in/out)
};


// Bridge-to-host error message
struct ErrorMsg : public MsgHeader
{
	wchar_t str[64];
};


// Universal bridge message format
union BridgeMessage
{
	MsgHeader header;
	NewInstanceMsg newInstance;
	InitMsg init;
	DispatchMsg dispatch;
	ParameterMsg parameter;
	ErrorMsg error;
	uint8 dummy[2048];  // Enough space for most default structs, e.g. 2x speaker negotiation struct

	void SetType(MsgHeader::BridgeMessageType msgType, uint32 size)
	{
		header.isUsed = true;
		header.size = size;
		header.type = msgType;
	}

	void NewInstance(const wchar_t *memName)
	{
		SetType(MsgHeader::newInstance, sizeof(NewInstanceMsg));

		wcsncpy(newInstance.memName, memName, std::size(newInstance.memName) - 1);
	}

	void Init(const wchar_t *pluginPath, uint32 mixBufSize, int32 pluginID, bool fullMemDump)
	{
		SetType(MsgHeader::init, sizeof(InitMsg));

		init.result = 0;
		init.hostPtrSize = sizeof(intptr_t);
		init.mixBufSize = mixBufSize;
		init.pluginID = pluginID;
		init.fullMemDump = fullMemDump;
		wcsncpy(init.str, pluginPath, std::size(init.str) - 1);
	}

	void Dispatch(int32 opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
	{
		SetType(MsgHeader::dispatch, sizeof(DispatchMsg) + extraDataSize);

		dispatch.result = 0;
		dispatch.opcode = opcode;
		dispatch.index = index;
		dispatch.value = value;
		dispatch.ptr = ptr;
		dispatch.opt = opt;
	}

	void Dispatch(Vst::VstOpcodeToHost opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
	{
		Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
	}

	void Dispatch(Vst::VstOpcodeToPlugin opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
	{
		Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
	}

	void SetParameter(int32 index, float value)
	{
		SetType(MsgHeader::setParameter, sizeof(ParameterMsg));

		parameter.index = index;
		parameter.value = value;
	}

	void GetParameter(int32 index)
	{
		SetType(MsgHeader::getParameter, sizeof(ParameterMsg));

		parameter.index = index;
		parameter.value = 0.0f;
	}

	void Automate()
	{
		// Dummy message
		SetType(MsgHeader::automate, sizeof(MsgHeader));
	}

	void Error(const wchar_t *text)
	{
		SetType(MsgHeader::errorMsg, sizeof(ErrorMsg));

		wcsncpy(error.str, text, std::size(error.str) - 1);
		error.str[std::size(error.str) - 1] = 0;
	}

	// Copy message to target and clear delivery status
	void CopyTo(BridgeMessage &target)
	{
		std::memcpy(&target, this, std::min(static_cast<size_t>(header.size), sizeof(BridgeMessage)));
		header.isUsed = false;
	}
};

// This is the maximum size of dispatch data that can be sent in a message. If you want to dispatch more data, use a secondary medium for sending it along.
inline constexpr size_t DISPATCH_DATA_SIZE = (sizeof(BridgeMessage) - sizeof(DispatchMsg));
static_assert(DISPATCH_DATA_SIZE >= 256, "There should be room for at least 256 bytes of dispatch data!");


// The array size should be more than enough for any realistic scenario with nested and simultaneous dispatch calls
inline constexpr int MSG_STACK_SIZE = 16;
using MsgStack = std::array<BridgeMessage, MSG_STACK_SIZE>;


// Ensuring that our HWND looks the same to both 32-bit and 64-bit processes
struct BridgeHWND
{
public:
	void operator=(HWND handle) { m_handle = static_cast<int32>(reinterpret_cast<intptr_t>(handle)); }
	operator HWND() const { return reinterpret_cast<HWND>(static_cast<intptr_t>(m_handle)); }
protected:
	BridgeAtomic<int32> m_handle;
};


// Layout of the shared memory chunk
struct SharedMemLayout
{
	union
	{
		Vst::AEffect effect;  // Native layout from host perspective
		AEffect32 effect32;
		AEffect64 effect64;
	};
	MsgStack ipcMessages;
	AutomationQueue automationQueue;
	Vst::VstTimeInfo timeInfo;
	BridgeHWND hostCommWindow;
	BridgeHWND bridgeCommWindow;
	int32 bridgePluginID;
	BridgeAtomic<int32> tailSize;
	BridgeAtomic<int32> audioThreadToHostMsgID;
	BridgeAtomic<int32> audioThreadToBridgeMsgID;
};
static_assert(sizeof(Vst::AEffect) <= sizeof(AEffect64), "Something's going very wrong here.");


// For caching parameter information
struct ParameterInfo
{
	Vst::VstParameterProperties props;
	char name[64];
	char label[64];
	char display[64];
};


#pragma pack(pop)

// Common variables that we will find in both the host and plugin side of the bridge (this is not shared memory)
class BridgeCommon
{
public:
	BridgeCommon()
	{
		m_instanceCount++;
	}

	~BridgeCommon()
	{
		m_instanceCount--;
	}

protected:
	enum WindowMessage : UINT
	{
		WM_BRIDGE_KEYFIRST = WM_USER + 4000,  // Must be consistent with VSTEditor.cpp!
		WM_BRIDGE_KEYLAST = WM_BRIDGE_KEYFIRST + WM_KEYLAST - WM_KEYFIRST,
		WM_BRIDGE_MESSAGE_TO_BRIDGE,
		WM_BRIDGE_MESSAGE_TO_HOST,
		WM_BRIDGE_DELETE_PLUGIN,

		WM_BRIDGE_SUCCESS = 1337,
	};

	static std::vector<BridgeCommon *> m_plugins;
	static HWND m_communicationWindow;
	static int m_instanceCount;
	static thread_local bool m_isAudioThread;

	// Signals for host <-> bridge communication
	Signal m_sigToHostAudio, m_sigToBridgeAudio;
	Signal m_sigProcessAudio;
	Event m_sigBridgeReady;

	Event m_otherProcess;  // Handle of "other" process (host handle in the bridge and vice versa)

	// Shared memory segments
	MappedMemory m_queueMem;     // AEffect, message, some fixed size VST structures
	MappedMemory m_processMem;   // Process message + sample buffer
	MappedMemory m_getChunkMem;  // effGetChunk temporary memory
	MappedMemory m_eventMem;     // VstEvents memory

	// Pointer into shared memory
	SharedMemLayout /*volatile*/ *m_sharedMem = nullptr;

	// Pointer size of the "other" side of the bridge, in bytes
	int32 m_otherPtrSize = 0;

	int32 m_thisPluginID = 0;
	int32 m_otherPluginID = 0;

	static void CreateCommunicationWindow(WNDPROC windowProc)
	{
		static constexpr TCHAR windowClassName[] = _T("OpenMPTPluginBridgeCommunication");
		static bool registered = false;
		if(!registered)
		{
			registered = true;
			WNDCLASSEX wndClass;
			wndClass.cbSize = sizeof(WNDCLASSEX);
			wndClass.style = CS_HREDRAW | CS_VREDRAW;
			wndClass.lpfnWndProc = windowProc;
			wndClass.cbClsExtra = 0;
			wndClass.cbWndExtra = 0;
			wndClass.hInstance = GetModuleHandle(nullptr);
			wndClass.hIcon = nullptr;
			wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
			wndClass.hbrBackground = nullptr;
			wndClass.lpszMenuName = nullptr;
			wndClass.lpszClassName = windowClassName;
			wndClass.hIconSm = nullptr;
			RegisterClassEx(&wndClass);
		}

		m_communicationWindow = CreateWindow(
		    windowClassName,
		    _T("OpenMPT Plugin Bridge Communication"),
		    WS_POPUP,
		    CW_USEDEFAULT,
		    CW_USEDEFAULT,
		    1,
		    1,
		    HWND_MESSAGE,
		    nullptr,
		    GetModuleHandle(nullptr),
		    nullptr);
	}

	bool CreateSignals(const wchar_t *mapName)
	{
		wchar_t readyName[64];
		wcscpy(readyName, mapName);
		wcscat(readyName, L"rdy");
		return m_sigToHostAudio.Create(mapName, L"sha")
		       && m_sigToBridgeAudio.Create(mapName, L"sba")
		       && m_sigProcessAudio.Create(mapName, L"prc")
		       && m_sigBridgeReady.Create(false, readyName);
	}

	// Copy a message to shared memory and return relative position.
	int CopyToSharedMemory(const BridgeMessage &msg, MsgStack &stack)
	{
		MPT_ASSERT(msg.header.isUsed);
		for(int i = 0; i < MSG_STACK_SIZE; i++)
		{
			BridgeMessage &targetMsg = stack[i];
			bool expected = false;
			if(targetMsg.header.isUsed.compare_exchange_strong(expected, true))
			{
				std::memcpy(&targetMsg, &msg, std::min(sizeof(BridgeMessage), size_t(msg.header.size)));
				return i;
			}
		}
		return -1;
	}
};


OPENMPT_NAMESPACE_END