[PYTHON] Make an audio interface controller with pyusb (1)

I'm doing music activities as a hobby [^ 1], but I was originally playing with it privately & I started making music on the Linux machine I used at work when I was a home worker, so Linux is still the main thing It is a production environment of. And although there are various hurdles to making music on Linux [^ 2], one of the major problems is that there are also ** audio interface options in the title **. No, as I will explain later, it has improved considerably recently, so it is becoming less of a problem.

To put it simply, an audio interface is a device that can record / play back with low noise by connecting various devices, and it is almost indispensable for music production, but of course the Linux market size in music production is hard. It's far from the scale that motivates wear makers to write drivers for Linux, so you can't expect that.

However, in the last few years, the number of devices that claim ** USB Class Compliant ** compatible with USB Audio Class [^ 3] has increased, and since they can use basic functions with general-purpose drivers, they can also be used on Linux as a result. It has become like. [^ 4] On the other hand, there are naturally limits to general-purpose drivers [^ 5], and you cannot use any of the unique functions of each device. I've been using Native Instrument Komplete Audio 6 so far, and it wasn't inconvenient even with a general-purpose driver, but the recently purchased Focusrite Scarlett 18i20 audio interface has many features that general-purpose drivers can't handle. Can only be controlled via the Focusrite software (Focusrite Control).

When asked "Why did you buy such a thing?" ** "Because I was planning to make a controller for Linux from the beginning" **, but it seems to be just a source of technical articles, so I will do it several times from now on. I will write the creation of an audio interface controller with pyusb separately.

Library and software to use

As mentioned above, there is no Linux version of the driver to use the original function, so here we will capture and analyze the interaction between Scarlett 18i 20 and Focusrite Control on Windows.

At first I thought that libusb alone would do the trick, but a quick glance at the source of libusb's WinUSB backend seems to be incompatible with some devices, and the Scarlett 18i 20 seems to be incompatible. That's it. (If you run the code below without using usbdk, an error will occur.) If you use the usbdk backend, you can operate with pyusb, but in some cases ** it may involve the OS and fall ** So, if you want to imitate what you are doing in the article, you are at your own risk.

Protocol analysis procedure

It's just this once I write it, but this is quite annoying. Of course, the analysis of the protocol is more or less troublesome, but for example, even if nothing is done, a large amount of data logs will be spit out as shown in the image below.

scarlet1-1.png

Anyway, communication is done at a tremendous frequency, and even if you operate something with Focusrite Control, the log will flow immediately. However, if you look at this large amount of communication, you can see that this is a good clue.

First of all, there is a part that looks like a serial number, so it is probable that Scarlett judges whether the controller software is in a consistent state by the serial number. And I am worried that the control transfer from Scarlett of 272 bytes is almost filled with 0. So, after applying the following filters, try various operations.

DataSize == 272 && HexString != /^011000000001[0-9A-Z]{4}0{264}/

As a result of trial and error, it seems that this large amount of logs is for audio data, probably for Focusrite Control's monitor display. This will be used later if you want to create a similar monitor function, but since it is not necessary recently, it will be suppressed with the following filter.

DataSize != 272 && HexString != /^2102000003001800011000000/ && DataSize != 0 && HexString != "A103000003001001" && HexString != "0100000000000000"

When this filter is applied, interrupt transfer that is originally required is suppressed, but it is necessary to remove a part of the filter and check it.

Immediately after turning on the power of Scarlett or when executing the script described later, a large amount of communication other than the above can be confirmed. As far as the dump of the communication contents is seen, it is probably a setup sequence that inquires about the status. Also, if you run a program that uses usbdk, you can check the same communication, so

Can be guessed.

Simple status inquiry

Now that you have a general idea of the flow of analysis, first analyze the Scarlett status query and write the status query process in Python. The original purpose is to operate on Linux, but since I will write the code by trial and error while analyzing the regular communication contents by Focusrite Control, I will write the code once in the Windows environment.

Query processing analysis

First, we need to find out how Focusrite Control is receiving Scarlett status. If you think simply, you should be able to detect the state change when you press the state switching button on the main unit by interrupt transfer.

If you actually press INST or PAD on the Scarlett main unit, you can send and receive the following data. RECV is the received data from Scarlett, and SEND is the transmitted data to Scarlett.

RECV Interrupt 00 00 80 00 00 00 00 00
SEND Control   21 02 00 00 03 00 18 00 00 00 80 00 08 00 EE 99
               00 00 00 00 00 00 00 00 7C 00 00 00 18 00 00 00
