Vendor Audio Requests implementation

Discussions about USB Audio on XMOS devices
MaximLiadov
XCore Addict
Posts: 130
Joined: Mon Apr 16, 2018 9:14 am

Post by MaximLiadov »

fabriceo wrote: Wed Apr 12, 2023 4:59 pm Anyone having solved that ?
Dear Fabrice,

Seems to be at least one person has solved it successfuly.
https://www.xcore.com/viewtopic.php?t=8110
I have read this topic 10 times but could not get how to do this. I asked the person in private message, but got no answer.
MaximLiadov
XCore Addict
Posts: 130
Joined: Mon Apr 16, 2018 9:14 am

Post by MaximLiadov »

I have found something here:
Status Interrupts
A status interrupt pipe can be used to inform the host that a setting has changed on the device. This feedback maintains synchronization between the Mac’s user interface and the device state. Currently, AppleUSBAudio updates certain audio controls on the Mac for interrupts originating from Feature, Selector and Clock units as described in Table 6.
https://developer.apple.com/library/arc ... index.html
Feature unit Volume and mute User turns volume knob <--- !!!

it also refers to Section 6 in UAC Standard. It says:
Interrupts are used as a means to inform the Host that a change has occurred in the current state of the Audio Function.

The interrupt data message is always 6 bytes in length. The first bInfo field is required for all interrupt data messages. It contains information in D0 indicating whether this is a vendor-specific interrupt (D0 = 0b1) or a class-specific interrupt (D0 = 0b0). Bit D1 indicates whether the interrupt originated from an interface (D1 = 0b0) or an endpoint (D1 = 0b1). Bits D7..2 of the bInfo field are reserved. For vendor-specific interrupts, the layout of the remainder of the interrupt message is undefined. For class-specific interrupts, the layout is defined as follows.

So seems to be I need to use both types of interrupt, vendor-specific for device's settings update and class-specific for volume and mute. Right?
User avatar
Ross
Verified
XCore Expert
Posts: 976
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

You need to report an interrupt with the volume control feature unit as the source. The host should then interrogate the unit as to its current state. This is pretty much the same as the clock validity stuff.
MaximLiadov
XCore Addict
Posts: 130
Joined: Mon Apr 16, 2018 9:14 am

Post by MaximLiadov »

Ross wrote: Sat Apr 15, 2023 11:29 pm You need to report an interrupt with the volume control feature unit as the source. The host should then interrogate the unit as to its current state. This is pretty much the same as the clock validity stuff.
Dear Ross,

I confirm, it does work! I've made it work finally.
MaximLiadov
XCore Addict
Posts: 130
Joined: Mon Apr 16, 2018 9:14 am

Post by MaximLiadov »

Here is the most important part of code:

usb_buffer.xc:

Code: Select all

