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