Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ctrlmidich option for "own channel" regardless of number #3394

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
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
33 changes: 20 additions & 13 deletions src/audiomixerboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1343,49 +1343,56 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector<CChannelInfo>& vecChanInf

void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx, const int iValue )
{
const int iRealChannelIdx = getRealChannelIdx ( iChannelIdx );

// only apply new fader level if channel index is valid and the fader is visible
if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
if ( ( iRealChannelIdx >= 0 ) && ( iRealChannelIdx < MAX_NUM_CHANNELS ) )
{
if ( vecpChanFader[static_cast<size_t> ( iChannelIdx )]->IsVisible() )
if ( vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->IsVisible() )
{
vecpChanFader[static_cast<size_t> ( iChannelIdx )]->SetFaderLevel ( iValue );
vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->SetFaderLevel ( iValue );
}
}
}

void CAudioMixerBoard::SetPanValue ( const int iChannelIdx, const int iValue )
{
const int iRealChannelIdx = getRealChannelIdx ( iChannelIdx );

// only apply new pan value if channel index is valid and the panner is visible
if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) && bDisplayPans )
if ( ( iRealChannelIdx >= 0 ) && ( iRealChannelIdx < MAX_NUM_CHANNELS ) && bDisplayPans )
{
if ( vecpChanFader[static_cast<size_t> ( iChannelIdx )]->IsVisible() )
if ( vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->IsVisible() )
{
vecpChanFader[static_cast<size_t> ( iChannelIdx )]->SetPanValue ( iValue );
vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->SetPanValue ( iValue );
}
}
}

void CAudioMixerBoard::SetFaderIsSolo ( const int iChannelIdx, const bool bIsSolo )
{
// only apply solo if channel index is valid and the fader is visible
if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
const int iRealChannelIdx = getRealChannelIdx ( iChannelIdx );

// only apply solo if channel index is valid and the fader is visible
if ( ( iRealChannelIdx >= 0 ) && ( iRealChannelIdx < MAX_NUM_CHANNELS ) )
{
if ( vecpChanFader[static_cast<size_t> ( iChannelIdx )]->IsVisible() )
if ( vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->IsVisible() )
{
vecpChanFader[static_cast<size_t> ( iChannelIdx )]->SetFaderIsSolo ( bIsSolo );
vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->SetFaderIsSolo ( bIsSolo );
}
}
}

