Go Down

Topic: Yet another FHT thread, the basics (Read 674 times) previous topic - next topic

mrExplore

Jan 09, 2018, 03:34 pm Last Edit: Jan 09, 2018, 04:22 pm by mrExplore
Hi all,

I'm probably the 4x106th person asking about this but... I spent allmost the whole day trying to comprehend what the Open Music Labs FHT library does.

What am I trying to do ? I would like to make neopixel strip flash to music (am I the first to try this :) ?).
I have an arduino mega, neopixel led strip and this microphone board from adafruit: Electret Microphone Amplifier - MAX9814

Now I'm looking at the example code over at open music labs: Programming example

Now I'm confused about these "frequency bins". I understand that at the end of the example code there should be an "array of bins" ?

1. Which array would that be ? >> EDIT: Ok, that would be fht_input[]
2. Does each of the values tell me something about the level at a certain frequency ?
3. And if 2=yes, how to tell to which frequency (range?) the value refers ?

In the end I would like to narrow it down to say 5 frequency bands that my ledstrip will flash to.

EDIT: I'm now trying to understand what is explained here :http://wiki.openmusiclabs.com/wiki/FFTFunctions

Thanks in advance !

jremington

#1
Jan 09, 2018, 05:14 pm Last Edit: Jan 09, 2018, 05:21 pm by jremington
The FHT converts an array of amplitude values, sampled at regular intervals in time, to frequency values, in place.

Most people use one of the fht_mag() functions to convert the array into frequency magnitudes, which tells you the amount of a particular frequency component in the input.

If you want to make any sense out of the output, it is extremely important that no frequency higher than 1/2 the sample frequency is in the input. That usually means you need a good low pass filter on the input, something that the OpenMusicLabs author forgets to tell you.

For the Arduino, the default analogRead() sample frequency is 9.6 kHz, so that means no frequencies higher than 4.8 kHz in the input.

Finally, if you have 64 input bins, there are 64/2 = 32 frequency magnitude output bins. At the default sample frequency of 9.6 kHz, the output frequency bins are 4800Hz/32 = 150 Hz wide (0-149, 150-299, etc.).

mrExplore

OK, thnx for your answer.

But wait:
Quote
If you want to make any sense out of the output, it is extremely important that no frequency higher than 1/2 the sample frequency is in the input. That usually means you need a good low pass filter on the input, something that the OpenMusicLabs author forgets to tell you.

For the Arduino, the default analogRead() sample frequency is 9.6 kHz, so that means no frequencies higher than 4.8 kHz in the input.
I'm a bit confused, I see various sound/music reactive projects using fft/fht, but music contains frequencies above 4.8K right ? For example this track: https://www.youtube.com/watch?v=ti2FWCtSUC8

The "high beats" seem to be in the 14K range:



So that would mean these highs can not be detected ?

On the other hand you say, the default sample rate is 9.6 kHz
Is there an option to set it in another mode, might this be the following line in the example code then :

Code: [Select]
ADCSRA = 0xe5; // set the adc to free running mode





PieterP

But wait:I'm a bit confused, I see various sound/music reactive projects using fft/fht, but music contains frequencies above 4.8K right ? For example this track: https://www.youtube.com/watch?v=ti2FWCtSUC8

The "high beats" seem to be in the 14K range:

So that would mean these highs can not be detected ?

Correct.


On the other hand you say, the default sample rate is 9.6 kHz
Is there an option to set it in another mode, might this be the following line in the example code then :

Code: [Select]
ADCSRA = 0xe5; // set the adc to free running mode

See §24.4 of the ATmega328P datasheet.

Pieter

Grumpy_Mike

Quote
So that would mean these highs can not be detected ?
Yes but it is worse than that, unless the input is filtered the "numbers" from those high beats are spread as "random" noise all over the other bins. This is called aliasing and messes up your analysis.
https://en.wikipedia.org/wiki/Aliasing

Quote
am I the first to try this
No.
I did a complete project of this in my book
Arduino Music

jremington

Quote
am I the first to try this
The following statement is an underestimate:

Quote
I'm probably the 4x10^6th person asking about this

mrExplore

hmm, ok... thanks for clearing that up guys !
Good to know that Higher frequencies on the input mess up all the readings.

