midi-communications-windows 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.version +6 -0
  4. data/.yardoc/checksums +8 -0
  5. data/.yardoc/complete +0 -0
  6. data/.yardoc/object_types +0 -0
  7. data/.yardoc/objects/root.dat +0 -0
  8. data/.yardoc/proxy_types +0 -0
  9. data/.yardopts +8 -0
  10. data/Gemfile +3 -0
  11. data/LICENSE +165 -0
  12. data/README.md +176 -0
  13. data/Rakefile +13 -0
  14. data/doc/MIDICommunicationsWindows/API/MIDIHdr.html +144 -0
  15. data/doc/MIDICommunicationsWindows/API/MIDIInCaps.html +142 -0
  16. data/doc/MIDICommunicationsWindows/API/MIDIOutCaps.html +140 -0
  17. data/doc/MIDICommunicationsWindows/API/MSG.html +143 -0
  18. data/doc/MIDICommunicationsWindows/API.html +981 -0
  19. data/doc/MIDICommunicationsWindows/Device/ClassMethods.html +365 -0
  20. data/doc/MIDICommunicationsWindows/Device/InstanceMethods.html +1096 -0
  21. data/doc/MIDICommunicationsWindows/Device.html +988 -0
  22. data/doc/MIDICommunicationsWindows/Error.html +438 -0
  23. data/doc/MIDICommunicationsWindows/Input.html +1554 -0
  24. data/doc/MIDICommunicationsWindows/Message.html +700 -0
  25. data/doc/MIDICommunicationsWindows/Output.html +1697 -0
  26. data/doc/MIDICommunicationsWindows/TypeConversion.html +369 -0
  27. data/doc/MIDICommunicationsWindows.html +210 -0
  28. data/doc/_index.html +262 -0
  29. data/doc/class_list.html +54 -0
  30. data/doc/css/common.css +1 -0
  31. data/doc/css/full_list.css +206 -0
  32. data/doc/css/style.css +1089 -0
  33. data/doc/file.README.html +240 -0
  34. data/doc/file.testing-on-windows.html +253 -0
  35. data/doc/file_list.html +64 -0
  36. data/doc/frames.html +22 -0
  37. data/doc/index.html +240 -0
  38. data/doc/js/app.js +801 -0
  39. data/doc/js/full_list.js +334 -0
  40. data/doc/js/jquery.js +4 -0
  41. data/doc/method_list.html +382 -0
  42. data/doc/top-level-namespace.html +112 -0
  43. data/docs/testing-on-windows.md +222 -0
  44. data/examples/input.rb +27 -0
  45. data/examples/list_ports.rb +17 -0
  46. data/examples/output.rb +22 -0
  47. data/examples/sysex_output.rb +15 -0
  48. data/lib/midi-communications-windows/api.rb +340 -0
  49. data/lib/midi-communications-windows/device.rb +310 -0
  50. data/lib/midi-communications-windows/input.rb +486 -0
  51. data/lib/midi-communications-windows/message.rb +133 -0
  52. data/lib/midi-communications-windows/output.rb +206 -0
  53. data/lib/midi-communications-windows/type_conversion.rb +20 -0
  54. data/lib/midi-communications-windows/version.rb +4 -0
  55. data/lib/midi-communications-windows.rb +76 -0
  56. data/midi-communications-windows.gemspec +32 -0
  57. metadata +220 -0
