11#include "IPlugAPP_host.h"
22#define MAX_PATH_LEN 2048
27std::unique_ptr<IPlugAPPHost> IPlugAPPHost::sInstance;
30IPlugAPPHost::IPlugAPPHost()
31: mIPlug(MakePlug(InstanceInfo{this}))
35IPlugAPPHost::~IPlugAPPHost()
42 mMidiIn->cancelCallback();
45 mMidiOut->closePort();
51 sInstance = std::make_unique<IPlugAPPHost>();
52 return sInstance.get();
55bool IPlugAPPHost::Init()
57 mIPlug->SetHost(
"standalone", mIPlug->GetPluginVersion(
false));
62 TryToChangeAudioDriverType();
66 SelectMIDIDevice(ERoute::kInput, mState.mMidiInDev.Get());
67 SelectMIDIDevice(ERoute::kOutput, mState.mMidiOutDev.Get());
69 mIPlug->OnParamReset(kReset);
70 mIPlug->OnActivate(
true);
75bool IPlugAPPHost::OpenWindow(HWND pParent)
77 return mIPlug->OpenWindow(pParent) !=
nullptr;
80void IPlugAPPHost::CloseWindow()
82 mIPlug->CloseWindow();
85bool IPlugAPPHost::InitState()
88 TCHAR strPath[MAX_PATH_LEN];
89 SHGetFolderPathA( NULL, CSIDL_LOCAL_APPDATA, NULL, 0, strPath );
90 mINIPath.SetFormatted(MAX_PATH_LEN,
"%s\\%s\\", strPath, BUNDLE_NAME);
92 mINIPath.SetFormatted(MAX_PATH_LEN,
"%s/Library/Application Support/%s/", getenv(
"HOME"), BUNDLE_NAME);
94 #error NOT IMPLEMENTED
99 if (stat(mINIPath.Get(), &st) == 0)
101 mINIPath.Append(
"settings.ini");
105 if (stat(mINIPath.Get(), &st) == 0)
107 DBGMSG(
"Reading ini file from %s\n", mINIPath.Get());
109 mState.mAudioDriverType = GetPrivateProfileInt(
"audio",
"driver", 0, mINIPath.Get());
111 GetPrivateProfileString(
"audio",
"indev",
"Built-in Input", buf, STRBUFSZ, mINIPath.Get()); mState.mAudioInDev.Set(buf);
112 GetPrivateProfileString(
"audio",
"outdev",
"Built-in Output", buf, STRBUFSZ, mINIPath.Get()); mState.mAudioOutDev.Set(buf);
115 mState.mAudioInChanL = GetPrivateProfileInt(
"audio",
"in1", 1, mINIPath.Get());
116 mState.mAudioInChanR = GetPrivateProfileInt(
"audio",
"in2", 2, mINIPath.Get());
117 mState.mAudioOutChanL = GetPrivateProfileInt(
"audio",
"out1", 1, mINIPath.Get());
118 mState.mAudioOutChanR = GetPrivateProfileInt(
"audio",
"out2", 2, mINIPath.Get());
121 mState.mBufferSize = GetPrivateProfileInt(
"audio",
"buffer", 512, mINIPath.Get());
122 mState.mAudioSR = GetPrivateProfileInt(
"audio",
"sr", 44100, mINIPath.Get());
125 GetPrivateProfileString(
"midi",
"indev",
"no input", buf, STRBUFSZ, mINIPath.Get()); mState.mMidiInDev.Set(buf);
126 GetPrivateProfileString(
"midi",
"outdev",
"no output", buf, STRBUFSZ, mINIPath.Get()); mState.mMidiOutDev.Set(buf);
128 mState.mMidiInChan = GetPrivateProfileInt(
"midi",
"inchan", 0, mINIPath.Get());
129 mState.mMidiOutChan = GetPrivateProfileInt(
"midi",
"outchan", 0, mINIPath.Get());
139 CreateDirectory(mINIPath.Get(), NULL);
140 mINIPath.Append(
"settings.ini");
143 mode_t process_mask = umask(0);
144 int result_code = mkdir(mINIPath.Get(), S_IRWXU | S_IRWXG | S_IRWXO);
149 mINIPath.Append(
"settings.ini");
157 #error NOT IMPLEMENTED
164void IPlugAPPHost::UpdateINI()
167 const char* ini = mINIPath.Get();
169 sprintf(buf,
"%u", mState.mAudioDriverType);
170 WritePrivateProfileString(
"audio",
"driver", buf, ini);
172 WritePrivateProfileString(
"audio",
"indev", mState.mAudioInDev.Get(), ini);
173 WritePrivateProfileString(
"audio",
"outdev", mState.mAudioOutDev.Get(), ini);
175 sprintf(buf,
"%u", mState.mAudioInChanL);
176 WritePrivateProfileString(
"audio",
"in1", buf, ini);
177 sprintf(buf,
"%u", mState.mAudioInChanR);
178 WritePrivateProfileString(
"audio",
"in2", buf, ini);
179 sprintf(buf,
"%u", mState.mAudioOutChanL);
180 WritePrivateProfileString(
"audio",
"out1", buf, ini);
181 sprintf(buf,
"%u", mState.mAudioOutChanR);
182 WritePrivateProfileString(
"audio",
"out2", buf, ini);
187 str.SetFormatted(32,
"%i", mState.mBufferSize);
188 WritePrivateProfileString(
"audio",
"buffer", str.Get(), ini);
190 str.SetFormatted(32,
"%i", mState.mAudioSR);
191 WritePrivateProfileString(
"audio",
"sr", str.Get(), ini);
193 WritePrivateProfileString(
"midi",
"indev", mState.mMidiInDev.Get(), ini);
194 WritePrivateProfileString(
"midi",
"outdev", mState.mMidiOutDev.Get(), ini);
196 sprintf(buf,
"%u", mState.mMidiInChan);
197 WritePrivateProfileString(
"midi",
"inchan", buf, ini);
198 sprintf(buf,
"%u", mState.mMidiOutChan);
199 WritePrivateProfileString(
"midi",
"outchan", buf, ini);
204 auto str = mDAC->getDeviceInfo(deviceID).name;
205 std::size_t pos = str.find(
':');
207 if (pos != std::string::npos)
209 std::string subStr = str.substr(pos + 1);
220 auto deviceIDs = mDAC->getDeviceIds();
222 for (
auto deviceID : deviceIDs)
226 if (std::string_view(deviceNameToTest) == name)
239 auto nameStrView = std::string_view(nameToTest);
241 if (direction == ERoute::kInput)
243 if (nameStrView == OFF_TEXT)
return 0;
247 if (nameStrView ==
"virtual input")
return 1;
250 for (
int i = 0; i < mMidiIn->getPortCount(); i++)
252 if (nameStrView == mMidiIn->getPortName(i).c_str())
258 if (nameStrView == OFF_TEXT)
return 0;
262 if (nameStrView ==
"virtual output")
return 1;
265 for (
int i = 0; i < mMidiOut->getPortCount(); i++)
267 if (nameStrView == mMidiOut->getPortName(i).c_str())
275void IPlugAPPHost::ProbeAudioIO()
277 DBGMSG(
"\nRtAudio Version %s", RtAudio::getVersion().c_str());
279 RtAudio::DeviceInfo info;
281 mAudioInputDevIDs.clear();
282 mAudioOutputDevIDs.clear();
284 auto deviceIDs = mDAC->getDeviceIds();
286 for (
auto deviceID : deviceIDs)
288 info = mDAC->getDeviceInfo(deviceID);
290 if (info.inputChannels > 0)
292 mAudioInputDevIDs.push_back(deviceID);
295 if (info.outputChannels > 0)
297 mAudioOutputDevIDs.push_back(deviceID);
300 if (info.isDefaultInput)
302 mDefaultInputDev = deviceID;
305 if (info.isDefaultOutput)
307 mDefaultOutputDev = deviceID;
312void IPlugAPPHost::ProbeMidiIO()
314 if (!mMidiIn || !mMidiOut)
318 int nInputPorts = mMidiIn->getPortCount();
320 mMidiInputDevNames.push_back(OFF_TEXT);
323 mMidiInputDevNames.push_back(
"virtual input");
326 for (
int i=0; i<nInputPorts; i++)
328 mMidiInputDevNames.push_back(mMidiIn->getPortName(i));
331 int nOutputPorts = mMidiOut->getPortCount();
333 mMidiOutputDevNames.push_back(OFF_TEXT);
336 mMidiOutputDevNames.push_back(
"virtual output");
339 for (
int i=0; i<nOutputPorts; i++)
341 mMidiOutputDevNames.push_back(mMidiOut->getPortName(i));
347bool IPlugAPPHost::AudioSettingsInStateAreEqual(AppState& os, AppState& ns)
349 if (os.mAudioDriverType != ns.mAudioDriverType)
return false;
350 if (std::string_view(os.mAudioInDev.Get()) != ns.mAudioInDev.Get())
return false;
351 if (std::string_view(os.mAudioOutDev.Get()) != ns.mAudioOutDev.Get())
return false;
352 if (os.mAudioSR != ns.mAudioSR)
return false;
353 if (os.mBufferSize != ns.mBufferSize)
return false;
354 if (os.mAudioInChanL != ns.mAudioInChanL)
return false;
355 if (os.mAudioInChanR != ns.mAudioInChanR)
return false;
356 if (os.mAudioOutChanL != ns.mAudioOutChanL)
return false;
357 if (os.mAudioOutChanR != ns.mAudioOutChanR)
return false;
363bool IPlugAPPHost::MIDISettingsInStateAreEqual(AppState& os, AppState& ns)
365 if (std::string_view(os.mMidiInDev.Get()) != ns.mMidiInDev.Get())
return false;
366 if (std::string_view(os.mMidiOutDev.Get()) != ns.mMidiOutDev.Get())
return false;
367 if (os.mMidiInChan != ns.mMidiInChan)
return false;
368 if (os.mMidiOutChan != ns.mMidiOutChan)
return false;
373bool IPlugAPPHost::TryToChangeAudioDriverType()
383 if (mState.mAudioDriverType == kDeviceASIO)
384 mDAC = std::make_unique<RtAudio>(RtAudio::WINDOWS_ASIO);
385 else if (mState.mAudioDriverType == kDeviceDS)
386 mDAC = std::make_unique<RtAudio>(RtAudio::WINDOWS_DS);
388 if (mState.mAudioDriverType == kDeviceCoreAudio)
389 mDAC = std::make_unique<RtAudio>(RtAudio::MACOSX_CORE);
393 #error NOT IMPLEMENTED
398 mDAC->setErrorCallback(ErrorCallback);
405bool IPlugAPPHost::TryToChangeAudio()
409 auto inputID =
GetAudioDeviceID(mState.mAudioDriverType == kDeviceASIO ? mState.mAudioOutDev.Get() : mState.mAudioInDev.Get());
413 #error NOT IMPLEMENTED
417 bool failedToFindDevice =
false;
418 bool resetToDefault =
false;
422 if (mDefaultInputDev)
424 resetToDefault =
true;
425 inputID = mDefaultInputDev;
427 if (mAudioInputDevIDs.size())
431 failedToFindDevice =
true;
436 if (mDefaultOutputDev)
438 resetToDefault =
true;
439 outputID = mDefaultOutputDev;
441 if (mAudioOutputDevIDs.size())
445 failedToFindDevice =
true;
450 DBGMSG(
"Couldn't find previous audio device, reseting to default\n");
454 if (failedToFindDevice)
455 MessageBox(gHWND,
"Please check the audio settings",
"Error", MB_OK);
457 if (inputID && outputID)
459 return InitAudio(inputID.value(), outputID.value(), mState.mAudioSR, mState.mBufferSize);
465bool IPlugAPPHost::SelectMIDIDevice(
ERoute direction,
const char* pPortName)
469 if (direction == ERoute::kInput)
473 mState.mMidiInDev.Set(OFF_TEXT);
481 mMidiIn->closePort();
490 mMidiIn->openPort(port-1);
496 std::string virtualMidiInputName =
"To ";
497 virtualMidiInputName += BUNDLE_NAME;
498 mMidiIn->openVirtualPort(virtualMidiInputName);
503 mMidiIn->openPort(port-2);
507 #error NOT IMPLEMENTED
515 mState.mMidiOutDev.Set(OFF_TEXT);
523 mMidiOut->closePort();
530 mMidiOut->openPort(port-1);
536 std::string virtualMidiOutputName =
"From ";
537 virtualMidiOutputName += BUNDLE_NAME;
538 mMidiOut->openVirtualPort(virtualMidiOutputName);
543 mMidiOut->openPort(port-2);
547 #error NOT IMPLEMENTED
555void IPlugAPPHost::CloseAudio()
557 if (mDAC && mDAC->isStreamOpen())
559 if (mDAC->isStreamRunning())
573bool IPlugAPPHost::InitAudio(uint32_t inID, uint32_t outID, uint32_t sr, uint32_t iovs)
577 RtAudio::StreamParameters iParams, oParams;
578 iParams.deviceId = inID;
579 iParams.nChannels = GetPlug()->
MaxNChannels(ERoute::kInput);
580 iParams.firstChannel = 0;
582 oParams.deviceId = outID;
583 oParams.nChannels = GetPlug()->
MaxNChannels(ERoute::kOutput);
584 oParams.firstChannel = 0;
588 DBGMSG(
"trying to start audio stream @ %i sr, buffer size %i\nindev = %s\noutdev = %s\ninputs = %i\noutputs = %i\n",
591 RtAudio::StreamOptions options;
592 options.flags = RTAUDIO_NONINTERLEAVED;
597 mSampleRate =
static_cast<double>(sr);
599 mAudioEnding =
false;
602 mIPlug->SetBlockSize(APP_SIGNAL_VECTOR_SIZE);
603 mIPlug->SetSampleRate(mSampleRate);
606 auto status = mDAC->openStream(&oParams, iParams.nChannels > 0 ? &iParams :
nullptr, RTAUDIO_FLOAT64, sr, &mBufferSize, &AudioCallback,
this, &options);
608 if (status != RtAudioErrorType::RTAUDIO_NO_ERROR)
610 DBGMSG(
"%s", mDAC->getErrorText().c_str());
614 for (
int i = 0; i < iParams.nChannels; i++)
616 mInputBufPtrs.Add(
nullptr);
619 for (
int i = 0; i < oParams.nChannels; i++)
621 mOutputBufPtrs.Add(
nullptr);
624 if (mDAC->startStream() != RTAUDIO_NO_ERROR)
626 DBGMSG(
"Error starting stream: %s\n", mDAC->getErrorText().c_str());
630 mActiveState = mState;
635bool IPlugAPPHost::InitMidi()
639 mMidiIn = std::make_unique<RtMidiIn>();
641 catch (RtMidiError &error)
644 error.printMessage();
650 mMidiOut = std::make_unique<RtMidiOut>();
652 catch (RtMidiError &error)
655 error.printMessage();
659 mMidiIn->setCallback(&MIDICallback,
this);
660 mMidiIn->ignoreTypes(
false,
true,
false );
665void ApplyFades(
double *pBuffer,
int nChans,
int nFrames,
bool down)
667 for (
int i = 0; i < nChans; i++)
669 double *pIO = pBuffer + (i * nFrames);
673 for (
int j = 0; j < nFrames; j++)
674 pIO[j] *= ((
double) (nFrames - (j + 1)) / (double) nFrames);
678 for (
int j = 0; j < nFrames; j++)
679 pIO[j] *= ((
double) j / (double) nFrames);
685int IPlugAPPHost::AudioCallback(
void* pOutputBuffer,
void* pInputBuffer, uint32_t nFrames,
double streamTime, RtAudioStreamStatus status,
void* pUserData)
689 int nins = _this->GetPlug()->
MaxNChannels(ERoute::kInput);
690 int nouts = _this->GetPlug()->
MaxNChannels(ERoute::kOutput);
692 double* pInputBufferD =
static_cast<double*
>(pInputBuffer);
693 double* pOutputBufferD =
static_cast<double*
>(pOutputBuffer);
695 bool startWait = _this->mVecWait >= APP_N_VECTOR_WAIT;
696 bool doFade = _this->mVecWait == APP_N_VECTOR_WAIT || _this->mAudioEnding;
698 if (startWait && !_this->mAudioDone)
701 ApplyFades(pInputBufferD, nins, nFrames, _this->mAudioEnding);
703 for (
int i = 0; i < nFrames; i++)
705 _this->mBufIndex %= APP_SIGNAL_VECTOR_SIZE;
707 if (_this->mBufIndex == 0)
709 for (
int c = 0; c < nins; c++)
711 _this->mInputBufPtrs.Set(c, (pInputBufferD + (c * nFrames)) + i);
714 for (
int c = 0; c < nouts; c++)
716 _this->mOutputBufPtrs.Set(c, (pOutputBufferD + (c * nFrames)) + i);
719 _this->mIPlug->AppProcess(_this->mInputBufPtrs.GetList(), _this->mOutputBufPtrs.GetList(), APP_SIGNAL_VECTOR_SIZE);
721 _this->mSamplesElapsed += APP_SIGNAL_VECTOR_SIZE;
724 for (
int c = 0; c < nouts; c++)
726 pOutputBufferD[c * nFrames + i] *= APP_MULT;
733 ApplyFades(pOutputBufferD, nouts, nFrames, _this->mAudioEnding);
735 if (_this->mAudioEnding)
736 _this->mAudioDone =
true;
740 memset(pOutputBufferD, 0, nFrames * nouts *
sizeof(
double));
743 _this->mVecWait = std::min(_this->mVecWait + 1, uint32_t(APP_N_VECTOR_WAIT + 1));
749void IPlugAPPHost::MIDICallback(
double deltatime, std::vector<uint8_t>* pMsg,
void* pUserData)
753 if (pMsg->size() == 0 || _this->mExiting)
756 if (pMsg->size() > 3)
758 if (pMsg->size() > MAX_SYSEX_SIZE)
760 DBGMSG(
"SysEx message exceeds MAX_SYSEX_SIZE\n");
764 SysExData data { 0,
static_cast<int>(pMsg->size()), pMsg->data() };
766 _this->mIPlug->mSysExMsgsFromCallback.Push(data);
769 else if (pMsg->size())
772 msg.mStatus = pMsg->at(0);
773 pMsg->size() > 1 ? msg.mData1 = pMsg->at(1) : msg.mData1 = 0;
774 pMsg->size() > 2 ? msg.mData2 = pMsg->at(2) : msg.mData2 = 0;
776 _this->mIPlug->mMidiMsgsFromCallback.Push(msg);
781void IPlugAPPHost::ErrorCallback(RtAudioErrorType type,
const std::string &errorText)
783 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.
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.