Just another minor detail no one ever mention when showing off their cool projects :(

I found this blogpost and it looked interesting.
I did get a bit hopeful reading this instructable. This guy claims that he can get the sampling rate up to 38.5kHz

Quote
I manually set the Arduino's internal analog to digital converter (ADC) counter to 500kHz and read an 8 bit value from analog input 0 from the ADCH directly (I just read the most significant 8 bits of the 10 bit ADC to save time in the code).  I set the ADC counter to 500kHz because the ADC takes 13 clock cycles to read a new analog value.  500/13 =~ 38.5kHz which gets me pretty close to 40kHz (standard audio sampling rate) without introducing extra noise.
However:
Quote
Also, continuous monitoring of A0 means that the other analog pins are now useless
I would like to sample the whole musical spectrum ánd I need other analog inputs for potmeters, so this is not going to work.

Sow... what are my options here,
- Buying an arduino zero ?
- Using an external ADC ?

Or...

I have an arduino uno lying around, let's say I use the techniques described in the blogpost and link.
Then I would have :
- an arduino uno doing the ADC with a sampling rate of > 40 kHz ánd doing the fft/fht
- one arduino mega to do the light effects and connect potmeters to
- then I would have the mega read the results from the uno in some way

Or...

Could I hook my microphone Electret Microphone Amplifier - MAX9814 up to the msgeq7 that I started out with ?

Again, in the end, the level of 8 frequency bands would be enough

This is getting quite complicated... for my level.






jremington

#7
Jan 09, 2018, 09:07 pm Last Edit: Jan 09, 2018, 09:09 pm by jremington
In fact, the following line in the FFT/FHT example code sets the input sample rate to 38.5 kHz, another fact that the OpenMusicLabs author forgot to mention:
Code: [Select]
ADCSRA = 0xe5; // set the adc to free running mode

So, with that setting you can process the entire conventional audio spectrum but you still need to make sure that no signals with frequencies higher than 19 kHz get to the input.

PS: Avoid Instructables, most of them are crap and some can even lead to destruction of your Arduino.

Grumpy_Mike

#8
Jan 09, 2018, 09:52 pm Last Edit: Jan 09, 2018, 09:58 pm by Grumpy_Mike
Quote
I would like to sample the whole musical spectrum ánd I need other analog inputs for potmeters, so this is not going to work.
What you do is take a batch of samples and process them then display it. While you are doing the processing and displaying you are missing a whole load of audio samples anyway. So you read the other analogue inputs before you do the processing. You can not, and do not need to continuously sample the audio, you have to miss some. Not even with the more powerful processors in the Arduino range, for just flashing lights their is simply no need.

You can do a continuous FFT but that requires a type if chip known as an FPGA  https://en.wikipedia.org/wiki/Field-programmable_gate_array

mrExplore

Thanks for you replies

In fact, the following line in the FFT/FHT example code sets the input sample rate to 38.5 kHz
.....
So, with that setting you can process the entire conventional audio spectrum but you still need to make sure that no signals with frequencies higher than 19 kHz get to the input.
Ok, so this microphone I'm using Electret Microphone Amplifier - MAX9814 has a range of 20Hz-20kHz but signal between 19kHz and 20kHz would be problematic for the FHT. Is there a workaround for this ?

What you do is take a batch of samples and process them then display it. While you are doing the processing and displaying you are missing a whole load of audio samples anyway. So you read the other analogue inputs before you do the processing. You can not, and do not need to continuously sample the audio, you have to miss some. Not even with the more powerful processors in the Arduino range, for just flashing lights their is simply no need.

You can do a continuous FFT but that requires a type if chip known as an FPGA  https://en.wikipedia.org/wiki/Field-programmable_gate_array
Ok, good point. I hope I understood correctly because I've been spending some time now to get this concept to work.


But first, i found this page Fast sampling from analog input This guys fiddles with the prescaler and interrupts.

When I run this code:
Code: [Select]
int numSamples=0;
long t, t0;

void setup()
{
  Serial.begin(115200);

  ADCSRA = 0;             // clear ADCSRA register
  ADCSRB = 0;             // clear ADCSRB register
  ADMUX |= (0 & 0x07);    // set A0 analog input pin
  ADMUX |= (1 << REFS0);  // set reference voltage
  ADMUX |= (1 << ADLAR);  // left align ADC value to 8 bits from ADCH register

  // sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
  // for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
  //ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
  ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
  //ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz

  ADCSRA |= (1 << ADATE); // enable auto trigger
  ADCSRA |= (1 << ADIE);  // enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN);  // enable ADC
  ADCSRA |= (1 << ADSC);  // start ADC measurements
}

ISR(ADC_vect)
{
  byte x = ADCH;  // read 8 bit value from ADC
  numSamples++;
}
 
void loop()
{
  if (numSamples>=1000)
  {
    t = micros()-t0;  // calculate elapsed time

    Serial.print("Sampling frequency: ");
    Serial.print((float)1000000/t);
    Serial.println(" KHz");
    delay(2000);
   
    // restart
    t0 = micros();
    numSamples=0;
  }
}


This is the result on the serial monitor:
Code: [Select]
Sampling frequency: 76.88 KHz
Sampling frequency: 76.92 KHz
Sampling frequency: 76.88 KHz
Sampling frequency: 76.95 KHz
Sampling frequency: 76.88 KHz
Sampling frequency: 76.95 KHz
Sampling frequency: 76.88 KHz
Sampling frequency: 76.90 KHz



So if I understand correctly ISR(ADC_vect) is fired everytime the ADC has new data, at a frequency of ~77kHz ?

So my thought was/is:
- I disable interrupts at the beginning of my code
- When a reading from the FHT is needed, I enable interrupts so the ISR(ADC_vect) routine is fired.
- In this routine 256 readings are taken and put into fht_input[]
- While the reading is been done my main code should wait until 256 readings have been done
- When 256 reading have been taken interrupts are disabled and the contents of fht_input[] can be processed by the main code

Might this work  ?

Here's my code so far. I haven't gotten it to work like I want to but if I'm completely going in the wrong direction, please let me know

Code: [Select]
#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h> // include the library

bool bSampling;                                   //boolean that indicates an FHT sample is being taken
int intSampleNumber = 0;                          //keeps track of the number of samples that have been taken
long lngStartTime, lngEndTime, lngSampleTime;     //start, end and duration of sampling time to determine frequency


void setup()
{
  Serial.begin(115200);

  //Disable interrupts until a reading is needed
  cli();

  ADCSRA = 0;             // clear ADCSRA register
  ADCSRB = 0;             // clear ADCSRB register
  ADMUX |= (0 & 0x07);    // set A0 analog input pin
  ADMUX |= (1 << REFS0);  // set reference voltage
  ADMUX |= (1 << ADLAR);  // left align ADC value to 8 bits from ADCH register

  // sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
  // for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
  //ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
  ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
  //ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz

  ADCSRA |= (1 << ADATE); // enable auto trigger
  ADCSRA |= (1 << ADIE);  // enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN);  // enable ADC
  ADCSRA |= (1 << ADSC);  // start ADC measurements
}


ISR(ADC_vect){
 
    byte m = ADCL;  // fetch adc data, first the low byte
    byte j = ADCH;  // then the high byte
    int k = (j << 8) | m; // form into an int
    k -= 0x0200; // form into a signed int
    k <<= 6; // form into a 16b signed int
    fht_input[intSampleNumber] = k; // put real data into bins
 
    intSampleNumber++;
 
    //if enough samples have been taken (FHT_N): set samplenumber back to zero
    //and disable interrupts so ISR(ADC_vect) will not be called until needed
    if(intSampleNumber == FHT_N){
     
      intSampleNumber = 0;                  //reset intSampleNumber for the next reading
     
      bSampling = false;                    //set bSampling to false when sampling finishes
      Serial.println(bSampling);
     
    }
     
}

 
void loop()
{
    //A reading is needed:   
    lngStartTime = micros();                  //mark the time at which the sampling started, it starts right after enabling interrupts
    Serial.print("StartTime: ");
    Serial.println(lngStartTime);
    bSampling = true;                         
    sei();                                    //enable interrupts so an FHT reading is made bij the ISR(ADC_vect) interrupt
   
    while(bSampling == true){                         //Wait until the reading is finished, bSampling is set to false in the interrupt routine, when enough samples have been taken
      Serial.println("=======");
    }

    cli();                                //disable interrupts

    lngEndTime = micros();                //mark the time at which the sampling stopped
      Serial.print("  EndTime: ");
      Serial.println(lngEndTime);

    delay(500);

    fht_window(); // window the data for better frequency response
    fht_reorder(); // reorder the data before doing the fht
    fht_run(); // process the data in the fht
   
    //trying different magnitude functions
    //fht_mag_lin8();         
    //fht_mag_lin();
    fht_mag_log();       // take the output of the fht
    //fht_mag_octave();
//    for(int a = 0; a < FHT_N - 1; a++){
//      Serial.print(fht_input[a]);
//      Serial.print("   ");
//    }
//    Serial.println("<");

   
 
}

jremington

#10
Jan 10, 2018, 06:23 pm Last Edit: Jan 10, 2018, 06:29 pm by jremington
Quote
Is there a workaround for this ?
As mentioned in reply #1, you need a low pass filter between the microphone amplifier and the analog input.

Quote
Might this work  ?
Yes. To test your code, replace the microphone and amplifier with a sine wave signal generator, or the ADC input routine with some lines that calculate a signal for which you know the answer.

Example:
Code: [Select]

/*
 fft_test_sine
 example sketch for testing the fft library.
 This generates a simple sine wave data set consisting
 of two frequences f1 and f2, transforms it, calculates
 and prints the amplitude of the transform.
 */

// do #defines BEFORE #includes
#define LIN_OUT 1 // use the lin output function
#define FFT_N 64 // set to 64 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(9600); // output on the serial port
}