RECV Control
SEND Interrupt 
RECV Interrupt 01 00 00 00 00 00 00 00
SEND Control   A1 03 00 00 03 00 28 00
RECV Control   00 00 80 00 18 00 EE 99 00 00 00 00 00 00 00 00 
               01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
               00 00 00 00 00 00 00 00

This is the result when the microphone mode of analog input 1 of Scarlett 18i20 is changed to INST, but in fact, even if other buttons are pressed, the exchange other than the last data has not changed except for the serial number. In other words

It seems to be the flow. The final format of the received data is

It has become.

Inquiry code

Now that I know about it, I'll write a code that actually inquires about the status of the analog input.

analogstat.py


import usb.core
import usb.backend.libusb1
from ctypes import c_void_p, c_int
backend = usb.backend.libusb1.get_backend(find_library=lambda x: "libusb-1.0.dll")
backend.lib.libusb_set_option.argtypes = [c_void_p, c_int]
backend.lib.libusb_set_debug(backend.ctx, 5)
#Below are the options for using usbdk
backend.lib.libusb_set_option(backend.ctx, 1)
#Scarlett 18 i20 Vendor ID and Product ID
VENDOR_ID = 0x1235
PRODUCT_ID = 0x8215

device = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID, backend=backend)
device.set_configuration()
#The setup sequence runs on its own, probably because of usbdk, and the next serial number is usually 0x77.
device.ctrl_transfer(0x21, 0x02, 0x00, 0x03,
    [0x00, 0x00, 0x80, 0x00, 0x08, 0x00, 0x77, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x7C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00])
#Interrupt transfer read.
#Read from device endpoint 3=83, 8 bytes, timeout 100ms
ret = device.read(0x83, 8, 100)
print(' '.join(map(lambda x: '{0:0{1}x}'.format(x, 2), ret)))
ret = bytearray(device.ctrl_transfer(0xA1, 0x03, 0x00, 0x03, 0x0028))
print(' '.join(map(lambda x: '{0:0{1}x}'.format(x, 2), ret)))

The final output of the result of executing the above code with INST, PAD and AIR of input 1, AIR of input 4, PAD of input 5 and AIR of input 8 turned on is

RECV Control 00 00 80 00 18 00 77 00 00 00 00 00 00 00 00 00 
             01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00
             01 00 00 01 00 00 00 01

bingo. Let's try strange communication such as shifting the serial number when normal operation is confirmed.

device.ctrl_transfer(0x21, 0x02, 0x00, 0x03,
    [0x00, 0x00, 0x80, 0x00, 0x08, 0x00, 0xFF, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x7C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00])

Send the control transfer of the analog input status inquiry with the serial number advanced.

RECV Control 00 00 80 00 00 00 77 00 03 00 00 00 00 00 00 00

Apparently, the error status is entered in the latter 8 bytes. Although I requested to send 40 bytes of data, only 16 bytes were sent, but this is certainly the behavior recognized by the USB standard. NG only if the data length exceeds the required size.


It's been long, so this time it's up to here. next time

Let's try these two.

[^ 1]: https://return0.info/ Although it is said to be an overkill as a hobby ... [^ 2]: We do not recommend it because there are problems such as the lack of software sound sources as well as compatible equipment. However, I am using a plug-in that has only binary for Linux, and in the first place I mainly record performances without using a lot of software sound sources, so I can make works of quality that offers from overseas underground labels. However. [^ 3]: Standard Documents is quite huge, and appropriate reverse engineering like this one is done. To be honest, it is troublesome to use it as a reference. [^ 4]: Behind this is the increasing demand for music production on mobile / tablet devices equipped with iOS and Android, and it seems that there is a strong aspect of supporting devices for which it is difficult to install a dedicated driver. [^ 5]: Recording, playback, switching of clock sources of digital devices, switching of sampling frequency are applicable. Also, in the case of a general-purpose driver, there may be a disadvantage in terms of latency as well as in terms of functionality. However, if the round trip latency including all processing from input to output is about 20ms, it does not seem to be a problem. Pro Tools HDX used in professional studios seems to be in the world of 0.7ms.

Recommended Posts

Make an audio interface controller with pyusb (2)
Make an audio interface controller with pyusb (1)
Make Scrapy an exe with Pyinstaller
[Python] Make a game with Pyxel-Use an editor-
Make a monitoring device with an infrared sensor
Reinforcement learning 37 Make an automatic start with Atari's wrapper
How to make an HTTPS server with Go / Gin
Make an umbrella reminder with Raspberry Pi Zero W
I tried to make an OCR application with PySimpleGUI
Make an application using tkinter an executable file with cx_freeze
Make Lambda Layers with Lambda
Make Yubaba with Discord.py
Make slides with iPython
Make an air conditioner integrated PC "airpi" with Raspberry Pi 3!