Midterm Project

image.png

image.png

/*
  Unified IO → Multi-Channel USB-MIDI (Nano 33 IoT / SAMD21)

  Channel mapping:
    CH 1: VL53L0X distance -> CC#1 (Mod Wheel), reversed (near=0, far=127)
    CH 2: Buttons D5..D10  -> Notes (C4..A4) NoteOn/Off
    CH 3: FSR A6           -> CC#11 (Expression)
    CH 4: Pot A3           -> CC#74 (Brightness)
    CH 5: Pot A2           -> CC#71 (Resonance, with simple noise smoothing)

  VL53L0X enable/disable by BUTTON on D3:
    - Hold D3 (LOW) to enable measuring
    - Release D3 to disable and IMMEDIATELY send CC=0
*/

#include <Wire.h>
#include <VL53L0X.h>
#include "MIDIUSB.h"

// ---------- VL53L0X + control button ----------
VL53L0X sensor;
const int PIN_XSHUT   = 2;    // VL53L0X XSHUT
const int PIN_EN_BTN  = 3;    // Enable button (to GND, INPUT_PULLUP)
bool sensorOn = false;

// 距离映射范围(mm):近->0,远->127
const int D_NEAR = 50;
const int D_FAR  = 600;

// ---------- Buttons D5..D10 ----------
const uint8_t BTN_PINS[]  = {5, 6, 7, 8, 9, 10};
const uint8_t BTN_COUNT   = sizeof(BTN_PINS) / sizeof(BTN_PINS[0]);
uint8_t btnPrev[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH}; // INPUT_PULLUP
const uint8_t BTN_NOTES[] = {60, 62, 64, 65, 67, 69}; // C4..A4

// ---------- Analog inputs ----------
const int PIN_FSR  = A6;
const int PIN_POT1 = A3;
const int PIN_POT2 = A2;

const int FSR_MIN = 450;
const int FSR_MAX = 900;

// ---------- MIDI config ----------
const uint8_t CH_VL53 = 1;
const uint8_t CH_BTN  = 2;
const uint8_t CH_FSR  = 3;
const uint8_t CH_POT1 = 4;
const uint8_t CH_POT2 = 5;

const uint8_t CC_VL53 = 1;   // Mod Wheel
const uint8_t CC_FSR  = 11;  // Expression
const uint8_t CC_POT1 = 74;  // Brightness
const uint8_t CC_POT2 = 71;  // Resonance

// ---------- Throttle / Deadband ----------
const uint16_t ANALOG_SEND_INTERVAL_MS = 8;  // ~125 Hz
const uint8_t  CC_DEADBAND       = 1;        // 通用
const uint8_t  CC_DEADBAND_POT2  = 2;        // A2 单独更宽一点

uint32_t tLastFSR = 0, tLastPOT1 = 0, tLastPOT2 = 0, tLastVL = 0;
int lastFSRcc = -1, lastPOT1cc = -1, lastPOT2cc = -1, lastVLcc = -1;
uint8_t invalidCount = 0;

// ---------- A2 简单去噪参数 ----------
float pot2Filt = -1.0f;
const float POT2_ALPHA = 0.20f;  // 0.1 更稳、0.3 更灵敏

// ---------- Utils ----------
static inline uint8_t clamp127(int v) {
  if (v < 0) return 0;
  if (v > 127) return 127;
  return (uint8_t)v;
}

static inline int median3(int a, int b, int c) {
  // 返回三个数的中位数(不分支版也行,这里直观可靠)
  if (a > b) { int t=a; a=b; b=t; }
  if (b > c) { int t=b; b=c; c=t; }
  if (a > b) { int t=a; a=b; b=t; }
  return b;
}

static inline int analogReadMedian3(uint8_t pin) {
  int a = analogRead(pin);
  delayMicroseconds(200);
  int b = analogRead(pin);
  delayMicroseconds(200);
  int c = analogRead(pin);
  return median3(a, b, c);
}

void hardResetVL() {
  digitalWrite(PIN_XSHUT, LOW);
  delay(10);
  digitalWrite(PIN_XSHUT, HIGH);
  delay(10);
}

bool enableVL() {
  hardResetVL();
  if (!sensor.init()) {
    Serial.println("VL53L0X init failed");
    return false;
  }
  sensor.setTimeout(200);
  sensor.setMeasurementTimingBudget(20000); // 20ms ≈ 50Hz
  delay(50);
  sensorOn = true;
  lastVLcc = -1;
  Serial.println("VL53 ENABLED");
  return true;
}

void disableVL() {
  sensorOn = false;
  digitalWrite(PIN_XSHUT, LOW);
  Serial.println("VL53 DISABLED");
}

