Android sound frequency fft and peak

Most sound libraries not working on APDE this sketch tested directly on APDE 5.1 android 8
I am working on enhance it to be more reliable
Thanks for @noel one of the best teachers


import android.media.AudioRecord;
import android.media.AudioFormat;
import android.media.MediaRecorder;

// Audio configuration
int sampleRate = 44100;
int bufferSize = 1024;
short[] audioBuffer;
AudioRecord audioRecord;

// Visualization variables
float[] volumes = new float[100];
int volumeIndex = 0;
float smoothVolume = 0;
float smoothingFactor = 0.2;
float peakVoltage = 0;
float peakDecay = 0.995;

// Frequency detection
float currentFrequency = 0;
float[] spectrum = new float[50];
color[] spectrumColors = new color[50];

// UI Elements
Slider volumeSlider;
Slider fftBandsSlider;
int fftBands = 20;
int currentMode = 0; // 0=spectrum, 1=waveform, 2=oscilloscope
boolean showSliders = true;

void setup() {
  fullScreen();
  orientation(LANDSCAPE);
  noStroke();
  
  // Initialize spectrum colors
  for (int i = 0; i < spectrumColors.length; i++) {
    spectrumColors[i] = color(
      map(i, 0, spectrumColors.length, 150, 255),
      map(i, 0, spectrumColors.length, 255, 150),
      100
    );
  }
  
  // Audio setup
  audioBuffer = new short[bufferSize];
  audioRecord = new AudioRecord(
    MediaRecorder.AudioSource.MIC,
    sampleRate,
    AudioFormat.CHANNEL_IN_MONO,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSize * 2);
  
  // Create sliders
  int sliderHeight = height/12;
  int padding = sliderHeight/2;
  
  volumeSlider = new Slider(
    padding, 
    height - padding - sliderHeight*2, 
    width - padding*2, 
    sliderHeight, 
    10, 200, 
    "Gain");
  volumeSlider.setValue(100);
  
  fftBandsSlider = new Slider(
    padding, 
    height - padding - sliderHeight, 
    width - padding*2, 
    sliderHeight, 
    5, 50, 
    "Bands");
  fftBandsSlider.setValue(20);
  
  // Check permissions
  if (!hasPermission("android.permission.RECORD_AUDIO")) {
    requestPermission("android.permission.RECORD_AUDIO");
  } else {
    startMonitoring();
  }
}

void draw() {
  background(0);
  
  // Process audio
  fftBands = (int)fftBandsSlider.getValue();
  int samplesRead = audioRecord.read(audioBuffer, 0, bufferSize);
  
  if (samplesRead > 0) {
    // Calculate volume with gain
    float gain = volumeSlider.getValue()/100.0;
    float currentPeak = 0;
    
    for (int i = 0; i < samplesRead; i++) {
      float sample = abs(audioBuffer[i]) / 32768.0 * gain;
      if (sample > currentPeak) currentPeak = sample;
    }
    
    // Update peak voltage (with decay)
    if (currentPeak > peakVoltage) {
      peakVoltage = currentPeak;
    } else {
      peakVoltage *= peakDecay;
    }
    
    // Calculate average volume
    float sum = 0;
    for (int i = 0; i < samplesRead; i++) {
      sum += abs(audioBuffer[i]) / 32768.0 * gain;
    }
    float avgVolume = sum / samplesRead;
    
    // Smooth volume
    smoothVolume = smoothingFactor * avgVolume + (1-smoothingFactor)*smoothVolume;
    volumes[volumeIndex] = smoothVolume;
    volumeIndex = (volumeIndex + 1) % volumes.length;
    
    // Frequency detection
    currentFrequency = detectFrequency(audioBuffer, samplesRead);
    
    // Update spectrum
    updateSpectrum();
    
    // Draw visualization
    switch(currentMode) {
      case 0: drawSpectrum(); break;
      case 1: drawWaveform(); break;
      case 2: drawOscilloscope(); break;
    }
  }
  
  // Draw UI
  drawUI();
}

float detectFrequency(short[] buffer, int length) {
  int zeroCrossings = 0;
  boolean wasPositive = buffer[0] > 0;
  
  for (int i = 1; i < length; i++) {
    boolean isPositive = buffer[i] > 0;
    if (isPositive != wasPositive) {
      zeroCrossings++;
      wasPositive = isPositive;
    }
  }
  
  float duration = length / (float)sampleRate;
  float frequency = (zeroCrossings / 2.0) / duration;
  
  return (frequency > 50 && frequency < 2000) ? frequency : 0;
}

void updateSpectrum() {
  int bands = spectrum.length;
  int samplesPerBand = bufferSize/bands;
  
  for (int b = 0; b < min(fftBands, bands); b++) {
    float sum = 0;
    int start = b * samplesPerBand;
    int end = min((b+1)*samplesPerBand, bufferSize);
    
    for (int i = start; i < end; i++) {
      sum += abs(audioBuffer[i]) / 32768.0;
    }
    
    spectrum[b] = 0.2*(sum/(end-start)) + 0.8*spectrum[b];
  }
}

void drawWaveform() {
  noFill();
  stroke(100, 255, 100, 150);
  strokeWeight(2);
  beginShape();
  for (int i = 0; i < min(bufferSize, width); i++) {
    float x = map(i, 0, min(bufferSize, width), 0, width);
    float y = map(audioBuffer[i]/32768.0, -1, 1, height*0.3, height*0.7);
    vertex(x, y);
  }
  endShape();
}

