Using an external trigger with USB Audio

New to XMOS and XCore? Get started here.
Post Reply
RobV
Junior Member
Posts: 6
Joined: Sun Mar 29, 2015 5:13 am

Using an external trigger with USB Audio

Post by RobV »

Hello All,

I would like to use an external device to trigger the USB audio software to go into loopback mode sending whatever is sampled by the ADC back out to the DAC as well as set the sample rate to a fixed frequency.

Ideally I would be able to flip a switch to raise a pin high on the XMOS chip to trigger this event. I would also like this functionality to work without the device (multifunction audio board) being connected to any USB host.

Any advice on where to get started?

My initial thought is to add a bit of code in the endpoint0 that will poll the trigger pin and, if found high would use the audiorequests to route the audio streams into loopback mode. This will require using the mixer correct? If I set MAX_MIX_COUNTS to zero I think should be able to add the mixer without busting the core limit if I don't use the MIDI or SPDIF.

Thanks,
Rob.


User avatar
infiniteimprobability
XCore Legend
Posts: 1126
Joined: Thu May 27, 2010 10:08 am
Contact:

Post by infiniteimprobability »

I would like to use an external device to trigger the USB audio software to go into loopback mode sending whatever is sampled by the ADC back out to the DAC as well as set the sample rate to a fixed frequency.
OK - should be possible. You can do pretty much whatever you want as it's all source code..
What do you need to do with the USB side? Should it still stream to nothing or should the device disappear off the bus when loopback occurs?
Ideally I would be able to flip a switch to raise a pin high on the XMOS chip to trigger this event. I would also like this functionality to work without the device (multifunction audio board) being connected to any USB host.

Any advice on where to get started?
Depends really - lots of ways you could skin this cat (including putting a shim in between decouple/audio to route, like the mixer does). Personally, if it's just as simple as loopback when an external pin is set, I'd just add some code to audio.xc. Poll a pin...if set then loopback samples at I2S, but carry on talking to decouple but ignore samples from host and send zeros (if input is enabled). That way you avoid messing with the USB side of stuff.
My initial thought is to add a bit of code in the endpoint0 that will poll the trigger pin and, if found high would use the audiorequests to route the audio streams into loopback mode. This will require using the mixer correct? If I set MAX_MIX_COUNTS to zero I think should be able to add the mixer without busting the core limit if I don't use the MIDI or SPDIF.
Are you using mixer? How many cores do you have/are using? What's your channel count? You may not need mixer.. It's important to understand this to make the right recommendation.
User avatar
Ross
XCore Expert
Posts: 966
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

Something like the following quick hack into audio.xc might do what you are looking for:

Code: Select all


in port p_trigger = XS1_PORT_1A;

.....

    p_trigger :> trigger;

#if NUM_USB_CHAN_IN > 0
#pragma loop unroll
        for(int i = 0; i < I2S_CHANS_ADC; i++)
        {
            if(readBuffNo)
                outuint(c_out, samplesIn_1[i]);
            else
                outuint(c_out, samplesIn_0[i]);
        }
        /* Send over the digi channels - no odd buffering required */
#pragma loop unroll
        for(int i = I2S_CHANS_ADC; i < NUM_USB_CHAN_IN; i++)
        {
            outuint(c_out, samplesIn_0[i]);
        }
#endif

#if NUM_USB_CHAN_OUT > 0
#pragma loop unroll
        for(int i = 0; i < NUM_USB_CHAN_OUT; i++)
        {
            samplesOut[i] = inuint(c_out);

           // Loop ADC to DAC if p_trigger port high
            if(trigger)
                samplesOut[i] = samplesIn[i];
        }
Fixed sample rate is a bit harder - you will upset the host if you just start streaming at a different freq
RobV
Junior Member
Posts: 6
Joined: Sun Mar 29, 2015 5:13 am

Post by RobV »

Ross,

Thanks, that's the kind of quick hack I was looking for!

Concerning messing with the host, I don't plan to use this application to stream audio data to the host. What if I set NUM_USB_CHAN_IN (0) in the customdefines.h and hard code the the ADC into audio.xc like so:

First, remove the possibility of sending sample to host:

Code: Select all

in port p_trigger = XS1_PORT_1A;

.....

    p_trigger :> trigger;

// Remove conditional outuint() since NUM_USB_CHAN_IN = 0 anyway

#if NUM_USB_CHAN_OUT > 0
#pragma loop unroll
        for(int i = 0; i < NUM_USB_CHAN_OUT; i++)
        {
            samplesOut[i] = inuint(c_out);

           // Loop ADC to DAC if p_trigger port high
            if(trigger)
                samplesOut[i] = samplesIn[i];
        }



