Benutzer-Werkzeuge

Webseiten-Werkzeuge


arduino:spektrumanalysator:programmversion_0.4

Spektrumanalysator - Programmversion 0.4

Diese Programmversion das Audio Adaptor Borad ein und steuert einen NeoPixel-Streifen mit 72 NeoPixeln an. Es wirt eine Fast Fourier Transformation für beide Stereo-Kanäle durchgeführt. Die 512 Frequenzen pro Kanal werden logarithmisch je 36 Frequenzbändern zugeordnet. (Sehr hilfreich war diese Diskussion im Teensy-Forum. Die Ermittelten Werte werden anschließend in Farbwerte umgerechnet und auf dem NeoPixel-Streifen dargestellt.

Das Ergebnis ist durchaus zufriedenstellend, es zeigt sich aber, dass man sehr häufig manuell den Eingangspegel nachregelt, wenn sich leise mit lauten Songs abwechseln, oder man einfach den Pegel des Eingangssignal verringert oder erhöht. Daher habe ich eine automatische Eingangspegelregelung programmiert, die später im Menü aktivierbar sein wird. Die automatische Eingangspegelregelung bestimmt kontinuierlich den Maximalpegel des Eingangssignals und ferringert diesen kontinuierlich um einen bestimmten (später im Menü einstellbaren) Wert. Anschließend wird in Abhängigkeit des maximalen Eingangspegels die Verstärkung des Eingangssignals geregelt.

  • Dabei soll die Regelung einerseits so träge sein, dass Pegelschwankungen in einem Song auch durch die Ausschlage des Spektrumanalysators abgebildet werden.
  • Außerdem soll durchaus weiterhin der Pegel der Lautsprecher durch den Spektrumanalysator abgebildet werden.

Die automatische Eingangspegelregelung soll also nur innerhalb eines gewissen Bereichs greifen. Optimale Werte bzw. ein praxisnaher Einstellbereich der im Menü veränderbaren Grenzwerte muss experimentell bestimmt werden.

Interessante Links:

// Spektrumanalysator
// Für einen NeoPixel-Streifen mit 72 NeoPixeln

//Bibliotheken
#include <Audio.h>                                        // Audio
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Encoder.h>                                      // Encoder
#include <Adafruit_NeoPixel.h>                            // NeoPixel

// Pins
#define encoderChannelA  24                               // Kanal A
#define encoderChannelB  25                               // Kanal B
#define NEOPIXELPIN      33                               // NeoPixel-Streifen

// Dreh-Encoder
Encoder myEnc(encoderChannelA, encoderChannelB);

long newPosition = 0;
long oldPosition;

// Menü Spektrumanalysator
byte displayMode = 0;                                     // Verschiedene Anzeigemodi

// Automatische Eingangspegelregelung
boolean autoInputLevelControl = true;                     // Aktiviert und deaktiviert die automatische Eingangspegelregelung
float peakMaxDecrease = 0.001;                            // Wert, um den der Maximalpeak kontinuierlich verringert wird. (Kann später im Menü angepasst werden.)

float level_L[36];                                        // Array für die Daten der FFT (linker Kanal)
float level_R[36];                                        // Array für die Daten der FFT (rechter Kanal)
float leftPeak;                                           // Pegel linker Kanal. Kann Werte zwischen 0.00 und 1.00 annehmen.
float rightPeak;                                          // Pegel rechter Kanal. Kann Werte zwischen 0.00 und 1.00 annehmen.
float masterPeak;                                         // Pegel des rechten und des linken Kanals. Kann Werte zwischen 0.00 und 1.00 annehmen.
float masterPeakMax;                                      // Maximalpegel beider Audiokanäle, von dem schrittweise "peakDecrease" substrahiert wird. Kann Werte zwischen 0.00 und 1.00 annehmen.
float peakDecrease = 0.001;
float autoGain;                                           // Anhebung des Eingangssignals: Dämpfung zwischen 0.00 bis 1.00, Anhebung ab 1.01

// Konfiguriert den NeoPixel-Ring
int numPixels = 72;                                             // Anzahl der NeoPixel
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numPixels, NEOPIXELPIN, NEO_GRBW + NEO_KHZ800);

