// ─────────────────────────────────────────────────────────────────────
// FM Test Sketch — Retro Radio
//
// Standalone test for the TEA5767 + I2S-ADC bridge audio path.
// Drop this in its own .ino, flash, and watch the Serial Monitor at
// 115200 baud. It walks through each stage of the FM signal chain so
// you can see exactly where (if anywhere) things break.
//
// Wiring assumed:
//   TEA5767 SDA       → GPIO 21
//   TEA5767 SCL       → GPIO 22
//   TEA5767 audio (R) → GPIO 35
//   TEA5767 audio (B) → GND
//   TEA5767 VCC       → 5V
//   TEA5767 GND       → GND
//
//   MAX98357A BCLK    → GPIO 13
//   MAX98357A LRC     → GPIO 14
//   MAX98357A DIN     → GPIO 12
//   MAX98357A VIN     → 5V
//   MAX98357A GND     → GND
//
//   Telescoping antenna → TEA5767 antenna jack
//
// Commands you can type in Serial Monitor while it's running:
//   t101.7   tune to a specific frequency in MHz
//   u        tune up   0.1 MHz
//   d        tune down 0.1 MHz
//   s        print status (signal level, stereo flag)
//   r        print one ADC reading (raw, useful for sanity-checking)
//   a        toggle audio passthrough on/off
// ─────────────────────────────────────────────────────────────────────

#include <Wire.h>
#include "driver/i2s.h"
#include "driver/adc.h"

#define FM_SDA          21
#define FM_SCL          22
#define FM_ADC_PIN      35
#define FM_ADC_CHANNEL  ADC1_CHANNEL_7
#define TEA5767_ADDR    0x60

#define I2S_BCLK        13
#define I2S_LRC         14
#define I2S_DIN         12

#define I2S_FM_PORT     I2S_NUM_0    // FM ADC bridge — only port 0 supports it
#define I2S_OUT_PORT    I2S_NUM_1    // MAX98357A output
#define SAMPLE_RATE     44100

float    fmFrequencyMHz = 101.7f;
bool     fmModulePresent = false;
bool     fmI2SReady     = false;
bool     outI2SReady    = false;
bool     audioPassthrough = true;

// ─────────────────────────────────────────────────────────────────────
// TEA5767 driver
// ─────────────────────────────────────────────────────────────────────

bool tea5767Write(float freqMHz) {
  unsigned long freqHz = (unsigned long)(freqMHz * 1000000.0f);
  unsigned long pll = (4UL * (freqHz + 225000UL)) / 32768UL;

  uint8_t b[5];
  b[0] = (pll >> 8) & 0x3F;
  b[1] =  pll & 0xFF;
  b[2] = 0x90;   // high-side LO, search up
  b[3] = 0x10;   // XTAL=1
  b[4] = 0x00;

  Wire.beginTransmission(TEA5767_ADDR);
  Wire.write(b, 5);
  uint8_t err = Wire.endTransmission();
  if (err != 0) {
    Serial.print("  I2C write error: ");
    Serial.println(err);   // 2 = NACK on address; 3 = NACK on data
    return false;
  }
  return true;
}

bool tea5767ReadStatus(uint8_t* outSignal, bool* outStereo, float* outFreq) {
  uint8_t b[5];
  int got = Wire.requestFrom((uint8_t)TEA5767_ADDR, (uint8_t)5);
  if (got != 5) {
    Serial.print("  I2C read got ");
    Serial.print(got);
    Serial.println(" bytes instead of 5");
    return false;
  }
  for (int i = 0; i < 5; i++) b[i] = Wire.read();

  *outSignal = (b[3] >> 4) & 0x0F;
  *outStereo = (b[2] & 0x80) != 0;

  // Decode current PLL back to a frequency for cross-checking.
  unsigned long pll = ((unsigned long)(b[0] & 0x3F) << 8) | b[1];
  unsigned long freqHz = (pll * 32768UL) / 4UL - 225000UL;
  *outFreq = freqHz / 1000000.0f;
  return true;
}

void printStatus() {
  uint8_t signal = 0;
  bool stereo = false;
  float reportedFreq = 0;
  if (tea5767ReadStatus(&signal, &stereo, &reportedFreq)) {
    Serial.print("  Tuned at ");      Serial.print(reportedFreq, 2); Serial.println(" MHz");
    Serial.print("  Signal level: ");  Serial.print(signal); Serial.println(" / 15");
    Serial.print("  Stereo: ");        Serial.println(stereo ? "yes" : "no");
  }
}

// ─────────────────────────────────────────────────────────────────────
// I2S setup — FM input on port 0, MAX98357A output on port 1
// ─────────────────────────────────────────────────────────────────────

