Skip to content

Commit 4728bf9

Browse files
committed
Documented the vendored JUCE MIDI-CI patches as patch files
1 parent c8aba8f commit 4728bf9

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

Patches/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Vendored JUCE patches
2+
3+
The JUCE modules vendored under `JuceLibraryCode/modules` are based on JUCE
4+
7.0.11 with two local patches to `juce_midi_ci`, kept as the patch files in this
5+
folder. They exist because stock JUCE (verified absent through JUCE master as of
6+
July 2026) doesn't support what the MPE Profile negotiation needs:
7+
8+
- **`juce_midi_ci-profile-inquiry-inactive.patch`** — a Profile Inquiry Reply
9+
must list a profile as either enabled or disabled, but stock
10+
`ChannelProfileStates::getInactive()` returns every supported profile,
11+
including the active ones, so active profiles were also reported as disabled.
12+
The fix excludes active profiles from the inactive list.
13+
- **`juce_midi_ci-profile-details-inquiry.patch`** — stock JUCE unconditionally
14+
NAKs a Profile Details Inquiry with a non-zero target. This adds a
15+
`profileDetailsInquired()` hook to `ci::ProfileDelegate` (default: empty, which
16+
still NAKs) and makes the profile host answer with the delegate's data, so a
17+
responder can serve the MPE Profile optional-features inquiry.
18+
19+
The same patches are applied in both SendMIDI and ReceiveMIDI, keeping their
20+
vendored modules identical.
21+
22+
## Re-applying
23+
24+
`Projucer --resave` re-copies the modules from the external JUCE and **silently
25+
overwrites these patches**. After a resave, restore the vendored code before
26+
committing:
27+
28+
```
29+
git checkout -- JuceLibraryCode/modules JuceLibraryCode/AppConfig.h
30+
```
31+
32+
To apply the patches onto a fresh stock module copy instead (for example after
33+
deliberately updating the vendored JUCE), from the repository root:
34+
35+
```
36+
git apply Patches/juce_midi_ci-profile-inquiry-inactive.patch
37+
git apply Patches/juce_midi_ci-profile-details-inquiry.patch
38+
```
39+
40+
Note that JUCE ships these sources with CRLF line endings while the vendored
41+
copies are LF; if a fresh copy still has CRLF, apply with
42+
`git apply --ignore-whitespace` (or normalize to LF first) and verify with
43+
`git apply --reverse --check <patch>`, which succeeds when a tree contains
44+
exactly what a patch describes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--- a/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileDelegate.h
2+
+++ b/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileDelegate.h
3+
@@ -56,6 +56,10 @@
4+
[[maybe_unused]] ProfileAtAddress profileAtAddress,
5+
[[maybe_unused]] int numChannels,
6+
[[maybe_unused]] bool enabled) = 0;
7+
+
8+
+ virtual std::vector<std::byte> profileDetailsInquired ([[maybe_unused]] MUID x,
9+
+ [[maybe_unused]] ProfileAtAddress profileAtAddress,
10+
+ [[maybe_unused]] std::byte target) { return std::vector<std::byte>(); }
11+
};
12+
13+
} // namespace juce::midi_ci
14+
--- a/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIDevice.cpp
15+
+++ b/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIDevice.cpp
16+
@@ -1057,7 +1057,16 @@
17+
18+
device.profileHost->setProfileEnablement (profileAtAddress, enabled ? jmax (1, numChannels) : 0);
19+
}
20+
+
21+
+ virtual std::vector<std::byte> profileDetailsInquired (MUID x, ProfileAtAddress profileAtAddress, std::byte target) override
22+
+ {
23+
+ if (auto* d = device.options.getProfileDelegate())
24+
+ return d->profileDetailsInquired (x, profileAtAddress, target);
25+
+
26+
+ return std::vector<std::byte>();
27+
+ }
28+
29+
+
30+
private:
31+
Impl& device;
32+
};
33+
--- a/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileHost.cpp
34+
+++ b/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileHost.cpp
35+
@@ -111,23 +111,37 @@
36+
37+
bool messageReceived (const Message::ProfileDetails& body) const
38+
{
39+
+ const auto address = ChannelAddress{}.withGroup (output->getIncomingGroup())
40+
+ .withChannel (output->getIncomingHeader().deviceID);
41+
+ const ProfileAtAddress profileAtAddress { body.profile, address };
42+
+
43+
if (body.target == std::byte{})
44+
{
45+
- const auto address = ChannelAddress{}.withGroup (output->getIncomingGroup())
46+
- .withChannel (output->getIncomingHeader().deviceID);
47+
- const ProfileAtAddress profileAtAddress { body.profile, address };
48+
const auto state = host->getState (profileAtAddress);
49+
std::vector<std::byte> extraData;
50+
detail::Marshalling::Writer { extraData } (state.active, state.supported);
51+
- detail::MessageTypeUtils::send (*output, Message::ProfileDetailsResponse { body.profile, body.target, extraData });
52+
+ sendProfileDetailsResponse(body, extraData);
53+
}
54+
else
55+
{
56+
- detail::MessageTypeUtils::sendNAK (*output, std::byte { 0x04 });
57+
+ const auto extraData = host->delegate.profileDetailsInquired(output->getIncomingHeader().source, profileAtAddress, body.target);
58+
+ if (extraData.empty())
59+
+ {
60+
+ detail::MessageTypeUtils::sendNAK (*output, std::byte { 0x04 });
61+
+ }
62+
+ else
63+
+ {
64+
+ sendProfileDetailsResponse(body, extraData);
65+
+ }
66+
}
67+
68+
return true;
69+
}
70+
+
71+
+ void sendProfileDetailsResponse (const Message::ProfileDetails& body, const std::vector<std::byte>& extraData) const
72+
+ {
73+
+ detail::MessageTypeUtils::send (*output, Message::ProfileDetailsResponse { body.profile, body.target, extraData });
74+
+ }
75+
76+
template <typename Body>
77+
bool profileEnablementReceived (const Body& request) const
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- a/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileStates.cpp
2+
+++ b/JuceLibraryCode/modules/juce_midi_ci/ci/juce_CIProfileStates.cpp
3+
@@ -52,7 +52,7 @@
4+
std::vector<Profile> result;
5+
6+
for (const auto& item : entries)
7+
- if (item.state.isSupported())
8+
+ if (item.state.isSupported() && !item.state.isActive())
9+
result.push_back (item.profile);
10+
11+
return result;

0 commit comments

Comments
 (0)