// ---------- MIDI helpers ----------
void midiSendCC(uint8_t ch, uint8_t cc, uint8_t val) {
  midiEventPacket_t evt = {0x0B, (uint8_t)(0xB0 | ((ch - 1) & 0x0F)), cc, val};
  MidiUSB.sendMIDI(evt);
}
void midiSendNoteOn(uint8_t ch, uint8_t note, uint8_t vel) {
  midiEventPacket_t evt = {0x09, (uint8_t)(0x90 | ((ch - 1) & 0x0F)), note, vel};
  MidiUSB.sendMIDI(evt);
}
void midiSendNoteOff(uint8_t ch, uint8_t note, uint8_t vel) {
  midiEventPacket_t evt = {0x08, (uint8_t)(0x80 | ((ch - 1) & 0x0F)), note, vel};
  MidiUSB.sendMIDI(evt);
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  Wire.begin();
  Wire.setClock(400000);

  pinMode(PIN_XSHUT, OUTPUT);
  pinMode(PIN_EN_BTN, INPUT_PULLUP);
  digitalWrite(PIN_XSHUT, LOW);

  for (uint8_t i = 0; i < BTN_COUNT; i++) {
    pinMode(BTN_PINS[i], INPUT_PULLUP);
  }

  Serial.println("Hold D3 to enable VL53; release to disable (sends CC=0). USB-MIDI ready.");
}

void loop() {
  // --- VL53 enable/disable by button ---
  bool enablePressed = (digitalRead(PIN_EN_BTN) == LOW);
  if (enablePressed && !sensorOn) {
    enableVL();
  } else if (!enablePressed && sensorOn) {
    disableVL();
    if (lastVLcc != 0) {
      midiSendCC(CH_VL53, CC_VL53, 0);
      lastVLcc = 0;
      MidiUSB.flush();
    }
  }

  const uint32_t now = millis();

  // --- VL53 distance -> CC on CH1 (near=0, far=127) ---
  if (sensorOn && (now - tLastVL >= 10)) {
    tLastVL = now;
    uint16_t dist = sensor.readRangeSingleMillimeters();
    if (!sensor.timeoutOccurred() && dist < 8190) {
      int d = constrain((int)dist, D_NEAR, D_FAR);
      int cc = map(d, D_NEAR, D_FAR, 0, 127);
      cc = clamp127(cc);
      if (lastVLcc < 0 || abs(cc - lastVLcc) > CC_DEADBAND) {
        lastVLcc = cc;
        midiSendCC(CH_VL53, CC_VL53, cc);
      }
    }
  }

  // --- Buttons -> Notes on CH2 ---
  for (uint8_t i = 0; i < BTN_COUNT; i++) {
    uint8_t s = digitalRead(BTN_PINS[i]);
    if (s != btnPrev[i]) {
      btnPrev[i] = s;
      uint8_t note = BTN_NOTES[i];
      if (s == LOW) midiSendNoteOn(CH_BTN, note, 100);
      else          midiSendNoteOff(CH_BTN, note, 0);
    }
  }

  // --- FSR -> CC on CH3 ---
  if (now - tLastFSR >= ANALOG_SEND_INTERVAL_MS) {
    tLastFSR = now;
    int raw = analogRead(PIN_FSR);
    int cc  = map(raw, FSR_MIN, FSR_MAX, 0, 127);
    cc = clamp127(cc);
    if (lastFSRcc < 0 || abs(cc - lastFSRcc) > CC_DEADBAND) {
      lastFSRcc = cc;
      midiSendCC(CH_FSR, CC_FSR, cc);
    }
  }

  // --- Pot1 A3 -> CC on CH4 ---
  if (now - tLastPOT1 >= ANALOG_SEND_INTERVAL_MS) {
    tLastPOT1 = now;
    int raw = analogRead(PIN_POT1);
    int cc  = map(raw, 0, 1023, 0, 127);
    cc = clamp127(cc);
    if (lastPOT1cc < 0 || abs(cc - lastPOT1cc) > CC_DEADBAND) {
      lastPOT1cc = cc;
      midiSendCC(CH_POT1, CC_POT1, cc);
    }
  }

  // --- Pot2 A2 -> CC on CH5 (median-of-3 + IIR smoothing) ---
  if (now - tLastPOT2 >= ANALOG_SEND_INTERVAL_MS) {
    tLastPOT2 = now;

    // 1) median-of-3 去除尖峰
    int rawMed = analogReadMedian3(PIN_POT2); // 0..1023

    // 2) 一阶低通 IIR:filt += α*(raw - filt)
    if (pot2Filt < 0) pot2Filt = rawMed;  // 首次赋初值
    pot2Filt += POT2_ALPHA * (rawMed - pot2Filt);

    // 3) 映射到 0..127(使用浮点更平滑)
    int cc = (int)lround((pot2Filt / 1023.0f) * 127.0f);
    cc = clamp127(cc);

    if (lastPOT2cc < 0 || abs(cc - lastPOT2cc) > CC_DEADBAND_POT2) {
      lastPOT2cc = cc;
      midiSendCC(CH_POT2, CC_POT2, (uint8_t)cc);
    }
  }

  MidiUSB.flush();
  delay(2);
}