void CAudioMixerBoard::SetFaderIsMute ( const int iChannelIdx, const bool bIsMute )
{
const int iRealChannelIdx = getRealChannelIdx ( iChannelIdx );

// only apply mute if channel index is valid and the fader is visible
if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
if ( ( iRealChannelIdx >= 0 ) && ( iRealChannelIdx < MAX_NUM_CHANNELS ) )
{
if ( vecpChanFader[static_cast<size_t> ( iChannelIdx )]->IsVisible() )
if ( vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->IsVisible() )
{
vecpChanFader[static_cast<size_t> ( iChannelIdx )]->SetFaderIsMute ( bIsMute );
vecpChanFader[static_cast<size_t> ( iRealChannelIdx )]->SetFaderIsMute ( bIsMute );
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/audiomixerboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,15 @@ class CAudioMixerBoard : public QGroupBox, public CAudioMixerBoardSlots<MAX_NUM_
template<unsigned int slotId>
inline void connectFaderSignalsToMixerBoardSlots();

// When handling MIDI controllers for adjusting Jamulus channel controls,
ann0see marked this conversation as resolved.
Show resolved Hide resolved
// each channel is assigned by the server. As a user's Jamulus channel
// will vary on each server connection, to enable the assignment of MIDI
// controllers to the user's own Jamulus channel, the constant I_MY_CHANNEL
// is passed into the methods used to handle the requests. This method then
// maps I_MY_CHANNEL to the current value of the user's Jamulus channel,
// held in iMyChannel.
inline int getRealChannelIdx ( const int iChannelIdx ) const { return iChannelIdx == I_MY_CHANNEL ? iMyChannelID : iChannelIdx; }

signals:
void ChangeChanGain ( int iId, float fGain, bool bIsMyOwnFader );
void ChangeChanPan ( int iId, float fPan );
Expand Down
2 changes: 2 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ class CClient : public QObject
Channel.GetBufErrorRates ( vecErrRates, dLimit, dMaxUpLimit );
}

bool IsMIDIEnabled() { return Sound.IsMIDIEnabled(); }

// settings
CChannelCoreInfo ChannelInfo;
QString strClientName;
Expand Down
3 changes: 1 addition & 2 deletions src/clientdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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 @@ -219,7 +218,7 @@ CClientDlg::CClientDlg ( CClient* pNCliP,
MainMixerBoard->SetNumMixerPanelRows ( pSettings->iNumMixerPanelRows );

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

// reset mixer board
MainMixerBoard->HideAll();
Expand Down
1 change: 0 additions & 1 deletion src/clientdlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,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
1 change: 0 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,6 @@ int main ( int argc, char** argv )
CClientDlg ClientDlg ( &Client,
&Settings,
strConnOnStartupAddress,
strMIDISetup,
bShowComplRegConnList,
bShowAnalyzerConsole,
bMuteStream,
Expand Down
205 changes: 127 additions & 78 deletions src/sound/soundbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@

#include "soundbase.h"

// This is used as a lookup table for parsing option letters, mapping
// a single character to an EMidiCtlType
char const sMidiCtlChar[] = {
// Has to follow order of EMidiCtlType
/* [EMidiCtlType::Fader] = */ 'f',
/* [EMidiCtlType::Pan] = */ 'p',
/* [EMidiCtlType::Solo] = */ 's',
/* [EMidiCtlType::Mute] = */ 'm',
/* [EMidiCtlType::MuteMyself] = */ 'o',
/* [EMidiCtlType::None] = */ '\0' };

/* Implementation *************************************************************/
CSoundBase::CSoundBase ( const QString& strNewSystemDriverTechniqueName,
void ( *fpNewProcessCallback ) ( CVector<int16_t>& psData, void* pParg ),
Expand Down Expand Up @@ -232,98 +221,156 @@ QVector<QString> CSoundBase::LoadAndInitializeFirstValidDriver ( const bool bOpe
}

/******************************************************************************\
* MIDI handling *
* Command Line Handling *
\******************************************************************************/
void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup )
{
int iMIDIOffsetFader = 70; // Behringer X-TOUCH: offset of 0x46

// parse the server info string according to definition: there is
pljones marked this conversation as resolved.
Show resolved Hide resolved
// the legacy definition with just one or two numbers that only
// provides a definition for the controller offset of the level
// controllers (default 70 for the sake of Behringer X-Touch)
// [MIDI channel];[offset for level]
//
// The more verbose new form is a sequence of offsets for various
// controllers: at the current point, 'f', 'p', 's', and 'm' are
// parsed for fader, pan, solo, mute controllers respectively.
// However, at the current point of time only 'f' and 'p'
// controllers are actually implemented. The syntax for a Korg
// nanoKONTROL2 with 8 fader controllers starting at offset 0 and
// 8 pan controllers starting at offset 16 would be
//
// [MIDI channel];f0*8;p16*8
//
// Namely a sequence of letters indicating the kind of controller,
// followed by the offset of the first such controller, followed
// by * and a count for number of controllers (if more than 1)
if ( !strMIDISetup.isEmpty() )
if ( strMIDISetup.isEmpty() )
pljones marked this conversation as resolved.
Show resolved Hide resolved
{
// split the different parameter strings
const QStringList slMIDIParams = strMIDISetup.split ( ";" );
// should be caught in main.cpp
return;
}

// Parse the --ctrlmidich string. There are two formats.
// Default to the legacy kind of specifying the fader controller offset
// without an indication of the count of controllers.
bool bSimple = true;

// [MIDI channel]
if ( slMIDIParams.count() >= 1 )
// split the different parameter strings
const QStringList slMIDIParams = strMIDISetup.split ( ";" );
int iNumParams = slMIDIParams.count();

if ( iNumParams >= 1 )
{
bool bChOK = false;
int i = slMIDIParams[0].toUInt ( &bChOK );
if ( bChOK )
{
iCtrlMIDIChannel = slMIDIParams[0].toUInt();
// [MIDI channel] supplied (else use default)
iCtrlMIDIChannel = i;
}
else
{
// iCtrlMIDIChannel == INVALID_MIDI_CH, so no point continuing
return;
}
}

bool bSimple = true; // Indicates the legacy kind of specifying
// the fader controller offset without an
// indication of the count of controllers
// Use Behringer X-TOUCH as default offset of 0x46
int iMIDIOffsetFader = 70;
if ( iNumParams >= 2 )
{
// if there is a second parameter that can be parsed as a number,
// we have the legacy specification of controllers.
int i = slMIDIParams[1].toUInt ( &bSimple );
ann0see marked this conversation as resolved.
Show resolved Hide resolved
if ( bSimple )
{
// [offset for fader] supplied (else use default)
iMIDIOffsetFader = i;
}
}

// [offset for level]
if ( slMIDIParams.count() >= 2 )
if ( bSimple )
{
// For the legacy specification, we consider every controller
// up to the maximum number of channels (or the maximum
// controller number) a fader.
for ( int i = 0; i + iMIDIOffsetFader <= 127 && i < MAX_NUM_CHANNELS; i++ )
{
int i = slMIDIParams[1].toUInt ( &bSimple );
// if the second parameter can be parsed as a number, we
// have the legacy specification of controllers.
if ( bSimple )
iMIDIOffsetFader = i;
// add a list entry for the CMidiCtlEntry
aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i };
}
return;
}

if ( bSimple )
// We have named controllers
// Validate and see whether "MyChannel" option is present

bool bMyChannel = false;
QStringList slValid; // keep track of valid entries to make later processing simple

for ( int i = 0; i < iNumParams; i++ )
{
QString sParm = slMIDIParams[i].trimmed();

if ( sParm.isEmpty() )
{
// skip empty entries silently
continue;
}

int iCtrl = sMidiCtl.indexOf ( sParm[0] );
if ( iCtrl < 0 )
{
// skip unknown entries silently
continue;
}

if ( static_cast<EMidiCtlType> ( iCtrl ) == EMidiCtlType::MyChannel )
{
// For the legacy specification, we consider every controller
// up to the maximum number of channels (or the maximum
// controller number) a fader.
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
// once seen, just remember this
bMyChannel = true;
continue;
}

const QStringList slP = sParm.mid ( 1 ).split ( '*' );

if ( slP.count() > 2 )
{
// skip invalid entries silently
continue;
}

bool bIsUInt = false;

unsigned int u = slP[0].toUInt ( &bIsUInt );
if ( !bIsUInt )
{
// skip invalid entries silently
continue;
}
int iFirst = u;

// silently default incoherent count to 1
int iNum = 1;
if ( static_cast<EMidiCtlType> ( iCtrl ) != EMidiCtlType::MuteMyself && slP.count() == 2 )
{
bIsUInt = false;
unsigned int u = slP[1].toUInt ( &bIsUInt );
if ( bIsUInt )
{
if ( i + iMIDIOffsetFader > 127 )
break;
aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i };
iNum = u;
}
return;
}

// We have named controllers
// store the valid entry in a more splittable format
slValid.append ( QString ( "%1*%2*%3" ).arg ( iCtrl ).arg ( iFirst ).arg ( iNum ) );
}

for ( int i = 1; i < slMIDIParams.count(); i++ )
foreach ( QString sParm, slValid )
{
const QStringList slP = sParm.split ( '*' );
const EMidiCtlType eTyp = static_cast<EMidiCtlType> ( slP[0].toInt() );
const int iFirst = slP[1].toUInt();
const int iNum = slP[2].toUInt();
for ( int iOff = 0; iOff < iNum && iOff + iFirst <= 127 && iOff < MAX_NUM_CHANNELS; iOff++ )
{
QString sParm = slMIDIParams[i].trimmed();
if ( sParm.isEmpty() )
continue;

int iCtrl = QString ( sMidiCtlChar ).indexOf ( sParm[0] );
if ( iCtrl < 0 )
continue;
EMidiCtlType eTyp = static_cast<EMidiCtlType> ( iCtrl );

const QStringList slP = sParm.mid ( 1 ).split ( '*' );
int iFirst = slP[0].toUInt();
int iNum = ( slP.count() > 1 ) ? slP[1].toUInt() : 1;
for ( int iOff = 0; iOff < iNum; iOff++ )
// For MyChannel option, first offset is "MyChannel", then the rest are 0 to iNum-1 channels
if ( bMyChannel )
{
aMidiCtls[iFirst + iOff] = { eTyp, iOff == 0 ? I_MY_CHANNEL : iOff - 1 };
}
else
{
if ( iOff >= MAX_NUM_CHANNELS )
break;
if ( iFirst + iOff >= 128 )
break;
aMidiCtls[iFirst + iOff] = { eTyp, iOff };
}
}
}
}

/******************************************************************************\
* MIDI handling *
\******************************************************************************/
void CSoundBase::ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes )
{
if ( vMIDIPaketBytes.Size() > 0 )
Expand Down Expand Up @@ -356,9 +403,11 @@ void CSoundBase::ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes )
// make sure packet is long enough
if ( vMIDIPaketBytes.Size() > 2 && vMIDIPaketBytes[1] <= uint8_t ( 127 ) && vMIDIPaketBytes[2] <= uint8_t ( 127 ) )
{
// Where "MyChannel" is in effect, cCtrl.iChannel will be I_MY_CHANNEL
// for the first CC number in the range for a cCtrl.eType and then zero upwards.
const CMidiCtlEntry& cCtrl = aMidiCtls[vMIDIPaketBytes[1]];
const int iValue = vMIDIPaketBytes[2];
;

switch ( cCtrl.eType )
{
case Fader:
Expand Down
Loading
Loading