Module: MIDICommunicationsWindows::API Private

Extended by:
FFI::Library
Defined in:
lib/midi-communications-windows/api.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Low-level FFI bindings to the Windows Multimedia (WinMM) MIDI API.

This module binds winmm.dll directly. Most users should use the higher-level Input, Output and Device instead.

Why the bindings look the way they do

Pointer-sized types are never :ulong. Windows is LLP64: long stays 32 bits on 64-bit Windows while HANDLE, DWORD_PTR and UINT_PTR are pointer-sized. Measured from inside FFI on Windows 11 x64: FFI::Pointer.size is 8 and FFI.type_size(:ulong) is 4. Declaring a handle as :ulong truncates it — silently, because the low half of a handle often looks plausible. Every pointer-sized parameter here is :uintptr_t.

The W variants, never the A ones. midiInGetDevCapsA returns the port name in the machine's ANSI code page, which mangles any name that is not ASCII. A user-created loopback called "Reloj Bitwig" is a realistic port name, so the wide-character functions are the only correct choice.

dwCallback is :uintptr_t, not a callback type. WinMM overloads that parameter by the flags in dwFlags: it holds a function pointer under CALLBACK_FUNCTION, a thread id under CALLBACK_THREAD, a window handle under CALLBACK_WINDOW, and nothing under CALLBACK_NULL. Typing it as a callback would make the binding lie about three of those four.

These bindings were written from Microsoft's Win32 documentation. They are not derived from any existing Ruby binding.

Defined Under Namespace

Classes: MIDIHdr, MIDIInCaps, MIDIOutCaps, MSG

Constant Summary collapse

MAXPNAMELEN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Length of a port name in MIDIINCAPSW/MIDIOUTCAPSW, in characters, including the terminating NUL. This is the origin of WinMM's 31-character limit on port names.

32
MMSYSERR_NOERROR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

MMRESULT value meaning success.

0
MAX_ERROR_TEXT_LENGTH =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Characters reserved for an error description. WinMM truncates to fit.

256
CALLBACK_NULL =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

How WinMM should notify a client of input. Passed in dwFlags; decides how dwCallback is interpreted.

0x0000_0000
CALLBACK_WINDOW =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0001_0000
CALLBACK_THREAD =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0002_0000
CALLBACK_FUNCTION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0003_0000
MM_MIM_OPEN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Notification messages delivered for an input device.

0x3C1
MM_MIM_CLOSE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C2
MM_MIM_DATA =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C3
MM_MIM_LONGDATA =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C4
MM_MIM_ERROR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C5
MM_MIM_LONGERROR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C6
MM_MOM_OPEN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Notification messages delivered for an output device.

0x3C7
MM_MOM_CLOSE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C8
MM_MOM_DONE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x3C9
MHDR_DONE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

MIDIHDR#dwFlags bits.

0x0000_0001
MHDR_PREPARED =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0000_0002
MHDR_INQUEUE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0000_0004
WM_QUIT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Thread message queue.

0x0012
PM_NOREMOVE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0x0000
WM_USER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Windows posts messages of its own to the same queue WinMM uses. Measured: a WM_USER with both parameters zero follows every WinMM notification, including those after the port opens and closes. Nothing here emits them and nothing here knows what does, so the reader dispatches on the message id and ignores everything it did not ask for.

0x0400

Class Method Summary collapse

Class Method Details

.check!(code, operation, direction) ⇒ void

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.

This method returns an undefined value.

Raises unless the call succeeded.

Parameters:

  • code (Integer) —

    the MMRESULT a WinMM call returned

  • operation (Symbol) —

    the function that returned it, for the message

  • direction (Symbol) —

    :input or :output, to pick the error table

Raises:

  • (Error) —

    when code is anything but MMSYSERR_NOERROR



291
292
293
294
295
# File 'lib/midi-communications-windows/api.rb', line 291

def check!(code, operation, direction)
  return if code == MMSYSERR_NOERROR

  raise Error.new(operation, code, error_text(code, direction))
end

.error_text(code, direction) ⇒ 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.

WinMM's own description of an error code.

The two error tables are not the same, which is why the direction has to be known: the same numeric code can mean different things for input and for output.

Parameters:

  • code (Integer) —

    an MMRESULT

  • direction (Symbol) —

    :input or :output

Returns:

  • (String, nil) —

    the description, or nil if WinMM has none



306
307
308
309
310
311
312
313
314
315
# File 'lib/midi-communications-windows/api.rb', line 306

def error_text(code, direction)
  buffer = FFI::MemoryPointer.new(:uint16, MAX_ERROR_TEXT_LENGTH)

  result = case direction
           when :input then midiInGetErrorTextW(code, buffer, MAX_ERROR_TEXT_LENGTH)
           when :output then midiOutGetErrorTextW(code, buffer, MAX_ERROR_TEXT_LENGTH)
           end

  read_wide_string(buffer, MAX_ERROR_TEXT_LENGTH) if result == MMSYSERR_NOERROR
end

.read_wide_string(pointer, max_characters) ⇒ 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 a NUL-terminated UTF-16LE string out of native memory.

Reading is done in code units rather than bytes on purpose: a byte-wise search for a NUL pair would stop in the middle of a pair such as 'A' 'Ā' (41 00 00 01) and return a truncated name.

A name that does not decode is repaired rather than raised on: a port whose name arrives mangled is still a port the caller may want to open, and losing the whole enumeration over one bad character would be worse than showing a replacement character.

Parameters:

  • pointer (FFI::Pointer) —

    memory holding the string

  • max_characters (Integer) —

    capacity of the field, in characters

Returns:

  • (String) —

    the decoded string, in UTF-8



331
332
333
334
335
336
337
338
# File 'lib/midi-communications-windows/api.rb', line 331

def read_wide_string(pointer, max_characters)
  units = pointer.read_array_of_uint16(max_characters)
  units = units.take_while { |unit| !unit.zero? }

  units.pack('S<*')
       .force_encoding(Encoding::UTF_16LE)
       .encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
end