Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion OpenAudio_ArduinoLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#include "output_i2s_f32.h"
#include "output_i2s_quad_f32.h"
#include "output_spdif3_f32.h"
#include "input_tdm8_f32.h"
#include "input_tdm16_f32.h"
#include "output_tdm8_f32.h"
#include "output_tdm16_f32.h"
#include "play_queue_f32.h"
#include "record_queue_f32.h"
#include "synth_pinknoise_f32.h"
Expand Down Expand Up @@ -76,4 +80,5 @@
#include "radioNoiseBlanker_F32.h"
#include "synth_sin_cos_f32.h"
#include "UART_F32.h"
// #include "USB_Audio_F32.h" Include this separately if needed. Then in IDE Tools>USB Type>Audio
// #include "USB_Audio_F32.h" // Uncomment for USB Audio (Tools > USB Type > Audio or MIDI+Audio+Serial).
// // Requires patched Teensy core. Run scripts/setup.ps1 first.
171 changes: 30 additions & 141 deletions USB_Audio_F32.h
Original file line number Diff line number Diff line change
@@ -1,146 +1,35 @@
/*
* USB_Audio_F32
*
* Created: Chip Audette (OpenAudio), Mar 2017
* Float32 wrapper for the Audio USB classes from the Teensy Audio Library
*
* License: MIT License. Use at your own risk.
*/
/*
* MIT License
*
* Copyright (c) 2025 alex6679
* https://github.com/alex6679/teensy-4-usbAudio
*
* This file is derived from the teensy-4-usbAudio project (Teensy 4.x
* multi-channel USB Audio 2.0 with asynchronous feedback).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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
* AUTHORS 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 IN THE
* SOFTWARE.
*/

#ifndef usb_audio_f32_h_
#define usb_audio_f32_h_
//#include "Arduino.h"
#include <AudioStream_F32.h>
#include <Audio.h>

class AudioInputUSB_F32 : public AudioStream_F32
{
//GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI node
//GUI: shortName:usbAudioIn //this line used for automatic generation of GUI node
public:
AudioInputUSB_F32() : AudioStream_F32(0, NULL) {
//i16_to_f32.disconnectFromUpdateAll(); //requires modification to AudioStream.h
//output_queue.disconnectFromUpdateAll(); //requires modification to AudioStream.h

makeConnections();
}
AudioInputUSB_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
//i16_to_f32.disconnectFromUpdateAll(); //requires modification to AudioStream.h
//output_queue.disconnectFromUpdateAll(); //requires modification to AudioStream.h

makeConnections();
}

void makeConnections(void) {
//make the audio connections
patchCord100_L = new AudioConnection(usb_in, 0, i16_to_f32_L, 0); //usb_in is an Int16 audio object. So, convert it!
patchCord100_R = new AudioConnection(usb_in, 1, i16_to_f32_R, 0); //usb_in is an Int16 audio object. So, convert it!
patchCord101_L = new AudioConnection_F32(i16_to_f32_L, 0, output_queue_L, 0);
patchCord101_R = new AudioConnection_F32(i16_to_f32_R, 0, output_queue_R, 0);
}

//define audio processing blocks.
AudioInputUSB usb_in; //from the original Teensy Audio Library, expects Int16 audio data
AudioConvert_I16toF32 i16_to_f32_L, i16_to_f32_R;
AudioRecordQueue_F32 output_queue_L,output_queue_R;

//define the audio connections
AudioConnection *patchCord100_L, *patchCord100_R;
AudioConnection_F32 *patchCord101_L, *patchCord101_R;

