Skip to content

Commit 713cfbc

Browse files
committed
Make "My Channel" work for all controls
1 parent b1eda2e commit 713cfbc

File tree

2 files changed

+158
-80
lines changed

2 files changed

+158
-80
lines changed

src/sound/soundbase.cpp

Lines changed: 144 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,6 @@
2424

2525
#include "soundbase.h"
2626

27-
// This is used as a lookup table for parsing option letters, mapping
28-
// a single character to an EMidiCtlType
29-
char const sMidiCtlChar[] = {
30-
// Has to follow order of EMidiCtlType
31-
/* [EMidiCtlType::Fader] = */ 'f',
32-
/* [EMidiCtlType::Pan] = */ 'p',
33-
/* [EMidiCtlType::Solo] = */ 's',
34-
/* [EMidiCtlType::Mute] = */ 'm',
35-
/* [EMidiCtlType::MuteMyself] = */ 'o',
36-
/* [EMidiCtlType::OurFader] = */ 'z', // Proposed addition: a new enum value for "our fader"
37-
/* [EMidiCtlType::None] = */ '\0' };
38-
3927
/* Implementation *************************************************************/
4028
CSoundBase::CSoundBase ( const QString& strNewSystemDriverTechniqueName,
4129
void ( *fpNewProcessCallback ) ( CVector<int16_t>& psData, void* pParg ),
@@ -233,98 +221,176 @@ QVector<QString> CSoundBase::LoadAndInitializeFirstValidDriver ( const bool bOpe
233221
}
234222

235223
/******************************************************************************\
236-
* MIDI handling *
224+
* Command Line Handling *
237225
\******************************************************************************/
238226
void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup )
239227
{
228+
if ( strMIDISetup.isEmpty() )
229+
return;
230+
240231
int iMIDIOffsetFader = 70; // Behringer X-TOUCH: offset of 0x46
241232

242-
// parse the server info string according to definition: there is
243-
// the legacy definition with just one or two numbers that only
244-
// provides a definition for the controller offset of the level
233+
// default to the legacy kind of specifying
234+
// the fader controller offset without an
235+
// indication of the count of controllers
236+
bool bSimple = true;
237+
238+
// Parse the --ctrlmidich string. There are two formats.
239+
//
240+
// The legacy definition has just one or two numbers that only
241+
// provides a definition for the controller offset of the fader
245242
// controllers (default 70 for the sake of Behringer X-Touch)
246-
// [MIDI channel];[offset for level]
243+
// [MIDI channel];[offset for first fader]
247244
//
248-
// The more verbose new form is a sequence of offsets for various
249-
// controllers: at the current point, 'f', 'p', 's', and 'm' are
250-
// parsed for fader, pan, solo, mute controllers respectively.
251-
// However, at the current point of time only 'f' and 'p'
252-
// controllers are actually implemented. The syntax for a Korg
253-
// nanoKONTROL2 with 8 fader controllers starting at offset 0 and
254-
// 8 pan controllers starting at offset 16 would be
245+
// The more verbose new form is a sequence of offsets and counts for various
246+
// controllers:
247+
// [MIDI channel];[control letter][offset]*[count](;...)
248+
// Currently, the following control letters are defined:
249+
// Fader - 'f'
250+
// Pan - 'p'
251+
// Solo - 's'
252+
// Mute - 'm'
253+
// 'offset' is the base MIDI CC number for the control.
254+
// 'count' is the number of CC values for the control (Jamulus channels).
255255
//
256+
// Additionally, 'o' has a single offset and controls Mute Myself.
257+
//
258+
// In addition, 'z' reserves the first CC number for a control
259+
// to mean "my Jamulus channel". (For example
260+
// 1;f0*9;z
261+
// would mean fader CC0 controlled "my" channel, with CC1 to CC8 for normal channels 1 to 8.
262+
//
263+
// An example for a Korg nanoKONTROL2 with 8 fader controllers
264+
// starting at offset 0 and 8 pan controllers starting at offset 16
265+
// would be
256266
// [MIDI channel];f0*8;p16*8
257267
//
258-
// Namely a sequence of letters indicating the kind of controller,
259-
// followed by the offset of the first such controller, followed
260-
// by * and a count for number of controllers (if more than 1)
261-
if ( !strMIDISetup.isEmpty() )
262-
{
263-
// split the different parameter strings
264-
const QStringList slMIDIParams = strMIDISetup.split ( ";" );
268+
// However, at the current point of time only 'f' and 'p'
269+
// controllers are actually implemented.
270+
271+
// split the different parameter strings
272+
const QStringList slMIDIParams = strMIDISetup.split ( ";" );
265273

266-
// [MIDI channel]
267-
if ( slMIDIParams.count() >= 1 )
274+
int iMIDIParamsStart = 0;
275+
if ( slMIDIParams.count() >= 1 )
276+
{
277+
bool bChOK = false;
278+
int i = slMIDIParams[0].toUInt ( &bChOK );
279+
if ( bChOK )
268280
{
269-
iCtrlMIDIChannel = slMIDIParams[0].toUInt();
281+
// [MIDI channel] supplied
282+
iCtrlMIDIChannel = i;
283+
iMIDIParamsStart = 1;
270284
}
285+
else
286+
{
287+
// Something else, use default channel and try new-style parsing
288+
bSimple = false;
289+
}
290+
}
271291

272-
bool bSimple = true; // Indicates the legacy kind of specifying
273-
// the fader controller offset without an
274-
// indication of the count of controllers
292+
if ( bSimple && slMIDIParams.count() >= 2 )
293+
{
294+
// if there is a second parameter that can be parsed as a number,
295+
// we have the legacy specification of controllers.
296+
int i = slMIDIParams[1].toUInt ( &bSimple );
297+
if ( bSimple )
298+
{
299+
// [offset for fader] supplied (else use default)
300+
iMIDIOffsetFader = i;
301+
}
302+
}
275303

276-
// [offset for level]
277-
if ( slMIDIParams.count() >= 2 )
304+
if ( bSimple )
305+
{
306+
// For the legacy specification, we consider every controller
307+
// up to the maximum number of channels (or the maximum
308+
// controller number) a fader.
309+
for ( int i = 0; i + iMIDIOffsetFader <= 127 && i < MAX_NUM_CHANNELS; i++ )
278310
{
279-
int i = slMIDIParams[1].toUInt ( &bSimple );
280-
// if the second parameter can be parsed as a number, we
281-
// have the legacy specification of controllers.
282-
if ( bSimple )
283-
iMIDIOffsetFader = i;
311+
// add a list entry for the CMidiCtlEntry
312+
aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i };
284313
}
314+
return;
315+
}
285316

286-
if ( bSimple )
317+
// We have named controllers
318+
// Validate and see whether "MyChannel" option is present
319+
320+
bool hasMyChannel = false;
321+
QStringList slValid;
322+
323+
// if the first param was not a number, use "any channel" and see if it is a valid param
324+
for ( int i = iMIDIParamsStart; i < slMIDIParams.count(); i++ )
325+
{
326+
QString sParm = slMIDIParams[i].trimmed();
327+
328+
// skip empty entries silently
329+
if ( sParm.isEmpty() )
330+
continue;
331+
332+
// skip unknown entries silently
333+
int iCtrl = sMidiCtl.indexOf ( sParm[0] );
334+
if ( iCtrl < 0 )
335+
continue;
336+
337+
// once seen, just remember this
338+
if ( static_cast<EMidiCtlType> ( iCtrl ) == EMidiCtlType::MyChannel )
287339
{
288-
// For the legacy specification, we consider every controller
289-
// up to the maximum number of channels (or the maximum
290-
// controller number) a fader.
291-
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
292-
{
293-
if ( i + iMIDIOffsetFader > 127 )
294-
break;
295-
aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i };
296-
}
297-
return;
340+
hasMyChannel = true;
341+
continue;
298342
}
299343

300-
// We have named controllers
344+
const QStringList slP = sParm.mid ( 1 ).split ( '*' );
345+
346+
// skip invalid entries silently
347+
if ( slP.count() > 2 )
348+
continue;
301349

302-
for ( int i = 1; i < slMIDIParams.count(); i++ )
350+
bool bIsUInt = false;
351+
352+
// skip invalid entries silently
353+
unsigned int u = slP[0].toUInt ( &bIsUInt );
354+
if ( !bIsUInt )
355+
continue;
356+
int iFirst = u;
357+
358+
// silently default incoherent count to 1
359+
int iNum = 1;
360+
if ( slP.count() == 2 )
303361
{
304-
QString sParm = slMIDIParams[i].trimmed();
305-
if ( sParm.isEmpty() )
306-
continue;
307-
308-
int iCtrl = QString ( sMidiCtlChar ).indexOf ( sParm[0] );
309-
if ( iCtrl < 0 )
310-
continue;
311-
EMidiCtlType eTyp = static_cast<EMidiCtlType> ( iCtrl );
312-
313-
const QStringList slP = sParm.mid ( 1 ).split ( '*' );
314-
int iFirst = slP[0].toUInt();
315-
int iNum = ( slP.count() > 1 ) ? slP[1].toUInt() : 1;
316-
for ( int iOff = 0; iOff < iNum; iOff++ )
362+
bIsUInt = false;
363+
unsigned int u = slP[0].toUInt ( &bIsUInt );
364+
if ( bIsUInt )
365+
iNum = u;
366+
}
367+
slValid.append ( QString ( "%1*%2*%3" ).arg ( iCtrl ).arg ( iFirst ).arg ( iNum ) );
368+
}
369+
370+
foreach ( QString sParm, slValid )
371+
{
372+
const QStringList slP = sParm.split ( '*' );
373+
const EMidiCtlType eTyp = static_cast<EMidiCtlType> ( slP[0].toInt() );
374+
const int iFirst = slP[0].toUInt();
375+
const int iNum = slP[1].toUInt();
376+
for ( int iOff = 0; iOff < iNum && iOff + iFirst <= 127 && iOff < MAX_NUM_CHANNELS; iOff++ )
377+
{
378+
// For MyChannel option, first offset is "MyChannel", then the rest are 0 to iNum-1 channels
379+
if ( hasMyChannel )
380+
{
381+
aMidiCtls[iFirst + iOff] = { eTyp, iOff == 0 ? INVALID_INDEX : iOff - 1 };
382+
}
383+
else
317384
{
318-
if ( iOff >= MAX_NUM_CHANNELS )
319-
break;
320-
if ( iFirst + iOff >= 128 )
321-
break;
322385
aMidiCtls[iFirst + iOff] = { eTyp, iOff };
323386
}
324387
}
325388
}
326389
}
327390