#include "usbaudio20.h"
#define INT_EP
...
void buffer(
..
#ifdef INT_EP
        chanend ?c_ep_int, chanend ?c_clk_int, // c_clk_int - interrupt channnel
#endif
...
#ifdef INT_EP
    XUD_ep ep_int = XUD_InitEp(c_ep_int);  // c_ep_int - Interrupt Endpoint
#endif
...
#ifdef INT_EP
            
            /* Interrupt processing. Volume was changed on Device */
            
            case inuint_byref(c_clk_int, u_tmp): //read interrupt request
            chkct(c_clk_int, XS1_CT_END);

            /* Check if we have interrupt pending.
             * Note, this his means we can loose interrupts... */
            if(!g_intFlag)
            {
                g_intFlag = 1;

                g_intData[2] = u_tmp; // hope it's a number of channel
                g_intData[3] = FU_VOLUME_CONTROL;
                g_intData[5] = FU_USBOUT;

                XUD_SetReady_In(ep_int, g_intData, 6);
            }
            break;

            /* Interrupt EP data sent, clear flag */
            case XUD_SetData_Select(c_ep_int, ep_int, result):
            {
                g_intFlag = 0;
                break;
            }
#endif
In all other sources I searched (Ctlr+H) string #if defined(SPDIF_RX) and replaced it with #define INT_EP to make declared Interrupt Endpoint. Hope this information would be helpful to someone. Please tell me, if something incorrect in my code.

Also the comment was there: "we can loose interrupts..." What is it about? Any idea how not loose anything?
User avatar
fabriceo
XCore Addict
Posts: 213
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Hi Guys, thank you for sharing !
so basically you are using the existing "interrupt endpoint" which was initially created for informing the host of a spdif-clock-change, and you send a new g_intData record with information related to Feature Unit... good and relatively easy to implement if you do not use the existing clockgen mechanism (as I understand you use an ASRC in your product).

but here MaximLiadov, in your USBBufferXC you have 2 "case" statements.
The first one is originally triggered by the clockgen code sending 1byte+CT_END on the chanend c_clk_int.
this informs the XUD that a 6 byte record is ready to be sent out to the host with XUD_SetReady_In(ep_int, g_intData, 6);
The second case is triggered by XUD when the host acknowledge the reception on the Interrupt record, via chanend c_ep_int;

but somewhere in your application code, you need to trigger the first case by sending u_temp+CTEND to c_clk_int when your front panel volume is changed, did you do that ? another possibility is to use the SOF interruption (in usbbuffer) to call XUD_SetReady_In(ep_int, g_intData, 6) upon volume change (checking a global memory location on tile1) so you don't need to care about c_clk_int channel anymore :)

the comment about "we can loose interrupt" just means that the code is written in a way that we may send multiple XUD_SetReady_In(ep_int, g_intData, 6) without having received XUD acknowledgement. in your case, if you consider the front-panel volume knob being the "master" then no problem with that.
User avatar
fabriceo
XCore Addict
Posts: 213
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Ross, by the way, in main.xc, the interrupt endpoint for spdif/adat clockgen seems to be declared as XUD_EPTYPE_BUL in epTypeTableIn ??
am I mistaken, or is there a trick somewhere?
MaximLiadov
XCore Addict
Posts: 130
Joined: Mon Apr 16, 2018 9:14 am

Post by MaximLiadov »

Dear Fabrice, you comments are just brilliant and extremely valuable, as always!
fabriceo wrote: Sun Apr 16, 2023 4:38 pmgood and relatively easy to implement if you do not use the existing clockgen mechanism (as I understand you use an ASRC in your product).
Exactly! Works for me. External ASRC chip is a nice option. No need in any "source select" switch, 192 kHz support, pain free implementation, +1 free XMOS core, and I can mix USB + SPDIF.
but here MaximLiadov, in your USBBufferXC you have 2 "case" statements.
The first one is originally triggered by the clockgen code sending 1byte+CT_END on the chanend c_clk_int.
this informs the XUD that a 6 byte record is ready to be sent out to the host with XUD_SetReady_In(ep_int, g_intData, 6);
The second case is triggered by XUD when the host acknowledge the reception on the Interrupt record, via chanend c_ep_int;
but somewhere in your application code, you need to trigger the first case by sending u_temp+CTEND to c_clk_int when your front panel volume is changed, did you do that ?
Yes, I do send it every time volume knob is rotated. Then I need to make sure I did not lose any request when all these operations processed.

I added else to code:

Code: Select all

if(!g_intFlag)
            {
...
                XUD_SetReady_In(ep_int, g_intData, 6);
            }
            else
                printf("loose interrupt\n");
I see "loose interrupt" when knob is rotating too fast.
another possibility is to use the SOF interruption (in usbbuffer) to call XUD_SetReady_In(ep_int, g_intData, 6) upon volume change (checking a global memory location on tile1) so you don't need to care about c_clk_int channel anymore :)
I actively use global memory around project, but this sounds unreal for my current level.
the comment about "we can loose interrupt" just means that the code is written in a way that we may send multiple XUD_SetReady_In(ep_int, g_intData, 6) without having received XUD acknowledgement. in your case, if you consider the front-panel volume knob being the "master" then no problem with that.
It would be nice to have just one master fader in OS. Is it possible to implement? Having 3 faders and updating all them is just a mess to be honest. I cannot find where is this in project. Any help would be much appriciated.
User avatar
fabriceo
XCore Addict
Posts: 213
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Hi Guys
after some tests this morning, I confirm that it is possible to trigger the Audio Control Interrupt by calling XUD_SetReady_In(ep_int, g_intData, 6) from the SOF select case (which happens every 125us).
This way it is quite easy to detect a change in some global variables, including volsOut[0] or mutesOut[0].
Then the XUD acknowledge the transfer of g_intData to the host by triggering the select case XUD_SetData_Select(c_ep_int, ep_int, result):
Just after the host issues a Get request for FU_USBOUT to get the value of volsOut[0] and the window panel is updated accordingly.
It is possible to add instructions in audiorequest.xc to flag the fact that the host as read the value of volsOut[0], or to overwrite the value of volsOut[0] with the volume knob value.
To find the code section, just search for "buffer[0] = volsOut[ sp.wValue&0xff ];"

For my previous question about the type of enpoint, it is called Interrupt endpoint but it is not pulled by the host so it is not flagged as XUD_EPTYPE_INT...

Ross, considering the possible coexistence with a SPDIF/ADAT interrupt, would be great to enhance the usb_audio_app 7.2 to extend this interrupt concept to other source like Volume or Terminal, with some helpers or defines :)
User avatar
Ross
Verified
XCore Expert
Posts: 976
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

fabriceo wrote: Sun Apr 16, 2023 6:16 pm Ross, by the way, in main.xc, the interrupt endpoint for spdif/adat clockgen seems to be declared as XUD_EPTYPE_BUL in epTypeTableIn ??
am I mistaken, or is there a trick somewhere?
That's a mistake, thanks for spotting. Actually it doesn't matter since the underlying code only really cares about ISO or not. I will fix though, to avoid confusion.