diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 55ab3be36..d08d610e7 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -25,21 +25,13 @@
"type": "string",
"enum": [
"BuildAndPublishAll",
- "T_BuildAndPackageElectronProjection",
- "T_BuildAndPackAllAppSDKs",
- "T_BuildAppSdkRuntimeAndToolsInstaller",
- "T_BuildAppSDKToolsAndTests",
- "T_BuildConsoleApp",
- "T_BuildCppSamples",
- "T_BuildCSharpSamples",
- "T_BuildPowerShellProjection",
- "T_BuildSettingsApp",
- "T_BuildUserToolsSharedComponents",
- "T_CopySharedDesignAssets",
+ "T_BuildServiceAndPlugins",
+ "T_BuildServiceAndPluginsInstaller",
"T_CreateVersionIncludes",
"T_Prerequisites",
"T_ZipPowershellDevUtilities",
- "T_ZipSamples"
+ "T_ZipServicePdbs",
+ "T_ZipWdmaud2"
]
},
"Verbosity": {
diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi
index 73b94e473..1a39f9494 100644
--- a/build/staging/version/BundleInfo.wxi
+++ b/build/staging/version/BundleInfo.wxi
@@ -1,5 +1,5 @@
-
-
-
+
+
+
diff --git a/build/staging/version/WindowsMidiServicesVersion.cs b/build/staging/version/WindowsMidiServicesVersion.cs
index 7522e02a8..205a9fa67 100644
--- a/build/staging/version/WindowsMidiServicesVersion.cs
+++ b/build/staging/version/WindowsMidiServicesVersion.cs
@@ -9,15 +9,15 @@ public static class MidiNuGetBuildInformation
{
public const bool IsPreview = true;
public const string Source = "GitHub Preview";
- public const string BuildDate = "2026-01-18";
- public const string Name = "SDK Release Candidate 1";
- public const string BuildFullVersion = "1.0.14-rc.1.213";
+ public const string BuildDate = "2026-01-19";
+ public const string Name = "Service Preview 14";
+ public const string BuildFullVersion = "1.0.15-preview.14.73";
public const ushort VersionMajor = 1;
public const ushort VersionMinor = 0;
- public const ushort VersionPatch = 14;
- public const ushort VersionBuildNumber = 213;
- public const string Preview = "rc.1.213";
- public const string AssemblyFullVersion = "1.0.14.213";
- public const string FileFullVersion = "1.0.14.213";
+ public const ushort VersionPatch = 15;
+ public const ushort VersionBuildNumber = 73;
+ public const string Preview = "preview.14.73";
+ public const string AssemblyFullVersion = "1.0.15.73";
+ public const string FileFullVersion = "1.0.15.73";
}
}
diff --git a/build/staging/version/WindowsMidiServicesVersion.h b/build/staging/version/WindowsMidiServicesVersion.h
index 498434dd4..99b233470 100644
--- a/build/staging/version/WindowsMidiServicesVersion.h
+++ b/build/staging/version/WindowsMidiServicesVersion.h
@@ -7,14 +7,14 @@
#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_IS_PREVIEW true
#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_SOURCE L"GitHub Preview"
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_DATE L"2026-01-18"
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_NAME L"SDK Release Candidate 1"
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_FULL L"1.0.14-rc.1.213"
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_DATE L"2026-01-19"
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_NAME L"Service Preview 14"
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_FULL L"1.0.15-preview.14.73"
#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_MAJOR 1
#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_MINOR 0
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_PATCH 14
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_BUILD_NUMBER 213
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_PREVIEW L"rc.1.213"
-#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_FILE L"1.0.14.213"
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_PATCH 15
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_BUILD_NUMBER 73
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_PREVIEW L"preview.14.73"
+#define WINDOWS_MIDI_SERVICES_NUGET_BUILD_VERSION_FILE L"1.0.15.73"
#endif
diff --git a/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.cpp b/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.cpp
index 4c9975153..1dd5f8e96 100644
--- a/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.cpp
+++ b/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.cpp
@@ -315,4 +315,42 @@ void UmpIteratorTests::TestCopyWordsToVector()
VERIFY_ARE_EQUAL(words[i], destination[i]);
}
+}
+
+
+void UmpIteratorTests::TestGetMessageWordsByIndex()
+{
+ std::vector destination{};
+
+ uint32_t words[] =
+ {
+ 0x20000001,
+ 0x40000001, 0x01234567,
+ 0xF0000001, 0x18675309, 0x01010101, 0x02020202,
+ 0x40000001, 0x01234567,
+ 0x10000001,
+ 0x00000000,
+ };
+
+ std::vector readWords{};
+
+ UmpBufferIterator bufferIterator(words, ARRAYSIZE(words));
+ for (auto it = bufferIterator.begin(); it < bufferIterator.end(); ++it)
+ {
+ uint8_t currentMessageWordCount = it.CurrentMessageWordCount();
+
+ for (uint8_t i = 0; i < currentMessageWordCount; i++)
+ {
+ readWords.push_back(it.GetCurrentMessageWord(i));
+ }
+ }
+
+ VERIFY_ARE_EQUAL(ARRAYSIZE(words), readWords.size());
+
+ // now, check values
+ for (uint32_t i = 0; i < ARRAYSIZE(words); i++)
+ {
+ VERIFY_ARE_EQUAL(words[i], readWords[i]);
+ }
+
}
\ No newline at end of file
diff --git a/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.h b/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.h
index 10eb68d58..97fee07d7 100644
--- a/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.h
+++ b/src/api/Test/Midi2.SharedIncludes.unittests/UmpIteratorTests.h
@@ -36,6 +36,8 @@ class UmpIteratorTests
TEST_METHOD(TestValidateCompleteBufferHasCompleteUmps);
TEST_METHOD(TestGetMessageType);
TEST_METHOD(TestCopyWordsToVector);
+ TEST_METHOD(TestGetMessageWordsByIndex);
+
private:
diff --git a/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.cpp b/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.cpp
index ec1f95bac..8f06f641a 100644
--- a/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.cpp
+++ b/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.cpp
@@ -20,7 +20,8 @@
_Use_decl_annotations_
void MidiUMPToBSTransformTests::InternalTestMessages(
std::vector const words,
- std::vector const expectedBytes
+ std::vector const expectedBytes,
+ std::vector const expectedGroups
)
{
wil::com_ptr_nothrow transformLib;
@@ -48,13 +49,38 @@ void MidiUMPToBSTransformTests::InternalTestMessages(
// set the callback
- m_MidiInCallback = [&](PVOID payload, UINT payloadSize, LONGLONG /*payloadPosition*/, LONGLONG)
+ byte lastGroupIndex{ 127 }; // an invalid group index
+ int32_t currentGroupArrayIndex{ -1 };
+
+ m_MidiInCallback = [&](PVOID payload, UINT payloadSize, LONGLONG /*payloadPosition*/, LONGLONG context)
{
//std::cout << "callback" << std::endl;
auto receivedBytes = static_cast(payload);
- std::cout << "message received:" << std::endl;
+ std::cout << "message received for group " << context << ":" << std::endl;
+
+ // validate the group index
+ if (expectedGroups.size() > 0)
+ {
+ // group index has changed
+ if (lastGroupIndex != context)
+ {
+ currentGroupArrayIndex++;
+
+ // make sure we're not out of range on the group indexes
+ if (currentGroupArrayIndex >= expectedGroups.size())
+ {
+ std::cout << "More group indexes reported than expected" << std::endl;
+ VERIFY_FAIL();
+ }
+
+ lastGroupIndex = expectedGroups[currentGroupArrayIndex];
+ }
+
+ VERIFY_ARE_EQUAL(context, lastGroupIndex);
+ }
+
for (uint32_t i = 0; i < payloadSize; i++)
{
@@ -68,8 +94,9 @@ void MidiUMPToBSTransformTests::InternalTestMessages(
else
{
std::cout
+ << "rec: 0x"
<< std::setfill('0') << std::setw(2) << std::hex << (uint16_t)receivedBytes[i]
- << "(" << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(expectedBytes[expectedBytesIndex]) << ") ";
+ << " (exp: 0x" << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(expectedBytes[expectedBytesIndex]) << ") ";
VERIFY_ARE_EQUAL(expectedBytes[expectedBytesIndex], receivedBytes[i]);
@@ -178,5 +205,167 @@ void MidiUMPToBSTransformTests::TestChannelVoiceMessages()
0xCF, 0x1F,
};
- InternalTestMessages(input, expectedOutput);
+ std::vector expectedGroups =
+ {
+ 0
+ };
+
+ InternalTestMessages(input, expectedOutput, expectedGroups);
+}
+
+void MidiUMPToBSTransformTests::TestMixedGroupMessages()
+{
+ std::vector input =
+ {
+ 0x20E01230,
+ 0x20E11231,
+ 0x22E21232,
+ 0x22E31233,
+ 0x20E41234,
+ 0x20E51235,
+ 0x22E61236,
+ 0x22E71237,
+ 0x24E81238,
+ 0x24E91239,
+ 0x22EA123A,
+ 0x22EB123B,
+ 0x29EC123C,
+ 0x20ED123D,
+ 0x29EE123E,
+ 0x21EF123F,
+ };
+
+ std::vector expectedOutput =
+ {
+ 0xE0, 0x12, 0x30,
+ 0xE1, 0x12, 0x31,
+
+ 0xE2, 0x12, 0x32,
+ 0xE3, 0x12, 0x33,
+
+ 0xE4, 0x12, 0x34,
+ 0xE5, 0x12, 0x35,
+
+ 0xE6, 0x12, 0x36,
+ 0xE7, 0x12, 0x37,
+
+ 0xE8, 0x12, 0x38,
+ 0xE9, 0x12, 0x39,
+
+ 0xEA, 0x12, 0x3A,
+ 0xEB, 0x12, 0x3B,
+
+ 0xEC, 0x12, 0x3C,
+
+ 0xED, 0x12, 0x3D,
+
+ 0xEE, 0x12, 0x3E,
+
+ 0xEF, 0x12, 0x3F,
+ };
+
+
+ // only needs to contain the index when the group index changes
+ std::vector expectedGroups =
+ {
+ 0,
+ 2,
+ 0,
+ 2,
+ 4,
+ 2,
+ 9,
+ 0,
+ 9,
+ 1
+ };
+
+ InternalTestMessages(input, expectedOutput, expectedGroups);
+}
+
+
+
+void MidiUMPToBSTransformTests::ValidateGithubIssue822()
+{
+
+ // figure out the 14 bit value from the 32 bit value
+
+ uint32_t data32_1 = 0x10000000;
+ uint16_t fourteenBit1 = (uint16_t)(data32_1 >> 18);
+ uint8_t fourteenBit1MSB = (uint8_t)((fourteenBit1 >> 7) & 0x7F);
+ uint8_t fourteenBit1LSB = (uint8_t)(fourteenBit1 & 0x7F);
+
+ uint32_t data32_2 = 0x90000000;
+ uint16_t fourteenBit2 = (uint16_t)(data32_2 >> 18);
+ uint8_t fourteenBit2MSB = (uint8_t)((fourteenBit2 >> 7) & 0x7F);
+ uint8_t fourteenBit2LSB = (uint8_t)(fourteenBit2 & 0x7F);
+
+
+ std::vector input =
+ {
+ 0x40201020, data32_1, // group 0 MSB 10, LSB 20, Data 1
+ 0x43201020, data32_2, // group 3 with same RPN MSB 10, LSB 20
+
+ 0x40201020, data32_1, // group 0 MSB 10, LSB 20
+ 0x40201020, data32_2, // group 0 with same RPN MSB 10, and LSB 20
+
+ 0x40201020, data32_1, // group 0 MSB 10, LSB 20
+ 0x43201121, data32_2, // group 3 MSB 11, LSB 21
+
+ 0x43201121, data32_1, // group 3 MSB 11, LSB 21
+ };
+
+
+ std::vector expectedOutput =
+ {
+ // group 0
+ 0xb0, 0x65, 0x10, // NRPN MSB
+ 0xb0, 0x64, 0x20, // NRPN LSB
+ 0xb0, 0x06, fourteenBit1MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit1LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 3
+ 0xb0, 0x65, 0x10, // NRPN MSB
+ 0xb0, 0x64, 0x20, // NRPN LSB
+ 0xb0, 0x06, fourteenBit2MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit2LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 0
+ 0xb0, 0x65, 0x10, // NRPN MSB
+ 0xb0, 0x64, 0x20, // NRPN LSB
+ 0xb0, 0x06, fourteenBit1MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit1LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 0 again with same MSB/LSB for NRPN, so just data comes through
+ 0xb0, 0x06, fourteenBit2MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit2LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 0 again
+ 0xb0, 0x06, fourteenBit1MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit1LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 3, but different NRPN LSB/MSB
+ 0xb0, 0x65, 0x11, // NRPN MSB
+ 0xb0, 0x64, 0x21, // NRPN LSB
+ 0xb0, 0x06, fourteenBit2MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit2LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ // group 3 again, same NRPN LSB/MSB
+ 0xb0, 0x06, fourteenBit1MSB, // NRPN data 1 (value / MSB)
+ 0xb0, 0x26, fourteenBit1LSB, // NRPN data 2 (fine adjustment / LSB)
+
+ };
+
+
+ // only needs to contain the index when the group index changes
+ std::vector expectedGroups =
+ {
+ 0,
+ 3,
+ 0,
+ 3,
+ };
+
+ InternalTestMessages(input, expectedOutput, expectedGroups);
+
}
diff --git a/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.h b/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.h
index 7a791b10f..d3c41c8d7 100644
--- a/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.h
+++ b/src/api/Test/Midi2.Transform.unittests/MidiUMPToBSTransformTests.h
@@ -29,10 +29,13 @@ class MidiUMPToBSTransformTests
//TEST_METHOD_CLEANUP(TestCleanup);
TEST_METHOD(TestChannelVoiceMessages);
+ TEST_METHOD(TestMixedGroupMessages);
+ TEST_METHOD(ValidateGithubIssue822);
void InternalTestMessages(
_In_ std::vector words,
- _In_ std::vector const expectedBytes);
+ _In_ std::vector const expectedBytes,
+ _In_ std::vector const expectedGroups);
STDMETHOD(Callback)(_In_ MessageOptionFlags, _In_ PVOID Data, _In_ UINT Size, _In_ LONGLONG Position, LONGLONG Context)
{
diff --git a/src/api/Transform/UMPToByteStream/Midi2.UMP2BSMidiTransform.cpp b/src/api/Transform/UMPToByteStream/Midi2.UMP2BSMidiTransform.cpp
index 355791c4b..f36683405 100644
--- a/src/api/Transform/UMPToByteStream/Midi2.UMP2BSMidiTransform.cpp
+++ b/src/api/Transform/UMPToByteStream/Midi2.UMP2BSMidiTransform.cpp
@@ -4,6 +4,8 @@
#include "midi2.UMP2BSTransform.h"
+#include "ump_iterator.h"
+
_Use_decl_annotations_
HRESULT
CMidi2UMP2BSMidiTransform::Initialize(
@@ -83,29 +85,83 @@ CMidi2UMP2BSMidiTransform::SendMidiMessage(
);
#endif
+ RETURN_HR_IF(E_INVALIDARG, length < sizeof(uint32_t));
+
// can only transform 1 set of messages at a time
auto lock = m_SendLock.lock();
+ WindowsMidiServicesInternal::UmpBufferIterator bufferIterator(static_cast(inputData), length / sizeof(uint32_t));
// we can keep this as a local because of how the callback works
std::vector translatedBytes{};
translatedBytes.reserve(length); // as an approximation of output data size, this is reasonable
+ byte currentTranslatedBytesGroupIndex = 127;
- // Send the UMP(s) to the parser
- uint32_t *data = (uint32_t *)inputData;
- for (UINT i = 0; i < (length / sizeof(uint32_t)); i++)
- {
- m_UMP2BS.UMPStreamParse(data[i]);
+ auto it = bufferIterator.begin();
- // retrieve the bytestream message from the parser and add to our translated data
- while (m_UMP2BS.availableBS())
+ while (it < bufferIterator.end())
+ {
+ if (it.CurrentMessageSeemsComplete())
+ {
+ auto currentMessageWordCount = it.CurrentMessageWordCount();
+
+ if (it.CurrentMessageGroupIndex() != currentTranslatedBytesGroupIndex)
+ {
+ // send the translated version of everything we've received in this call
+ if (translatedBytes.size() > 0)
+ {
+ // For transforms, by convention the context contains the group index.
+ auto hr = m_Callback->Callback(
+ (MessageOptionFlags)(optionFlags | MessageOptionFlags_ContextContainsGroupIndex),
+ static_cast(translatedBytes.data()),
+ static_cast(translatedBytes.size()),
+ position,
+ currentTranslatedBytesGroupIndex);
+
+ if (FAILED(hr))
+ {
+ m_UMP2BS.resetBuffer();
+ RETURN_IF_FAILED(hr);
+ }
+
+ // clear the transmitted bytes
+ translatedBytes.clear();
+ }
+
+ // workaround because UMP2BS short-circuits RPN/NRPN msb/lsb across groups
+ //uint8_t gr = m_UMP2BS.group;
+ m_UMP2BS.resetBuffer();
+ //m_UMP2BS.group = gr;
+ // end workaround
+
+ currentTranslatedBytesGroupIndex = it.CurrentMessageGroupIndex();
+ }
+
+ // parse entire single message
+ for (uint8_t i = 0; i < currentMessageWordCount; i++)
+ {
+ m_UMP2BS.UMPStreamParse(it.GetCurrentMessageWord(i));
+ }
+
+ while (m_UMP2BS.availableBS())
+ {
+ translatedBytes.push_back(m_UMP2BS.readBS());
+ }
+ }
+ else
{
- translatedBytes.push_back(m_UMP2BS.readBS());
+ // incomplete UMP
+ m_UMP2BS.resetBuffer();
+ RETURN_IF_FAILED(E_INVALIDARG);
}
+
+ ++it; // moves to next message, not next word
}
- // send the translated version of everything we've received in this call
+
+
+ // get anything from the last spin round
if (translatedBytes.size() > 0)
{
// For transforms, by convention the context contains the group index.
diff --git a/src/app-sdk/tools/midicheckservice/midicheckservice_main.cpp b/src/app-sdk/tools/midicheckservice/midicheckservice_main.cpp
index 1b6ccc1ca..73c4cf101 100644
--- a/src/app-sdk/tools/midicheckservice/midicheckservice_main.cpp
+++ b/src/app-sdk/tools/midicheckservice/midicheckservice_main.cpp
@@ -274,6 +274,16 @@ bool VerifyWdmaud2Registry()
+void PauseIfAppropriate()
+{
+ if (!m_optionQuiet)
+ {
+ system("pause");
+ }
+
+}
+
+
int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
{
@@ -285,6 +295,7 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
if (!ParseCommandLine(argc, argv))
{
// user picked help or some other option which skips evaluation
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_CHECK_SKIPPED);
}
@@ -314,6 +325,8 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
else
{
WriteInfo("wdmaud2.drv is not present in registry in values midi-midi9. Most likely, the feature has not yet been enabled on this PC.");
+
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_NOT_ENABLED_IN_REGISTRY);
}
@@ -328,6 +341,7 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
{
WriteInfo("Successfully tested connectivity to service: MIDI Service is available and running from System32.");
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_SUCCESS);
}
else
@@ -336,6 +350,7 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
WriteInfo("However, this appears to be a development build, and so may not have the right");
WriteInfo("connections to the MIDI 1.0 APIs, the MIDI 2.0 class driver, etc.");
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_SUCCESS_DEV_BUILD);
}
}
@@ -350,6 +365,7 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
ShutdownMidisrv();
}
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_NOT_ENABLED_OR_NOT_STARTED);
}
@@ -362,6 +378,7 @@ int __cdecl wmain(_In_ int argc, _In_ WCHAR* argv[])
ShutdownMidisrv();
// service transport is not available.
+ PauseIfAppropriate();
return static_cast(MIDISRV_CHECK_RETURN_VALUE_NOT_INSTALLED);
}
}