USB Audio Mixing

InvalidSyntax

New member
Hello, I'd like to preface this by saying that I've read through the Wiki and the Audio Library documentation beforehand. Despite completing some basic projects, I remain a beginner when it comes to Arduino and audio electronics.

My current project is fairly straightforward. Using a USB-connection, I'd like to be able to play audio from my PC directly into my Teensy 3.2 and output it using the built-in DAC. This much I have done, and the Teensy generated the proper signal when viewed on an oscilloscope. However, my goal is to mix the audio with a number of sine waves (for the purpose of distortion) before it is converted to an Analog waveform.

I'm aware of the "Mixer" feature of the Audio Library, but it is limited to only 4 inputs per mixing "block" and I'll likely need more sine waves than that. Adding more mixing blocks seems tedious. The alternative is to somehow generate those sine waves in code (and perhaps make the mixing happen digitally in the Audio Buffer before outputting with the DAC).

Am I approaching this problem correctly? Which of these options seem more feasible, and for the latter option (hard-coding the sine wave generation and mixing), would it be possible to provide an outline of what this would entail? Code examples would be much appreciated.

Thank you for your time and consideration.
 
Adding more mixing blocks seems tedious.

A click and drag in the audio design tool and it writes the program for you.

How many sine waves do you need? With 4 mixers fed into one mixer you would have your USB audio plus 15 sine waves all mixed together.
 
Hello, thank you for your quick response.

For the most part, you are correct - I should be able to make do with the Teensy Design Tool. However, in the case that I want to dynamically add more/different sine waves, or perhaps want to have more control over the audio samples, I would like to ask specifically about the hard-coded version:

How feasible would it be to create such a program (something that, given a set of sine wave amplitudes and frequencies, generates and mixes the sine waves digitally, and then adds this to the USB audio in real time)? Should I avoid trying to alter/mix the bytes of the USB audio altogether? Would the discrepancy between CPU calculations and the incoming audio cause a significant delay?

My goal with these questions is to get an idea of what I can and cannot do with the Teensy with regards to this specific project, so any advice is good advice.
 
How feasible would it be to create such a program (something that, given a set of sine wave amplitudes and frequencies, generates and mixes the sine waves digitally, and then adds this to the USB audio in real time)? Should I avoid trying to alter/mix the bytes of the USB audio altogether? Would the discrepancy between CPU calculations and the incoming audio cause a significant delay?

My goal with these questions is to get an idea of what I can and cannot do with the Teensy with regards to this specific project, so any advice is good advice.

@InvalidSyntax:

I can't speak specifically to mixing USB audio with waveforms, but I can tell you that in my TeensyMIDIPolySynth project (which used physical pots read thru analog pin inputs to adjust many different settings, & its current successor, which uses a 7" RA8875 touchscreen for all of the adjustment controls on-screen as "virtual digital pots") mixes 3-voices, each of which can be any combination of 4 VFO waveforms (sine, square, triangle, and saw), each of which can be modulated by any mixture of 6 LFO waveforms (sine, square, pulse, triangle, saw, and sample/hold), all of which can be envelope filtered, which can also be modified by any mixture of another 6 LFO waveforms (same list of types as before). It does all of this multiplied by 12 to implement a 12-poly synth, which can output both audio (headphone and/or line out) and USB audio (for use with my Win11 PC). It also does MIDI in/out, but that is not pertinent to your project.

I know that description included lots of words, but what you can take away from that verbosity is that you should easily be able to define your sine waves (as VFOs), you should easily be able to control both their frequency & amplitude in real-time, and you should easily be able to mix all of this with your USB audio input, & put the resulting audio mix back out, either as audible (headphone jack or line out), or via USB audio. To use the capabilities provided by the audio library, use of the audio adapter is probably easiest.

All of these capabilities are supported very nicely by the audio design tool (for the mixing, routing, and connectivity). With the addition of some creative & versatile code (that you write), you should easily be able to take user inputs (for the settings) & modify the parameters of your VFOs & audio source to create the exact mix needed for your output.

I would suggest starting as follows (maybe even playing with the audio tutorial first to get a feel for the basics):

1) just take in USB audio & put it right back out (no modifications) to get the basic audio routing working
2) run your USB audio thru a mixer (with only your USB audio in the mix on one input, all by itself) to exercise the ability to raise & lower the amplitude of your input audio
3) add a single (fixed frequency) sine wave VFO to one of the other inputs on the mixer, to exercise the ability to raise & lower the amplitude of each of the inputs (your audio & your sine wave)
4) add the ability to modify the frequency of your one sine wave VFO
5) add the rest of your sine wave generators + sufficient mixers (cascaded as needed) to allow everything to be independently amplitude controlled
6) play till your heart's content . . .

Hope this helps to answer your questions. Feel free to ask more if/as needed.

Mark J Culross
KD5RXT
 
@kd5rxt-mark:

