USB interface for multi channel outputs, not just stereo

I didn't test different sampling frequencies yet, but I want to try out 48kHz in the next days. I tested different bIntervals by just changing the value AUDIO_POLLING_INTERVAL_480 in usb_desk.h for testing purposes (Manually setting the value is normally not necessary). Or also by setting the AUDIO_SUBSLOT_SIZE to 3 (24bit per samples). Then 1059bytes/ms are required at 8channels/44.1kHz.
 
I tested the usb input now also with 48kHz and I fixed a small bug (unrelated to the sampling frequency), that caused sometimes a nearly empty buffer at the beginning of a stream. As far as I can tell, the usb input also works for 48kHz.
 
I've had a very brief play and it seems to be the best I've heard yet - nice job!

I can see the extra samples being inserted (Teensy->PC) but can't hear them. Curious to know how the PC reacts if you just send the occasional packet with one fewer sample in it. For example, 44.1kHz ought to be 9 packets of 44 samples and 1 of 45, forever; but if you need to make the occasional 44-sample packet a 43-sample, or the 45 another 44, rather than inserting a duplicate sample, does it all fail horribly? Similarly if you would drop a sample, can you just send an extra 45-sample packet? I confess I can't follow your code well enough to try this for myself :(
 
I have to admit, I need to clean that part of my implementation up. My focus was on the usb input and I only adapted the usb output, because my changes of course broke the original two channel code.
When I have some time within the next days, I will try out your suggestion. Maybe we should also try to change the type of the usb output endpoint from adaptive to asynchronous.
Did you also test the usb input? And did you use 96kHz as sampling rate?
 
I only tested input very briefly … even more so than the output! Seemed OK but I need to do a thorough test, recording known-perfect waveforms and then looking at them with a spectral display to highlight any discontinuities.

Didn’t touch 96kHz. I will get to it at some point, for sure. If you think it’s working as-is, apart from sample dropping or stuffing, I’d say don’t change the endpoint type yet.

I did do some multi-channel USB objects, and edits to the Design Tool so you can place them. Feel free to use any of that; alternatively, I’ll fork your code and do a PR with those added, once we’re confident it’s as good as it can be. I’m kinda hoping the next Teensyduino will pull in a lot of the pending Audio PRs (or at least the good ones ;)), and this looks like a definite candidate - there’s been lots of interest over the years…
 
I tested you suggestion:
There is now the variable 'async' in line 789 in usb_audio.cpp. (I will change that later, but for now for some quick tests it should be fine). If async is set to false, the usb audio output sends always the expect number of samples. (E.g. 9 times 44 samples and then once 45 samples). If the buffer gets too full, it just skips one sample of the buffer and if the buffer gets too empty it sends one sample twice. This is the version you already tested.
If async is set to true (as in the code I commited), then it never discards or stuffs samples. Instead it sends one sample more or one sample less too the host. At a first test this also seems to work. I used this triangular waveforms from your example and recorded them in Goldwave. Just visually there don't seem to be any glitches.
However, now the usb output behaves like an asynchronous source endpoint:
1711137904597.png

So I wanted to know if it makes any difference if I change the usb descriptor from adaptive to asynchronous (Line 1644 and 2791 in usb_desc.c). It didn't make any difference and I kept it for now.
All in all I think defining the usb output as asynchronous source and adapting the number of sent samples is the best solution.
 
Regarding a pull request: I am not sure how to proceed. I changed a lot compared to the original code like the update to the usb2 specification. Unfortunatley it is not possible for me to test it with many different hosts and it would therefore be very risky to merge it into the main branch. Maybe it is possible to add new build options like USB_MIDI_UAC2AUDIO_SERIAL in addition to USB_MIDI_AUDIO_SERIAL (for some transition time)? Then we can be sure that we don't break old code and people who want to try out the new interface can easily switch.
 
That sounds like good news, will give it a go as soon as I can.