Next, remove conditional statement for driving the ADC in the main loop:

Code: Select all

#if (I2S_CHANS_DAC != 0) && (NUM_USB_CHAN_OUT != 0)
            index = 0;
#pragma loop unroll
            /* Output "even" channel to DAC (i.e. left) */
            for(int i = 0; i < I2S_CHANS_DAC; i+=I2S_CHANS_PER_FRAME)
            {
                p_i2s_dac[index++] <: bitrev(samplesOut[(frameCount)+i]);
            }
#endif

#ifndef CODEC_MASTER
            /* Clock out the LR Clock, the DAC data and Clock in the next sample into ADC */
            doI2SClocks(divide);
#endif


            /* Input previous L sample into L in buffer */
            index = 0;
            /* First input (i.e. frameCoint == 0) we read last ADC channel of previous frame.. */
            unsigned buffIndex = frameCount ? !readBuffNo : readBuffNo;

#pragma loop unroll
            /* First time around we get channel 7 of TDM8 */
            for(int i = 0; i < 2; i++)
            {
                // p_i2s_adc[index++] :> sample;
                // Manual IN instruction since compiler generates an extra setc per IN (bug #15256)
                asm volatile("in %0, res[%1]" : "=r"(sample)  : "r"(p_i2s_adc[index++]));

                /* Note the use of readBuffNo changes based on frameCount */
                    samplesIn_0[((frameCount-1)&(I2S_CHANS_PER_FRAME-1))+i] = bitrev(sample); // channel 1 only
            }
#endif
Doing this along with making sure other conditional like making sure ADC pins are setup &c. would cause the audio.xc sample the ADC independently and would only use the samples to overwrite the USB streaming input.

It looks like the audio loop goes forever regardless of whether a host is connected or not, so for fixing the sample rate, perhaps I could re-purpose the HID to read the same trigger pin and request that the stream be paused (in case there is a host connected) and send an AudioClassRequest to change the sample rate. I can also setup an internal flag in HID to indicate the request has been sent to avoid toggling between play and pause while the pin is high.
RobV
Junior Member
Posts: 6
Joined: Sun Mar 29, 2015 5:13 am

Post by RobV »

infiniteimprobability:
Should it still stream to nothing or should the device disappear off the bus when loopback occurs?
I would like the device to remain present if connected to a host, perhaps it could just pause the stream from the host which I mentioned in the last post.

Streaming to nothing could be an option as well. Using the quick hack to loopback from the previous post perhaps I could just disable the the portion of decouple.xc which waits for the audio core request:

Code: Select all

in port p_trigger = XS1_PORT_1A;

.....

    p_trigger :> trigger;

....
                /* Wait for the audio code to request samples and respond with command 
                    unless trigger is set*/
       if(!trigger)
          {
                inuint(c_mix_out);
                outct(c_mix_out, SET_STREAM_FORMAT_OUT);
                outuint(c_mix_out, dsdMode);
                outuint(c_mix_out, sampRes);

                /* Wait for handshake back */
                chkct(c_mix_out, XS1_CT_END);
                asm("outct res[%0],%1"::"r"(buffer_aud_ctl_chan),"r"(XS1_CT_END));
          }
Do the same for the Audio.xc:

Code: Select all

in port p_trigger = XS1_PORT_1A;

.....

    p_trigger :> trigger;

....
          if(!trigger)
            /* Do samples transfer */
            /* The below looks a bit odd but forces the compiler to inline twice */
            unsigned command;
       {
            if(readBuffNo)
                command = DoSampleTransfer(c_out, 1, underflowWord);
            else
                command = DoSampleTransfer(c_out, 0, underflowWord);


            if(command)
            {
                return command;
            }
       }
This would allow (I think) the two cores to carry on independently of each other, without issues caused by running at two sample rates (say 192k from USB, and 48k forced upon the audio core) since no core would be waiting on a request between the audio/decoupler, which should happen at a specific rate.
Are you using mixer? How many cores do you have/are using? What's your channel count? You may not need mixer.. It's important to understand this to make the right recommendation.
Right now I'm trying to use the U8 in my application for the sake of economics. The application is only two channels. I want to make something that "just works" for a prototype and then eventually come up with a more elegant solution. I would like to also add DSP eventually, so if I never need the SPDIF or MIDI it sounds like the U8 will just be able to pull it off. I was thinking of using the mixer in one core mode initially since it has the built in loopback. I also want the volume functionality of the mixer as well.

Bear with me, I'm doing a lot of thinking out loud here!

Thanks,
Rob.
Post Reply