@@ -0,0 +1,240 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ File: README
8
+
9
+ &mdash; MIDI Communications Windows API
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css">
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css">
16
+
17
+ <script type="text/javascript">
18
+ pathId = "README";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div id="main_progress" aria-hidden="true"></div>
31
+
32
+ <div class="nav_wrap">
33
+ <iframe id="nav" src="file_list.html?1"></iframe>
34
+ <div id="resizer"></div>
35
+ </div>
36
+
37
+ <div id="main" tabindex="-1">
38
+ <div id="header">
39
+ <div id="menu">
40
+
41
+ <a href="_index.html">Index</a> &raquo;
42
+ <span class="title">File: README</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><div id='filecontents'><h1 id="MIDI_Communications_Windows_Layer">MIDI Communications Windows Layer</h1>
63
+ <p><a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-2.7+-red.svg" alt="Ruby Version" /></a>
64
+ <a href="https://www.gnu.org/licenses/lgpl-3.0.html"><img src="https://img.shields.io/badge/license-LGPL--3.0--or--later-blue.svg" alt="License" /></a></p>
65
+ <p><strong>Realtime MIDI IO with Ruby for Windows.</strong></p>
66
+ <p>Access the <a href="https://learn.microsoft.com/en-us/windows/win32/multimedia/midi-reference">Windows Multimedia (WinMM) MIDI API</a> with Ruby.</p>
67
+ <p>This library is part of a suite of Ruby libraries for MIDI:</p>
68
+ <table>
69
+ <thead>
70
+ <tr>
71
+ <th>Function</th>
72
+ <th>Library</th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ <tr>
77
+ <td>MIDI Events representation</td>
78
+ <td><a href="https://github.com/javier-sy/midi-events">MIDI Events</a></td>
79
+ </tr>
80
+ <tr>
81
+ <td>MIDI Data parsing</td>
82
+ <td><a href="https://github.com/javier-sy/midi-parser">MIDI Parser</a></td>
83
+ </tr>
84
+ <tr>
85
+ <td>MIDI communication with Instruments and Control Surfaces</td>
86
+ <td><a href="https://github.com/javier-sy/midi-communications">MIDI Communications</a></td>
87
+ </tr>
88
+ <tr>
89
+ <td>Low level MIDI interface to MacOS</td>
90
+ <td><a href="https://github.com/javier-sy/midi-communications-macos">MIDI Communications MacOS Layer</a></td>
91
+ </tr>
92
+ <tr>
93
+ <td>Low level MIDI interface to Windows</td>
94
+ <td><a href="https://github.com/javier-sy/midi-communications-windows">MIDI Communications Windows Layer</a> (this library)</td>
95
+ </tr>
96
+ <tr>
97
+ <td>Low level MIDI interface to Linux</td>
98
+ <td><strong>TO DO</strong> (by now <a href="https://github.com/javier-sy/midi-communications">MIDI Communications</a> uses <a href="http://github.com/arirusso/alsa-rawmidi">alsa-rawmidi</a>)</td>
99
+ </tr>
100
+ <tr>
101
+ <td>Low level MIDI interface to JRuby</td>
102
+ <td><strong>TO DO</strong> (by now <a href="https://github.com/javier-sy/midi-communications">MIDI Communications</a> uses <a href="http://github.com/arirusso/midi-jruby">midi-jruby</a>)</td>
103
+ </tr>
104
+ </tbody>
105
+ </table>
106
+ <h2 id="Status">Status</h2>
107
+ <p><strong>Early.</strong> Everything is written and has been exercised end to end on Windows 11
108
+ 25H2 against system loopback endpoints: enumeration, short messages in both
109
+ directions, System Exclusive in both directions including a message larger than
110
+ the whole buffer queue, twenty open/close cycles, and the error path. What has
111
+ not been exercised is anything needing real hardware — a physical interface slow
112
+ enough to make <code>Output</code>'s System Exclusive wait mean something, and a port
113
+ appearing or disappearing while the program runs.</p>
114
+ <p>The version stays in the 0.0.x series because that was a virtual machine with no
115
+ MIDI hardware in it, not because anything is known to be missing. It will not be
116
+ published to RubyGems until a physical MIDI interface has closed the items
117
+ <a href="docs/testing-on-windows.md"><code>docs/testing-on-windows.md</code></a> lists as untestable
118
+ without one.</p>
119
+ <p>That document is also how to rebuild the test environment: it is the procedure
120
+ these measurements were taken with, including how to make two ports collide in
121
+ WinMM's 31 characters of name.</p>
122
+ <h3 id="One_thing_worth_knowing_before_you_use_it">One thing worth knowing before you use it</h3>
123
+ <p><code>Input#gets</code> <strong>waits</strong>. It does not return an empty array when nothing has
124
+ arrived; it blocks until something does, and then returns everything that
125
+ accumulated. Calling it on a quiet port looks exactly like a hung program. That
126
+ is deliberate — <code>Musa::Clock::InputMidiClock</code> reads in a loop with no delay of
127
+ its own and relies on it — but it will surprise anyone arriving from an API that
128
+ polls.</p>
129
+ <h2 id="Requirements">Requirements</h2>
130
+ <ul>
131
+ <li><a href="http://github.com/ffi/ffi">ffi</a></li>
132
+ </ul>
133
+ <p>Nothing else. <code>winmm.dll</code> is part of Windows, so this gem binds a library that
134
+ is already on the machine and ships no compiled artifact of its own.</p>
135
+ <h3 id="Which_versions_of_Windows">Which versions of Windows</h3>
136
+ <p><strong>By construction, old ones.</strong> Everything this gem calls is the oldest MIDI API
137
+ Windows has — <code>midiInOpen</code>, <code>midiOutShortMsg</code>, <code>midiOutLongMsg</code> and their
138
+ neighbours — plus three functions for a thread's message queue, <code>GetMessageW</code>,
139
+ <code>PeekMessageW</code> and <code>PostThreadMessageW</code>. All of it, the Unicode entry points
140
+ included, dates from the NT era. Nothing here calls Windows MIDI Services, which
141
+ means nothing here needs Windows 11.</p>
142
+ <p>What changes on an older system is not whether the gem works but what the
143
+ platform gives it. Before Windows MIDI Services a MIDI port is <strong>exclusive</strong> —
144
+ one program at a time — and there are no system loopback endpoints, so routing
145
+ MIDI between applications on one machine needs a third-party driver such as
146
+ loopMIDI. The same code, a poorer platform.</p>
147
+ <p>A 32-bit Ruby should also be fine: the two size checks that only hold on 64-bit
148
+ Windows are guarded, and the two that are checked everywhere describe structures
149
+ whose layout does not depend on the word size.</p>
150
+ <p><strong>None of which has been run.</strong> The only system this has ever executed on is
151
+ Windows 11 25H2, in a virtual machine, on an x64 Ruby under ARM emulation. The
152
+ paragraphs above say what the code asks of Windows, not what has been observed;
153
+ treat them as a reason to expect it to work rather than as a report that it does.</p>
154
+ <p>Where the floor actually sits is a question about Ruby, not about this gem:
155
+ RubyInstaller recommends Windows 10 or 11 and publishes no hard cutoff.</p>
156
+ <h2 id="Installation">Installation</h2>
157
+ <p>If you're using Bundler, add this line to your application's Gemfile:</p>
158
+ <p><code>gem &quot;midi-communications-windows&quot;</code></p>
159
+ <p>Otherwise</p>
160
+ <p><code>gem install midi-communications-windows</code></p>
161
+ <h2 id="Documentation">Documentation</h2>
162
+ <p><a href="http://rubydoc.info/github/javier-sy/midi-communications-windows">rdoc</a></p>
163
+ <h2 id="Why_WinMM_and_not_one_of_the_newer_Windows_MIDI_APIs">Why WinMM and not one of the newer Windows MIDI APIs</h2>
164
+ <p>Windows has three MIDI APIs a program could reach for, and as of February 2026
165
+ the oldest is the right one for Ruby.</p>
166
+ <p><strong>Windows MIDI Services</strong> became generally available in Windows 11 in February
167
+ 2026, replacing the MIDI stack underneath. Rather than retiring the older APIs,
168
+ Microsoft reconnected them to the new service, so a WinMM client needs nothing
169
+ installed to reach what the new stack provides — including the loopback
170
+ endpoints the system now creates itself, which is what previously required a
171
+ third-party driver.</p>
172
+ <p>Multi-client access arrives the same way, but <strong>per endpoint, not per API</strong>.
173
+ Measured on Windows 11 25H2 through this library: two clients opened the same
174
+ loopback input at once and both received every message, while a second open of
175
+ the classic <code>wdmaud</code> software synthesiser was refused with &quot;The specified device
176
+ is already in use&quot;. Endpoints carried by the new transports are shared;
177
+ endpoints still on the old drivers are exclusive, as they always were. On
178
+ Windows 10 there is no Windows MIDI Services at all.</p>
179
+ <p><strong>The Windows MIDI Services App SDK</strong> offers MIDI 2.0 and UMP, neither of which
180
+ this library needs: <code>midi-communications</code> and MusaDSL are MIDI 1.0 throughout.
181
+ It is published only as WinRT, and Microsoft's guidance is that other languages
182
+ obtain a WinRT projection from their toolchain. Ruby has none.</p>
183
+ <p><strong>WinRT <code>Windows.Devices.Midi</code></strong> is, under the new stack, another compatibility
184
+ layer onto the same service. It offers what WinMM offers, in exchange for
185
+ hand-written COM.</p>
186
+ <h2 id="Differences_from_the_macOS_layer">Differences from the macOS layer</h2>
187
+ <p>The two libraries implement the same contract over platforms that do not have
188
+ the same shape. Where they differ, they differ because the platforms do:</p>
189
+ <ul>
190
+ <li><strong><code>manufacturer</code> and <code>model</code> are <code>nil</code>.</strong> WinMM reports <code>wMid</code> and <code>wPid</code>,
191
+ numeric codes from a manufacturer registry that stopped being maintained in
192
+ the 1990s and that class-compliant USB devices almost all answer with
193
+ Microsoft's. Core MIDI reports real strings. Inventing text from the codes
194
+ would describe the code table, not the hardware.</li>
195
+ <li><strong><code>display_name</code> is the port name.</strong> On macOS it is composed as
196
+ &quot;manufacturer model (name)&quot;, which here would be a name wrapped in the
197
+ punctuation of two absent fields.</li>
198
+ <li><strong>Port ids are unique within a direction, not across both.</strong> A WinMM port is
199
+ identified by its index among inputs or among outputs, and that index is what
200
+ is passed to <code>midiInOpen</code>. Core MIDI numbers endpoints of both directions from
201
+ one counter. Input 0 and output 0 are both valid here, and unrelated.</li>
202
+ <li><strong>Port names are truncated to 31 characters, and do not identify a port.</strong>
203
+ That is all WinMM stores, and it drops the rest silently. Two loopback
204
+ endpoints whose names differed only past character 31 came back identical in
205
+ every field of <code>MIDIINCAPSW</code> — same name, same <code>wMid</code>, same <code>wPid</code>, same
206
+ <code>wTechnology</code> — distinguishable only by index. Core MIDI names are neither
207
+ truncated nor, in practice, ambiguous.</li>
208
+ <li><strong>Enumeration is not cached, but wrappers are reused.</strong> The macOS layer reads
209
+ the device list once and keeps it, so a device plugged in later is never seen.
210
+ Here WinMM is asked on every call, and a port still present comes back as the
211
+ same object it was before, so opening a port and then reading from it works
212
+ whether or not the caller kept the reference.</li>
213
+ <li><strong>There is no packet-list parsing.</strong> Core MIDI delivers a list of packets that
214
+ has to be walked; WinMM delivers one complete short message at a time, with
215
+ running status already resolved by the driver.</li>
216
+ </ul>
217
+ <h2 id="Relationship_to_midi_winmm">Relationship to midi-winmm</h2>
218
+ <p>This library is <strong>not</strong> derived from <a href="http://github.com/arirusso/midi-winmm">midi-winmm</a>,
219
+ which was written in 2011 for 32-bit Ruby. Windows is LLP64: on a 64-bit Ruby,
220
+ <code>unsigned long</code> is 32 bits while a <code>HANDLE</code> is 64, so that library's
221
+ <code>typedef :ulong, :HANDLE</code> truncates every handle it obtains. It also reads port
222
+ names through the ANSI entry points, which mangle any name that is not ASCII.
223
+ Both are addressed here by construction; see the notes in
224
+ <code>lib/midi-communications-windows/api.rb</code>.</p>
225
+ <h2 id="Author">Author</h2>
226
+ <ul>
227
+ <li><a href="https://github.com/javier-sy">Javier Sánchez Yeste</a></li>
228
+ </ul>
229
+ <h2 id="License">License</h2>
230
+ <p><a href="https://github.com/javier-sy/midi-communications-windows">MIDI Communications Windows Layer</a> Copyright (c) 2026 <a href="https://yeste.studio">Javier Sánchez Yeste</a>, licensed under LGPL 3.0 License</p></div></div>
231
+
232
+ <div id="footer">
233
+ Generated on Sun Sep 6 21:20:52 2026 by
234
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
235
+ 0.9.45 (ruby-3.4.7).
236
+ </div>
237
+
238
+ </div>
239
+ </body>
240
+ </html>
@@ -0,0 +1,253 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ File: testing-on-windows
8
+
9
+ &mdash; MIDI Communications Windows API
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css">
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css">
16
+
17
+ <script type="text/javascript">
18
+ pathId = "testing-on-windows";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div id="main_progress" aria-hidden="true"></div>
31
+
32
+ <div class="nav_wrap">
33
+ <iframe id="nav" src="file_list.html?1"></iframe>
34
+ <div id="resizer"></div>
35
+ </div>
36
+
37
+ <div id="main" tabindex="-1">
38
+ <div id="header">
39
+ <div id="menu">
40
+
41
+ <a href="_index.html">Index</a> &raquo;
42
+ <span class="title">File: testing-on-windows</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><div id='filecontents'><h1 id="Testing_this_gem_on_Windows_without_MIDI_hardware">Testing this gem on Windows without MIDI hardware</h1>
63
+ <p><em>Written 2026-09-06. Section 1 is the part with a shelf life: it describes
64
+ software that was in preview at the time, which Microsoft's release notes said
65
+ was headed for Windows 11 25H2 and later during the last week of November 2026.
66
+ If that has happened, most of section 1 collapses into &quot;nothing to install&quot; and
67
+ the rest still applies.</em></p>
68
+ <p>This is <strong>the procedure the library was measured with</strong>, not a promise that it
69
+ works on another machine. All of it ran once, on one particular installation.
70
+ Where something was checked, it says so; where it was not, it says that too.</p>
71
+ <p>The machine was:</p>
72
+ <p>| | |
73
+ |---|---|
74
+ | Windows | 11 25H2, build <strong>26200.9278</strong> |
75
+ | CPU | ARM64 — a VMware VM on Apple silicon |
76
+ | Ruby | 3.4.10 <code>x64-mingw-ucrt</code>, <strong>emulated</strong> under Prism (no ARM64 Ruby present) |
77
+ | ffi | 1.17.4 <code>x64-mingw-ucrt</code>, precompiled binary |
78
+ | MIDI hardware | <strong>none</strong> — no USB device passed through to the guest |</p>
79
+ <p>That last row is why this document exists: with no hardware, a loopback pair is
80
+ the only way to exercise input and output at the same time.</p>
81
+ <p>Nothing measured on this setup says anything about <strong>timing</strong>. Emulation inside
82
+ a virtual machine; latency and jitter need real hardware and a native Ruby.</p>
83
+ <hr />
84
+ <h2 id="1__What_has_to_be_installed">1. What has to be installed</h2>
85
+ <p>Windows 11 25H2 ships <strong>Windows MIDI Services</strong> in the box: the <code>midisrv</code>
86
+ service (<code>C:\Windows\System32\midisrv.exe</code>) and the <code>wdmaud2.drv</code> shim,
87
+ registered as <code>midi1</code> under
88
+ <code>HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Drivers32</code>. That shim is what
89
+ makes endpoints from the new stack <strong>visible to WinMM</strong> without this gem doing
90
+ anything special, and therefore what makes this setup able to test a gem that
91
+ speaks only WinMM.</p>
92
+ <p>What is <em>not</em> in the box are the MIDI 1.0 loopback transport and the tools. Those
93
+ come from the [<code>microsoft/MIDI</code>] repository, release <code>inbox-dev-preview-6</code>:</p>
94
+ <ul>
95
+ <li><code>Windows.MIDI.Services.Basic.MIDI.1.0.Loopback.Preview.1.0.18-preview.3-arm64.exe</code></li>
96
+ <li><code>Windows.MIDI.Services.Tools.0.99.64-devpreview.6-arm64.exe</code></li>
97
+ </ul>
98
+ <p>There are <code>-x64</code> variants of both. The measuring machine used the <strong>native ARM64</strong>
99
+ ones (PE header <code>0xAA64</code>) even though the Ruby was emulated x64: the gem talks to
100
+ <code>winmm.dll</code>, and Windows handles the crossing.</p>
101
+ <p>The Network and Bluetooth transports from the same release are not needed.</p>
102
+ <h3 id="Warnings_that_are_not_optional">Warnings that are not optional</h3>
103
+ <ul>
104
+ <li><strong>These are unsigned preview binaries.</strong> The release notes require Windows
105
+ <strong>Developer Mode</strong>, and are explicit: <em>&quot;There are almost certainly bugs and
106
+ missing/incomplete features.&quot;</em> This is not production software.</li>
107
+ <li><strong>Do not redistribute them.</strong> The release's permitted-use table forbids it. A
108
+ repository should link to the release and never host the installers.</li>
109
+ <li>Minimum system version declared: <strong>Windows 11 25H2</strong>.</li>
110
+ <li>Downloaded through a browser they carry Mark of the Web and SmartScreen will
111
+ object; <code>Unblock-File</code> clears it. Fetched with <code>Invoke-WebRequest</code> they do not.</li>
112
+ </ul>
113
+ <h2 id="2__Installing">2. Installing</h2>
114
+ <p>Both installers are <strong>WiX bundles</strong>, so they accept <code>/quiet</code>, <code>/norestart</code> and
115
+ <code>/log</code>. Both need <strong>elevation</strong>, and Developer Mode writes to <code>HKLM</code>, so it is
116
+ worth doing the whole thing in one elevated script and getting one UAC prompt.</p>
117
+ <p>The release notes fix the order: <strong>loopback transport first, tools second.</strong></p>
118
+ <pre class="code powershell"><code class="powershell"># --- 1. Developer Mode (the key may not exist)
119
+ $k = &#39;HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock&#39;
120
+ if (-not (Test-Path $k)) { New-Item $k -Force | Out-Null }
121
+ Set-ItemProperty $k -Name AllowDevelopmentWithoutDevLicense -Value 1 -Type DWord
122
+ Set-ItemProperty $k -Name AllowAllTrustedApps -Value 1 -Type DWord
123
+
124
+ # --- 2. installers, in this order
125
+ Start-Process -Wait -FilePath &#39;&lt;...&gt;Basic.MIDI.1.0.Loopback.Preview...-arm64.exe&#39; `
126
+ -ArgumentList &#39;/quiet&#39;,&#39;/norestart&#39;,&#39;/log&#39;,&#39;loopback.log&#39;
127
+ Start-Process -Wait -FilePath &#39;&lt;...&gt;Tools.0.99.64-devpreview.6-arm64.exe&#39; `
128
+ -ArgumentList &#39;/quiet&#39;,&#39;/norestart&#39;,&#39;/log&#39;,&#39;tools.log&#39;
129
+ </code></pre>
130
+ <p><strong>Both return exit <code>3010</code></strong>, which is <code>ERROR_SUCCESS_REBOOT_REQUIRED</code>: installed
131
+ correctly, reboot pending. The measuring machine <strong>was not rebooted</strong>, and every
132
+ measurement was taken that way. If something behaves oddly, that reboot is the
133
+ first suspect.</p>
134
+ <p>To check the transport registered:</p>
135
+ <pre class="code powershell"><code class="powershell">Get-ChildItem &#39;HKLM:\SOFTWARE\Microsoft\Windows MIDI Services\Transport Plugins&#39;
136
+ # Midi2BasicLoopbackMidiTransport should appear, with Enabled = 1
137
+ </code></pre>
138
+ <h3 id="_midi_exe__and_the_PATH"><code>midi.exe</code> and the PATH</h3>
139
+ <p>The console lands at:</p>
140
+ <pre class="code ruby"><code class="ruby">C:\Program Files\Windows MIDI Services\Tools\Console\midi.exe
141
+ </code></pre>
142
+ <p>The installer <strong>does</strong> add it to the machine PATH, but <strong>a shell opened before
143
+ installing still has the old environment</strong> and will say <code>midi</code> does not exist. In
144
+ a fresh shell, <code>midi</code> is enough; in the installing shell, use the full path. This
145
+ detail cost time and produced one wrong report before it was noticed.</p>
146
+ <h2 id="3__Creating_the_loopback_pair">3. Creating the loopback pair</h2>
147
+ <pre class="code powershell"><code class="powershell">$midi = &#39;C:\Program Files\Windows MIDI Services\Tools\Console\midi.exe&#39;
148
+
149
+ &amp; $midi basic-loopback create --name &quot;Reloj Bitwig ñ prueba de longitud&quot;
150
+ &amp; $midi basic-loopback list
151
+ </code></pre>
152
+ <p>Options for <code>create</code>:</p>
153
+ <table>
154
+ <thead>
155
+ <tr>
156
+ <th>option</th>
157
+ <th>what it does</th>
158
+ </tr>
159
+ </thead>
160
+ <tbody>
161
+ <tr>
162
+ <td><code>-n</code>, <code>--name</code></td>
163
+ <td>the endpoint's name</td>
164
+ </tr>
165
+ <tr>
166
+ <td><code>-u</code>, <code>--unique-identifier</code></td>
167
+ <td>an identifier of your own</td>
168
+ </tr>
169
+ <tr>
170
+ <td><code>-s</code>, <code>--save-to-config</code></td>
171
+ <td><strong>persists it in the system configuration</strong></td>
172
+ </tr>
173
+ </tbody>
174
+ </table>
175
+ <p><strong>Without <code>--save-to-config</code> a loopback is temporary</strong>: it disappears when
176
+ <code>midisrv</code> or the machine restarts, and nothing is written to disk. Every
177
+ measurement was taken that way, deliberately. <code>--save-to-config</code> does modify the
178
+ user's system configuration — ask before using it.</p>
179
+ <p>A <code>create</code> returns an <strong>Association Id</strong>, a GUID. Keep it: it is the only thing
180
+ that will remove the loopback again.</p>
181
+ <h3 id="Checking_through_WinMM__which_is_what_the_gem_sees">Checking through WinMM, which is what the gem sees</h3>
182
+ <pre class="code ruby"><code class="ruby">ruby -Ilib examples/list_ports.rb
183
+ </code></pre>
184
+ <p>One loopback shows up as <strong>one input and one output with the same name, byte for
185
+ byte</strong>. With two loopbacks created, enumeration gives 2 inputs and 3 outputs —
186
+ the third being the <em>Microsoft GS Wavetable Synth</em>, which is always there.</p>
187
+ <h2 id="4__The_name_collision_recipe">4. The name-collision recipe</h2>
188
+ <p>WinMM stores <strong>31 characters</strong> of a name (<code>MAXPNAMELEN</code> is 32, less the NUL) and
189
+ drops the rest <strong>silently</strong>. To reproduce the case where two distinct ports are
190
+ indistinguishable, create two loopbacks whose <strong>first 31 characters match</strong>:</p>
191
+ <pre class="code powershell"><code class="powershell">&amp; $midi basic-loopback create --name &quot;Reloj Bitwig ñ prueba de longitud&quot;
192
+ &amp; $midi basic-loopback create --name &quot;Reloj Bitwig ñ prueba de longitud DOS&quot;
193
+ </code></pre>
194
+ <p>The first is 33 characters, the second 37; both truncate to
195
+ <code>Reloj Bitwig ñ prueba de longit</code>. In <code>MIDIINCAPSW</code>/<code>MIDIOUTCAPSW</code> they come back
196
+ <strong>identical field by field</strong>: same name, same <code>wMid</code> (1), same <code>wPid</code> (25 for
197
+ inputs, 26 for outputs), same <code>vDriverVersion</code>, same <code>wTechnology</code>. Only the
198
+ index separates them.</p>
199
+ <p>If you change the names, keep the character counts working — the recipe depends
200
+ on one being longer than 31 and both agreeing up to that point.</p>
201
+ <p>The truncation happens <strong>upstream of WinMM</strong>: <code>midi enumerate midi-services-endpoints</code> shows the service already holding the 31-character name.
202
+ Whether the console or the service truncates was not determined; for the gem it
203
+ makes no difference.</p>
204
+ <p>The <code>ñ</code> survives intact (<code>U+00F1</code> in the UTF-16LE dump), which is what validates
205
+ the <strong>W</strong> entry points against a genuinely non-ASCII character.</p>
206
+ <h2 id="5__Removing">5. Removing</h2>
207
+ <pre class="code powershell"><code class="powershell">&amp; $midi basic-loopback remove --association-id &quot;{bfba125e-3aca-446c-9b94-692a70153034}&quot;
208
+ </code></pre>
209
+ <p><strong>The braces around the GUID are required.</strong> <code>list</code> prints the id without them;
210
+ they have to be added.</p>
211
+ <p>Afterwards, check the machine is back where it started — with <code>list</code>, and better
212
+ still by enumerating through WinMM: with no loopbacks there should be <strong>0 inputs
213
+ and 1 output</strong>.</p>
214
+ <h2 id="6__What_this_setup_can_test__and_what_it_cannot">6. What this setup can test, and what it cannot</h2>
215
+ <p>Exercised with it, and working: enumeration with colliding names, short messages
216
+ in all three lengths (3, 2 and 1 bytes), accumulation in <code>gets</code>, System Exclusive
217
+ both ways at 200, 3000 and 5000 bytes, buffer recycling, closing and reopening,
218
+ and the propagation of an error from opening a port.</p>
219
+ <p><strong>Not testable with this setup</strong>, and therefore still untested:</p>
220
+ <ul>
221
+ <li><strong><code>SYSEX_TIMEOUT</code> / <code>Output#wait_until_sent</code>.</strong> The only real output device is
222
+ the <em>GS Wavetable Synth</em>, a software synthesiser that marks <code>MHDR_DONE</code> in
223
+ under a millisecond because there is no wire. This needs a physical interface.</li>
224
+ <li><strong><code>RESET_TIMEOUT</code> / <code>Input#await_returned_buffers</code>.</strong> It never came close:
225
+ closes took between 4 and 21 ms against a one-second bound. The
226
+ device-went-away path needs something to unplug.</li>
227
+ <li><strong>Renumbering in <code>Device.enumerate</code>.</strong> Needs a port to actually appear or go.</li>
228
+ <li><strong><code>MM_MIM_LONGERROR</code>.</strong> None could be provoked, not even with a System
229
+ Exclusive message larger than the entire buffer queue. The code handling it is
230
+ unexercised — its presence has been shown harmless, which is not the same
231
+ thing.</li>
232
+ <li><strong>That an input and an output of the same device share a name.</strong> Established
233
+ for a loopback, where they are literally the same device. Not for a real USB
234
+ interface.</li>
235
+ <li><strong>Any measurement of time.</strong></li>
236
+ </ul>
237
+ <p>Those are the items a session with real hardware should close.</p>
238
+ <h2 id="7__One_finding_from_here_worth_not_forgetting">7. One finding from here worth not forgetting</h2>
239
+ <p>Multi-client access <strong>follows the endpoint, not the API</strong>. Two WinMM clients on
240
+ the same BLOOP input both work and <strong>both receive every message</strong>. The <em>GS
241
+ Wavetable Synth</em>, which belongs to the classic <code>wdmaud</code> stack, refuses the second
242
+ open with <em>&quot;The specified device is already in use.&quot;</em> So on one machine, through
243
+ one API, shareable ports and exclusive ports coexist.</p></div></div>
244
+
245
+ <div id="footer">
246
+ Generated on Sun Sep 6 21:20:52 2026 by
247
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
248
+ 0.9.45 (ruby-3.4.7).
249
+ </div>
250
+
251
+ </div>
252
+ </body>
253
+ </html>
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html >
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <meta charset="utf-8">
6
+
7
+ <link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen">
8
+
9
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen">
10
+
11
+
12
+
13
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
14
+
15
+ <script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
16
+
17
+
18
+ <title>File List</title>
19
+ <base id="base_target" target="_parent">
20
+ </head>
21
+ <body>
22
+ <div id="content">
23
+ <div class="fixed_header">
24
+ <h1 id="full_list_header">File List</h1>
25
+ <div id="full_list_nav">
26
+
27
+ <span><a target="_self" href="class_list.html">
28
+ Classes
29
+ </a></span>
30
+
31
+ <span><a target="_self" href="method_list.html">
32
+ Methods
33
+ </a></span>
34
+
35
+ <span><a target="_self" href="file_list.html">
36
+ Files
37
+ </a></span>
38
+
39
+ </div>
40
+
41
+ <div id="search">
42
+ <label for="search-class">Search:</label>
43
+ <input id="search-class" type="text">
44
+ </div>
45
+ </div>
46
+
47
+ <ul id="full_list" class="file">
48
+
49
+
50
+ <li id="object_README" class="odd">
51
+ <div class="item"><span class="object_link"><a href="index.html" title="README">README</a></span></div>
52
+ </li>
53
+
54
+
55
+ <li id="object_testing-on-windows" class="even">
56
+ <div class="item"><span class="object_link"><a href="file.testing-on-windows.html" title="testing-on-windows">testing-on-windows</a></span></div>
57
+ </li>
58
+
59
+
60
+
61
+ </ul>
62
+ </div>
63
+ </body>
64
+ </html>
data/doc/frames.html ADDED
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>MIDI Communications Windows API</title>
6
+ </head>
7
+ <script type="text/javascript">
8
+ var mainUrl = 'index.html';
9
+ try {
10
+ var match = decodeURIComponent(window.location.hash).match(/^#!(.+)/);
11
+ var name = match ? match[1] : mainUrl;
12
+ var url = new URL(name, location.href);
13
+ window.top.location.replace(url.origin === location.origin ? name : mainUrl);
14
+ } catch (e) {
15
+ window.top.location.replace(mainUrl);
16
+ }
17
+ </script>
18
+ <noscript>
19
+ <h1>Oops!</h1>
20
+ <h2>YARD requires JavaScript!</h2>
21
+ </noscript>
22
+ </html>