With regard to the PR, I was thinking only as far as forking your repo, adding in some of my modifications to allow placement of wider USB objects in the Design Tool, and submitting a PR for you to consider. Pulling the result into the official Teensyduino release would of course be a matter for Paul to decide in a future release cycle. The release times do tend to be quite long (1.59 took 10 months from beta #1 to release), so there would in theory be plenty of time to find any real show-stoppers and back out the changes if they couldn't be fixed.
 
Sure, I expressed myself unclearly. I was just thinking about how we could prepare the code so that there is the chance that Paul accepts a pull request.
I have to admit, I have never used the Design Tool, but I would give it a try and test the changes you made to the USB object.
 
I've done a PR to your repo, having done a bit more testing - this essentially adds in stuff I'd already done previously. Minor change to your code to make it compile with 8 channels @ 96kHz. Haven't time for more right now, but will be back!
 
WoW :D
I've been using your asynchronous spdif input successful and now you bring us hope for USB 8ch.
Alex thanks for this wish I have time to test this out now.
 
Hallo, ich möchte mit einem T4.0 eine Schnittstelle für ein IPhone erhalten. Das Interface sollte 5x Audio Line in besitzen, 1x Stereo Output und 1x Midi Input. Kann ich so etwas realisieren???
 
I don't know. There are several things that could cause problems. E.g. can your IPhone act as usb host? If yes, then I am still not sure if my code will work with an IPhone. I used my Windows 11 notebook for testing during the develpement and at a first test with an Ubuntu PC there were of course some problems. I wouldn't be surprised if it does not work with an IPhone out of the box.
Regarding the number of channels: do you mean 5.1 audio input? This can be done by setting the number of usb audio channels to 6 (I read somewhere in the usb audio specification that it always has to be an even number of channels.). However, then you get automatically also a 6 channel ouput instead of stereo. But I hope that this is not a problem.
Midi and usb audio can be used in parallel, but I have no experience with Midi.
 
Ich habe es einmal mit einem IS2-USB-Code versucht, es hat funktioniert und das Audiosignal auf das iPhone übertragen. Außerdem konnte ich das Audiosignal des iPhones über die Kopfhörerbuchse des Audioshield hören. Als ich dann einen Code mit Midi-Eingang geschrieben habe, funktionierte nur das Audio-Setup. Ich habe wahrscheinlich einen Fehler gemacht.

Wie kann ich die USB-Kanäle von 2 auf 6 ändern?
 
Finally got back to testing this - sorry for the delay! This is with a Teensy 4.1 on Windows 10 x64, and using 8 channel 96kHz settings on the basis that if that works pretty much anything should. My latest test code is this:
C++:
#include <Audio.h>

#define AUDIO_kHz ((int) AUDIO_SAMPLE_RATE / 1000)
#define AUDIO_CHANNELS USB_AUDIO_NO_CHANNELS_480

extern "C"
{
    struct usb_string_descriptor_struct
    {
        uint8_t bLength;
        uint8_t bDescriptorType;
        uint16_t wString[6+1+1+2+1];
    };
    
  usb_string_descriptor_struct usb_string_serial_number={
    2+(6+1+1+2+1)*2,3,
    {'A','u','d','i','o','-','0'+AUDIO_CHANNELS,'/','0'+(AUDIO_kHz / 10),'0' + (AUDIO_kHz % 10),'B'}
  };
}

// GUItool: begin automatically generated code
AudioSynthWaveform       wav1;           //xy=260,158
AudioSynthWaveform       wav2;           //xy=265,194
AudioSynthWaveform       wav3;           //xy=267,232
AudioSynthWaveform       wav4;           //xy=271,269
AudioSynthWaveform       wav5;           //xy=275,307
AudioSynthWaveform       wav6;           //xy=280,343
AudioSynthWaveform       wav7;           //xy=284,380
AudioSynthWaveform       wav8;           //xy=288,418
AudioInputUSBOct         usb_oct_in;     //xy=305,509
AudioRecordWAVoct        recordWAVoct;  //xy=569,599
AudioMixer4              mixer1;         //xy=575,417
AudioMixer4              mixer2;         //xy=580,489
AudioMixer4              mixer3;         //xy=717,467
AudioOutputI2S           i2sOut;         //xy=810,344
AudioOutputUSBOct        usb_oct_out;    //xy=813,249

AudioConnection          patchCord1(wav1, 0, usb_oct_out, 0);
AudioConnection          patchCord2(wav1, 0, i2sOut, 0);
AudioConnection          patchCord3(wav2, 0, usb_oct_out, 1);
AudioConnection          patchCord4(wav3, 0, usb_oct_out, 2);
AudioConnection          patchCord5(wav4, 0, usb_oct_out, 3);
AudioConnection          patchCord6(wav5, 0, usb_oct_out, 4);
AudioConnection          patchCord7(wav6, 0, usb_oct_out, 5);
AudioConnection          patchCord8(wav7, 0, usb_oct_out, 6);
AudioConnection          patchCord9(wav8, 0, usb_oct_out, 7);
AudioConnection          patchCord10(usb_oct_in, 0, mixer1, 0);
AudioConnection          patchCord11(usb_oct_in, 0, recordWAVoct, 0);
AudioConnection          patchCord12(usb_oct_in, 1, mixer1, 1);
AudioConnection          patchCord13(usb_oct_in, 1, recordWAVoct, 1);
AudioConnection          patchCord14(usb_oct_in, 2, mixer1, 2);
AudioConnection          patchCord15(usb_oct_in, 2, recordWAVoct, 2);
AudioConnection          patchCord16(usb_oct_in, 3, mixer1, 3);
AudioConnection          patchCord17(usb_oct_in, 3, recordWAVoct, 3);
AudioConnection          patchCord18(usb_oct_in, 4, mixer2, 0);
AudioConnection          patchCord19(usb_oct_in, 4, recordWAVoct, 4);
AudioConnection          patchCord20(usb_oct_in, 5, mixer2, 1);
AudioConnection          patchCord21(usb_oct_in, 5, recordWAVoct, 5);
AudioConnection          patchCord22(usb_oct_in, 6, mixer2, 2);
AudioConnection          patchCord23(usb_oct_in, 6, recordWAVoct, 6);
AudioConnection          patchCord24(usb_oct_in, 7, mixer2, 3);
AudioConnection          patchCord25(usb_oct_in, 7, recordWAVoct, 7);
AudioConnection          patchCord26(mixer1, 0, mixer3, 0);
AudioConnection          patchCord27(mixer2, 0, mixer3, 1);
AudioConnection          patchCord28(mixer3, 0, i2sOut, 1);

AudioControlSGTL5000     sgtl5000;       //xy=830,388
// GUItool: end automatically generated code

//=====================================================================
AudioSynthWaveform* wavs[] = {
  &wav1,
  &wav2,
  &wav3,
  &wav4,
  &wav5,
  &wav6,
  &wav7,
  &wav8
};

AudioMixer4* mixers[] = {&mixer1,&mixer2};

uint32_t ledOff;

void setup()
{
  pinMode(LED_BUILTIN,OUTPUT);
  AudioMemory(150 * 128 / AUDIO_BLOCK_SAMPLES); // empirical calculation!

  while (!Serial)
    ;

  if (CrashReport)
    Serial.print(CrashReport);
    
  while (!SD.begin(BUILTIN_SDCARD))
  {
    Serial.println("SD wait...");
    delay(250);
  }

  // At 8/96, we're generating 1,536,000 bytes/sec,
  // so a 128kB buffer will give an SD write every
  // 42.6ms - can be marginal on even a good card!
  recordWAVoct.createBuffer(128*1024,AudioBuffer::inHeap);
 
  // sgtl5000.setAddress(HIGH);
  sgtl5000.enable();
  sgtl5000.volume(0.05f);

  for (int i=0;i<8;i++)
  {
    wavs[i]->begin(0.5f,220.0f + 110.0f*i,WAVEFORM_TRIANGLE);
    wavs[i]->phase(15.0f*i);
  }

  for (int i=0;i<2;i++)
  {
    for (int j=0;j<4;j++)
      mixers[i]->gain(j,0.25f);
  }

  Serial.printf("Audio block size %d samples; sample rate %.2f; %d channels\n",AUDIO_BLOCK_SAMPLES,AUDIO_SAMPLE_RATE_EXACT,AUDIO_CHANNELS);
  Serial.println("Running");
}

uint32_t lastBlocks;
int idx;
#define BUFL 20
char sbuf[BUFL], filename[BUFL+6];

void startRecording(void)
{
  sprintf(filename, "%s.wav", sbuf);
  recordWAVoct.record(filename);
  Serial.printf("Recording %s\n", filename);
}

void stopRecording(void)
{
  recordWAVoct.stop();
  Serial.println("Recording stopped");
}

void loop()
{
  while (Serial.available() > 0)
  {
    char ch = Serial.read();
    if ('\n' == ch)
    {
      if (idx > 0)
      {
        sbuf[idx] = 0;
        startRecording();
        idx = 0;
      }
      else
        stopRecording();
        
      continue;
    }
    sbuf[idx] = ch;
    if (idx < BUFL-1)
      idx++;
  }
 
  if (millis() > ledOff)
  {
    digitalWrite(LED_BUILTIN,0); 
  }

  if (millis() - lastBlocks > 500)
  {
    const char* rec = recordWAVoct.isRecording()?"; rec":"";
    lastBlocks = millis();
    Serial.printf("Blocks %d; max %d%s\n",AudioMemoryUsage(),AudioMemoryUsageMax(),rec);
    AudioMemoryUsageMaxReset();
  }
}
Audio topology is:
1717599146058.png

My testing is pretty simple: using GoldWave I:
  • record the waveforms coming from the usb_oct_out object, then
  • play the recording back through usb_oct_in, recording that to SD card using recordWAVoct*
  • retrieve the recording from the SD card by plugging it into the PC
Looking at and listening to the two recordings, I'm neither seeing nor hearing any glitches - if they're present, GoldWave's spectrogram and waterfall displays are very good at showing these up, even if they're inaudible. So I'm concluding at this time your code is working fine :)

As you note in post #59 above, it's not 100% clear what the best way of submitting a PR for this is. For convenience it's very helpful to have the Audio block size, sample rate and USB channel count accessible from the Tools menu, but unfortunately the relevant files (as found in the changedConfigFiles folder) don't form part of the cores repo. Also, it might be a bit more elegant to get rid of AudioData.h, putting its content [back] into AudioStream.h, and then using some conditional compilation magic to allow the latter to be #included in usb_desc.h ... although that might not be necessary if the settings are available from Tools, as that makes them available for all source files ... needs some thinking about!


* you need my buffered SD recording upgrade for this, which can be found here, and is discussed on this thread.
 
Good to hear that the usb interface works also with 8 channel 96kHz.
Regarding a pull request: I also have some things that I still need to clean up first (besides AudioData.h). When I find some time the next days I'll have a look at the issues. Maybe I'll need to come back to you for some advice.
 
@alex6679
Bit off topic question related to this thread: Resampling I2S slave inputs at the T4/ T4.1
Do you think it's possible - resample between 8khz ~ 50khz 8 channel 16bit TDM on T4.1 ?

Since you can't change USB frequencies @ run time I would love to have I2S slave input resampling the only problem is this TDM 8 channels 16bit.
I try to implement that of course and I run into memory footprint issues. Been awhile don't remember all the details now.
 
@h4yn0nnym0u5e: This are good news. Thanks also for fixing the include of AudioStream.h in usb_desc.h.
I am currently testing some changes I made to the AudioInputUSB and AudioOutputUSB (not pushed yet).
To understand my changes some background info is needed: In my projects I don't use the standard Teensy audio library, but a 32bit floating point version similar to Chipaudette's audio library (https://github.com/chipaudette/OpenAudio_ArduinoLibrary).
The problem was that with my current version of the usb audio interface it was not possible for me to add usb audio to my audio library without changing the core library. That's of course annoying since after every update of Teensyduino I would need to manually change/merge some files in Teensy 4 core.
My idea was to split AudioInputUSB as well as AudioOutputUSB into two classes:
- Two low level classes (one for the input and one for the output) that implement the basic functionalities for receiving and transmitting audio data (the more complex stuff). This classes don't inherit from AudioSream.
- Two high level classes that inherit from AudioStream and that are quite simple. Each of this classes have one instance of the corresponding low-level class as a private member.
This classes have the same names and interface as the original AudioInputUSB and AudioOutputUSB in order to not break old code.
With this concept I can now just implement a 32bit floating point version of the two high-level classes in my audio library without the need to change the core library. It's of course also very simple to add usb audio to e.g. Chipaudette's audio library.
This update is of course only relevant if our version of the usb audio interface gets merged into Paul's core library.

@Chris O.: I think that's possible. I am currently quite busy with clean up and improving the usb audio interface. After that is finished, we could try to resolve your problem together.
 
That sounds a very useful approach. I should probably consider something similar for my filesystem streaming updates, which currently only support 16 bit WAV but would be more useful if they allowed for higher bit depths. Especially the case if we did a similar approach to the audio I/O, so “from source” files retain the full dynamic range: you could imagine a portable Teensy-based multitrack recorder where you could export to a full DAW when you got home.

OT again :) sorry
 
Ok, after I quite excessively tested the new version, I pushed the changes. Even if I mostly only moved code around, I was worried that I messed something up. Indeed it did not work at the beginning and I found a bug that was not related to my changes, but was in the code for probably quite some time.
I also removed some code that I only used for debugging the usb communication between the Teensy and the host. It was quite useful at the beginning, but Wireshark provides the same information.
Now there is still something related to the usb audio descriptors and docB's problems with Ubuntu (https://forum.pjrc.com/index.php?threads/linux-usb-audio-issue-with-teensy-4-1.74730/#post-341486) that I need to improve.
 
Great - I'll probably wait for that before re-testing.

I think there are severe difficulties catering for all possible combinations of interface, due to the lack of available PIDs. I don't believe Paul documents which ones he has available, and in any case it's all a bit informal due to using a set obtained before the USB org clamped down on ID resellers. I think ... could be wrong. Anyway, it's probably good to have as a demo, but I suspect Audio+Dual Serial would never be merged with the Teensyduino distribution.
 
We have a positive report from a Mac user … looking good :)
Yes, it's working with no issues on Mac, using Sonoma 14.5, Logic and Garageband with a Micromod and a custom board with the SGTL5000. If you need any more info let me know. I used the AudioOutputUSBoct and the AudioOuputUSB regular and both works well, no clicks so far, no strange noises.
 
Back
Top