ラベル ArEG の投稿を表示しています。 すべての投稿を表示
ラベル ArEG の投稿を表示しています。 すべての投稿を表示

2022年5月23日月曜日

ArEG 2台目製作 ファームウェア修正

Arduinoを使ったEnvelope Generatorの2台目を製作しました。


改良点
  • ロータリースイッチを小型のものにしてパネル幅を縮小。
  • LEDを3mmに変更。
  • ファームウェアのバグで出力波形がおかしくなる時があったのを修正。
  • GateDelayの最大値を大きくした。
  • 忘れていた手動GATEスイッチを付けた。

EGを複数台使う


VCAとVCFやVCOなどを別のADSR波形で変調することができ、音作りの幅が広がります。単体のシンセではVCA用とVCF用に2系統のEGが用意されていることが多いようです。

ArEGにはGate信号を遅延させて出力する仕組みを設けており、2台目のEGに遅延したGATE信号を送ることが出来ます。元のEGと遅延させたGATE信号を入力したEGのADSR出力を、ミキサーでミックスすると合成波形を得ることができます。

ADSRを合成した波形

CH1:GATE CH2:合成したADSR波形

ロータリースイッチの変更


前回は秋月の大型のロータリースイッチを使っていましたが、今回はAliExpressで購入した小型のものを使いました。


左:AliExpressで購入したもの 右:秋月で購入したもの

秋月のロータリースイッチはΦ26で、AliExpressのものは幅10mmです。パネル幅をHP10(幅50.5mm)からHP8(幅40.3mm)に縮小することができました。

LED


LEDを5mmから3mmに変更しました。ポリカパネルと5mmのLEDは接着剤を使わずに固定できましたが、アクリルパネルと3mmのLEDの場合は穴径の調整が難しくそのままではユルユルなので試しにアクリル接着剤(二塩化メチレン)を使ってみました。


慣れないせいか、付属の注射器をうまく操るのが難しくそこら中にかかってしまいました。拭き取りが甘いと透明アクリルが白濁します。

LED(エポキシもしくはシリコーン樹脂と思われます)との接着だと時間が経っても固着力が弱く、ジャックなど他の部品を取り付けるときにポロッと外れてしまいました。見栄えは悪いですが、結局ホットボンドで固定しました。

ADSR波形出力のバグ


Attackステート時にThresholdまで電圧が上る前にGate OffしてしまうとAttackのゲートが閉じず、ADSR波形が連続してしまうというバグがありました。


CH1:GATE CH2:ADSR

左の波形が誤動作している状態。

AttackステートからDecayステートを経ず、いきなりReleaseステートに入る場合があるのを忘れていたので修正しました。

GateDelayの最大値


暫定的に最大遅延102msにしていましたが、最大遅延1024msに変更しました。

プログラムの都合上、次のGATE信号が来るまでしか遅延できません。120BPMの4部音符だとGATEの間隔500ms、8部音符だと250msなのでわりと簡単にオーバーしてしまいますが、GateDelayのインジケータLEDがつきっぱなしになるので判別はできます。

ファームウェア(Arduinoスケッチ)

メモ


パネル加工時に間違えてロータリースイッチの位置が右(POT側)に寄りすぎて操作がしにくいです。ジャックの中間ぐらいにずらしたほうが良い。

テンポが早い場合調整幅が小さくなるので、GateDelayの設定はAカーブのPOTを使ったほうが良いかも。

2022年1月22日土曜日

ArEG Arduinoを使ったEnvelope Generatorの製作




仕様

  • 入力: GATE信号 L:GND H:+5V~+12V
  • 出力: ADSR波形 GND~最大+6V程度
  • 出力: ADSR波形反転 GND~最小-6V程度
  • 出力: GATE DELAY 入力のGATE信号を遅延して出力する 0ms~102ms
  • 電源: Eurorack16Pin

回路図


Arduinoのスケッチ

/*
 * ArEG
 *
 * 2022.01.11
 *
 */

#include <TimerOne.h>
#include <MsTimer2.h>

#define TITLE_STR1  ("ArEG")
#define TITLE_STR2  ("20220111")

// Pin definition
const int ThresholdPin = 2; // INT0
const int GateInPin = 3;    // INT1

const int DelayedGatePin = 4;

const int AttackPin = 5;
const int DecayPin = 6;
const int ReleasePin = 7;

const int DelayedLedPin = 12;
const int GateLedPin = 13;