bool setupFMInputI2S() {
  i2s_config_t cfg = {};
  cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN);
  cfg.sample_rate = SAMPLE_RATE;
  cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
  cfg.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
  cfg.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S;
  cfg.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
  cfg.dma_buf_count = 4;
  cfg.dma_buf_len = 512;
  cfg.use_apll = false;

  esp_err_t err;
  err = i2s_driver_install(I2S_FM_PORT, &cfg, 0, NULL);
  if (err != ESP_OK) { Serial.printf("  install err: %d\n", err); return false; }
  err = i2s_set_adc_mode(ADC_UNIT_1, FM_ADC_CHANNEL);
  if (err != ESP_OK) { Serial.printf("  adc_mode err: %d\n", err); return false; }
  err = i2s_adc_enable(I2S_FM_PORT);
  if (err != ESP_OK) { Serial.printf("  adc_enable err: %d\n", err); return false; }
  return true;
}

bool setupOutputI2S() {
  i2s_config_t cfg = {};
  cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
  cfg.sample_rate = SAMPLE_RATE;
  cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
  cfg.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
  cfg.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S;
  cfg.intr_alloc_flags = 0;
  cfg.dma_buf_count = 8;
  cfg.dma_buf_len = 256;
  cfg.use_apll = false;

  esp_err_t err = i2s_driver_install(I2S_OUT_PORT, &cfg, 0, NULL);
  if (err != ESP_OK) { Serial.printf("  install err: %d\n", err); return false; }

  i2s_pin_config_t pins = {};
  pins.bck_io_num = I2S_BCLK;
  pins.ws_io_num = I2S_LRC;
  pins.data_out_num = I2S_DIN;
  pins.data_in_num = I2S_PIN_NO_CHANGE;
  err = i2s_set_pin(I2S_OUT_PORT, &pins);
  if (err != ESP_OK) { Serial.printf("  set_pin err: %d\n", err); return false; }
  return true;
}

// Read one ADC sample (raw 12-bit) for debugging.
int readOneAdcSample() {
  uint16_t buf[16];
  size_t got = 0;
  i2s_read(I2S_FM_PORT, buf, sizeof(buf), &got, pdMS_TO_TICKS(100));
  if (got == 0) return -1;
  return buf[0] & 0x0FFF;
}

// ─────────────────────────────────────────────────────────────────────
// Audio passthrough loop
// ─────────────────────────────────────────────────────────────────────

void pumpAudio() {
  if (!fmI2SReady || !outI2SReady) return;

  static uint16_t adcBuf[256];
  size_t bytesRead = 0;
  esp_err_t err = i2s_read(I2S_FM_PORT, adcBuf, sizeof(adcBuf), &bytesRead, 0);
  if (err != ESP_OK || bytesRead == 0) return;

  int samples = bytesRead / 2;
  static int16_t stereoBuf[512];
  for (int i = 0; i < samples && i < 256; i++) {
    int16_t raw = adcBuf[i] & 0x0FFF;
    int32_t centred = (int32_t)raw - 2048;
    int16_t scaled = (int16_t)(centred << 4);
    stereoBuf[i*2]     = scaled;
    stereoBuf[i*2 + 1] = scaled;
  }
  size_t written = 0;
  i2s_write(I2S_OUT_PORT, stereoBuf, samples * 4, &written, 0);
}