void drawSpectrum() {
  noStroke();
  float bandWidth = width / (float)fftBands;
  
  for (int i = 0; i < min(fftBands, spectrum.length); i++) {
    float h = map(spectrum[i], 0, 0.5, 0, height*0.6);
    float x = i * bandWidth;
    float y = height - h;
    
    fill(spectrumColors[i % spectrumColors.length]);
    rect(x, y, bandWidth-2, h);
  }
}

void drawOscilloscope() {
  // Draw grid
  stroke(50);
  strokeWeight(1);
  for (int y = 0; y < height; y += height/10) {
    line(0, y, width, y);
  }
  for (int x = 0; x < width; x += width/10) {
    line(x, 0, x, height);
  }
  
  // Draw waveform
  noFill();
  stroke(0, 255, 0);
  strokeWeight(2);
  beginShape();
  for (int i = 0; i < min(bufferSize, width); i++) {
    float x = map(i, 0, min(bufferSize, width), 0, width);
    float y = map(audioBuffer[i]/32768.0, -1, 1, height*0.1, height*0.9);
    vertex(x, y);
  }
  endShape();
  
  // Draw peak voltage indicator
  float peakY = map(peakVoltage, 0, 1, height*0.9, height*0.1);
  stroke(255, 0, 0);
  line(0, peakY, width, peakY);
  
  // Display peak voltage
  fill(255, 0, 0);
  textSize(30);
  textAlign(RIGHT, TOP);
  text("Peak: " + nf(peakVoltage*100, 1, 1) + "%", width-20, 20);
}

void drawUI() {
  // Draw sliders if visible
  if (showSliders) {
    volumeSlider.draw();
    fftBandsSlider.draw();
  }
  
  // Mode button
  drawButton(width - 150, 50, 100, 50, "Mode", color(100));
  
  // Toggle sliders button
  drawButton(width - 150, 120, 100, 50, showSliders ? "Hide UI" : "Show UI", color(80, 80, 120));
  
  // Frequency display
  fill(200, 240, 255);
  textSize(36);
  textAlign(CENTER);
  text(nf(currentFrequency, 1, 1) + " Hz", width/2, 50);
  
  // Mode indicator
  String[] modeNames = {"Spectrum", "Waveform", "Oscilloscope"};
  fill(200);
  textSize(24);
  textAlign(LEFT, TOP);
  text("Mode: " + modeNames[currentMode], 20, 20);
}

void drawButton(float x, float y, float w, float h, String label, color btnColor) {
  fill(btnColor);
  rect(x, y, w, h, 10);
  fill(255);
  textSize(20);
  textAlign(CENTER, CENTER);
  text(label, x + w/2, y + h/2);
}

void mousePressed() {
  // Check slider controls
  if (showSliders) {
    if (volumeSlider.mousePressed()) return;
    if (fftBandsSlider.mousePressed()) return;
  }
  
  // Check mode button (top-right)
  if (mouseX > width - 150 && mouseX < width - 50 && 
      mouseY > 50 && mouseY < 100) {
    currentMode = (currentMode + 1) % 3; // Cycle between 3 modes
  }
  
  // Check UI toggle button (below mode button)
  if (mouseX > width - 150 && mouseX < width - 50 && 
      mouseY > 120 && mouseY < 170) {
    showSliders = !showSliders;
  }
}

void mouseDragged() {
  if (showSliders) {
    if (volumeSlider.mouseDragged()) return;
    if (fftBandsSlider.mouseDragged()) return;
  }
}

void mouseReleased() {
  if (showSliders) {
    volumeSlider.mouseReleased();
    fftBandsSlider.mouseReleased();
  }
}

class Slider {
  float x, y, w, h;
  float minVal, maxVal;
  float value;
  boolean dragging = false;
  String label;
  
  Slider(float x, float y, float w, float h, float minVal, float maxVal, String label) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.minVal = minVal;
    this.maxVal = maxVal;
    this.label = label;
    this.value = minVal;
  }
  
  void setValue(float val) {
    value = constrain(val, minVal, maxVal);
  }
  
  float getValue() {
    return value;
  }
  
  void draw() {
    // Track
    fill(100);
    rect(x, y, w, h, h/2);
    
    // Thumb
    float thumbX = map(value, minVal, maxVal, x, x + w);
    fill(200);
    ellipse(thumbX, y + h/2, h * 1.5, h * 1.5);
    
    // Label and value
    fill(255);
    textSize(h * 0.6);
    textAlign(LEFT, CENTER);
    text(label + ": " + nf(value, 1, 1), x, y - h);
  }
  
  boolean mousePressed() {
    if (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h) {
      dragging = true;
      return true;
    }
    return false;
  }
  
  boolean mouseDragged() {
    if (dragging) {
      value = constrain(map(mouseX, x, x + w, minVal, maxVal), minVal, maxVal);
      return true;
    }
    return false;
  }
  
  void mouseReleased() {
    dragging = false;
  }
}

void startMonitoring() {
  if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
    audioRecord.startRecording();
  }
}

void onPermissionGranted(String permission) {
  if (permission.equals("android.permission.RECORD_AUDIO")) {
    startMonitoring();
  }
}

void onPause() {
  if (audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING) {
    audioRecord.stop();
  }
  super.onPause();
}

void onDestroy() {
  if (audioRecord != null) {
    audioRecord.release();
  }
  super.onDestroy();
}


1 Like