// Anzeige Spektrumanalysator
float n;
int i;                                                    // Variable zum zählen
byte r = 0;
byte g = 0;
byte b = 0;
byte w = 0;

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;             //xy=98,272
AudioMixer4              mixer_R;          //xy=245,320
AudioMixer4              mixer_L;          //xy=247,235
AudioAnalyzePeak         peak_R;           //xy=442,293
AudioAnalyzeFFT1024      fft1024_R;        //xy=451,327
AudioAnalyzePeak         peak_L;           //xy=455,146
AudioAnalyzeFFT1024      fft1024_L;        //xy=461,184
AudioConnection          patchCord1(i2s1, 0, mixer_L, 0);
AudioConnection          patchCord2(i2s1, 1, mixer_R, 0);
AudioConnection          patchCord3(mixer_R, peak_R);
AudioConnection          patchCord4(mixer_R, fft1024_R);
AudioConnection          patchCord5(mixer_L, peak_L);
AudioConnection          patchCord6(mixer_L, fft1024_L);
AudioControlSGTL5000     sgtl5000_1;        //xy=107,430
// GUItool: end automatically generated code

// Definiert die Tracking-Variablen für die IF-Abfragen
unsigned long previousMillisAutoInputLevelControl = 0;          // Berechnung des maximalen Peaks für die automatische Inputlevel-Regelung

// Definiert die Intervalle für die IF-Abfragen in Millisekunden
unsigned long intervalAutoInputLevelControl = 100;              // Berechnung des maximalen Peaks für die automatische Inputlevel-Regelung

// Taktung Schleifen
unsigned long lastMillis = 0;
unsigned long duration = 0;


void setup() {
  Serial.begin(115200);                 
    
  AudioMemory(22);                                              // Audiospeicher

  // enable the audio shield
  sgtl5000_1.enable();
  sgtl5000_1.muteHeadphone();
  sgtl5000_1.muteLineout();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  sgtl5000_1.lineInLevel(5);
  //sgtl5000_1.autoVolumeControl(2, 3, 0, -18, 3, 30);           // (maxGain, response, hardLimit, threshold, attack, decay)
  //sgtl5000_1.autoVolumeEnable();
  
  // Configure the window algorithm to use
  fft1024_L.windowFunction(AudioWindowHanning1024);
  //myFFT.windowFunction(NULL);

  // Initialisiert den NeoPixel-Teststrip
  strip.begin();
  strip.show();                                                 // Initialize all pixels to 'off'
  strip.setBrightness(64);                                      // 1/4 der maximalen Helligkeit
  
  // Legt den Anzeigemodus fest (provisorisch)
  displayMode = 0;

  // Schaltet die automatische Inputlevelregelung ein bzw. aus. (Kann später im Menü aktiviert und deaktiviert werden.)
  autoInputLevelControl = true;
}