const int DelayTimePin = 0;   // A0

enum EG_STATE {
  ST_ATTACK,
  ST_DECAY,
  ST_RELEASE
};

volatile enum EG_STATE state = ST_RELEASE;
volatile bool isStateChanged = true;

int delayTime = 1;

//-------------------------------------------------------------------
// Threshold
//
void threshold()
{
	state = ST_DECAY;
	isStateChanged = true;
}

//-------------------------------------------------------------------
// Delayed Gate
//
void delayedGateOn()
{
  Timer1.stop();
  digitalWrite(DelayedGatePin, HIGH);
  digitalWrite(DelayedLedPin, HIGH);
}

void delayedGateOff()
{
  MsTimer2::stop();
  digitalWrite(DelayedGatePin, LOW);
  digitalWrite(DelayedLedPin, LOW);
}

//-------------------------------------------------------------------
// Gate in
//
void gateIn()
{
  bool isGateOn = digitalRead(GateInPin);
  
  if (isGateOn) {
    // ADSR
    state = ST_ATTACK;
    isStateChanged = true;
    digitalWrite(GateLedPin, HIGH);

    // Delayed gate
    Timer1.initialize(delayTime * 1000UL);
    Timer1.attachInterrupt(delayedGateOn);   
  }
  else {
    // ADSR
    state = ST_RELEASE;
    isStateChanged = true;
    digitalWrite(GateLedPin, LOW);

    // Delayed gate
    MsTimer2::set(delayTime, delayedGateOff);
    MsTimer2::start();
  }
}

//-------------------------------------------------------------------
// Main routine
//
void setup()
{
  pinMode(AttackPin, INPUT);	// Hi-Z
  pinMode(DecayPin, INPUT);		// Hi-Z
  pinMode(ReleasePin, INPUT);	// Hi-Z

  pinMode(DelayedGatePin, OUTPUT);
  
  pinMode(GateLedPin, OUTPUT);
  pinMode(DelayedLedPin, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(GateInPin), gateIn, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ThresholdPin), threshold, FALLING);
}

void loop()
{
 
  if (isStateChanged) {
    isStateChanged = false;
    switch (state) {
    case ST_ATTACK:
	  pinMode(DecayPin, INPUT);   // Hi-Z
	  pinMode(ReleasePin, INPUT); // Hi-Z
	  pinMode(AttackPin, OUTPUT);
	  digitalWrite(AttackPin, HIGH);
      break;
    case ST_DECAY:
	  pinMode(AttackPin, INPUT);  // Hi-Z
	  pinMode(DecayPin, OUTPUT);
	  digitalWrite(DecayPin, LOW);
      break;
    case ST_RELEASE:
	  pinMode(ReleasePin, OUTPUT);
      digitalWrite(ReleasePin, LOW);
      break;
    }
  }
  
  delayTime = analogRead(DelayTimePin) / 10;
}

プログラムの都合上、Gate Delayは次のGATE信号がHになる前までしか設定出来ません。1音を越えて遅延させることが出来ないとうことです。

出力波形

Korg SQ-1よりGATE信号を出力してArEGの出力を観測しました。

ADSR

C1:ADSR C2:GATE

INVERT

C1:INVERT C2:GATE

GATE DELAY

C1:GATE C2:DELAYED GATE

遅延時間最大の設定です。⊿X=102.3ms。

メモ


マニュアルGATEスイッチを付け忘れた。

コンデンサを切り替えて時定数を変更するとフレーズの雰囲気がガラッと変わる。秋月のロータリースイッチはでかすぎるが。POTで連続可変にすることを妄想。

インジケーターLEDはあったほうが良い。5φのLEDは接着剤を使わなくても穴にグッと押し込めばハマってくれる。パネルはダイソーのクリップボードを切り出したもの(ポリカ)

2021年12月5日日曜日

ArEG Arduinoを使ったEnvelope Generatorの構想

以前もArduinoを利用してEnvelope Generatorを製作しました。 今回はその改良版を製作します。

ステート制御にGPIOを3ステートで利用


コンデンサへの充放電の制御にダイオードを使って電流の逆流を防止していましたが、AVRのGPIOはWriteモードの時H/L出力できるのに加えて、Readモードの時Hi-Zとなる3ステートバッファとなっています。Hi-Zを使えばダイオードを入れなくても電流が流れなくなります。

