Custom Audio Source

Status
Not open for further replies.

jrubisch

New member
Hi everybody,

I've got a pretty simple question for seasoned C programmers, but not me :)

I've generated some C code from a PureData patch via https://enzienaudio.com/. I've got that running on my local machine with libsoundio, so that part is tested and verified.

I'd like to patch the output into I2S on Teensy. What part of the library should I look at for glue code to do that?


I'm looking at the i2s output

https://github.com/PaulStoffregen/Audio/blob/master/output_i2s.cpp

and some audio generator, like

https://github.com/PaulStoffregen/Audio/blob/master/synth_waveform.cpp


but I'm unsure as to how to make the connection.

Looking at the Arduino examples (e.g. https://github.com/PaulStoffregen/Audio/blob/master/examples/Synthesis/Waveforms/Waveforms.ino), I see that they are connected via AudioConnections, but how is that implemented?

In other words, what is the interface for the AudioStream (?) Is it just the
Code:
void AudioSynthWaveform::update(void)
method?



Thanks, guys!
 
First, before answering your question I'd like to state the overly obvious. If you haven't yet run the audio library on your hardware, please do that first! In addition to verifying your hardware is good, a little hands-on experience will go a long way when you're struggling to get the data side working.

With that in mind, the answer you seek is the queue objects. You need this one:

https://www.pjrc.com/teensy/gui/?info=AudioPlayQueue

This object lets your program send 128 sample buffers to the audio library. When the library gives you another buffer (the getBuffer() functions returns a non-NULL pointer), you just fill it up with 128 samples and then call playBuffer() to send it into the audio library.

To make your samples go to the I2S digital output, just connect the queue object to an I2S output. If you only need mono sound, connect its 1 output to both the inputs on the I2S object. If you need stereo sound, create 2 queue objects and connect each one to the I2S, and of course in your code get buffers from both and fill one's buffer with the left channel data and the other with the right channel data.

You can connect these queue objects together with other stuff in the library. For example, you could connect the queue to a mixer and combine your data with other data from other stuff in the audio library, and send that along to the I2S. Or you could send it to the DAC pins, or to USB so your PC can receive it, or anything else the audio library can do....
 
Thank you, that was very enlightening. I managed to plug in the sound generating code into two AudioQueues, and the output is _almost_ what I expected.

I've got one question remaining, though... maybe you could help. First, this is the loop code:

Code:
void loop() {
  // put your main code here, to run repeatedly:

  p1 = queue1.getBuffer();
  p2 = queue2.getBuffer();

  hv_process(context, NULL, outBuffers, FRAME_COUNT);

  for(int sample = 0; sample < FRAME_COUNT; sample++) {
    int16_t value1 = (int16_t) (outBuffers[0][sample] * pow(2,15));
    int16_t value2 = (int16_t) (outBuffers[1][sample] * pow(2,15));
    p1[sample] = value1;
    p2[sample] = value2;
    Serial.println(value1);
  }

  queue1.playBuffer();
  queue2.playBuffer();
  
}

Is it correct to place this in void loop() at all? Is the frequency at which loop() runs too slow? Or should it be in its own infinite loop in the setup() method? What would be the correct way to ensure everything is in sync.

With this setup, anyways, output stops and starts back again every loop, which is clearly not what I want.

Could you point me as to how I would correctly set this up?

Thanks again!
 
Thank you, that was very enlightening. I managed to plug in the sound generating code into two AudioQueues, and the output is _almost_ what I expected.

I've got one question remaining, though... maybe you could help. First, this is the loop code:

Code:
void loop() {
  // put your main code here, to run repeatedly:

  p1 = queue1.getBuffer();
  p2 = queue2.getBuffer();

  hv_process(context, NULL, outBuffers, FRAME_COUNT);

  for(int sample = 0; sample < FRAME_COUNT; sample++) {
    int16_t value1 = (int16_t) (outBuffers[0][sample] * pow(2,15));
    int16_t value2 = (int16_t) (outBuffers[1][sample] * pow(2,15));
    p1[sample] = value1;
    p2[sample] = value2;
    Serial.println(value1);
  }

  queue1.playBuffer();
  queue2.playBuffer();
  
}

Is it correct to place this in void loop() at all? Is the frequency at which loop() runs too slow? Or should it be in its own infinite loop in the setup() method? What would be the correct way to ensure everything is in sync.

With this setup, anyways, output stops and starts back again every loop, which is clearly not what I want.

Could you point me as to how I would correctly set this up?

Thanks again!
While the compiler typically will optimize pow (2, 15) to 32768.0, it is probably better to use (1<<15), 0x8000, or just 32768. The result of pow is double precision. This will force the multiply to be done in double precision mode. On all Teensys, double precision is emulated, so a multiply will be several hundreds if not thousands of instructions. If the outBuffers type is an integer type or float, this will be a big slowdown. If you specify the value 32768 as an integer, the compiler won't need to convert the expression to double precision, and will do either integer or single precision floating point multiply. The integer multiply is generally 1 cycle on the Teensy, and if you are on a Teensy 3.5/3.6, the single precision multiply is done in hardware is fast.
 
While the compiler typically will optimize pow (2, 15) to 32768.0, it is probably better to use (1<<15), 0x8000, or just 32768. The result of pow is double precision. This will force the multiply to be done in double precision mode. On all Teensys, double precision is emulated, so a multiply will be several hundreds if not thousands of instructions. If the outBuffers type is an integer type or float, this will be a big slowdown. If you specify the value 32768 as an integer, the compiler won't need to convert the expression to double precision, and will do either integer or single precision floating point multiply. The integer multiply is generally 1 cycle on the Teensy, and if you are on a Teensy 3.5/3.6, the single precision multiply is done in hardware is fast.

Thank you, again very helpful.

So the most probable cause of all this is that the code generating the audio isn't optimized for the platform and cannot provide audio fast enough. I've tried feeding in random numbers which causes no lags at all. I will try a more simple PD patch (which is the audio source, so to speak)
 
Status
Not open for further replies.
Back
Top