Audio Library

Status
Not open for further replies.
OK I'm bad :) Works much better now, had been trying to get lo_lvl_right to work and not seeing any change, that works as well as the filter now.

I pushed the fix to github and have tested it myself now, much better - definitely hearing the difference, still haven't taken it to the audio sweep equipment at work yet tho.

I tried updating the AudioFilterBiquad coefficients without resetting the other elements to zero and I thought I perceived less artefacts introduced, you can try it by adding ',true' to the call as in
Code:
BassFilter.updateCoefs(updateFilter,true);
if you would, please.
 
I pushed the fix to github and have tested it myself now, much better - definitely hearing the difference, still haven't taken it to the audio sweep equipment at work yet tho.

I tried updating the AudioFilterBiquad coefficients without resetting the other elements to zero and I thought I perceived less artefacts introduced, you can try it by adding ',true' to the call as in
Code:
BassFilter.updateCoefs(updateFilter,true);
if you would, please.


I don't hear any now.
 
Follow-up on AudioInputAnalog issues

I've attributed one issue to the DC offsetting done in Audio.cpp, inside of AudioInputAnalog::update (line 1120). One of my tests was to blow on the microphone, knowing well what that should look like on the output data. This code would create a large (2000+ 16bit counts) error in the DC offset likely related to clipping from blowing on the mic.

Code:
        // find and subtract DC offset....
	// TODO: this may not be correct, needs testing with more types of signals
	dc = dc_average;
	p = out_left->data;
	end = p + AUDIO_BLOCK_SAMPLES;
	do {
		s = (uint16_t)(*p) - dc; // TODO: should be saturating subtract
		*p++ = s;
		dc += s >> 13; // approx 5.38 Hz high pass filter
	} while (p < end);
	dc_average = dc;

After setting a static DC offset by hand, looking at the buffer made it clear what the other issue was. The data is nearly correct now, but some kind of settling/ramp up or lower bit error is happening between blocks of data coming back from the ADC over DMA. See attached image. I've tried other AUDIO_BLOCK_SAMPLES sizes (in AudioStream.h) and get the same settling/ramp up at the start of each block. The settling does not happen when doing charting a series of analogRead(). My next step is to hook up an oscope and check for anything that could explain this.

Printing this data is done in Audio.cpp after copy_to_fft_buffer(buffer+256, block->data); in void AudioAnalyzeFFT256::update(void):
Code:
	for (int i = 0; i < 512; i = i + 2){ // input data has imaginary numbers in the odd bins
		Serial.println(buffer[i]);
	}

Pete: I looked over PlayMidiTones example and tried a bunch of things, but I don't think the commented out sections apply anymore. "'class AudioInputAnalog' has no member named 'connect'"
 

Attachments

  • Buffer Settling Oddness.png
    Buffer Settling Oddness.png
    15.4 KB · Views: 190
I don't hear any now.

Excellent, pending no discovery of why not to I will change the routine so that the 'natural' behaviour is to leave the other three elements un-touched when updating the coefficients. I'll make the ',true' flag reset the other three elements of the filter parameter array to zero instead.

I intend to test everything else I've added by writing (or, where possible, modifying existing) examples for the majority, I expect I will have the most done by the end of the coming weekend - work switched codecs to a Texas Instruments one so catering to that on their time instead slows my progress on the SGTL5000 atm.



@Paul: Will you require examples covering every addition I propose before you accept my pull request? if I rename flterCalc(..) to calcBiquad(..) and add it publicly to the library can that be acceptable? Is there anything else I need to do before you can accept the pull request?
 
I'm ordering one of these audio shields next month, and a Teensy3.1 to dedicate to using it. I've been looking for something nice to handle audio processing on W.A.L.T.E.R. 2.0. :)

8-Dale
 
just received the audio shield.
i tried these examples successfully:
PassThrough
PlayWavFromSdCard
FFT

i am now wondering how i could record something to the sd card and then play it back, in the same sketch?
do you have such an example handy?

thanks.
 
