#!/usr/bin/env python3 # # Copyright (c) 2021 Hans Baier # SPDX-License-Identifier: CERN-OHL-W-2.0 from amaranth import * from usb_protocol.types import USBRequestType, USBRequestRecipient, USBTransferType, USBSynchronizationType, USBUsageType, USBDirection, USBStandardRequests from usb_protocol.types.descriptors.uac2 import AudioClassSpecificRequestCodes from usb_protocol.emitters import DeviceDescriptorCollection from usb_protocol.emitters.descriptors import uac2, standard from usb_protocol.emitters.descriptors import midi1 class USBDescriptors(): MAX_PACKET_SIZE_MIDI = 64 CLOCK_ID = 1 def __init__(self, *, ila_max_packet_size: int, use_ila=False) -> None: # ILA self.USE_ILA = use_ila self.ILA_MAX_PACKET_SIZE = ila_max_packet_size def create_usb1_descriptors(self, no_channels: int, max_packet_size: int): """ Creates the descriptors for the main USB interface """ return self.create_descriptors("ADATface (USB1)", no_channels, max_packet_size, self.USE_ILA) def create_usb2_descriptors(self, no_channels: int, max_packet_size: int): """ Creates the descriptors for the secondary USB interface """ return self.create_descriptors("ADATface (USB2)", no_channels, max_packet_size) def create_descriptors(self, product_id: str, no_channels: int, max_packet_size: int, create_ila=False): """ Creates the descriptors for the main USB interface """ descriptors = DeviceDescriptorCollection() with descriptors.DeviceDescriptor() as d: d.bcdUSB = 2.00 d.bDeviceClass = 0xEF d.bDeviceSubclass = 0x02 d.bDeviceProtocol = 0x01 d.idVendor = 0x1209 d.idProduct = 0xADA1 if "USB1" in product_id else 0xADA2 d.iManufacturer = "OpenAudioGear" d.iProduct = product_id d.iSerialNumber = "0" d.bcdDevice = 0.01 d.bNumConfigurations = 1 with descriptors.ConfigurationDescriptor() as configDescr: # Interface Association interfaceAssociationDescriptor = uac2.InterfaceAssociationDescriptorEmitter() interfaceAssociationDescriptor.bInterfaceCount = 3 # Audio Control + Inputs + Outputs configDescr.add_subordinate_descriptor(interfaceAssociationDescriptor) # Interface Descriptor (Control) interfaceDescriptor = uac2.StandardAudioControlInterfaceDescriptorEmitter() interfaceDescriptor.bInterfaceNumber = 0 configDescr.add_subordinate_descriptor(interfaceDescriptor) # AudioControl Interface Descriptor audioControlInterface = self.create_audio_control_interface_descriptor(no_channels) configDescr.add_subordinate_descriptor(audioControlInterface) self.create_output_channels_descriptor(configDescr, no_channels, max_packet_size) self.create_input_channels_descriptor(configDescr, no_channels, max_packet_size) midi_interface, midi_streaming_interface = self.create_midi_interface_descriptor() configDescr.add_subordinate_descriptor(midi_interface) configDescr.add_subordinate_descriptor(midi_streaming_interface) if create_ila: with configDescr.InterfaceDescriptor() as i: i.bInterfaceNumber = 4 with i.EndpointDescriptor() as e: e.bEndpointAddress = USBDirection.IN.to_endpoint_address(4) e.wMaxPacketSize = self.ILA_MAX_PACKET_SIZE return descriptors def create_audio_control_interface_descriptor(self, number_of_channels): audioControlInterface = uac2.ClassSpecificAudioControlInterfaceDescriptorEmitter() # AudioControl Interface Descriptor (ClockSource) clockSource = uac2.ClockSourceDescriptorEmitter() clockSource.bClockID = self.CLOCK_ID clockSource.bmAttributes = uac2.ClockAttributes.INTERNAL_FIXED_CLOCK clockSource.bmControls = uac2.ClockFrequencyControl.HOST_READ_ONLY audioControlInterface.add_subordinate_descriptor(clockSource) # streaming input port from the host to the USB interface inputTerminal = uac2.InputTerminalDescriptorEmitter() inputTerminal.bTerminalID = 2 inputTerminal.wTerminalType = uac2.USBTerminalTypes.USB_STREAMING # The number of channels needs to be 2 here in order to be recognized # default audio out device by Windows. We provide an alternate # setting with the full channel count, which also references # this terminal ID inputTerminal.bNrChannels = 2 inputTerminal.bCSourceID = 1 audioControlInterface.add_subordinate_descriptor(inputTerminal) # audio output port from the USB interface to the outside world outputTerminal = uac2.OutputTerminalDescriptorEmitter() outputTerminal.bTerminalID = 3 outputTerminal.wTerminalType = uac2.OutputTerminalTypes.SPEAKER outputTerminal.bSourceID = 2 outputTerminal.bCSourceID = 1 audioControlInterface.add_subordinate_descriptor(outputTerminal) # audio input port from the outside world to the USB interface inputTerminal = uac2.InputTerminalDescriptorEmitter() inputTerminal.bTerminalID = 4 inputTerminal.wTerminalType = uac2.InputTerminalTypes.MICROPHONE inputTerminal.bNrChannels = number_of_channels inputTerminal.bCSourceID = 1 audioControlInterface.add_subordinate_descriptor(inputTerminal) # audio output port from the USB interface to the host outputTerminal = uac2.OutputTerminalDescriptorEmitter() outputTerminal.bTerminalID = 5 outputTerminal.wTerminalType = uac2.USBTerminalTypes.USB_STREAMING outputTerminal.bSourceID = 4 outputTerminal.bCSourceID = 1 audioControlInterface.add_subordinate_descriptor(outputTerminal) return audioControlInterface def create_output_streaming_interface(self, c, *, no_channels: int, alt_setting_nr: int, max_packet_size): # Interface Descriptor (Streaming, OUT, active setting) activeAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter() activeAudioStreamingInterface.bInterfaceNumber = 1 activeAudioStreamingInterface.bAlternateSetting = alt_setting_nr activeAudioStreamingInterface.bNumEndpoints = 2 c.add_subordinate_descriptor(activeAudioStreamingInterface) # AudioStreaming Interface Descriptor (General) audioStreamingInterface = uac2.ClassSpecificAudioStreamingInterfaceDescriptorEmitter() audioStreamingInterface.bTerminalLink = 2 audioStreamingInterface.bFormatType = uac2.FormatTypes.FORMAT_TYPE_I audioStreamingInterface.bmFormats = uac2.TypeIFormats.PCM audioStreamingInterface.bNrChannels = no_channels c.add_subordinate_descriptor(audioStreamingInterface) # AudioStreaming Interface Descriptor (Type I) typeIStreamingInterface = uac2.TypeIFormatTypeDescriptorEmitter() typeIStreamingInterface.bSubslotSize = 4 typeIStreamingInterface.bBitResolution = 24 c.add_subordinate_descriptor(typeIStreamingInterface) # Endpoint Descriptor (Audio out) audioOutEndpoint = standard.EndpointDescriptorEmitter() audioOutEndpoint.bEndpointAddress = USBDirection.OUT.to_endpoint_address(1) # EP 1 OUT audioOutEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \ (USBSynchronizationType.ASYNC << 2) | \ (USBUsageType.DATA << 4) audioOutEndpoint.wMaxPacketSize = max_packet_size audioOutEndpoint.bInterval = 1 c.add_subordinate_descriptor(audioOutEndpoint) # AudioControl Endpoint Descriptor audioControlEndpoint = uac2.ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter() c.add_subordinate_descriptor(audioControlEndpoint) # Endpoint Descriptor (Feedback IN) feedbackInEndpoint = standard.EndpointDescriptorEmitter() feedbackInEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(1) # EP 1 IN feedbackInEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \ (USBSynchronizationType.NONE << 2) | \ (USBUsageType.FEEDBACK << 4) feedbackInEndpoint.wMaxPacketSize = 4 feedbackInEndpoint.bInterval = 4 c.add_subordinate_descriptor(feedbackInEndpoint) def create_output_channels_descriptor(self, c, no_channels: int, max_packet_size: int): # # Interface Descriptor (Streaming, OUT, quiet setting) # quietAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter() quietAudioStreamingInterface.bInterfaceNumber = 1 quietAudioStreamingInterface.bAlternateSetting = 0 c.add_subordinate_descriptor(quietAudioStreamingInterface) # we need the default alternate setting to be stereo # out for windows to automatically recognize # and use this audio interface self.create_output_streaming_interface(c, no_channels=2, alt_setting_nr=1, max_packet_size=max_packet_size) if no_channels > 2: self.create_output_streaming_interface(c, no_channels=no_channels, alt_setting_nr=2, max_packet_size=max_packet_size) def create_input_streaming_interface(self, c, *, no_channels: int, alt_setting_nr: int, channel_config: int=0, max_packet_size: int): # Interface Descriptor (Streaming, IN, active setting) activeAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter() activeAudioStreamingInterface.bInterfaceNumber = 2 activeAudioStreamingInterface.bAlternateSetting = alt_setting_nr activeAudioStreamingInterface.bNumEndpoints = 1 c.add_subordinate_descriptor(activeAudioStreamingInterface) # AudioStreaming Interface Descriptor (General) audioStreamingInterface = uac2.ClassSpecificAudioStreamingInterfaceDescriptorEmitter() audioStreamingInterface.bTerminalLink = 5 audioStreamingInterface.bFormatType = uac2.FormatTypes.FORMAT_TYPE_I audioStreamingInterface.bmFormats = uac2.TypeIFormats.PCM audioStreamingInterface.bNrChannels = no_channels audioStreamingInterface.bmChannelConfig = channel_config c.add_subordinate_descriptor(audioStreamingInterface) # AudioStreaming Interface Descriptor (Type I) typeIStreamingInterface = uac2.TypeIFormatTypeDescriptorEmitter() typeIStreamingInterface.bSubslotSize = 4 typeIStreamingInterface.bBitResolution = 24 # we use all 24 bits c.add_subordinate_descriptor(typeIStreamingInterface) # Endpoint Descriptor (Audio out) audioOutEndpoint = standard.EndpointDescriptorEmitter() audioOutEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(2) # EP 2 IN audioOutEndpoint.bmAttributes = USBTransferType.ISOCHRONOUS | \ (USBSynchronizationType.ASYNC << 2) | \ (USBUsageType.DATA << 4) audioOutEndpoint.wMaxPacketSize = max_packet_size audioOutEndpoint.bInterval = 1 c.add_subordinate_descriptor(audioOutEndpoint) # AudioControl Endpoint Descriptor audioControlEndpoint = uac2.ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter() c.add_subordinate_descriptor(audioControlEndpoint) def create_input_channels_descriptor(self, c, no_channels: int, max_packet_size: int): # # Interface Descriptor (Streaming, IN, quiet setting) # quietAudioStreamingInterface = uac2.AudioStreamingInterfaceDescriptorEmitter() quietAudioStreamingInterface.bInterfaceNumber = 2 quietAudioStreamingInterface.bAlternateSetting = 0 c.add_subordinate_descriptor(quietAudioStreamingInterface) # Windows wants a stereo pair as default setting, so let's have it self.create_input_streaming_interface(c, no_channels=2, alt_setting_nr=1, channel_config=0x3, max_packet_size=max_packet_size) if no_channels > 2: self.create_input_streaming_interface(c, no_channels=no_channels, alt_setting_nr=2, channel_config=0x0, max_packet_size=max_packet_size) def create_midi_interface_descriptor(self): midi_interface = midi1.StandardMidiStreamingInterfaceDescriptorEmitter() midi_interface.bInterfaceNumber = 3 midi_interface.bNumEndpoints = 2 midi_streaming_interface = midi1.ClassSpecificMidiStreamingInterfaceDescriptorEmitter() outToHostJack = midi1.MidiOutJackDescriptorEmitter() outToHostJack.bJackID = 1 outToHostJack.bJackType = midi1.MidiStreamingJackTypes.EMBEDDED outToHostJack.add_source(2) midi_streaming_interface.add_subordinate_descriptor(outToHostJack) inToDeviceJack = midi1.MidiInJackDescriptorEmitter() inToDeviceJack.bJackID = 2 inToDeviceJack.bJackType = midi1.MidiStreamingJackTypes.EXTERNAL midi_streaming_interface.add_subordinate_descriptor(inToDeviceJack) inFromHostJack = midi1.MidiInJackDescriptorEmitter() inFromHostJack.bJackID = 3 inFromHostJack.bJackType = midi1.MidiStreamingJackTypes.EMBEDDED midi_streaming_interface.add_subordinate_descriptor(inFromHostJack) outFromDeviceJack = midi1.MidiOutJackDescriptorEmitter() outFromDeviceJack.bJackID = 4 outFromDeviceJack.bJackType = midi1.MidiStreamingJackTypes.EXTERNAL outFromDeviceJack.add_source(3) midi_streaming_interface.add_subordinate_descriptor(outFromDeviceJack) outEndpoint = midi1.StandardMidiStreamingBulkDataEndpointDescriptorEmitter() outEndpoint.bEndpointAddress = USBDirection.OUT.to_endpoint_address(3) outEndpoint.wMaxPacketSize = self.MAX_PACKET_SIZE_MIDI midi_streaming_interface.add_subordinate_descriptor(outEndpoint) outMidiEndpoint = midi1.ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter() outMidiEndpoint.add_associated_jack(3) midi_streaming_interface.add_subordinate_descriptor(outMidiEndpoint) inEndpoint = midi1.StandardMidiStreamingBulkDataEndpointDescriptorEmitter() inEndpoint.bEndpointAddress = USBDirection.IN.to_endpoint_address(3) inEndpoint.wMaxPacketSize = self.MAX_PACKET_SIZE_MIDI midi_streaming_interface.add_subordinate_descriptor(inEndpoint) inMidiEndpoint = midi1.ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter() inMidiEndpoint.add_associated_jack(1) midi_streaming_interface.add_subordinate_descriptor(inMidiEndpoint) return (midi_interface, midi_streaming_interface)