void loop() {
  int i,k;
  float f1=2.0,f2=5.0;  //the two input frequencies (bin values)
  for (i = 0 ; i < FFT_N ; i++) { // create samples
    // amplitudes are 1000 for f1 and 500 for f2
    k=1000*sin(2*PI*f1*i/FFT_N)+500.*sin(2*PI*f2*i/FFT_N);
    fft_input[2*i] = k; // put real data into even bins
    fft_input[2*i+1] = 0; // set odd bins to 0
  }
 
  fft_window();  //Try with and without this line, it smears

  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data using the fft
  fft_mag_lin(); // calculate the magnitude of the output

  // print the frequency index and amplitudes

  Serial.println("bin  amplitude");
  for (i=0; i<FFT_N/2; i++) {
    Serial.print(i);
    Serial.print("       ");
    Serial.println(2*fft_lin_out[i]); //*2 for "negative frequency" amplitude
  }
  Serial.println("Done");
  while(1); //wait here
}


Grumpy_Mike

#11
Jan 10, 2018, 06:48 pm Last Edit: Jan 10, 2018, 06:48 pm by Grumpy_Mike
Quote
So my thought was/is:
- I disable interrupts at the beginning of my code
Sledge hammer to crack a nut and might lead to other problems, just disable the ADC interrupt flag in the ADC control registers when you want in the ISR. The interrupts will be automatically disabled when you send all the data out to the LEDs anyway, so playing with the global flag will be undone by that. By using the specific interrupt enable you want you have much better control.

