From 3130088c9104f237c2372b7c277f4c4680f00b34 Mon Sep 17 00:00:00 2001 From: Tom Williams Date: Wed, 23 Nov 2022 17:53:47 +0000 Subject: [PATCH 1/6] Initial commit for host_usb_mixer_control --- lib_xua/src/host_usb_mixer_control/EULA | 27 + lib_xua/src/host_usb_mixer_control/LICENSING | 3 + .../src/host_usb_mixer_control/Makefile.OSX | 2 + .../src/host_usb_mixer_control/Makefile.Win32 | 4 + .../OSX/libusb-1.0.0.dylib | Bin 0 -> 141344 bytes .../src/host_usb_mixer_control/OSX/libusb.h | 1252 +++++++++++++++++ .../host_usb_mixer_control/OSX/usb_mixer.cpp | 940 +++++++++++++ lib_xua/src/host_usb_mixer_control/README | 103 ++ lib_xua/src/host_usb_mixer_control/VERSION | 1 + .../src/host_usb_mixer_control/Win32/global.h | 61 + .../Win32/usb_mixer.cpp | 925 ++++++++++++ .../src/host_usb_mixer_control/mixer_app.cpp | 719 ++++++++++ .../host_usb_mixer_control/module_description | 1 + lib_xua/src/host_usb_mixer_control/setup.sh | 1 + .../src/host_usb_mixer_control/usb_mixer.h | 124 ++ 15 files changed, 4163 insertions(+) create mode 100644 lib_xua/src/host_usb_mixer_control/EULA create mode 100644 lib_xua/src/host_usb_mixer_control/LICENSING create mode 100644 lib_xua/src/host_usb_mixer_control/Makefile.OSX create mode 100644 lib_xua/src/host_usb_mixer_control/Makefile.Win32 create mode 100644 lib_xua/src/host_usb_mixer_control/OSX/libusb-1.0.0.dylib create mode 100644 lib_xua/src/host_usb_mixer_control/OSX/libusb.h create mode 100644 lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp create mode 100644 lib_xua/src/host_usb_mixer_control/README create mode 100644 lib_xua/src/host_usb_mixer_control/VERSION create mode 100644 lib_xua/src/host_usb_mixer_control/Win32/global.h create mode 100644 lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp create mode 100644 lib_xua/src/host_usb_mixer_control/mixer_app.cpp create mode 100644 lib_xua/src/host_usb_mixer_control/module_description create mode 100644 lib_xua/src/host_usb_mixer_control/setup.sh create mode 100644 lib_xua/src/host_usb_mixer_control/usb_mixer.h diff --git a/lib_xua/src/host_usb_mixer_control/EULA b/lib_xua/src/host_usb_mixer_control/EULA new file mode 100644 index 00000000..e82f77f9 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/EULA @@ -0,0 +1,27 @@ +Software License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +• Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimers. + +• Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimers in the documentation +and/or other materials provided with the distribution. + +• Neither the name of XMOS, nor the names of its contributors may be used to +endorse or promote products derived from this Software without specific +prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/lib_xua/src/host_usb_mixer_control/LICENSING b/lib_xua/src/host_usb_mixer_control/LICENSING new file mode 100644 index 00000000..13c2f420 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/LICENSING @@ -0,0 +1,3 @@ +All code contained in this package under XMOS copyright must be +licensing for any use from XMOS. Please contact support@xmos.com for +details of licensing. diff --git a/lib_xua/src/host_usb_mixer_control/Makefile.OSX b/lib_xua/src/host_usb_mixer_control/Makefile.OSX new file mode 100644 index 00000000..d46367f9 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/Makefile.OSX @@ -0,0 +1,2 @@ +all: + g++ -g -o xmos_mixer OSX/usb_mixer.cpp mixer_app.cpp -I. -IOSX OSX/libusb-1.0.0.dylib -m64 \ No newline at end of file diff --git a/lib_xua/src/host_usb_mixer_control/Makefile.Win32 b/lib_xua/src/host_usb_mixer_control/Makefile.Win32 new file mode 100644 index 00000000..dcae45f4 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/Makefile.Win32 @@ -0,0 +1,4 @@ +THESYCON_DIR = C:\Program/ Files\Thesycon\XMOSinternalUsbAudio_v5.50.0_eval_2022-11-17_SDK + +all: + cl -o xmos_mixer.exe -I. -I Win32 -I $(THESYCON_DIR)\source\tusbaudioapi_inc -I $(THESYCON_DIR)\source\libwn_min mixer_app.cpp Win32\usb_mixer.cpp $(THESYCON_DIR)\lib\release\x64\tusbaudioapi.lib diff --git a/lib_xua/src/host_usb_mixer_control/OSX/libusb-1.0.0.dylib b/lib_xua/src/host_usb_mixer_control/OSX/libusb-1.0.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..0e2e07787999e056ab5bbad1ca2f91d8ae10bf86 GIT binary patch literal 141344 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 literal 0 HcmV?d00001 diff --git a/lib_xua/src/host_usb_mixer_control/OSX/libusb.h b/lib_xua/src/host_usb_mixer_control/OSX/libusb.h new file mode 100644 index 00000000..e5487bc7 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/OSX/libusb.h @@ -0,0 +1,1252 @@ +/** + * Module: host_usb_mixer_control + * Version: 1v0 + * Build: d94b0511afe40ece896637f88c6379f9b6f9f603 + * File: libusb.h + * + * The copyrights, all other intellectual and industrial + * property rights are retained by XMOS and/or its licensors. + * Terms and conditions covering the use of this code can + * be found in the Xmos End User License Agreement. + * + * Copyright XMOS Ltd 2010 + * + * In the case where this code is a modification of existing code + * under a separate license, the separate license terms are shown + * below. The modifications to the code are still covered by the + * copyright notice above. + * + **/ +/* + * Public libusb header file + * Copyright (C) 2007-2008 Daniel Drake + * Copyright (c) 2001 Johannes Erdfelt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __LIBUSB_H__ +#define __LIBUSB_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \def libusb_cpu_to_le16 + * \ingroup misc + * Convert a 16-bit value from host-endian to little-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the host-endian value to convert + * \returns the value in little-endian byte order + */ +#define libusb_cpu_to_le16(x) ({ \ + union { \ + uint8_t b8[2]; \ + uint16_t b16; \ + } _tmp; \ + uint16_t _tmp2 = (uint16_t)(x); \ + _tmp.b8[1] = _tmp2 >> 8; \ + _tmp.b8[0] = _tmp2 & 0xff; \ + _tmp.b16; \ +}) + +/** \def libusb_le16_to_cpu + * \ingroup misc + * Convert a 16-bit value from little-endian to host-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the little-endian value to convert + * \returns the value in host-endian byte order + */ +#define libusb_le16_to_cpu libusb_cpu_to_le16 + +/* standard USB stuff */ + +/** \ingroup desc + * Device and/or Interface Class codes */ +enum libusb_class_code { + /** In the context of a \ref libusb_device_descriptor "device descriptor", + * this bDeviceClass value indicates that each interface specifies its + * own class information and all interfaces operate independently. + */ + LIBUSB_CLASS_PER_INTERFACE = 0, + + /** Audio class */ + LIBUSB_CLASS_AUDIO = 1, + + /** Communications class */ + LIBUSB_CLASS_COMM = 2, + + /** Human Interface Device class */ + LIBUSB_CLASS_HID = 3, + + /** Printer dclass */ + LIBUSB_CLASS_PRINTER = 7, + + /** Picture transfer protocol class */ + LIBUSB_CLASS_PTP = 6, + + /** Mass storage class */ + LIBUSB_CLASS_MASS_STORAGE = 8, + + /** Hub class */ + LIBUSB_CLASS_HUB = 9, + + /** Data class */ + LIBUSB_CLASS_DATA = 10, + + /** Class is vendor-specific */ + LIBUSB_CLASS_VENDOR_SPEC = 0xff +}; + +/** \ingroup desc + * Descriptor types as defined by the USB specification. */ +enum libusb_descriptor_type { + /** Device descriptor. See libusb_device_descriptor. */ + LIBUSB_DT_DEVICE = 0x01, + + /** Configuration descriptor. See libusb_config_descriptor. */ + LIBUSB_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB_DT_STRING = 0x03, + + /** Interface descriptor. See libusb_interface_descriptor. */ + LIBUSB_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See libusb_endpoint_descriptor. */ + LIBUSB_DT_ENDPOINT = 0x05, + + /** HID descriptor */ + LIBUSB_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB_DT_HUB = 0x29 +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB_DT_DEVICE_SIZE 18 +#define LIBUSB_DT_CONFIG_SIZE 9 +#define LIBUSB_DT_INTERFACE_SIZE 9 +#define LIBUSB_DT_ENDPOINT_SIZE 7 +#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB_DT_HUB_NONVAR_SIZE 7 + +#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup desc + * Endpoint direction. Values for bit 7 of the + * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. + */ +enum libusb_endpoint_direction { + /** In: device-to-host */ + LIBUSB_ENDPOINT_IN = 0x80, + + /** Out: host-to-device */ + LIBUSB_ENDPOINT_OUT = 0x00 +}; + +#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. + */ +enum libusb_transfer_type { + /** Control endpoint */ + LIBUSB_TRANSFER_TYPE_CONTROL = 0, + + /** Isochronous endpoint */ + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1, + + /** Bulk endpoint */ + LIBUSB_TRANSFER_TYPE_BULK = 2, + + /** Interrupt endpoint */ + LIBUSB_TRANSFER_TYPE_INTERRUPT = 3 +}; + +/** \ingroup misc + * Standard requests, as defined in table 9-3 of the USB2 specifications */ +enum libusb_standard_request { + /** Request status of the specific recipient */ + LIBUSB_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified interface */ + LIBUSB_REQUEST_GET_INTERFACE = 0x0A, + + /** Select an alternate interface for the specified interface */ + LIBUSB_REQUEST_SET_INTERFACE = 0x0B, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB_REQUEST_SYNCH_FRAME = 0x0C +}; + +/** \ingroup misc + * Request type bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. */ +enum libusb_request_type { + /** Standard */ + LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) +}; + +/** \ingroup misc + * Recipient bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. Values 4 through 31 are reserved. */ +enum libusb_request_recipient { + /** Device */ + LIBUSB_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB_RECIPIENT_OTHER = 0x03 +}; + +#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0C + +/** \ingroup desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_sync_type { + /** No synchronization */ + LIBUSB_ISO_SYNC_TYPE_NONE = 0, + + /** Asynchronous */ + LIBUSB_ISO_SYNC_TYPE_ASYNC = 1, + + /** Adaptive */ + LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 2, + + /** Synchronous */ + LIBUSB_ISO_SYNC_TYPE_SYNC = 3 +}; + +#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_usage_type { + /** Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_DATA = 0, + + /** Feedback endpoint */ + LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 1, + + /** Implicit feedback Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 2 +}; + +/** \ingroup desc + * A structure representing the standard USB device descriptor. This + * descriptor is documented in section 9.6.1 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_device_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this + * context. */ + uint8_t bDescriptorType; + + /** USB specification release number in binary-coded decimal. A value of + * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ + uint16_t bcdUSB; + + /** USB-IF class code for the device. See \ref libusb_class_code. */ + uint8_t bDeviceClass; + + /** USB-IF subclass code for the device, qualified by the bDeviceClass + * value */ + uint8_t bDeviceSubClass; + + /** USB-IF protocol code for the device, qualified by the bDeviceClass and + * bDeviceSubClass values */ + uint8_t bDeviceProtocol; + + /** Maximum packet size for endpoint 0 */ + uint8_t bMaxPacketSize0; + + /** USB-IF vendor ID */ + uint16_t idVendor; + + /** USB-IF product ID */ + uint16_t idProduct; + + /** Device release number in binary-coded decimal */ + uint16_t bcdDevice; + + /** Index of string descriptor describing manufacturer */ + uint8_t iManufacturer; + + /** Index of string descriptor describing product */ + uint8_t iProduct; + + /** Index of string descriptor containing device serial number */ + uint8_t iSerialNumber; + + /** Number of possible configurations */ + uint8_t bNumConfigurations; +}; + +/** \ingroup desc + * A structure representing the standard USB endpoint descriptor. This + * descriptor is documented in section 9.6.3 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_endpoint_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in + * this context. */ + uint8_t bDescriptorType; + + /** The address of the endpoint described by this descriptor. Bits 0:3 are + * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, + * see \ref libusb_endpoint_direction. + */ + uint8_t bEndpointAddress; + + /** Attributes which apply to the endpoint when it is configured using + * the bConfigurationValue. Bits 0:1 determine the transfer type and + * correspond to \ref libusb_transfer_type. Bits 2:3 are only used for + * isochronous endpoints and correspond to \ref libusb_iso_sync_type. + * Bits 4:5 are also only used for isochronous endpoints and correspond to + * \ref libusb_iso_usage_type. Bits 6:7 are reserved. + */ + uint8_t bmAttributes; + + /** Maximum packet size this endpoint is capable of sending/receiving. */ + uint16_t wMaxPacketSize; + + /** Interval for polling endpoint for data transfers. */ + uint8_t bInterval; + + /** For audio devices only: the rate at which synchronization feedback + * is provided. */ + uint8_t bRefresh; + + /** For audio devices only: the address if the synch endpoint */ + uint8_t bSynchAddress; + + /** Extra descriptors. If libusb encounters unknown endpoint descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup desc + * A structure representing the standard USB interface descriptor. This + * descriptor is documented in section 9.6.5 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE + * in this context. */ + uint8_t bDescriptorType; + + /** Number of this interface */ + uint8_t bInterfaceNumber; + + /** Value used to select this alternate setting for this interface */ + uint8_t bAlternateSetting; + + /** Number of endpoints used by this interface (excluding the control + * endpoint). */ + uint8_t bNumEndpoints; + + /** USB-IF class code for this interface. See \ref libusb_class_code. */ + uint8_t bInterfaceClass; + + /** USB-IF subclass code for this interface, qualified by the + * bInterfaceClass value */ + uint8_t bInterfaceSubClass; + + /** USB-IF protocol code for this interface, qualified by the + * bInterfaceClass and bInterfaceSubClass values */ + uint8_t bInterfaceProtocol; + + /** Index of string descriptor describing this interface */ + uint8_t iInterface; + + /** Array of endpoint descriptors. This length of this array is determined + * by the bNumEndpoints field. */ + const struct libusb_endpoint_descriptor *endpoint; + + /** Extra descriptors. If libusb encounters unknown interface descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup desc + * A collection of alternate settings for a particular USB interface. + */ +struct libusb_interface { + /** Array of interface descriptors. The length of this array is determined + * by the num_altsetting field. */ + const struct libusb_interface_descriptor *altsetting; + + /** The number of alternate settings that belong to this interface */ + int num_altsetting; +}; + +/** \ingroup desc + * A structure representing the standard USB configuration descriptor. This + * descriptor is documented in section 9.6.3 of the USB 2.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_config_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG + * in this context. */ + uint8_t bDescriptorType; + + /** Total length of data returned for this configuration */ + uint16_t wTotalLength; + + /** Number of interfaces supported by this configuration */ + uint8_t bNumInterfaces; + + /** Identifier value for this configuration */ + uint8_t bConfigurationValue; + + /** Index of string descriptor describing this configuration */ + uint8_t iConfiguration; + + /** Configuration characteristics */ + uint8_t bmAttributes; + + /** Maximum power consumption of the USB device from this bus in this + * configuration when the device is fully opreation. Expressed in units + * of 2 mA. */ + uint8_t MaxPower; + + /** Array of interfaces supported by this configuration. The length of + * this array is determined by the bNumInterfaces field. */ + const struct libusb_interface *interface; + + /** Extra descriptors. If libusb encounters unknown configuration + * descriptors, it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup asyncio + * Setup packet for control transfers. */ +struct libusb_control_setup { + /** Request type. Bits 0:4 determine recipient, see + * \ref libusb_request_recipient. Bits 5:6 determine type, see + * \ref libusb_request_type. Bit 7 determines data transfer direction, see + * \ref libusb_endpoint_direction. + */ + uint8_t bmRequestType; + + /** Request. If the type bits of bmRequestType are equal to + * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD + * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to + * \ref libusb_standard_request. For other cases, use of this field is + * application-specific. */ + uint8_t bRequest; + + /** Value. Varies according to request */ + uint16_t wValue; + + /** Index. Varies according to request, typically used to pass an index + * or offset */ + uint16_t wIndex; + + /** Number of bytes to transfer */ + uint16_t wLength; +}; + +#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) + +/* libusb */ + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +/** \ingroup lib + * Structure representing a libusb session. The concept of individual libusb + * sessions allows for your program to use two libraries (or dynamically + * load two modules) which both independently use libusb. This will prevent + * interference between the individual libusb users - for example + * libusb_set_debug() will not affect the other user of the library, and + * libusb_exit() will not destroy resources that the other user is still + * using. + * + * Sessions are created by libusb_init() and destroyed through libusb_exit(). + * If your application is guaranteed to only ever include a single libusb + * user (i.e. you), you do not have to worry about contexts: pass NULL in + * every function call where a context is required. The default context + * will be used. + * + * For more information, see \ref contexts. + */ +typedef struct libusb_context libusb_context; + +/** \ingroup dev + * Structure representing a USB device detected on the system. This is an + * opaque type for which you are only ever provided with a pointer, usually + * originating from libusb_get_device_list(). + * + * Certain operations can be performed on a device, but in order to do any + * I/O you will have to first obtain a device handle using libusb_open(). + * + * Devices are reference counted with libusb_device_ref() and + * libusb_device_unref(), and are freed when the reference count reaches 0. + * New devices presented by libusb_get_device_list() have a reference count of + * 1, and libusb_free_device_list() can optionally decrease the reference count + * on all devices in the list. libusb_open() adds another reference which is + * later destroyed by libusb_close(). + */ +typedef struct libusb_device libusb_device; + + +/** \ingroup dev + * Structure representing a handle on a USB device. This is an opaque type for + * which you are only ever provided with a pointer, usually originating from + * libusb_open(). + * + * A device handle is used to perform I/O and other operations. When finished + * with a device handle, you should call libusb_close(). + */ +typedef struct libusb_device_handle libusb_device_handle; + +/** \ingroup misc + * Error codes. Most libusb functions return 0 on success or one of these + * codes on failure. + */ +enum libusb_error { + /** Success (no error) */ + LIBUSB_SUCCESS = 0, + + /** Input/output error */ + LIBUSB_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB_ERROR_NOT_SUPPORTED = -12, + + /** Other error */ + LIBUSB_ERROR_OTHER = -99 +}; + +/** \ingroup asyncio + * Transfer status codes */ +enum libusb_transfer_status { + /** Transfer completed without error. Note that this does not indicate + * that the entire amount of requested data was transferred. */ + LIBUSB_TRANSFER_COMPLETED, + + /** Transfer failed */ + LIBUSB_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected (endpoint + * stalled). For control endpoints: control request not supported. */ + LIBUSB_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB_TRANSFER_OVERFLOW +}; + +/** \ingroup asyncio + * libusb_transfer.flags values */ +enum libusb_transfer_flags { + /** Report short frames as errors */ + LIBUSB_TRANSFER_SHORT_NOT_OK = 1<<0, + + /** Automatically free() transfer buffer during libusb_free_transfer() */ + LIBUSB_TRANSFER_FREE_BUFFER = 1<<1, + + /** Automatically call libusb_free_transfer() after callback returns. + * If this flag is set, it is illegal to call libusb_free_transfer() + * from your transfer callback, as this will result in a double-free + * when this flag is acted upon. */ + LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2 +}; + +/** \ingroup asyncio + * Isochronous packet descriptor. */ +struct libusb_iso_packet_descriptor { + /** Length of data to request in this packet */ + unsigned int length; + + /** Amount of data that was actually transferred */ + unsigned int actual_length; + + /** Status code for this packet */ + enum libusb_transfer_status status; +}; + +struct libusb_transfer; + +/** \ingroup asyncio + * Asynchronous transfer callback function type. When submitting asynchronous + * transfers, you pass a pointer to a callback function of this type via the + * \ref libusb_transfer::callback "callback" member of the libusb_transfer + * structure. libusb will call this function later, when the transfer has + * completed or failed. See \ref asyncio for more information. + * \param transfer The libusb_transfer struct the callback function is being + * notified about. + */ +typedef void (*libusb_transfer_cb_fn)(struct libusb_transfer *transfer); + +/** \ingroup asyncio + * The generic USB transfer structure. The user populates this structure and + * then submits it in order to request a transfer. After the transfer has + * completed, the library populates the transfer with the results and passes + * it back to the user. + */ +struct libusb_transfer { + /** Handle of the device that this transfer will be submitted to */ + libusb_device_handle *dev_handle; + + /** A bitwise OR combination of \ref libusb_transfer_flags. */ + uint8_t flags; + + /** Address of the endpoint where this transfer will be sent. */ + unsigned char endpoint; + + /** Type of the endpoint from \ref libusb_transfer_type */ + unsigned char type; + + /** Timeout for this transfer in millseconds. A value of 0 indicates no + * timeout. */ + unsigned int timeout; + + /** The status of the transfer. Read-only, and only for use within + * transfer callback function. + * + * If this is an isochronous transfer, this field may read COMPLETED even + * if there were errors in the frames. Use the + * \ref libusb_iso_packet_descriptor::status "status" field in each packet + * to determine if errors occurred. */ + enum libusb_transfer_status status; + + /** Length of the data buffer */ + int length; + + /** Actual length of data that was transferred. Read-only, and only for + * use within transfer callback function. Not valid for isochronous + * endpoint transfers. */ + int actual_length; + + /** Callback function. This will be invoked when the transfer completes, + * fails, or is cancelled. */ + libusb_transfer_cb_fn callback; + + /** User context data to pass to the callback function. */ + void *user_data; + + /** Data buffer */ + unsigned char *buffer; + + /** Number of isochronous packets. Only used for I/O with isochronous + * endpoints. */ + int num_iso_packets; + + /** Isochronous packet descriptors, for isochronous transfers only. */ + struct libusb_iso_packet_descriptor iso_packet_desc +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +int libusb_init(libusb_context **ctx); +void libusb_exit(libusb_context *ctx); +void libusb_set_debug(libusb_context *ctx, int level); + +ssize_t libusb_get_device_list(libusb_context *ctx, + libusb_device ***list); +void libusb_free_device_list(libusb_device **list, int unref_devices); +libusb_device *libusb_ref_device(libusb_device *dev); +void libusb_unref_device(libusb_device *dev); + +int libusb_get_configuration(libusb_device_handle *dev, int *config); +int libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc); +int libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config); +int libusb_get_config_descriptor(libusb_device *dev, uint8_t config_index, + struct libusb_config_descriptor **config); +int libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config); +void libusb_free_config_descriptor(struct libusb_config_descriptor *config); +uint8_t libusb_get_bus_number(libusb_device *dev); +uint8_t libusb_get_device_address(libusb_device *dev); +int libusb_get_max_packet_size(libusb_device *dev, unsigned char endpoint); + +int libusb_open(libusb_device *dev, libusb_device_handle **handle); +void libusb_close(libusb_device_handle *dev_handle); +libusb_device *libusb_get_device(libusb_device_handle *dev_handle); + +int libusb_set_configuration(libusb_device_handle *dev, int configuration); +int libusb_claim_interface(libusb_device_handle *dev, int iface); +int libusb_release_interface(libusb_device_handle *dev, int iface); + +libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx, + uint16_t vendor_id, uint16_t product_id); + +int libusb_set_interface_alt_setting(libusb_device_handle *dev, + int interface_number, int alternate_setting); +int libusb_clear_halt(libusb_device_handle *dev, unsigned char endpoint); +int libusb_reset_device(libusb_device_handle *dev); + +int libusb_kernel_driver_active(libusb_device_handle *dev, int interface); +int libusb_detach_kernel_driver(libusb_device_handle *dev, int interface); +int libusb_attach_kernel_driver(libusb_device_handle *dev, int interface); + +/* async I/O */ + +/** \ingroup asyncio + * Get the data section of a control transfer. This convenience function is here + * to remind you that the data does not start until 8 bytes into the actual + * buffer, as the setup packet comes first. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns pointer to the first byte of the data section + */ +static inline unsigned char *libusb_control_transfer_get_data( + struct libusb_transfer *transfer) +{ + return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; +} + +/** \ingroup asyncio + * Get the control setup packet of a control transfer. This convenience + * function is here to remind you that the control setup occupies the first + * 8 bytes of the transfer data buffer. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns a casted pointer to the start of the transfer data buffer + */ +static inline struct libusb_control_setup *libusb_control_transfer_get_setup( + struct libusb_transfer *transfer) +{ + return (struct libusb_control_setup *) transfer->buffer; +} + +/** \ingroup asyncio + * Helper function to populate the setup packet (first 8 bytes of the data + * buffer) for a control transfer. The wIndex, wValue and wLength values should + * be given in host-endian byte order. + * + * \param buffer buffer to output the setup packet into + * \param bmRequestType see the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field of + * \ref libusb_control_setup + * \param bRequest see the + * \ref libusb_control_setup::bRequest "bRequest" field of + * \ref libusb_control_setup + * \param wValue see the + * \ref libusb_control_setup::wValue "wValue" field of + * \ref libusb_control_setup + * \param wIndex see the + * \ref libusb_control_setup::wIndex "wIndex" field of + * \ref libusb_control_setup + * \param wLength see the + * \ref libusb_control_setup::wLength "wLength" field of + * \ref libusb_control_setup + */ +static inline void libusb_fill_control_setup(unsigned char *buffer, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint16_t wLength) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *) buffer; + setup->bmRequestType = bmRequestType; + setup->bRequest = bRequest; + setup->wValue = libusb_cpu_to_le16(wValue); + setup->wIndex = libusb_cpu_to_le16(wIndex); + setup->wLength = libusb_cpu_to_le16(wLength); +} + +struct libusb_transfer *libusb_alloc_transfer(int iso_packets); +int libusb_submit_transfer(struct libusb_transfer *transfer); +int libusb_cancel_transfer(struct libusb_transfer *transfer); +void libusb_free_transfer(struct libusb_transfer *transfer); + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a control transfer. + * + * If you pass a transfer buffer to this function, the first 8 bytes will + * be interpreted as a control setup packet, and the wLength field will be + * used to automatically populate the \ref libusb_transfer::length "length" + * field of the transfer. Therefore the recommended approach is: + * -# Allocate a suitably sized data buffer (including space for control setup) + * -# Call libusb_fill_control_setup() + * -# If this is a host-to-device transfer with a data stage, put the data + * in place after the setup packet + * -# Call this function + * -# Call libusb_submit_transfer() + * + * It is also legal to pass a NULL buffer to this function, in which case this + * function will not attempt to populate the length field. Remember that you + * must then populate the buffer and length fields later. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_control_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, + unsigned int timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *) buffer; + transfer->dev_handle = dev_handle; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + if (setup) + transfer->length = LIBUSB_CONTROL_SETUP_SIZE + + libusb_le16_to_cpu(setup->wLength); + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an interrupt transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an isochronous transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param num_iso_packets the number of isochronous packets + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, int num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->num_iso_packets = num_iso_packets; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup asyncio + * Convenience function to set the length of all packets in an isochronous + * transfer, based on the num_iso_packets field in the transfer structure. + * + * \param transfer a transfer + * \param length the length to set in each isochronous packet descriptor + * \see libusb_get_max_packet_size() + */ +static inline void libusb_set_iso_packet_lengths( + struct libusb_transfer *transfer, unsigned int length) +{ + int i; + for (i = 0; i < transfer->num_iso_packets; i++) + transfer->iso_packet_desc[i].length = length; +} + +/** \ingroup asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer. + * + * This is a thorough function which loops through all preceding packets, + * accumulating their lengths to find the position of the specified packet. + * Typically you will assign equal lengths to each packet in the transfer, + * and hence the above method is sub-optimal. You may wish to use + * libusb_get_iso_packet_buffer_simple() instead. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer_simple() + */ +static inline unsigned char *libusb_get_iso_packet_buffer( + struct libusb_transfer *transfer, unsigned int packet) +{ + int i; + size_t offset = 0; + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + for (i = 0; i < _packet; i++) + offset += transfer->iso_packet_desc[i].length; + + return transfer->buffer + offset; +} + +/** \ingroup asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer, for transfers where each + * packet is of identical size. + * + * This function relies on the assumption that every packet within the transfer + * is of identical size to the first packet. Calculating the location of + * the packet buffer is then just a simple calculation: + * buffer + (packet_size * packet) + * + * Do not use this function on transfers other than those that have identical + * packet lengths for each packet. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer() + */ +static inline unsigned char *libusb_get_iso_packet_buffer_simple( + struct libusb_transfer *transfer, unsigned int packet) +{ + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + return transfer->buffer + (transfer->iso_packet_desc[0].length * _packet); +} + +/* sync I/O */ + +int libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, + unsigned char *data, uint16_t length, unsigned int timeout); + +int libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +int libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +/** \ingroup desc + * Retrieve a descriptor from the default control pipe. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. + * + * \param dev a device handle + * \param desc_type the descriptor type, see \ref libusb_descriptor_type + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +static inline int libusb_get_descriptor(libusb_device_handle *dev, + uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) +{ + return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (desc_type << 8) | desc_index, 0, data, + length, 1000); +} + +/** \ingroup desc + * Retrieve a descriptor from a device. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. The string returned is Unicode, as + * detailed in the USB specifications. + * + * \param dev a device handle + * \param desc_index the index of the descriptor to retrieve + * \param langid the language ID for the string descriptor + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + * \see libusb_get_string_descriptor_ascii() + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev, + uint8_t desc_index, uint16_t langid, unsigned char *data, int length) +{ + return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_STRING << 8) | desc_index, + langid, data, length, 1000); +} + +int libusb_get_string_descriptor_ascii(libusb_device_handle *dev, + uint8_t index, unsigned char *data, int length); + +/* polling and timeouts */ + +int libusb_try_lock_events(libusb_context *ctx); +void libusb_lock_events(libusb_context *ctx); +void libusb_unlock_events(libusb_context *ctx); +int libusb_event_handling_ok(libusb_context *ctx); +int libusb_event_handler_active(libusb_context *ctx); +void libusb_lock_event_waiters(libusb_context *ctx); +void libusb_unlock_event_waiters(libusb_context *ctx); +int libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); + +int libusb_handle_events_timeout(libusb_context *ctx, struct timeval *tv); +int libusb_handle_events(libusb_context *ctx); +int libusb_handle_events_locked(libusb_context *ctx, struct timeval *tv); +int libusb_get_next_timeout(libusb_context *ctx, struct timeval *tv); + +/** \ingroup poll + * File descriptor for polling + */ +struct libusb_pollfd { + /** Numeric file descriptor */ + int fd; + + /** Event flags to poll for from . POLLIN indicates that you + * should monitor this file descriptor for becoming ready to read from, + * and POLLOUT indicates that you should monitor this file descriptor for + * nonblocking write readiness. */ + short events; +}; + +/** \ingroup poll + * Callback function, invoked when a new file descriptor should be added + * to the set of file descriptors monitored for events. + * \param fd the new file descriptor + * \param events events to monitor for, see \ref libusb_pollfd for a + * description + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (*libusb_pollfd_added_cb)(int fd, short events, void *user_data); + +/** \ingroup poll + * Callback function, invoked when a file descriptor should be removed from + * the set of file descriptors being monitored for events. After returning + * from this callback, do not use that file descriptor again. + * \param fd the file descriptor to stop monitoring + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (*libusb_pollfd_removed_cb)(int fd, void *user_data); + +const struct libusb_pollfd **libusb_get_pollfds(libusb_context *ctx); +void libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp b/lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp new file mode 100644 index 00000000..236a1e69 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp @@ -0,0 +1,940 @@ +#include +#include +#include +#include "usb_mixer.h" +#include "libusb.h" + +/* Note: this is all quite generic and could be simplified ALOT for a specific design */ + +// TODO we dont really need to store mixer input strings +// Currently res, max, min dont get populated + +#define XMOS_VID 0x20b1 +uint16_t XMOS_PID[] = { + 0x0002, //L1_AUDIO2 + 0x0003, //L1_AUDIO1 + 0x0004, //L2_AUDIO2 + 0x000E, //xk_216_AUDIO2 + 0x000F, //xk_216_AUDIO1 + 0x0016, //xk_316_AUDIO2 + 0x0017, //xk_316_AUDIO1 +}; + +#define USB_REQUEST_TO_DEV 0x21 /* D7 Data direction: 0 (Host to device) + * D6:5 Type: 01 (Class) + * D4:0 Receipient: 1 (Interface) */ +#define USB_REQUEST_FROM_DEV 0xa1 + +#define USB_CS_INTERFACE 0x24 +#define USB_INPUT_TERM_TYPE 0x02 +#define USB_MIXER_UNIT_TYPE 0x04 +#define USB_FEATURE_UNIT_TYPE 0x06 + +#define INPUT_TERMINAL USB_INPUT_TERM_TYPE +#define EXTENSION_UNIT 0x9 +#define CS_INTERFACE USB_CS_INTERFACE +#define FEATURE_UNIT 0x06 + +#define CS_XU_SEL 0x6 +#define MU_MIXER_CONTROL 0x1 + +// Output from PC +#define USB_STREAMING 0x01 +// Input to device +//#define MICROPHONE 0x02 + +#define ID_XU_OUT 51 +#define ID_XU_IN 52 + +#define OFFSET_BLENGTH 0 +#define OFFSET_BDESCRIPTORTYPE 1 +#define OFFSET_BDESCRIPTORSUBTYPE 2 +#define OFFSET_BUNITID 3 + +#define OFFSET_FU_BSOURCEID 4 + +#define OFFSET_XU_BNRINPINS 6 +#define OFFSET_XU_BSOURCEID 7 + +#define OFFSET_IT_WTERMINALTYPE 5 +#define OFFSET_IT_BNRCHANNELS 8 +#define OFFSET_IT_ICHANNELNAMES 13 + +typedef struct +{ + double min; + double max; + double res; + double weight; +} mixer_node; + +typedef struct +{ + unsigned int id; + unsigned int num_inputs; + char input_names[USB_MIXER_INPUTS][USB_MIXER_MAX_NAME_LEN]; /* Current mixer input names - + * we dont really need to store these */ + int input_connections[USB_MIXER_INPUTS]; + unsigned int num_outputs; + char output_names[USB_MIXER_INPUTS][USB_MIXER_MAX_NAME_LEN]; + unsigned int num_inPins; + mixer_node nodes[USB_MIXER_INPUTS * USB_MIXER_OUTPUTS]; +} usb_mixer_device; + +typedef struct { + int cur; + int default_value; + char name[USB_MIXER_MAX_NAME_LEN]; + enum usb_chan_type ctype; +}channel_map_node; + +typedef struct { + int numInputs; + int numOutputs; + channel_map_node map[USB_MAX_CHANNEL_MAP_SIZE]; +}channel_mapp; + +typedef struct +{ + unsigned int id; + unsigned int numInputs; + char inputStrings[USB_MIXER_INPUTS*4][USB_MIXER_MAX_NAME_LEN]; /* Complete list of all possible inputs */ + unsigned int numOutputs; + unsigned int state[USB_MIXER_INPUTS]; +} t_usb_mixSel; + +typedef struct { + unsigned int device_open; + unsigned int num_usb_mixers; + usb_mixer_device usb_mixer[USB_MIXERS]; + t_usb_mixSel usb_mixSel[USB_MIXERS]; + + channel_mapp audChannelMap; + channel_mapp usbChannelMap; + + +} usb_mixer_handle; + +static usb_mixer_handle *usb_mixers = NULL; + +static libusb_device_handle *devh = NULL; + +bool is_supported_device(uint16_t pid) +{ + for(uint16_t id : XMOS_PID) + { + if (pid == id) return true; + } + fprintf(stderr, "ERROR :: Device not supported\n"); + return false; +} + +/* Issue a generic control/class GET request to a specific unit in the Audio Interface */ +int usb_audio_class_get(unsigned char bRequest, unsigned char cs, unsigned char cn, unsigned short unitID, unsigned short wLength, unsigned char *data) +{ + return libusb_control_transfer(devh, + USB_REQUEST_FROM_DEV, + bRequest, + (cs<<8) | cn, /* wValue */ + (unitID & 0xff) << 8 | 0x0, + data, + wLength, + 0); +} + +/* Issue a generic control/class SET request to a specific unit in the Audio Interface */ +int usb_audio_class_set(unsigned char bRequest, unsigned char cs, unsigned char cn, unsigned short unitID, unsigned short wLength, unsigned char *data) +{ + return libusb_control_transfer(devh, + USB_REQUEST_TO_DEV, + bRequest, + (cs<<8) | cn, /* wValue */ + (unitID & 0xff) << 8 | 0x0, + data, + wLength, + 0); +} + +/* Note, this never get cached in an object since it can change on the device side */ +int usb_mixer_mem_get(unsigned int mixer, unsigned offset, unsigned char *data) +{ + return libusb_control_transfer(devh, + USB_REQUEST_FROM_DEV, /* nRequest */ + MEM, /* bRequest */ + offset, /* wValue */ + (usb_mixers->usb_mixer[mixer].id & 0xff) << 8 | 0x0, /* wIndex */ + data, 64, 0); +} + +static const unsigned char *find_input_term_unit_by_id(const unsigned char *data, int length, int id) +{ + const unsigned char *interface_data = data; + while (length) + { + const unsigned char *interface_len = interface_data; + int sub_type = *(interface_len + 2); + if (sub_type == USB_INPUT_TERM_TYPE) + { + int unit_id = *(interface_len + 3); + if (unit_id == id) + { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static const unsigned char *find_connected_feature_unit_by_id(const unsigned char *data, int length, int id) { + const unsigned char *interface_data = data; + while (length) + { + const unsigned char *interface_len = interface_data; + int sub_type = *(interface_len + 2); + if (sub_type == USB_FEATURE_UNIT_TYPE) { + //int unit_id = *(interface_len + 3); + int src_unit_id = *(interface_len + 4); + if (src_unit_id == id) { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static const unsigned char *findUnit(const unsigned char *descs, int length, int id) +{ + const unsigned char *interface_data = descs; + while (length) + { + const unsigned char *interface_len = interface_data; + int bDescriptorType = *(interface_len + 1); + if (bDescriptorType == CS_INTERFACE) + { + int unit_id = *(interface_len + 3); + if (unit_id == id) + { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static int get_num_mixer_units(const unsigned char *data, int length) { + const unsigned char *interface_data = data; + int interface_len = length; + int num_mixer_units_found = 0; + + while (interface_len) { + const unsigned char *interfaces = interface_data; + int interface_type = *(interfaces + 1); + int unit_type = *(interfaces + 2); + if (interface_type == USB_CS_INTERFACE && unit_type == USB_MIXER_UNIT_TYPE) { + num_mixer_units_found++; + } + interface_data+=*interfaces; + interface_len -= *interfaces; + } + + return num_mixer_units_found; +} + +static double dev_get_mixer_value(unsigned int mixer, unsigned int nodeId) +{ + // MU_MIXER_CONTROL 0x01 + short data; + usb_audio_class_get(CUR, 0x01<<8, nodeId, usb_mixers->usb_mixer[mixer].id, 2,(unsigned char *) &data); + return ((double) data / 256); +} + +/* Populates min, max, res */ +static unsigned short dev_get_mixer_range(unsigned int mixer, unsigned int channel, + double *min, double *max, double *res) +{ + short data[64]; + + short min2, max2, res2; + + usb_audio_class_get(RANGE, MU_MIXER_CONTROL, channel, usb_mixers->usb_mixer[mixer].id, 8, (unsigned char *) data); + + min2 = data[1]; + max2 = data[2]; + res2 = data[3]; + //printf("%f, %f, %f\n", (double)min2/256, (double)max2/256, (double) res2/256); + *min = (double)min2/256; + *max = (double)max2/256; + *res = (double)res2/256; + + return 0; +} + +int dev_get_channel_map(int channel, int unitId) +{ + short data; + usb_audio_class_get(CUR, 0, channel, unitId, 2,(unsigned char *) &data); + return data; +} + +static int dev_set_channel_map(int channel, int val, int unitId) +{ + short value = val; + usb_audio_class_set(CUR, 0, channel, unitId, 1, (unsigned char *)&value); + return 0; +} + +static int mixer_update_all_nodes(unsigned int mixer_index) +{ + int i = 0; + int j = 0; + double min, max, res; + + for (i = 0; i < usb_mixers->usb_mixer[mixer_index].num_inputs; i++) + { + for (j = 0; j < usb_mixers->usb_mixer[mixer_index].num_outputs; j++) + { + dev_get_mixer_range(mixer_index, (i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j, &min, &max, &res); + + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].min = min; + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].max = max; + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].res = res; + //printf("%f, %f, %f\n", min, max, res); + + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].weight = + dev_get_mixer_value(mixer_index, (i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j); + } + } + return 0; +} + +/* Start at unit %id, find it in descs, keep recursively parsing up path(s) until get to Input Term and add strings */ +int addStrings(const unsigned char *data, int length, int mixer_index, int id, int chanCount) +{ + const unsigned char *currentUnitPtr = NULL; + + /* Find this unit in the descs */ + currentUnitPtr = findUnit(data, length, id); + + if(currentUnitPtr != NULL) + { + /* Check if unit is a Input term */ + if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == INPUT_TERMINAL) + { + + /* Get channel names */ +#ifdef DEBUG + printf("Input terminal found on path (ID: %d): %d channels, total: %d\n",*(currentUnitPtr+OFFSET_BUNITID), + *(currentUnitPtr+OFFSET_IT_BNRCHANNELS), chanCount); +#endif + + int iChannelNames = *(currentUnitPtr+OFFSET_IT_ICHANNELNAMES); + int wTerminalType = *(currentUnitPtr+OFFSET_IT_WTERMINALTYPE); + +#ifdef DEBUG + printf("iChannelNames: %d wTerminalType: %d\n", iChannelNames, wTerminalType); + + printf("Channels found:\n"); + +#endif + for(int i = 0; i < *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); i++) + { + unsigned char mixer_input_name[USB_MIXER_MAX_NAME_LEN]; + memset(mixer_input_name, 0 ,USB_MIXER_MAX_NAME_LEN); + if (wTerminalType == 1) + { + strcpy(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], "DAW - "); + + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_OUT; + + usb_mixers->audChannelMap.numOutputs = usb_mixers->audChannelMap.numOutputs +1; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_OUT; + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_OUT; + + } + else + { + strcpy(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], "AUD - "); + + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_IN; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_IN; + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_IN; + + + usb_mixers->usbChannelMap.numOutputs = usb_mixers->usbChannelMap.numOutputs +1; + } + /* Get relevant string descriptor */ + libusb_get_string_descriptor_ascii(devh, iChannelNames+i, mixer_input_name, + USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount])); + + strcat(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], (char *)mixer_input_name); + + /* Add to channel mappers also */ + //strcat(usb_mixers->channel_maps[usb_mixers->num_channel_maps].name, (char *)mixer_input_name); + strcat(usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs ].name, (char *)mixer_input_name); + strcat(usb_mixers->usbChannelMap.map[usb_mixers->audChannelMap.numInputs].name, (char *)mixer_input_name); + + usb_mixers->audChannelMap.numInputs = usb_mixers->audChannelMap.numInputs +1; + usb_mixers->usbChannelMap.numInputs = usb_mixers->usbChannelMap.numInputs +1; + + //usb_mixers->num_channel_maps = usb_mixers->num_channel_maps+1; + chanCount++; + } + +#ifdef DEBUG + int meh = chanCount - *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); + for(int i = 0; i < *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); i++) + { + printf("%d: %s\n", i,usb_mixers->usb_mixSel[mixer_index].inputStrings[meh+i]); + } + + printf("\n\n"); +#endif + } + else + { + /* Unit not a input terminal, keep going... */ + if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == FEATURE_UNIT) + { + chanCount = addStrings(data, length, mixer_index, *(currentUnitPtr+OFFSET_FU_BSOURCEID), chanCount); + } + else if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == EXTENSION_UNIT) + { + /* Multiple inputs for Extention units */ + for (int i = 0; i < *(currentUnitPtr+OFFSET_XU_BNRINPINS); i++) + { + chanCount = addStrings(data, length, mixer_index, *(currentUnitPtr+OFFSET_XU_BSOURCEID+i), chanCount); + } + } + else + { + fprintf(stderr,"ERROR: Currently don't support this unit: %d\n", + *(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE)); + exit(1); + } + } + } + else + { + fprintf(stderr,"ERROR: Couldn't find unit %d in descs\n", id ); + exit(1); + } + + return chanCount; +} + +/* Returns the source of an mix sel output */ +static unsigned char get_mixsel_value(unsigned int mixer, unsigned int channel) +{ + unsigned char data[64]; + usb_audio_class_get(CUR, CS_XU_SEL, channel, usb_mixers->usb_mixSel[mixer].id, 1, (unsigned char *)data); + return data[0]; +} + +static int get_mixer_info(const unsigned char *data, int length, unsigned int mixer_index, libusb_config_descriptor *config_desc) +{ + const unsigned char *interface_data = data; + int interface_len = length; + int num_mixer_units_found = 0; + //const unsigned char *current_input_term_unit_ptr = NULL; + //int current_input_term_unit_index = 0; + //const unsigned char *current_feature_unit_ptr = NULL; + int devChanInputCount = 0; + + while (interface_len) + { + const unsigned char *interfaces = interface_data; + int interface_type = *(interfaces + 1); /* bDescriptorType */ + int unit_type = *(interfaces + 2); /* bDescriptorSubType */ + + /* Look for a mixer unit */ + if (interface_type == USB_CS_INTERFACE && unit_type == USB_MIXER_UNIT_TYPE) + { + int unit_id = *(interfaces + 3); /* bUnitId */ + int bNrInPins = *(interfaces + 4); + int num_in = *(interfaces + 4); /* bNrInPins - NOTE This is pins NOT channels!! */ + /* Total number of inputs is the sum of the channel counts in the input + * clusters. We need to inspect the sources to gain channel counts */ + int chansIn = 0; +#ifdef DEBUG + printf("Found Mixer Unit %d with %d inputs\n", unit_id, bNrInPins); + printf("Inspecting mixer inputs... \n\n"); +#endif + /* For each input pin need to find out inspect its output cluster */ + for (int i = 1; i <= bNrInPins; i++) + { + int sourceId = *(interfaces+4+i); +#ifdef DEBUG + printf("baSourceID(%d): %d\n", i, sourceId); +#endif + + /* Find the unit in the config desc */ + int descsLength = length; + const unsigned char *descsData = data; + int found = 0; + int bDescriptorSubType; + int bDescriptorType; + int bUnitId; + + while(descsLength) + { + int currentLength = *descsData; + bDescriptorSubType = *(descsData + 2); + bDescriptorType = *(descsData + 1); + + if(bDescriptorType == USB_CS_INTERFACE) + { + if(bDescriptorSubType == USB_FEATURE_UNIT_TYPE || + bDescriptorSubType == USB_INPUT_TERM_TYPE || + bDescriptorSubType == EXTENSION_UNIT ) + { + bUnitId = *(descsData+3); + if(bUnitId == sourceId) + { + found = 1; + break; + } + } + } + + descsData+=currentLength; + descsLength -= currentLength; + } + + if(found) + { + int bNrChannelsOffset = 0; + int bNrChannels; + + /* We found the unit in the descs. Now inspect channel cluster */ +#ifdef DEBUG + printf("Found unit %d, type %d\n", bUnitId, bDescriptorSubType); +#endif + /* We are looking for bNrChannels in the descs, this is in a different location in desc depending + * on unit type */ + switch(bDescriptorSubType) + { + case USB_INPUT_TERM_TYPE: + bNrChannelsOffset = 8; + bNrChannels = *(descsData+bNrChannelsOffset); + break; + case EXTENSION_UNIT: + bNrChannelsOffset = 7 + *(descsData+6); + bNrChannels = *(descsData+bNrChannelsOffset); + + break; + default: + printf("ERR\n"); + exit(1); + break; + } +#ifdef DEBUG + printf("Output chan count: %d\n\n", bNrChannels); +#endif + chansIn += bNrChannels; + + } + else + { + fprintf(stderr,"ERROR: Mixer input connected to something we dont understand...\n"); + exit(1); + } + } + + /* get number of output channels straight from mixer unit descriptor: bNrChannels */ + int num_out = *(interfaces + 5 + num_in); +#ifdef DEBUG + printf("Mixer Unit parse complete: bUnitId: %d, Total Input Chans: %d, Output Chans: %d\n\n", unit_id, chansIn, num_out); +#endif + usb_mixers->usb_mixer[mixer_index].id = unit_id; + usb_mixers->usb_mixer[mixer_index].num_inputs = chansIn; + usb_mixers->usb_mixer[mixer_index].num_inPins = bNrInPins; + usb_mixers->usb_mixer[mixer_index].num_outputs = num_out; + + /* Go through all input pins */ + const unsigned char *in_unit_start_ptr = interfaces + 5; + // const unsigned char *currentUnitPtr = NULL; + // int current_input_term_unit_id = 0; + + /* We expect this to be a single input from an XU, but we'll keep it slightly generic here */ + for (int num = 0; num < usb_mixers->usb_mixer[mixer_index].num_inPins; num++) + { + /* Save source ID */ + usb_mixers->usb_mixer[mixer_index].input_connections[num] = *(in_unit_start_ptr+num); +#ifdef DEBUG + printf("Inspecting for Input Terms from mixer unit input pin %d (id: %d)\n", + num,usb_mixers->usb_mixer[mixer_index].input_connections[num]); +#endif + + devChanInputCount = addStrings(data, length, mixer_index, + usb_mixers->usb_mixer[mixer_index].input_connections[num], devChanInputCount); + + } + + /* The the first input pin at the mix select for the moment. + * probably should be checking if its an XU here */ + usb_mixers->usb_mixSel[mixer_index].id = usb_mixers->usb_mixer[mixer_index].input_connections[0]; + usb_mixers->usb_mixSel[mixer_index].numInputs = devChanInputCount; + usb_mixers->usb_mixSel[mixer_index].numOutputs = chansIn; + + /* Set up mixer output names */ + unsigned char mixer_output_name[USB_MIXER_MAX_NAME_LEN]; + unsigned int iChannelNames = *(interfaces + 10 + bNrInPins); + + for (int i = 0; i < usb_mixers->usb_mixer[mixer_index].num_outputs; i++) + { + /* Get relevant string descriptor */ + strcpy(usb_mixers->usb_mixer[mixer_index].output_names[i], "MIX - "); + libusb_get_string_descriptor_ascii(devh, iChannelNames+i, mixer_output_name, + USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[i])); + strcat(usb_mixers->usb_mixer[mixer_index].output_names[i], (char *)mixer_output_name); + } + } + + interface_data+=*interfaces; + interface_len -= *interfaces; + } + + return num_mixer_units_found; +} + +static int find_xmos_device(unsigned int id) +{ + libusb_device *dev; + libusb_device **devs; + int i = 0; + int found = 0; + + libusb_get_device_list(NULL, &devs); + + while ((dev = devs[i++]) != NULL) + { + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(dev, &desc); + // printf("VID = 0x%x, PID = 0x%x\n", desc.idVendor, desc.idProduct); + if (desc.idVendor == XMOS_VID && is_supported_device(desc.idProduct)) + { + if (found == id) + { + if (libusb_open(dev, &devh) < 0) + { + return -1; + } + else + { + libusb_config_descriptor *config_desc = NULL; + libusb_get_active_config_descriptor(dev, &config_desc); + if (config_desc != NULL) + { + //unsigned int num_mixers_found = 0; + + usb_mixers->device_open = 1; + usb_mixers->num_usb_mixers = 0; + + for (int j = 0; j < config_desc->bNumInterfaces; j++) + { + const libusb_interface_descriptor *inter_desc = + ((libusb_interface *)&config_desc->interface[j])->altsetting; + + usb_mixers->num_usb_mixers += get_num_mixer_units(inter_desc->extra, inter_desc->extra_length); + } + + for (int j = 0; j < config_desc->bNumInterfaces; j++) + { + const libusb_interface_descriptor *inter_desc = + ((libusb_interface *)&config_desc->interface[j])->altsetting; + get_mixer_info(inter_desc->extra, inter_desc->extra_length, j, config_desc); + } + } + } + break; + } + found++; + } + } + + libusb_free_device_list(devs, 1); + + /* Init channel maps from device */ + for(int i = 0; i < usb_mixers->audChannelMap.numOutputs; i++) + { + usb_mixers->audChannelMap.map[i].cur = dev_get_channel_map(i, ID_XU_OUT); + + } + + for(int i = 0; i < usb_mixers->usbChannelMap.numOutputs; i++) + { + usb_mixers->usbChannelMap.map[i].cur = dev_get_channel_map(i, ID_XU_IN); + //printf("%d\n", usb_mixers->usbChannelMap.map[i].cur); + } + + /* Now add the mix outputs */ + for(int i = 0; i < usb_mixers->num_usb_mixers; i++) + { + for(int j = 0; j < usb_mixers->usb_mixer[i].num_outputs;j++) + { + //strcpy(usb_mixers->channel_maps[usb_mixers->num_channel_maps].name, usb_mixers->usb_mixer[i].output_names[j]); + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + //usb_mixers->num_channel_maps = usb_mixers->num_channel_maps+1; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + strcpy(usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].name, usb_mixers->usb_mixer[i].output_names[j]); + usb_mixers->audChannelMap.numInputs = usb_mixers->audChannelMap.numInputs +1; + + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + strcpy(usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].name, usb_mixers->usb_mixer[i].output_names[j]); + usb_mixers->usbChannelMap.numInputs = usb_mixers->usbChannelMap.numInputs +1; + } + } + + if(devh) + { + /* Populate mixer input strings */ + for(int i = 0; i < usb_mixers->num_usb_mixers; i++) + { + mixer_update_all_nodes(i); + + /* Get current each mixer input and populate channel number state and strings from device */ + for (int j = 0; j < usb_mixers->usb_mixSel[i].numOutputs; j++) + { + /* Get value from device */ + int inputChan = get_mixsel_value(i, j); + + usb_mixers->usb_mixSel[i].state[j] = inputChan; + + /* Look up channel string */ + strcpy(usb_mixers->usb_mixer[i].input_names[j], usb_mixers->usb_mixSel[i].inputStrings[inputChan]); + } + } + } + + return devh ? 0 : -1; +} + +// End of libusb interface functions + +int usb_mixer_connect() +{ + int result = 0; + + // Allocate internal storage + usb_mixers = (usb_mixer_handle *)malloc(sizeof(usb_mixer_handle)); + memset(usb_mixers, 0, sizeof(usb_mixer_handle)); + + result = libusb_init(NULL); + if (result < 0) + { + // printf("libusb_init failure\n"); + return USB_MIXER_FAILURE; + } + + result = find_xmos_device(0); + if (result < 0) + { + // printf("find_xmos_device failure\n"); + return USB_MIXER_FAILURE; + } + + return USB_MIXER_SUCCESS; +} + +int usb_mixer_disconnect() { + libusb_close(devh); + + libusb_exit(NULL); + + free(usb_mixers); + + return USB_MIXER_SUCCESS; +} + +/* Getter for num_usb_mixers */ +int usb_mixer_get_num_mixers() +{ + return usb_mixers->num_usb_mixers; +} + +int usb_mixer_get_num_outputs(unsigned int mixer) +{ + return usb_mixers->usb_mixer[mixer].num_outputs; +} + +int usb_mixer_get_num_inputs(unsigned int mixer) +{ + return usb_mixers->usb_mixer[mixer].num_inputs; +} + +int usb_mixer_get_layout(unsigned int mixer, unsigned int *inputs, unsigned int *outputs) { + *inputs = usb_mixers->usb_mixer[mixer].num_inputs; + *outputs = usb_mixers->usb_mixer[mixer].num_outputs; + return 0; +} + +/* MixSel getters and setters */ +char *usb_mixsel_get_input_string(unsigned int mixer, unsigned int input) +{ + return usb_mixers->usb_mixSel[mixer].inputStrings[input]; +} + +int usb_mixsel_get_input_count(unsigned int mixer) +{ + return usb_mixers->usb_mixSel[mixer].numInputs; +} + +int usb_mixsel_get_output_count(unsigned int mixer) +{ + return usb_mixers->usb_mixSel[mixer].numOutputs; +} + +char *usb_mixer_get_input_name(unsigned int mixer, unsigned int input) { + return usb_mixers->usb_mixer[mixer].input_names[input]; +} + +char *usb_mixer_get_output_name(unsigned int mixer, unsigned int output) { + return usb_mixers->usb_mixer[mixer].output_names[output]; +} + +unsigned char usb_mixsel_get_state(unsigned int mixer, unsigned int channel) +{ + return usb_mixers->usb_mixSel[mixer].state[channel]; +} + +void usb_mixsel_set_state(unsigned int mixer, unsigned int dst, unsigned int src) +{ + // write to device + usb_audio_class_set(CUR, CS_XU_SEL, dst, usb_mixers->usb_mixSel[mixer].id, 1, (unsigned char *)&src); + + // update object state + usb_mixers->usb_mixSel[mixer].state[dst] = src; + + // update local object strings + // TODO we don't really need to store strings since we can look them up...*/ + strcpy(usb_mixers->usb_mixer[mixer].input_names[dst], usb_mixers->usb_mixSel[mixer].inputStrings[src]); +} + +double usb_mixer_get_value(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].weight; +} + +double usb_mixer_get_res(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].res; +} + +double usb_mixer_get_min(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].min; +} + +double usb_mixer_get_max(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].max; +} + +int usb_mixer_set_value(unsigned int mixer, unsigned int nodeId, double val) +{ + /* check if update required */ + if(usb_mixers->usb_mixer[mixer].nodes[nodeId].weight != val) + { + /* update local object */ + usb_mixers->usb_mixer[mixer].nodes[nodeId].weight= val; + + /* write to device */ + short value = (short) (val * 256); + + usb_audio_class_set(CUR, 1, 1<<8 | nodeId & 0xff, usb_mixers->usb_mixer[mixer].id, 2, (unsigned char *)&value); + + } + return 0; +} + +int usb_mixer_get_range(unsigned int mixer, unsigned int mixer_unit, int *min, int *max, int *res) +{ + // range 0x02 + return 0; +} + +int usb_get_usb_channel_map(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].cur; +} + +int usb_get_aud_channel_map(int channel) +{ + return usb_mixers->audChannelMap.map[channel].cur; +} + +int usb_set_aud_channel_map(int channel, int val) +{ + /* Check if update required */ + if(usb_mixers->audChannelMap.map[channel].cur != val) + { + /* Update local object */ + usb_mixers->audChannelMap.map[channel].cur = val; + + /* Write setting to dev */ + dev_set_channel_map(channel, val, ID_XU_OUT); + } + return 0; +} + +int usb_set_usb_channel_map(int channel, int val) +{ + /* Check if update required */ + if(usb_mixers->usbChannelMap.map[channel].cur != val) + { + /* Update local object */ + usb_mixers->usbChannelMap.map[channel].cur = val; + + /* Write setting to dev */ + dev_set_channel_map(channel, val, ID_XU_IN); + } + return 0; +} + +enum usb_chan_type usb_get_aud_channel_map_type(int channel) +{ + return usb_mixers->audChannelMap.map[channel].ctype; +} + +enum usb_chan_type usb_get_usb_channel_map_type(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].ctype; +} + +char *usb_get_aud_channel_map_name(int channel) +{ + return usb_mixers->audChannelMap.map[channel].name; +} + +char *usb_get_usb_channel_map_name(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].name; +} + +int usb_get_aud_channel_map_num_outputs() +{ + return usb_mixers->audChannelMap.numOutputs; +} +int usb_get_usb_channel_map_num_outputs() +{ + return usb_mixers->usbChannelMap.numOutputs; +} + +int usb_get_aud_channel_map_num_inputs() +{ + return usb_mixers->audChannelMap.numInputs; +} +int usb_get_usb_channel_map_num_inputs() +{ + return usb_mixers->usbChannelMap.numInputs; +} + diff --git a/lib_xua/src/host_usb_mixer_control/README b/lib_xua/src/host_usb_mixer_control/README new file mode 100644 index 00000000..f8417c75 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/README @@ -0,0 +1,103 @@ +The XMOS USB Audio L2 Reference Design contains an 18x8 mixer unit +(note that at sample rates above 96Khz only the first two outputs are +enabled). + +This unit takes input takes 18 inputs: USB OUT channels 1..10 and +DEVICE IN channels 1..6,9..10 and produces 8 outputs: Mixer Output +1..8 + +Before the mixer there is an unit that allows the selection of the 18 mixer inputs +from all the possible device inputs (DAW and physical audio). This is +an extention unit with id 50 in the descriptors + +After the mixer unit there is are channel map units for each output terminal: +Each of these outputs can select a source from one of 28 channels sources: USB OUT +channels 1..10, DEVICE IN channels 1..10 and Mixer Output 1..8 + +The channel map units are extention unit with init ids 51 and 52. This unit +lets you implement arbitrary routings including loopbacks. + +The mixer is control on MAC OS X via the command line utility +xmos_mixer. Running this application requires having the +libusb-1.0.0.dylib in the dynamic library load path. Sourcing the +setup.sh script will do this. Source code for the application is +provided as a guide on how to communicate with the device. + +Here are the commands for the mixer application (note that the usb +audio reference design has only one unit so the mixer_id argument +should alway be 0): + + --display-info + +Show information about the device. + + --display-mixer-nodes mixer_id + +Display all the weights of all the mixer nodes (and their id) of a particular mixer. + + --display-min mixer_id + +Display the minimum allowable weights of a particular mixer. + + --display-max mixer_id + +Display the maximum allowable weights of a particular mixer. + + --display-res mixer_id + +Display the resolution of a particular mixer. + + --set-value mixer_id mixer_unit value + +Set the weight value in the mixer. The second argument should +correspond to the values shown by the --display-unit command. Values +can range from -127db to +128db with the special value -inf for mute. + + --get-value mixer_id mixer_unit + +Get the weight value in the mixer. The second argument should +correspond to the values shown by the --display-unit command. Values +can range from -127db to +128db with the special value -inf for mute. + + --set-mixer-souces mixer_id, dst_channel_id, src_channel_id + +Allows the selection of the mixer inputs. Sets mixer input (dst) to src + + --display-current-mixer-sources mixer_id + +Displays the current inputs to a particular mixer + + --display-available-mixer-sources mixer_id + +Displays all the input channels available that can be fed into the inputs of a particular mixer + + --set-aud-channel-map dst src + +Sets a channel map value for the device audio output + + --display-aud-channel-map + +Show audio output channel map i.e. for each audio output of the device what the source is. + + --display-aud-channel-map-sources + +Show the available audio output channel map sources. + + --set-daw-channel-map dst src + +Sets a channel map value for the DAW output to the host + + --display-daw-channel-map + +Show audio output channel map i.e. for each DAW output to host, what the source is. + + --display-daw-channel-map-sources + +Show the DAW output channel map sources. + + + + + + + diff --git a/lib_xua/src/host_usb_mixer_control/VERSION b/lib_xua/src/host_usb_mixer_control/VERSION new file mode 100644 index 00000000..c37b1fb1 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/VERSION @@ -0,0 +1 @@ +VERSION := 1v0 diff --git a/lib_xua/src/host_usb_mixer_control/Win32/global.h b/lib_xua/src/host_usb_mixer_control/Win32/global.h new file mode 100644 index 00000000..a2ec2a2d --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/Win32/global.h @@ -0,0 +1,61 @@ +/** + * Module: host_usb_mixer_control + * Version: 1v0 + * Build: d94b0511afe40ece896637f88c6379f9b6f9f603 + * File: global.h + * + * The copyrights, all other intellectual and industrial + * property rights are retained by XMOS and/or its licensors. + * Terms and conditions covering the use of this code can + * be found in the Xmos End User License Agreement. + * + * Copyright XMOS Ltd 2010 + * + * In the case where this code is a modification of existing code + * under a separate license, the separate license terms are shown + * below. The modifications to the code are still covered by the + * copyright notice above. + * + **/ +/************************************************************************ + * + * Module: global.h + * Description: + * APP global includes, constants, declarations, etc. + * + * Author(s): + * Udo Eberhardt + * + * Companies: + * Thesycon GmbH, Germany http://www.thesycon.de + * + ************************************************************************/ + +#ifndef __global_h__ +#define __global_h__ + +// define the Windows versions supported by the application +#define _WIN32_WINNT 0x0500 //Windows 2000 or later +//#define _WIN32_WINNT 0x0501 //Windows XP or later +//#define _WIN32_WINNT 0x0600 //Windows Vista or later + +// exclude rarely-used stuff from Windows headers +#define WIN32_LEAN_AND_MEAN + +#include + +#include +#include + + +// version defs +//#include "version.h" + + +// TUSBAUDIO driver API +#include "tusbaudioapi.h" + + +#endif // __global_h__ + +/*************************** EOF **************************************/ diff --git a/lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp b/lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp new file mode 100644 index 00000000..51a41ce1 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp @@ -0,0 +1,925 @@ +#include +#include +#include +#include "usb_mixer.h" +#include "global.h" + + +//########## Thesycon .dll ########## +// libwn.h pulls in windows.h +#include "libwn.h" +// TUSBAUDIO driver API +#include "tusbaudioapi.h" +#include "TUsbAudioApiDll.h" + +//driver interface +TUsbAudioApiDll gDrvApi; + +//################################### + +/* Note: this is all quite generic and could be simplified ALOT for a specific design */ + +// TODO we dont really need to store mixer input strings +// Currently res, max, min dont get populated + +#define XMOS_VID 0x20b1 +#define XMOS_L1_AUDIO2_PID 0x0002 +#define XMOS_L1_AUDIO1_PID 0x0003 +#define XMOS_L2_AUDIO2_PID 0x0004 + +#define USB_REQUEST_TO_DEV 0x21 /* D7 Data direction: 0 (Host to device) + * D6:5 Type: 01 (Class) + * D4:0 Receipient: 1 (Interface) */ +#define USB_REQUEST_FROM_DEV 0xa1 + +#define USB_CS_INTERFACE 0x24 +#define USB_INPUT_TERM_TYPE 0x02 +#define USB_MIXER_UNIT_TYPE 0x04 +#define USB_FEATURE_UNIT_TYPE 0x06 + +#define INPUT_TERMINAL USB_INPUT_TERM_TYPE +#define EXTENSION_UNIT 0x9 +#define CS_INTERFACE USB_CS_INTERFACE +#define FEATURE_UNIT 0x06 + +#define CS_XU_SEL 0x6 +#define MU_MIXER_CONTROL 0x1 + +// Output from PC +#define USB_STREAMING 0x01 +// Input to device +//#define MICROPHONE 0x02 + +#define ID_XU_OUT 51 +#define ID_XU_IN 52 + +#define OFFSET_BLENGTH 0 +#define OFFSET_BDESCRIPTORTYPE 1 +#define OFFSET_BDESCRIPTORSUBTYPE 2 +#define OFFSET_BUNITID 3 + +#define OFFSET_FU_BSOURCEID 4 + +#define OFFSET_XU_BNRINPINS 6 +#define OFFSET_XU_BSOURCEID 7 + +#define OFFSET_IT_WTERMINALTYPE 5 +#define OFFSET_IT_BNRCHANNELS 8 +#define OFFSET_IT_ICHANNELNAMES 13 + +typedef struct +{ + double min; + double max; + double res; + double weight; +} mixer_node; + +typedef struct +{ + unsigned int id; + unsigned int num_inputs; + char input_names[USB_MIXER_INPUTS][USB_MIXER_MAX_NAME_LEN]; /* Current mixer input names- + * we dont really need to store these */ + int input_connections[USB_MIXER_INPUTS]; + unsigned int num_outputs; + char output_names[USB_MIXER_INPUTS][USB_MIXER_MAX_NAME_LEN]; + unsigned int num_inPins; + mixer_node nodes[USB_MIXER_INPUTS * USB_MIXER_OUTPUTS]; +} usb_mixer_device; + +typedef struct { + int cur; + int default_value; + char name[USB_MIXER_MAX_NAME_LEN]; + enum usb_chan_type ctype; +} channel_map_node; + +typedef struct { + int numInputs; + int numOutputs; + channel_map_node map[USB_MAX_CHANNEL_MAP_SIZE]; +} channel_mapp; + +typedef struct +{ + unsigned int id; + unsigned int numInputs; + char inputStrings[USB_MIXER_INPUTS*4][USB_MIXER_MAX_NAME_LEN]; /* Complete list of all possible inputs */ + unsigned int numOutputs; + unsigned int state[USB_MIXER_INPUTS]; +} t_usb_mixSel; + +typedef struct { + unsigned int device_open; + unsigned int num_usb_mixers; + usb_mixer_device usb_mixer[USB_MIXERS]; + t_usb_mixSel usb_mixSel[USB_MIXERS]; + + channel_mapp audChannelMap; + channel_mapp usbChannelMap; + + +} usb_mixer_handle; + +static usb_mixer_handle *usb_mixers = NULL; + +static TUsbAudioHandle h; + +/* Issue a generic control/class GET request to a specific unit in the Audio Interface */ +int usb_audio_class_get(unsigned char bRequest, unsigned char cs, unsigned char cn, unsigned short unitID, unsigned short wLength, unsigned char *data) +{ + return TUSBAUDIO_AudioControlRequestGet(h, + unitID, + bRequest, + cs, + cn, + data, + wLength, + NULL, + 1000); +} + +/* Issue a generic control/class SET request to a specific unit in the Audio Interface */ +int usb_audio_class_set(unsigned char bRequest, unsigned char cs, unsigned char cn, unsigned short unitID, unsigned short wLength, unsigned char *data) +{ + return TUSBAUDIO_AudioControlRequestSet(h, + unitID, + bRequest, + cs, + cn, + data, + wLength, + NULL, + 1000); +} + +/* Note, this never get cached in an object since it can change on the device side */ +int usb_mixer_mem_get(unsigned int mixer, unsigned offset, unsigned char *data) +{ + return TUSBAUDIO_AudioControlRequestGet(h, + usb_mixers->usb_mixer[mixer].id, + MEM, + 0, // cs + offset, //was cn + &data, + 64, + NULL, + 1000); +} + +static const unsigned char *find_input_term_unit_by_id(const unsigned char *data, int length, int id) +{ + const unsigned char *interface_data = data; + while (length) + { + const unsigned char *interface_len = interface_data; + int sub_type = *(interface_len + 2); + if (sub_type == USB_INPUT_TERM_TYPE) + { + int unit_id = *(interface_len + 3); + if (unit_id == id) + { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static const unsigned char *find_connected_feature_unit_by_id(const unsigned char *data, int length, int id) { + const unsigned char *interface_data = data; + while (length) + { + const unsigned char *interface_len = interface_data; + int sub_type = *(interface_len + 2); + if (sub_type == USB_FEATURE_UNIT_TYPE) { + //int unit_id = *(interface_len + 3); + int src_unit_id = *(interface_len + 4); + if (src_unit_id == id) { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static const unsigned char *findUnit(const unsigned char *descs, int length, int id) +{ + const unsigned char *interface_data = descs; + while (length) + { + const unsigned char *interface_len = interface_data; + int bDescriptorType = *(interface_len + 1); + if (bDescriptorType == CS_INTERFACE) + { + int unit_id = *(interface_len + 3); + if (unit_id == id) + { + return interface_len; + } + } + interface_data+=*interface_len; + length -= *interface_len; + } + return NULL; +} + +static int get_num_mixer_units(const unsigned char *data, int length) { + const unsigned char *interface_data = data; + int interface_len = length; + int num_mixer_units_found = 0; + + while (interface_len) { + const unsigned char *interfaces = interface_data; + int interface_type = *(interfaces + 1); + int unit_type = *(interfaces + 2); + if (interface_type == USB_CS_INTERFACE && unit_type == USB_MIXER_UNIT_TYPE) { + num_mixer_units_found++; + } + interface_data+=*interfaces; + interface_len -= *interfaces; + } + + return num_mixer_units_found; +} + +static double dev_get_mixer_value(unsigned int mixer, unsigned int nodeId) +{ + // MU_MIXER_CONTROL 0x01 + short data; + usb_audio_class_get(CUR, 0x01<<8, nodeId, usb_mixers->usb_mixer[mixer].id, 2,(unsigned char *) &data); + return ((double) data / 256); +} + +/* Populates min, max, res */ +static unsigned short dev_get_mixer_range(unsigned int mixer, unsigned int channel, + double *min, double *max, double *res) +{ + short data[64]; + + short min2, max2, res2; + + usb_audio_class_get(RANGE, MU_MIXER_CONTROL, channel, usb_mixers->usb_mixer[mixer].id, 8, (unsigned char *) data); + + min2 = data[1]; + max2 = data[2]; + res2 = data[3]; + //printf("%f, %f, %f\n", (double)min2/256, (double)max2/256, (double) res2/256); + *min = (double)min2/256; + *max = (double)max2/256; + *res = (double)res2/256; + + return 0; +} + +int dev_get_channel_map(int channel, int unitId) +{ + short data; + usb_audio_class_get(CUR, 0, channel, unitId, 2,(unsigned char *) &data); + return data; +} + +static int dev_set_channel_map(int channel, int val, int unitId) +{ + short value = val; + usb_audio_class_set(CUR, 0, channel, unitId, 1, (unsigned char *)&value); + return 0; +} + +static int mixer_update_all_nodes(unsigned int mixer_index) +{ + int i = 0; + int j = 0; + double min, max, res; + + for (i = 0; i < usb_mixers->usb_mixer[mixer_index].num_inputs; i++) + { + for (j = 0; j < usb_mixers->usb_mixer[mixer_index].num_outputs; j++) + { + dev_get_mixer_range(mixer_index, (i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j, &min, &max, &res); + + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].min = min; + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].max = max; + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].res = res; + //printf("%f, %f, %f\n", min, max, res); + + usb_mixers->usb_mixer[mixer_index].nodes[(i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j].weight = + dev_get_mixer_value(mixer_index, (i*usb_mixers->usb_mixer[mixer_index].num_outputs)+j); + } + } + return 0; +} + +/* Start at unit %id, find it in descs, keep recursively parsing up path(s) until get to Input Term and add strings */ +int addStrings(const unsigned char *data, int length, int mixer_index, int id, int chanCount) +{ + const unsigned char *currentUnitPtr = NULL; + + /* Find this unit in the descs */ + currentUnitPtr = findUnit(data, length, id); + TUsbAudioStatus st; + + if(currentUnitPtr != NULL) + { + /* Check if unit is a Input term */ + if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == INPUT_TERMINAL) + { + + /* Get channel names */ +#ifdef DEBUG + printf("Input terminal found on path (ID: %d): %d channels, total: %d\n",*(currentUnitPtr+OFFSET_BUNITID), + *(currentUnitPtr+OFFSET_IT_BNRCHANNELS), chanCount); +#endif + + int iChannelNames = *(currentUnitPtr+OFFSET_IT_ICHANNELNAMES); + int wTerminalType = *(currentUnitPtr+OFFSET_IT_WTERMINALTYPE); + +#ifdef DEBUG + printf("iChannelNames: %d wTerminalType: %d\n", iChannelNames, wTerminalType); + + printf("Channels found:\n"); + +#endif + for(int i = 0; i < *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); i++) + { + WCHAR mixer_input_name[USB_MIXER_MAX_NAME_LEN]; + char mixer_input_name_copy[USB_MIXER_MAX_NAME_LEN]; + if (wTerminalType == 1) + { + strcpy(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], "DAW - "); + + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_OUT; + + usb_mixers->audChannelMap.numOutputs = usb_mixers->audChannelMap.numOutputs +1; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_OUT; + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_OUT; + + } + else + { + strcpy(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], "AUD - "); + + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_IN; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_IN; + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_IN; + + + usb_mixers->usbChannelMap.numOutputs = usb_mixers->usbChannelMap.numOutputs +1; + } + /* Get relevant string descriptor */ + //libusb_get_string_descriptor_ascii(devh, iChannelNames+i, mixer_input_name, USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount])); + st = TUSBAUDIO_GetUsbStringDescriptorString(h, iChannelNames+i, 0, mixer_input_name, USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount])); + if ( TSTATUS_SUCCESS != st ) { + return USB_MIXER_FAILURE; + } + wcstombs(mixer_input_name_copy, mixer_input_name, USB_MIXER_MAX_NAME_LEN); + + strcat(usb_mixers->usb_mixSel[mixer_index].inputStrings[chanCount], (char *)mixer_input_name_copy); + + /* Add to channel mappers also */ + //strcat(usb_mixers->channel_maps[usb_mixers->num_channel_maps].name, (char *)mixer_input_name_copy); + strcat(usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs ].name, (char *)mixer_input_name_copy); + strcat(usb_mixers->usbChannelMap.map[usb_mixers->audChannelMap.numInputs].name, (char *)mixer_input_name_copy); + + usb_mixers->audChannelMap.numInputs = usb_mixers->audChannelMap.numInputs +1; + usb_mixers->usbChannelMap.numInputs = usb_mixers->usbChannelMap.numInputs +1; + + //usb_mixers->num_channel_maps = usb_mixers->num_channel_maps+1; + chanCount++; + } + +#ifdef DEBUG + int meh = chanCount - *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); + for(int i = 0; i < *(currentUnitPtr+OFFSET_IT_BNRCHANNELS); i++) + { + printf("%d: %s\n", i,usb_mixers->usb_mixSel[mixer_index].inputStrings[meh+i]); + } + + printf("\n\n"); +#endif + } + else + { + /* Unit not a input terminal, keep going... */ + if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == FEATURE_UNIT) + { + chanCount = addStrings(data, length, mixer_index, *(currentUnitPtr+OFFSET_FU_BSOURCEID), chanCount); + } + else if(*(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE) == EXTENSION_UNIT) + { + /* Multiple inputs for Extention units */ + for (int i = 0; i < *(currentUnitPtr+OFFSET_XU_BNRINPINS); i++) + { + chanCount = addStrings(data, length, mixer_index, *(currentUnitPtr+OFFSET_XU_BSOURCEID+i), chanCount); + } + } + else + { + fprintf(stderr,"ERROR: Currently don't support this unit: %d\n", + *(currentUnitPtr+OFFSET_BDESCRIPTORSUBTYPE)); + exit(1); + } + } + } + else + { + fprintf(stderr,"ERROR: Couldn't find unit %d in descs\n", id ); + exit(1); + } + + return chanCount; +} + +/* Returns the source of an mix sel output */ +static unsigned char get_mixsel_value(unsigned int mixer, unsigned int channel) +{ + unsigned char data[64]; + usb_audio_class_get(CUR, CS_XU_SEL, channel, usb_mixers->usb_mixSel[mixer].id, 1, (unsigned char *)data); + return data[0]; +} + +static int get_mixer_info(const unsigned char *data, int length, unsigned int mixer_index) +{ + const unsigned char *interface_data = data; + int interface_len = length; + int num_mixer_units_found = 0; + //const unsigned char *current_input_term_unit_ptr = NULL; + //int current_input_term_unit_index = 0; + //const unsigned char *current_feature_unit_ptr = NULL; + int devChanInputCount = 0; + + while (interface_len) + { + const unsigned char *interfaces = interface_data; + int interface_type = *(interfaces + 1); /* bDescriptorType */ + int unit_type = *(interfaces + 2); /* bDescriptorSubType */ + + /* Look for a mixer unit */ + if (interface_type == USB_CS_INTERFACE && unit_type == USB_MIXER_UNIT_TYPE) + { + int unit_id = *(interfaces + 3); /* bUnitId */ + int bNrInPins = *(interfaces + 4); + int num_in = *(interfaces + 4); /* bNrInPins - NOTE This is pins NOT channels!! */ + /* Total number of inputs is the sum of the channel counts in the input + * clusters. We need to inspect the sources to gain channel counts */ + int chansIn = 0; +#ifdef DEBUG + printf("Found Mixer Unit %d with %d inputs\n", unit_id, bNrInPins); + printf("Inspecting mixer inputs... \n\n"); +#endif + /* For each input pin need to find out inspect its output cluster */ + for (int i = 1; i <= bNrInPins; i++) + { + int sourceId = *(interfaces+4+i); +#ifdef DEBUG + printf("baSourceID(%d): %d\n", i, sourceId); +#endif + + /* Find the unit in the config desc */ + int descsLength = length; + const unsigned char *descsData = data; + int found = 0; + int bDescriptorSubType; + int bDescriptorType; + int bUnitId; + + while(descsLength) + { + int currentLength = *descsData; + bDescriptorSubType = *(descsData + 2); + bDescriptorType = *(descsData + 1); + + if(bDescriptorType == USB_CS_INTERFACE) + { + if(bDescriptorSubType == USB_FEATURE_UNIT_TYPE || + bDescriptorSubType == USB_INPUT_TERM_TYPE || + bDescriptorSubType == EXTENSION_UNIT ) + { + bUnitId = *(descsData+3); + if(bUnitId == sourceId) + { + found = 1; + break; + } + } + } + + descsData+=currentLength; + descsLength -= currentLength; + } + + if(found) + { + int bNrChannelsOffset = 0; + int bNrChannels; + + /* We found the unit in the descs. Now inspect channel cluster */ +#ifdef DEBUG + printf("Found unit %d, type %d\n", bUnitId, bDescriptorSubType); +#endif + /* We are looking for bNrChannels in the descs, this is in a different location in desc depending + * on unit type */ + switch(bDescriptorSubType) + { + case USB_INPUT_TERM_TYPE: + bNrChannelsOffset = 8; + bNrChannels = *(descsData+bNrChannelsOffset); + break; + case EXTENSION_UNIT: + bNrChannelsOffset = 7 + *(descsData+6); + bNrChannels = *(descsData+bNrChannelsOffset); + + break; + default: + printf("ERR\n"); + exit(1); + break; + } +#ifdef DEBUG + printf("Output chan count: %d\n\n", bNrChannels); +#endif + chansIn += bNrChannels; + + } + else + { + fprintf(stderr,"ERROR: Mixer input connected to something we dont understand...\n"); + exit(1); + } + } + + /* get number of output channels straight from mixer unit descriptor: bNrChannels */ + int num_out = *(interfaces + 5 + num_in); +#ifdef DEBUG + printf("Mixer Unit parse complete: bUnitId: %d, Total Input Chans: %d, Output Chans: %d\n\n", unit_id, chansIn, num_out); +#endif + usb_mixers->usb_mixer[mixer_index].id = unit_id; + usb_mixers->usb_mixer[mixer_index].num_inputs = chansIn; + usb_mixers->usb_mixer[mixer_index].num_inPins = bNrInPins; + usb_mixers->usb_mixer[mixer_index].num_outputs = num_out; + + /* Go through all input pins */ + const unsigned char *in_unit_start_ptr = interfaces + 5; + // const unsigned char *currentUnitPtr = NULL; + // int current_input_term_unit_id = 0; + + /* We expect this to be a single input from an XU, but we'll keep it slightly generic here */ + for (int num = 0; num < usb_mixers->usb_mixer[mixer_index].num_inPins; num++) + { + /* Save source ID */ + usb_mixers->usb_mixer[mixer_index].input_connections[num] = *(in_unit_start_ptr+num); +#ifdef DEBUG + printf("Inspecting for Input Terms from mixer unit input pin %d (id: %d)\n", + num,usb_mixers->usb_mixer[mixer_index].input_connections[num]); +#endif + + devChanInputCount = addStrings(data, length, mixer_index, + usb_mixers->usb_mixer[mixer_index].input_connections[num], devChanInputCount); + + } + + /* The the first input pin at the mix select for the moment. + * probably should be checking if its an XU here */ + usb_mixers->usb_mixSel[mixer_index].id = usb_mixers->usb_mixer[mixer_index].input_connections[0]; + usb_mixers->usb_mixSel[mixer_index].numInputs = devChanInputCount; + usb_mixers->usb_mixSel[mixer_index].numOutputs = chansIn; + + /* Set up mixer output names */ + WCHAR mixer_output_name[USB_MIXER_MAX_NAME_LEN]; + char mixer_output_name_copy[USB_MIXER_MAX_NAME_LEN]; + unsigned int iChannelNames = *(interfaces + 10 + bNrInPins); + + for (int i = 0; i < usb_mixers->usb_mixer[mixer_index].num_outputs; i++) + { + /* Get relevant string descriptor */ + strcpy(usb_mixers->usb_mixer[mixer_index].output_names[i], "MIX - "); + // libusb_get_string_descriptor_ascii(devh, iChannelNames+i, mixer_output_name, USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[i])); + TUsbAudioStatus st = TUSBAUDIO_GetUsbStringDescriptorString(h, iChannelNames+i, 0, mixer_output_name, USB_MIXER_MAX_NAME_LEN - strlen(usb_mixers->usb_mixSel[mixer_index].inputStrings[i])); + if ( TSTATUS_SUCCESS != st ) { + return USB_MIXER_FAILURE; + } + wcstombs(mixer_output_name_copy, mixer_output_name, USB_MIXER_MAX_NAME_LEN); + // strcat(usb_mixers->usb_mixer[mixer_index].output_names[i], (char *)mixer_output_name); + strcat(usb_mixers->usb_mixer[mixer_index].output_names[i], mixer_output_name_copy); + } + } + + interface_data+=*interfaces; + interface_len -= *interfaces; + } + + return num_mixer_units_found; +} + +static int find_xmos_device(unsigned int id) { + //libusb_device *dev; + //libusb_device **devs; + int i = 0; + int found = 0; + TUsbAudioStatus st; + + unsigned int devcnt = TUSBAUDIO_GetDeviceCount(); + if ( 0 == devcnt ) { + return USB_MIXER_FAILURE; + } + + st = TUSBAUDIO_OpenDeviceByIndex(0,&h); + if ( TSTATUS_SUCCESS != st ) { + h = 0; + // skip + } else { + unsigned int numBytes = 0; + unsigned char descBuffer[64*1024]; + + st = TUSBAUDIO_GetUsbConfigDescriptor(h, descBuffer, 64*1024, &numBytes); + if ( TSTATUS_SUCCESS != st ) { + return USB_MIXER_FAILURE; + } + + usb_mixers->num_usb_mixers = get_num_mixer_units(descBuffer, numBytes); + + get_mixer_info(descBuffer, numBytes, 0); + + /* Init channel maps from device */ + for(int i = 0; i < usb_mixers->audChannelMap.numOutputs; i++) + { + usb_mixers->audChannelMap.map[i].cur = dev_get_channel_map(i, ID_XU_OUT); + + } + + for(int i = 0; i < usb_mixers->usbChannelMap.numOutputs; i++) + { + usb_mixers->usbChannelMap.map[i].cur = dev_get_channel_map(i, ID_XU_IN); + //printf("%d\n", usb_mixers->usbChannelMap.map[i].cur); + } + + /* Now add the mix outputs */ + for(int i = 0; i < usb_mixers->num_usb_mixers; i++) + { + for(int j = 0; j < usb_mixers->usb_mixer[i].num_outputs;j++) + { + //strcpy(usb_mixers->channel_maps[usb_mixers->num_channel_maps].name, usb_mixers->usb_mixer[i].output_names[j]); + //usb_mixers->channel_maps[usb_mixers->num_channel_maps].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + //usb_mixers->num_channel_maps = usb_mixers->num_channel_maps+1; + + usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + strcpy(usb_mixers->audChannelMap.map[usb_mixers->audChannelMap.numInputs].name, usb_mixers->usb_mixer[i].output_names[j]); + usb_mixers->audChannelMap.numInputs = usb_mixers->audChannelMap.numInputs +1; + + usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].ctype = (enum usb_chan_type) USB_CHAN_MIXER; + strcpy(usb_mixers->usbChannelMap.map[usb_mixers->usbChannelMap.numInputs].name, usb_mixers->usb_mixer[i].output_names[j]); + usb_mixers->usbChannelMap.numInputs = usb_mixers->usbChannelMap.numInputs +1; + } + } + + if(h) + { + /* Populate mixer input strings */ + for(int i = 0; i < usb_mixers->num_usb_mixers; i++) + { + mixer_update_all_nodes(i); + + /* Get current each mixer input and populate channel number state and strings from device */ + for (int j = 0; j < usb_mixers->usb_mixSel[i].numOutputs; j++) + { + /* Get value from device */ + int inputChan = get_mixsel_value(i, j); + + usb_mixers->usb_mixSel[i].state[j] = inputChan; + + /* Look up channel string */ + strcpy(usb_mixers->usb_mixer[i].input_names[j], usb_mixers->usb_mixSel[i].inputStrings[inputChan]); + } + } + } + } + + return h ? 0 : -1; +} + +// End of libusb interface functions + +int usb_mixer_connect() { + int result = 0; + + gDrvApi.LoadByGUID("{E5A2658B-817D-4A02-A1DE-B628A93DDF5D}", false); + + + // Allocate internal storage + usb_mixers = (usb_mixer_handle *)malloc(sizeof(usb_mixer_handle)); + memset(usb_mixers, 0, sizeof(usb_mixer_handle)); + + // enumerate devices + TUsbAudioStatus st = TUSBAUDIO_EnumerateDevices(); + if ( TSTATUS_SUCCESS != st ) { + return USB_MIXER_FAILURE; + } + + result = find_xmos_device(0); + if (result < 0) { + return USB_MIXER_FAILURE; + } + + return USB_MIXER_SUCCESS; +} + +int usb_mixer_disconnect() { + if ( 0 != h ) { + TUSBAUDIO_CloseDevice(h); + } + + free(usb_mixers); + + return USB_MIXER_SUCCESS; +} + +/* Getter for num_usb_mixers */ +int usb_mixer_get_num_mixers() +{ + return usb_mixers->num_usb_mixers; +} + +int usb_mixer_get_num_outputs(unsigned int mixer) +{ + return usb_mixers->usb_mixer[mixer].num_outputs; +} + +int usb_mixer_get_num_inputs(unsigned int mixer) +{ + return usb_mixers->usb_mixer[mixer].num_inputs; +} + +int usb_mixer_get_layout(unsigned int mixer, unsigned int *inputs, unsigned int *outputs) { + *inputs = usb_mixers->usb_mixer[mixer].num_inputs; + *outputs = usb_mixers->usb_mixer[mixer].num_outputs; + return 0; +} + +/* MixSel getters and setters */ +char *usb_mixsel_get_input_string(unsigned int mixer, unsigned int input) +{ + return usb_mixers->usb_mixSel[mixer].inputStrings[input]; +} + +int usb_mixsel_get_input_count(unsigned int mixer) +{ + return usb_mixers->usb_mixSel[mixer].numInputs; +} + +int usb_mixsel_get_output_count(unsigned int mixer) +{ + return usb_mixers->usb_mixSel[mixer].numOutputs; +} + +char *usb_mixer_get_input_name(unsigned int mixer, unsigned int input) { + return usb_mixers->usb_mixer[mixer].input_names[input]; +} + +char *usb_mixer_get_output_name(unsigned int mixer, unsigned int output) { + return usb_mixers->usb_mixer[mixer].output_names[output]; +} + +unsigned char usb_mixsel_get_state(unsigned int mixer, unsigned int channel) +{ + return usb_mixers->usb_mixSel[mixer].state[channel]; +} + +void usb_mixsel_set_state(unsigned int mixer, unsigned int dst, unsigned int src) +{ + // write to device + usb_audio_class_set(CUR, CS_XU_SEL, dst, usb_mixers->usb_mixSel[mixer].id, 1, (unsigned char *)&src); + + // update object state + usb_mixers->usb_mixSel[mixer].state[dst] = src; + + // update local object strings + // TODO we don't really need to store strings since we can look them up...*/ + strcpy(usb_mixers->usb_mixer[mixer].input_names[dst], usb_mixers->usb_mixSel[mixer].inputStrings[src]); +} + +double usb_mixer_get_value(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].weight; +} + +double usb_mixer_get_res(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].res; +} + +double usb_mixer_get_min(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].min; +} + +double usb_mixer_get_max(unsigned int mixer, unsigned int nodeId) +{ + return (double)usb_mixers->usb_mixer[mixer].nodes[nodeId].max; +} + +int usb_mixer_set_value(unsigned int mixer, unsigned int nodeId, double val) +{ + /* check if update required */ + if(usb_mixers->usb_mixer[mixer].nodes[nodeId].weight != val) + { + /* update local object */ + usb_mixers->usb_mixer[mixer].nodes[nodeId].weight= val; + + /* write to device */ + short value = (short) (val * 256); + + usb_audio_class_set(CUR, 1, 1<<8 | nodeId & 0xff, usb_mixers->usb_mixer[mixer].id, 2, (unsigned char *)&value); + + } + return 0; +} + +int usb_mixer_get_range(unsigned int mixer, unsigned int mixer_unit, int *min, int *max, int *res) +{ + // range 0x02 + return 0; +} + +int usb_get_usb_channel_map(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].cur; +} + +int usb_get_aud_channel_map(int channel) +{ + return usb_mixers->audChannelMap.map[channel].cur; +} + +int usb_set_aud_channel_map(int channel, int val) +{ + /* Check if update required */ + if(usb_mixers->audChannelMap.map[channel].cur != val) + { + /* Update local object */ + usb_mixers->audChannelMap.map[channel].cur = val; + + /* Write setting to dev */ + dev_set_channel_map(channel, val, ID_XU_OUT); + } + return 0; +} + +int usb_set_usb_channel_map(int channel, int val) +{ + /* Check if update required */ + if(usb_mixers->usbChannelMap.map[channel].cur != val) + { + /* Update local object */ + usb_mixers->usbChannelMap.map[channel].cur = val; + + /* Write setting to dev */ + dev_set_channel_map(channel, val, ID_XU_IN); + } + return 0; +} + +enum usb_chan_type usb_get_aud_channel_map_type(int channel) +{ + return usb_mixers->audChannelMap.map[channel].ctype; +} + +enum usb_chan_type usb_get_usb_channel_map_type(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].ctype; +} + +char *usb_get_aud_channel_map_name(int channel) +{ + return usb_mixers->audChannelMap.map[channel].name; +} + +char *usb_get_usb_channel_map_name(int channel) +{ + return usb_mixers->usbChannelMap.map[channel].name; +} + +int usb_get_aud_channel_map_num_outputs() +{ + return usb_mixers->audChannelMap.numOutputs; +} +int usb_get_usb_channel_map_num_outputs() +{ + return usb_mixers->usbChannelMap.numOutputs; +} + +int usb_get_aud_channel_map_num_inputs() +{ + return usb_mixers->audChannelMap.numInputs; +} +int usb_get_usb_channel_map_num_inputs() +{ + return usb_mixers->usbChannelMap.numInputs; +} + diff --git a/lib_xua/src/host_usb_mixer_control/mixer_app.cpp b/lib_xua/src/host_usb_mixer_control/mixer_app.cpp new file mode 100644 index 00000000..31de96b8 --- /dev/null +++ b/lib_xua/src/host_usb_mixer_control/mixer_app.cpp @@ -0,0 +1,719 @@ +#include +#include +#include +#include "usb_mixer.h" + +#define MIXER_UNIT_DISPLAY_VALUE 2 +#define MIXER_UNIT_DISPLAY_MIN 3 +#define MIXER_UNIT_DISPLAY_MAX 4 +#define MIXER_UNIT_DISPLAY_RES 5 + +// TODO +// res, min, max + +int mixer_init(void) +{ + /* Open the connection to the USB mixer */ + if (usb_mixer_connect() == USB_MIXER_FAILURE) + { + return USB_MIXER_FAILURE; + } + + + return USB_MIXER_SUCCESS; +} + +int mixer_deinit(void) { + // Close the connection to the USB mixer + if (usb_mixer_disconnect() == USB_MIXER_FAILURE) { + return USB_MIXER_FAILURE; + } + + return USB_MIXER_SUCCESS; +} + +int mixer_display(unsigned int mixer_index, unsigned int type) { + int i = 0; + int j = 0; + + int num_inputs = usb_mixer_get_num_inputs(mixer_index); + int num_outputs = usb_mixer_get_num_outputs(mixer_index); + + + printf("\n"); + switch (type) { + case MIXER_UNIT_DISPLAY_VALUE: + //mixer_update_all_values(mixer_index); + printf(" Mixer Values (%d)\n", mixer_index); + printf(" ----------------\n\n"); + break; + case MIXER_UNIT_DISPLAY_MIN: + printf(" Mixer Ranges Min (%d)\n", mixer_index); + printf(" --------------------\n\n"); + break; + case MIXER_UNIT_DISPLAY_MAX: + printf(" Mixer Ranges Max (%d)\n", mixer_index); + printf(" --------------------\n\n"); + break; + case MIXER_UNIT_DISPLAY_RES: + printf(" Mixer Ranges Res (%d)\n", mixer_index); + printf(" --------------------\n\n"); + break; + default: + return USB_MIXER_FAILURE; + break; + } + + printf(" \t\t\t"); + printf("Mixer Outputs\n"); + printf("\t\t "); + for (i = 0; i < num_outputs; i++) { + printf(" %d", i+1); + } + printf("\n"); + for (i = 0; i < num_inputs; i++) { + printf(" %-20s", usb_mixer_get_input_name(mixer_index,i)); + for (j = 0; j < num_outputs; j++) { + switch (type) { + case MIXER_UNIT_DISPLAY_VALUE: + { + double mixNodeVal = usb_mixer_get_value(mixer_index, (i*num_outputs)+j); + int nodeid = (i*num_outputs)+j; + + if (mixNodeVal <= -127.996)// todo shoud be < min + { + printf("\t%3d:[ %s ]", nodeid,"-inf"); + } + else + { + printf("\t%3d:[%08.03f]", nodeid, mixNodeVal); + } + } + break; + case MIXER_UNIT_DISPLAY_MIN: + { + int nodeid = (i*num_outputs)+j; + printf("\t%3d:[%08.03f]", nodeid, usb_mixer_get_min(mixer_index, (i*num_outputs)+j)) ; + } + break; + case MIXER_UNIT_DISPLAY_MAX: + { + int nodeid = (i*num_outputs)+j; + printf("\t%3d:[%08.03f]", nodeid, usb_mixer_get_max(mixer_index, (i*num_outputs)+j)) ; + } + break; + case MIXER_UNIT_DISPLAY_RES: + { + int nodeid = (i*num_outputs)+j; + printf("\t%3d:[%08.03f]", nodeid, usb_mixer_get_res(mixer_index, (i*num_outputs)+j)) ; + } + break; + default: + return USB_MIXER_FAILURE; + break; + } + } + printf("\n"); + } + printf("\n"); + + return USB_MIXER_SUCCESS; +} + +/* Displays basic mixer information */ +int mixer_display_info(void) +{ + unsigned int i = 0; + int num_mixers = usb_mixer_get_num_mixers(); + + printf("\n"); + printf(" Mixer Info\n"); + printf(" ----------\n\n"); + printf(" Mixers : %d\n\n", num_mixers); + + for (i = 0; i < num_mixers; i++) + { + int num_inputs = usb_mixer_get_num_inputs(i); + int num_outputs = usb_mixer_get_num_outputs(i); + + + printf(" Mixer %d\n", i); + printf(" -------\n"); + + printf(" Inputs : %d\n" + " Outputs : %d\n\n", num_inputs, num_outputs); + + printf(" Mixer Output Labels:\n"); + for(int j = 0; j < num_outputs; j++) + { + printf(" %d: %s\n", j,usb_mixer_get_output_name(i,j)); + } + + //printf("\n Selectable Inputs (%d): \n", usb_mixsel_get_input_count(i)); + //for(int j = 0; j < usb_mixsel_get_input_count(i); j++) + //{ + // printf(" %d: %s\n", j, usb_mixsel_get_input_string(i,j)); + //} + } + + printf("\n"); + + return USB_MIXER_SUCCESS; +} + +void display_available_mixer_sources(int mixIndex) +{ + printf("\n"); + printf(" Available Mixer Sources (%d)\n", mixIndex); + printf(" -------------------------\n\n"); + + for(int j = 0; j < usb_mixsel_get_input_count(mixIndex); j++) + { + printf(" %d: %s\n", j, usb_mixsel_get_input_string(mixIndex,j)); + } +} + + +/* Gets the current mixer inputs from the device an displays them */ +void display_mixer_sources(int mixerIndex) +{ + printf("\n"); + printf(" Current Mixer Sources (%d)\n", mixerIndex); + printf(" -------------------------\n\n"); + + /* Note, mixSel output cound and mixer input chan count should be the same! */ + printf(" Number of mixer sources: %d\n", usb_mixsel_get_output_count(mixerIndex)); + + /* Get the current channel number for every mixer input */ + for(int i = 0; i < usb_mixsel_get_output_count(mixerIndex); i++) + { + int inputChan = (int)usb_mixsel_get_state(mixerIndex, i); + char *str = usb_mixer_get_input_name(mixerIndex,i); + printf(" Mixer input %d: Source chan id: %d (%s)\n", i, inputChan, str); + } +} + +/* set mixer source */ +void set_mixer_source(unsigned mixerIndex, unsigned dst, unsigned src) +{ + usb_mixsel_set_state(mixerIndex, dst, src); + + /* String lookup */ + char *str = usb_mixer_get_input_name(mixerIndex, dst); + int state = usb_mixsel_get_state(mixerIndex, dst); + + printf("\n Set mixer(%d) input %d to device input %d (%s)\n", mixerIndex, dst, state, str); +} + +void display_aud_channel_map() +{ + printf("\n"); + printf(" Audio Output Channel Map\n"); + printf(" ------------------------\n\n"); + + for (int i=0;i>8), offset&0xff, unitId, data); +} + +void print_levels(const char* levelTitle, unsigned char* levels, int levelBytes) +{ + unsigned levelCount = levelBytes/2; + unsigned short* levelData = (unsigned short*) levels; + + printf("\n %s Level Data\n" + " ----------------------\n\n" + "%d bytes (%d channels) returned:\n" + , levelTitle, levelBytes, levelCount); + + for(int i = 0; i Date: Thu, 1 Dec 2022 09:37:20 +0000 Subject: [PATCH 2/6] moved mixer control to correct path --- host_usb_mixer_control/.makefile | 10 +++ host_usb_mixer_control/.project | 77 ++++++++++++++++++ .../EULA | 0 .../LICENSING | 0 .../Makefile.OSX | 0 .../Makefile.Win32 | 0 .../OSX/libusb-1.0.0.dylib | Bin .../OSX/libusb.h | 0 .../OSX/usb_mixer.cpp | 0 .../README | 0 .../VERSION | 0 .../Win32/global.h | 0 .../Win32/usb_mixer.cpp | 0 .../mixer_app.cpp | 0 .../module_description | 0 .../setup.sh | 0 .../usb_mixer.h | 0 17 files changed, 87 insertions(+) create mode 100644 host_usb_mixer_control/.makefile create mode 100644 host_usb_mixer_control/.project rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/EULA (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/LICENSING (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/Makefile.OSX (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/Makefile.Win32 (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/OSX/libusb-1.0.0.dylib (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/OSX/libusb.h (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/OSX/usb_mixer.cpp (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/README (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/VERSION (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/Win32/global.h (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/Win32/usb_mixer.cpp (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/mixer_app.cpp (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/module_description (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/setup.sh (100%) rename {lib_xua/src/host_usb_mixer_control => host_usb_mixer_control}/usb_mixer.h (100%) diff --git a/host_usb_mixer_control/.makefile b/host_usb_mixer_control/.makefile new file mode 100644 index 00000000..595ea033 --- /dev/null +++ b/host_usb_mixer_control/.makefile @@ -0,0 +1,10 @@ +all: + @echo ======================================================= + @echo Build complete [module only - cannot be run on its own] + @echo ======================================================= + +clean: + @echo ======================================================= + @echo Build clean [module only - cannot be run on its own] + @echo ======================================================= + diff --git a/host_usb_mixer_control/.project b/host_usb_mixer_control/.project new file mode 100644 index 00000000..875c0c73 --- /dev/null +++ b/host_usb_mixer_control/.project @@ -0,0 +1,77 @@ + + + host_usb_mixer_control + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + ?name? + + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.autoBuildTarget + all + + + org.eclipse.cdt.make.core.buildArguments + + + + org.eclipse.cdt.make.core.buildCommand + xmake + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.contents + org.eclipse.cdt.make.core.activeConfigSettings + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.fullBuildTarget + all + + + org.eclipse.cdt.make.core.stopOnError + true + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + true + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.core.cnature + + diff --git a/lib_xua/src/host_usb_mixer_control/EULA b/host_usb_mixer_control/EULA similarity index 100% rename from lib_xua/src/host_usb_mixer_control/EULA rename to host_usb_mixer_control/EULA diff --git a/lib_xua/src/host_usb_mixer_control/LICENSING b/host_usb_mixer_control/LICENSING similarity index 100% rename from lib_xua/src/host_usb_mixer_control/LICENSING rename to host_usb_mixer_control/LICENSING diff --git a/lib_xua/src/host_usb_mixer_control/Makefile.OSX b/host_usb_mixer_control/Makefile.OSX similarity index 100% rename from lib_xua/src/host_usb_mixer_control/Makefile.OSX rename to host_usb_mixer_control/Makefile.OSX diff --git a/lib_xua/src/host_usb_mixer_control/Makefile.Win32 b/host_usb_mixer_control/Makefile.Win32 similarity index 100% rename from lib_xua/src/host_usb_mixer_control/Makefile.Win32 rename to host_usb_mixer_control/Makefile.Win32 diff --git a/lib_xua/src/host_usb_mixer_control/OSX/libusb-1.0.0.dylib b/host_usb_mixer_control/OSX/libusb-1.0.0.dylib similarity index 100% rename from lib_xua/src/host_usb_mixer_control/OSX/libusb-1.0.0.dylib rename to host_usb_mixer_control/OSX/libusb-1.0.0.dylib diff --git a/lib_xua/src/host_usb_mixer_control/OSX/libusb.h b/host_usb_mixer_control/OSX/libusb.h similarity index 100% rename from lib_xua/src/host_usb_mixer_control/OSX/libusb.h rename to host_usb_mixer_control/OSX/libusb.h diff --git a/lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp b/host_usb_mixer_control/OSX/usb_mixer.cpp similarity index 100% rename from lib_xua/src/host_usb_mixer_control/OSX/usb_mixer.cpp rename to host_usb_mixer_control/OSX/usb_mixer.cpp diff --git a/lib_xua/src/host_usb_mixer_control/README b/host_usb_mixer_control/README similarity index 100% rename from lib_xua/src/host_usb_mixer_control/README rename to host_usb_mixer_control/README diff --git a/lib_xua/src/host_usb_mixer_control/VERSION b/host_usb_mixer_control/VERSION similarity index 100% rename from lib_xua/src/host_usb_mixer_control/VERSION rename to host_usb_mixer_control/VERSION diff --git a/lib_xua/src/host_usb_mixer_control/Win32/global.h b/host_usb_mixer_control/Win32/global.h similarity index 100% rename from lib_xua/src/host_usb_mixer_control/Win32/global.h rename to host_usb_mixer_control/Win32/global.h diff --git a/lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp b/host_usb_mixer_control/Win32/usb_mixer.cpp similarity index 100% rename from lib_xua/src/host_usb_mixer_control/Win32/usb_mixer.cpp rename to host_usb_mixer_control/Win32/usb_mixer.cpp diff --git a/lib_xua/src/host_usb_mixer_control/mixer_app.cpp b/host_usb_mixer_control/mixer_app.cpp similarity index 100% rename from lib_xua/src/host_usb_mixer_control/mixer_app.cpp rename to host_usb_mixer_control/mixer_app.cpp diff --git a/lib_xua/src/host_usb_mixer_control/module_description b/host_usb_mixer_control/module_description similarity index 100% rename from lib_xua/src/host_usb_mixer_control/module_description rename to host_usb_mixer_control/module_description diff --git a/lib_xua/src/host_usb_mixer_control/setup.sh b/host_usb_mixer_control/setup.sh similarity index 100% rename from lib_xua/src/host_usb_mixer_control/setup.sh rename to host_usb_mixer_control/setup.sh diff --git a/lib_xua/src/host_usb_mixer_control/usb_mixer.h b/host_usb_mixer_control/usb_mixer.h similarity index 100% rename from lib_xua/src/host_usb_mixer_control/usb_mixer.h rename to host_usb_mixer_control/usb_mixer.h From 9f00f9159a16a91127fbe99961f50257689bf373 Mon Sep 17 00:00:00 2001 From: Tom Williams Date: Wed, 21 Dec 2022 15:07:14 +0000 Subject: [PATCH 3/6] improvements to documentation --- host_usb_mixer_control/README | 2 +- host_usb_mixer_control/Win32/global.h | 4 +- host_usb_mixer_control/Win32/usb_mixer.cpp | 8 -- lib_xua/doc/rst/sw_mixer.rst | 127 +++++++++++++++------ 4 files changed, 93 insertions(+), 48 deletions(-) diff --git a/host_usb_mixer_control/README b/host_usb_mixer_control/README index f8417c75..6e4282cd 100644 --- a/host_usb_mixer_control/README +++ b/host_usb_mixer_control/README @@ -59,7 +59,7 @@ Get the weight value in the mixer. The second argument should correspond to the values shown by the --display-unit command. Values can range from -127db to +128db with the special value -inf for mute. - --set-mixer-souces mixer_id, dst_channel_id, src_channel_id + --set-mixer-source mixer_id, dst_channel_id, src_channel_id Allows the selection of the mixer inputs. Sets mixer input (dst) to src diff --git a/host_usb_mixer_control/Win32/global.h b/host_usb_mixer_control/Win32/global.h index a2ec2a2d..af46ab64 100644 --- a/host_usb_mixer_control/Win32/global.h +++ b/host_usb_mixer_control/Win32/global.h @@ -51,9 +51,11 @@ // version defs //#include "version.h" - +// libwn.h pulls in windows.h +#include "libwn.h" // TUSBAUDIO driver API #include "tusbaudioapi.h" +#include "TUsbAudioApiDll.h" #endif // __global_h__ diff --git a/host_usb_mixer_control/Win32/usb_mixer.cpp b/host_usb_mixer_control/Win32/usb_mixer.cpp index 51a41ce1..8284ac7c 100644 --- a/host_usb_mixer_control/Win32/usb_mixer.cpp +++ b/host_usb_mixer_control/Win32/usb_mixer.cpp @@ -4,14 +4,6 @@ #include "usb_mixer.h" #include "global.h" - -//########## Thesycon .dll ########## -// libwn.h pulls in windows.h -#include "libwn.h" -// TUSBAUDIO driver API -#include "tusbaudioapi.h" -#include "TUsbAudioApiDll.h" - //driver interface TUsbAudioApiDll gDrvApi; diff --git a/lib_xua/doc/rst/sw_mixer.rst b/lib_xua/doc/rst/sw_mixer.rst index d2f41592..b7b116d7 100755 --- a/lib_xua/doc/rst/sw_mixer.rst +++ b/lib_xua/doc/rst/sw_mixer.rst @@ -104,76 +104,127 @@ The main requirements of this control utility are to Whilst using the XMOS Host control example application, consider the example of setting the mixer to perform a loop-back from analogue inputs 1 and 2 to analogue outputs 1 and 2. +.. note:: + + The command outputs shown are examples, actual output will depend on the mixer configuration. + Firstly consider the inputs to the mixer. The following will displays which channels are mapped to which mixer inputs:: - ./xmos_mixer --display-aud-channel-map 0 + $ ./xmos_mixer --display-aud-channel-map + + Audio Output Channel Map + ------------------------ + + 0 (DEVICE OUT - Analogue 1) source is 0 (DAW OUT - Analogue 1) + 1 (DEVICE OUT - Analogue 2) source is 1 (DAW OUT - Analogue 2) + 2 (DEVICE OUT - SPDIF 1) source is 8 (DAW OUT - SPDIF 1) + 3 (DEVICE OUT - SPDIF 2) source is 9 (DAW OUT - SPDIF 1) + $ _ + +This displays which channels are mapped to which outputs. By default all +of these bypass the mixer. The following command will displays which channels could possibly be mapped to mixer inputs. Notice that analogue inputs 1 and 2 are on mixer inputs 10 and 11:: -./xmos_mixer --display-aud-channel-map-sources 0 + $ ./xmos_mixer --display-aud-channel-map-sources -Now examine the audio output mapping using the following command:: + Audio Output Channel Map Source List + ------------------------------------ - ./xmos_mixer --display-aud-channel-map 0 + 0 (DAW OUT - Analogue 1) + 1 (DAW OUT - Analogue 2) + 2 (DAW OUT - SPDIF 1) + 3 (DAW OUT - SPDIF 2) + 4 (DEVICE IN - Analogue 1) + 5 (DEVICE IN - Analogue 2) + 6 (MIX - Mix 1) + 7 (MIX - Mix 2) + $ _ -This displays which channels are mapped to which outputs. By default all -of these bypass the mixer. We can also see what all the possible -mappings are with the following command:: +Using the indexes output from the previous command, we will now map the first two mixer outputs to physical outputs 1 and 2:: - ./xmos_mixer --display-aud-channel-map-sources 0 - -We will now map the first two mixer outputs to physical outputs 1 and 2:: - - ./xmos_mixer --set-aud-channel-map 0 26 - ./xmos_mixer --set-aud-channel-map 1 27 + $ ./xmos_mixer --set-aud-channel-map 0 6 + $ ./xmos_mixer --set-aud-channel-map 1 7 + $ _ You can confirm the effect of this by re-checking the map:: - ./xmos_mixer --display-aud-channel-map 0 + $ ./xmos_mixer --display-aud-channel-map + + Audio Output Channel Map + ------------------------ + + 0 (DEVICE OUT - Analogue 1) source is 0 (MIX - Mix 1) + 1 (DEVICE OUT - Analogue 2) source is 1 (MIX - Mix 2) + 2 (DEVICE OUT - SPDIF 1) source is 8 (DAW OUT - SPDIF 1) + 3 (DEVICE OUT - SPDIF 2) source is 9 (DAW OUT - SPDIF 1) + $ _ This now derives analogue outputs 1 and 2 from the mixer, rather than directly from USB. However, since the mixer is still mapped to pass the USB channels through to the outputs there will be no functional change. -The mixer nodes need to be individually set. They can be displayed +The mixer nodes need to be individually set. The nodes in mixer_id 0 can be displayed with the following command:: - ./xmos_mixer --display-mixer-nodes 0 + $ ./xmos_mixer --display-mixer-nodes 0 -To get the audio from the analogue inputs to outputs 1 and 2, nodes 80 -and 89 need to be set:: + Mixer Values (0) + ---------------- - ./xmos_mixer --set-value 0 80 0 - ./xmos_mixer --set-value 0 89 0 + Mixer outputs + 1 2 + DAW - Analogue 1 0:[0000.000] 1:[ -inf ] + DAW - Analogue 2 2:[ -inf ] 3:[0000.000] + DAW - SPDIF 1 4:[ -inf ] 5:[ -inf ] + DAW - SPDIF 1 6:[ -inf ] 7:[ -inf ] + AUD - Analogue 1 8:[ -inf ] 9:[ -inf ] + AUD - Analogue 2 10:[ -inf ] 11:[ -inf ] + $ _ + +To get the audio from the analogue inputs to outputs 1 and 2, mixer_id 0 node 8 +and node 11 need to be set to 0:: + + $ ./xmos_mixer --set-value 0 8 0 + $ ./xmos_mixer --set-value 0 11 0 + $ _ At the same time, the original mixer outputs can be muted:: - ./xmos_mixer --set-value 0 0 -inf - ./xmos_mixer --set-value 0 9 -inf + $ ./xmos_mixer --set-value 0 0 -inf + $ ./xmos_mixer --set-value 0 3 -inf + $ _ -Now audio inputs on analogue 1/2 should be heard on outputs 1/2. +Now audio inputs on analogue 1 and 2 should be heard on outputs 1 and 2 respectively. -As mentioned above, the flexibility of the mixer is such that there will be multiple ways to create -a particular mix. Another option to create the same routing would be to change the mixer sources -such that mixer 1/2 outputs come from the analogue inputs. +.. As mentioned above, the flexibility of the mixer is such that there will be multiple ways to create +.. a particular mix. Another option to create the same routing would be to change the mixer sources +.. such that mixer outputs 1 and 2 come from the analogue inputs 1 and 2. -To demonstrate this, firstly undo the changes above (or simply reset the device):: +.. To demonstrate this, firstly undo the changes above (or simply reset the device):: - ./xmos_mixer --set-value 0 80 -inf - ./xmos_mixer --set-value 0 89 -inf - ./xmos_mixer --set-value 0 0 0 - ./xmos_mixer --set-value 0 9 0 +.. $ ./xmos_mixer --set-value 0 8 -inf +.. $ ./xmos_mixer --set-value 0 11 -inf +.. $ ./xmos_mixer --set-value 0 0 0 +.. $ ./xmos_mixer --set-value 0 3 0 +.. $ _ -The mixer should now have the default values. The sources for mixer 1/2 can now be changed:: +.. The mixer should now have the default values. The sources for mixer 0 output 1 and 2 can now be changed +.. using indices from the Audio Output Channel Map Source List:: - ./xmos_mixer --set-mixer-source 0 0 10 - ./xmos_mixer --set-mixer-source 0 1 11 +.. $ ./xmos_mixer --set-mixer-source 0 0 4 -If you re-run the following command then the first column now has "AUD - Analogue 1 and 2" rather -than "DAW (Digital Audio Workstation i.e. the host) - Analogue 1 and 2" confirming the new mapping. -Again, by playing audio into analogue inputs 1/2 this can be heard looped through to analogue outputs 1/2:: +.. Set mixer(0) input 0 to device input 4 (AUD - Analogue 1) +.. $ ./xmos_mixer --set-mixer-source 0 1 5 + +.. Set mixer(0) input 1 to device input 5 (AUD - Analogue 2) +.. $ _ + +.. If you re-run the following command then the first column now has "AUD - Analogue 1 and 2" rather +.. than "DAW (Digital Audio Workstation i.e. the host) - Analogue 1 and 2" confirming the new mapping. +.. Again, by playing audio into analogue inputs 1/2 this can be heard looped through to analogue outputs 1/2:: - ./xmos_mixer --display-mixer-nodes 0 +.. $ ./xmos_mixer --display-mixer-nodes 0 From 94e58edfaf5b0cde10b3472e367d70f58c4c0724 Mon Sep 17 00:00:00 2001 From: Tom Williams Date: Wed, 21 Dec 2022 17:22:39 +0000 Subject: [PATCH 4/6] documentation clarifications and some spelling / grammer fixes --- host_usb_mixer_control/README | 8 ++--- lib_xua/doc/rst/sw_mixer.rst | 58 +++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/host_usb_mixer_control/README b/host_usb_mixer_control/README index 6e4282cd..dbdc2f3e 100644 --- a/host_usb_mixer_control/README +++ b/host_usb_mixer_control/README @@ -8,13 +8,13 @@ DEVICE IN channels 1..6,9..10 and produces 8 outputs: Mixer Output Before the mixer there is an unit that allows the selection of the 18 mixer inputs from all the possible device inputs (DAW and physical audio). This is -an extention unit with id 50 in the descriptors +an extension unit with id 50 in the descriptors After the mixer unit there is are channel map units for each output terminal: Each of these outputs can select a source from one of 28 channels sources: USB OUT channels 1..10, DEVICE IN channels 1..10 and Mixer Output 1..8 -The channel map units are extention unit with init ids 51 and 52. This unit +The channel map units are extension unit with init ids 51 and 52. This unit lets you implement arbitrary routings including loopbacks. The mixer is control on MAC OS X via the command line utility @@ -23,9 +23,9 @@ libusb-1.0.0.dylib in the dynamic library load path. Sourcing the setup.sh script will do this. Source code for the application is provided as a guide on how to communicate with the device. -Here are the commands for the mixer application (note that the usb +Here are the commands for the mixer application (note that the USB audio reference design has only one unit so the mixer_id argument -should alway be 0): +should always be 0): --display-info diff --git a/lib_xua/doc/rst/sw_mixer.rst b/lib_xua/doc/rst/sw_mixer.rst index b7b116d7..31b82740 100755 --- a/lib_xua/doc/rst/sw_mixer.rst +++ b/lib_xua/doc/rst/sw_mixer.rst @@ -102,14 +102,14 @@ The main requirements of this control utility are to functionality to their end users. Whilst using the XMOS Host control example application, consider the example of setting the -mixer to perform a loop-back from analogue inputs 1 and 2 to analogue outputs 1 and 2. +mixer to perform a loop-back from analogue inputs 1 & 2 to analogue outputs 1 & 2. .. note:: - The command outputs shown are examples, actual output will depend on the mixer configuration. + The command outputs shown are examples; the actual output will depend on the mixer configuration. -Firstly consider the inputs to the mixer. The following will displays which channels are mapped -to which mixer inputs:: +The following will show the index for each device output along with which channel is currently mapped to it. +In this example the analogue outputs 1 & 2 are 0 & 1 respectively:: $ ./xmos_mixer --display-aud-channel-map @@ -118,15 +118,28 @@ to which mixer inputs:: 0 (DEVICE OUT - Analogue 1) source is 0 (DAW OUT - Analogue 1) 1 (DEVICE OUT - Analogue 2) source is 1 (DAW OUT - Analogue 2) - 2 (DEVICE OUT - SPDIF 1) source is 8 (DAW OUT - SPDIF 1) - 3 (DEVICE OUT - SPDIF 2) source is 9 (DAW OUT - SPDIF 1) + 2 (DEVICE OUT - SPDIF 1) source is 2 (DAW OUT - SPDIF 1) + 3 (DEVICE OUT - SPDIF 2) source is 3 (DAW OUT - SPDIF 2) $ _ -This displays which channels are mapped to which outputs. By default all -of these bypass the mixer. +The DAW Output Map can be seen with:: -The following command will displays which channels could possibly be mapped to mixer inputs. Notice -that analogue inputs 1 and 2 are on mixer inputs 10 and 11:: + $ ./xmos_mixer --display-daw-channel-map + + DAW Output To Host Channel Map + ------------------------ + + 0 (DEVICE IN - Analogue 1) source is 4 (DEVICE IN - Analogue 1) + 1 (DEVICE IN - Analogue 2) source is 5 (DEVICE IN - Analogue 2) + $ _ + +.. note:: + + In both cases, by default, these bypass the mixer. + +The following command will list the channels which can be mapped to the device outputs from the +Audio Output Channel Map. Note that, in this example, analogue inputs 1 & 2 are source 4 & 5 and +Mix 1 & 2 are source 6 & 7:: $ ./xmos_mixer --display-aud-channel-map-sources @@ -143,7 +156,7 @@ that analogue inputs 1 and 2 are on mixer inputs 10 and 11:: 7 (MIX - Mix 2) $ _ -Using the indexes output from the previous command, we will now map the first two mixer outputs to physical outputs 1 and 2:: +Using the indices from the previous commands, we will now re-map the first two mixer channels (Mix 1 & Mix 2) to device outputs 1 & 2:: $ ./xmos_mixer --set-aud-channel-map 0 6 $ ./xmos_mixer --set-aud-channel-map 1 7 @@ -156,16 +169,21 @@ You can confirm the effect of this by re-checking the map:: Audio Output Channel Map ------------------------ - 0 (DEVICE OUT - Analogue 1) source is 0 (MIX - Mix 1) - 1 (DEVICE OUT - Analogue 2) source is 1 (MIX - Mix 2) - 2 (DEVICE OUT - SPDIF 1) source is 8 (DAW OUT - SPDIF 1) - 3 (DEVICE OUT - SPDIF 2) source is 9 (DAW OUT - SPDIF 1) + 0 (DEVICE OUT - Analogue 1) source is 6 (MIX - Mix 1) + 1 (DEVICE OUT - Analogue 2) source is 7 (MIX - Mix 2) + 2 (DEVICE OUT - SPDIF 1) source is 2 (DAW OUT - SPDIF 1) + 3 (DEVICE OUT - SPDIF 2) source is 3 (DAW OUT - SPDIF 2) $ _ -This now derives analogue outputs 1 and 2 from the mixer, rather than directly from USB. However, -since the mixer is still mapped to pass the USB channels through to the outputs there will be no +This now derives analogue outputs 1 & 2 from the mixer, rather than directly from USB. However, +since the mixer is mapped, by default, to just pass the USB channels through to the outputs there will be no functional change. + +.. note:: + + The USB audio reference design has only one unit so the mixer_id argument should always be 0. + The mixer nodes need to be individually set. The nodes in mixer_id 0 can be displayed with the following command:: @@ -179,13 +197,13 @@ with the following command:: DAW - Analogue 1 0:[0000.000] 1:[ -inf ] DAW - Analogue 2 2:[ -inf ] 3:[0000.000] DAW - SPDIF 1 4:[ -inf ] 5:[ -inf ] - DAW - SPDIF 1 6:[ -inf ] 7:[ -inf ] + DAW - SPDIF 2 6:[ -inf ] 7:[ -inf ] AUD - Analogue 1 8:[ -inf ] 9:[ -inf ] AUD - Analogue 2 10:[ -inf ] 11:[ -inf ] $ _ -To get the audio from the analogue inputs to outputs 1 and 2, mixer_id 0 node 8 -and node 11 need to be set to 0:: +With mixer outputs 1 & 2 mapped to device outputs analogue 1 & 2; to get the audio from the analogue inputs to device +outputs mixer_id 0 node 8 and node 11 need to be set to 0db:: $ ./xmos_mixer --set-value 0 8 0 $ ./xmos_mixer --set-value 0 11 0 From 9922190450ffb12e09a7f66d7869bbc9d30abbae Mon Sep 17 00:00:00 2001 From: Tom Williams Date: Wed, 18 Jan 2023 16:01:08 +0000 Subject: [PATCH 5/6] removed versioning and license files as they are now part of the top level. added the help option to the readme --- host_usb_mixer_control/EULA | 27 --------------------------- host_usb_mixer_control/LICENSING | 3 --- host_usb_mixer_control/README | 12 ++++++++++++ host_usb_mixer_control/VERSION | 1 - lib_xua/doc/rst/sw_mixer.rst | 3 +++ 5 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 host_usb_mixer_control/EULA delete mode 100644 host_usb_mixer_control/LICENSING delete mode 100644 host_usb_mixer_control/VERSION diff --git a/host_usb_mixer_control/EULA b/host_usb_mixer_control/EULA deleted file mode 100644 index e82f77f9..00000000 --- a/host_usb_mixer_control/EULA +++ /dev/null @@ -1,27 +0,0 @@ -Software License - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal with -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -• Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimers. - -• Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimers in the documentation -and/or other materials provided with the distribution. - -• Neither the name of XMOS, nor the names of its contributors may be used to -endorse or promote products derived from this Software without specific -prior written permission. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE -SOFTWARE. diff --git a/host_usb_mixer_control/LICENSING b/host_usb_mixer_control/LICENSING deleted file mode 100644 index 13c2f420..00000000 --- a/host_usb_mixer_control/LICENSING +++ /dev/null @@ -1,3 +0,0 @@ -All code contained in this package under XMOS copyright must be -licensing for any use from XMOS. Please contact support@xmos.com for -details of licensing. diff --git a/host_usb_mixer_control/README b/host_usb_mixer_control/README index dbdc2f3e..b194224a 100644 --- a/host_usb_mixer_control/README +++ b/host_usb_mixer_control/README @@ -27,6 +27,9 @@ Here are the commands for the mixer application (note that the USB audio reference design has only one unit so the mixer_id argument should always be 0): + --help + + --display-info Show information about the device. @@ -95,6 +98,15 @@ Show audio output channel map i.e. for each DAW output to host, what the source Show the DAW output channel map sources. + --get-mixer-levels-input + + --get-mixer-levels-output + + --vendor-audio-request-get bRequest, ControlSelector, ChannelNumber, UnitId + + --vendor-audio-request-set bRequest, ControlSelector, ChannelNumber, UnitId, Data[0], Data[1],... + + diff --git a/host_usb_mixer_control/VERSION b/host_usb_mixer_control/VERSION deleted file mode 100644 index c37b1fb1..00000000 --- a/host_usb_mixer_control/VERSION +++ /dev/null @@ -1 +0,0 @@ -VERSION := 1v0 diff --git a/lib_xua/doc/rst/sw_mixer.rst b/lib_xua/doc/rst/sw_mixer.rst index 31b82740..f14b28d6 100755 --- a/lib_xua/doc/rst/sw_mixer.rst +++ b/lib_xua/doc/rst/sw_mixer.rst @@ -87,6 +87,9 @@ intended as an example of how you might add mixer control to your own control ap intended to be exposed to end users. For details, consult the README file in the host_usb_mixer_control directory. +A list of arguments can also be seen with:: + + $ ./xmos_mixer --help The main requirements of this control utility are to From 2f31260612a3cdf4e82b04fa93f200535938ac35 Mon Sep 17 00:00:00 2001 From: Tom Williams Date: Wed, 18 Jan 2023 16:51:47 +0000 Subject: [PATCH 6/6] updated copyright headers --- host_usb_mixer_control/OSX/libusb.h | 19 ------------------- host_usb_mixer_control/OSX/usb_mixer.cpp | 2 ++ host_usb_mixer_control/Win32/global.h | 22 +++------------------- host_usb_mixer_control/Win32/usb_mixer.cpp | 2 ++ host_usb_mixer_control/mixer_app.cpp | 2 ++ host_usb_mixer_control/usb_mixer.h | 22 +++------------------- 6 files changed, 12 insertions(+), 57 deletions(-) diff --git a/host_usb_mixer_control/OSX/libusb.h b/host_usb_mixer_control/OSX/libusb.h index e5487bc7..11263803 100644 --- a/host_usb_mixer_control/OSX/libusb.h +++ b/host_usb_mixer_control/OSX/libusb.h @@ -1,22 +1,3 @@ -/** - * Module: host_usb_mixer_control - * Version: 1v0 - * Build: d94b0511afe40ece896637f88c6379f9b6f9f603 - * File: libusb.h - * - * The copyrights, all other intellectual and industrial - * property rights are retained by XMOS and/or its licensors. - * Terms and conditions covering the use of this code can - * be found in the Xmos End User License Agreement. - * - * Copyright XMOS Ltd 2010 - * - * In the case where this code is a modification of existing code - * under a separate license, the separate license terms are shown - * below. The modifications to the code are still covered by the - * copyright notice above. - * - **/ /* * Public libusb header file * Copyright (C) 2007-2008 Daniel Drake diff --git a/host_usb_mixer_control/OSX/usb_mixer.cpp b/host_usb_mixer_control/OSX/usb_mixer.cpp index 236a1e69..b678a7ed 100644 --- a/host_usb_mixer_control/OSX/usb_mixer.cpp +++ b/host_usb_mixer_control/OSX/usb_mixer.cpp @@ -1,3 +1,5 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include diff --git a/host_usb_mixer_control/Win32/global.h b/host_usb_mixer_control/Win32/global.h index af46ab64..cec10f4d 100644 --- a/host_usb_mixer_control/Win32/global.h +++ b/host_usb_mixer_control/Win32/global.h @@ -1,22 +1,6 @@ -/** - * Module: host_usb_mixer_control - * Version: 1v0 - * Build: d94b0511afe40ece896637f88c6379f9b6f9f603 - * File: global.h - * - * The copyrights, all other intellectual and industrial - * property rights are retained by XMOS and/or its licensors. - * Terms and conditions covering the use of this code can - * be found in the Xmos End User License Agreement. - * - * Copyright XMOS Ltd 2010 - * - * In the case where this code is a modification of existing code - * under a separate license, the separate license terms are shown - * below. The modifications to the code are still covered by the - * copyright notice above. - * - **/ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + /************************************************************************ * * Module: global.h diff --git a/host_usb_mixer_control/Win32/usb_mixer.cpp b/host_usb_mixer_control/Win32/usb_mixer.cpp index 8284ac7c..08b0d406 100644 --- a/host_usb_mixer_control/Win32/usb_mixer.cpp +++ b/host_usb_mixer_control/Win32/usb_mixer.cpp @@ -1,3 +1,5 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include diff --git a/host_usb_mixer_control/mixer_app.cpp b/host_usb_mixer_control/mixer_app.cpp index 31de96b8..5736ae3e 100644 --- a/host_usb_mixer_control/mixer_app.cpp +++ b/host_usb_mixer_control/mixer_app.cpp @@ -1,3 +1,5 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include diff --git a/host_usb_mixer_control/usb_mixer.h b/host_usb_mixer_control/usb_mixer.h index c7da062d..3749a8a8 100644 --- a/host_usb_mixer_control/usb_mixer.h +++ b/host_usb_mixer_control/usb_mixer.h @@ -1,22 +1,6 @@ -/** - * Module: host_usb_mixer_control - * Version: 1v0 - * Build: c9de4042d132c4eddebaa352f91fb98ef883e272 - * File: usb_mixer.h - * - * The copyrights, all other intellectual and industrial - * property rights are retained by XMOS and/or its licensors. - * Terms and conditions covering the use of this code can - * be found in the Xmos End User License Agreement. - * - * Copyright XMOS Ltd 2010 - * - * In the case where this code is a modification of existing code - * under a separate license, the separate license terms are shown - * below. The modifications to the code are still covered by the - * copyright notice above. - * - **/ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + #define USB_MIXER_SUCCESS 0 #define USB_MIXER_FAILURE -1