見出し画像

HAGIWO MOD1 : ADSR Envelope Generator | DIY Eurorack Modular Synth

HAGIWO MOD1 ADSR Envelope Genaratorの紹介

MOD1について

Eurorack Modular Synth用のDIY向け汎用CV/Gateモジュール。
ユーザーが自分でプログラミングをして、自由に機能を実装する使い方を想定している。
下記リンクを参照。

ADSR Envelope Genarator機能紹介

HAGIWO MOD1 ADSR Envelope generator Ver1.0


ADSR output and 2 gate out.

The first gate out outputs HIGH only during the attack phase. This can be used as a gate-to-trigger conversion.

The second gate out outputs HIGH during the decay to release phases. This can be used as a gate delay or trigger-to-gate conversion.

--Pin assign---
POT1  A0  atk time
POT2  A1  decay time
POT3  A2  rel time
F1    D17  trig in
F2    D9  atk phase gate out
F3    D10  decay ~ rel phase gate out
F4    D11 EG output
BUTTON    sustain level (6 range)
LED       EG output
EEPROM    sustain level

生成AIプロンプト

ソースコードは全てChatGPT-o1で生成しました。
プロンプトを公開しています。

ソースコード

/* 
HAGIWO MOD1 ADSR Envelope generator Ver1.0

ADSR output and 2 gate out.
The first gate out outputs HIGH only during the attack phase. This can be used as a gate-to-trigger conversion.
The second gate out outputs HIGH during the decay to release phases. This can be used as a gate delay or trigger-to-gate conversion.

--Pin assign---
POT1  A0  atk time
POT2  A1  decay time
POT3  A2  rel time
F1    D17  trig in
F2    D9  atk phase gate out
F3    D10  decay ~ rel phase gate out
F4    D11 EG output
BUTTON    sustain level (6 range)
LED       EG output
EEPROM    sustain level
*/

#include <EEPROM.h> // Include EEPROM library for read/write

// ========== Pin Definitions ==========
int triggerPin = 17;  // Gate input pin
int buttonPin  = 4;   // Button input pin, uses internal pull-up (LOW pressed)
int attackPin  = A0;  // Attack pot
int decayPin   = A1;  // Decay pot
int releasePin = A2;  // Release pot

int attackLedPin = 9;   // Indicator for ATTACK state
int decayLedPin  = 10;  // Indicator for DECAY->RELEASE states

// ========== Sustain Levels (6 examples) ==========
int sustainLevels[6] = {
  146, 292, 438, 584, 730, 1023
};
int sustainIndex = 0;
int sustainLevel = sustainLevels[sustainIndex];

// ========== ADSR State Machine ==========
enum {IDLE, ATTACK, DECAY, SUSTAIN, RELEASE};
int envelopeState = IDLE;
float envelope = 0.0;  // 0..1023

// ========== Timing & Rates ==========
unsigned long lastUpdateTime = 0;  // For ADSR step every 1ms
float attackRate  = 0.0;
float decayRate   = 0.0;
float releaseRate = 0.0;

// Attack time=0 flag (A0 <=10 => instant to max)
bool attackIsZero = false;

// ========== Button Debounce Variables ==========
bool lastButtonState = HIGH;             // pull-up => default HIGH
unsigned long buttonPreviousMillis = 0;
const unsigned long debounceDelay  = 30; // ms

// ========== Function Prototypes ==========
void updateADSR(); 
void handleButtonInput();

void setup() {
  // Configure Timer2 for ~62.5kHz PWM
  // Fast PWM 8-bit, no prescaler => 16MHz / 256 = 62.5kHz
  TCCR2A = 0;
  TCCR2B = 0;

  // Set Fast PWM mode
  TCCR2A |= (1 << WGM21) | (1 << WGM20);
  // No prescaler
  TCCR2B |= (1 << CS20);
  // Non-inverting PWM on OC2A / OC2B
  TCCR2A |= (1 << COM2A1) | (1 << COM2B1);

  pinMode(triggerPin, INPUT);       
  pinMode(buttonPin, INPUT_PULLUP);  // Internal pull-up => LOW when pressed
  pinMode(11, OUTPUT);               // OCR2A => Envelope output
  pinMode(3, OUTPUT);                // OCR2B => LED indicator
  pinMode(attackLedPin, OUTPUT);     // D9 => ATTACK indicator
  pinMode(decayLedPin, OUTPUT);      // D10 => DECAY/RELEASE indicator

  // Initialize duty cycle
  OCR2A = 0;
  OCR2B = 0;

  // Read sustainIndex from EEPROM at startup
  sustainIndex = EEPROM.read(0); // Read index from address 0
  if (sustainIndex >= 6) {
    sustainIndex = 0; // Ensure valid range
  }
  sustainLevel = sustainLevels[sustainIndex];
}

