Module: MIDICommunicationsWindows::Device
- Defined in:
- lib/midi-communications-windows/device.rb
Overview
Enumeration of the MIDI ports WinMM offers, and the attributes every port has whichever direction it runs in.
What a port's identity is here, and what it is not
WinMM identifies a port by its index within its direction: the same
integer is passed to midiInGetDevCapsW and to midiInOpen, and there is
nothing else to identify a port by. So the index is the identity, and
InstanceMethods#id is that index, unmodified.
This differs from Core MIDI, where endpoints are numbered from a single
counter shared by sources and destinations, and an input and an output
therefore never share an id. Here they do: input 0 and output 0 are both
valid and unrelated. Nothing in midi-communications compares an id across
directions — Input.all and Output.all each search their own list — and
shifting the output indices to imitate Core MIDI would replace the real
identity with an invented one.
A port's name does not identify it
WinMM stores 31 characters of a name and silently drops the rest, so two
ports whose names differ only past that point arrive with the same name.
Measured: two loopback endpoints created as "Reloj Bitwig ñ prueba de
longitud" and "Reloj Bitwig ñ prueba de longitud DOS" come back through
midiInGetDevCapsW identical in every field — same name, same wMid, same
wPid, same vDriverVersion, same wTechnology. Nothing in the structure
tells them apart. Only the index does.
This is not a contrived case: two ports of one interface whose long names
differ at the end collapse the same way. Windows does have a stable unique
identifier for each endpoint — the device interface id, which the newer APIs
expose — but midiInGetDevCapsW does not carry it, so it is not reachable
from here.
A consumer matching ports by name should know it may be matching the wrong one.
Manufacturer and model are nil, deliberately
WinMM reports wMid and wPid: numeric codes from the MMSYSTEM
manufacturer registry, which stopped being maintained in the 1990s.
Measured across every port on a Windows 11 machine — a software synth and
two loopback endpoints — wMid was 1, Microsoft, every time. wPid was no
better: 25 for every input and 26 for every output, the same for two
different devices, so it names a generic class and a direction rather than a
model. Turning those into text would label every controller on the machine
as made by Microsoft — a fact about the code table, presented as a fact
about the hardware.
So both are nil. A caller filtering by manufacturer finds nothing and can see that it found nothing, which is the truthful outcome; a caller offered an empty string or an invented name would match, or not match, for reasons that are not real.
Defined Under Namespace
Modules: ClassMethods, InstanceMethods
Class Method Summary collapse
-
.all ⇒ Array<Input, Output>
Every port, of both directions.
-
.all_by_type ⇒ Hash{Symbol => Array<Input>, Array<Output>}
Every port, grouped by direction.
-
.enumerate(direction, count, klass) ⇒ Array<Input>, Array<Output>
private
Asks WinMM what ports exist, reusing the wrapper for each one that was already there.
-
.input_name(id) ⇒ String
private
The name of an input port.
-
.inputs ⇒ Array<Input>
Every input port WinMM offers.
-
.output_name(id) ⇒ String
private
The name of an output port.
-
.outputs ⇒ Array<Output>
Every output port WinMM offers.
-
.port_name(capabilities) ⇒ String
private
Reads
szPnameout of a capabilities structure.
Class Method Details
.all ⇒ Array<Input, Output>
Every port, of both directions.
218 219 220 |
# File 'lib/midi-communications-windows/device.rb', line 218 def all all_by_type.values.flatten end |
.all_by_type ⇒ Hash{Symbol => Array<Input>, Array<Output>}
Every port, grouped by direction.
This is the shape midi-communications asks its platform adapters for.
212 213 214 |
# File 'lib/midi-communications-windows/device.rb', line 212 def all_by_type { input: inputs, output: outputs } end |
.enumerate(direction, count, klass) ⇒ Array<Input>, Array<Output>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Asks WinMM what ports exist, reusing the wrapper for each one that was already there.
The list is read afresh every time, so a port that appeared or went away
is reflected — but a port that is still present comes back as the object
it came back as last time. Otherwise Input.first.open and
Input.first.gets would be two different objects, and the second would
not be open.
"Still present" means the same index reporting the same name. The index alone is not enough: WinMM renumbers, so index 1 after a device is unplugged may be a different port than index 1 before, and handing back a wrapper holding a handle to the old one would be worse than making a new object.
260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/midi-communications-windows/device.rb', line 260 def enumerate(direction, count, klass) @ports_semaphore.synchronize do known = @ports[direction].to_h { |port| [port.id, port] } @ports[direction] = Array.new(count) do |id| name = direction == :input ? input_name(id) : output_name(id) previous = known[id] previous && previous.name == name ? previous : klass.new(id, name) end end end |
.input_name(id) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The name of an input port.
278 279 280 281 282 283 284 285 |
# File 'lib/midi-communications-windows/device.rb', line 278 def input_name(id) capabilities = API::MIDIInCaps.new API.check!(API.midiInGetDevCapsW(id, capabilities, API::MIDIInCaps.size), :midiInGetDevCapsW, :input) port_name(capabilities) end |
.inputs ⇒ Array<Input>
Every input port WinMM offers.
225 226 227 |
# File 'lib/midi-communications-windows/device.rb', line 225 def inputs enumerate(:input, API.midiInGetNumDevs, Input) end |
.output_name(id) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The name of an output port.
292 293 294 295 296 297 298 299 |
# File 'lib/midi-communications-windows/device.rb', line 292 def output_name(id) capabilities = API::MIDIOutCaps.new API.check!(API.midiOutGetDevCapsW(id, capabilities, API::MIDIOutCaps.size), :midiOutGetDevCapsW, :output) port_name(capabilities) end |
.outputs ⇒ Array<Output>
Every output port WinMM offers.
Note that this does not include the MIDI Mapper, which WinMM addresses by
the reserved id -1 and does not count among its devices.
236 237 238 |
# File 'lib/midi-communications-windows/device.rb', line 236 def outputs enumerate(:output, API.midiOutGetNumDevs, Output) end |
.port_name(capabilities) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Reads szPname out of a capabilities structure.
306 307 308 |
# File 'lib/midi-communications-windows/device.rb', line 306 def port_name(capabilities) API.read_wide_string(capabilities.to_ptr + capabilities.offset_of(:szPname), API::MAXPNAMELEN) end |