void update(void) {
//Serial.println("AudioSynthNoiseWhite_F32: update().");
output_queue_L.begin();
output_queue_R.begin();

//manually update audio blocks in the desired order
usb_in.update(); //the output should be routed directly via the AudioConnection
i16_to_f32_L.update(); // output is routed via the AudioConnection
i16_to_f32_R.update(); // output is routed via the AudioConnection
output_queue_L.update();
output_queue_R.update();
#include "input_usb_f32.h"
#include "output_usb_f32.h"

//handle the output for the left channel
audio_block_f32_t *block;
block = output_queue_L.getAudioBlock();
if (block == NULL) return;
AudioStream_F32::transmit(block,0);
output_queue_L.freeAudioBlock();
output_queue_L.end();

//handle the output for the left channel
block = output_queue_R.getAudioBlock();
if (block == NULL) return;
AudioStream_F32::transmit(block,1);
output_queue_R.freeAudioBlock();
output_queue_R.end();
}
private:

};

class AudioOutputUSB_F32 : public AudioStream_F32
{
//GUI: inputs:2, outputs:0 //this line used for automatic generation of GUI node
//GUI: shortName:usbAudioOut //this line used for automatic generation of GUI node
public:
AudioOutputUSB_F32() : AudioStream_F32(2, inputQueueArray_f32) {
makeConnections();
}

AudioOutputUSB_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) {
makeConnections();
}

void makeConnections(void) {
//make the audio connections
patchCord100_L = new AudioConnection_F32(queue_L, 0, f32_to_i16_L, 0); //noise is an Int16 audio object. So, convert it!
patchCord100_R = new AudioConnection_F32(queue_R, 0, f32_to_i16_R, 0); //noise is an Int16 audio object. So, convert it!
patchCord101_L = new AudioConnection(f32_to_i16_L, 0, usb_out, 0); //Int16 audio connection
patchCord101_R = new AudioConnection(f32_to_i16_R, 0, usb_out, 1); //Int16 audio connection
}

//define audio processing blocks.
AudioPlayQueue_F32 queue_L,queue_R;
AudioConvert_F32toI16 f32_to_i16_L, f32_to_i16_R;
AudioOutputUSB usb_out; //from the original Teensy Audio Library, expects Int16 audio data

//define the audio connections
AudioConnection_F32 *patchCord100_L, *patchCord100_R;
AudioConnection *patchCord101_L, *patchCord101_R;

void update(void) {
//Serial.println("AudioSynthNoiseWhite_F32: update().");
//queue_L.begin();
//queue_R.begin();

//is there audio waiting for us for the left channel?
audio_block_f32_t *block;
block = receiveReadOnly_f32(0);
if (!block) return; //if no audio, return now.

//there is some audio, so execute the processing chain for the left channel
queue_L.playAudioBlock(block);
AudioStream_F32::release(block);
queue_L.update();
f32_to_i16_L.update();

//see if there is a right channel
block = receiveReadOnly_f32(1);
if (block) {
//there is a right channel. process it now
queue_R.playAudioBlock(block);
AudioStream_F32::release(block);
queue_R.update();
f32_to_i16_R.update();
}

//whether or not there was right-channel audio, update the usb_out
usb_out.update();
return;
}
private:
audio_block_f32_t *inputQueueArray_f32[2];
};

#endif
#endif
162 changes: 162 additions & 0 deletions docs/MULTICHANNEL_USB_AUDIO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Multi-Channel USB Audio for Teensy 4.x

This extension adds multi-channel USB Audio Class 2.0 support with asynchronous feedback to OpenAudio_ArduinoLibrary, enabling full-speed (12 Mbit) and high-speed (480 Mbit) multi-channel audio streaming between a Teensy 4.x and a host computer.

## Features

- **Multi-channel**: Up to 16 channels (2/4/6/8/10/12/14/16), configurable via `USB Audio Channels` in the Tools menu
- **Multiple sample rates**: 44.1, 48, 96, or 192kHz operation
- **Multiple bit depths**: 16, 24, or 32 bit integer formats
- **Asynchronous feedback**: USB audio output (Teensy → Host) uses an isochronous feedback endpoint so the host adapts its sample rate to the Teensy's actual clock, preventing buffer under/overruns.
- **PI controller**: A proportional-integral controller drives the feedback value, using a ring-buffer-based fill-rate estimator (`LastCall`) for stable rate tracking.
- **AudioClass 2.0**: Complies with the USB Audio 2.0 specification for wide host compatibility.
- **Float32 native**: `audio_block_f32_t` input and output from USB I/O objects, for direct compatibility with OpenAudio library.

