diff --git a/src/client.h b/src/client.h index 7412582618..3bfa3f0139 100644 --- a/src/client.h +++ b/src/client.h @@ -286,10 +286,14 @@ class CClient : public QObject Channel.GetBufErrorRates ( vecErrRates, dLimit, dMaxUpLimit ); } - //### TODO: BEGIN ###// - // Refactor this to use signal/slot mechanism. https://github.com/jamulussoftware/jamulus/pull/3479/files#r1976382416 + // ### TODO: BEGIN ###// + // Refactor this to use signal/slot mechanism. https://github.com/jamulussoftware/jamulus/pull/3479/files#r1976382416 CProtocol* getConnLessProtocol() { return &ConnLessProtocol; } - //### TODO: END ###// + // ### TODO: END ###// + + // MIDI control + void EnableMIDI ( bool bEnable ) { Sound.EnableMIDI ( bEnable ); } + bool IsMIDIEnabled() const { return Sound.IsMIDIEnabled(); } // settings CChannelCoreInfo ChannelInfo; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 7ae940a0bf..18423b771d 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -537,6 +537,8 @@ CClientDlg::CClientDlg ( CClient* pNCliP, QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::NumMixerPanelRowsChanged, this, &CClientDlg::OnNumMixerPanelRowsChanged ); + QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::MIDIControllerUsageChanged, this, &CClientDlg::OnMIDIControllerUsageChanged ); + QObject::connect ( this, &CClientDlg::SendTabChange, &ClientSettingsDlg, &CClientSettingsDlg::OnMakeTabChange ); QObject::connect ( MainMixerBoard, &CAudioMixerBoard::ChangeChanGain, this, &CClientDlg::OnChangeChanGain ); @@ -1287,11 +1289,11 @@ void CClientDlg::Disconnect() TimerDetectFeedback.stop(); bDetectFeedback = false; - //### TODO: BEGIN ###// - // is this still required??? - // immediately update status bar + // ### TODO: BEGIN ###// + // is this still required??? + // immediately update status bar OnTimerStatus(); - //### TODO: END ###// + // ### TODO: END ###// // reset LEDs ledBuffers->Reset(); @@ -1519,3 +1521,12 @@ void CClientDlg::SetPingTime ( const int iPingTime, const int iOverallDelayMs, c } void CClientDlg::OnOpenMidiSettings() { ShowGeneralSettings ( SETTING_TAB_MIDI ); } + +void CClientDlg::OnMIDIControllerUsageChanged ( bool bEnabled ) +{ + // Update the mixer board's MIDI flag to trigger proper user numbering display + MainMixerBoard->SetMIDICtrlUsed ( bEnabled ); + + // Enable/disable runtime MIDI via the sound interface through the public CClient interface + pClient->EnableMIDI ( bEnabled ); +} diff --git a/src/clientdlg.h b/src/clientdlg.h index a3910a18f0..52aa968cd7 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -244,6 +244,7 @@ public slots: void accept() { close(); } // introduced by pljones void OnOpenMidiSettings(); + void OnMIDIControllerUsageChanged ( bool bEnabled ); signals: void SendTabChange ( int iTabIdx ); diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 0932aef359..6d130126fd 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -399,11 +399,15 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet chbAudioAlerts->setAccessibleName ( tr ( "Audio Alerts check box" ) ); // MIDI settings + chbUseMIDIController->setWhatsThis ( tr ( "Enable/disable MIDI-in port" ) ); + + chbUseMIDIController->setAccessibleName ( tr ( "Enable or disable MIDI-in port check box" ) ); + QString strMidiSettings = "" + tr ( "MIDI controller settings" ) + ": " + tr ( "There is one global MIDI channel parameter (1-16) and two parameters you can set " - "for each item controlled: offset and consecutive CC numbers (count). First set the " + "for each item controlled: First MIDI CC and consecutive CC numbers (count). First set the " "channel you want Jamulus to listen on (0 for all channels). Then, for each item " - "you want to control (volume fader, pan, solo, mute), set the offset (CC number " + "you want to control (volume fader, pan, solo, mute), set the first MIDI CC (CC number " "to start from) and number of consecutive CC numbers (count). There is one " "exception that does not require establishing consecutive CC numbers which is " "the “Mute Myself” parameter - it only requires a single CC number as it is only " @@ -412,6 +416,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet tr ( "You can either type in the MIDI CC values or use the \"Learn\" button: click on " "\"Learn\", move the fader/knob on your MIDI controller, and the MIDI CC number " "will be saved." ); + lblChannel->setWhatsThis ( strMidiSettings ); lblMuteMyself->setWhatsThis ( strMidiSettings ); faderGroup->setWhatsThis ( strMidiSettings ); @@ -835,6 +840,22 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet ApplyMIDIMappingFromSettings(); } ); + // Connect MIDI controller checkbox + QObject::connect ( chbUseMIDIController, &QCheckBox::toggled, this, [this] ( bool checked ) { + pSettings->bUseMIDIController = checked; + + if ( checked ) + { + pClient->ApplyMIDIMapping ( pSettings->GetMIDIMapString() ); + } + else + { + pClient->ApplyMIDIMapping ( "" ); + } + + emit MIDIControllerUsageChanged ( checked ); + } ); + // MIDI Learn buttons midiLearnButtons[0] = butLearnMuteMyself; midiLearnButtons[1] = butLearnFaderOffset; @@ -886,6 +907,7 @@ void CClientSettingsDlg::showEvent ( QShowEvent* event ) spnSoloCount->setValue ( pSettings->midiSoloCount ); spnMuteOffset->setValue ( pSettings->midiMuteOffset ); spnMuteCount->setValue ( pSettings->midiMuteCount ); + chbUseMIDIController->setChecked ( pSettings->bUseMIDIController ); QDialog::showEvent ( event ); } @@ -1331,7 +1353,19 @@ void CClientSettingsDlg::OnAudioPanValueChanged ( int value ) UpdateAudioFaderSlider(); } -void CClientSettingsDlg::ApplyMIDIMappingFromSettings() { pClient->ApplyMIDIMapping ( pSettings->GetMIDIMapString() ); } +void CClientSettingsDlg::ApplyMIDIMappingFromSettings() +{ + // Only apply MIDI mapping if the controller is enabled + if ( pSettings->bUseMIDIController ) + { + pClient->ApplyMIDIMapping ( pSettings->GetMIDIMapString() ); + } + else + { + // If disabled, ensure no MIDI mapping is applied + pClient->ApplyMIDIMapping ( "" ); + } +} void CClientSettingsDlg::ResetMidiLearn() { diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 71373200f8..3f436dbce5 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -120,6 +120,7 @@ public slots: void AudioChannelsChanged(); void CustomDirectoriesChanged(); void NumMixerPanelRowsChanged ( int value ); + void MIDIControllerUsageChanged ( bool bEnabled ); private: enum MidiLearnTarget diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index d76acd0112..fc6e64fbbd 100644 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -1341,6 +1341,13 @@ MIDI + + + + Enable MIDI-in port + + + @@ -1377,12 +1384,18 @@ - Channel + MIDI Channel + + + 55 + 0 + + 50 @@ -1422,6 +1435,12 @@ + + + 65 + 0 + + 50 @@ -1499,12 +1518,18 @@ - Offset + First MIDI CC + + + 65 + 0 + + 50 @@ -1560,6 +1585,12 @@ + + + 65 + 0 + + 50 @@ -1624,12 +1655,18 @@ - Offset + First MIDI CC + + + 65 + 0 + + 50 @@ -1685,6 +1722,12 @@ + + + 65 + 0 + + 50 @@ -1749,12 +1792,18 @@ - Offset + First MIDI CC + + + 65 + 0 + + 50 @@ -1810,6 +1859,12 @@ + + + 65 + 0 + + 50 @@ -1874,12 +1929,18 @@ - Offset + First MIDI CC + + + 65 + 0 + + 50 @@ -1935,6 +1996,12 @@ + + + 65 + 0 + + 50 @@ -1995,8 +2062,8 @@ butDriverSetup cbxLInChan cbxRInChan - cbxLOutChan cbxROutChan + cbxLOutChan cbxAudioChannels cbxAudioQuality rbtBufferDelayPreferred @@ -2012,6 +2079,7 @@ cbxInputBoost chbDetectFeedback sldAudioPan + chbUseMIDIController spnChannel spnMuteMyself butLearnMuteMyself diff --git a/src/main.cpp b/src/main.cpp index d011c36916..039a510ac9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -662,14 +662,12 @@ int main ( int argc, char** argv ) if ( !strServerListFileName.isEmpty() ) { - qInfo() << "Note:" - << "Server list persistence file will only take effect when running as a directory."; + qInfo() << "Note:" << "Server list persistence file will only take effect when running as a directory."; } if ( !strServerListFilter.isEmpty() ) { - qInfo() << "Note:" - << "Server list filter will only take effect when running as a directory."; + qInfo() << "Note:" << "Server list filter will only take effect when running as a directory."; } } else @@ -821,7 +819,7 @@ int main ( int argc, char** argv ) bIsClient = true; // Client only - TODO: maybe a switch in interface to change to server? // bUseMultithreading = true; - QApplication* pApp = new QApplication ( argc, argv ); + QApplication* pApp = new QApplication ( argc, argv ); # else QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv ); # endif @@ -877,7 +875,7 @@ int main ( int argc, char** argv ) qWarning() << "No JSON-RPC support in this build."; } #else - CRpcServer* pRpcServer = nullptr; + CRpcServer* pRpcServer = nullptr; if ( iJsonRpcPortNumber != INVALID_PORT ) { @@ -920,17 +918,11 @@ int main ( int argc, char** argv ) #ifndef SERVER_ONLY if ( bIsClient ) { - if ( strMIDISetup.isEmpty() ) - { - CClientSettings tmpSettings ( nullptr, strIniFileName ); - tmpSettings.Load ( CommandLineOptions ); - strMIDISetup = tmpSettings.GetMIDIMapString(); - } - + // Create client with empty MIDI string initially (safer initialization) CClient Client ( iPortNumber, iQosNumber, strConnOnStartupAddress, - strMIDISetup, + "", // Always start with empty MIDI bNoAutoJackConnect, strClientName, bEnableIPv6, @@ -940,6 +932,7 @@ int main ( int argc, char** argv ) CClientSettings Settings ( &Client, strIniFileName ); Settings.Load ( CommandLineOptions ); + // Parse command line MIDI parameters if provided if ( !strMIDISetup.isEmpty() ) { QStringList slMIDIParams = strMIDISetup.split ( ";" ); @@ -992,6 +985,14 @@ int main ( int argc, char** argv ) } } } + + // Enable MIDI controller and apply settings when command line MIDI is provided + Settings.bUseMIDIController = true; + Client.ApplyMIDIMapping ( Settings.GetMIDIMapString() ); + } + else if ( Settings.bUseMIDIController ) + { + Client.ApplyMIDIMapping ( Settings.GetMIDIMapString() ); } # ifndef NO_JSON_RPC diff --git a/src/settings.cpp b/src/settings.cpp index 7cf45dde23..8f4748a7fe 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -289,231 +289,232 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, bEnableAudioAlerts = bValue; } - // MIDI settings - if ( GetNumericIniSet ( IniXMLDocument, "client", "midichannel", 0, 16, iValue ) ) + // name + pClient->ChannelInfo.strName = FromBase64ToString ( + GetIniSetting ( IniXMLDocument, "client", "name_base64", ToBase64 ( QCoreApplication::translate ( "CMusProfDlg", "No Name" ) ) ) ); + + // instrument + if ( GetNumericIniSet ( IniXMLDocument, "client", "instrument", 0, CInstPictures::GetNumAvailableInst() - 1, iValue ) ) { - midiChannel = iValue; + pClient->ChannelInfo.iInstrument = iValue; } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midifaderoffset", 0, 127, iValue ) ) + // country + if ( GetNumericIniSet ( IniXMLDocument, "client", "country", 0, static_cast ( QLocale::LastCountry ), iValue ) ) { - midiFaderOffset = iValue; + pClient->ChannelInfo.eCountry = CLocale::WireFormatCountryCodeToQtCountry ( iValue ); } - - if ( GetNumericIniSet ( IniXMLDocument, "client", "midifadercount", 0, 127, iValue ) ) + else { - midiFaderCount = iValue; + // if no country is given, use the one from the operating system + pClient->ChannelInfo.eCountry = QLocale::system().country(); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midipanoffset", 0, 127, iValue ) ) + // city + pClient->ChannelInfo.strCity = FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "city_base64" ) ); + + // skill level + if ( GetNumericIniSet ( IniXMLDocument, "client", "skill", 0, 3 /* SL_PROFESSIONAL */, iValue ) ) { - midiPanOffset = iValue; + pClient->ChannelInfo.eSkillLevel = static_cast ( iValue ); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midipancount", 0, 127, iValue ) ) + // audio fader + if ( GetNumericIniSet ( IniXMLDocument, "client", "audfad", AUD_FADER_IN_MIN, AUD_FADER_IN_MAX, iValue ) ) { - midiPanCount = iValue; + pClient->SetAudioInFader ( iValue ); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midisolooffset", 0, 127, iValue ) ) + + // reverberation level + if ( GetNumericIniSet ( IniXMLDocument, "client", "revlev", 0, AUD_REVERB_MAX, iValue ) ) { - midiSoloOffset = iValue; + pClient->SetReverbLevel ( iValue ); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midisolocount", 0, 127, iValue ) ) + // reverberation channel assignment + if ( GetFlagIniSet ( IniXMLDocument, "client", "reverblchan", bValue ) ) { - midiSoloCount = iValue; + pClient->SetReverbOnLeftChan ( bValue ); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midimuteoffset", 0, 127, iValue ) ) + // sound card selection + const QString strError = pClient->SetSndCrdDev ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "auddev_base64", "" ) ) ); + + if ( !strError.isEmpty() ) { - midiMuteOffset = iValue; +# ifndef HEADLESS + // special case: when settings are loaded no GUI is yet created, therefore + // we have to create a warning message box here directly + QMessageBox::warning ( nullptr, APP_NAME, strError ); +# endif } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midimutecount", 0, 127, iValue ) ) + // sound card channel mapping settings: make sure these settings are + // set AFTER the sound card device is set, otherwise the settings are + // overwritten by the defaults + // + // sound card left input channel mapping + if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdinlch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) { - midiMuteCount = iValue; + pClient->SetSndCrdLeftInputChannel ( iValue ); } - if ( GetNumericIniSet ( IniXMLDocument, "client", "midimutemyself", 0, 127, iValue ) ) + // sound card right input channel mapping + if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdinrch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) { - midiMuteMyself = iValue; + pClient->SetSndCrdRightInputChannel ( iValue ); } - // Code that uses pClient is guarded. MIDI settings in particular need to be decoupled from - // the Client state to enable opening the MIDI-in port without the --ctrlmidich pàrameter. - if ( pClient ) + // sound card left output channel mapping + if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdoutlch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) { - // name - pClient->ChannelInfo.strName = FromBase64ToString ( - GetIniSetting ( IniXMLDocument, "client", "name_base64", ToBase64 ( QCoreApplication::translate ( "CMusProfDlg", "No Name" ) ) ) ); + pClient->SetSndCrdLeftOutputChannel ( iValue ); + } - // instrument - if ( GetNumericIniSet ( IniXMLDocument, "client", "instrument", 0, CInstPictures::GetNumAvailableInst() - 1, iValue ) ) - { - pClient->ChannelInfo.iInstrument = iValue; - } + // sound card right output channel mapping + if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdoutrch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) + { + pClient->SetSndCrdRightOutputChannel ( iValue ); + } - // country - if ( GetNumericIniSet ( IniXMLDocument, "client", "country", 0, static_cast ( QLocale::LastCountry ), iValue ) ) - { - pClient->ChannelInfo.eCountry = CLocale::WireFormatCountryCodeToQtCountry ( iValue ); - } - else + // sound card preferred buffer size index + if ( GetNumericIniSet ( IniXMLDocument, "client", "prefsndcrdbufidx", FRAME_SIZE_FACTOR_PREFERRED, FRAME_SIZE_FACTOR_SAFE, iValue ) ) + { + // additional check required since only a subset of factors are + // defined + if ( ( iValue == FRAME_SIZE_FACTOR_PREFERRED ) || ( iValue == FRAME_SIZE_FACTOR_DEFAULT ) || ( iValue == FRAME_SIZE_FACTOR_SAFE ) ) { - // if no country is given, use the one from the operating system - pClient->ChannelInfo.eCountry = QLocale::system().country(); + pClient->SetSndCrdPrefFrameSizeFactor ( iValue ); } + } - // city - pClient->ChannelInfo.strCity = FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "city_base64" ) ); - - // skill level - if ( GetNumericIniSet ( IniXMLDocument, "client", "skill", 0, 3 /* SL_PROFESSIONAL */, iValue ) ) - { - pClient->ChannelInfo.eSkillLevel = static_cast ( iValue ); - } + // automatic network jitter buffer size setting + if ( GetFlagIniSet ( IniXMLDocument, "client", "autojitbuf", bValue ) ) + { + pClient->SetDoAutoSockBufSize ( bValue ); + } - // audio fader - if ( GetNumericIniSet ( IniXMLDocument, "client", "audfad", AUD_FADER_IN_MIN, AUD_FADER_IN_MAX, iValue ) ) - { - pClient->SetAudioInFader ( iValue ); - } + // network jitter buffer size + if ( GetNumericIniSet ( IniXMLDocument, "client", "jitbuf", MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL, iValue ) ) + { + pClient->SetSockBufNumFrames ( iValue ); + } - // reverberation level - if ( GetNumericIniSet ( IniXMLDocument, "client", "revlev", 0, AUD_REVERB_MAX, iValue ) ) - { - pClient->SetReverbLevel ( iValue ); - } + // network jitter buffer size for server + if ( GetNumericIniSet ( IniXMLDocument, "client", "jitbufserver", MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL, iValue ) ) + { + pClient->SetServerSockBufNumFrames ( iValue ); + } - // reverberation channel assignment - if ( GetFlagIniSet ( IniXMLDocument, "client", "reverblchan", bValue ) ) - { - pClient->SetReverbOnLeftChan ( bValue ); - } + // enable OPUS64 setting + if ( GetFlagIniSet ( IniXMLDocument, "client", "enableopussmall", bValue ) ) + { + pClient->SetEnableOPUS64 ( bValue ); + } - // sound card selection - const QString strError = pClient->SetSndCrdDev ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "auddev_base64", "" ) ) ); + // GUI design + if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) + { + pClient->SetGUIDesign ( static_cast ( iValue ) ); + } - if ( !strError.isEmpty() ) + // MeterStyle + if ( GetNumericIniSet ( IniXMLDocument, "client", "meterstyle", 0, 4 /* MT_LED_ROUND_BIG */, iValue ) ) + { + pClient->SetMeterStyle ( static_cast ( iValue ) ); + } + else + { + // if MeterStyle is not found in the ini, set it based on the GUI design + if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) { -# ifndef HEADLESS - // special case: when settings are loaded no GUI is yet created, therefore - // we have to create a warning message box here directly - QMessageBox::warning ( nullptr, APP_NAME, strError ); -# endif - } + switch ( iValue ) + { + case GD_STANDARD: + pClient->SetMeterStyle ( MT_BAR_WIDE ); + break; - // sound card channel mapping settings: make sure these settings are - // set AFTER the sound card device is set, otherwise the settings are - // overwritten by the defaults - // - // sound card left input channel mapping - if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdinlch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) - { - pClient->SetSndCrdLeftInputChannel ( iValue ); - } + case GD_ORIGINAL: + pClient->SetMeterStyle ( MT_LED_STRIPE ); + break; - // sound card right input channel mapping - if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdinrch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) - { - pClient->SetSndCrdRightInputChannel ( iValue ); - } + case GD_SLIMFADER: + pClient->SetMeterStyle ( MT_BAR_NARROW ); + break; - // sound card left output channel mapping - if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdoutlch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) - { - pClient->SetSndCrdLeftOutputChannel ( iValue ); + default: + pClient->SetMeterStyle ( MT_LED_STRIPE ); + break; + } } + } - // sound card right output channel mapping - if ( GetNumericIniSet ( IniXMLDocument, "client", "sndcrdoutrch", 0, MAX_NUM_IN_OUT_CHANNELS - 1, iValue ) ) - { - pClient->SetSndCrdRightOutputChannel ( iValue ); - } + // audio channels + if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 2 /* CC_STEREO */, iValue ) ) + { + pClient->SetAudioChannels ( static_cast ( iValue ) ); + } - // sound card preferred buffer size index - if ( GetNumericIniSet ( IniXMLDocument, "client", "prefsndcrdbufidx", FRAME_SIZE_FACTOR_PREFERRED, FRAME_SIZE_FACTOR_SAFE, iValue ) ) - { - // additional check required since only a subset of factors are - // defined - if ( ( iValue == FRAME_SIZE_FACTOR_PREFERRED ) || ( iValue == FRAME_SIZE_FACTOR_DEFAULT ) || ( iValue == FRAME_SIZE_FACTOR_SAFE ) ) - { - pClient->SetSndCrdPrefFrameSizeFactor ( iValue ); - } - } + // audio quality + if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 2 /* AQ_HIGH */, iValue ) ) + { + pClient->SetAudioQuality ( static_cast ( iValue ) ); + } - // automatic network jitter buffer size setting - if ( GetFlagIniSet ( IniXMLDocument, "client", "autojitbuf", bValue ) ) - { - pClient->SetDoAutoSockBufSize ( bValue ); - } + // MIDI settings + if ( GetNumericIniSet ( IniXMLDocument, "client", "midichannel", 0, 16, iValue ) ) + { + midiChannel = iValue; + } - // network jitter buffer size - if ( GetNumericIniSet ( IniXMLDocument, "client", "jitbuf", MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL, iValue ) ) - { - pClient->SetSockBufNumFrames ( iValue ); - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midifaderoffset", 0, 127, iValue ) ) + { + midiFaderOffset = iValue; + } - // network jitter buffer size for server - if ( GetNumericIniSet ( IniXMLDocument, "client", "jitbufserver", MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL, iValue ) ) - { - pClient->SetServerSockBufNumFrames ( iValue ); - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midifadercount", 0, 127, iValue ) ) + { + midiFaderCount = iValue; + } - // enable OPUS64 setting - if ( GetFlagIniSet ( IniXMLDocument, "client", "enableopussmall", bValue ) ) - { - pClient->SetEnableOPUS64 ( bValue ); - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midipanoffset", 0, 127, iValue ) ) + { + midiPanOffset = iValue; + } - // GUI design - if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) - { - pClient->SetGUIDesign ( static_cast ( iValue ) ); - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midipancount", 0, 127, iValue ) ) + { + midiPanCount = iValue; + } - // MeterStyle - if ( GetNumericIniSet ( IniXMLDocument, "client", "meterstyle", 0, 4 /* MT_LED_ROUND_BIG */, iValue ) ) - { - pClient->SetMeterStyle ( static_cast ( iValue ) ); - } - else - { - // if MeterStyle is not found in the ini, set it based on the GUI design - if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) - { - switch ( iValue ) - { - case GD_STANDARD: - pClient->SetMeterStyle ( MT_BAR_WIDE ); - break; + if ( GetNumericIniSet ( IniXMLDocument, "client", "midisolooffset", 0, 127, iValue ) ) + { + midiSoloOffset = iValue; + } - case GD_ORIGINAL: - pClient->SetMeterStyle ( MT_LED_STRIPE ); - break; + if ( GetNumericIniSet ( IniXMLDocument, "client", "midisolocount", 0, 127, iValue ) ) + { + midiSoloCount = iValue; + } - case GD_SLIMFADER: - pClient->SetMeterStyle ( MT_BAR_NARROW ); - break; + if ( GetNumericIniSet ( IniXMLDocument, "client", "midimuteoffset", 0, 127, iValue ) ) + { + midiMuteOffset = iValue; + } - default: - pClient->SetMeterStyle ( MT_LED_STRIPE ); - break; - } - } - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midimutecount", 0, 127, iValue ) ) + { + midiMuteCount = iValue; + } - // audio channels - if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 2 /* CC_STEREO */, iValue ) ) - { - pClient->SetAudioChannels ( static_cast ( iValue ) ); - } + if ( GetNumericIniSet ( IniXMLDocument, "client", "midimutemyself", 0, 127, iValue ) ) + { + midiMuteMyself = iValue; + } - // audio quality - if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 2 /* AQ_HIGH */, iValue ) ) - { - pClient->SetAudioQuality ( static_cast ( iValue ) ); - } + if ( GetFlagIniSet ( IniXMLDocument, "client", "usemidicontroller", bValue ) ) + { + bUseMIDIController = bValue; } // custom directories @@ -603,7 +604,7 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, } // selected Settings Tab - if ( GetNumericIniSet ( IniXMLDocument, "client", "settingstab", 0, 3, iValue ) ) + if ( GetNumericIniSet ( IniXMLDocument, "client", "settingstab", 0, 2, iValue ) ) { iSettingsTab = iValue; } @@ -611,7 +612,6 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, // fader settings ReadFaderSettingsFromXML ( IniXMLDocument ); } - void CClientSettings::ReadFaderSettingsFromXML ( const QDomDocument& IniXMLDocument ) { int iIdx; @@ -820,6 +820,7 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is SetNumericIniSet ( IniXMLDocument, "client", "midimuteoffset", midiMuteOffset ); SetNumericIniSet ( IniXMLDocument, "client", "midimutecount", midiMuteCount ); SetNumericIniSet ( IniXMLDocument, "client", "midimutemyself", midiMuteMyself ); + SetFlagIniSet ( IniXMLDocument, "client", "usemidicontroller", bUseMIDIController ); // fader settings WriteFaderSettingsToXML ( IniXMLDocument ); diff --git a/src/settings.h b/src/settings.h index 0db6dde0e1..79fce1e5b5 100644 --- a/src/settings.h +++ b/src/settings.h @@ -204,7 +204,7 @@ class CClientSettings : public CSettings // MIDI settings int midiChannel = 0; // Default MIDI channel 0 int midiMuteMyself = 0; - int midiFaderOffset = 70; + int midiFaderOffset = 0; int midiFaderCount = 0; int midiPanOffset = 0; int midiPanCount = 0; @@ -212,6 +212,7 @@ class CClientSettings : public CSettings int midiSoloCount = 0; int midiMuteOffset = 0; int midiMuteCount = 0; + bool bUseMIDIController = false; QString GetMIDIMapString() const; protected: diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index 74044315c4..a8bec59418 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -311,7 +311,7 @@ int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) // query the usable buffer sizes ASIOGetBufferSize ( &HWBufferInfo.lMinSize, &HWBufferInfo.lMaxSize, &HWBufferInfo.lPreferredSize, &HWBufferInfo.lGranularity ); - //### TEST: BEGIN ###// + // ### TEST: BEGIN ###// /* #include QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). @@ -319,12 +319,12 @@ int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) ); _exit(1); */ - //### TEST: END ###// + // ### TEST: END ###// - //### TODO: BEGIN ###// - // see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 - // (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer) - //### TODO: END ###// + // ### TODO: BEGIN ###// + // see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 + // (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer) + // ### TODO: END ###// // calculate "nearest" buffer size and set internal parameter accordingly // first check minimum and maximum values @@ -629,6 +629,30 @@ bool CSound::CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) ); } +void CSound::EnableMIDI ( bool bEnable ) +{ + if ( bEnable ) + { + // Enable MIDI only if it's not already enabled + if ( !bMidiEnabled && iCtrlMIDIChannel != INVALID_MIDI_CH ) + { + Midi.MidiStart(); + bMidiEnabled = true; + } + } + else + { + // Disable MIDI only if it's currently enabled + if ( bMidiEnabled ) + { + Midi.MidiStop(); + bMidiEnabled = false; + } + } +} + +bool CSound::IsMIDIEnabled() const { return bMidiEnabled; } + void CSound::bufferSwitch ( long index, ASIOBool ) { int iCurSample; diff --git a/src/sound/asio/sound.h b/src/sound/asio/sound.h index 12caaf0aa3..7259be2e38 100644 --- a/src/sound/asio/sound.h +++ b/src/sound/asio/sound.h @@ -82,6 +82,10 @@ class CSound : public CSoundBase virtual float GetInOutLatencyMs() { return fInOutLatencyMs; } + // MIDI port toggle + virtual void EnableMIDI ( bool bEnable ); + virtual bool IsMIDIEnabled() const; + protected: virtual QString LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ); virtual void UnloadCurrentDriver(); @@ -138,4 +142,7 @@ class CSound : public CSoundBase // Windows native MIDI support CMidi Midi; + +private: + bool bMidiEnabled = false; // Tracks the runtime state of MIDI }; diff --git a/src/sound/coreaudio-mac/sound.cpp b/src/sound/coreaudio-mac/sound.cpp index 5fc793424c..cfbbe78f00 100644 --- a/src/sound/coreaudio-mac/sound.cpp +++ b/src/sound/coreaudio-mac/sound.cpp @@ -31,6 +31,7 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* const bool, const QString& ) : CSoundBase ( "CoreAudio", fpNewProcessCallback, arg, strMIDISetup ), + midiClient ( static_cast ( NULL ) ), midiInPortRef ( static_cast ( NULL ) ) { // Apple Mailing Lists: Subject: GUI Apps should set kAudioHardwarePropertyRunLoop @@ -60,24 +61,12 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* iSelInputRightChannel = 0; iSelOutputLeftChannel = 0; iSelOutputRightChannel = 0; +} - // Optional MIDI initialization -------------------------------------------- - if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) - { - // create client and ports - MIDIClientRef midiClient = static_cast ( NULL ); - MIDIClientCreate ( CFSTR ( APP_NAME ), NULL, NULL, &midiClient ); - MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), callbackMIDI, this, &midiInPortRef ); - - // open connections from all sources - const int iNMIDISources = MIDIGetNumberOfSources(); - - for ( int i = 0; i < iNMIDISources; i++ ) - { - MIDIEndpointRef src = MIDIGetSource ( i ); - MIDIPortConnectSource ( midiInPortRef, src, NULL ); - } - } +CSound::~CSound() +{ + // Ensure MIDI resources are properly cleaned up + DestroyMIDIPort(); } void CSound::GetAvailableInOutDevices() @@ -718,6 +707,98 @@ void CSound::Stop() CSoundBase::Stop(); } +void CSound::EnableMIDI ( bool bEnable ) +{ + if ( bEnable && ( iCtrlMIDIChannel != INVALID_MIDI_CH ) ) + { + // Create MIDI port if we have valid MIDI channel and no port exists + if ( midiInPortRef == static_cast ( NULL ) ) + { + CreateMIDIPort(); + } + } + else + { + // Destroy MIDI port if it exists + if ( midiInPortRef != static_cast ( NULL ) ) + { + DestroyMIDIPort(); + } + } +} + +bool CSound::IsMIDIEnabled() const { return ( midiInPortRef != static_cast ( NULL ) ); } + +void CSound::CreateMIDIPort() +{ + if ( midiClient == static_cast ( NULL ) ) + { + // Create MIDI client + OSStatus result = MIDIClientCreate ( CFSTR ( APP_NAME ), NULL, NULL, &midiClient ); + if ( result != noErr ) + { + qWarning() << "Failed to create CoreAudio MIDI client. Error code:" << result; + return; + } + } + + if ( midiInPortRef == static_cast ( NULL ) ) + { + // Create MIDI input port + OSStatus result = MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), callbackMIDI, this, &midiInPortRef ); + if ( result != noErr ) + { + qWarning() << "Failed to create CoreAudio MIDI input port. Error code:" << result; + return; + } + + // Connect to all available MIDI sources + const int iNMIDISources = MIDIGetNumberOfSources(); + for ( int i = 0; i < iNMIDISources; i++ ) + { + MIDIEndpointRef src = MIDIGetSource ( i ); + MIDIPortConnectSource ( midiInPortRef, src, NULL ); + } + + qInfo() << "CoreAudio MIDI port created and connected to" << iNMIDISources << "sources"; + } +} + +void CSound::DestroyMIDIPort() +{ + if ( midiInPortRef != static_cast ( NULL ) ) + { + // Disconnect from all sources before disposing + const int iNMIDISources = MIDIGetNumberOfSources(); + for ( int i = 0; i < iNMIDISources; i++ ) + { + MIDIEndpointRef src = MIDIGetSource ( i ); + MIDIPortDisconnectSource ( midiInPortRef, src ); + } + + // Dispose of the MIDI input port + OSStatus result = MIDIPortDispose ( midiInPortRef ); + if ( result != noErr ) + { + qWarning() << "Failed to dispose CoreAudio MIDI input port. Error code:" << result; + } + midiInPortRef = static_cast ( NULL ); + + qInfo() << "CoreAudio MIDI port destroyed"; + } + + // Only dispose client if no ports are using it + if ( midiClient != static_cast ( NULL ) && midiInPortRef == static_cast ( NULL ) ) + { + OSStatus result = MIDIClientDispose ( midiClient ); + if ( result != noErr ) + { + qWarning() << "Failed to dispose CoreAudio MIDI client. Error code:" << result; + } + midiClient = static_cast ( NULL ); + } +} + int CSound::Init ( const int iNewPrefMonoBufferSize ) { UInt32 iActualMonoBufferSize; diff --git a/src/sound/coreaudio-mac/sound.h b/src/sound/coreaudio-mac/sound.h index 9ba70a01fd..96f2e397ce 100644 --- a/src/sound/coreaudio-mac/sound.h +++ b/src/sound/coreaudio-mac/sound.h @@ -44,6 +44,8 @@ class CSound : public CSoundBase const bool, const QString& ); + virtual ~CSound(); + virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); virtual void Stop(); @@ -63,6 +65,10 @@ class CSound : public CSoundBase virtual int GetLeftOutputChannel() { return iSelOutputLeftChannel; } virtual int GetRightOutputChannel() { return iSelOutputRightChannel; } + // MIDI functions + virtual void EnableMIDI ( const bool bEnable ); + virtual bool IsMIDIEnabled() const; + // these variables/functions should be protected but cannot since we want // to access them from the callback function CVector vecsTmpAudioSndCrdStereo; @@ -108,6 +114,9 @@ class CSound : public CSoundBase bool ConvertCFStringToQString ( const CFStringRef stringRef, QString& sOut ); + void CreateMIDIPort(); + void DestroyMIDIPort(); + // callbacks static OSStatus deviceNotification ( AudioDeviceID, UInt32, const AudioObjectPropertyAddress* inAddresses, void* inRefCon ); @@ -126,7 +135,8 @@ class CSound : public CSoundBase AudioDeviceIOProcID audioInputProcID; AudioDeviceIOProcID audioOutputProcID; - MIDIPortRef midiInPortRef; + MIDIClientRef midiClient; + MIDIPortRef midiInPortRef; QString sChannelNamesInput[MAX_NUM_IN_OUT_CHANNELS]; QString sChannelNamesOutput[MAX_NUM_IN_OUT_CHANNELS]; diff --git a/src/sound/jack/sound.cpp b/src/sound/jack/sound.cpp index e53dbece42..50f7324201 100644 --- a/src/sound/jack/sound.cpp +++ b/src/sound/jack/sound.cpp @@ -85,21 +85,7 @@ void CSound::OpenJack ( const bool bNoAutoJackConnect, const char* jackClientNam } // optional MIDI initialization - if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) - { - input_port_midi = jack_port_register ( pJackClient, "input midi", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); - - if ( input_port_midi == nullptr ) - { - throw CGenErr ( QString ( tr ( "The JACK port registration failed. This is probably an error with JACK. Please stop %1 and JACK. " - "Afterwards, check if another MIDI program can connect to JACK." ) ) - .arg ( APP_NAME ) ); - } - } - else - { - input_port_midi = nullptr; - } + input_port_midi = nullptr; // tell the JACK server that we are ready to roll if ( jack_activate ( pJackClient ) ) @@ -192,16 +178,66 @@ void CSound::Stop() CSoundBase::Stop(); } +void CSound::EnableMIDI ( bool bEnable ) +{ + if ( bEnable && ( iCtrlMIDIChannel != INVALID_MIDI_CH ) ) + { + // Create MIDI port if we have valid MIDI channel and no port exists + if ( input_port_midi == nullptr ) + { + CreateMIDIPort(); + } + } + else + { + // Destroy MIDI port if it exists + if ( input_port_midi != nullptr ) + { + DestroyMIDIPort(); + } + } +} + +bool CSound::IsMIDIEnabled() const { return ( input_port_midi != nullptr ); } + +void CSound::CreateMIDIPort() +{ + if ( pJackClient != nullptr && input_port_midi == nullptr ) + { + input_port_midi = jack_port_register ( pJackClient, "input midi", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + + if ( input_port_midi == nullptr ) + { + qWarning() << "Failed to create JACK MIDI port at runtime"; + } + } +} + +void CSound::DestroyMIDIPort() +{ + if ( pJackClient != nullptr && input_port_midi != nullptr ) + { + if ( jack_port_unregister ( pJackClient, input_port_midi ) == 0 ) + { + input_port_midi = nullptr; + } + else + { + qWarning() << "Failed to destroy JACK MIDI port"; + } + } +} + int CSound::Init ( const int /* iNewPrefMonoBufferSize */ ) { - //### TODO: BEGIN ###// - // try setting buffer size seems not to work! -> no audio after this operation! - // Doesn't this give an infinite loop? The set buffer size function will call our - // registered callback which calls "EmitReinitRequestSignal()". In that function - // this CSound::Init() function is called... - // jack_set_buffer_size ( pJackClient, iNewPrefMonoBufferSize ); - //### TODO: END ###// + // ### TODO: BEGIN ###// + // try setting buffer size seems not to work! -> no audio after this operation! + // Doesn't this give an infinite loop? The set buffer size function will call our + // registered callback which calls "EmitReinitRequestSignal()". In that function + // this CSound::Init() function is called... + // jack_set_buffer_size ( pJackClient, iNewPrefMonoBufferSize ); + // ### TODO: END ###// // without a Jack server, Jamulus makes no sense to run, throw an error message if ( bJackWasShutDown ) @@ -305,10 +341,10 @@ int CSound::process ( jack_nframes_t nframes, void* arg ) // copy packet and send it to the MIDI parser - //### TODO: BEGIN ###// - // do not call malloc in real-time callback + // ### TODO: BEGIN ###// + // do not call malloc in real-time callback CVector vMIDIPaketBytes ( in_event.size ); - //### TODO: END ###// + // ### TODO: END ###// for ( i = 0; i < static_cast ( in_event.size ); i++ ) { diff --git a/src/sound/jack/sound.h b/src/sound/jack/sound.h index 55b922ef7d..2008452135 100644 --- a/src/sound/jack/sound.h +++ b/src/sound/jack/sound.h @@ -87,6 +87,8 @@ class CSound : public CSoundBase virtual void Stop(); virtual float GetInOutLatencyMs() { return fInOutLatencyMs; } + virtual void EnableMIDI ( bool bEnable ) override; + virtual bool IsMIDIEnabled() const override; // these variables should be protected but cannot since we want // to access them from the callback function @@ -105,6 +107,8 @@ class CSound : public CSoundBase void OpenJack ( const bool bNoAutoJackConnect, const char* jackClientName ); void CloseJack(); + void CreateMIDIPort(); + void DestroyMIDIPort(); // callbacks static int process ( jack_nframes_t nframes, void* arg ); diff --git a/src/sound/midi-win/midi.cpp b/src/sound/midi-win/midi.cpp index 654b05760d..0fbf27cfcf 100644 --- a/src/sound/midi-win/midi.cpp +++ b/src/sound/midi-win/midi.cpp @@ -39,6 +39,11 @@ extern CSound* pSound; void CMidi::MidiStart() { + if ( m_bIsActive ) + { + return; // MIDI is already active, no need to start again + } + QString selMIDIDevice = pSound->GetMIDIDevice(); /* Get the number of MIDI In devices in this computer */ @@ -87,21 +92,36 @@ void CMidi::MidiStart() continue; // try next device, if any } - // success, add it to list of open handles + // Success, add it to the list of open handles vecMidiInHandles.append ( hMidiIn ); } + + if ( !vecMidiInHandles.isEmpty() ) + { + m_bIsActive = true; // Set active state if at least one device was started + } } void CMidi::MidiStop() { - // stop MIDI if running + if ( !m_bIsActive ) + { + return; // MIDI is already stopped, no need to stop again + } + + // Stop MIDI if running for ( int i = 0; i < vecMidiInHandles.size(); i++ ) { midiInStop ( vecMidiInHandles.at ( i ) ); midiInClose ( vecMidiInHandles.at ( i ) ); } + + vecMidiInHandles.clear(); // Clear the list of handles + m_bIsActive = false; // Set active state to false } +bool CMidi::IsActive() const { return m_bIsActive; } + // See https://learn.microsoft.com/en-us/previous-versions//dd798460(v=vs.85) // for the definition of the MIDI input callback function. void CALLBACK CMidi::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) diff --git a/src/sound/midi-win/midi.h b/src/sound/midi-win/midi.h index 3ce01f9ced..a87d5a5688 100644 --- a/src/sound/midi-win/midi.h +++ b/src/sound/midi-win/midi.h @@ -31,16 +31,18 @@ class CMidi { public: - CMidi() {} + CMidi() : m_bIsActive ( false ) {} virtual ~CMidi() {} void MidiStart(); void MidiStop(); + bool IsActive() const; protected: int iMidiDevs; QVector vecMidiInHandles; // windows handles + bool m_bIsActive; // Tracks if MIDI is currently active static void CALLBACK MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); }; diff --git a/src/sound/soundbase.cpp b/src/sound/soundbase.cpp index d517219bc6..770206509f 100644 --- a/src/sound/soundbase.cpp +++ b/src/sound/soundbase.cpp @@ -422,4 +422,12 @@ void CSoundBase::ParseMIDIMessage ( const CVector& vMIDIPaketBytes ) } } -void CSoundBase::SetMIDIMapping ( const QString& strMIDISetup ) { ParseCommandLineArgument ( strMIDISetup ); } +void CSoundBase::SetMIDIMapping ( const QString& strMIDISetup ) +{ + // Parse the MIDI mapping + ParseCommandLineArgument ( strMIDISetup ); + + // Enable/disable MIDI port based on whether mapping is empty + bool bShouldEnable = !strMIDISetup.isEmpty(); + EnableMIDI ( bShouldEnable ); +} diff --git a/src/sound/soundbase.h b/src/sound/soundbase.h index 26188161d7..1f054835ee 100644 --- a/src/sound/soundbase.h +++ b/src/sound/soundbase.h @@ -117,8 +117,10 @@ class CSoundBase : public QThread void EmitReinitRequestSignal ( const ESndCrdResetType eSndCrdResetType ) { emit ReinitRequest ( eSndCrdResetType ); } // this needs to be public so that it can be called from CMidi - void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); - void SetMIDIMapping ( const QString& strMIDISetup ); + void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); + void SetMIDIMapping ( const QString& strMIDISetup ); + virtual void EnableMIDI ( bool /* bEnable */ ) {} // Default empty implementation + virtual bool IsMIDIEnabled() const { return false; } // Default false protected: virtual QString LoadAndInitializeDriver ( QString, bool ) { return ""; }