391+
/******************************************************************************\
392+
* MIDI handling *
393+
\******************************************************************************/
328394
void CSoundBase::ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes )
329395
{
330396
if ( vMIDIPaketBytes.Size() > 0 )
@@ -357,22 +423,22 @@ void CSoundBase::ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes )
357423
// make sure packet is long enough
358424
if ( vMIDIPaketBytes.Size() > 2 && vMIDIPaketBytes[1] <= uint8_t ( 127 ) && vMIDIPaketBytes[2] <= uint8_t ( 127 ) )
359425
{
426+
// Where "MyChannel" is in effect, cCtrl.iChannel will be INVALID_INDEX
427+
// for the first CC number in the range for a cCtrl.eType and then zero upwards.
360428
const CMidiCtlEntry& cCtrl = aMidiCtls[vMIDIPaketBytes[1]];
361429
const int iValue = vMIDIPaketBytes[2];
362-
;
430+
363431
switch ( cCtrl.eType )
364432
{
365433
case Fader:
366-
case OurFader:
367434
{
368435
// we are assuming that the controller number is the same
369436
// as the audio fader index and the range is 0-127
370437
const int iFaderLevel = static_cast<int> ( static_cast<double> ( iValue ) / 127 * AUD_MIX_FADER_MAX );
371-
const int iTheChannel = cCtrl.eType == OurFader ? INVALID_INDEX : cCtrl.iChannel;
372438

373439
// consider offset for the faders
374440

375-
emit ControllerInFaderLevel ( iTheChannel, iFaderLevel );
441+
emit ControllerInFaderLevel ( cCtrl.iChannel, iFaderLevel );
376442
}
377443
break;
378444
case Pan:

src/sound/soundbase.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ enum EMidiCtlType
4949
Solo,
5050
Mute,
5151
MuteMyself,
52-
OurFader, // Proposed addition: a MidiCtrlType for our own fader level
52+
MyChannel,
5353
None
5454
};
5555

@@ -159,7 +159,19 @@ class CSoundBase : public QThread
159159
QMutex MutexAudioProcessCallback;
160160
QMutex MutexDevProperties;
161161

162-
QString strSystemDriverTechniqueName;
162+
QString strSystemDriverTechniqueName;
163+
164+
// This is used as a lookup table for parsing option letters, mapping
165+
// a single character to an EMidiCtlType. Has to follow order of EMidiCtlType.
166+
const QString sMidiCtl = QString ( "%1%2%3%4%5%6" )
167+
.arg ( /* [EMidiCtlType::Fader] = */ 'f' )
168+
.arg ( /* [EMidiCtlType::Pan] = */ 'p' )
169+
.arg ( /* [EMidiCtlType::Solo] = */ 's' )
170+
.arg ( /* [EMidiCtlType::Mute] = */ 'm' )
171+
.arg ( /* [EMidiCtlType::MuteMyself] = */ 'o' )
172+
.arg ( /* [EMidiCtlType::MyChannel] = */ 'z' )
173+
.arg ( /* [EMidiCtlType::None] = */ '\0' );
174+
163175
int iCtrlMIDIChannel;
164176
QVector<CMidiCtlEntry> aMidiCtls;
165177

0 commit comments

Comments
 (0)