Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/JSON-RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ Results:
| result.clients | array | The client list. See jamulusclient/clientListReceived for the format. |


### jamulusclient/getMidiSettings

Returns all MIDI controller settings.

Parameters:

| Name | Type | Description |
| --- | --- | --- |
| params | object | No parameters (empty object). |

Results:

| Name | Type | Description |
| --- | --- | --- |
| result | object | MIDI settings object. |


### jamulusclient/pollServerList

Request list of servers in a directory.
Expand Down Expand Up @@ -240,6 +257,23 @@ Results:
| result | string | Always "ok". |


### jamulusclient/setMidiSettings

Sets one or more MIDI controller settings.

Parameters:

| Name | Type | Description |
| --- | --- | --- |
| params | object | Any subset of MIDI settings fields to set. |

Results:

| Name | Type | Description |
| --- | --- | --- |
| result | string | Always "ok". |


### jamulusclient/setName

Sets your name.
Expand Down
3 changes: 3 additions & 0 deletions src/audiomixerboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,9 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector<CChannelInfo>& vecChanInf
}
Mutex.unlock(); // release mutex

// Ensure MIDI state is applied to faders during the connection process
SetMIDICtrlUsed ( pSettings->bUseMIDIController );

// sort the channels according to the selected sorting type
ChangeFaderOrder ( eChSortType );

Expand Down
36 changes: 32 additions & 4 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,21 @@
\******************************************************************************/

#include "client.h"
#include "settings.h"
#include "util.h"

