F.R.E.Q. (Frequency Reactive Emissive Quadrants)

Introduction

F.R.E.Q. is a very basic frequency/volume dependent music visualizer. This board uses an Arduino Nano and an MSGEQ7 Graphic Equalizer Display Filter. Pulling from SparkFun where I sourced this IC, “The seven band graphic equalizer IC is a CMOS chip that divides the audio spectrum into seven bands. 63Hz, 160Hz, 400Hz, 1kHz, 2.5kHz, 6.25kHz and 16kHz. The seven frequencies are peak detected and multiplexed to the output to provide a DC representation of the amplitude of each band.”

Mechanical

While never fabricated, a 3D printed case was designed in Fusion 360 utilizing the mounting holes on the four corners of the F.R.E.Q. PCB. This case features a clamping mechanism for the RGB strip to enter and be secured by the case. This would prevent stresses on the RGB strip wires and hopefully prevent the wires from pulling out of the screw terminals.

Hardware

  • (1) – Arduino Nano
  • (3) – TIP120 Darlington Transistors
  • (1) – MSGEQ7 IC
  • (1) – 3.5mm Audio Jack
  • (1) – 12V Barrel Jack
  • (2) – Screw Terminal Block
  • (1) – COTS Voltage Regulator

I find the MSGEQ7 to be a fascinating IC as it has an interesting take on data output. Functionally, the IC samples audio and as the “Strobe” pin is triggered, the “Output” pin toggles over the seven frequencies, giving an analog voltage representative of the loudness of said frequency. Here is the timing diagram from the MSGEQ7 datasheet:

The Arduino simultaneously triggers the Strobe pin and reads the analog voltage of the IC. The values of the different frequency bands are stored in an array and the loudness of the three predominant frequency bands is converted to colors. These colors are then converted to PWM values for R, G, and B and the Arduino sends the PWM signals to the Base pin of the TIP120 of each color. This ultimately results in a frequency and volume-dependent light show.

Potential Redesign

This was one of my first boards and there are quite a few things I would change if I were to redesign F.R.E.Q. First of all, I avoided doing my own voltage regulation on this board, requiring an external COTS voltage regulator. With 20/20 hindsight and a lot of knowledge regarding PCB design and electronics in general gained since creating this board, I certainly should have included a local 5v regulator for the board. Other improvements in a potential redesign include a far more consolidated board, locking terminal blocks instead of screw terminal blocks, and thicker traces. From a software standpoint, the code was always finnicky and I never really got the exact rainbow frequency band I was expecting. In summary, as it stands now, F.R.E.Q. is flawed but at least it is functional.

Code

Admittedly, it has been a very long time since I have looked at this code, so if you decide to take inspiration from it, go over in detail yourself to make sure it all checks out. Last time I checked, the code worked.

/*---------------------------------------------------------------
Even Industries
F.R.E.Q.
Engineer: Joseph Even
Description: Music Reactive RGB Light Strip Controller
----------------------------------------------------------------*/
#include 
#define PI 3.14159
#define COLOR_RATIO 0.078692


char buf[4];
// PIN ASSIGNMENTS
  const byte r_out = 3; //D3
  const byte g_out = 6; // D6
  const byte b_out = 9; // D9
  const byte volume_led = 12; // D12
  const byte ic_strobe = 14; // A0 
  const byte ic_read = 15; // A1
  const byte ic_reset = 16; // A2

// VARIABLES
  long spectrumValue[7] = {0};
  
// top_indices
void top_indices(int spectrumValue[]);
  int high_index[3] = {0};
  int high = 0;
  int mid = 0;
  int low = 0;
  byte first_index = 0;
  byte second_index = 0;
  byte third_index = 0;

// color_create
void color_create(long spectrumValue[], int high, int mid, int low);
  /*
  float high_weight = 1.75;
  float mid_weight = 1.25;
  float low_weight = 1.15;
  */
  float color_val = 0;

// color_out
void color_out(int color_val);
  float  r_raw = 0;
  float  g_raw = 0;
  float  b_raw = 0;

   float r_constrain = 0;
   float g_constrain = 0;
   float b_constrain = 0;
  float r_val = 0;
  float g_val = 0;
  float b_val = 0;


// EXTRA FUNCTIONS
float mapf(float x, float in_min, float in_max, float out_min, float out_max); 


float strictMap(float val, float inmin, float inmax, float outmin, float outmax);

void setup() {
  Serial.begin(115200);
  
  
  pinMode(ic_read, INPUT);
  pinMode(ic_strobe, OUTPUT);
  pinMode(ic_reset, OUTPUT);
  digitalWrite(volume_led, LOW); 
  
  pinMode(r_out, OUTPUT);
  pinMode(g_out, OUTPUT);
  pinMode(b_out, OUTPUT);
  
  digitalWrite(ic_reset, LOW);
  digitalWrite(ic_strobe, HIGH);
} // End setup