While playing with the audio shield using various effects I've noticed that the first time I output audio to line output [*] after the shield has been powered up, it starts at very low volume and takes about 4 seconds to reach full power. Any [edit] further [/edit] audio is at its correct output level.
Anyone else noticed that? No doubt I'm forgetting to set something properly.

This mp3 demonstrates: http://members.shaw.ca/el.supremo/sw...load_16000.mp3
I played an audio frequency sweep with the shield and recorded the line output using GoldWave on Win 7. The sweep actually goes from 10 to 22kHz but the mp3 compression cuts it off just above 18kHz. The first half of the file was recorded soon after the Teensy and audio shield had been powered up. The second half was recorded a minute or so later. Even if you can't view this with a spectrum analyzer or with Goldwave, you can still hear that the first few seconds of the first part are noticeably quieter than the second part.


Pete
[* edit] This also occurs on the headphone output
 
Last edited:
OK, swept at last. The filters look good, the SGTL5000 applies all 7 biquad filters to both channels and the AudioFilterBiquad is only being applied to the right channel (or was it left channel and I accidentally swapped sides :eek:) - both look as they ought on the sweeper when it tests the right channel.

There is a little 'wobble' in the gain-intensity between ~4Khz and ~20KHz but the TWR-AUDIO-SGTL I was using had the same 'feature' when I swept it, it is there with no filters in play as well. It is not easy to perceive using human hearing anyway. The sweeper is using 96KS/s and sweeps to 48KHz, the SGTL5000 has a low pass @~20KHz but it doesn't attenuate well above ~23KHz but only a pathological audiophile with the hearing of a child might care.

The attached sweep is line-in thru I2S to DAP to line-out, right channel only. I changed line out level (CHIP_LINE_OUT_VOL) to 0x0808 for this sweep to make the parametric EQ max gain drive output to max out only just above 0dB but the value Paul sets (0x0505) in his .init() makes the output level closer to the input level so it is a better value to use for most purposes.

The green line is with the 'tone' pot in the middle, purple and red lines are 'tone' pot at extremities.
 

Attachments

  • teensy-sweep.JPG
    teensy-sweep.JPG
    91.3 KB · Views: 196
Last edited:
Hi!
I've started playing with teensy 3.1 and this great audio library. First of all, thank you for providing such a great hardware and software.
I have got a couple of questions...
- As somebody else mentioned earlier in this thread, AudioSynthWaveform does not work very well at high frequencies. Has anybody found a solution for this matter? Btw, I changed the constructor of the AudioSynthWaveform to accept an initial non-zero phase (e.g., for having a cosine). If someone else is interested, I can post the code.

- About the CPU usage. Reading through the code I got how all the update() thing works. Basically, all the chain processing on the 128 samples has to be done in 2.9ms (i.e., the playing time of 128 samples in 44.1KHz, that is about 278kcycles at 96MHz.). Am I right? I tried to use AudioProcessorUsageMax() in the loop(), but printed values are higher than 100. What does that mean? Am I out of the 2.9ms?

Thanks,
Enrico
 
Hi!
- As somebody else mentioned earlier in this thread, AudioSynthWaveform does not work very well at high frequencies. Has anybody found a solution for this matter?

It's an aliasing problem. The waveforms in the wavetables will be calculated to contain a certain number of partials. If your highest partial is greater than the nyquist frequency (Half the sample rate) you will hear aliasing, so as you increase the frequency, the partials move upwards and you will hear them come back down as aliased partials. You can't simple calculate the wave with fewer partials because it will sound dull and less resemble the waveform you are trying to achieve. This is a fundamental part of digital and additive synthesis theory and it's great fun and a challenge to work around this problem and come up with your own unique code.

The best practical tutorial out there for my money is this: http://www.earlevel.com/main/2012/05/04/a-wavetable-oscillator—part-1/

I personally think it would be a shame if the audio library did this all for you, as anyone who really needs to get rid of aliasing artifacts in their project could benefit from learning about it!
 
