Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f049a5
Add MIDI GUI tab and learn function
ignotus666 May 22, 2025
21d39a2
Fix clang-format issues
ignotus666 May 22, 2025
084fe9a
Delete extra line
ignotus666 May 22, 2025
6df6339
Fix GetNumericIniSet function range
ignotus666 May 23, 2025
9a2257f
Avoid repetitions in learn button handling
ignotus666 May 28, 2025
aedd894
Remove duplicate code for MIDI strings
ignotus666 May 28, 2025
6c2dc14
Clang-format errors corrected
ignotus666 May 28, 2025
a2c06db
Set autoDefault to false for Learn buttons
ignotus666 May 28, 2025
d4e1e58
SImplify learn button setup, add error handling, initialise learn but…
ignotus666 May 28, 2025
a36bc38
Add comment
ignotus666 May 29, 2025
03900bd
Add runtime MIDI in port toggle, remove pClient guards
ignotus666 Jun 2, 2025
b6d9f12
clang-format and remove android MIDI changes
ignotus666 Jun 2, 2025
d5dd70b
Add whats this for enable MIDI, correct tab order
ignotus666 Jun 2, 2025
455d062
Fix tabbing and whats this
ignotus666 Jun 2, 2025
0818f2b
Merge pull request #33 from ignotus666/midi-gui-learn2
ignotus666 Jun 2, 2025
b7faa03
Make clang-format happy
ignotus666 Jun 2, 2025
d694d14
Fix prepend user with number
ignotus666 Jun 2, 2025
280a372
Clang-format
ignotus666 Jun 2, 2025
09cd625
Fix show last opened tab
ignotus666 Jun 3, 2025
e6defec
Add curly braces
ignotus666 Jun 3, 2025
2970f5e
Fix MIDI-in port toggle misbehaving when launched enabled (macOS)
ignotus666 Jun 4, 2025
1909b62
Rename tab, improve layout and correct whats this
ignotus666 Jun 5, 2025
e35023d
Make check box text consistent
ignotus666 Jun 9, 2025
f718efa
Grey out MIDI controls when disabled
ignotus666 Jun 11, 2025
1e1759c
Correct tab order
ignotus666 Jun 11, 2025
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
6 changes: 6 additions & 0 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ CClient::CClient ( const quint16 iPortNumber,

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

QObject::connect ( &Sound, &CSoundBase::MidiCCReceived, this, &CClient::OnMidiCCReceived );

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

Expand Down Expand Up @@ -1550,6 +1552,10 @@ void CClient::FreeClientChannel ( const int iServerChannelID )
*/
}

void CClient::ApplyMIDIMapping ( const QString& midiMap ) { Sound.SetMIDIMapping ( midiMap ); }

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
6 changes: 6 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@ class CClient : public QObject
CChannelCoreInfo ChannelInfo;
QString strClientName;

void ApplyMIDIMapping ( const QString& midiMap );

protected:
// callback function must be static, otherwise it does not work
static void AudioCallback ( CVector<short>& psData, void* arg );
Expand Down Expand Up @@ -471,4 +473,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 );
};
7 changes: 5 additions & 2 deletions src/clientdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ CClientDlg::CClientDlg ( CClient* pNCliP,

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

pSettingsMenu->addAction ( tr ( "&MIDI Settings..." ), this, SLOT ( OnOpenMidiSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_M ) );

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

Expand Down Expand Up @@ -987,9 +989,8 @@ void CClientDlg::ShowGeneralSettings ( int iTab )
// open general settings dialog
emit SendTabChange ( iTab );
ClientSettingsDlg.show();
ClientSettingsDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Settings" ), pClient->strClientName ) );

// make sure dialog is upfront and has focus
ClientSettingsDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Settings" ), pClient->strClientName ) );
ClientSettingsDlg.raise();
ClientSettingsDlg.activateWindow();
}
Expand Down Expand Up @@ -1516,3 +1517,5 @@ void CClientDlg::SetPingTime ( const int iPingTime, const int iOverallDelayMs, c
// set current LED status
ledDelay->SetLight ( eOverallDelayLEDColor );
}

void CClientDlg::OnOpenMidiSettings() { ShowGeneralSettings ( SETTING_TAB_MIDI ); }
2 changes: 2 additions & 0 deletions src/clientdlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ public slots:

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

void OnOpenMidiSettings();