Note as you play about with the prescalier the accuracy of the A/D suffers.

mrExplore

Thanks for the input :)

I'll look into it


mrExplore

#13
Jan 10, 2018, 08:36 pm Last Edit: Jan 10, 2018, 08:42 pm by mrExplore
Ok I'm looking into this now:

....just disable the ADC interrupt flag in the ADC control registers when you want in the ISR...
I am, again confused...sorry

Is this the way to set the interrupt flag ?
clear the bit / to zero : ADCSRA |= (0 << ADIF); 
set the bit / to one :    ADCSRA |= (1 << ADIF);

setting a bit means setting it to 1, clearing means setting it to 0 right ?

From the datasheet, page 319:
Quote
Bit 4 - ADIF: ADC Interrupt Flag
This bit is set ( to 1 ?) when an ADC conversion completes and the Data Registers are updated. The ADC Conversion Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared ( to 0 ? ) by hardware when executing the corresponding interrupt handling vector. Alternatively, ADIF is cleared by writing a logical one to the flag.
"Alternatively, ADIF is cleared by writing a logical one to the flag"
So writing a 1 to the flag clears it ?
And writing a 0 sets it ?




jremington

#14
Jan 10, 2018, 09:11 pm Last Edit: Jan 10, 2018, 09:12 pm by jremington
Please read the data sheet more carefully.

To disable ADC interrupts, use

Code: [Select]
   ADCSRA &= ~(1<<ADIE);  //not ADIF

To enable ADC interrupts, use:

Code: [Select]
   ADCSRA |= (1<<ADIE);

The interrupt itself sets the ADIF flag. Counter intuitively, you clear the interrupt flag it by writing a 1 to it as follows:

Code: [Select]
   ADCSRA |= (1<<ADIF);

But you normally don't need to do that, as the ADC interrupt routine clears it automatically.

Go Up