I have written my own tone generator using the ARM sin function which is also table based. It uses cubic interpolation which takes longer to calculate but gives a much better result.
The output of the generator, according to the spectrum display in GoldWave, is clean down to at least -80dB.
I have also implemented the square wave and sawtooth and am working on the triangle. I'll try to package it as a pull request for Paul to evaluate.

Pete
 
@Paul,
Don't know if this fits here or in Tech Support.

While writing the code to synthesize the sine wave, etc. I found that the square wave that is shown by GoldWave is anything but square. I do not have a scope so I can't check this out for at least a week when I can probably borrow one.
The problem also occurs with the Audio library's AudioSynthWaveform so I have played a 60Hz square wave into the PC and recorded it using Goldwave using this sketch:

Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>

AudioSynthWaveform mysine(AudioWaveformSquare);

AudioOutputI2S dac;

AudioControlSGTL5000 codec;

AudioConnection c1(mysine, 0, dac, 0);
AudioConnection c2(mysine, 0, dac, 1);

int volume = 0;

void setup() {
  Serial.begin(115200);
  while (!Serial) ;
  delay(2000);

  AudioMemory(4);

  codec.enable();
  codec.volume(40);
  // I want output on the line out too
  codec.unmuteLineout();

  delay(200);
  Serial.println("Begin AudioTest");
  delay(50);

  mysine.frequency(60);
  mysine.amplitude(.5);
  delay(1000);

  Serial.println("setup done");
  AudioProcessorUsageMaxReset();
  AudioMemoryUsageMaxReset();  
}

unsigned long last_time = millis();
void loop() {
  if(1) {
    if(millis() - last_time >= 2000) {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
      last_time = millis();
    }
  }

  int n;
  n = analogRead(15);
  if (n != volume) {
    volume = n;
    codec.volume((float)n / 10.23);
  }

}

This image shows the waveform as recorded by Goldwave:
PlaySynthSquare_waveform.gif


It obviously isn't square and a 100Hz signal has the same general shape although perhaps not quite as bad. It could also be the audio card in my PC but is this to be expected with low frequency waveforms?

Pete
 
@saxen
AudioSynthWaveform does not work very well at high frequencies.
This is the spectrum of a 5kHz signal from AudioSynthWaveform:
PlaySynthSine_5000_spectrum.gif



and this is the spectrum of a 5kHz signal from my code which uses the arm sine function.
my_sine_5000_1.gif


The cubic interpolation used by the arm sine function will produce better results than linear interpolation but it takes a bit more cpu time. AudioSynthWaveform uses up to 2% whereas my arm sine version uses 6%.

I tried to use AudioProcessorUsageMax() in the loop(), but printed values are higher than 100. What does that mean? Am I out of the 2.9ms?

Once the processor usage exceeds 100 you won't be able to produce output buffers fast enough and the missing buffers will be replaced with zeros which will distort the output audio.

Pete
 
Just another quick check-in here, and yet another apology for neglecting the audio library over the last several days.

Really, I *really* want to be working on the audio library right now. At the moment I'm still wrapping up the Teensyduino 1.18 release. PJRC is also going to start making a OctoWS2811 board to help make big LED projects easier. Yesterday I spent the whole afternoon rewiring my 1920 LED test board to do a final test of that hardware before we order a batch of PCBs. The good news is the test was successful and those PCBs were ordered, so at least that project isn't demanding attention right now.

Yeah, yeah, excuses, excuses.....

I really am going to get back to the audio library soon. I'm sorry for the delay.
 
I suspect the Octows2811 board will be helpful to the big led setups.

It would be nice if there was a one stop shopping kit for those of us that do neopixels on a smaller scale, but has all of the recommended practices in place (setups for doing 5/3.3v power from batteries, appropriate capacitors, resistor on the data line, voltage regulator for the pixels, 74hc245 or similar for coming from 3.3v boards, heat shield if needed for the voltage regulator).
 
AudioInputAnalog (Built-in ADC) working with FFT

