From f058359974acd6956f7226eb3f3e232fb5a782e7 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 28 Nov 2024 16:44:58 +0000 Subject: [PATCH 1/9] Added Windows native MIDI support --- src/sound/asio/sound.cpp | 74 ++++++++++++++++++++++++++++++++++++++++ src/sound/asio/sound.h | 10 +++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index e23609e7af..aaf8a51127 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -577,6 +577,23 @@ CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ) asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; + + // Optional MIDI initialization -------------------------------------------- + if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) + { + MidiStart(); + } +} + +CSound::~CSound() +{ + // stop MIDI if running + if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) + { + MidiStop(); + } + + UnloadCurrentDriver(); } void CSound::ResetChannelMapping() @@ -1218,3 +1235,60 @@ int64_t CSound::Flip64Bits ( const int64_t iIn ) return iOut; } + +// Windows Native MIDI support +void CSound::MidiStart() +{ + midiPort = 0; // might want to make this settable, Windows allocates device numbers in order + + MMRESULT result = midiInOpen ( &hMidiIn, midiPort, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << "! Failed to open MIDI input device. Error code: " << result; + hMidiIn = 0; + return; + } + + result = midiInStart ( hMidiIn ); + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << "! Failed to start MIDI input. Error code: " << result; + midiInClose ( hMidiIn ); + hMidiIn = 0; + return; + } +} + +void CSound::MidiStop() +{ + // stop MIDI if running + if ( hMidiIn != 0 ) + { + midiInStop ( hMidiIn ); + midiInClose ( hMidiIn ); + } +} + +void CALLBACK CSound::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) +{ + Q_UNUSED ( hMidiIn ); + Q_UNUSED ( dwInstance ); + Q_UNUSED ( dwParam2 ); + + if ( wMsg == MIM_DATA ) + { + BYTE status = dwParam1 & 0xFF; + BYTE data1 = ( dwParam1 >> 8 ) & 0xFF; + BYTE data2 = ( dwParam1 >> 16 ) & 0xFF; + + // copy packet and send it to the MIDI parser + CVector vMIDIPaketBytes ( 3 ); + + vMIDIPaketBytes[0] = static_cast ( status ); + vMIDIPaketBytes[1] = static_cast ( data1 ); + vMIDIPaketBytes[2] = static_cast ( data2 ); + + pSound->ParseMIDIMessage ( vMIDIPaketBytes ); + } +} diff --git a/src/sound/asio/sound.h b/src/sound/asio/sound.h index 3765a2f633..09773348c0 100644 --- a/src/sound/asio/sound.h +++ b/src/sound/asio/sound.h @@ -56,7 +56,7 @@ class CSound : public CSoundBase public: CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, const QString& ); - virtual ~CSound() { UnloadCurrentDriver(); } + virtual ~CSound(); virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); @@ -134,4 +134,12 @@ class CSound : public CSoundBase static long asioMessages ( long selector, long value, void* message, double* opt ); char* cDriverNames[MAX_NUMBER_SOUND_CARDS]; + + // Windows native MIDI + HMIDIIN hMidiIn; // windows handle + UINT midiPort; + + void MidiStart(); + void MidiStop(); + static void CALLBACK MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); }; From 09d739721de4033ca60ef5c12e5a354f104a05c2 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 30 Nov 2024 17:26:16 +0000 Subject: [PATCH 2/9] Add support for multiple devices --- src/sound/asio/sound.cpp | 49 ++++++++++++++++++++++++++-------------- src/sound/asio/sound.h | 6 +++-- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index aaf8a51127..41dc685eb5 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -1239,34 +1239,49 @@ int64_t CSound::Flip64Bits ( const int64_t iIn ) // Windows Native MIDI support void CSound::MidiStart() { - midiPort = 0; // might want to make this settable, Windows allocates device numbers in order - - MMRESULT result = midiInOpen ( &hMidiIn, midiPort, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); - - if ( result != MMSYSERR_NOERROR ) + /* Get the number of MIDI In devices in this computer */ + iMidiDevs = midiInGetNumDevs(); + if ( iMidiDevs > MAX_MIDI_DEVS ) { - qWarning() << "! Failed to open MIDI input device. Error code: " << result; - hMidiIn = 0; - return; + iMidiDevs = MAX_MIDI_DEVS; } - result = midiInStart ( hMidiIn ); - if ( result != MMSYSERR_NOERROR ) + // printf("Found %d MIDI device%s\n", iMidiDevs, iMidiDevs == 1 ? "" : "s"); + + // open all connected MIDI devices and set the callback function to handle incoming messages + for ( int i = 0; i < iMidiDevs; i++ ) { - qWarning() << "! Failed to start MIDI input. Error code: " << result; - midiInClose ( hMidiIn ); - hMidiIn = 0; - return; + MMRESULT result = midiInOpen ( &hMidiIn[i], i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << "! Failed to open MIDI input device. Error code: " << result; + hMidiIn[i] = 0; + return; + } + + result = midiInStart ( hMidiIn[i] ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << "! Failed to start MIDI input. Error code: " << result; + midiInClose ( hMidiIn[i] ); + hMidiIn[i] = 0; + return; + } } } void CSound::MidiStop() { // stop MIDI if running - if ( hMidiIn != 0 ) + for ( int i = 0; i < iMidiDevs; i++ ) { - midiInStop ( hMidiIn ); - midiInClose ( hMidiIn ); + if ( hMidiIn[i] != 0 ) + { + midiInStop ( hMidiIn[i] ); + midiInClose ( hMidiIn[i] ); + } } } diff --git a/src/sound/asio/sound.h b/src/sound/asio/sound.h index 09773348c0..7ae63f1473 100644 --- a/src/sound/asio/sound.h +++ b/src/sound/asio/sound.h @@ -136,8 +136,10 @@ class CSound : public CSoundBase char* cDriverNames[MAX_NUMBER_SOUND_CARDS]; // Windows native MIDI - HMIDIIN hMidiIn; // windows handle - UINT midiPort; +#define MAX_MIDI_DEVS 4 + + int iMidiDevs; + HMIDIIN hMidiIn[MAX_MIDI_DEVS]; // windows handles void MidiStart(); void MidiStop(); From be04d898513200042830a0344045bb28cabb5d5d Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 2 Dec 2024 11:31:02 +0000 Subject: [PATCH 3/9] Add comments with links to Windows MIDI API --- src/sound/asio/sound.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index 41dc685eb5..6e9930c807 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -1236,7 +1236,11 @@ int64_t CSound::Flip64Bits ( const int64_t iIn ) return iOut; } +//--------------------------------------------------------------------------------------- // Windows Native MIDI support +// +// For API, see https://learn.microsoft.com/en-gb/windows/win32/multimedia/midi-reference + void CSound::MidiStart() { /* Get the number of MIDI In devices in this computer */ @@ -1285,6 +1289,8 @@ void CSound::MidiStop() } } +// See https://learn.microsoft.com/en-us/previous-versions//dd798460(v=vs.85) +// for the definition of the MIDI input callback function. void CALLBACK CSound::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) { Q_UNUSED ( hMidiIn ); @@ -1293,6 +1299,8 @@ void CALLBACK CSound::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwIns if ( wMsg == MIM_DATA ) { + // See https://learn.microsoft.com/en-gb/windows/win32/multimedia/mim-data + // The three bytes of a MIDI message are encoded into the 32-bit dwParam1 parameter. BYTE status = dwParam1 & 0xFF; BYTE data1 = ( dwParam1 >> 8 ) & 0xFF; BYTE data2 = ( dwParam1 >> 16 ) & 0xFF; From 494f9e41ec555db1b569e7d10c39fd25d1f74ddf Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Tue, 3 Dec 2024 15:40:09 +0000 Subject: [PATCH 4/9] Re-organise Windows Midi into separate class for future re-use --- Jamulus.pro | 4 +- src/sound/asio/sound.cpp | 84 +----------------------- src/sound/asio/sound.h | 12 +--- src/sound/midi-win/midi.cpp | 125 ++++++++++++++++++++++++++++++++++++ src/sound/midi-win/midi.h | 57 ++++++++++++++++ src/sound/soundbase.h | 5 +- 6 files changed, 193 insertions(+), 94 deletions(-) create mode 100644 src/sound/midi-win/midi.cpp create mode 100644 src/sound/midi-win/midi.h diff --git a/Jamulus.pro b/Jamulus.pro index 3176affb22..5bb5760f44 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -155,8 +155,10 @@ win32 { } # Important: Keep those ASIO includes local to this build target in # order to avoid poisoning other builds license-wise. - HEADERS += src/sound/asio/sound.h + HEADERS += src/sound/asio/sound.h \ + src/sound/midi-win/midi.h SOURCES += src/sound/asio/sound.cpp \ + src/sound/midi-win/midi.cpp \ libs/ASIOSDK2/common/asio.cpp \ libs/ASIOSDK2/host/asiodrivers.cpp \ libs/ASIOSDK2/host/pc/asiolist.cpp diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index 6e9930c807..a1d1274b69 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -581,7 +581,7 @@ CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ) // Optional MIDI initialization -------------------------------------------- if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) { - MidiStart(); + Midi.MidiStart(); } } @@ -590,7 +590,7 @@ CSound::~CSound() // stop MIDI if running if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) { - MidiStop(); + Midi.MidiStop(); } UnloadCurrentDriver(); @@ -1235,83 +1235,3 @@ int64_t CSound::Flip64Bits ( const int64_t iIn ) return iOut; } - -//--------------------------------------------------------------------------------------- -// Windows Native MIDI support -// -// For API, see https://learn.microsoft.com/en-gb/windows/win32/multimedia/midi-reference - -void CSound::MidiStart() -{ - /* Get the number of MIDI In devices in this computer */ - iMidiDevs = midiInGetNumDevs(); - if ( iMidiDevs > MAX_MIDI_DEVS ) - { - iMidiDevs = MAX_MIDI_DEVS; - } - - // printf("Found %d MIDI device%s\n", iMidiDevs, iMidiDevs == 1 ? "" : "s"); - - // open all connected MIDI devices and set the callback function to handle incoming messages - for ( int i = 0; i < iMidiDevs; i++ ) - { - MMRESULT result = midiInOpen ( &hMidiIn[i], i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); - - if ( result != MMSYSERR_NOERROR ) - { - qWarning() << "! Failed to open MIDI input device. Error code: " << result; - hMidiIn[i] = 0; - return; - } - - result = midiInStart ( hMidiIn[i] ); - - if ( result != MMSYSERR_NOERROR ) - { - qWarning() << "! Failed to start MIDI input. Error code: " << result; - midiInClose ( hMidiIn[i] ); - hMidiIn[i] = 0; - return; - } - } -} - -void CSound::MidiStop() -{ - // stop MIDI if running - for ( int i = 0; i < iMidiDevs; i++ ) - { - if ( hMidiIn[i] != 0 ) - { - midiInStop ( hMidiIn[i] ); - midiInClose ( hMidiIn[i] ); - } - } -} - -// See https://learn.microsoft.com/en-us/previous-versions//dd798460(v=vs.85) -// for the definition of the MIDI input callback function. -void CALLBACK CSound::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) -{ - Q_UNUSED ( hMidiIn ); - Q_UNUSED ( dwInstance ); - Q_UNUSED ( dwParam2 ); - - if ( wMsg == MIM_DATA ) - { - // See https://learn.microsoft.com/en-gb/windows/win32/multimedia/mim-data - // The three bytes of a MIDI message are encoded into the 32-bit dwParam1 parameter. - BYTE status = dwParam1 & 0xFF; - BYTE data1 = ( dwParam1 >> 8 ) & 0xFF; - BYTE data2 = ( dwParam1 >> 16 ) & 0xFF; - - // copy packet and send it to the MIDI parser - CVector vMIDIPaketBytes ( 3 ); - - vMIDIPaketBytes[0] = static_cast ( status ); - vMIDIPaketBytes[1] = static_cast ( data1 ); - vMIDIPaketBytes[2] = static_cast ( data2 ); - - pSound->ParseMIDIMessage ( vMIDIPaketBytes ); - } -} diff --git a/src/sound/asio/sound.h b/src/sound/asio/sound.h index 7ae63f1473..efd08cfbe5 100644 --- a/src/sound/asio/sound.h +++ b/src/sound/asio/sound.h @@ -29,6 +29,7 @@ #include "../../util.h" #include "../../global.h" #include "../soundbase.h" +#include "../midi-win/midi.h" // The following includes require the ASIO SDK to be placed in // libs/ASIOSDK2 during build. @@ -135,13 +136,6 @@ class CSound : public CSoundBase char* cDriverNames[MAX_NUMBER_SOUND_CARDS]; - // Windows native MIDI -#define MAX_MIDI_DEVS 4 - - int iMidiDevs; - HMIDIIN hMidiIn[MAX_MIDI_DEVS]; // windows handles - - void MidiStart(); - void MidiStop(); - static void CALLBACK MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); + // Windows native MIDI support + CMidi Midi; }; diff --git a/src/sound/midi-win/midi.cpp b/src/sound/midi-win/midi.cpp new file mode 100644 index 0000000000..dacd553e2c --- /dev/null +++ b/src/sound/midi-win/midi.cpp @@ -0,0 +1,125 @@ +/******************************************************************************\ + * Copyright (c) 2004-2024 + * + * Author(s): + * Tony Mountifield + * + * Description: + * MIDI interface for Windows operating systems + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#include "midi.h" + +/* Implementation *************************************************************/ + +// pointer to the sound object (for passing received MIDI messages upwards) +extern CSound* pSound; + +//--------------------------------------------------------------------------------------- +// Windows Native MIDI support +// +// For API, see https://learn.microsoft.com/en-gb/windows/win32/multimedia/midi-reference + +void CMidi::MidiStart() +{ + /* Get the number of MIDI In devices in this computer */ + iMidiDevs = midiInGetNumDevs(); + if ( iMidiDevs > MAX_MIDI_DEVS ) + { + iMidiDevs = MAX_MIDI_DEVS; + } + + qInfo() << qUtf8Printable ( QString ( "- MIDI devices found: %1" ).arg ( iMidiDevs ) ); + + // open all connected MIDI devices and set the callback function to handle incoming messages + for ( int i = 0; i < iMidiDevs; i++ ) + { + MIDIINCAPS mic; + + MMRESULT result = midiInGetDevCaps ( i, &mic, sizeof ( MIDIINCAPS ) ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << qUtf8Printable ( QString ( "! Failed to identify MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); + continue; // try next device, if any + } + + qInfo() << qUtf8Printable ( QString ( " %1: %2" ).arg ( i ).arg ( mic.szPname ) ); + + result = midiInOpen ( &hMidiIn[i], i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << qUtf8Printable ( QString ( "! Failed to open MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); + hMidiIn[i] = 0; + continue; // try next device, if any + } + + result = midiInStart ( hMidiIn[i] ); + + if ( result != MMSYSERR_NOERROR ) + { + qWarning() << qUtf8Printable ( QString ( "! Failed to start MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); + midiInClose ( hMidiIn[i] ); + hMidiIn[i] = 0; + continue; // try next device, if any + } + } +} + +void CMidi::MidiStop() +{ + // stop MIDI if running + for ( int i = 0; i < iMidiDevs; i++ ) + { + if ( hMidiIn[i] != 0 ) + { + midiInStop ( hMidiIn[i] ); + midiInClose ( hMidiIn[i] ); + } + } +} + +// See https://learn.microsoft.com/en-us/previous-versions//dd798460(v=vs.85) +// for the definition of the MIDI input callback function. +void CALLBACK CMidi::MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) +{ + Q_UNUSED ( hMidiIn ); + Q_UNUSED ( dwInstance ); + Q_UNUSED ( dwParam2 ); + + if ( wMsg == MIM_DATA ) + { + // See https://learn.microsoft.com/en-gb/windows/win32/multimedia/mim-data + // The three bytes of a MIDI message are encoded into the 32-bit dwParam1 parameter. + BYTE status = dwParam1 & 0xFF; + BYTE data1 = ( dwParam1 >> 8 ) & 0xFF; + BYTE data2 = ( dwParam1 >> 16 ) & 0xFF; + + // copy packet and send it to the MIDI parser + CVector vMIDIPaketBytes ( 3 ); + + vMIDIPaketBytes[0] = static_cast ( status ); + vMIDIPaketBytes[1] = static_cast ( data1 ); + vMIDIPaketBytes[2] = static_cast ( data2 ); + + pSound->ParseMIDIMessage ( vMIDIPaketBytes ); + } +} diff --git a/src/sound/midi-win/midi.h b/src/sound/midi-win/midi.h new file mode 100644 index 0000000000..5c9ba65301 --- /dev/null +++ b/src/sound/midi-win/midi.h @@ -0,0 +1,57 @@ +/******************************************************************************\ + * Copyright (c) 2004-2024 + * + * Author(s): + * Tony Mountifield + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#pragma once + +#include "../../util.h" +#include "../../global.h" + +// Max number of Windows native MIDI interfaces to support +#define MAX_MIDI_DEVS 8 + +/* Classes ********************************************************************/ +class CMidi +{ +public: + CMidi() {} + + virtual ~CMidi() {} + + void MidiStart(); + void MidiStop(); + +protected: + int iMidiDevs; + HMIDIIN hMidiIn[MAX_MIDI_DEVS]; // windows handles + + static void CALLBACK MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); +}; + +// Provide the definition of CSound for the MIDI callback +// This must be after the above definition of CMidi +#if defined( JACK_ON_WINDOWS ) +# include "sound/jack/sound.h" +#else +# include "sound/asio/sound.h" +#endif diff --git a/src/sound/soundbase.h b/src/sound/soundbase.h index 4aa6c629bc..293d6b1b95 100644 --- a/src/sound/soundbase.h +++ b/src/sound/soundbase.h @@ -113,6 +113,9 @@ class CSoundBase : public QThread // in a callback function it has to be public -> better solution void EmitReinitRequestSignal ( const ESndCrdResetType eSndCrdResetType ) { emit ReinitRequest ( eSndCrdResetType ); } + // this needs to be public so that it can be called from CMidi + void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); + protected: virtual QString LoadAndInitializeDriver ( QString, bool ) { return ""; } virtual void UnloadCurrentDriver() {} @@ -151,8 +154,6 @@ class CSoundBase : public QThread ( *fpProcessCallback ) ( psData, pProcessCallbackArg ); } - void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); - bool bRun; bool bCallbackEntered; QMutex MutexAudioProcessCallback; From 7e111009cf742178fd848664a973fb3265b38381 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Tue, 3 Dec 2024 23:13:07 +0000 Subject: [PATCH 5/9] Add d option to ctrlmidich for device name --- src/sound/soundbase.cpp | 27 ++++++++++++++++++--------- src/sound/soundbase.h | 3 +++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/sound/soundbase.cpp b/src/sound/soundbase.cpp index 464b81bd41..10f44e66cc 100644 --- a/src/sound/soundbase.cpp +++ b/src/sound/soundbase.cpp @@ -33,6 +33,7 @@ char const sMidiCtlChar[] = { /* [EMidiCtlType::Solo] = */ 's', /* [EMidiCtlType::Mute] = */ 'm', /* [EMidiCtlType::MuteMyself] = */ 'o', + /* [EMidiCtlType::Device] = */ 'd', /* [EMidiCtlType::None] = */ '\0' }; /* Implementation *************************************************************/ @@ -309,16 +310,24 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) continue; EMidiCtlType eTyp = static_cast ( 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++ ) + if ( eTyp == Device ) { - if ( iOff >= MAX_NUM_CHANNELS ) - break; - if ( iFirst + iOff >= 128 ) - break; - aMidiCtls[iFirst + iOff] = { eTyp, iOff }; + // save MIDI device name to select + strMIDIDevice = sParm.mid ( 1 ); + } + else + { + 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++ ) + { + if ( iOff >= MAX_NUM_CHANNELS ) + break; + if ( iFirst + iOff >= 128 ) + break; + aMidiCtls[iFirst + iOff] = { eTyp, iOff }; + } } } } diff --git a/src/sound/soundbase.h b/src/sound/soundbase.h index 293d6b1b95..4cd94b551f 100644 --- a/src/sound/soundbase.h +++ b/src/sound/soundbase.h @@ -49,6 +49,7 @@ enum EMidiCtlType Solo, Mute, MuteMyself, + Device, None }; @@ -167,6 +168,8 @@ class CSoundBase : public QThread QString strCurDevName; QString strDriverNames[MAX_NUMBER_SOUND_CARDS]; + QString strMIDIDevice; + signals: void ReinitRequest ( int iSndCrdResetType ); void ControllerInFaderLevel ( int iChannelIdx, int iValue ); From 82182e9b609a6833a6ba80b9c48a2fde7c407ebd Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 4 Dec 2024 16:01:40 +0000 Subject: [PATCH 6/9] Only open selected MIDI device if specified --- src/sound/midi-win/midi.cpp | 14 +++++++++++++- src/sound/soundbase.cpp | 2 +- src/sound/soundbase.h | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sound/midi-win/midi.cpp b/src/sound/midi-win/midi.cpp index dacd553e2c..3a06bf7932 100644 --- a/src/sound/midi-win/midi.cpp +++ b/src/sound/midi-win/midi.cpp @@ -39,6 +39,8 @@ extern CSound* pSound; void CMidi::MidiStart() { + QString selMIDIDevice = pSound->GetMIDIDevice(); + /* Get the number of MIDI In devices in this computer */ iMidiDevs = midiInGetNumDevs(); if ( iMidiDevs > MAX_MIDI_DEVS ) @@ -58,10 +60,20 @@ void CMidi::MidiStart() if ( result != MMSYSERR_NOERROR ) { qWarning() << qUtf8Printable ( QString ( "! Failed to identify MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); + hMidiIn[i] = 0; + continue; // try next device, if any + } + + QString midiDev ( mic.szPname ); + + if ( !selMIDIDevice.isEmpty() && selMIDIDevice != midiDev ) + { + qInfo() << qUtf8Printable ( QString ( " %1: %2 (ignored)" ).arg ( i ).arg ( midiDev ) ); + hMidiIn[i] = 0; continue; // try next device, if any } - qInfo() << qUtf8Printable ( QString ( " %1: %2" ).arg ( i ).arg ( mic.szPname ) ); + qInfo() << qUtf8Printable ( QString ( " %1: %2" ).arg ( i ).arg ( midiDev ) ); result = midiInOpen ( &hMidiIn[i], i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); diff --git a/src/sound/soundbase.cpp b/src/sound/soundbase.cpp index 10f44e66cc..3d3be8d8a2 100644 --- a/src/sound/soundbase.cpp +++ b/src/sound/soundbase.cpp @@ -313,7 +313,7 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) if ( eTyp == Device ) { // save MIDI device name to select - strMIDIDevice = sParm.mid ( 1 ); + strMIDIDevice = sParm.mid ( 1 ); } else { diff --git a/src/sound/soundbase.h b/src/sound/soundbase.h index 4cd94b551f..bcfa21243a 100644 --- a/src/sound/soundbase.h +++ b/src/sound/soundbase.h @@ -107,6 +107,8 @@ class CSoundBase : public QThread virtual void OpenDriverSetup() {} + virtual const QString& GetMIDIDevice() { return strMIDIDevice; } + bool IsRunning() const { return bRun; } bool IsCallbackEntered() const { return bCallbackEntered; } From b2d502cab0b95036c2afcedc3bebce447f82df04 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 4 Dec 2024 16:41:35 +0000 Subject: [PATCH 7/9] Remove limit on MIDI device numbers --- src/sound/midi-win/midi.cpp | 29 +++++++++++------------------ src/sound/midi-win/midi.h | 7 ++----- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/sound/midi-win/midi.cpp b/src/sound/midi-win/midi.cpp index 3a06bf7932..ef4c6a9aff 100644 --- a/src/sound/midi-win/midi.cpp +++ b/src/sound/midi-win/midi.cpp @@ -43,24 +43,20 @@ void CMidi::MidiStart() /* Get the number of MIDI In devices in this computer */ iMidiDevs = midiInGetNumDevs(); - if ( iMidiDevs > MAX_MIDI_DEVS ) - { - iMidiDevs = MAX_MIDI_DEVS; - } qInfo() << qUtf8Printable ( QString ( "- MIDI devices found: %1" ).arg ( iMidiDevs ) ); // open all connected MIDI devices and set the callback function to handle incoming messages for ( int i = 0; i < iMidiDevs; i++ ) { - MIDIINCAPS mic; + HMIDIIN hMidiIn; // windows handle + MIDIINCAPS mic; // device name and capabilities MMRESULT result = midiInGetDevCaps ( i, &mic, sizeof ( MIDIINCAPS ) ); if ( result != MMSYSERR_NOERROR ) { qWarning() << qUtf8Printable ( QString ( "! Failed to identify MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); - hMidiIn[i] = 0; continue; // try next device, if any } @@ -69,43 +65,40 @@ void CMidi::MidiStart() if ( !selMIDIDevice.isEmpty() && selMIDIDevice != midiDev ) { qInfo() << qUtf8Printable ( QString ( " %1: %2 (ignored)" ).arg ( i ).arg ( midiDev ) ); - hMidiIn[i] = 0; continue; // try next device, if any } qInfo() << qUtf8Printable ( QString ( " %1: %2" ).arg ( i ).arg ( midiDev ) ); - result = midiInOpen ( &hMidiIn[i], i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); + result = midiInOpen ( &hMidiIn, i, (DWORD_PTR) MidiCallback, 0, CALLBACK_FUNCTION ); if ( result != MMSYSERR_NOERROR ) { qWarning() << qUtf8Printable ( QString ( "! Failed to open MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); - hMidiIn[i] = 0; continue; // try next device, if any } - result = midiInStart ( hMidiIn[i] ); + result = midiInStart ( hMidiIn ); if ( result != MMSYSERR_NOERROR ) { qWarning() << qUtf8Printable ( QString ( "! Failed to start MIDI input device %1. Error code: %2" ).arg ( i ).arg ( result ) ); - midiInClose ( hMidiIn[i] ); - hMidiIn[i] = 0; + midiInClose ( hMidiIn ); continue; // try next device, if any } + + // success, add it to list of open handles + vecMidiInHandles.append ( hMidiIn ); } } void CMidi::MidiStop() { // stop MIDI if running - for ( int i = 0; i < iMidiDevs; i++ ) + for ( int i = 0; i < vecMidiInHandles.size(); i++ ) { - if ( hMidiIn[i] != 0 ) - { - midiInStop ( hMidiIn[i] ); - midiInClose ( hMidiIn[i] ); - } + midiInStop ( vecMidiInHandles.at ( i ) ); + midiInClose ( vecMidiInHandles.at ( i ) ); } } diff --git a/src/sound/midi-win/midi.h b/src/sound/midi-win/midi.h index 5c9ba65301..0bdd56f4b8 100644 --- a/src/sound/midi-win/midi.h +++ b/src/sound/midi-win/midi.h @@ -27,9 +27,6 @@ #include "../../util.h" #include "../../global.h" -// Max number of Windows native MIDI interfaces to support -#define MAX_MIDI_DEVS 8 - /* Classes ********************************************************************/ class CMidi { @@ -42,8 +39,8 @@ class CMidi void MidiStop(); protected: - int iMidiDevs; - HMIDIIN hMidiIn[MAX_MIDI_DEVS]; // windows handles + int iMidiDevs; + QVector vecMidiInHandles; // windows handles static void CALLBACK MidiCallback ( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); }; From 97a3508b0bab192b3bdc1ad0812cfb1baf9475d2 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 4 Dec 2024 17:38:53 +0000 Subject: [PATCH 8/9] Keep MIDI and ASIO separate in Jamulus.pro --- Jamulus.pro | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index 5bb5760f44..3d1d5acd45 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -147,6 +147,11 @@ win32 { INCLUDEPATH += "$${programfilesdir}/JACK2/include" LIBS += "$${programfilesdir}/JACK2/lib/$${libjackname}" } else { + message(Using native Windows MIDI.) + + HEADERS += src/sound/midi-win/midi.h + SOURCES += src/sound/midi-win/midi.cpp + message(Using ASIO.) message(Please review the ASIO SDK licence.) @@ -155,10 +160,8 @@ win32 { } # Important: Keep those ASIO includes local to this build target in # order to avoid poisoning other builds license-wise. - HEADERS += src/sound/asio/sound.h \ - src/sound/midi-win/midi.h + HEADERS += src/sound/asio/sound.h SOURCES += src/sound/asio/sound.cpp \ - src/sound/midi-win/midi.cpp \ libs/ASIOSDK2/common/asio.cpp \ libs/ASIOSDK2/host/asiodrivers.cpp \ libs/ASIOSDK2/host/pc/asiolist.cpp From b6e902f6f599efb0426668dc0f5d85164b22e99b Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 4 Dec 2024 17:44:43 +0000 Subject: [PATCH 9/9] Correct copyright year for the new files --- src/sound/midi-win/midi.cpp | 2 +- src/sound/midi-win/midi.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sound/midi-win/midi.cpp b/src/sound/midi-win/midi.cpp index ef4c6a9aff..6d58b771ee 100644 --- a/src/sound/midi-win/midi.cpp +++ b/src/sound/midi-win/midi.cpp @@ -1,5 +1,5 @@ /******************************************************************************\ - * Copyright (c) 2004-2024 + * Copyright (c) 2024 * * Author(s): * Tony Mountifield diff --git a/src/sound/midi-win/midi.h b/src/sound/midi-win/midi.h index 0bdd56f4b8..5ad1ab9c8d 100644 --- a/src/sound/midi-win/midi.h +++ b/src/sound/midi-win/midi.h @@ -1,5 +1,5 @@ /******************************************************************************\ - * Copyright (c) 2004-2024 + * Copyright (c) 2024 * * Author(s): * Tony Mountifield