// ─────────────────────────────────────────────────────────────────────
// Setup — runs the diagnostic steps in order
// ─────────────────────────────────────────────────────────────────────

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("══════════════════════════════════════════");
  Serial.println("  FM Test Sketch");
  Serial.println("══════════════════════════════════════════");

  // ── Step 1: I2C bus + TEA5767 presence ─────────────────────────────
  Serial.println();
  Serial.println("[1] Bringing up I2C and checking for TEA5767…");
  Wire.begin(FM_SDA, FM_SCL);
  Wire.setClock(100000);
  delay(50);

  Wire.beginTransmission(TEA5767_ADDR);
  uint8_t err = Wire.endTransmission();
  if (err == 0) {
    Serial.println("  ✓ TEA5767 responded on I2C address 0x60");
    fmModulePresent = true;
  } else {
    Serial.print("  ✗ No response from 0x60 (Wire err ");
    Serial.print(err);
    Serial.println(")");
    Serial.println("    → Check VCC=5V, GND, SDA→21, SCL→22, common ground");
    return;
  }

  // ── Step 2: Tune to default station ────────────────────────────────
  Serial.println();
  Serial.print("[2] Tuning to "); Serial.print(fmFrequencyMHz, 1); Serial.println(" MHz…");
  if (tea5767Write(fmFrequencyMHz)) {
    Serial.println("  ✓ Tune command sent");
  } else {
    Serial.println("  ✗ Tune command failed");
    return;
  }
  delay(150);   // let PLL lock

  // ── Step 3: Read status back ───────────────────────────────────────
  Serial.println();
  Serial.println("[3] Reading status…");
  printStatus();
  Serial.println("    → Signal 6+ is decent; 0–2 means no station here or no antenna");

  // ── Step 4: Bring up I2S-ADC bridge on port 0 ──────────────────────
  Serial.println();
  Serial.println("[4] Initializing I2S-ADC bridge on port 0 (GPIO 35)…");
  fmI2SReady = setupFMInputI2S();
  if (fmI2SReady) Serial.println("  ✓ ADC bridge running");
  else {
    Serial.println("  ✗ ADC bridge failed — audio path won't work");
    return;
  }

  // ── Step 5: Sample the audio line ──────────────────────────────────
  Serial.println();
  Serial.println("[5] Sampling audio line on GPIO 35…");
  int minV = 9999, maxV = 0;
  unsigned long t0 = millis();
  while (millis() - t0 < 500) {
    int v = readOneAdcSample();
    if (v < 0) continue;
    if (v < minV) minV = v;
    if (v > maxV) maxV = v;
  }
  Serial.print("  Over 500 ms: min="); Serial.print(minV);
  Serial.print(", max="); Serial.print(maxV);
  Serial.print(", swing="); Serial.println(maxV - minV);
  if (maxV - minV < 50) {
    Serial.println("  ✗ Tiny swing — audio line looks dead or DC-only");
    Serial.println("    → Check red wire from TEA5767 audio jack → GPIO 35");
    Serial.println("    → Check black wire from audio jack → GND");
  } else if (maxV - minV < 200) {
    Serial.println("  ~ Weak signal. Tuner OK but station might be weak");
  } else {
    Serial.println("  ✓ Healthy audio swing — real signal present");
  }

  // ── Step 6: Bring up output I2S on port 1 ──────────────────────────
  Serial.println();
  Serial.println("[6] Initializing output I2S on port 1 (MAX98357A)…");
  outI2SReady = setupOutputI2S();
  if (outI2SReady) Serial.println("  ✓ Output I2S running");
  else {
    Serial.println("  ✗ Output I2S failed");
    return;
  }

  // ── Done ───────────────────────────────────────────────────────────
  Serial.println();
  Serial.println("══════════════════════════════════════════");
  Serial.println("  All systems go — audio should be playing");
  Serial.println("══════════════════════════════════════════");
  Serial.println("Commands: t<freq>  u  d  s  r  a");
}

// ─────────────────────────────────────────────────────────────────────
// Main loop
// ─────────────────────────────────────────────────────────────────────

void loop() {
  if (audioPassthrough) pumpAudio();

  if (Serial.available()) {
    char cmd = Serial.read();
    if (cmd == 't') {
      String s = Serial.readStringUntil('\n');
      s.trim();
      float f = s.toFloat();
      if (f >= 87.5f && f <= 108.0f) {
        fmFrequencyMHz = f;
        Serial.print("Tuning to "); Serial.print(f, 1); Serial.println(" MHz");
        tea5767Write(f);
        delay(150);
        printStatus();
      } else {
        Serial.println("Bad frequency (use 87.5–108.0)");
      }
    } else if (cmd == 'u') {
      fmFrequencyMHz += 0.1f;
      if (fmFrequencyMHz > 108.0f) fmFrequencyMHz = 108.0f;
      Serial.print("Up to "); Serial.print(fmFrequencyMHz, 1); Serial.println(" MHz");
      tea5767Write(fmFrequencyMHz);
      delay(150);
      printStatus();
    } else if (cmd == 'd') {
      fmFrequencyMHz -= 0.1f;
      if (fmFrequencyMHz < 87.5f) fmFrequencyMHz = 87.5f;
      Serial.print("Down to "); Serial.print(fmFrequencyMHz, 1); Serial.println(" MHz");
      tea5767Write(fmFrequencyMHz);
      delay(150);
      printStatus();
    } else if (cmd == 's') {
      printStatus();
    } else if (cmd == 'r') {
      int v = readOneAdcSample();
      Serial.print("ADC raw: "); Serial.println(v);
    } else if (cmd == 'a') {
      audioPassthrough = !audioPassthrough;
      Serial.println(audioPassthrough ? "Audio ON" : "Audio OFF");
    }
  }
}