Looks like the "approx 5.38 Hz high pass filter" in AudioInputAnalog was the second issue. Just installed 1.18 and here is what is required to get a nice FFT working from AudioInputAnalog (built-in ADC).

Audio.cpp changes:
Code:
line 7:
static arm_cfft_radix4_instance_f32 fft_inst;

line 12:
arm_cfft_radix4_init_f32(&fft_inst, 256, 0, 1);

under line 89 add: 
	float32_t FloatBuffer[512];
	for (uint32_t i = 0; i < 512; i++) FloatBuffer[i] = float32_t(buffer[i]); 

line 91:
	arm_cfft_radix4_f32(&fft_inst, FloatBuffer);

line 95:
	arm_cmplx_mag_f32(FloatBuffer, FloatBuffer, 128);

line 98:
	output[i] = int32_t(FloatBuffer[i]);

line 102:
	output[i] += int32_t(FloatBuffer[i]);

line 960:
	#define PDB_PERIOD 72e3 // 1087 does nothing

line 981:
	//analogReference(INTERNAL); // range 0 to 1.2 volts
	analogReference(DEFAULT); // range 0 to 3.3 volts

line 983:
	analogReadAveraging(1);

line 985:
	//for (i=0; i < 1024; i++) {
	//	sum += analogRead(pin);
	//}
	analogRead(pin);  // sets up mux
	dc_average = 32810; // this should be public

line 1131:
	//dc += s >> 13; // approx 5.38 Hz high pass filter

line 1133:
	//dc_average = dc;

The floating point FFT (f32) produces a much nicer output vs fixed point (q15). Didn't think it would be that much different given the input data is fixed point. Maybe something else is wrong I didn't catch when trying q15.

Teensyduino code:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>

AudioInputAnalog analogPinInput(A9); // analog A9 (pin 23)
AudioAnalyzeFFT256 myFFT(1);
AudioConnection c1(analogPinInput, 0, myFFT, 0);

void setup() {
  AudioMemory(12);
}

void loop() {
  if (myFFT.available()) {
    for (int i=0; i<128; i++) {
      Serial.print(myFFT.output[i]);
      Serial.print(" ");
    }
    Serial.println("");
  }
}

Processing Code:
Code:
import processing.serial.*;
import processing.opengl.*;

Serial myPort;

int MaxValue = 20000;
int fftsize = 128;
boolean dataready = false;
String[] fftarray = new String[128];
byte xPos = 0;

public void setup() {
  size(128, 980, OPENGL); //P2D);  
  background(0);
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');
}

public void draw() {
  if (dataready) {
    dataready = false;
    background(0);
    stroke(255);
    for (int i = 0; i < fftsize; i++) {
      float y0 = 0;
      if (i != 0) y0 = height - map(float(fftarray[i-1]), 0, MaxValue, 0, height);
      float y1 = height - map(float(fftarray[i]), 0, MaxValue, 0, height); 
      if (i != 0) line(i-1, y0, 0, i, y1, 0);
    }
  }
}

void serialEvent (Serial myPort) {
  String inString = myPort.readStringUntil('\n');
  if (inString != null) {
    fftarray = inString.split("\\s+");
    dataready = true;
  }
}

Still an issue is the sampling rate. I found 72e3 is the fastest (FFT update rate) somewhat sane value for PDB_PERIOD, but the FFT tops out at around 3700Hz (iirc), meaning the actual sampling rate is double that. Something is up because this should be quite a bit faster than 22500Hz (48e6).
 
Before trying to nail it down myself, I was wondering if anyone else has a problem with periodical (every few minutes) bursts of distortion through their audio shield? It sounds as if the audio processing is being interrupted to deal with something else and the sound crackles, just for about 1 second.

I've tried to reproduce the issue, but it doesn't seem to be in response to any specific code or user interaction. It usually happens within 10 seconds of the sketch being uploaded.

Any ideas?

Thanks
 