signals:
void SendTabChange ( int iTabIdx );
};
203 changes: 201 additions & 2 deletions src/clientsettingsdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSetP, QWidget* parent ) :
CBaseDlg ( parent, Qt::Window ), // use Qt::Window to get min/max window buttons
pClient ( pNCliP ),
pSettings ( pNSetP )
pSettings ( pNSetP ),
midiLearnTarget ( None )
{
setupUi ( this );

Expand Down Expand Up @@ -397,6 +398,39 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet
"A second sound device may be required to hear the alerts." ) );
chbAudioAlerts->setAccessibleName ( tr ( "Audio Alerts check box" ) );

// MIDI settings
QString strMidiSettings = "<b>" + tr ( "MIDI controller settings" ) + ":</b> " +
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 "
"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 "
"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 "
"applied to one’s own audio stream." ) +
"<br>" +
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 );
panGroup->setWhatsThis ( strMidiSettings );
soloGroup->setWhatsThis ( strMidiSettings );
muteGroup->setWhatsThis ( strMidiSettings );

spnChannel->setAccessibleName ( tr ( "MIDI channel spin box" ) );
spnMuteMyself->setAccessibleName ( tr ( "Mute Myself MIDI CC number spin box" ) );
spnFaderOffset->setAccessibleName ( tr ( "Fader offset spin box" ) );
spnPanOffset->setAccessibleName ( tr ( "Pan offset spin box" ) );
spnSoloOffset->setAccessibleName ( tr ( "Solo offset spin box" ) );
spnMuteOffset->setAccessibleName ( tr ( "Mute offset spin box" ) );
butLearnMuteMyself->setAccessibleName ( tr ( "Mute Myself MIDI learn button" ) );
butLearnFaderOffset->setAccessibleName ( tr ( "Fader offset MIDI learn button" ) );
butLearnPanOffset->setAccessibleName ( tr ( "Pan offset MIDI learn button" ) );
butLearnSoloOffset->setAccessibleName ( tr ( "Solo offset MIDI learn button" ) );
butLearnMuteOffset->setAccessibleName ( tr ( "Mute offset MIDI learn button" ) );

// init driver button
#if defined( _WIN32 ) && !defined( WITH_JACK )
butDriverSetup->setText ( tr ( "ASIO Device Settings" ) );
Expand Down Expand Up @@ -750,12 +784,78 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet

tabSettings->setCurrentIndex ( pSettings->iSettingsTab );

// MIDI tab
QObject::connect ( spnChannel, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiChannel = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnMuteMyself, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiMuteMyself = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnFaderOffset, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiFaderOffset = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnFaderCount, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiFaderCount = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnPanOffset, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiPanOffset = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnPanCount, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiPanCount = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnSoloOffset, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiSoloOffset = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnSoloCount, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiSoloCount = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnMuteOffset, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiMuteOffset = v;
ApplyMIDIMappingFromSettings();
} );

QObject::connect ( spnMuteCount, static_cast<void ( QSpinBox::* ) ( int )> ( &QSpinBox::valueChanged ), this, [this] ( int v ) {
pSettings->midiMuteCount = v;
ApplyMIDIMappingFromSettings();
} );

// MIDI Learn buttons
midiLearnButtons[0] = butLearnMuteMyself;
midiLearnButtons[1] = butLearnFaderOffset;
midiLearnButtons[2] = butLearnPanOffset;
midiLearnButtons[3] = butLearnSoloOffset;
midiLearnButtons[4] = butLearnMuteOffset;

for ( QPushButton* button : midiLearnButtons )
{
QObject::connect ( button, &QPushButton::clicked, this, &CClientSettingsDlg::OnLearnButtonClicked );
}

// Connect MIDI CC signal from sound engine
QObject::connect ( pClient, &CClient::MidiCCReceived, this, &CClientSettingsDlg::OnMidiCCReceived );

// Timers ------------------------------------------------------------------
// start timer for status bar
TimerStatus.start ( DISPLAY_UPDATE_TIME );
}