(I am the same as the original poster, just misplaced my original account info.)
Thanks for the help, I'll follow your advice and implement the mentioned tests (primarily 5 and 6 as I've tried the rest) to suit my project's needs.

On the subject of your TeensyMIDIPolySynth project, you mention that you were able to output audio through headphones (as in, using a female audio jack). I'm also aware that the audio adapter does this easily. However, I'd like to try and hard-wire a connection from the Teensy's DAC pin to a 3.55 female audio jack with pins for Left, Right, and Ground. For this, I'm assuming that I'd need to amplify the signal from the DAC before connecting it to the audio jack. Would I need to conduct my own tests to see what kind of amplification I need, or is there available info on this subject? In addition, do you have any tips to get the best quality sound when using this approach?

Note: The previous question does not address the issue of mono/stereo audio. Assume that at the moment that I'm fine with only dealing with mono sound, although if you have anything to add on this subject (how to deconstruct the left/right audio from the DAC, etc.) I'd be happy to hear it.
 
@kd5rxt-mark:

(I am the same as the original poster, just misplaced my original account info.)
Thanks for the help, I'll follow your advice and implement the mentioned tests (primarily 5 and 6 as I've tried the rest) to suit my project's needs.

On the subject of your TeensyMIDIPolySynth project, you mention that you were able to output audio through headphones (as in, using a female audio jack).

@InvalidSyntax(2):

In my project, I was using the Audio Adapter, which has the headphone jack right on the board. Sorry, I don't have any experience with making use of signals directly from DACs.

Mark J Culross
KD5RXT
 
The DAC output on Teensy 3.2 can usually drive the "line in" inputs on computer speakers and consumer stereo systems. But it is not strong enough (low impedance) to directly drive headphones or speakers (typically 33 or 8 ohms). For that you would need to add a small amplifier.

With only 1 single DAC pin, the only feasible stereo connection would be to send the same analog signal to both left and right inputs on whatever equipment will receive the signal. How well this works with a direct connection, or connected with only a capacitor to block the DC offset, depends on the input impedance of whatever device you're connecting to receive the signal. For example, if using a consumer stereo system with 50K input impedance, diving both left and right inputs with the same DAC signal would be a 25K ohm load. The DAC pin can drive that pretty well.

Hopefully from this explanation you can understand there is not single simple answer involving only the Teensy side. How well direct DAC pin connection works depends on the nature (specifically input impedance) of whatever you connect.
 
Hello, I am following up on this thread with a problem I'm having as I run a number of tests. I chose to post this question here because it is directly related to my project.

The test in question is fairly straightforward: I use the Teensy 3.2's mixer to add a sine wave .WAV file being played on my laptop, to a sine wave being generated by the Teensy. The sine waves both have a frequency of 1 kHz but the generated wave has a 180 degree phase shift. Ideally, these two waves would then cancel out, leaving only a bit of noise when measured. However, the result is instead a sine wave that oscillates in terms of peak-to-peak voltage.

The image below is a snapshot of this peak changing over time. This effect is more pronounced if I change the amplitudes and such, but regardless the expected cancellation does not occur.

oscilloscope.jpg

What I've tried: I have tried with and without a DC-blocking capacitor. I experimented with the high-res sine waves, in addition to a number of different frequencies. It seems that the sine waves only cancel when both are generated by the Teensy, otherwise this odd wave is produced. The sine wave I generated was using Audacity, but I've also attempted to use other tone generators to no avail. I'm quite stumped, as the mixing is happening digitally and the resulting frequency shouldn't vary so much that it creates this "new" waveform. My next step would be to attempt using a look-up table, but I'm not familiar with Teensy enough to create a mixer for the LUT and the incoming USB audio.

I'll be including my code for this in a separate reply. Feel free to offer advice/guidance relating to my code, as well as suggestions on how to resolve this problem.
 
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputUSB            usb1;           //xy=285.00000381469727,266.00000381469727
AudioSynthWaveformSine   sine1;          //xy=285.00000381469727,317.0000047683716
AudioSynthWaveformSine   sine2;          //xy=294,369
AudioAnalyzeRMS   rms1;          //xy=720,201
AudioMixer4              mixer1;         //xy=580,292
AudioOutputAnalog        dac1;           //xy=851,261
AudioConnection          patchCord1(usb1, 0, mixer1, 0);
AudioConnection          patchCord2(usb1, 0, rms1, 0);
AudioConnection          patchCord5(usb1, 1, mixer1, 1);
AudioConnection          patchCord6(sine1, 0, mixer1, 2);
AudioConnection          patchCord7(sine2, 0, mixer1, 3);
AudioConnection          patchCord8(mixer1, dac1);
// GUItool: end automatically generated code

