Skip to content

Commit

Permalink
Make "My Channel" work for all controls
Browse files Browse the repository at this point in the history
  • Loading branch information
pljones committed Oct 4, 2024
1 parent b1eda2e commit 109d4eb
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 80 deletions.
222 changes: 144 additions & 78 deletions src/sound/soundbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +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::OurFader] = */ 'z', // Proposed addition: a new enum value for "our fader"
/* [EMidiCtlType::None] = */ '\0' };

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

/******************************************************************************\
* MIDI handling *
* Command Line Handling *
\******************************************************************************/
void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup )
{
if ( strMIDISetup.isEmpty() )
return;

int iMIDIOffsetFader = 70; // Behringer X-TOUCH: offset of 0x46

// parse the server info string according to definition: there is
// the legacy definition with just one or two numbers that only
// provides a definition for the controller offset of the level
// default to the legacy kind of specifying
// the fader controller offset without an
// indication of the count of controllers
bool bSimple = true;

// Parse the --ctrlmidich string. There are two formats.
//
// The legacy definition has just one or two numbers that only
// provides a definition for the controller offset of the fader
// controllers (default 70 for the sake of Behringer X-Touch)
// [MIDI channel];[offset for level]
// [MIDI channel];[offset for first fader]
//
// 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
// The more verbose new form is a sequence of offsets and counts for various
// controllers:
// [MIDI channel];[control letter][offset]*[count](;...)
// Currently, the following control letters are defined:
// Fader - 'f'
// Pan - 'p'
// Solo - 's'
// Mute - 'm'
// 'offset' is the base MIDI CC number for the control.
// 'count' is the number of CC values for the control (Jamulus channels).
//
// Additionally, 'o' has a single offset and controls Mute Myself.
//
// In addition, 'z' reserves the first CC number for a control
// to mean "my Jamulus channel". (For example
// 1;f0*9;z
// would mean fader CC0 controlled "my" channel, with CC1 to CC8 for normal channels 1 to 8.
//
// An example 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() )
{
// split the different parameter strings
const QStringList slMIDIParams = strMIDISetup.split ( ";" );
// However, at the current point of time only 'f' and 'p'
// controllers are actually implemented.

// split the different parameter strings
const QStringList slMIDIParams = strMIDISetup.split ( ";" );

// [MIDI channel]
if ( slMIDIParams.count() >= 1 )
int iMIDIParamsStart = 0;
if ( slMIDIParams.count() >= 1 )
{
bool bChOK = false;
int i = slMIDIParams[0].toUInt ( &bChOK );
if ( bChOK )
{
iCtrlMIDIChannel = slMIDIParams[0].toUInt();
// [MIDI channel] supplied
iCtrlMIDIChannel = i;
iMIDIParamsStart = 1;
}
else
{
// Something else, use default channel and try new-style parsing
bSimple = false;
}
}

bool bSimple = true; // Indicates the legacy kind of specifying
// the fader controller offset without an
// indication of the count of controllers
if ( bSimple && slMIDIParams.count() >= 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 );
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 hasMyChannel = false;
QStringList slValid;

// if the first param was not a number, use "any channel" and see if it is a valid param
for ( int i = iMIDIParamsStart; i < slMIDIParams.count(); i++ )
{
QString sParm = slMIDIParams[i].trimmed();

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

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

// once seen, just remember this
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++ )
{
if ( i + iMIDIOffsetFader > 127 )
break;
aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i };
}
return;
hasMyChannel = true;
continue;
}

// We have named controllers
const QStringList slP = sParm.mid ( 1 ).split ( '*' );

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

for ( int i = 1; i < slMIDIParams.count(); i++ )
bool bIsUInt = false;

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

// silently default incoherent count to 1
int iNum = 1;
if ( slP.count() == 2 )
{
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++ )
bIsUInt = false;
unsigned int u = slP[0].toUInt ( &bIsUInt );
if ( bIsUInt )
iNum = u;
}
slValid.append ( QString ( "%1*%2*%3" ).arg ( iCtrl ).arg ( iFirst ).arg ( iNum ) );
}

foreach ( QString sParm, slValid )
{
const QStringList slP = sParm.split ( '*' );
const EMidiCtlType eTyp = static_cast<EMidiCtlType> ( slP[0].toInt() );
const int iFirst = slP[0].toUInt();
const int iNum = slP[1].toUInt();
for ( int iOff = 0; iOff < iNum && iOff + iFirst <= 127 && iOff < MAX_NUM_CHANNELS; iOff++ )
{
// For MyChannel option, first offset is "MyChannel", then the rest are 0 to iNum-1 channels
if ( hasMyChannel )
{
aMidiCtls[iFirst + iOff] = { eTyp, iOff == 0 ? INVALID_INDEX : 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 @@ -357,22 +423,22 @@ 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 INVALID_INDEX
// 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:
case OurFader:
{
// we are assuming that the controller number is the same
// as the audio fader index and the range is 0-127
const int iFaderLevel = static_cast<int> ( static_cast<double> ( iValue ) / 127 * AUD_MIX_FADER_MAX );
const int iTheChannel = cCtrl.eType == OurFader ? INVALID_INDEX : cCtrl.iChannel;

// consider offset for the faders

emit ControllerInFaderLevel ( iTheChannel, iFaderLevel );
emit ControllerInFaderLevel ( cCtrl.iChannel, iFaderLevel );
}
break;
case Pan:
Expand Down
17 changes: 15 additions & 2 deletions src/sound/soundbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ enum EMidiCtlType
Solo,
Mute,
MuteMyself,
OurFader, // Proposed addition: a MidiCtrlType for our own fader level
MyChannel,
None
};

Expand Down Expand Up @@ -159,7 +159,20 @@ class CSoundBase : public QThread
QMutex MutexAudioProcessCallback;
QMutex MutexDevProperties;

QString strSystemDriverTechniqueName;
QString strSystemDriverTechniqueName;

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

int iCtrlMIDIChannel;
QVector<CMidiCtlEntry> aMidiCtls;

Expand Down

0 comments on commit 109d4eb

Please sign in to comment.