void loop() {
  // Aktuelle Zeit abfragen
  unsigned long currentMillis = millis();
  
  // Encoder
  newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
  }
  if (newPosition < 0) {
    newPosition = 0;
  }
  if (newPosition >= 120) {
    newPosition = 120;
  }  

  // Gain Control  
  if (autoInputLevelControl == false) {                        // Die manuelle Inputlevel-Kontrolle ist nur aktiv, wenn die automatische Inputlevel-Regelung deaktiviert ist
    mixer_L.gain(0, (newPosition / 40.0));
    mixer_R.gain(0, (newPosition / 40.0));
  }

  // Fast Fourier Transformation (FFT)
  if (fft1024_L.available() && fft1024_R.available()) {        // Wenn die FFT neue Daten berechnet hat, werden für beide Kanäle je 512 FFT Frequenzen ausgelesen und in je 36 Bändern zusammengefasst.
    // Zuerst ist der linke Kanal an der Reihe
    level_L[35] =  fft1024_L.read(0);
    level_L[34] =  fft1024_L.read(1);
    level_L[33] =  fft1024_L.read(2);
    level_L[32] =  fft1024_L.read(3);
    level_L[31] =  fft1024_L.read(4, 5);
    level_L[30] =  fft1024_L.read(6, 7);
    level_L[29] =  fft1024_L.read(8, 9);
    level_L[28] =  fft1024_L.read(10, 11);
    level_L[27] =  fft1024_L.read(12, 14);
    level_L[26] =  fft1024_L.read(15, 17);
    level_L[25] = fft1024_L.read(18, 20);
    level_L[24] = fft1024_L.read(21, 24);
    level_L[23] = fft1024_L.read(25, 28);
    level_L[22] = fft1024_L.read(29, 32);
    level_L[21] = fft1024_L.read(33, 37);
    level_L[20] = fft1024_L.read(38, 43);
    level_L[19] = fft1024_L.read(44, 49);
    level_L[18] = fft1024_L.read(50, 56);
    level_L[17] = fft1024_L.read(57, 64);
    level_L[16] = fft1024_L.read(65, 73);
    level_L[15] = fft1024_L.read(74, 83);
    level_L[14] = fft1024_L.read(84, 94);
    level_L[13] = fft1024_L.read(95, 107);
    level_L[12] = fft1024_L.read(108, 121);
    level_L[11] = fft1024_L.read(122, 137);
    level_L[10] = fft1024_L.read(138, 155);
    level_L[9] = fft1024_L.read(156, 175);
    level_L[8] = fft1024_L.read(176, 198);
    level_L[7] = fft1024_L.read(199, 223);
    level_L[6] = fft1024_L.read(224, 251);
    level_L[5] = fft1024_L.read(252, 283);
    level_L[4] = fft1024_L.read(284, 319);
    level_L[3] = fft1024_L.read(320, 359);
    level_L[2] = fft1024_L.read(360, 404);
    level_L[1] = fft1024_L.read(405, 454);
    level_L[0] = fft1024_L.read(455, 511);
      
    // Und dann der rechte Kanal
    level_R[0] =  fft1024_R.read(0);
    level_R[1] =  fft1024_R.read(1);
    level_R[2] =  fft1024_R.read(2);
    level_R[3] =  fft1024_R.read(3);
    level_R[4] =  fft1024_R.read(4, 5);
    level_R[5] =  fft1024_R.read(6, 7);
    level_R[6] =  fft1024_R.read(8, 9);
    level_R[7] =  fft1024_R.read(10, 11);
    level_R[8] =  fft1024_R.read(12, 14);
    level_R[9] =  fft1024_R.read(15, 17);
    level_R[10] = fft1024_R.read(18, 20);
    level_R[11] = fft1024_R.read(21, 24);
    level_R[12] = fft1024_R.read(25, 28);
    level_R[13] = fft1024_R.read(29, 32);
    level_R[14] = fft1024_R.read(33, 37);
    level_R[15] = fft1024_R.read(38, 43);
    level_R[16] = fft1024_R.read(44, 49);
    level_R[17] = fft1024_R.read(50, 56);
    level_R[18] = fft1024_R.read(57, 64);
    level_R[19] = fft1024_R.read(65, 73);
    level_R[20] = fft1024_R.read(74, 83);
    level_R[21] = fft1024_R.read(84, 94);
    level_R[22] = fft1024_R.read(95, 107);
    level_R[23] = fft1024_R.read(108, 121);
    level_R[24] = fft1024_R.read(122, 137);
    level_R[25] = fft1024_R.read(138, 155);
    level_R[26] = fft1024_R.read(156, 175);
    level_R[27] = fft1024_R.read(176, 198);
    level_R[28] = fft1024_R.read(199, 223);
    level_R[29] = fft1024_R.read(224, 251);
    level_R[30] = fft1024_R.read(252, 283);
    level_R[31] = fft1024_R.read(284, 319);
    level_R[32] = fft1024_R.read(320, 359);
    level_R[33] = fft1024_R.read(360, 404);
    level_R[34] = fft1024_R.read(405, 454);
    level_R[35] = fft1024_R.read(455, 511);

    // Anzeigemodi
    //Effekt "0"
    if (displayMode == 0) { 
     
      // Anschließend werden Farbwerte für die linke Seite des NeoPixel-Streifens berechnet
      for (i=0; i<(numPixels/2); i++) {
        n = level_L[i];
        if (n >= 0.01) {
          // Scale 'heat' down from 0-255 to 0-191
          byte t192 = round((n * 2000  /255.0) * 191);
      
          byte heatramp = t192 & 0x3F; // 0..63
          heatramp <<= 2; // scale up to 0..252
       
          if(t192 > 0x80) {                       // hottest
            strip.setPixelColor(i, 255, 255, heatramp, 0);
            } 
          else if(t192 > 0x40) {             // middle
            strip.setPixelColor(i, 255, heatramp, 0, 0);
            }
          else {                               // coolest
            strip.setPixelColor(i, heatramp, 0, 0, 0);
          }
        }
        else {
          strip.setPixelColor(i, 0, 0, 0, 0);
        }
      }

      // Dann werden die Farbwerte für die rechte Seite des NeoPixel-Streifens berechnet
      for (i=0; i<(numPixels/2); i++) {
        n = level_R[i];
        if (n >= 0.01) {
          // Scale 'heat' down from 0-255 to 0-191
          byte t192 = round((n * 2000  /255.0) * 191);
      
          byte heatramp = t192 & 0x3F; // 0..63
          heatramp <<= 2; // scale up to 0..252
      
          if(t192 > 0x80) {                       // hottest
            strip.setPixelColor(i+36, 255, 255, heatramp, 0);
            } 
          else if(t192 > 0x40) {             // middle
            strip.setPixelColor(i+36, 255, heatramp, 0, 0);
            }
          else {                               // coolest
            strip.setPixelColor(i+36, heatramp, 0, 0, 0);
          }
        }
        else {
          strip.setPixelColor(i+36, 0, 0, 0, 0);
        }
      }

      // Die Daten werden an den NeoPixel-Streifen geschickt
      strip.show();
    }


    // Automatische Eingangspegelregelung
    if (autoInputLevelControl == true) {
      // Es werden die Pegel des linken und rechten Audiokanals ausgelesen.
      if (peak_L.available() && peak_R.available()) {
        leftPeak = peak_L.read();                                       // Der Pegel des linken Kanals wird in die Variable geschrieben.
        rightPeak = peak_R.read();                                      // Der Pegel des rechten Kanals wird in die Variable geschrieben.
      }
      // Für die automatische Eingangspegelregelung wird der lautere der beiden Audiokanäle verwendet.
      masterPeak = max(leftPeak, rightPeak);
 
      // Der Maximalpegel wird gespeichert
      if (masterPeak > masterPeakMax) {
        masterPeakMax = masterPeak;
      }
      // Alle 100 Millikekunden wird die Eingangspegelanpassung berechnet
      if ((unsigned long)(currentMillis - previousMillisAutoInputLevelControl) >= intervalAutoInputLevelControl) {
        // Die automatische Eingangspegelanpassung hebt leise Eingangspegel an, aber ohne den Pegel vollständig zu kompensieren.
        // Auf diese Weise haben leise Eingangssignale einen geringeren Pegelausschalg als laute, es wird aber dennoch "mehr" angezeigt. 
        masterPeakMax = masterPeakMax - peakDecrease;                   // Der Maximalpegel wird schrittweise um den Wert "peakDecrease" verringert.   

        autoGain = (1.00 - masterPeakMax) * 4;                          // autoGain wird berechnet
        if (autoGain > 4.00) {                                          // Die Maximale Pegelanhebung soll den Faktor 4 nicht überschreiten
          autoGain = 4.00;
        }
        if (autoGain < 1.00) {                                          // Die Maximale Pegelanhebung soll den Faktor 1 nicht unterschreiten
          autoGain = 1.00;
        }
        mixer_L.gain(0, autoGain);                                      // Der Eingangspegel des linken Kanals wird angepasst
        mixer_R.gain(0, autoGain);                                      // Der Eingangspegel des rechten Kanals wird angepasst
      
        previousMillisAutoInputLevelControl = currentMillis;
      }    
    }


    // Es wird die Zeit in Millisekunden berechnet, die für einen Durchgang benötigt wurde
    duration = millis()- lastMillis;
      
    // Serieller Output
    Serial.print(" cpu:");
    Serial.print(AudioProcessorUsageMax());
    Serial.print(" mem:");
    Serial.print(AudioMemoryUsageMax());
    Serial.print(" duration:");
    Serial.print(duration);
    Serial.print(" pos:");
    Serial.print(float(newPosition) / 40);
    Serial.print(" peak_L:");
    Serial.print(leftPeak);
    Serial.print(" peak_R:");
    Serial.print(rightPeak);
    Serial.print(" masterPeak:");
    Serial.print(masterPeak);
    Serial.print(" masterPeakMax:");
    Serial.print(masterPeakMax);
    if (autoInputLevelControl == true) {
      Serial.print(" autoGain:");
      Serial.print(autoGain);
    }
    Serial.println();

    lastMillis = millis();
  }
}                                                     // Void Loop Ende
arduino/spektrumanalysator/programmversion_0.4.txt · Zuletzt geändert: 18.05.2023 12:34 von 127.0.0.1