float vol;
void setup() {
  AudioMemory(12);
  Serial.begin(9600);
  analogReference(EXTERNAL);
  vol = usb1.volume();
  sine1.amplitude(0.2);
  sine1.frequency(1000);
  
  sine2.amplitude(0.2);
  sine2.frequency(1000);

  //Mixer begins "off"
  mixer1.gain(0, 0);
  mixer1.gain(1, 0);
  mixer1.gain(2, 0);
  mixer1.gain(3, 0);
  
  while(Serial.available()) {}
  Serial.println("setup done");
}

float x; //Temporary variable to read RMS
int s = 0; //STATUS Variable, 1 means the audio from the PC is playing, 0 is not
void loop() {
   x = rms1.read();
  if(x != 0.0 && s == 0) { //If a change is detected, toggle the mixer
    sine2.phase(180);
    Serial.println("Changed.");
    x = 0.0;
    s = 1;
  }
  if(s == 1) {  
    vol = usb1.volume();
    sine2.amplitude(vol);
      mixer1.gain(0, 0.5);
      mixer1.gain(1, 0);
      mixer1.gain(3, 0.5);

  } else {  //If the USB audio is not being played, just output a sine wave
      mixer1.gain(0, 0);
      mixer1.gain(2, 0);
      mixer1.gain(3, 1);
  }
}

Several things to note: the amplitude of the generated sine wave is set to 0.2 because that's the amplitude of the sine wave I'm playing as USB Audio. If you run this on your own and use a different sine wave, change the amplitude and frequency accordingly. In addition, I currently use the RMS analyze feature to detect when the USB audio begins playing, but if you have any suggestions on how to better detect the PC's music being played or paused, please let me know.
 
You are reading the RMS result thousands of times a second even when it is not available. Try changing your loop function to this:
Code:
void loop()
{
  if(rms1.available()) {
    x = rms1.read();
    if(x != 0.0 && s == 0) { //If a change is detected, toggle the mixer
      sine2.phase(180);
      Serial.println("Changed.");
      x = 0.0;
      s = 1;
    }
    if(s == 1) {
      vol = usb1.volume();
      sine2.amplitude(vol);
      mixer1.gain(0, 0.5);
      mixer1.gain(1, 0);
      mixer1.gain(3, 0.5);

    } else {  //If the USB audio is not being played, just output a sine wave
      mixer1.gain(0, 0);
      mixer1.gain(2, 0);
      mixer1.gain(3, 1);
    }
  }
}

Pete
 
A few points:
  • you never set s back to 0, so you will only ever trigger/start the cancellation signal once: I assume that's not your intent...
  • audio updates, and hence new values for rms1.available() and rms1.read(), only occur every 128 samples / 2.9ms, which is too infrequent to get the Teensy sine wave in antiphase with the USB one
  • I can't see any particular guarantee that the PC and Teensy sine waves will be an exact 1kHz, so even if you do succeed in getting cancellation, it's likely to drift over time into reinforcement, then back to cancellation
I'd suggest you look into using the AudioRecordQueue object to do a sample-accurate analysis of the incoming USB: you'll get a couple of peaks and troughs of a 1kHz signal per audio block at 128 samples/block. Simply picking the timing of the peaks should get moderately good cancellation (within ±0.5 samples), for truly accurate cancellation you'd need to do more maths than I'm capable of right now!

With only one DAC output debugging using your scope is tricky - you could switch between outputting (a) a copy of the incoming USB, (b) the Teensy sine wave, and (c) the difference signal, every time an audio update occurs (easy with either rms1.available() or queue1.available() in your loop() code).
 
Hi again.

First of all, I'm aware that s is not set back to zero, this is only a temporary measure so I can focus on the main sine wave mixing tests. Secondly, while I see the merit of trying the AudioRecordQueue object, I can't help but get the feeling it is excessive for sine wave cancellation. I definitely see the applications of that towards a future noise cancellation project, but I have two alternatives in mind that I would appreciate feedback on:

1. I was thinking that I use a look-up table (like the one found here: https://forum.pjrc.com/threads/28403-Teensy-3-1-DAC-output-freq-limits) and analogWrite to handle a precise sine wave instead of the Teensy's built-in sine generator. Then, I could specifically ensure that the USB sine wave is created using that same LUT. This should theoretically cancel much better and not depend on variations in frequency. The issue here is that I am unsure how to connect the USB audio to the DAC output using analogWrite instead of the patchConnection offered by the Audio Library.

2. Based on the teensy's sine generator found here: https://github.com/PaulStoffregen/Audio/blob/master/synth_sine.cpp, I am also considering trying to ensure the USB Audio's sine wave is generated in a similar fashion. This, like step one, should let the cancellation happen without error, but avoids the use of a LUT.

Although I hoped the built-in sine wave generator and any sine wave being played as a .WAV could cancel well, I'm fine with changing the way the .WAV file was created, which is why I can propose these solutions. Are they viable solutions, and if so, do you know how I could approach them?
 
Back
Top