void loop() {
  digitalWrite(ic_reset, HIGH);
  digitalWrite(ic_reset, LOW);
  
  for (int i = 0; i < 7; i++) {
    digitalWrite(ic_strobe, LOW);
    delayMicroseconds(40); // Allow the output to settle
    spectrumValue[i] = analogRead(ic_read);
    digitalWrite(ic_strobe, HIGH);
  } // End for

  /*
  // Print Reads
    for (int i = 0; i < 7; i++) {       sprintf(buf, "%4d",spectrumValue[i]);          Serial.print(buf);     Serial.print(" ");     }   Serial.println("");   */      top_indices(spectrumValue);   // Volume Tuning (Hardware Pot with LED indicator)   if ( (spectrumValue[high_index[0]] > 1000) && (spectrumValue[high_index[0]] < 1023) ) {
    digitalWrite(volume_led, HIGH); 
  }
  
  
  color_create(spectrumValue, first_index, second_index, third_index);
  
  color_out(color_val);
  
} // End loop



// EXTERNAL FUNCTIONS

void top_indices(long spectrumValue[])  { // Find top 3 indices and corresponding values

  int i;
  for (i = 0; i < 7; i++) {     if (spectrumValue[i] > high) {
      low = mid;
      mid = high;
      high = spectrumValue[i];
      third_index = second_index;
      second_index = first_index;
      first_index = i;
    } // End if
    
    else if (spectrumValue[i] > mid) {
      low = mid;
      mid = spectrumValue[i];
      third_index = second_index;
      second_index = i;
    } // End else if
    
    else if (spectrumValue[i] > low) {
      low = spectrumValue[i];
      third_index = i;
    } // End else if
    
          
  } // End for

  // Index Numbers for Highest Values
  high_index[0] = first_index;
  high_index[1] = second_index;
  high_index[2] = third_index;
        
} // End top_indices



void color_create(long spectrumValue[], int high, int mid, int low) { //0 = purple, 1023 = red
  /*
  //Weight color to strongest frequency
    spectrumValue[high_index[0]] = (spectrumValue[high_index[0]] * high_weight);
    spectrumValue[high_index[1]] = (spectrumValue[high_index[2]] * mid_weight);
    spectrumValue[high_index[2]] = (spectrumValue[high_index[2]] * low_weight);
    constrain(spectrumValue[high], 1, 1023);
    constrain(spectrumValue[mid], 1, 1023);
    constrain(spectrumValue[low], 1, 1023);
   */
   
   
  

  // PWM Summation
  int pwm_sum = 0;
  for (int i = 0; i < 7; i++) {
    pwm_sum += spectrumValue[i]; 
 }
 /*
  Serial.print("Sum: ");
  Serial.println (pwm_sum); 
 */ 
  // Frequency Conversion
  float spectrumValue_adjusted[7] = {0};
  int factor[7] = {63, 160, 400, 1000, 2500, 6250, 13000};
    for (int i = 0; i < 7; i++)  {
      spectrumValue_adjusted[i] = ( factor[i] *  spectrumValue[i] / pwm_sum );
      
      /*
      Serial.print("Factor: ");
      Serial.println(factor[i]);
      Serial.print("Val: ");
      Serial.println(spectrumValue[i]);
      Serial.print("Val_adj: ");
      Serial.println(spectrumValue_adjusted[i]);
      */
    }
    
  
   
  // Frequency Summation
  float freq_sum = 0;
    for (int i = 0; i < 7; i++) { 
      freq_sum += spectrumValue_adjusted[i]; 
    }
  /*
  Serial.print("freq_sum: ");
  Serial.println(freq_sum);
  */
  
  // Map from 0-1023
  color_val = ( (freq_sum * (COLOR_RATIO)) - (63 * (COLOR_RATIO) ) );
  
  Serial.print("cv: ");
  Serial.println(color_val);
  
} // End color_create



void color_out(float color_val) { // Accepts values from 0-1023 and outputs RGB spectrum (0 = purple, 1023 = red)

  // Map to trig tolerant value 
    float color_val_pi_map = 0;
    color_val_pi_map =  mapf(color_val, 100, 700, .1, ( (5*PI) / 6 ) );
    float x = color_val_pi_map;
    
    Serial.print("cv_map: ");
    Serial.println(x);
    Serial.println("");
    
  // Apply color phase shift
    r_raw = sin( 2*x + (5*PI / 6) );
    g_raw = sin( 2*x + (3*PI / 2) );
    b_raw = sin( 2*x + (PI   / 6) );

    Serial.print(r_raw);
    Serial.print(" | ");
    Serial.print(g_raw);
    Serial.print(" | ");
    Serial.println(b_raw);
    
   
  // Final Values
    r_constrain = strictMap(r_raw, 0, 1, 0, 255);
    g_constrain = strictMap(g_raw, 0, 1, 0, 255);
    b_constrain = strictMap(b_raw, 0, 1, 0, 255);
    
    // Print color values
  
    
    Serial.print(r_constrain);
    Serial.print(" | ");
    Serial.print(g_constrain);
    Serial.print(" | ");
    Serial.println(b_constrain);
    Serial.println("  ");
    
  
  // Write to output
    analogWrite(r_out, r_constrain);
    analogWrite(g_out, g_constrain);
    analogWrite(b_out, b_constrain);
    
} // End color_out



float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
} // End mapf

float strictMap(float val, float inmin, float inmax, float outmin, float outmax)
{
  if (val <= inmin) return outmin;   if (val >= inmax) return outmax;
  return (val - inmin)*(outmax - outmin)/(inmax - inmin) + outmin;
}