Skip to content

Commit

Permalink
Merge pull request #3431 from softins/windows-native-midi
Browse files Browse the repository at this point in the history
Add Windows native MIDI support
  • Loading branch information
softins authored Dec 6, 2024
2 parents 0c0cca7 + b6e902f commit 3905234
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 12 deletions.
5 changes: 5 additions & 0 deletions Jamulus.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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.)

Expand Down
17 changes: 17 additions & 0 deletions src/sound/asio/sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,23 @@ CSound::CSound ( void ( *fpNewCallback ) ( CVector<int16_t>& psData, void* arg )
asioCallbacks.sampleRateDidChange = &sampleRateChanged;
asioCallbacks.asioMessage = &asioMessages;
asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo;

// Optional MIDI initialization --------------------------------------------
if ( iCtrlMIDIChannel != INVALID_MIDI_CH )
{
Midi.MidiStart();
}
}

CSound::~CSound()
{
// stop MIDI if running
if ( iCtrlMIDIChannel != INVALID_MIDI_CH )
{
Midi.MidiStop();
}

UnloadCurrentDriver();
}

void CSound::ResetChannelMapping()
Expand Down
6 changes: 5 additions & 1 deletion src/sound/asio/sound.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -56,7 +57,7 @@ class CSound : public CSoundBase
public:
CSound ( void ( *fpNewCallback ) ( CVector<int16_t>& 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();
Expand Down Expand Up @@ -134,4 +135,7 @@ class CSound : public CSoundBase
static long asioMessages ( long selector, long value, void* message, double* opt );

char* cDriverNames[MAX_NUMBER_SOUND_CARDS];

// Windows native MIDI support
CMidi Midi;
};
130 changes: 130 additions & 0 deletions src/sound/midi-win/midi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/******************************************************************************\
* Copyright (c) 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()
{
QString selMIDIDevice = pSound->GetMIDIDevice();

/* Get the number of MIDI In devices in this computer */
iMidiDevs = midiInGetNumDevs();

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++ )
{
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 ) );
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 ) );
continue; // try next device, if any
}

qInfo() << qUtf8Printable ( QString ( " %1: %2" ).arg ( i ).arg ( midiDev ) );

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 ) );
continue; // try next device, if any
}

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 );
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 < vecMidiInHandles.size(); i++ )
{
midiInStop ( vecMidiInHandles.at ( i ) );
midiInClose ( vecMidiInHandles.at ( 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<uint8_t> vMIDIPaketBytes ( 3 );

vMIDIPaketBytes[0] = static_cast<uint8_t> ( status );
vMIDIPaketBytes[1] = static_cast<uint8_t> ( data1 );
vMIDIPaketBytes[2] = static_cast<uint8_t> ( data2 );

pSound->ParseMIDIMessage ( vMIDIPaketBytes );
}
}
54 changes: 54 additions & 0 deletions src/sound/midi-win/midi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/******************************************************************************\
* Copyright (c) 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"

/* Classes ********************************************************************/
class CMidi
{
public:
CMidi() {}

virtual ~CMidi() {}

void MidiStart();
void MidiStop();

protected:
int iMidiDevs;
QVector<HMIDIIN> vecMidiInHandles; // 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
27 changes: 18 additions & 9 deletions src/sound/soundbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ char const sMidiCtlChar[] = {
/* [EMidiCtlType::Solo] = */ 's',
/* [EMidiCtlType::Mute] = */ 'm',
/* [EMidiCtlType::MuteMyself] = */ 'o',
/* [EMidiCtlType::Device] = */ 'd',
/* [EMidiCtlType::None] = */ '\0' };

/* Implementation *************************************************************/
Expand Down Expand Up @@ -309,16 +310,24 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup )
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++ )
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 };
}
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/sound/soundbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum EMidiCtlType
Solo,
Mute,
MuteMyself,
Device,
None
};

Expand Down Expand Up @@ -106,13 +107,18 @@ class CSoundBase : public QThread

virtual void OpenDriverSetup() {}

virtual const QString& GetMIDIDevice() { return strMIDIDevice; }

bool IsRunning() const { return bRun; }
bool IsCallbackEntered() const { return bCallbackEntered; }

// TODO this should be protected but since it is used
// 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<uint8_t>& vMIDIPaketBytes );

protected:
virtual QString LoadAndInitializeDriver ( QString, bool ) { return ""; }
virtual void UnloadCurrentDriver() {}
Expand Down Expand Up @@ -151,8 +157,6 @@ class CSoundBase : public QThread
( *fpProcessCallback ) ( psData, pProcessCallbackArg );
}

void ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes );

bool bRun;
bool bCallbackEntered;
QMutex MutexAudioProcessCallback;
Expand All @@ -166,6 +170,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 );
Expand Down

0 comments on commit 3905234

Please sign in to comment.