利点としては、555タイマーやロジックICを使った場合に比べ部品数を少なくできること、またダイオードを入れないのでダイオードの順方向電圧のため波形の底が0Vまで落ちない(0.6V程度になってしまう)問題が解消されます。

ADSR波形のステートについては「アナログ・エンベロープ・ジェネレーターをプログラムでシミュレートする」で少し書きました。

回路図

実験に使った回路のアナログ部分です。回路図のSWは実際にはAVRのGPIOです。

閾値の判定にコンパレーターを使う


Attackステートは出力波形がある電位(今回は3.3V)に達した時終了し、Decayステートに移行します。ADコンバーターを使って電位を測定することもできますが、処理に時間がかかるためAttackの立ち上がりが急峻な場合行き過ぎてしまいます。

コンパレーターを使って閾値を越えたかどうか判定し(閾値の前後でH/Lが切り替わる)コンパレーターの出力でAVRの割り込みを発生させる方法を取りました。

また、ヒステリシス付きコンパレーターでないとSustainレベルが高い場合に出力がバタついてしまいます。プログラムで回避する方法もあるのですが、ノイズ源になりそうなのでコンパレーターはヒステリシス付きにしました。

コンパレーター出力のバタ付く例

C1:ADSR出力 C2:Threshold

ヒステリシス付き反転入力コンパレーター部分のシミュレーション

定数はVh=3.3V Vl=3.0Vを目標に決めています。

実験したブレッドボード配線図

Arduinoのスケッチ
/*
 * ADSR Switch Test
 * 
 * スレッショルド割り込み: 負論理
 *
 * 2021.11.27
 *
 */

const int ThresholdPin = 2; // INT0

const int AttackPin = 5;
const int DecayPin = 6;
const int ReleasePin = 7;

volatile bool th = false; 

void threshold()
{
  th = true;
}

void setup()
{
  pinMode(AttackPin, INPUT);  // Hi-Z
  pinMode(DecayPin, INPUT);   // Hi-Z
  pinMode(ReleasePin, INPUT); // Hi-Z

  // 負論理
  attachInterrupt(digitalPinToInterrupt(ThresholdPin), threshold, FALLING);
  // 正論理
  //attachInterrupt(digitalPinToInterrupt(ThresholdPin), threshold, RISING);
    
  Serial.begin(9600);
}

void loop()
{
  th = false;
  
  // Attack
  pinMode(DecayPin, INPUT);   // Hi-Z
  pinMode(ReleasePin, INPUT); // Hi-Z
  pinMode(AttackPin, OUTPUT);
  digitalWrite(AttackPin, HIGH);

  while (th == false) // スレッショルド割り込みを待つ
    ;
  
  // Decay
  pinMode(AttackPin, INPUT); // Hi-Z
  pinMode(DecayPin, OUTPUT);
  digitalWrite(DecayPin, LOW);
  delay(80);
  
  // Release
  pinMode(ReleasePin, OUTPUT);
  digitalWrite(ReleasePin, LOW);
  delay(100);
}

検証のため、Gate信号入力はなくプログラムでDecay→Releaseを切り替えています。実用的には、delay()関数で制御している部分をGate信号の割り込み処理に置き換えます。

出力波形

C1:ADSR出力 C2:Threshold

反転入力 or 非反転入力のコンパレーター


ヒステリシス付きコンパレーターは反転入力、非反転入力がありますが入力が閾値を上回った場合、出力がHになるかLになるかの違いがあります。プログラム次第でどちらでも使えそうですが、非反転入力の場合は問題があります。

非反転入力のヒステリシス付きコンパレーターを使った回路

右下の「Threshold」出力の回路が異なります。

ヒステリシス付き非反転入力コンパレーター部分のシミュレーション

こちらも定数はVh=3.3V Vl=3.0Vを目標に決めています。(少しずれてしまっていますが本題とはあまり関係ない)

ブレッドボード配線図

この場合コンパレーターの出力が入力に影響を与えてしまいます。


C1:ADSR出力 C2:Threshold

ThresholdがHの場合とLの場合でDecayの波形が異なってしまい段差が出ています。

次図のように入力インピーダンスが高い反転入力の場合は同じパラメータでも影響が出ません。


ヒステリシス付きコンパレーターは正帰還を掛けるので、負帰還を掛ける増幅回路とは逆に、非反転入力の方が入力インピーダンスが低く、反転入力の方が入力インピーダンスが高くなります。