void CClientSettingsDlg::showEvent ( QShowEvent* )
void CClientSettingsDlg::showEvent ( QShowEvent* event )
{
UpdateDisplay();
UpdateDirectoryComboBox();
Expand All @@ -774,6 +874,20 @@ void CClientSettingsDlg::showEvent ( QShowEvent* )

// select the skill level
pcbxSkill->setCurrentIndex ( pcbxSkill->findData ( static_cast<int> ( pClient->ChannelInfo.eSkillLevel ) ) );

// MIDI tab: set widgets from settings
spnChannel->setValue ( pSettings->midiChannel );
spnMuteMyself->setValue ( pSettings->midiMuteMyself );
spnFaderOffset->setValue ( pSettings->midiFaderOffset );
spnFaderCount->setValue ( pSettings->midiFaderCount );
spnPanOffset->setValue ( pSettings->midiPanOffset );
spnPanCount->setValue ( pSettings->midiPanCount );
spnSoloOffset->setValue ( pSettings->midiSoloOffset );
spnSoloCount->setValue ( pSettings->midiSoloCount );
spnMuteOffset->setValue ( pSettings->midiMuteOffset );
spnMuteCount->setValue ( pSettings->midiMuteCount );

QDialog::showEvent ( event );
}

void CClientSettingsDlg::UpdateJitterBufferFrame()
Expand Down Expand Up @@ -1216,3 +1330,88 @@ void CClientSettingsDlg::OnAudioPanValueChanged ( int value )
pClient->SetAudioInFader ( value );
UpdateAudioFaderSlider();
}

void CClientSettingsDlg::ApplyMIDIMappingFromSettings() { pClient->ApplyMIDIMapping ( pSettings->GetMIDIMapString() ); }

void CClientSettingsDlg::ResetMidiLearn()
{
midiLearnTarget = None;
for ( QPushButton* button : midiLearnButtons )
{
button->setText ( tr ( "Learn" ) );
button->setEnabled ( true );
}
}

void CClientSettingsDlg::SetMidiLearnTarget ( MidiLearnTarget target, QPushButton* activeButton )
{
if ( midiLearnTarget == target )
{
ResetMidiLearn();
return;
}

ResetMidiLearn();
midiLearnTarget = target;
activeButton->setText ( tr ( "Listening..." ) );

// Disable all buttons except the active one
for ( QPushButton* button : midiLearnButtons )
{
button->setEnabled ( button == activeButton );
}
}

void CClientSettingsDlg::OnLearnButtonClicked()
{
QPushButton* sender = qobject_cast<QPushButton*> ( QObject::sender() );

MidiLearnTarget target = None;
if ( sender == butLearnMuteMyself )
target = MuteMyself;
else if ( sender == butLearnFaderOffset )
target = Fader;
else if ( sender == butLearnPanOffset )
target = Pan;
else if ( sender == butLearnSoloOffset )
target = Solo;
else if ( sender == butLearnMuteOffset )
target = Mute;

SetMidiLearnTarget ( target, sender );
}

void CClientSettingsDlg::OnMidiCCReceived ( int ccNumber )
{
if ( midiLearnTarget == None )
return;

// Validate MIDI CC number is within valid range (0-127)
if ( ccNumber < 0 || ccNumber > 127 )
{
qWarning() << "CClientSettingsDlg::OnMidiCCReceived: Invalid MIDI CC number received:" << ccNumber;
return;
}

switch ( midiLearnTarget )
{
case Fader:
spnFaderOffset->setValue ( ccNumber );
break;
case Pan:
spnPanOffset->setValue ( ccNumber );
break;
case Solo:
spnSoloOffset->setValue ( ccNumber );
break;
case Mute:
spnMuteOffset->setValue ( ccNumber );
break;
case MuteMyself:
spnMuteMyself->setValue ( ccNumber );
break;
default:
break;
}
ResetMidiLearn();
}
21 changes: 21 additions & 0 deletions src/clientsettingsdlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class CClientSettingsDlg : public CBaseDlg, private Ui_CClientSettingsDlgBase
void UpdateSoundCardFrame();
void UpdateDirectoryComboBox();
void UpdateAudioFaderSlider();
void ApplyMIDIMappingFromSettings();
QString GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText = "" );

virtual void showEvent ( QShowEvent* );
Expand Down Expand Up @@ -119,4 +120,24 @@ public slots:
void AudioChannelsChanged();
void CustomDirectoriesChanged();
void NumMixerPanelRowsChanged ( int value );

private:
enum MidiLearnTarget
{
None,
MuteMyself,
Fader,
Pan,
Solo,
Mute
};
MidiLearnTarget midiLearnTarget;

QPushButton* midiLearnButtons[5];
void SetMidiLearnTarget ( MidiLearnTarget target, QPushButton* activeButton );
void ResetMidiLearn();

private slots:
void OnLearnButtonClicked();
void OnMidiCCReceived ( int ccNumber );
};
Loading
Loading