## Compatibility

| OS | USB Audio 2.0 Support |
|---|---|
| Windows | Native for Windows 10 and later versions (UAC2 built-in) |
| macOS | Native (class-compliant) |
| Linux | Native (UAC2 driver) |
| ChromeOS | Native |

## Installation

### Prerequisites

1. Teensyduino 1.62.0 installed via Arduino Boards Manager or the Teensyduino installer
2. OpenAudio_ArduinoLibrary in your Arduino `libraries/` folder

### Quick Setup

From the OpenAudio library root:

```powershell
.\scripts\setup.ps1
```

The script handles everything:

1. **Detect Teensyduino** — Finds your install under `%LOCALAPPDATA%\Arduino15\`
2. **Backup** — Saves originals to `backups/<version>/`
3. **Patch core** — Installs multi-channel `usb_desc.h`, `usb_desc.c`, `usb.c`, `usb_audio.*`, `usb_audio_interface.*`
4. **Config** — Adds `boards.local.txt` (for USB Audio Channels menu) and updates `platform.txt`
5. **Arduino IDE library** — Copies the library into `Documents\Arduino\libraries\`
6. **Cache** — Clears Arduino IDE 2.x cache

After running, **restart Arduino IDE**. Examples appear under **File → Examples → OpenAudio_ArduinoLibrary**.

### Manual Steps (if the script doesn't work)

1. **Copy patched core files** from `patched_teensy_core/` to your Teensy core directory:
```
%LOCALAPPDATA%\Arduino15\packages\teensy\hardware\avr\<version>\cores\teensy4\
```
2. **Copy config files** from `patched_teensy_core/config/`:
- `boards.local.txt` → `%LOCALAPPDATA%\Arduino15\packages\teensy\hardware\avr\<version>\`
- `BM-platform.txt` or `TD-platform.txt` → rename to `platform.txt` in same directory
3. **Link library** into Arduino's sketchbook libraries folder:
```
mkdir "%USERPROFILE%\Documents\Arduino\libraries" -Force
New-Item -ItemType Junction -Path "%USERPROFILE%\Documents\Arduino\libraries\OpenAudio_ArduinoLibrary" -Target "C:\path\to\OpenAudio_ArduinoLibrary"
```
4. **Restart Arduino IDE**

> **Note:** The library ships without a `library.properties`, matching upstream OpenAudio (which is installed by zip, not Library Manager).

## Usage

### Selecting USB Type

In Arduino IDE: **Tools → USB Type → "Audio"** or **"MIDI + Audio + Serial"**

If you installed `boards.local.txt`, under the **Tools** menu you will also see settings for **Audio block size, Audio sample rate, and USB Audio Channels**.

### Basic Sketch

```cpp
#include <Audio.h>
#include <OpenAudio_ArduinoLibrary.h>
#include "USB_Audio_F32.h"

// Both classes automatically expose the channel count configured in Tools > USB channels.
// Connect one AudioConnection_F32 per channel.
AudioInputUSB_F32 usb_in;
AudioOutputUSB_F32 usb_out;

// One connection per channel
AudioConnection_F32 patch0(usb_in, 0, usb_out, 0);
AudioConnection_F32 patch1(usb_in, 1, usb_out, 1);
// ... add more for channels 2-7 as needed

void setup() {
AudioMemory(100);
AudioMemory_F32(100);
}

