1 /*
2  * PlugInterface.cpp
3  * -----------------
4  * Purpose: Default plugin interface implementation
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #include "stdafx.h"
12 #include "../Sndfile.h"
13 #include "PlugInterface.h"
14 #include "PluginManager.h"
15 #include "../../common/FileReader.h"
16 #ifdef MODPLUG_TRACKER
17 #include "../../mptrack/Moddoc.h"
18 #include "../../mptrack/Mainfrm.h"
19 #include "../../mptrack/InputHandler.h"
20 #include "../../mptrack/AbstractVstEditor.h"
21 #include "../../mptrack/DefaultVstEditor.h"
22 // LoadProgram/SaveProgram
23 #include "../../mptrack/FileDialog.h"
24 #include "../../mptrack/VstPresets.h"
25 #include "../../common/mptFileIO.h"
26 #include "../mod_specifications.h"
27 #endif // MODPLUG_TRACKER
28 #include "mpt/base/aligned_array.hpp"
29 #include "mpt/io/base.hpp"
30 #include "mpt/io/io.hpp"
31 #include "mpt/io/io_span.hpp"
32 
33 #include <cmath>
34 
35 #ifndef NO_PLUGINS
36 
37 OPENMPT_NAMESPACE_BEGIN
38 
39 
40 #ifdef MODPLUG_TRACKER
GetModDoc()41 CModDoc *IMixPlugin::GetModDoc() { return m_SndFile.GetpModDoc(); }
GetModDoc() const42 const CModDoc *IMixPlugin::GetModDoc() const { return m_SndFile.GetpModDoc(); }
43 #endif // MODPLUG_TRACKER
44 
45 
IMixPlugin(VSTPluginLib & factory,CSoundFile & sndFile,SNDMIXPLUGIN * mixStruct)46 IMixPlugin::IMixPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
47 	: m_Factory(factory)
48 	, m_SndFile(sndFile)
49 	, m_pMixStruct(mixStruct)
50 {
51 	m_SndFile.m_loadedPlugins++;
52 	m_MixState.pMixBuffer = mpt::align_bytes<8, MIXBUFFERSIZE * 2>(m_MixBuffer);
53 	while(m_pMixStruct != &(m_SndFile.m_MixPlugins[m_nSlot]) && m_nSlot < MAX_MIXPLUGINS - 1)
54 	{
55 		m_nSlot++;
56 	}
57 }
58 
59 
~IMixPlugin()60 IMixPlugin::~IMixPlugin()
61 {
62 #ifdef MODPLUG_TRACKER
63 	CloseEditor();
64 	CriticalSection cs;
65 #endif // MODPLUG_TRACKER
66 
67 	// First thing to do, if we don't want to hang in a loop
68 	if (m_Factory.pPluginsList == this) m_Factory.pPluginsList = m_pNext;
69 	if (m_pMixStruct)
70 	{
71 		m_pMixStruct->pMixPlugin = nullptr;
72 		m_pMixStruct = nullptr;
73 	}
74 
75 	if (m_pNext) m_pNext->m_pPrev = m_pPrev;
76 	if (m_pPrev) m_pPrev->m_pNext = m_pNext;
77 	m_pPrev = nullptr;
78 	m_pNext = nullptr;
79 	m_SndFile.m_loadedPlugins--;
80 }
81 
82 
InsertIntoFactoryList()83 void IMixPlugin::InsertIntoFactoryList()
84 {
85 	m_pMixStruct->pMixPlugin = this;
86 
87 	m_pNext = m_Factory.pPluginsList;
88 	if(m_Factory.pPluginsList)
89 	{
90 		m_Factory.pPluginsList->m_pPrev = this;
91 	}
92 	m_Factory.pPluginsList = this;
93 }
94 
95 
96 #ifdef MODPLUG_TRACKER
97 
SetSlot(PLUGINDEX slot)98 void IMixPlugin::SetSlot(PLUGINDEX slot)
99 {
100 	m_nSlot = slot;
101 	m_pMixStruct = &m_SndFile.m_MixPlugins[slot];
102 }
103 
104 
GetScaledUIParam(PlugParamIndex param)105 PlugParamValue IMixPlugin::GetScaledUIParam(PlugParamIndex param)
106 {
107 	const auto [paramMin, paramMax] = GetParamUIRange(param);
108 	return (std::clamp(GetParameter(param), paramMin, paramMax) - paramMin) / (paramMax - paramMin);
109 }
110 
111 
SetScaledUIParam(PlugParamIndex param,PlugParamValue value)112 void IMixPlugin::SetScaledUIParam(PlugParamIndex param, PlugParamValue value)
113 {
114 	const auto [paramMin, paramMax] = GetParamUIRange(param);
115 	const auto scaledVal = paramMin + std::clamp(value, 0.0f, 1.0f) * (paramMax - paramMin);
116 	SetParameter(param, scaledVal);
117 }
118 
119 
GetFormattedParamName(PlugParamIndex param)120 CString IMixPlugin::GetFormattedParamName(PlugParamIndex param)
121 {
122 	CString paramName = GetParamName(param);
123 	CString name;
124 	if(paramName.IsEmpty())
125 	{
126 		name = MPT_CFORMAT("{}: Parameter {}")(mpt::cfmt::dec0<2>(param), mpt::cfmt::dec0<2>(param));
127 	} else
128 	{
129 		name = MPT_CFORMAT("{}: {}")(mpt::cfmt::dec0<2>(param), paramName);
130 	}
131 	return name;
132 }
133 
134 
135 // Get a parameter's current value, represented by the plugin.
GetFormattedParamValue(PlugParamIndex param)136 CString IMixPlugin::GetFormattedParamValue(PlugParamIndex param)
137 {
138 
139 	CString paramDisplay = GetParamDisplay(param);
140 	CString paramUnits = GetParamLabel(param);
141 	paramDisplay.Trim();
142 	paramUnits.Trim();
143 	paramDisplay += _T(" ") + paramUnits;
144 
145 	return paramDisplay;
146 }
147 
148 
GetFormattedProgramName(int32 index)149 CString IMixPlugin::GetFormattedProgramName(int32 index)
150 {
151 	CString rawname = GetProgramName(index);
152 
153 	// Let's start counting at 1 for the program name (as most MIDI hardware / software does)
154 	index++;
155 
156 	CString formattedName;
157 	if(rawname[0] >= 0 && rawname[0] < _T(' '))
158 		formattedName = MPT_CFORMAT("{} - Program {}")(mpt::cfmt::dec0<2>(index), index);
159 	else
160 		formattedName = MPT_CFORMAT("{} - {}")(mpt::cfmt::dec0<2>(index), rawname);
161 
162 	return formattedName;
163 }
164 
165 
SetEditorPos(int32 x,int32 y)166 void IMixPlugin::SetEditorPos(int32 x, int32 y)
167 {
168 	m_pMixStruct->editorX = x;
169 	m_pMixStruct->editorY = y;
170 }
171 
172 
GetEditorPos(int32 & x,int32 & y) const173 void IMixPlugin::GetEditorPos(int32 &x, int32 &y) const
174 {
175 	x = m_pMixStruct->editorX;
176 	y = m_pMixStruct->editorY;
177 }
178 
179 
180 #endif // MODPLUG_TRACKER
181 
182 
IsBypassed() const183 bool IMixPlugin::IsBypassed() const
184 {
185 	return m_pMixStruct != nullptr && m_pMixStruct->IsBypassed();
186 }
187 
188 
RecalculateGain()189 void IMixPlugin::RecalculateGain()
190 {
191 	float gain = 0.1f * static_cast<float>(m_pMixStruct ? m_pMixStruct->GetGain() : 10);
192 	if(gain < 0.1f) gain = 1.0f;
193 
194 	if(IsInstrument())
195 	{
196 		gain /= m_SndFile.GetPlayConfig().getVSTiAttenuation();
197 		gain = static_cast<float>(gain * (m_SndFile.m_nVSTiVolume / m_SndFile.GetPlayConfig().getNormalVSTiVol()));
198 	}
199 	m_fGain = gain;
200 }
201 
202 
SetDryRatio(uint32 param)203 void IMixPlugin::SetDryRatio(uint32 param)
204 {
205 	param = std::min(param, uint32(127));
206 	m_pMixStruct->fDryRatio = 1.0f - (param / 127.0f);
207 }
208 
209 
Bypass(bool bypass)210 void IMixPlugin::Bypass(bool bypass)
211 {
212 	m_pMixStruct->Info.SetBypass(bypass);
213 
214 #ifdef MODPLUG_TRACKER
215 	if(m_SndFile.GetpModDoc())
216 		m_SndFile.GetpModDoc()->UpdateAllViews(nullptr, PluginHint(m_nSlot + 1).Info(), nullptr);
217 #endif // MODPLUG_TRACKER
218 }
219 
220 
GetOutputLatency() const221 double IMixPlugin::GetOutputLatency() const
222 {
223 	if(GetSoundFile().IsRenderingToDisc())
224 		return 0;
225 	else
226 		return GetSoundFile().m_TimingInfo.OutputLatency;
227 }
228 
229 
ProcessMixOps(float * MPT_RESTRICT pOutL,float * MPT_RESTRICT pOutR,float * MPT_RESTRICT leftPlugOutput,float * MPT_RESTRICT rightPlugOutput,uint32 numFrames)230 void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT pOutR, float * MPT_RESTRICT leftPlugOutput, float * MPT_RESTRICT rightPlugOutput, uint32 numFrames)
231 {
232 /*	float *leftPlugOutput;
233 	float *rightPlugOutput;
234 
235 	if(m_Effect.numOutputs == 1)
236 	{
237 		// If there was just the one plugin output we copy it into our 2 outputs
238 		leftPlugOutput = rightPlugOutput = mixBuffer.GetOutputBuffer(0);
239 	} else if(m_Effect.numOutputs > 1)
240 	{
241 		// Otherwise we actually only cater for two outputs max (outputs > 2 have been mixed together already).
242 		leftPlugOutput = mixBuffer.GetOutputBuffer(0);
243 		rightPlugOutput = mixBuffer.GetOutputBuffer(1);
244 	} else
245 	{
246 		return;
247 	}*/
248 
249 	// -> mixop == 0 : normal processing
250 	// -> mixop == 1 : MIX += DRY - WET * wetRatio
251 	// -> mixop == 2 : MIX += WET - DRY * dryRatio
252 	// -> mixop == 3 : MIX -= WET - DRY * wetRatio
253 	// -> mixop == 4 : MIX -= middle - WET * wetRatio + middle - DRY
254 	// -> mixop == 5 : MIX_L += wetRatio * (WET_L - DRY_L) + dryRatio * (DRY_R - WET_R)
255 	//                 MIX_R += dryRatio * (WET_L - DRY_L) + wetRatio * (DRY_R - WET_R)
256 
257 	MPT_ASSERT(m_pMixStruct != nullptr);
258 
259 	int mixop;
260 	if(IsInstrument())
261 	{
262 		// Force normal mix mode for instruments
263 		mixop = 0;
264 	} else
265 	{
266 		mixop = m_pMixStruct->GetMixMode();
267 	}
268 
269 	float wetRatio = 1 - m_pMixStruct->fDryRatio;
270 	float dryRatio = IsInstrument() ? 1 : m_pMixStruct->fDryRatio; // Always mix full dry if this is an instrument
271 
272 	// Wet / Dry range expansion [0,1] -> [-1,1]
273 	if(GetNumInputChannels() > 0 && m_pMixStruct->IsExpandedMix())
274 	{
275 		wetRatio = 2.0f * wetRatio - 1.0f;
276 		dryRatio = -wetRatio;
277 	}
278 
279 	wetRatio *= m_fGain;
280 	dryRatio *= m_fGain;
281 
282 	float * MPT_RESTRICT plugInputL = m_mixBuffer.GetInputBuffer(0);
283 	float * MPT_RESTRICT plugInputR = m_mixBuffer.GetInputBuffer(1);
284 
285 	// Mix operation
286 	switch(mixop)
287 	{
288 
289 	// Default mix
290 	case 0:
291 		for(uint32 i = 0; i < numFrames; i++)
292 		{
293 			//rewbs.wetratio - added the factors. [20040123]
294 			pOutL[i] += leftPlugOutput[i] * wetRatio + plugInputL[i] * dryRatio;
295 			pOutR[i] += rightPlugOutput[i] * wetRatio + plugInputR[i] * dryRatio;
296 		}
297 		break;
298 
299 	// Wet subtract
300 	case 1:
301 		for(uint32 i = 0; i < numFrames; i++)
302 		{
303 			pOutL[i] += plugInputL[i] - leftPlugOutput[i] * wetRatio;
304 			pOutR[i] += plugInputR[i] - rightPlugOutput[i] * wetRatio;
305 		}
306 		break;
307 
308 	// Dry subtract
309 	case 2:
310 		for(uint32 i = 0; i < numFrames; i++)
311 		{
312 			pOutL[i] += leftPlugOutput[i] - plugInputL[i] * dryRatio;
313 			pOutR[i] += rightPlugOutput[i] - plugInputR[i] * dryRatio;
314 		}
315 		break;
316 
317 	// Mix subtract
318 	case 3:
319 		for(uint32 i = 0; i < numFrames; i++)
320 		{
321 			pOutL[i] -= leftPlugOutput[i] - plugInputL[i] * wetRatio;
322 			pOutR[i] -= rightPlugOutput[i] - plugInputR[i] * wetRatio;
323 		}
324 		break;
325 
326 	// Middle subtract
327 	case 4:
328 		for(uint32 i = 0; i < numFrames; i++)
329 		{
330 			float middle = (pOutL[i] + plugInputL[i] + pOutR[i] + plugInputR[i]) / 2.0f;
331 			pOutL[i] -= middle - leftPlugOutput[i] * wetRatio + middle - plugInputL[i];
332 			pOutR[i] -= middle - rightPlugOutput[i] * wetRatio + middle - plugInputR[i];
333 		}
334 		break;
335 
336 	// Left / Right balance
337 	case 5:
338 		if(m_pMixStruct->IsExpandedMix())
339 		{
340 			wetRatio /= 2.0f;
341 			dryRatio /= 2.0f;
342 		}
343 
344 		for(uint32 i = 0; i < numFrames; i++)
345 		{
346 			pOutL[i] += wetRatio * (leftPlugOutput[i] - plugInputL[i]) + dryRatio * (plugInputR[i] - rightPlugOutput[i]);
347 			pOutR[i] += dryRatio * (leftPlugOutput[i] - plugInputL[i]) + wetRatio * (plugInputR[i] - rightPlugOutput[i]);
348 		}
349 		break;
350 	}
351 
352 	// If dry mix is ticked, we add the unprocessed buffer,
353 	// except if this is an instrument since then it has already been done:
354 	if(m_pMixStruct->IsWetMix() && !IsInstrument())
355 	{
356 		for(uint32 i = 0; i < numFrames; i++)
357 		{
358 			pOutL[i] += plugInputL[i];
359 			pOutR[i] += plugInputR[i];
360 		}
361 	}
362 }
363 
364 
365 // Render some silence and return maximum level returned by the plugin.
RenderSilence(uint32 numFrames)366 float IMixPlugin::RenderSilence(uint32 numFrames)
367 {
368 	// The JUCE framework doesn't like processing while being suspended.
369 	const bool wasSuspended = !IsResumed();
370 	if(wasSuspended)
371 	{
372 		Resume();
373 	}
374 
375 	float out[2][MIXBUFFERSIZE]; // scratch buffers
376 	float maxVal = 0.0f;
377 	m_mixBuffer.ClearInputBuffers(MIXBUFFERSIZE);
378 
379 	while(numFrames > 0)
380 	{
381 		uint32 renderSamples = numFrames;
382 		LimitMax(renderSamples, mpt::saturate_cast<uint32>(std::size(out[0])));
383 		MemsetZero(out);
384 
385 		Process(out[0], out[1], renderSamples);
386 		for(size_t i = 0; i < renderSamples; i++)
387 		{
388 			maxVal = std::max(maxVal, std::fabs(out[0][i]));
389 			maxVal = std::max(maxVal, std::fabs(out[1][i]));
390 		}
391 
392 		numFrames -= renderSamples;
393 	}
394 
395 	if(wasSuspended)
396 	{
397 		Suspend();
398 	}
399 
400 	return maxVal;
401 }
402 
403 
404 // Get list of plugins to which output is sent. A nullptr indicates master output.
GetOutputPlugList(std::vector<IMixPlugin * > & list)405 size_t IMixPlugin::GetOutputPlugList(std::vector<IMixPlugin *> &list)
406 {
407 	// At the moment we know there will only be 1 output.
408 	// Returning nullptr means plugin outputs directly to master.
409 	list.clear();
410 
411 	IMixPlugin *outputPlug = nullptr;
412 	if(!m_pMixStruct->IsOutputToMaster())
413 	{
414 		PLUGINDEX nOutput = m_pMixStruct->GetOutputPlugin();
415 		if(nOutput > m_nSlot && nOutput != PLUGINDEX_INVALID)
416 		{
417 			outputPlug = m_SndFile.m_MixPlugins[nOutput].pMixPlugin;
418 		}
419 	}
420 	list.push_back(outputPlug);
421 
422 	return 1;
423 }
424 
425 
426 // Get a list of plugins that send data to this plugin.
GetInputPlugList(std::vector<IMixPlugin * > & list)427 size_t IMixPlugin::GetInputPlugList(std::vector<IMixPlugin *> &list)
428 {
429 	std::vector<IMixPlugin *> candidatePlugOutputs;
430 	list.clear();
431 
432 	for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
433 	{
434 		IMixPlugin *candidatePlug = m_SndFile.m_MixPlugins[plug].pMixPlugin;
435 		if(candidatePlug)
436 		{
437 			candidatePlug->GetOutputPlugList(candidatePlugOutputs);
438 
439 			for(auto &outPlug : candidatePlugOutputs)
440 			{
441 				if(outPlug == this)
442 				{
443 					list.push_back(candidatePlug);
444 					break;
445 				}
446 			}
447 		}
448 	}
449 
450 	return list.size();
451 }
452 
453 
454 // Get a list of instruments that send data to this plugin.
GetInputInstrumentList(std::vector<INSTRUMENTINDEX> & list)455 size_t IMixPlugin::GetInputInstrumentList(std::vector<INSTRUMENTINDEX> &list)
456 {
457 	list.clear();
458 	const PLUGINDEX nThisMixPlug = m_nSlot + 1;		//m_nSlot is position in mixplug array.
459 
460 	for(INSTRUMENTINDEX ins = 0; ins <= m_SndFile.GetNumInstruments(); ins++)
461 	{
462 		if(m_SndFile.Instruments[ins] != nullptr && m_SndFile.Instruments[ins]->nMixPlug == nThisMixPlug)
463 		{
464 			list.push_back(ins);
465 		}
466 	}
467 
468 	return list.size();
469 }
470 
471 
GetInputChannelList(std::vector<CHANNELINDEX> & list)472 size_t IMixPlugin::GetInputChannelList(std::vector<CHANNELINDEX> &list)
473 {
474 	list.clear();
475 
476 	PLUGINDEX nThisMixPlug = m_nSlot + 1;		//m_nSlot is position in mixplug array.
477 	const CHANNELINDEX chnCount = m_SndFile.GetNumChannels();
478 	for(CHANNELINDEX nChn=0; nChn<chnCount; nChn++)
479 	{
480 		if(m_SndFile.ChnSettings[nChn].nMixPlugin == nThisMixPlug)
481 		{
482 			list.push_back(nChn);
483 		}
484 	}
485 
486 	return list.size();
487 
488 }
489 
490 
SaveAllParameters()491 void IMixPlugin::SaveAllParameters()
492 {
493 	if (m_pMixStruct == nullptr)
494 	{
495 		return;
496 	}
497 	m_pMixStruct->defaultProgram = -1;
498 
499 	// Default implementation: Save all parameter values
500 	PlugParamIndex numParams = std::min(GetNumParameters(), static_cast<int32>((std::numeric_limits<uint32>::max() - sizeof(uint32)) / sizeof(IEEE754binary32LE)));
501 	uint32 nLen = numParams * sizeof(IEEE754binary32LE);
502 	if (!nLen) return;
503 	nLen += sizeof(uint32);
504 
505 	try
506 	{
507 		m_pMixStruct->pluginData.resize(nLen);
508 		auto memFile = std::make_pair(mpt::as_span(m_pMixStruct->pluginData), mpt::IO::Offset(0));
509 		mpt::IO::WriteIntLE<uint32>(memFile, 0);	// Plugin data type
510 		BeginGetProgram();
511 		for(PlugParamIndex i = 0; i < numParams; i++)
512 		{
513 			mpt::IO::Write(memFile, IEEE754binary32LE(GetParameter(i)));
514 		}
515 		EndGetProgram();
516 	} catch(mpt::out_of_memory e)
517 	{
518 		m_pMixStruct->pluginData.clear();
519 		mpt::delete_out_of_memory(e);
520 	}
521 }
522 
523 
RestoreAllParameters(int32)524 void IMixPlugin::RestoreAllParameters(int32 /*program*/)
525 {
526 	if(m_pMixStruct != nullptr && m_pMixStruct->pluginData.size() >= sizeof(uint32))
527 	{
528 		FileReader memFile(mpt::as_span(m_pMixStruct->pluginData));
529 		uint32 type = memFile.ReadUint32LE();
530 		if(type == 0)
531 		{
532 			const uint32 numParams = GetNumParameters();
533 			if((m_pMixStruct->pluginData.size() - sizeof(uint32)) >= (numParams * sizeof(IEEE754binary32LE)))
534 			{
535 				BeginSetProgram();
536 				for(uint32 i = 0; i < numParams; i++)
537 				{
538 					const auto value = memFile.ReadFloatLE();
539 					SetParameter(i, std::isfinite(value) ? value : 0.0f);
540 				}
541 				EndSetProgram();
542 			}
543 		}
544 	}
545 }
546 
547 
548 #ifdef MODPLUG_TRACKER
ToggleEditor()549 void IMixPlugin::ToggleEditor()
550 {
551 	// We only really need this mutex for bridged plugins, as we may be processing window messages (in the same thread) while the editor opens.
552 	// The user could press the toggle button while the editor is loading and thus close the editor while still being initialized.
553 	// Note that this does not protect against closing the module while the editor is still loading.
554 	static bool initializing = false;
555 	if(initializing)
556 		return;
557 	initializing = true;
558 
559 	if (m_pEditor)
560 	{
561 		CloseEditor();
562 	} else
563 	{
564 		m_pEditor = OpenEditor();
565 
566 		if (m_pEditor)
567 			m_pEditor->OpenEditor(CMainFrame::GetMainFrame());
568 	}
569 	initializing = false;
570 }
571 
572 
573 // Provide default plugin editor
OpenEditor()574 CAbstractVstEditor *IMixPlugin::OpenEditor()
575 {
576 	try
577 	{
578 		return new CDefaultVstEditor(*this);
579 	} catch(mpt::out_of_memory e)
580 	{
581 		mpt::delete_out_of_memory(e);
582 		return nullptr;
583 	}
584 }
585 
586 
CloseEditor()587 void IMixPlugin::CloseEditor()
588 {
589 	if(m_pEditor)
590 	{
591 		if (m_pEditor->m_hWnd) m_pEditor->DoClose();
592 		delete m_pEditor;
593 		m_pEditor = nullptr;
594 	}
595 }
596 
597 
598 // Automate a parameter from the plugin GUI (both custom and default plugin GUI)
AutomateParameter(PlugParamIndex param)599 void IMixPlugin::AutomateParameter(PlugParamIndex param)
600 {
601 	CModDoc *modDoc = GetModDoc();
602 	if(modDoc == nullptr)
603 	{
604 		return;
605 	}
606 
607 	// TODO: Check if any params are actually automatable, and if there are but this one isn't, chicken out
608 
609 	if(m_recordAutomation)
610 	{
611 		// Record parameter change
612 		modDoc->RecordParamChange(GetSlot(), param);
613 	}
614 
615 	modDoc->SendNotifyMessageToAllViews(WM_MOD_PLUGPARAMAUTOMATE, m_nSlot, param);
616 
617 	if(auto *vstEditor = GetEditor(); vstEditor && vstEditor->m_hWnd)
618 	{
619 		// Mark track modified if GUI is open and format supports plugins
620 		SetModified();
621 
622 		// Do not use InputHandler in case we are coming from a bridged plugin editor
623 		if((GetAsyncKeyState(VK_SHIFT) & 0x8000) && TrackerSettings::Instance().midiMappingInPluginEditor)
624 		{
625 			// Shift pressed -> Open MIDI mapping dialog
626 			CMainFrame::GetMainFrame()->PostMessage(WM_MOD_MIDIMAPPING, m_nSlot, param);
627 		}
628 
629 		// Learn macro
630 		int macroToLearn = vstEditor->GetLearnMacro();
631 		if (macroToLearn > -1)
632 		{
633 			modDoc->LearnMacro(macroToLearn, param);
634 			vstEditor->SetLearnMacro(-1);
635 		}
636 	}
637 }
638 
639 
SetModified()640 void IMixPlugin::SetModified()
641 {
642 	CModDoc *modDoc = GetModDoc();
643 	if(modDoc != nullptr && m_SndFile.GetModSpecifications().supportsPlugins)
644 	{
645 		modDoc->SetModified();
646 	}
647 }
648 
649 
SaveProgram()650 bool IMixPlugin::SaveProgram()
651 {
652 	mpt::PathString defaultDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
653 	const bool useDefaultDir = !defaultDir.empty();
654 	if(!useDefaultDir && m_Factory.dllPath.IsFile())
655 	{
656 		defaultDir = m_Factory.dllPath.GetPath();
657 	}
658 
659 	CString progName = m_Factory.libraryName.ToCString() + _T(" - ") + GetCurrentProgramName();
660 	SanitizeFilename(progName);
661 
662 	FileDialog dlg = SaveFileDialog()
663 		.DefaultExtension("fxb")
664 		.DefaultFilename(progName)
665 		.ExtensionFilter("VST Plugin Programs (*.fxp)|*.fxp|"
666 			"VST Plugin Banks (*.fxb)|*.fxb||")
667 		.WorkingDirectory(defaultDir);
668 	if(!dlg.Show(m_pEditor)) return false;
669 
670 	if(useDefaultDir)
671 	{
672 		TrackerSettings::Instance().PathPluginPresets.SetWorkingDir(dlg.GetWorkingDirectory());
673 	}
674 
675 	const bool isBank = (dlg.GetExtension() == P_("fxb"));
676 
677 	try
678 	{
679 		mpt::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
680 		mpt::ofstream &f = sf;
681 		f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
682 		if(f.good() && VSTPresets::SaveFile(f, *this, isBank))
683 			return true;
684 	} catch(const std::exception &)
685 	{
686 
687 	}
688 	Reporting::Error("Error saving preset.", m_pEditor);
689 	return false;
690 }
691 
692 
LoadProgram(mpt::PathString fileName)693 bool IMixPlugin::LoadProgram(mpt::PathString fileName)
694 {
695 	mpt::PathString defaultDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
696 	bool useDefaultDir = !defaultDir.empty();
697 	if(!useDefaultDir && m_Factory.dllPath.IsFile())
698 	{
699 		defaultDir = m_Factory.dllPath.GetPath();
700 	}
701 
702 	if(fileName.empty())
703 	{
704 		FileDialog dlg = OpenFileDialog()
705 			.DefaultExtension("fxp")
706 			.ExtensionFilter("VST Plugin Programs and Banks (*.fxp,*.fxb)|*.fxp;*.fxb|"
707 			"VST Plugin Programs (*.fxp)|*.fxp|"
708 			"VST Plugin Banks (*.fxb)|*.fxb|"
709 			"All Files|*.*||")
710 			.WorkingDirectory(defaultDir);
711 		if(!dlg.Show(m_pEditor)) return false;
712 
713 		if(useDefaultDir)
714 		{
715 			TrackerSettings::Instance().PathPluginPresets.SetWorkingDir(dlg.GetWorkingDirectory());
716 		}
717 		fileName = dlg.GetFirstFile();
718 	}
719 
720 	const char *errorStr = nullptr;
721 	InputFile f(fileName, SettingCacheCompleteFileBeforeLoading());
722 	if(f.IsValid())
723 	{
724 		FileReader file = GetFileReader(f);
725 		errorStr = VSTPresets::GetErrorMessage(VSTPresets::LoadFile(file, *this));
726 	} else
727 	{
728 		errorStr = "Can't open file.";
729 	}
730 
731 	if(errorStr == nullptr)
732 	{
733 		if(GetModDoc() != nullptr && GetSoundFile().GetModSpecifications().supportsPlugins)
734 		{
735 			GetModDoc()->SetModified();
736 		}
737 		return true;
738 	} else
739 	{
740 		Reporting::Error(errorStr, m_pEditor);
741 		return false;
742 	}
743 }
744 
745 
746 #endif // MODPLUG_TRACKER
747 
748 
749 ////////////////////////////////////////////////////////////////////
750 // IMidiPlugin: Default implementation of plugins with MIDI input //
751 ////////////////////////////////////////////////////////////////////
752 
IMidiPlugin(VSTPluginLib & factory,CSoundFile & sndFile,SNDMIXPLUGIN * mixStruct)753 IMidiPlugin::IMidiPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
754 	: IMixPlugin(factory, sndFile, mixStruct)
755 	, m_MidiCh{{}}
756 {
757 	for(auto &chn : m_MidiCh)
758 	{
759 		chn.midiPitchBendPos = EncodePitchBendParam(MIDIEvents::pitchBendCentre); // centre pitch bend on all channels
760 		chn.ResetProgram();
761 	}
762 }
763 
764 
ApplyPitchWheelDepth(int32 & value,int8 pwd)765 void IMidiPlugin::ApplyPitchWheelDepth(int32 &value, int8 pwd)
766 {
767 	if(pwd != 0)
768 	{
769 		value = (value * ((MIDIEvents::pitchBendMax - MIDIEvents::pitchBendCentre + 1) / 64)) / pwd;
770 	} else
771 	{
772 		value = 0;
773 	}
774 }
775 
776 
777 // Get the MIDI channel currently associated with a given tracker channel
GetMidiChannel(CHANNELINDEX trackChannel) const778 uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const
779 {
780 	if(trackChannel >= std::size(m_SndFile.m_PlayState.Chn))
781 		return 0;
782 
783 	if(auto ins = m_SndFile.m_PlayState.Chn[trackChannel].pModInstrument; ins != nullptr)
784 		return ins->GetMIDIChannel(m_SndFile, trackChannel);
785 	else
786 		return 0;
787 }
788 
789 
MidiCC(MIDIEvents::MidiCC nController,uint8 nParam,CHANNELINDEX trackChannel)790 void IMidiPlugin::MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel)
791 {
792 	//Error checking
793 	LimitMax(nController, MIDIEvents::MIDICC_end);
794 	LimitMax(nParam, uint8(127));
795 	auto midiCh = GetMidiChannel(trackChannel);
796 
797 	if(m_SndFile.m_playBehaviour[kMIDICCBugEmulation])
798 		MidiSend(MIDIEvents::Event(MIDIEvents::evControllerChange, midiCh, nParam, static_cast<uint8>(nController)));	// param and controller are swapped (old broken implementation)
799 	else
800 		MidiSend(MIDIEvents::CC(nController, midiCh, nParam));
801 }
802 
803 
804 // Set MIDI pitch for given MIDI channel to the specified raw 14-bit position
MidiPitchBendRaw(int32 pitchbend,CHANNELINDEX trackerChn)805 void IMidiPlugin::MidiPitchBendRaw(int32 pitchbend, CHANNELINDEX trackerChn)
806 {
807 	SendMidiPitchBend(GetMidiChannel(trackerChn), EncodePitchBendParam(Clamp(pitchbend, MIDIEvents::pitchBendMin, MIDIEvents::pitchBendMax)));
808 }
809 
810 
811 // Bend MIDI pitch for given MIDI channel using fine tracker param (one unit = 1/64th of a note step)
MidiPitchBend(int32 increment,int8 pwd,CHANNELINDEX trackerChn)812 void IMidiPlugin::MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackerChn)
813 {
814 	auto midiCh = GetMidiChannel(trackerChn);
815 	if(m_SndFile.m_playBehaviour[kOldMIDIPitchBends])
816 	{
817 		// OpenMPT Legacy: Old pitch slides never were really accurate, but setting the PWD to 13 in plugins would give the closest results.
818 		increment = (increment * 0x800 * 13) / (0xFF * pwd);
819 		increment = EncodePitchBendParam(increment);
820 	} else
821 	{
822 		increment = EncodePitchBendParam(increment);
823 		ApplyPitchWheelDepth(increment, pwd);
824 	}
825 
826 	int32 newPitchBendPos = (increment + m_MidiCh[midiCh].midiPitchBendPos) & kPitchBendMask;
827 	Limit(newPitchBendPos, EncodePitchBendParam(MIDIEvents::pitchBendMin), EncodePitchBendParam(MIDIEvents::pitchBendMax));
828 
829 	SendMidiPitchBend(midiCh, newPitchBendPos);
830 }
831 
832 
833 // Set MIDI pitch for given MIDI channel using fixed point pitch bend value (converted back to 0-16383 MIDI range)
SendMidiPitchBend(uint8 midiCh,int32 newPitchBendPos)834 void IMidiPlugin::SendMidiPitchBend(uint8 midiCh, int32 newPitchBendPos)
835 {
836 	MPT_ASSERT(EncodePitchBendParam(MIDIEvents::pitchBendMin) <= newPitchBendPos && newPitchBendPos <= EncodePitchBendParam(MIDIEvents::pitchBendMax));
837 	m_MidiCh[midiCh].midiPitchBendPos = newPitchBendPos;
838 	MidiSend(MIDIEvents::PitchBend(midiCh, DecodePitchBendParam(newPitchBendPos)));
839 }
840 
841 
842 // Apply vibrato effect through pitch wheel commands on a given MIDI channel.
MidiVibrato(int32 depth,int8 pwd,CHANNELINDEX trackerChn)843 void IMidiPlugin::MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackerChn)
844 {
845 	auto midiCh = GetMidiChannel(trackerChn);
846 	depth = EncodePitchBendParam(depth);
847 	if(depth != 0 || (m_MidiCh[midiCh].midiPitchBendPos & kVibratoFlag))
848 	{
849 		ApplyPitchWheelDepth(depth, pwd);
850 
851 		// Temporarily add vibrato offset to current pitch
852 		int32 newPitchBendPos = (depth + m_MidiCh[midiCh].midiPitchBendPos) & kPitchBendMask;
853 		Limit(newPitchBendPos, EncodePitchBendParam(MIDIEvents::pitchBendMin), EncodePitchBendParam(MIDIEvents::pitchBendMax));
854 
855 		MidiSend(MIDIEvents::PitchBend(midiCh, DecodePitchBendParam(newPitchBendPos)));
856 	}
857 
858 	// Update vibrato status
859 	if(depth != 0)
860 		m_MidiCh[midiCh].midiPitchBendPos |= kVibratoFlag;
861 	else
862 		m_MidiCh[midiCh].midiPitchBendPos &= ~kVibratoFlag;
863 }
864 
865 
MidiCommand(const ModInstrument & instr,uint16 note,uint16 vol,CHANNELINDEX trackChannel)866 void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel)
867 {
868 	if(trackChannel >= MAX_CHANNELS)
869 		return;
870 
871 	auto midiCh = GetMidiChannel(trackChannel);
872 	PlugInstrChannel &channel = m_MidiCh[midiCh];
873 
874 	uint16 midiBank = instr.wMidiBank - 1;
875 	uint8 midiProg = instr.nMidiProgram - 1;
876 	bool bankChanged = (channel.currentBank != midiBank) && (midiBank < 0x4000);
877 	bool progChanged = (channel.currentProgram != midiProg) && (midiProg < 0x80);
878 	//get vol in [0,128[
879 	uint8 volume = static_cast<uint8>(std::min(vol / 2u, 127u));
880 
881 	// Bank change
882 	if(bankChanged)
883 	{
884 		uint8 high = static_cast<uint8>(midiBank >> 7);
885 		uint8 low = static_cast<uint8>(midiBank & 0x7F);
886 
887 		//m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_BANKSEL], 0, m_nSlot + 1);
888 		MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Coarse, midiCh, high));
889 		MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Fine, midiCh, low));
890 
891 		channel.currentBank = midiBank;
892 	}
893 
894 	// Program change
895 	// According to the MIDI specs, a bank change alone doesn't have to change the active program - it will only change the bank of subsequent program changes.
896 	// Thus we send program changes also if only the bank has changed.
897 	if(progChanged || (midiProg < 0x80 && bankChanged))
898 	{
899 		channel.currentProgram = midiProg;
900 		//m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM], 0, m_nSlot + 1);
901 		MidiSend(MIDIEvents::ProgramChange(midiCh, midiProg));
902 	}
903 
904 
905 	// Specific Note Off
906 	if(note > NOTE_MAX_SPECIAL)
907 	{
908 		uint8 i = static_cast<uint8>(note - NOTE_MAX_SPECIAL - NOTE_MIN);
909 		if(channel.noteOnMap[i][trackChannel])
910 		{
911 			channel.noteOnMap[i][trackChannel]--;
912 			MidiSend(MIDIEvents::NoteOff(midiCh, i, 0));
913 		}
914 	}
915 
916 	// "Hard core" All Sounds Off on this midi and tracker channel
917 	// This one doesn't check the note mask - just one note off per note.
918 	// Also less likely to cause a VST event buffer overflow.
919 	else if(note == NOTE_NOTECUT)	// ^^
920 	{
921 		MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllNotesOff, midiCh, 0));
922 		MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllSoundOff, midiCh, 0));
923 
924 		// Turn off all notes
925 		for(uint8 i = 0; i < std::size(channel.noteOnMap); i++)
926 		{
927 			channel.noteOnMap[i][trackChannel] = 0;
928 			MidiSend(MIDIEvents::NoteOff(midiCh, i, volume));
929 		}
930 
931 	}
932 
933 	// All "active" notes off on this midi and tracker channel
934 	// using note mask.
935 	else if(note == NOTE_KEYOFF || note == NOTE_FADE) // ==, ~~
936 	{
937 		for(uint8 i = 0; i < std::size(channel.noteOnMap); i++)
938 		{
939 			// Some VSTis need a note off for each instance of a note on, e.g. fabfilter.
940 			while(channel.noteOnMap[i][trackChannel])
941 			{
942 				MidiSend(MIDIEvents::NoteOff(midiCh, i, volume));
943 				channel.noteOnMap[i][trackChannel]--;
944 			}
945 		}
946 	}
947 
948 	// Note On
949 	else if(note >= NOTE_MIN && note < NOTE_MIN + mpt::array_size<decltype(channel.noteOnMap)>::size)
950 	{
951 		note -= NOTE_MIN;
952 
953 		// Reset pitch bend on each new note, tracker style.
954 		// This is done if the pitch wheel has been moved or there was a vibrato on the previous row (in which case the "vstVibratoFlag" bit of the pitch bend memory is set)
955 		auto newPitchBendPos = EncodePitchBendParam(Clamp(m_SndFile.m_PlayState.Chn[trackChannel].GetMIDIPitchBend(), MIDIEvents::pitchBendMin, MIDIEvents::pitchBendMax));
956 		if(m_MidiCh[midiCh].midiPitchBendPos != newPitchBendPos)
957 		{
958 			SendMidiPitchBend(midiCh, newPitchBendPos);
959 		}
960 
961 		// count instances of active notes.
962 		// This is to send a note off for each instance of a note, for plugs like Fabfilter.
963 		// Problem: if a note dies out naturally and we never send a note off, this counter
964 		// will block at max until note off. Is this a problem?
965 		// Safe to assume we won't need more than 255 note offs max on a given note?
966 		if(channel.noteOnMap[note][trackChannel] < uint8_max)
967 		{
968 			channel.noteOnMap[note][trackChannel]++;
969 		}
970 
971 		MidiSend(MIDIEvents::NoteOn(midiCh, static_cast<uint8>(note), volume));
972 	}
973 }
974 
975 
IsNotePlaying(uint8 note,CHANNELINDEX trackerChn)976 bool IMidiPlugin::IsNotePlaying(uint8 note, CHANNELINDEX trackerChn)
977 {
978 	if(!ModCommand::IsNote(note) || trackerChn >= std::size(m_MidiCh[GetMidiChannel(trackerChn)].noteOnMap[note]))
979 		return false;
980 
981 	note -= NOTE_MIN;
982 	return (m_MidiCh[GetMidiChannel(trackerChn)].noteOnMap[note][trackerChn] != 0);
983 }
984 
985 
ReceiveMidi(uint32 midiCode)986 void IMidiPlugin::ReceiveMidi(uint32 midiCode)
987 {
988 	ResetSilence();
989 
990 	// I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
991 	// This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
992 	PLUGINDEX receiver;
993 	if(m_pMixStruct != nullptr && (receiver = m_pMixStruct->GetOutputPlugin()) != PLUGINDEX_INVALID)
994 	{
995 		IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin;
996 		// Add all events to the plugin's queue.
997 		plugin->MidiSend(midiCode);
998 	}
999 
1000 #ifdef MODPLUG_TRACKER
1001 	if(m_recordMIDIOut)
1002 	{
1003 		// Spam MIDI data to all views
1004 		::PostMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, midiCode, reinterpret_cast<LPARAM>(this));
1005 	}
1006 #endif // MODPLUG_TRACKER
1007 }
1008 
1009 
ReceiveSysex(mpt::const_byte_span sysex)1010 void IMidiPlugin::ReceiveSysex(mpt::const_byte_span sysex)
1011 {
1012 	ResetSilence();
1013 
1014 	// I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
1015 	// This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
1016 	PLUGINDEX receiver;
1017 	if(m_pMixStruct != nullptr && (receiver = m_pMixStruct->GetOutputPlugin()) != PLUGINDEX_INVALID)
1018 	{
1019 		IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin;
1020 		// Add all events to the plugin's queue.
1021 		plugin->MidiSysexSend(sysex);
1022 	}
1023 }
1024 
1025 
1026 // SNDMIXPLUGIN functions
1027 
SetGain(uint8 gain)1028 void SNDMIXPLUGIN::SetGain(uint8 gain)
1029 {
1030 	Info.gain = gain;
1031 	if(pMixPlugin != nullptr) pMixPlugin->RecalculateGain();
1032 }
1033 
1034 
SetBypass(bool bypass)1035 void SNDMIXPLUGIN::SetBypass(bool bypass)
1036 {
1037 	if(pMixPlugin != nullptr)
1038 		pMixPlugin->Bypass(bypass);
1039 	else
1040 		Info.SetBypass(bypass);
1041 }
1042 
1043 
Destroy()1044 void SNDMIXPLUGIN::Destroy()
1045 {
1046 	if(pMixPlugin)
1047 	{
1048 		pMixPlugin->Release();
1049 		pMixPlugin = nullptr;
1050 	}
1051 	pluginData.clear();
1052 	pluginData.shrink_to_fit();
1053 }
1054 
1055 OPENMPT_NAMESPACE_END
1056 
1057 #endif // NO_PLUGINS
1058