/* Implementation *************************************************************/
CClient::CClient ( const quint16 iPortNumber,
const quint16 iQosNumber,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNoAutoJackConnect,
const QString& strNClientName,
const bool bNEnableIPv6,
const bool bNMuteMeInPersonalMix ) :
ChannelInfo(),
strClientName ( strNClientName ),
pSignalHandler ( CSignalHandler::getSingletonP() ),
pSettings ( nullptr ),
Channel ( false ), /* we need a client channel -> "false" */
CurOpusEncoder ( nullptr ),
CurOpusDecoder ( nullptr ),
Expand All @@ -49,7 +51,7 @@ CClient::CClient ( const quint16 iPortNumber,
bMuteOutStream ( false ),
fMuteOutStreamGain ( 1.0f ),
Socket ( &Channel, iPortNumber, iQosNumber, "", bNEnableIPv6 ),
Sound ( AudioCallback, this, strMIDISetup, bNoAutoJackConnect, strNClientName ),
Sound ( AudioCallback, this, bNoAutoJackConnect, strNClientName ),
iAudioInFader ( AUD_FADER_IN_MIDDLE ),
bReverbOnLeftChan ( false ),
iReverbLevel ( 0 ),
Expand All @@ -68,8 +70,7 @@ CClient::CClient ( const quint16 iPortNumber,
bJitterBufferOK ( true ),
bEnableIPv6 ( bNEnableIPv6 ),
bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ),
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ),
pSignalHandler ( CSignalHandler::getSingletonP() )
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL )
{
int iOpusError;

Expand Down Expand Up @@ -173,6 +174,8 @@ CClient::CClient ( const quint16 iPortNumber,

QObject::connect ( pSignalHandler, &CSignalHandler::HandledSignal, this, &CClient::OnHandledSignal );

QObject::connect ( &Sound, &CSoundBase::MidiCCReceived, this, [this] ( int ccNumber ) { emit MidiCCReceived ( ccNumber ); } );

// start timer so that elapsed time works
PreciseTime.start();

Expand All @@ -193,6 +196,29 @@ CClient::CClient ( const quint16 iPortNumber,
}
}

// MIDI setup will be handled after settings are assigned
void CClient::ApplyMidiSettingsFromConfig()
{
if ( pSettings )
{
Sound.SetCtrlMIDIChannel ( pSettings->iMidiChannel );
Sound.SetMIDIControllerMapping ( pSettings->iMidiFaderOffset,
pSettings->iMidiFaderCount,
pSettings->iMidiPanOffset,
pSettings->iMidiPanCount,
pSettings->iMidiSoloOffset,
pSettings->iMidiSoloCount,
pSettings->iMidiMuteOffset,
pSettings->iMidiMuteCount,
pSettings->iMidiMuteMyself );
Sound.EnableMIDI ( pSettings->bUseMIDIController );
if ( !pSettings->strMidiDevice.isEmpty() )
{
Sound.SetMIDIDevice ( pSettings->strMidiDevice );
}
}
}

CClient::~CClient()
{
// if we were running, stop sound device
Expand Down Expand Up @@ -1547,6 +1573,8 @@ void CClient::FreeClientChannel ( const int iServerChannelID )
*/
}

void CClient::OnMidiCCReceived ( int ccNumber ) { emit MidiCCReceived ( ccNumber ); }

// find, and optionally create, a client channel for the supplied server channel ID
// returns a client channel ID or INVALID_INDEX
int CClient::FindClientChannel ( const int iServerChannelID, const bool bCreateIfNew )
Expand Down
22 changes: 19 additions & 3 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ class CClient : public QObject
CClient ( const quint16 iPortNumber,
const quint16 iQosNumber,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNoAutoJackConnect,
const QString& strNClientName,
const bool bNEnableIPv6,
Expand Down Expand Up @@ -293,11 +292,26 @@ class CClient : public QObject
CProtocol* getConnLessProtocol() { return &ConnLessProtocol; }
//### TODO: END ###//

// MIDI control
void EnableMIDI ( bool bEnable ) { Sound.EnableMIDI ( bEnable ); }
bool IsMIDIEnabled() const { return Sound.IsMIDIEnabled(); }

// settings
CChannelCoreInfo ChannelInfo;
QString strClientName;

public:
// Assign settings pointer
void SetSettings ( CClientSettings* settings ) { pSettings = settings; }

// Apply MIDI settings from config
void ApplyMidiSettingsFromConfig();
Copy link
Collaborator

@pljones pljones Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it all into SetSettings (and move SetSettings into client.cpp of course). You won't need that if ( *pSettings ) then, as you'll see pSettings = settings just having been done.


protected:
// Signal handler must be declared before pSettings for correct init order
CSignalHandler* pSignalHandler;
// Pointer to settings for MIDI and other config
CClientSettings* pSettings;
// callback function must be static, otherwise it does not work
static void AudioCallback ( CVector<short>& psData, void* arg );

Expand Down Expand Up @@ -407,8 +421,6 @@ class CClient : public QObject
int maxGainOrPanId;
int iCurPingTime;

CSignalHandler* pSignalHandler;

protected slots:
void OnHandledSignal ( int sigNum );
void OnSendProtMessage ( CVector<uint8_t> vecMessage );
Expand Down Expand Up @@ -473,4 +485,8 @@ protected slots:
void ControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo );
void ControllerInFaderIsMute ( int iChannelIdx, bool bIsMute );
void ControllerInMuteMyself ( bool bMute );
void MidiCCReceived ( int ccNumber );

private slots:
void OnMidiCCReceived ( int ccNumber );
};
21 changes: 19 additions & 2 deletions src/clientdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
CClientDlg::CClientDlg ( CClient* pNCliP,
CClientSettings* pNSetP,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNewShowComplRegConnList,
const bool bShowAnalyzerConsole,
const bool bMuteStream,
Expand Down Expand Up @@ -220,7 +219,7 @@ CClientDlg::CClientDlg ( CClient* pNCliP,
MainMixerBoard->SetNumMixerPanelRows ( pSettings->iNumMixerPanelRows );

// Pass through flag for MIDICtrlUsed
MainMixerBoard->SetMIDICtrlUsed ( !strMIDISetup.isEmpty() );
MainMixerBoard->SetMIDICtrlUsed ( pSettings->bUseMIDIController );

// reset mixer board
MainMixerBoard->HideAll();
Expand Down Expand Up @@ -401,6 +400,12 @@ CClientDlg::CClientDlg ( CClient* pNCliP,

pSettingsMenu->addAction ( tr ( "A&dvanced Settings..." ), this, SLOT ( OnOpenAdvancedSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_D ) );

pSettingsMenu->addAction (
tr ( "&MIDI Control Settings..." ),
this,
[this] { ShowGeneralSettings ( SETTING_TAB_MIDI ); },
QKeySequence ( Qt::CTRL + Qt::Key_M ) );

// Main menu bar -----------------------------------------------------------
QMenuBar* pMenu = new QMenuBar ( this );

Expand Down Expand Up @@ -536,6 +541,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 );
Expand Down Expand Up @@ -1526,3 +1533,13 @@ void CClientDlg::SetPingTime ( const int iPingTime, const int iOverallDelayMs, c
// set current LED status
ledDelay->SetLight ( eOverallDelayLEDColor );
}

// OnOpenMidiSettings slot removed; lambda is used in menu action
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 );
}
3 changes: 2 additions & 1 deletion src/clientdlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ class CClientDlg : public CBaseDlg, private Ui_CClientDlgBase
CClientDlg ( CClient* pNCliP,
CClientSettings* pNSetP,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNewShowComplRegConnList,
const bool bShowAnalyzerConsole,
const bool bMuteStream,
Expand Down Expand Up @@ -246,6 +245,8 @@ public slots:

void accept() { close(); } // introduced by pljones

void OnMIDIControllerUsageChanged ( bool bEnabled );

signals:
void SendTabChange ( int iTabIdx );
};
76 changes: 75 additions & 1 deletion src/clientrpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@

#include "clientrpc.h"

CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* parent ) : QObject ( parent )
CClientRpc::CClientRpc ( CClient* pClient, CClientSettings* pSettings, CRpcServer* pRpcServer, QObject* parent ) :
QObject ( parent ),
m_pSettings ( pSettings )
{
/// @rpc_notification jamulusclient/chatTextReceived
/// @brief Emitted when a chat text is received.
Expand Down Expand Up @@ -355,6 +357,78 @@ CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* pare
pClient->SetControllerInFaderLevel ( jsonChannelIndex.toInt(), jsonLevel.toInt() );
response["result"] = "ok";
} );