Finally my audio board has arrived! (Thanks Pieter!)
I just finished soldering all together, and tested the HP output with a modified version of PlaySineMikroe. Actually I didn't found anything among the example sketches with wich I can test the HP output without a microsd, even it's simple like a stick, i share it, maybe it will be handy for somebody

Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>

AudioControlSGTL5000 codec;
AudioSynthWaveform mysine(AudioWaveformSine);
AudioOutputI2S dac;

AudioConnection c1(mysine, 0, dac, 0);
AudioConnection c2(mysine, 0, dac, 1);

int volume = 0;

void setup() {
  codec.enable();
  AudioMemory(15);
  mysine.frequency(440);
  mysine.amplitude(0.9);

  codec.volume(70);

  Serial.println("setup done");
}

void loop() {
  int n;
  n = analogRead(15);
  Serial.println(n);
  if (n != volume) {
    volume = n;
    codec.volume((float)n / 10.23);
  }
}

Some initial toughts:
I have the 25k "volume" pot on the audio board, but even in the lowest setting, the output of mysine is audible. Pic1
If I begin increasing the volume, the next step introduces quite a big jump in volume (>20dB). The led on the T3.1 flickers when going frpom 0->1. Is this normal, that with the volume set to 0 i still have the output of mysine?

Also, as you can see on the pictures, I experience a constant, high pitched background noise, I guess this has to do something with ground problems, or the power source(laptop usb output)? On pic 2, i added a sh*tload of gain to the signal with the volume turned all the way down. Here is an audio sample about this.

Any advices?

@mattomatto: I don't experience the periodical bursts of distortion when running the sketch above (althrough as You see my situation is a bit worse :) )
 

Attachments

  • pic1.jpg
    pic1.jpg
    53 KB · Views: 195
  • pic2.jpg
    pic2.jpg
    53.7 KB · Views: 160
  • pic3.jpg
    pic3.jpg
    57 KB · Views: 204
Last edited:
I have managed a bit of time toward my fork of the Audio Library, fixed some things were amiss, and added examples for headphone & DAC balance. I've a filtering example I just need to test a bit more which I expect to add later. I will start on the AVC stuff shortly too. https://github.com/robsoles/Audio


@mattomatto: I have mostly been experimenting with 'LINE_IN->I2S_OUT->MCU->I2S_IN->DAP->DAC->LINE_OUT+HP' and listening carefully to adjustments and filters I am testing with no sign of a 'distortion burst' at any point so maybe you are playing from SD or something which I haven't even looked at yet - can you post an example sketch which seems to suffer the problem the most? (Not promising to look myself but others would and I might even :D)
 
...
Some initial toughts:
I have the 25k "volume" pot on the audio board, but even in the lowest setting, the output of mysine is audible. Pic1
If I begin increasing the volume, the next step introduces quite a big jump in volume (>20dB). The led on the T3.1 flickers when going frpom 0->1. Is this normal, that with the volume set to 0 i still have the output of mysine?

Also, as you can see on the pictures, I experience a constant, high pitched background noise, I guess this has to do something with ground problems, or the power source(laptop usb output)? On pic 2, i added a sh*tload of gain to the signal with the volume turned all the way down. Here is an audio sample about this.

Any advices?

...