void loop() {
  // ----- (1) Handle button to update sustain level & EEPROM -----
  handleButtonInput();

  // ----- (2) Read gate input -----
  bool gate = digitalRead(triggerPin);

  // Gate rising/falling detection
  static bool prevGate = false;
  if (gate && !prevGate) {
    // Gate rising => Attack
    envelopeState = ATTACK;
    // Do not reset envelope => start from current value
  }
  if (!gate && prevGate) {
    // Gate falling => Release
    if (envelopeState != IDLE) {
      envelopeState = RELEASE;
    }
  }
  prevGate = gate;

  // ----- (3) Read pot values for Attack/Decay/Release -----
  int aVal = analogRead(attackPin);
  int dVal = analogRead(decayPin);
  int rVal = analogRead(releasePin);

  // If Attack pot <=10 => Attack=0ms => envelope instantly 1023
  if (aVal <= 10) {
    attackIsZero = true;
    attackRate   = 999999.0; // Arbitrary large
  } else {
    attackIsZero = false;
    int attackTimeMs = map(aVal, 0, 1023, 1, 1000);
    attackRate       = 1023.0 / attackTimeMs;
  }

  // Decay
  int decayTimeMs   = map(dVal, 0, 1023, 1, 1000);
  float decDist     = 1023.0 - sustainLevel;
  decayRate         = decDist / decayTimeMs;

  // Release
  int releaseTimeMs = map(rVal, 0, 1023, 1, 1000);
  float relDist     = (float)sustainLevel;
  releaseRate       = relDist / releaseTimeMs;

  // ----- (4) Update ADSR at ~1ms intervals -----
  unsigned long currentMillis = millis();
  if (currentMillis - lastUpdateTime >= 1) {
    lastUpdateTime = currentMillis;
    updateADSR(); // Update envelope state machine

    // Write envelope to PWM: 0..1023 => 0..255
    int duty = (int)(envelope / 4.0);
    if (duty < 0) duty = 0;
    if (duty > 255) duty = 255;
    OCR2A = duty;
    OCR2B = duty;

    // Update ATTACK indicator (D9) => HIGH only in ATTACK
    if (envelopeState == ATTACK) {
      digitalWrite(attackLedPin, HIGH);
    } else {
      digitalWrite(attackLedPin, LOW);
    }

    // Update DECAY/RELEASE indicator (D10) => HIGH if DECAY,SUSTAIN,RELEASE
    if (envelopeState == DECAY || envelopeState == SUSTAIN || envelopeState == RELEASE) {
      digitalWrite(decayLedPin, HIGH);
    } else {
      digitalWrite(decayLedPin, LOW);
    }
  }
}

/*
   updateADSR()
   This function updates the envelope based on the current state.
   Attack starts from the current envelope value.
*/
void updateADSR() {
  switch (envelopeState) {
    case IDLE:
      envelope = 0.0;
      break;

    case ATTACK:
      if (attackIsZero) {
        envelope = 1023.0;
        envelopeState = DECAY;
      } else {
        envelope += attackRate;
        if (envelope >= 1023.0) {
          envelope = 1023.0;
          envelopeState = DECAY;
        }
      }
      break;

    case DECAY:
      envelope -= decayRate;
      if (envelope <= (float)sustainLevel) {
        envelope = (float)sustainLevel;
        envelopeState = SUSTAIN;
      }
      break;

    case SUSTAIN:
      // Keep sustain level until gate is released
      break;

    case RELEASE:
      if (envelope > 0.0) {
        envelope -= releaseRate;
      }
      if (envelope <= 0.0) {
        envelope = 0.0;
        envelopeState = IDLE;
      }
      break;
  }
}

/*
   handleButtonInput()
   This function handles the button press using a debounce approach.
   The button is internal pull-up => reads HIGH normally, LOW when pressed.
   Also stores sustainIndex to EEPROM on press.
*/
void handleButtonInput() {
  int reading = digitalRead(buttonPin); // LOW when pressed
  unsigned long currentMillis = millis();

  // Debounce check
  if (currentMillis - buttonPreviousMillis > debounceDelay) {
    // Detect transition HIGH->LOW (button press)
    if (reading == LOW && lastButtonState == HIGH) {
      sustainIndex++;
      if (sustainIndex >= 6) sustainIndex = 0;
      sustainLevel = sustainLevels[sustainIndex];
      EEPROM.write(0, sustainIndex); // Store index to EEPROM
      buttonPreviousMillis = currentMillis;
    }
  }
  lastButtonState = reading;
}

いいなと思ったら応援しよう!