/// @rpc_method jamulusclient/getMidiSettings
/// @brief Returns all MIDI controller settings.
/// @param {object} params - No parameters (empty object).
/// @result {object} result - MIDI settings object.
pRpcServer->HandleMethod ( "jamulusclient/getMidiSettings", [=] ( const QJsonObject& params, QJsonObject& response ) {
QJsonObject jsonMidiParams{ { "bUseMIDIController", m_pSettings->bUseMIDIController },
{ "midiChannel", m_pSettings->iMidiChannel },
{ "midiMuteMyself", m_pSettings->iMidiMuteMyself },
{ "midiFaderOffset", m_pSettings->iMidiFaderOffset },
{ "midiFaderCount", m_pSettings->iMidiFaderCount },
{ "midiPanOffset", m_pSettings->iMidiPanOffset },
{ "midiPanCount", m_pSettings->iMidiPanCount },
{ "midiSoloOffset", m_pSettings->iMidiSoloOffset },
{ "midiSoloCount", m_pSettings->iMidiSoloCount },
{ "midiMuteOffset", m_pSettings->iMidiMuteOffset },
{ "midiMuteCount", m_pSettings->iMidiMuteCount } };
response["result"] = jsonMidiParams;
Q_UNUSED ( params );
} );

/// @rpc_method jamulusclient/setMidiSettings
/// @brief Sets one or more MIDI controller settings.
/// @param {object} params - Any subset of MIDI settings fields to set.
/// @result {string} result - Always "ok".
pRpcServer->HandleMethod ( "jamulusclient/setMidiSettings", [=] ( const QJsonObject& params, QJsonObject& response ) {
if ( params.contains ( "bUseMIDIController" ) )
Copy link
Collaborator

@pljones pljones Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I trust Gemini to write C++?

std::unordered_map<String, std::function<void(String)>> setters = {
    { "bUseMIDIController", [this](String name) { m_pSettings->bUseMIDIController = params[name].toBool(); },
    { "midiChannel", [this](String name) { m_pSettings->midiChannel = params[name].toInt(); },
    ...
};
// Iterate through every defined setter in your map
for (const auto& [name, setter] : setters) 
{
    // Check if the incoming params collection actually has this key
    if (params.contains(name)) 
    {
        // Execute the lambda: [this](String n) { ... }
        setter(name);
    }
}

(And it followed Jamulus coding style better than I did...)

{
m_pSettings->bUseMIDIController = params["bUseMIDIController"].toBool();
}
if ( params.contains ( "midiChannel" ) )
{
m_pSettings->iMidiChannel = params["midiChannel"].toInt();
}
if ( params.contains ( "midiMuteMyself" ) )
{
m_pSettings->iMidiMuteMyself = params["midiMuteMyself"].toInt();
}
if ( params.contains ( "midiFaderOffset" ) )
{
m_pSettings->iMidiFaderOffset = params["midiFaderOffset"].toInt();
}
if ( params.contains ( "midiFaderCount" ) )
{
m_pSettings->iMidiFaderCount = params["midiFaderCount"].toInt();
}
if ( params.contains ( "midiPanOffset" ) )
{
m_pSettings->iMidiPanOffset = params["midiPanOffset"].toInt();
}
if ( params.contains ( "midiPanCount" ) )
{
m_pSettings->iMidiPanCount = params["midiPanCount"].toInt();
}
if ( params.contains ( "midiSoloOffset" ) )
{
m_pSettings->iMidiSoloOffset = params["midiSoloOffset"].toInt();
}
if ( params.contains ( "midiSoloCount" ) )
{
m_pSettings->iMidiSoloCount = params["midiSoloCount"].toInt();
}
if ( params.contains ( "midiMuteOffset" ) )
{
m_pSettings->iMidiMuteOffset = params["midiMuteOffset"].toInt();
}
if ( params.contains ( "midiMuteCount" ) )
{
m_pSettings->iMidiMuteCount = params["midiMuteCount"].toInt();
}
response["result"] = "ok";
} );
}

QJsonValue CClientRpc::SerializeSkillLevel ( ESkillLevel eSkillLevel )
Expand Down
4 changes: 3 additions & 1 deletion src/clientrpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@
#include "client.h"
#include "util.h"
#include "rpcserver.h"
#include "settings.h"

/* Classes ********************************************************************/
class CClientRpc : public QObject
{
Q_OBJECT

public:
CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* parent = nullptr );
CClientRpc ( CClient* pClient, CClientSettings* pSettings, CRpcServer* pRpcServer, QObject* parent = nullptr );

private:
CClientSettings* m_pSettings;
QJsonArray arrStoredChanInfo;
static QJsonValue SerializeSkillLevel ( ESkillLevel skillLevel );
};
Loading
Loading