void loop() {
// Audio processing runs in the background
delay(1);
}
```

Both classes automatically use the channel count configured in **Tools → USB channels** (the port arrays are sized at compile time). `AudioInputUSB_F32` transmits every received channel; `AudioOutputUSB_F32` accepts the same count of input ports. The compile-time constant `AudioInputUSB_F32::getNumChannels()` (or `AudioOutputUSB_F32::getNumChannels()`) returns that count, so sketches can auto-create loopback connections in a loop. See `USBAudioMultiChannel_F32` for a full 16-channel passthrough.

### Asynchronous vs Adaptive

By default, the output endpoint (Teensy → Host) runs in asynchronous mode. The Teensy sends a feedback value to the host, which adjusts its transmission rate accordingly. This eliminates the need for sample-rate conversion on the host side.

To switch to adaptive mode (simpler but may cause occasional glitches), comment out the `ASYNC_TX_ENDPOINT` define in `usb_desc.h`.

## Rates, Channels and Bit Depth Limits

USB bandwidth is finite. High speed (480 Mbit) can carry about **8192 bytes/ms**; full speed (12 Mbit) only **1023 bytes/ms**. The required bandwidth is roughly `sample_rate × channels × bytes_per_sample / 1000`.

Maximum bit depth that streams over **high speed**, per sample rate and channel count:

| Channels | 44.1kHz | 48kHz | 96kHz | 192kHz |
|---|:---:|:---:|:---:|:---:|
| 2 | 32-bit | 32-bit | 32-bit | 32-bit |
| 4 | 32-bit | 32-bit | 32-bit | 32-bit |
| 6 | 32-bit | 32-bit | 32-bit | 32-bit |
| 8 | 32-bit | 32-bit | 32-bit | 32-bit |
| 10 | 32-bit | 32-bit | 32-bit | 32-bit |
| 12 | 32-bit | 32-bit | 32-bit | 24-bit |
| 14 | 32-bit | 32-bit | 32-bit | 16-bit |
| 16 | 32-bit | 32-bit | 32-bit | 16-bit |

- If a combination exceeds the high-speed bandwidth, compilation fails with a missing `AUDIO_POLLING_INTERVAL_480` error — reduce channels or bit depth.
- On full speed, channels are automatically reduced to whatever fits (e.g. 16ch/48k/24-bit streams at 6ch over full speed). Use a high-speed link for the full channel count.
- At 176.4/192kHz with 24- or 32-bit samples, even 2 channels exceed full-speed bandwidth, so the full-speed descriptor falls back to 2 channels and streaming is only possible over a high-speed connection.
- When using more than 8 channels, the USB feature unit (volume/mute controls) is limited to the first 8 channels.


## Troubleshooting

### New menu options not appearing after setup
- After running the setup script, the Arduino IDE must fully load and finish "reloading boards" before new **Tools** menu options (e.g. **USB bit depth**) appear.
- On some systems the IDE takes **30+ seconds** to start up and reload boards. Let it finish loading — a notification such as "Reloading boards" will pop up in the corner — and only then check the **Tools** menu.
- If options still don't appear, close the IDE completely, re-run `.\scripts\update_teensy_audio.ps1` (which now replaces the menu block in `boards.txt` on every run), then restart the IDE and wait for it to fully load.

### Compilation errors about `USB_AUDIO_CHANNELS`
- The `boards.local.txt` may not be installed; the default falls back to 8 channels in `usb_desc.h`
- Check `usb_desc.h` for `USB_AUDIO_NO_CHANNELS_480` define

### Buffer underruns / glitches
- Increase `AudioMemory()` in your sketch
- Try adaptive mode (comment out `ASYNC_TX_ENDPOINT`)

### Restoring Original Files
```powershell
.\scripts\restore_teensy_audio.ps1
```

### Library examples not appearing in Arduino IDE

Arduino IDE looks for libraries under **File → Preferences → Sketchbook location** → `libraries/`. On Windows, OneDrive sometimes redirects `%USERPROFILE%\Documents` to a OneDrive path, causing the IDE to scan `C:\Users\<user>\OneDrive\Documents\Arduino\libraries\` instead of `C:\Users\<user>\Documents\Arduino\libraries\`.

**Fix:** In Arduino IDE, go to **File → Preferences** and check the **Sketchbook location** field. If it points to a OneDrive path, change it to `C:\Users\<user>\Documents\Arduino`.

Alternatively, copy or move `OpenAudio_ArduinoLibrary` into the OneDrive `libraries/` folder that the IDE is already scanning.

Loading