The 'AudioControlSGTL5000::volume(..)' function in (Paul's original and not modified in my fork of) the Audio Library mutes the headphone output in the SGTL5000 itself when codec.volume(0) is applied so your result suggests that it 'leaks' a little when muted anyway. I will not suggest it is 'normal' but it may be a weakness of the SGTL5000 that cannot (easily) be overcome.

If you take a copy of my fork and try it with 'AudioControlSGTL5000:dac_vol(..)' you can see if muting it at the DAC instead is any more effective at absolute mute or not, I am pretty sure of the register fiddling I do there but I have not tested for 'leak/bleed' when muting either or both channels.
Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>

AudioControlSGTL5000 codec;
AudioSynthWaveform mysine(AudioWaveformSine);
AudioOutputI2S dac;

AudioConnection c1(mysine, 0, dac, 0);
AudioConnection c2(mysine, 0, dac, 1);

int volume = 0;

void setup() {
  codec.enable();
  AudioMemory(15);
  mysine.frequency(440);
  mysine.amplitude(0.9);

  codec.volume(90);

  Serial.println("setup done");
}

void loop() {
  float n = analogRead(15)/10.23;
  n=floor(n); // volume update call will only occur for 0-100 steps of movement on the pot rather than 1024 steps where ~923 are moot.
  if ((int)n!=volume) {
    Serial.println(n);
    volume =(int)n;
    codec.dac_vol(n); // this influences both HP & LINE_OUT
  }
}
 
@Paul: Will you require examples covering every addition I propose before you accept my pull request?

No, that's not required, but an example or two that demonstrate the main features would be nice. Often with Arduino example, "less is more". Best to keep things simple and demonstrate one concept clearly.
 
And yes, with the power of the ARM chip, per-sample processing would make possible a number type zero-latency effects (1 sample to be axact, so not zero, but only a fraction of a millisec) wich is very enticing. But from here we get out of the scope of the current audio library, i know. Also, a lot of DSP methods are unusable per-sample , so the usage of audio blocks would be still required. and processing audio blocks need time, so this gets complicated here.
.....
I just thought that what i'm talking about is achievable within the bounds of the current lib.

I do understand what you're getting at.

Per-sample processing, or even variable or mixed size block processing is outside the scope of this library.

Developing this library is already quite challenging. I'm not going to add complexity to an already difficult project by deviating from the fixed block size approach. I believe the roughly 40 year history of modern software has shown restricting the scope of a design to highly uniform structure is good strategy. But it is a path that requires being able to say "no" to certain features.

The current structure was not picked arbitrarily. I've been working on this library for many months, and in the early days a LOT of time and effort went into investigating the feasibility of doing many types of projects people want with the many design trade-offs. A decision was made, and I'm sticking with it.

Also, can anybody point out how does the mixer "mix" two samples? What formula is used?

It's 16 bit saturating addition. Here's the relevant code from the applyGainThenAdd() function:

Code:
        if (mult == 65536) {
                do {
                        uint32_t tmp32 = *dst;
                        *dst++ = signed_add_16_and_16(tmp32, *src++);
                        tmp32 = *dst;
                        *dst++ = signed_add_16_and_16(tmp32, *src++);
                } while (dst < end);

Inside the loop "tmp32 = *dst" reads two audio samples at once (from the "dst" buffer) into a 32 bit temporary variable. Then the next line reads two more audio samples from the "src" buffer. The signed_add_16_and_16() function is merely a QADD16 instruction, which you can find documented here:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0473j/dom1361289886623.html

It simply adds both 16 bit halves. Each addition is done with "saturation", which is basically the same as clipping in analog circuitry, if the two numbers sum to greater than the audio range. The 32 bits are stored back into the "dst" buffer, and the point is incremented to the next 32 bits. This is done twice in the loop, so the overhead of checking for the end of the buffer is cut in half.

The mixer was one of the first objects I wrote. I've since learned its even faster than read 8 audio samples into variables. Even though the code is the same, the Cortex-M4 processor uses a special faster burst mode to access the RAM if you read consecutive 32 bit words back-to-back. So eventually I'll rewrite this to use 4 temporary variables, but the code will be pretty much identical, just 4 copies inside the loop instead of 2, and the operations rearranged.

You might notice the check if "mult" is 65536, which represents unity gain for the mixer channel. If it's some other number, a slight more complex version is used, where the 2 samples are multiplied by the gain, then right shifted so a multiply by 65536 becomes a multiply by 1. The 2 multiplied values are packed back into a single 32 bit variable, and then the same QADD16 instruction is used to add them. This code might also benefit from loop unrolling a bit.... eventually I'll go through most of these objects and do more optimization. For now, the goal is to get things simply working well with enough speed to be usable.
 
Last edited:
Status
Not open for further replies.
Back
Top