11#include "IPlugAPP_host.h"
15#include "win32_utf8.h"
23#define MAX_PATH_LEN 2048
28std::unique_ptr<IPlugAPPHost> IPlugAPPHost::sInstance;
31IPlugAPPHost::IPlugAPPHost()
32: mIPlug(MakePlug(InstanceInfo{this}))
36IPlugAPPHost::~IPlugAPPHost()
43 mMidiIn->cancelCallback();
46 mMidiOut->closePort();
52 sInstance = std::make_unique<IPlugAPPHost>();
53 return sInstance.get();
56bool IPlugAPPHost::Init()
58 mIPlug->SetHost(
"standalone", mIPlug->GetPluginVersion(
false));
63 TryToChangeAudioDriverType();
67 SelectMIDIDevice(ERoute::kInput, mState.mMidiInDev.Get());
68 SelectMIDIDevice(ERoute::kOutput, mState.mMidiOutDev.Get());
70 mIPlug->OnParamReset(kReset);
71 mIPlug->OnActivate(
true);
76bool IPlugAPPHost::OpenWindow(HWND pParent)
78 return mIPlug->OpenWindow(pParent) !=
nullptr;
81void IPlugAPPHost::CloseWindow()
83 mIPlug->CloseWindow();
86bool IPlugAPPHost::InitState()
89 char strPath[MAX_PATH_LEN];
90 SHGetSpecialFolderPathUTF8(NULL, strPath, MAX_PATH_LEN, CSIDL_LOCAL_APPDATA, FALSE);
91 mINIPath.SetFormatted(MAX_PATH_LEN,
"%s\\%s\\", strPath, BUNDLE_NAME);
93 mINIPath.SetFormatted(MAX_PATH_LEN,
"%s/Library/Application Support/%s/", getenv(
"HOME"), BUNDLE_NAME);
95 #error NOT IMPLEMENTED
100 if (stat(mINIPath.Get(), &st) == 0)
102 mINIPath.Append(
"settings.ini");
106 if (stat(mINIPath.Get(), &st) == 0)
108 DBGMSG(
"Reading ini file from %s\n", mINIPath.Get());
110 mState.mAudioDriverType = GetPrivateProfileInt(
"audio",
"driver", 0, mINIPath.Get());
112 GetPrivateProfileString(
"audio",
"indev",
"Built-in Input", buf, STRBUFSZ, mINIPath.Get()); mState.mAudioInDev.Set(buf);
113 GetPrivateProfileString(
"audio",
"outdev",
"Built-in Output", buf, STRBUFSZ, mINIPath.Get()); mState.mAudioOutDev.Set(buf);
116 mState.mAudioInChanL = GetPrivateProfileInt(
"audio",
"in1", 1, mINIPath.Get());
117 mState.mAudioInChanR = GetPrivateProfileInt(
"audio",
"in2", 2, mINIPath.Get());
118 mState.mAudioOutChanL = GetPrivateProfileInt(
"audio",
"out1", 1, mINIPath.Get());
119 mState.mAudioOutChanR = GetPrivateProfileInt(
"audio",
"out2", 2, mINIPath.Get());
122 mState.mBufferSize = GetPrivateProfileInt(
"audio",
"buffer", 512, mINIPath.Get());
123 mState.mAudioSR = GetPrivateProfileInt(
"audio",
"sr", 44100, mINIPath.Get());
126 GetPrivateProfileString(
"midi",
"indev",
"no input", buf, STRBUFSZ, mINIPath.Get()); mState.mMidiInDev.Set(buf);
127 GetPrivateProfileString(
"midi",
"outdev",
"no output", buf, STRBUFSZ, mINIPath.Get()); mState.mMidiOutDev.Set(buf);
129 mState.mMidiInChan = GetPrivateProfileInt(
"midi",
"inchan", 0, mINIPath.Get());
130 mState.mMidiOutChan = GetPrivateProfileInt(
"midi",
"outchan", 0, mINIPath.Get());
140 CreateDirectory(mINIPath.Get(), NULL);
141 mINIPath.Append(
"settings.ini");
144 mode_t process_mask = umask(0);
145 int result_code = mkdir(mINIPath.Get(), S_IRWXU | S_IRWXG | S_IRWXO);
150 mINIPath.Append(
"settings.ini");
158 #error NOT IMPLEMENTED
165void IPlugAPPHost::UpdateINI()
168 const char* ini = mINIPath.Get();
170 sprintf(buf,
"%u", mState.mAudioDriverType);
171 WritePrivateProfileString(
"audio",
"driver", buf, ini);
173 WritePrivateProfileString(
"audio",
"indev", mState.mAudioInDev.Get(), ini);
174 WritePrivateProfileString(
"audio",
"outdev", mState.mAudioOutDev.Get(), ini);
176 sprintf(buf,
"%u", mState.mAudioInChanL);
177 WritePrivateProfileString(
"audio",
"in1", buf, ini);
178 sprintf(buf,
"%u", mState.mAudioInChanR);
179 WritePrivateProfileString(
"audio",
"in2", buf, ini);
180 sprintf(buf,
"%u", mState.mAudioOutChanL);
181 WritePrivateProfileString(
"audio",
"out1", buf, ini);
182 sprintf(buf,
"%u", mState.mAudioOutChanR);
183 WritePrivateProfileString(
"audio",
"out2", buf, ini);
188 str.SetFormatted(32,
"%i", mState.mBufferSize);
189 WritePrivateProfileString(
"audio",
"buffer", str.Get(), ini);
191 str.SetFormatted(32,
"%i", mState.mAudioSR);
192 WritePrivateProfileString(
"audio",
"sr", str.Get(), ini);
194 WritePrivateProfileString(
"midi",
"indev", mState.mMidiInDev.Get(), ini);
195 WritePrivateProfileString(
"midi",
"outdev", mState.mMidiOutDev.Get(), ini);
197 sprintf(buf,
"%u", mState.mMidiInChan);
198 WritePrivateProfileString(
"midi",
"inchan", buf, ini);
199 sprintf(buf,
"%u", mState.mMidiOutChan);
200 WritePrivateProfileString(
"midi",
"outchan", buf, ini);
205 auto str = mDAC->getDeviceInfo(deviceID).name;
206 std::size_t pos = str.find(
':');
208 if (pos != std::string::npos)
210 std::string subStr = str.substr(pos + 1);
221 auto deviceIDs = mDAC->getDeviceIds();
223 for (
auto deviceID : deviceIDs)
227 if (std::string_view(deviceNameToTest) == name)
240 auto nameStrView = std::string_view(nameToTest);
242 if (direction == ERoute::kInput)
244 if (nameStrView == OFF_TEXT)
return 0;
248 if (nameStrView ==
"virtual input")
return 1;
251 for (
int i = 0; i < mMidiIn->getPortCount(); i++)
253 if (nameStrView == mMidiIn->getPortName(i).c_str())
259 if (nameStrView == OFF_TEXT)
return 0;
263 if (nameStrView ==
"virtual output")
return 1;
266 for (
int i = 0; i < mMidiOut->getPortCount(); i++)
268 if (nameStrView == mMidiOut->getPortName(i).c_str())
276void IPlugAPPHost::ProbeAudioIO()
278 mAudioInputDevIDs.clear();
279 mAudioOutputDevIDs.clear();
284 DBGMSG(
"\nRtAudio Version %s", RtAudio::getVersion().c_str());
286 RtAudio::DeviceInfo info;
288 auto deviceIDs = mDAC->getDeviceIds();
290 for (
auto deviceID : deviceIDs)
292 info = mDAC->getDeviceInfo(deviceID);
294 if (info.inputChannels > 0)
296 mAudioInputDevIDs.push_back(deviceID);
299 if (info.outputChannels > 0)
301 mAudioOutputDevIDs.push_back(deviceID);
304 if (info.isDefaultInput)
306 mDefaultInputDev = deviceID;
309 if (info.isDefaultOutput)
311 mDefaultOutputDev = deviceID;
316void IPlugAPPHost::ProbeMidiIO()
318 if (!mMidiIn || !mMidiOut)
322 int nInputPorts = mMidiIn->getPortCount();
324 mMidiInputDevNames.push_back(OFF_TEXT);
327 mMidiInputDevNames.push_back(
"virtual input");
330 for (
int i=0; i<nInputPorts; i++)
332 mMidiInputDevNames.push_back(mMidiIn->getPortName(i));
335 int nOutputPorts = mMidiOut->getPortCount();
337 mMidiOutputDevNames.push_back(OFF_TEXT);
340 mMidiOutputDevNames.push_back(
"virtual output");
343 for (
int i=0; i<nOutputPorts; i++)
345 mMidiOutputDevNames.push_back(mMidiOut->getPortName(i));
351bool IPlugAPPHost::AudioSettingsInStateAreEqual(AppState& os, AppState& ns)
353 if (os.mAudioDriverType != ns.mAudioDriverType)
return false;
354 if (std::string_view(os.mAudioInDev.Get()) != ns.mAudioInDev.Get())
return false;
355 if (std::string_view(os.mAudioOutDev.Get()) != ns.mAudioOutDev.Get())
return false;
356 if (os.mAudioSR != ns.mAudioSR)
return false;
357 if (os.mBufferSize != ns.mBufferSize)
return false;
358 if (os.mAudioInChanL != ns.mAudioInChanL)
return false;
359 if (os.mAudioInChanR != ns.mAudioInChanR)
return false;
360 if (os.mAudioOutChanL != ns.mAudioOutChanL)
return false;
361 if (os.mAudioOutChanR != ns.mAudioOutChanR)
return false;
367bool IPlugAPPHost::MIDISettingsInStateAreEqual(AppState& os, AppState& ns)
369 if (std::string_view(os.mMidiInDev.Get()) != ns.mMidiInDev.Get())
return false;
370 if (std::string_view(os.mMidiOutDev.Get()) != ns.mMidiOutDev.Get())
return false;
371 if (os.mMidiInChan != ns.mMidiInChan)
return false;
372 if (os.mMidiOutChan != ns.mMidiOutChan)
return false;
377bool IPlugAPPHost::TryToChangeAudioDriverType()
391 if (mState.mAudioDriverType == kDeviceASIO)
392 mDAC = std::make_unique<RtAudio>(RtAudio::WINDOWS_ASIO);
393 else if (mState.mAudioDriverType == kDeviceDS)
394 mDAC = std::make_unique<RtAudio>(RtAudio::WINDOWS_DS);
396 if (mState.mAudioDriverType == kDeviceCoreAudio)
397 mDAC = std::make_unique<RtAudio>(RtAudio::MACOSX_CORE);
401 #error NOT IMPLEMENTED
406 mDAC->setErrorCallback(ErrorCallback);
413bool IPlugAPPHost::TryToChangeAudio()
421 auto inputID =
GetAudioDeviceID(mState.mAudioDriverType == kDeviceASIO ? mState.mAudioOutDev.Get() : mState.mAudioInDev.Get());
425 #error NOT IMPLEMENTED
429 bool failedToFindDevice =
false;
430 bool resetToDefault =
false;
434 if (mDefaultInputDev)
436 resetToDefault =
true;
437 inputID = mDefaultInputDev;
439 if (mAudioInputDevIDs.size())
443 failedToFindDevice =
true;
448 if (mDefaultOutputDev)
450 resetToDefault =
true;
451 outputID = mDefaultOutputDev;
453 if (mAudioOutputDevIDs.size())
457 failedToFindDevice =
true;
462 DBGMSG(
"Couldn't find previous audio device, reseting to default\n");
466 if (failedToFindDevice)
467 MessageBox(gHWND,
"Please check the audio settings",
"Error", MB_OK);
469 if (inputID && outputID)
471 return InitAudio(inputID.value(), outputID.value(), mState.mAudioSR, mState.mBufferSize);
477bool IPlugAPPHost::SelectMIDIDevice(
ERoute direction,
const char* pPortName)
481 if (direction == ERoute::kInput)
485 mState.mMidiInDev.Set(OFF_TEXT);
493 mMidiIn->closePort();
502 mMidiIn->openPort(port-1);
508 std::string virtualMidiInputName =
"To ";
509 virtualMidiInputName += BUNDLE_NAME;
510 mMidiIn->openVirtualPort(virtualMidiInputName);
515 mMidiIn->openPort(port-2);
519 #error NOT IMPLEMENTED
527 mState.mMidiOutDev.Set(OFF_TEXT);
535 mMidiOut->closePort();
542 mMidiOut->openPort(port-1);
548 std::string virtualMidiOutputName =
"From ";
549 virtualMidiOutputName += BUNDLE_NAME;
550 mMidiOut->openVirtualPort(virtualMidiOutputName);
555 mMidiOut->openPort(port-2);
559 #error NOT IMPLEMENTED
567void IPlugAPPHost::CloseAudio()
569 if (mDAC && mDAC->isStreamOpen())
571 if (mDAC->isStreamRunning())
585bool IPlugAPPHost::InitAudio(uint32_t inID, uint32_t outID, uint32_t sr, uint32_t iovs)
589 RtAudio::StreamParameters iParams, oParams;
590 iParams.deviceId = inID;
591 iParams.nChannels = GetPlug()->
MaxNChannels(ERoute::kInput);
592 iParams.firstChannel = 0;
594 oParams.deviceId = outID;
595 oParams.nChannels = GetPlug()->
MaxNChannels(ERoute::kOutput);
596 oParams.firstChannel = 0;
600 DBGMSG(
"trying to start audio stream @ %i sr, buffer size %i\nindev = %s\noutdev = %s\ninputs = %i\noutputs = %i\n",
603 RtAudio::StreamOptions options;
604 options.flags = RTAUDIO_NONINTERLEAVED;
609 mSampleRate =
static_cast<double>(sr);
611 mAudioEnding =
false;
614 mIPlug->SetBlockSize(APP_SIGNAL_VECTOR_SIZE);
615 mIPlug->SetSampleRate(mSampleRate);
618 auto status = mDAC->openStream(&oParams, iParams.nChannels > 0 ? &iParams :
nullptr, RTAUDIO_FLOAT64, sr, &mBufferSize, &AudioCallback,
this, &options);
620 if (status != RtAudioErrorType::RTAUDIO_NO_ERROR)
622 DBGMSG(
"%s", mDAC->getErrorText().c_str());
626 for (
int i = 0; i < iParams.nChannels; i++)
628 mInputBufPtrs.Add(
nullptr);
631 for (
int i = 0; i < oParams.nChannels; i++)
633 mOutputBufPtrs.Add(
nullptr);
636 if (mDAC->startStream() != RTAUDIO_NO_ERROR)
638 DBGMSG(
"Error starting stream: %s\n", mDAC->getErrorText().c_str());
642 mActiveState = mState;
647bool IPlugAPPHost::InitMidi()
655 mMidiIn = std::make_unique<RtMidiIn>();
657 catch (RtMidiError &error)
660 error.printMessage();
666 mMidiOut = std::make_unique<RtMidiOut>();
668 catch (RtMidiError &error)
671 error.printMessage();
675 mMidiIn->setCallback(&MIDICallback,
this);
676 mMidiIn->ignoreTypes(
false,
true,
false );
681void ApplyFades(
double *pBuffer,
int nChans,
int nFrames,
bool down)
683 for (
int i = 0; i < nChans; i++)
685 double *pIO = pBuffer + (i * nFrames);
689 for (
int j = 0; j < nFrames; j++)
690 pIO[j] *= ((
double) (nFrames - (j + 1)) / (double) nFrames);
694 for (
int j = 0; j < nFrames; j++)
695 pIO[j] *= ((
double) j / (double) nFrames);
701int IPlugAPPHost::AudioCallback(
void* pOutputBuffer,
void* pInputBuffer, uint32_t nFrames,
double streamTime, RtAudioStreamStatus status,
void* pUserData)
705 int nins = _this->GetPlug()->
MaxNChannels(ERoute::kInput);
706 int nouts = _this->GetPlug()->
MaxNChannels(ERoute::kOutput);
708 double* pInputBufferD =
static_cast<double*
>(pInputBuffer);
709 double* pOutputBufferD =
static_cast<double*
>(pOutputBuffer);
711 bool startWait = _this->mVecWait >= APP_N_VECTOR_WAIT;
712 bool doFade = _this->mVecWait == APP_N_VECTOR_WAIT || _this->mAudioEnding;
714 if (startWait && !_this->mAudioDone)
717 ApplyFades(pInputBufferD, nins, nFrames, _this->mAudioEnding);
719 for (
int i = 0; i < nFrames; i++)
721 _this->mBufIndex %= APP_SIGNAL_VECTOR_SIZE;
723 if (_this->mBufIndex == 0)
725 for (
int c = 0; c < nins; c++)
727 _this->mInputBufPtrs.Set(c, (pInputBufferD + (c * nFrames)) + i);
730 for (
int c = 0; c < nouts; c++)
732 _this->mOutputBufPtrs.Set(c, (pOutputBufferD + (c * nFrames)) + i);
735 _this->mIPlug->AppProcess(_this->mInputBufPtrs.GetList(), _this->mOutputBufPtrs.GetList(), APP_SIGNAL_VECTOR_SIZE);
737 _this->mSamplesElapsed += APP_SIGNAL_VECTOR_SIZE;
740 for (
int c = 0; c < nouts; c++)
742 pOutputBufferD[c * nFrames + i] *= APP_MULT;
749 ApplyFades(pOutputBufferD, nouts, nFrames, _this->mAudioEnding);
751 if (_this->mAudioEnding)
752 _this->mAudioDone =
true;
756 memset(pOutputBufferD, 0, nFrames * nouts *
sizeof(
double));
759 _this->mVecWait = std::min(_this->mVecWait + 1, uint32_t(APP_N_VECTOR_WAIT + 1));
765void IPlugAPPHost::MIDICallback(
double deltatime, std::vector<uint8_t>* pMsg,
void* pUserData)
769 if (pMsg->size() == 0 || _this->mExiting)
772 if (pMsg->size() > 3)
774 if (pMsg->size() > MAX_SYSEX_SIZE)
776 DBGMSG(
"SysEx message exceeds MAX_SYSEX_SIZE\n");
780 SysExData data { 0,
static_cast<int>(pMsg->size()), pMsg->data() };
782 _this->mIPlug->mSysExMsgsFromCallback.Push(data);
785 else if (pMsg->size())
788 msg.mStatus = pMsg->at(0);
789 pMsg->size() > 1 ? msg.mData1 = pMsg->at(1) : msg.mData1 = 0;
790 pMsg->size() > 2 ? msg.mData2 = pMsg->at(2) : msg.mData2 = 0;
792 _this->mIPlug->mMidiMsgsFromCallback.Push(msg);
797void IPlugAPPHost::ErrorCallback(RtAudioErrorType type,
const std::string &errorText)
799 std::cerr <<
"\nerrorCallback: " << errorText <<
"\n\n";
IPlug logging a.k.a tracing functionality.
A class that hosts an IPlug as a standalone app and provides Audio/Midi I/O.
int GetMIDIPortNumber(ERoute direction, const char *name) const
std::optional< uint32_t > GetAudioDeviceID(const char *name) const
Returns the a validated audio device ID linked to a particular name.
std::string GetAudioDeviceName(uint32_t deviceID) const
Returns the name of the audio device with a given RTAudio device ID.
bool IsScreenshotMode() const
Check if in screenshot mode.
int MaxNChannels(ERoute direction) const
ERoute
Used to identify whether a bus/channel connection is an input or an output.
Encapsulates a MIDI message and provides helper functions.
This structure is used when queueing Sysex messages.