From fc4a10f189417f5231cfb5032171c6acce7cdecf Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Sat, 8 Feb 2025 23:08:20 -0500 Subject: [PATCH 1/3] Debugging 4-byte 0-value data getting to the BS2UMPMidiTransform --- src/api/Client/WinMM/MidiSrvPort.cpp | 68 +++-- src/api/Client/WinMM/MidiSrvPort.h | 276 +++++++++--------- .../LibMidi2Tests.cpp | 85 +++++- .../Midi2.Transform.unittests/LibMidi2Tests.h | 7 +- src/app-sdk/midi1monitor/pch.h | 3 - 5 files changed, 253 insertions(+), 186 deletions(-) diff --git a/src/api/Client/WinMM/MidiSrvPort.cpp b/src/api/Client/WinMM/MidiSrvPort.cpp index ae9ade06..19885622 100644 --- a/src/api/Client/WinMM/MidiSrvPort.cpp +++ b/src/api/Client/WinMM/MidiSrvPort.cpp @@ -463,7 +463,7 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON BYTE inProgressMidiMessage[3]{ 0 }; // status, data 1, data 2 BYTE countMidiMessageBytesReceived = 0; - BYTE immediateSingleByteMessage{ 0 }; // real time or other single-byte message that could interrupt the in-progress message + BYTE prioritySingleByteMessage{ 0 }; // real time or other single-byte message that could interrupt the in-progress message LPMIDIHDR buffer{ nullptr }; @@ -479,7 +479,7 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON // do not add it to the inProgressMidiMessage or any buffer even though // this shouldn't happen with midisrv-supplied message data - immediateSingleByteMessage = *callbackData; + prioritySingleByteMessage = *callbackData; } else if (!m_IsInSysex) { @@ -488,7 +488,10 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON { // we're not in SysEx mode, but this byte was provided // so we'll send it anyway - immediateSingleByteMessage = *callbackData; + // Convert from the QPC time to the number of ticks elapsed from the start time. + DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); + DWORD_PTR midiMessage = *callbackData; + WinmmClientCallback(MIM_ERROR, midiMessage, ticks); } else if (MIDI_BYTE_IS_SYSEX_START_STATUS(*callbackData)) { @@ -539,10 +542,9 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON { // Handle a data byte, and not sysex. - if (countMidiMessageBytesReceived > 0 && countMidiMessageBytesReceived < 3) + if (countMidiMessageBytesReceived >= 1 && countMidiMessageBytesReceived < 3) { - inProgressMidiMessage[countMidiMessageBytesReceived] = *callbackData; - countMidiMessageBytesReceived++; + inProgressMidiMessage[countMidiMessageBytesReceived++] = *callbackData; } else { @@ -551,9 +553,9 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON // to do that. We could even put in a reg setting to control this behavior. // Convert from the QPC time to the number of ticks elapsed from the start time. - DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); - UINT32 midiMessage = *callbackData; - WinmmClientCallback(MIM_ERROR, midiMessage, ticks); + //DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); + //DWORD_PTR midiMessage = *callbackData; + //WinmmClientCallback(MIM_ERROR, midiMessage, ticks); } } } @@ -621,40 +623,46 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON } - if (immediateSingleByteMessage > 0) + if (MIDI_BYTE_IS_STATUS_BYTE(prioritySingleByteMessage)) { // Convert from the QPC time to the number of ticks elapsed from the start time. DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); - UINT32 midiMessage = *callbackData; + DWORD_PTR midiMessage{ prioritySingleByteMessage }; WinmmClientCallback(MIM_DATA, midiMessage, ticks); - immediateSingleByteMessage = 0; + prioritySingleByteMessage = 0; } // do we have a complete non-SysEx message to send? If so, send it - if (countMidiMessageBytesReceived == 3 && MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0]) || - countMidiMessageBytesReceived == 2 && MIDI_MESSAGE_IS_TWO_BYTES(inProgressMidiMessage[0]) || - countMidiMessageBytesReceived == 1 && MIDI_MESSAGE_IS_ONE_BYTE(inProgressMidiMessage[0])) + if (countMidiMessageBytesReceived > 0) { - // Convert from the QPC time to the number of ticks elapsed from the start time. - DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); - UINT32 midiMessage{ inProgressMidiMessage[0] }; - - if (MIDI_MESSAGE_IS_TWO_BYTES(inProgressMidiMessage[0]) || MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0])) + if (countMidiMessageBytesReceived == 3 && MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0]) || + countMidiMessageBytesReceived == 2 && MIDI_MESSAGE_IS_TWO_BYTES(inProgressMidiMessage[0]) || + countMidiMessageBytesReceived == 1 && MIDI_MESSAGE_IS_ONE_BYTE(inProgressMidiMessage[0])) { - midiMessage |= (inProgressMidiMessage[1] << 8); - } + // Convert from the QPC time to the number of ticks elapsed from the start time. + DWORD ticks = (DWORD)(((position - startTime) / m_qpcFrequency) * 1000.0); + DWORD_PTR midiMessage{ static_cast(inProgressMidiMessage[0]) }; - if (MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0])) - { - midiMessage |= (inProgressMidiMessage[2] << 16); - } + if (MIDI_MESSAGE_IS_TWO_BYTES(inProgressMidiMessage[0]) || MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0])) + { + midiMessage |= (static_cast(inProgressMidiMessage[1]) << 8); + } - WinmmClientCallback(MIM_DATA, midiMessage, ticks); + if (MIDI_MESSAGE_IS_THREE_BYTES(inProgressMidiMessage[0])) + { + midiMessage |= (static_cast(inProgressMidiMessage[2]) << 16); + } - // reset for next message - countMidiMessageBytesReceived = 0; - inProgressMidiMessage[0] = 0; + WinmmClientCallback(MIM_DATA, midiMessage, ticks); + + // reset for next message + countMidiMessageBytesReceived = 0; + + inProgressMidiMessage[0] = 0; + inProgressMidiMessage[1] = 0; + inProgressMidiMessage[2] = 0; + } } // move to the next byte of data in the loop diff --git a/src/api/Client/WinMM/MidiSrvPort.h b/src/api/Client/WinMM/MidiSrvPort.h index 5d2c28c1..f94b6239 100644 --- a/src/api/Client/WinMM/MidiSrvPort.h +++ b/src/api/Client/WinMM/MidiSrvPort.h @@ -1,139 +1,137 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. - -#pragma once - -#define MIDI_NOTEOFF 0x80 // MIDI_NOTEOFF -#define MIDI_NOTEON 0x90 // MIDI_NOTEON -#define MIDI_POLYAFTERTOUCH 0xa0 // MIDI_POLYAFTERTOUCH -#define MIDI_CONTROLCHANGE 0xb0 // MIDI_CONTROLCHANGE -#define MIDI_PROGRAMCHANGE 0xc0 // MIDI_PROGRAMCHANGE -#define MIDI_MONOAFTERTOUCH 0xd0 // MIDI_MONOAFTERTOUCH (channel pressure) -#define MIDI_PITCHBEND 0xe0 // MIDI_PITCHBEND -#define MIDI_SYSEX 0xf0 // MIDI_SYSEX -#define MIDI_TIMECODE 0xf1 // MIDI_TIMECODE -#define MIDI_SONGPOSITIONPOINTER 0xf2 // MIDI_SONGPOSITIONPOINTER -#define MIDI_SONGSELECT 0xf3 // MIDI_SONGSELECT -#define MIDI_TUNEREQUEST 0xf6 // MIDI_TUNEREQUEST -#define MIDI_EOX 0xf7 // MIDI_EOX -#define MIDI_TIMINGCLOCK 0xf8 // MIDI_TIMINGCLOCK -#define MIDI_START 0xfa // MIDI_START -#define MIDI_CONTINUE 0xfb // MIDI_CONTINUE -#define MIDI_STOP 0xfc // MIDI_STOP -#define MIDI_ACTIVESENSE 0xfe // MIDI_ACTIVESENSE -#define MIDI_RESET 0xff // MIDI_RESET - -#define MIDI_STATUSBYTEFILTER 0x80 // filter for MIDI status byte to remove channel info - // a status byte must have the high bit set (0x80) -#define MIDI_SYSTEM_REALTIME_FILTER 0xf8 // filter for MIDI system realtime messages - // a system realtime message must have all these bits set - - -// For reference, this site is better than the formal MIDI 1.0 specs in many ways -// http://midi.teragonaudio.com/tech/midispec.htm - -#define MIDI_MESSAGE_IS_THREE_BYTES(status) ( \ - status == MIDI_NOTEOFF || \ - status == MIDI_NOTEON || \ - status == MIDI_POLYAFTERTOUCH || \ - status == MIDI_CONTROLCHANGE || \ - status == MIDI_PITCHBEND || \ - status == MIDI_SONGPOSITIONPOINTER) - -#define MIDI_MESSAGE_IS_TWO_BYTES(status) ( \ - status == MIDI_PROGRAMCHANGE || \ - status == MIDI_MONOAFTERTOUCH || \ - status == MIDI_TIMECODE || \ - status == MIDI_SONGSELECT) - -#define MIDI_MESSAGE_IS_ONE_BYTE(status) ( \ - status == MIDI_PROGRAMCHANGE || \ - status == MIDI_MONOAFTERTOUCH || \ - status == MIDI_TIMINGCLOCK || \ - status == MIDI_TUNEREQUEST || \ - status == MIDI_START || \ - status == MIDI_CONTINUE || \ - status == MIDI_STOP || \ - status == MIDI_ACTIVESENSE || \ - status == MIDI_RESET || \ - status == MIDI_EOX) - - -#define MIDI_MESSAGE_TERMINATES_RUNNING_STATUS(status) (\ - status >= MIDI_SYSEX && \ - status <= MIDI_EOX ) - -#define MIDI_STATUS_SUPPORTS_RUNNING_STATUS(status) (\ - status >= MIDI_NOTEOFF && \ - status < MIDI_SYSEX) - -#define MIDI_BYTE_IS_STATUS_BYTE(b) (\ - (b & MIDI_STATUSBYTEFILTER) == MIDI_STATUSBYTEFILTER) - -#define MIDI_BYTE_IS_SYSTEM_REALTIME_STATUS(b) (\ - (b & MIDI_SYSTEM_REALTIME_FILTER) == MIDI_SYSTEM_REALTIME_FILTER) - -#define MIDI_BYTE_IS_SYSEX_START_STATUS(b) (\ - b == MIDI_SYSEX) - -#define MIDI_BYTE_IS_SYSEX_END_STATUS(b) (\ - b == MIDI_EOX) - -#define MIDI_BYTE_IS_DATA_BYTE(b) (\ - (b & MIDI_STATUSBYTEFILTER) == 0) - - - -class CMidiPort : - public Microsoft::WRL::RuntimeClass< - Microsoft::WRL::RuntimeClassFlags, - IMidiCallback> -{ -public: - CMidiPort(); - ~CMidiPort(); - HRESULT RuntimeClassInitialize(_In_ GUID sessionId, _In_ std::wstring& interfaceId, _In_ MidiFlow flow, _In_ const MIDIOPENDESC* openDesc, _In_ DWORD_PTR flags); - HRESULT Shutdown(); - HRESULT MidMessage(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); - HRESULT ModMessage(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); - bool IsFlow(_In_ MidiFlow flow); - -private: - HRESULT Reset(); - HRESULT AddBuffer(_In_ LPMIDIHDR buffer, _In_ DWORD_PTR bufferSize); - HRESULT Start(); - HRESULT Stop(); - HRESULT Close(); - - // IMidiCallback, for receiving midi in messages from the service. - STDMETHOD(Callback)(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, _In_ LONGLONG context); - - HRESULT SendMidiMessage(_In_ UINT32 midiMessage); - HRESULT SendLongMessage(_In_ LPMIDIHDR buffer); - - HRESULT CompleteLongBuffer(_In_ UINT message, _In_ LONGLONG position); - - void WinmmClientCallback(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); - - wil::critical_section m_Lock; - - MIDIOPENDESC m_OpenDesc {0}; - DWORD_PTR m_Flags {0}; - - std::wstring m_InterfaceId; - - MidiFlow m_Flow {MidiFlowIn}; - wil::unique_event m_Stopped{wil::EventOptions::None}; - wil::unique_event m_ExitCallback{wil::EventOptions::ManualReset}; - bool m_InCallback {false}; - bool m_Started {false}; - LONGLONG m_StartTime{0}; - LONGLONG m_qpcFrequency{0}; - - wil::critical_section m_BuffersLock; - bool m_IsInSysex{false}; - BYTE m_RunningStatus {0}; - std::queue m_InBuffers; - wil::unique_event m_BuffersAdded{wil::EventOptions::None}; - - std::unique_ptr m_MidisrvTransport; -}; \ No newline at end of file +// Copyright (c) Microsoft Corporation. All rights reserved. + +#pragma once + +#define MIDI_NOTEOFF 0x80 // MIDI_NOTEOFF +#define MIDI_NOTEON 0x90 // MIDI_NOTEON +#define MIDI_POLYAFTERTOUCH 0xa0 // MIDI_POLYAFTERTOUCH +#define MIDI_CONTROLCHANGE 0xb0 // MIDI_CONTROLCHANGE +#define MIDI_PROGRAMCHANGE 0xc0 // MIDI_PROGRAMCHANGE +#define MIDI_MONOAFTERTOUCH 0xd0 // MIDI_MONOAFTERTOUCH (channel pressure) +#define MIDI_PITCHBEND 0xe0 // MIDI_PITCHBEND +#define MIDI_SYSEX 0xf0 // MIDI_SYSEX +#define MIDI_TIMECODE 0xf1 // MIDI_TIMECODE +#define MIDI_SONGPOSITIONPOINTER 0xf2 // MIDI_SONGPOSITIONPOINTER +#define MIDI_SONGSELECT 0xf3 // MIDI_SONGSELECT +#define MIDI_TUNEREQUEST 0xf6 // MIDI_TUNEREQUEST +#define MIDI_EOX 0xf7 // MIDI_EOX +#define MIDI_TIMINGCLOCK 0xf8 // MIDI_TIMINGCLOCK +#define MIDI_START 0xfa // MIDI_START +#define MIDI_CONTINUE 0xfb // MIDI_CONTINUE +#define MIDI_STOP 0xfc // MIDI_STOP +#define MIDI_ACTIVESENSE 0xfe // MIDI_ACTIVESENSE +#define MIDI_RESET 0xff // MIDI_RESET + +#define MIDI_STATUSBYTEFILTER 0x80 // filter for MIDI status byte to remove channel info + // a status byte must have the high bit set (0x80) +#define MIDI_SYSTEM_REALTIME_FILTER 0xf8 // filter for MIDI system realtime messages + // a system realtime message must have all these bits set + + +// For reference, this site is better than the formal MIDI 1.0 specs in many ways +// http://midi.teragonaudio.com/tech/midispec.htm + +#define MIDI_MESSAGE_IS_THREE_BYTES(status) ( \ + status == MIDI_NOTEOFF || \ + status == MIDI_NOTEON || \ + status == MIDI_POLYAFTERTOUCH || \ + status == MIDI_CONTROLCHANGE || \ + status == MIDI_PITCHBEND || \ + status == MIDI_SONGPOSITIONPOINTER) + +#define MIDI_MESSAGE_IS_TWO_BYTES(status) ( \ + status == MIDI_PROGRAMCHANGE || \ + status == MIDI_MONOAFTERTOUCH || \ + status == MIDI_TIMECODE || \ + status == MIDI_SONGSELECT) + +#define MIDI_MESSAGE_IS_ONE_BYTE(status) ( \ + status == MIDI_TIMINGCLOCK || \ + status == MIDI_TUNEREQUEST || \ + status == MIDI_START || \ + status == MIDI_CONTINUE || \ + status == MIDI_STOP || \ + status == MIDI_ACTIVESENSE || \ + status == MIDI_RESET || \ + status == MIDI_EOX) + + +#define MIDI_MESSAGE_TERMINATES_RUNNING_STATUS(status) (\ + status >= MIDI_SYSEX && \ + status <= MIDI_EOX ) + +#define MIDI_STATUS_SUPPORTS_RUNNING_STATUS(status) (\ + status >= MIDI_NOTEOFF && \ + status < MIDI_SYSEX) + +#define MIDI_BYTE_IS_STATUS_BYTE(b) (\ + (b & MIDI_STATUSBYTEFILTER) == MIDI_STATUSBYTEFILTER) + +#define MIDI_BYTE_IS_SYSTEM_REALTIME_STATUS(b) (\ + (b & MIDI_SYSTEM_REALTIME_FILTER) == MIDI_SYSTEM_REALTIME_FILTER) + +#define MIDI_BYTE_IS_SYSEX_START_STATUS(b) (\ + b == MIDI_SYSEX) + +#define MIDI_BYTE_IS_SYSEX_END_STATUS(b) (\ + b == MIDI_EOX) + +#define MIDI_BYTE_IS_DATA_BYTE(b) (\ + (b & MIDI_STATUSBYTEFILTER) == 0) + + + +class CMidiPort : + public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags, + IMidiCallback> +{ +public: + CMidiPort(); + ~CMidiPort(); + HRESULT RuntimeClassInitialize(_In_ GUID sessionId, _In_ std::wstring& interfaceId, _In_ MidiFlow flow, _In_ const MIDIOPENDESC* openDesc, _In_ DWORD_PTR flags); + HRESULT Shutdown(); + HRESULT MidMessage(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); + HRESULT ModMessage(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); + bool IsFlow(_In_ MidiFlow flow); + +private: + HRESULT Reset(); + HRESULT AddBuffer(_In_ LPMIDIHDR buffer, _In_ DWORD_PTR bufferSize); + HRESULT Start(); + HRESULT Stop(); + HRESULT Close(); + + // IMidiCallback, for receiving midi in messages from the service. + STDMETHOD(Callback)(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, _In_ LONGLONG context); + + HRESULT SendMidiMessage(_In_ UINT32 midiMessage); + HRESULT SendLongMessage(_In_ LPMIDIHDR buffer); + + HRESULT CompleteLongBuffer(_In_ UINT message, _In_ LONGLONG position); + + void WinmmClientCallback(_In_ UINT msg, _In_ DWORD_PTR param1, _In_ DWORD_PTR param2); + + wil::critical_section m_Lock; + + MIDIOPENDESC m_OpenDesc {0}; + DWORD_PTR m_Flags {0}; + + std::wstring m_InterfaceId; + + MidiFlow m_Flow {MidiFlowIn}; + wil::unique_event m_Stopped{wil::EventOptions::None}; + wil::unique_event m_ExitCallback{wil::EventOptions::ManualReset}; + bool m_InCallback {false}; + bool m_Started {false}; + LONGLONG m_StartTime{0}; + LONGLONG m_qpcFrequency{0}; + + wil::critical_section m_BuffersLock; + bool m_IsInSysex{false}; + BYTE m_RunningStatus {0}; + std::queue m_InBuffers; + wil::unique_event m_BuffersAdded{wil::EventOptions::None}; + + std::unique_ptr m_MidisrvTransport; +}; diff --git a/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.cpp b/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.cpp index 338ba592..ba68ddc0 100644 --- a/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.cpp +++ b/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.cpp @@ -16,7 +16,7 @@ #include "libmidi2\umpToBytestream.h" _Use_decl_annotations_ -void LibMidi2Tests::InternalTestSysEx( +void LibMidi2Tests::InternalTranslateMidi1BytesToUmpWords( uint8_t groupIndex, uint8_t const sysexBytes[], uint32_t const byteCount, @@ -71,7 +71,7 @@ void LibMidi2Tests::InternalTestSysEx( } _Use_decl_annotations_ -void LibMidi2Tests::InternalTestSysExFromBytes( +void LibMidi2Tests::InternalTranslateUmpWordsToMidi1Bytes( std::vector messageWords, std::vector const expectedBytes) { @@ -144,7 +144,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithSysEx7() 0x30351b1c, 0x1d1e1f00 }; - InternalTestSysEx(0, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(0, sysexBytes, _countof(sysexBytes), expectedWords); } void LibMidi2Tests::TestTranslateFromBytesWithSysEx7AndNonZeroGroup() @@ -161,7 +161,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithSysEx7AndNonZeroGroup() 0x39351b1c, 0x1d1e1f00 }; - InternalTestSysEx(9, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(9, sysexBytes, _countof(sysexBytes), expectedWords); } @@ -184,7 +184,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithEmbeddedRealTimeAndSysEx7() 0x30352b2c, 0x2d2e2f00 }; - InternalTestSysEx(0, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(0, sysexBytes, _countof(sysexBytes), expectedWords); } @@ -211,7 +211,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithEmbeddedStartStopSysEx7() 0x30027a7b, 0x00000000 }; - InternalTestSysEx(0, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(0, sysexBytes, _countof(sysexBytes), expectedWords); } void LibMidi2Tests::TestTranslateFromBytesWithLongSysEx7() @@ -229,10 +229,71 @@ void LibMidi2Tests::TestTranslateFromBytesWithLongSysEx7() //sysexBytes[0] = 0xf0; //sysexBytes[byteCount - 1] = 0xf7; - //InternalTestSysEx(0, sysexBytes, byteCount, expectedWords); + //InternalTranslateBytesToUmpWords(0, sysexBytes, byteCount, expectedWords); } +void LibMidi2Tests::TestProgramChangeFromBytes() +{ + const uint8_t bytes[] = + { + 0xC0, 0x12, + 0xB0, 0x00, 0x40, + 0xC0, 0x34, + 0xB0, 0x20, 0x30, + 0xC0, 0x56, + 0xB0, 0x20, 0x20, + 0xC0, 0x78, + }; + + std::vector expectedWords + { + 0x20C01200, + 0x20B00040, + 0x20C03400, + 0x20B02030, + 0x20C05600, + 0x20B02020, + 0x20C07800, + }; + + InternalTranslateMidi1BytesToUmpWords(0, bytes, _countof(bytes), expectedWords); +} + +void LibMidi2Tests::TestProgramChangeToBytes() +{ + + std::vector words + { + 0x20C01200, + 0x20B00040, + 0x20C03400, + 0x20B02030, + 0x20C05600, + 0x20B02020, + 0x20C07800, + }; + + std::vector bytes = + { + 0xC0, 0x12, + 0xB0, 0x00, 0x40, + 0xC0, 0x34, + 0xB0, 0x20, 0x30, + 0xC0, 0x56, + 0xB0, 0x20, 0x20, + 0xC0, 0x78, + }; + + InternalTranslateUmpWordsToMidi1Bytes(words, bytes); +} + + + + + + + void LibMidi2Tests::TestTranslateFromBytesWithEmptySysEx7() { const uint8_t sysexBytes[] = @@ -242,7 +303,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithEmptySysEx7() std::vector expectedWords{ 0x30000000, 0x00000000 }; - InternalTestSysEx(0, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(0, sysexBytes, _countof(sysexBytes), expectedWords); } void LibMidi2Tests::TestTranslateFromBytesWithShortSysEx7() @@ -254,7 +315,7 @@ void LibMidi2Tests::TestTranslateFromBytesWithShortSysEx7() std::vector expectedWords{ 0x30020a0b, 0x00000000 }; - InternalTestSysEx(0, sysexBytes, _countof(sysexBytes), expectedWords); + InternalTranslateMidi1BytesToUmpWords(0, sysexBytes, _countof(sysexBytes), expectedWords); } @@ -274,7 +335,7 @@ void LibMidi2Tests::TestTranslateToBytesWithSysEx7() 0xF0, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF7 }; - InternalTestSysExFromBytes(input, expectedOutput); + InternalTranslateUmpWordsToMidi1Bytes(input, expectedOutput); } @@ -296,7 +357,7 @@ void LibMidi2Tests::TestTranslateToBytesWithInterruptedSysEx7() 0x06, 0x07, 0xF7 }; - InternalTestSysExFromBytes(input, expectedOutput); + InternalTranslateUmpWordsToMidi1Bytes(input, expectedOutput); } void LibMidi2Tests::TestTranslateToBytesWithCanceledSysEx7() @@ -321,6 +382,6 @@ void LibMidi2Tests::TestTranslateToBytesWithCanceledSysEx7() 0x06, 0x07, 0xF7 // rest of sysex, including F7 }; - InternalTestSysExFromBytes(input, expectedOutput); + InternalTranslateUmpWordsToMidi1Bytes(input, expectedOutput); } \ No newline at end of file diff --git a/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.h b/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.h index 666d77d7..1bf7dbe1 100644 --- a/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.h +++ b/src/api/Test/Midi2.Transform.unittests/LibMidi2Tests.h @@ -44,13 +44,16 @@ class LibMidi2Tests TEST_METHOD(TestTranslateToBytesWithInterruptedSysEx7); TEST_METHOD(TestTranslateToBytesWithCanceledSysEx7); - void InternalTestSysEx( + TEST_METHOD(TestProgramChangeFromBytes); + TEST_METHOD(TestProgramChangeToBytes); + + void InternalTranslateMidi1BytesToUmpWords( _In_ uint8_t const groupIndex, _In_reads_bytes_(byteCount) uint8_t const sysexBytes[], _In_ uint32_t const byteCount, _In_ std::vector const expectedWords); - void InternalTestSysExFromBytes( + void InternalTranslateUmpWordsToMidi1Bytes( _In_ std::vector const messageWords, _In_ std::vector const expectedBytes); diff --git a/src/app-sdk/midi1monitor/pch.h b/src/app-sdk/midi1monitor/pch.h index 3679bd9d..bb11f1f0 100644 --- a/src/app-sdk/midi1monitor/pch.h +++ b/src/app-sdk/midi1monitor/pch.h @@ -103,8 +103,6 @@ namespace internal = ::WindowsMidiServicesInternal; status == MIDI_SONGSELECT) #define MIDI_MESSAGE_IS_ONE_BYTE(status) ( \ - status == MIDI_PROGRAMCHANGE || \ - status == MIDI_MONOAFTERTOUCH || \ status == MIDI_TIMINGCLOCK || \ status == MIDI_TUNEREQUEST || \ status == MIDI_START || \ @@ -114,7 +112,6 @@ namespace internal = ::WindowsMidiServicesInternal; status == MIDI_RESET || \ status == MIDI_EOX) - #define MIDI_MESSAGE_TERMINATES_RUNNING_STATUS(status) (\ status >= MIDI_SYSEX && \ status <= MIDI_EOX ) From 28855dfa62502404b64725cc68304cc5552362dc Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Sat, 8 Feb 2025 23:44:21 -0500 Subject: [PATCH 2/3] Debugging extra bytes of 00 data being sent to service from WinMM --- src/api/Client/WinMM/MidiSrvPort.cpp | 3 ++- src/api/Libs/MidiXProc/MidiXProc.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/Client/WinMM/MidiSrvPort.cpp b/src/api/Client/WinMM/MidiSrvPort.cpp index 19885622..3f6e7d40 100644 --- a/src/api/Client/WinMM/MidiSrvPort.cpp +++ b/src/api/Client/WinMM/MidiSrvPort.cpp @@ -767,7 +767,8 @@ CMidiPort::SendLongMessage(LPMIDIHDR buffer) // pass a timestamp of 0 to bypass scheduler // TODO: based on the buffer length, this message may require chunking into smaller // pieces to ensure it fits into the cross process queue. - RETURN_IF_FAILED(m_MidisrvTransport->SendMidiMessage(buffer->lpData, buffer->dwBufferLength, 0)); + // + RETURN_IF_FAILED(m_MidisrvTransport->SendMidiMessage(buffer->lpData, buffer->dwBytesRecorded, 0)); } // mark the buffer as completed diff --git a/src/api/Libs/MidiXProc/MidiXProc.cpp b/src/api/Libs/MidiXProc/MidiXProc.cpp index 523def89..a9032fde 100644 --- a/src/api/Libs/MidiXProc/MidiXProc.cpp +++ b/src/api/Libs/MidiXProc/MidiXProc.cpp @@ -37,17 +37,17 @@ GetRequiredBufferSize(ULONG& requestedSize GetSystemInfo(&systemInfo); // a 0 buffer size is invalid. - RETURN_HR_IF(E_INVALIDARG, requestedSize == 0); + RETURN_HR_IF(E_INVALIDARG, requestedSize == 0); - // if adding one allocation granularity to the requested size causes overflow, the requested size + // if adding one allocation granularity to the requested size causes overflow, the requested size // is invalid. - RETURN_HR_IF(E_INVALIDARG, (requestedSize + systemInfo.dwAllocationGranularity) < requestedSize); + RETURN_HR_IF(E_INVALIDARG, (requestedSize + systemInfo.dwAllocationGranularity) < requestedSize); // Requested size is rounded up to the nearest dwAllocationGranularity requestedSize = ((requestedSize + systemInfo.dwAllocationGranularity - 1) & ~(systemInfo.dwAllocationGranularity - 1)); - // If the adjusted buffer size is larger than the maximum permitted, fail the request. - RETURN_HR_IF(E_INVALIDARG, requestedSize > MAXIMUM_LOOPED_BUFFER_SIZE); + // If the adjusted buffer size is larger than the maximum permitted, fail the request. + RETURN_HR_IF(E_INVALIDARG, requestedSize > MAXIMUM_LOOPED_BUFFER_SIZE); return S_OK; } From f11c8bc345e529d8d72063712d7e84393842d3ac Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Sun, 9 Feb 2025 00:29:30 -0500 Subject: [PATCH 3/3] Fix issue with extra 00 MIDI 1 data going to service from WinMM --- src/api/Client/WinMM/MidiSrvPort.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/api/Client/WinMM/MidiSrvPort.cpp b/src/api/Client/WinMM/MidiSrvPort.cpp index 3f6e7d40..c39f1730 100644 --- a/src/api/Client/WinMM/MidiSrvPort.cpp +++ b/src/api/Client/WinMM/MidiSrvPort.cpp @@ -720,9 +720,30 @@ CMidiPort::SendMidiMessage(UINT32 midiMessage) // This should be on an initialized midi out port RETURN_HR_IF(E_INVALIDARG, nullptr == m_MidisrvTransport); + // NOTE: sizeof(midiMessage) there is not correct for how the service reads data. This results + // in extra 00 byte data being sent to the service, and then in the translator, being read as + // running status values for the initial status byte. + + UINT messageSize{ sizeof(UINT32) }; + byte status = midiMessage & 0x000000FF; + //byte* messagePointer = (byte*)(&midiMessage); + + if (MIDI_MESSAGE_IS_ONE_BYTE(status)) + { + messageSize = 1; + } + else if (MIDI_MESSAGE_IS_TWO_BYTES(status)) + { + messageSize = 2; + } + else if (MIDI_MESSAGE_IS_THREE_BYTES(status)) + { + messageSize = 3; + } + // send the message to the transport // pass a timestamp of 0 to bypass scheduler - RETURN_IF_FAILED(m_MidisrvTransport->SendMidiMessage(&midiMessage, sizeof(midiMessage), 0)); + RETURN_IF_FAILED(m_MidisrvTransport->SendMidiMessage(&midiMessage, messageSize, 0)); } return S_OK;