From 5ad0368097876a349c2fae14e0a411559f5d9e2d Mon Sep 17 00:00:00 2001 From: Hans Baier Date: Sat, 6 Mar 2021 04:42:41 +0700 Subject: [PATCH] add MIDI Streaming Descriptors --- usb_protocol/emitters/descriptors/uac.py | 67 ++++++++++++ usb_protocol/emitters/descriptors/uac2.py | 4 +- usb_protocol/types/descriptors/uac.py | 124 ++++++++++++++++++++-- usb_protocol/types/descriptors/uac2.py | 51 +++++++-- 4 files changed, 228 insertions(+), 18 deletions(-) create mode 100644 usb_protocol/emitters/descriptors/uac.py diff --git a/usb_protocol/emitters/descriptors/uac.py b/usb_protocol/emitters/descriptors/uac.py new file mode 100644 index 0000000..0798e87 --- /dev/null +++ b/usb_protocol/emitters/descriptors/uac.py @@ -0,0 +1,67 @@ +# +# This file is part of usb_protocol. +# +""" Convenience emitters for USB Audio Class 2 descriptors. """ + +from contextlib import contextmanager + +from .. import emitter_for_format +from ...types.descriptors.uac import * +from ...types.descriptors.uac2 import * +from ...emitters.descriptor import ComplexDescriptorEmitter + +###################### MIDI ######################### + +StandardMidiStreamingInterfaceDescriptorEmitter = emitter_for_format(StandardMidiStreamingInterfaceDescriptor) +ClassSpecificMidiStreamingInterfaceHeaderDescriptorEmitter = emitter_for_format(ClassSpecificMidiStreamingInterfaceHeaderDescriptor) +StandardMidiStreamingDataEndpointDescriptorEmitter = emitter_for_format(StandardMidiStreamingDataEndpointDescriptor) +StandardMidiStreamingBulkDataEndpointDescriptorEmitter = emitter_for_format(StandardMidiStreamingBulkDataEndpointDescriptor) +MidiInJackDescriptorEmitter = emitter_for_format(MidiInJackDescriptor) +MidiOutJackDescriptorElementEmitter = emitter_for_format(MidiOutJackDescriptorElement) +MidiOutJackDescriptorFootEmitter = emitter_for_format(MidiOutJackDescriptorFoot) +ClassSpecificMidiStreamingBulkDataEndpointDescriptorHeadEmitter = emitter_for_format(ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead) +ClassSpecificMidiStreamingBulkDataEndpointDescriptorElementEmitter = emitter_for_format(ClassSpecificMidiStreamingBulkDataEndpointDescriptorElement) + +class ClassSpecificMidiStreamingInterfaceDescriptorEmitter(ComplexDescriptorEmitter): + DESCRIPTOR_FORMAT = ClassSpecificMidiStreamingInterfaceHeaderDescriptor + + def _pre_emit(self): + # Figure out the total length of our descriptor, including subordinates. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() + +class MidiOutJackDescriptorEmitter(ComplexDescriptorEmitter): + DESCRIPTOR_FORMAT = MidiOutJackDescriptorHead + + def add_subordinate_descriptor(self, subordinate): + subordinate = subordinate.emit() + self._subordinates.append(subordinate) + + def add_source(self, sourceId, sourcePin=1): + sourceDescriptor = MidiOutJackDescriptorElementEmitter() + sourceDescriptor.baSourceID = sourceId + sourceDescriptor.BaSourcePin = sourcePin + self.add_subordinate_descriptor(sourceDescriptor) + + def _pre_emit(self): + self.add_subordinate_descriptor(MidiOutJackDescriptorFootEmitter()) + # Figure out the total length of our descriptor, including subordinates. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.bLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() + +class ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter(ComplexDescriptorEmitter): + DESCRIPTOR_FORMAT = ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead + + def add_subordinate_descriptor(self, subordinate): + subordinate = subordinate.emit() + self._subordinates.append(subordinate) + + def add_associated_jack(self, jackID): + jackDescriptor = ClassSpecificMidiStreamingBulkDataEndpointDescriptorElementEmitter() + jackDescriptor.baAssocJackID = jackID + self.add_subordinate_descriptor(jackDescriptor) + + def _pre_emit(self): + # Figure out the total length of our descriptor, including subordinates. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.bLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() \ No newline at end of file diff --git a/usb_protocol/emitters/descriptors/uac2.py b/usb_protocol/emitters/descriptors/uac2.py index d15bd4e..4a46009 100644 --- a/usb_protocol/emitters/descriptors/uac2.py +++ b/usb_protocol/emitters/descriptors/uac2.py @@ -10,6 +10,8 @@ from ...types.descriptors.uac import * from ...types.descriptors.uac2 import * from ...emitters.descriptor import ComplexDescriptorEmitter +###################### Audio ######################### + # Create our emitters. InterfaceAssociationDescriptorEmitter = emitter_for_format(InterfaceAssociationDescriptor) StandardAudioControlInterfaceDescriptorEmitter = emitter_for_format(StandardAudioControlInterfaceDescriptor) @@ -36,4 +38,4 @@ ExtendedTypeIIIFormatTypeDescriptorEmitter = emitt ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter = emitter_for_format(ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptor) AudioControlInterruptEndpointDescriptorEmitter = emitter_for_format(AudioControlInterruptEndpointDescriptor) AudioStreamingIsochronousEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousEndpointDescriptor) -AudioStreamingIsochronousFeedbackEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousFeedbackEndpointDescriptor) +AudioStreamingIsochronousFeedbackEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousFeedbackEndpointDescriptor) \ No newline at end of file diff --git a/usb_protocol/types/descriptors/uac.py b/usb_protocol/types/descriptors/uac.py index 796dec3..497a5a6 100644 --- a/usb_protocol/types/descriptors/uac.py +++ b/usb_protocol/types/descriptors/uac.py @@ -3,17 +3,14 @@ # """ common USB audio enums and descriptors """ -from build.lib.usb_protocol.emitters import descriptor -import unittest +from usb_protocol.types import USBSynchronizationType, USBUsageType from enum import IntEnum import construct -from construct import this, Default -from .. import LanguageIDs +from .. import USBTransferType from ..descriptor import \ - DescriptorField, DescriptorNumber, DescriptorFormat, \ - BCDFieldAdapter, DescriptorLength + DescriptorField, DescriptorNumber, DescriptorFormat class AudioInterfaceClassCode(IntEnum): AUDIO = 0x01 @@ -396,7 +393,7 @@ class EmbeddedFunctionTerminalTypes(IntEnum): AudioControlInterruptEndpointDescriptor = DescriptorFormat( "bLength" / construct.Const(7, construct.Int8ul), "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), - "bEndpointAddress" / DescriptorField(description="The address of the endpoint: D7: Direction (1 = IN); D6..4: Reserved; D3..0: endpoint number"), + "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), "bmAttributes" / DescriptorField(description="D1..0: Transfer type (0b11 = Interrupt)", default=0b11), "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") @@ -405,7 +402,7 @@ AudioControlInterruptEndpointDescriptor = DescriptorFormat( AudioStreamingIsochronousEndpointDescriptor = DescriptorFormat( "bLength" / construct.Const(7, construct.Int8ul), "bDescriptorType" / DescriptorNumber(DescriptorTypes.ENDPOINT), - "bEndpointAddress" / DescriptorField(description="The address of the endpoint: D3..0: endpoint number; D6..4: Reserved; D7: direction (0=OUT / 1=IN)"), + "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), "bmAttributes" / DescriptorField(description="D1..0: transfer type (01=isochronous); D3..2: synchronization type (01=asynchronous/10=adaptive/11=synchronous); D5..4: usage (00=data/10=feedback)", default=0b000101), "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") @@ -414,8 +411,117 @@ AudioStreamingIsochronousEndpointDescriptor = DescriptorFormat( AudioStreamingIsochronousFeedbackEndpointDescriptor = DescriptorFormat( "bLength" / construct.Const(7, construct.Int8ul), "bDescriptorType" / DescriptorNumber(DescriptorTypes.ENDPOINT), - "bEndpointAddress" / DescriptorField(description="The address of the endpoint: D3..0: endpoint number; D6..4: Reserved; D7: direction (0=OUT / 1=IN)"), + "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), "bmAttributes" / DescriptorField(description="D1..0: transfer type (01=isochronous); D3..2: synchronization type (00=no sync); D5..4: usage (10=feedback)", default=0b00100001), "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") ) + +###################### MIDI ######################### +class MidiStreamingInterfaceDescriptorTypes(IntEnum): + CS_UNDEFINED = 0x20 + CS_DEVICE = 0x21 + CS_CONFIGURATION = 0x22 + CS_STRING = 0x23 + CS_INTERFACE = 0x24 + CS_ENDPOINT = 0x25 + CS_GR_TRM_BLOCK = 0x26 + +class MidiStreamingInterfaceDescriptorSubtypes(IntEnum): + MS_DESCRIPTOR_UNDEFINED = 0x00 + MS_HEADER = 0x01 + MIDI_IN_JACK = 0x02 + MIDI_OUT_JACK = 0x03 + ELEMENT = 0x04 + +class MidiStreamingEndpointDescriptorSubtypes(IntEnum): + DESCRIPTOR_UNDEFINED = 0x00 + MS_GENERAL = 0x01 + MS_GENERAL_2_0 = 0x02 + +class MidiStreamingInterfaceHeaderClassRevision(IntEnum): + MS_MIDI_1_0 = 0x0100 + MS_MIDI_2_0 = 0x0200 + +class MidiStreamingJackTypes(IntEnum): + JACK_TYPE_UNDEFINED = 0x00 + EMBEDDED = 0x01 + EXTERNAL = 0x02 + +StandardMidiStreamingInterfaceDescriptor = DescriptorFormat( + "bLength" / construct.Const(9, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(DescriptorTypes.INTERFACE), + "bInterfaceNumber" / DescriptorField(description="ID of the streaming interface"), + "bAlternateSetting" / DescriptorField(description="alternate setting number for the interface", default=0), + "bNumEndpoints" / DescriptorField(description="Number of data endpoints used (excluding endpoint 0). Can be: 0 (no data endpoint); 1 (data endpoint); 2 (data + explicit feedback endpoint)", default=0), + "bInterfaceClass" / DescriptorNumber(AudioInterfaceClassCode.AUDIO), + "bInterfaceSubClass" / DescriptorNumber(AudioInterfaceSubclassCodes.MIDI_STREAMING), + "bInterfaceProtocol" / DescriptorNumber(0), + "iInterface" / DescriptorField(description="index of a string descriptor describing this interface (0 = unused)", default=0) +) + +ClassSpecificMidiStreamingInterfaceHeaderDescriptor = DescriptorFormat( + "bLength" / construct.Const(7, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), + "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.HEADER), + "bcdADC" / DescriptorField(description="Midi Streaming Class specification release version", default=1.0), + "wTotalLength" / DescriptorField(description="Total number of bytes of the class-specific MIDIStreaming interface descriptor. Includes the combined length of this descriptor header and all Jack and Element descriptors."), +) + +StandardMidiStreamingDataEndpointDescriptor = DescriptorFormat( + "bLength" / construct.Const(7, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), + "bEndpointAddress" / DescriptorField(description="endpoint address, use USBDirection.*.from_endpoint_address()"), + "bmAttributes" / DescriptorField(description="endpoint type, see USBTransferType (only NONE, BULK or INTERRUPT allowed)", default=USBTransferType.BULK), + "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of sending or receiving"), + "bInterval" / DescriptorField(description="Interval for polling endpoint for Interrupt data transfers. For bulk endpoints this field is ignored and must be reset to 0", default=0) +) + +MidiInJackDescriptor = DescriptorFormat( + "bLength" / construct.Const(6, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), + "bDescriptorSubtype" / DescriptorNumber(MidiStreamingInterfaceDescriptorSubtypes.MIDI_IN_JACK), + "bJackType" / DescriptorField(description="see MidiStreamingJackTypes"), + "bJackID" / DescriptorField(description="Constant uniquely identifying the MIDI IN Jack within the USB-MIDI function"), + "iJack" / DescriptorField(description="index of a string descriptor describing this jack (0 = unused)", default=0) +) + +MidiOutJackDescriptorHead = DescriptorFormat( + "bLength" / DescriptorField(description="Size of this descriptor, in bytes: 6+2*p"), + "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), + "bDescriptorSubtype" / DescriptorNumber(MidiStreamingInterfaceDescriptorSubtypes.MIDI_OUT_JACK), + "bJackType" / DescriptorField(description="see MidiStreamingJackTypes"), + "bJackID" / DescriptorField(description="Constant uniquely identifying the MIDI IN Jack within the USB-MIDI function"), + "bNrInputPins" / DescriptorField(description="Number of Input Pins of this MIDI OUT Jack: p", default=1) +) + +MidiOutJackDescriptorElement = DescriptorFormat( + "baSourceID" / construct.Int8ul, # ID of the Entity to which the first Input Pin of this MIDI OUT Jack is connected + "BaSourcePin" / construct.Int8ul, #Output Pin number of the Entity to which the first Input Pin of this MIDI OUT Jack is connected +) + +MidiOutJackDescriptorFoot = DescriptorFormat( + "iJack" / DescriptorField(description="index of a string descriptor describing this jack (0 = unused)", default=0) +) + +StandardMidiStreamingBulkDataEndpointDescriptor = DescriptorFormat( + "bLength" / construct.Const(9, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(DescriptorTypes.ENDPOINT), + "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), + "bmAttributes" / DescriptorField(description="D1..0: transfer type (10=bulk), D3..2: synchronization type (00=no sync);", default=USBTransferType.BULK | USBSynchronizationType.NONE | USBUsageType.DATA), + "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of", default=512), + "bInterval" / DescriptorField(description="Interval for polling endpoint for data transfers expressed in milliseconds. This field is ignored for bulk endpoints. Must be set to 0", default=0), + "bRefresh" / DescriptorField(description="must be set to 0", default=0), + "bSynchAddress" / DescriptorField(description="The address of the endpoint used to communicate synchronization information if required by this endpoint. Must be set to 0", default=0) +) + +ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead = DescriptorFormat( + "bLength" / DescriptorField(description="Size of this descriptor, in bytes: 4+n"), + "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), + "bDescriptorSubtype" / DescriptorField(description="see MidiStreamingEndpointDescriptorSubtypes", default=MidiStreamingEndpointDescriptorSubtypes.MS_GENERAL), + "bNumEmbMIDIJack" / DescriptorField(description="Number of Embedded MIDI Jacks: n", default=1) +) + +ClassSpecificMidiStreamingBulkDataEndpointDescriptorElement = DescriptorFormat( + "baAssocJackID" / construct.Int8ul # ID of the embedded eack that is associated with this endpoint +) \ No newline at end of file diff --git a/usb_protocol/types/descriptors/uac2.py b/usb_protocol/types/descriptors/uac2.py index 341d3c8..2c262f2 100644 --- a/usb_protocol/types/descriptors/uac2.py +++ b/usb_protocol/types/descriptors/uac2.py @@ -5,17 +5,13 @@ NOTE: This is not complete yet and will be extended as needed """ -from build.lib.usb_protocol.emitters import descriptor -import unittest -from enum import IntEnum +from usb_protocol.emitters import descriptor +from enum import IntEnum import construct -from construct import this, Default -from .. import LanguageIDs from ..descriptor import \ - DescriptorField, DescriptorNumber, DescriptorFormat, \ - BCDFieldAdapter, DescriptorLength + DescriptorField, DescriptorNumber, DescriptorFormat from .uac import * @@ -303,4 +299,43 @@ ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptor = DescriptorFo "bmControls" / DescriptorField(description="D1..0: pitch control D3..2: data overrun control; D5..4: data underrun control;", default=0), "bLockDelayUnits" / DescriptorField(description="wLockDelay unit: 0: undefined; 1: milliseconds; 2: decoded PCM samples;", default=0), "wLockDelay" / DescriptorField(description="the time it takes this endpoint to reliably lock its internal clock recovery circuitry. Units see bLockDelayUnits", default=0) -) \ No newline at end of file +) + +###################### MIDI ######################### + +class MidiStreamingGroupTerminalBlockDescriptorSubtypes(IntEnum): + GR_TRM_BLOCK_UNDEFINED = 0x00 + GR_TRM_BLOCK_HEADER = 0x01 + GR_TRM_BLOCK = 0x02 + +class GroupTerminalBlockType(IntEnum): + BIDIRECTIONAL = 0x00 + INPUT_ONLY = 0x01 + OUTPUT_ONLY = 0x02 + +class GroupTerminalDefaultMidiProtocol(IntEnum): + USE_MIDI_CI = 0x00 + MIDI_1_0_UP_TO_64_BITS = 0x01 + MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 0x02 + MIDI_1_0_UP_TO_128_BITS = 0x03 + MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 0x04 + MIDI_2_0 = 0x11 + MIDI_2_0_AND_JRTS = 0x12 + +class GroupTerminalNumber(IntEnum): + GROUP_1 = 0x00 + GROUP_2 = 0x01 + GROUP_3 = 0x02 + GROUP_4 = 0x03 + GROUP_5 = 0x04 + GROUP_6 = 0x05 + GROUP_7 = 0x06 + GROUP_8 = 0x07 + GROUP_9 = 0x08 + GROUP_10 = 0x09 + GROUP_11 = 0x0A + GROUP_12 = 0x0B + GROUP_13 = 0x0C + GROUP_14 = 0x0D + GROUP_15 = 0x0E + GROUP_16 = 0x0F \ No newline at end of file