製作モジュール一覧

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

2021年6月23日水曜日

アナログ・エンベロープ・ジェネレーターをプログラムでシミュレートする

アナログ回路とArduinoで作ったエンベロープ・ジェネレーターArduino_EGを利用していますが、今回はそれをソフトウェア的にシミュレートしました。

一番簡単なのはAttack、Release、Sustain、Relaseをパラメータとして線形補間することですが、よりアナログ的にCRの充放電をプログラムで表現しました。

LTSpiceによるArduio_EGのシミュレーション


Arduino_EGの動作をLTspiceでシミュレーションしたものです。

シミュレーション回路図

過渡解析

数式に置き換える


プログラムの出力をグラフ化してコメントを加えたものです。



ADSR波形ですが、曲線はA、D、Rの3つです。SustainはAttackの終わりからGateがOffになるまでコンデンサを放電させる目標の電位です。

コンデンサに充電するステート(Attack)はある電位に達した時終了し、コンデンサから放電するステート(Decay)に切り替わりますが、今回は基準とする電位を0.6としています。

v3は前回の波形の最終的な電位です。初期値は0としています。

C++を使ったほうがスッキリとしますが、組み込み用途を想定してCで書きました。

プログラム
/*
 * ADSR Envelope Test
 *
 */

#include <stdio.h>
#include <stdint.h>
#include <math.h>

#define ATTACK_THRESHOLD  (0.6f)

typedef enum {
	stNone,
	stAttack,
	stDecay,
	stRelease
} EGState;

typedef struct {
	float attackR;			// kΩ
	float decayR;			// kΩ
	float sustainLevel;		// 0.0f ~ ATTACK_THTESHOLD
	float releaseR;			// kΩ
	float capacitance;		// uF

	EGState state;
	float samplingPeriod;	// second
	uint32_t tick;
	float amplitude;
	float v1;
	float v2;
	float v3;
	float t1;
	float t2;
} Envelope;

void Envelope_init(Envelope* p_env, float samplingPeriod);
float Envelope_step(Envelope* p_env);
void Envelope_gateOn(Envelope* p_env);
void Envelope_gateOff(Envelope* p_env);

void Envelope_init(Envelope* p_env, float samplingPeriod)
{
	p_env->samplingPeriod = samplingPeriod;
	p_env->state = stNone;
	p_env->amplitude = 0.0f;
	p_env->v3 = 0.0f;
}

void Envelope_gateOn(Envelope* p_env)
{
	p_env->v3 = p_env->amplitude;
	p_env->tick = 0;
	p_env->state = stAttack;
}

void Envelope_gateOff(Envelope* p_env)
{
	p_env->v2 = p_env->amplitude;
	p_env->t2 = p_env->tick * p_env->samplingPeriod;
	p_env->state = stRelease;
}

static float attack(Envelope* p_env, float t)
{
	float tau = p_env->attackR * p_env->capacitance / 1000.0f;
	return 1.0f - ( 1.0f - p_env->v3 ) * expf( -t / tau );
}

static float decay(Envelope* p_env, float t)
{
	float tau = p_env->decayR * p_env->capacitance / 1000.0f;
	return ( p_env->v1 - p_env->sustainLevel ) * expf( -( t - p_env->t1 ) / tau ) + p_env->sustainLevel;
}

static float release(Envelope* p_env, float t)
{
	float tau = p_env->releaseR * p_env->capacitance / 1000.0f;
	return p_env->v2 * expf( -( t - p_env->t2 ) / tau );
}

float Envelope_step(Envelope* p_env)
{
	float t = p_env->tick * p_env->samplingPeriod;

	switch (p_env->state) {
	case stNone:
		break;
	case stAttack:
		p_env->amplitude = attack(p_env, t);
		if (p_env->amplitude > ATTACK_THRESHOLD) {
			p_env->v1 = p_env->amplitude;
			p_env->t1 = t;
			p_env->state = stDecay;
		}
		break;
	case stDecay:
		p_env->amplitude = decay(p_env, t);
		break;
	case stRelease:
		p_env->amplitude = release(p_env, t);
		break;
	}

	p_env->tick++;

	if (p_env->tick == INT32_MAX) {
		perror("tick overflow\n");
	}

	return p_env->amplitude;
}

#define LOOP_N (3)
#define STEP_LENGTH (1000)
#define GATE_LENGTH (500)
#define SAMPLING_PERIOD (0.001f)

int main()
{
	Envelope env;

	Envelope_init(&env, SAMPLING_PERIOD);
	env.capacitance = 22;
	env.attackR = 3;
	env.decayR = 4;
	env.releaseR = 10;
	env.sustainLevel = 0.3;

	for (int j = 0; j < LOOP_N; j++) {
		Envelope_gateOn(&env);
		for (int i = 0; i < STEP_LENGTH; i++) {
			if (i == GATE_LENGTH) {
				Envelope_gateOff(&env);
			}
			float v = Envelope_step(&env);
			printf("%f\n", v);
		}
	}
}

充放電に使うコンデンサの容量は
capacitance: 22uF

パラメータに使うPOTの値は
AttackR: 3kΩ
DecayR: 4kΩ
SustainLevel: 0.3
ReleaseR: 10kΩ

としています。

メモ:

微分方程式を数値積分したほうが計算量が少なくプログラムの動作にも合っていると思いますが、技量がありません。いずれやってみるかもしれません。

2018年8月6日月曜日

Arduino EGでけた。

回路図とう→「Arduino EG はんだ付け完了

外観

秋月の「大型ポリカーボネート・ケース 140」を使用しました。

内部

今回はスペースに余裕があります。

ケース内配線図

電池ケース

AD9833ファンクションジェネレータと同じように杉の三角材(△x15)を利用して電池ケースを両面テープで本体に貼り付けています。

ネジ止め

両面テープだけだと使っているうちに剥がれてしまうので、木ねじ(3.1 x 10)で三角材に固定しています。1箇所だけの固定なので両面テープも併用してグラグラしないようにしています。

本体側は両面テープだけでだいじょうぶです。←むしろ剥がすのが難しい(^q^;

DCプラグ

DCプラグはL字型のものを使いました。

仕様


大雑把ですが仕様です。測定値ではありません。違いがあったら順次更新します。

サイズ W 11cm x D 15cm x H 5cm

電源 単三 x 6 (9VACアダプタも使用可)

Gate In信号のスレッショルド
ArduinoのInterruptを使用しているので
VL = -0.5V ~ 0.3VCC = -0.5V ~ 1.5V
VH = 0.6VCC ~ VCC+0.5V = 3V ~ 5.5V

エンベロープ出力の電圧 0.6V ~ 5V (最大値はLVL POTで可変)

Github:
https://github.com/ryood/Arduino_EG

2018年7月28日土曜日

Arduino EG はんだ付け完了

回路図

基板図

部品面(部品挿入なし)

部品面(部品挿入)

ハンダ面

ケース内配線

動作確認中

テスト用ブレッドボード配線図

基板設計でミスってArduinoのピン(D2、D4~D6)を1つずらしてしまいました。

幸いArduinoのスケッチを変更することで対応できました。

Github:
https://github.com/ryood/Arduino_EG

2018年7月21日土曜日

Arduino EGの設計

Arduino Pro Mini(5V / 16MHz版)でエンベロープ・ジェネレーターを設計しました。

回路図

メモ:

出力レベルの監視用のArduinoのA0をIC1Aのバッファの後段から取るようにして試してみましたが、出力波形のGND付近に発振かクロック・ノイズのような波形が乗ってしまったので、バッファの前から取るようにしました。

理由はわかりませんが、もう少し調べてみるつもりです。

テスト用ブレッドボード配線図


C基板で収まるかどうかというところですが、POTが多いので無理しないでB基板でもいいような。

POTの値でエンベロープの時定数が決まるので、使いながら交換できるようにしておこうと思います。

Github:
https://github.com/ryood/Arduino_EG

<追記:2018.07.23>


ArduinoのA0に接続する点を出力バッファの後段(上図の(A)点)にした場合とバッファの前段(ブレッドボード配線図の通り)で比較しました。

出力バッファの前段

ch1:SyncIn ch2:Out

出力バッファの後段

ch1:SyncIn ch2:Out

Outの波形のGND側の線が太くなっています。拡大してみます。

出力バッファの前段(拡大)

ch1:SyncIn ch2:Out

出力バッファの後段(拡大)

ch1:SyncIn ch2:Out

単なるノイズではなく4kHz程度の周期性を持っています。MHz帯ならクロックノイズだとおもいますが、4KHz程度と低い周波数です。かと言って発振波形でもないと思います。

Gate信号(SyncIn)を無入力にしてもこのノイズは乗っていました。

出力バッファの後段(Gate信号なし、さらに拡大)

ch1:SyncIn ch2:Out

Arduinoで定期的に呼び出しているanalogRead()が原因のような気もします(@@?

バッファの前段をArduinoのA0につなぐとC1によってノイズが吸収される(@@???

</追記>

2018年7月10日火曜日

Arduino EGの構想

Arduinoとアナログ処理部の組み合わせでエンベロープ・ジェネレーターを製作することにしました。

シミュレーション回路図

前回のシミュレーションでは「InvAttack」のスイッチング動作をAVRの3-State Bufferで行いましたが、一応MOS-FETでスイッチングしておくことにしました。

「Attack」信号がHになる時間は、AVRのADCで出力レベルを監視して制御しますが、シミュレーションでは固定しています。

過渡解析

Gate信号V(gate)からADSRエンベロープV(out)を作り出しています。

ブレッドボード配線図


ArduinoはAruduino Pro Mini(5V/16MHz版)を使用しました。

MOS-FET M1はシミュレーション回路図では2N7002になっていますが、2N7000を使いました。パッケージが違うだけで特性はほとんど同じです。

出力バッファ用のOPAMPは単電源OPAMPのNJM13404を使用しました。

A、R、Dとラベルを貼ってあるところの抵抗は10kΩ程度のPOTを使いますが、手持ちがないので固定抵抗で代用しました。Rsustainは10kΩ/Aです。

「GateIn」信号はSQR FG から出力しました。

Rattack=4.7kΩ
Rdecay=2.2kΩ
Rrelease=2.2kΩ


ch1:GateIn(D2) ch2:OUT

細かいパラメータは少々異なりますが、だいたいシミュレーションと同じような波形が出力されました。

Rattack=10kΩ
Rdecay=10kΩ
Rrelease=2.2kΩ


ch1:GateIn(D2) ch2:OUT

Rattack=1kΩ
Rdecay=1kΩ
Rrelease=1kΩ


ch1:GateIn(D2) ch2:OUT

Arduinoのスケッチ <Arduino_EG.ino>

/*
 * Arduino EG
 *
 * 2018.07.10
 *
 */

#define UART_TRACE  (0)
#define PIN_CHECK   (0)

#define TITLE_STR1  ("Arduino EG")
#define TITLE_STR2  ("20180710")

const int ThresholdPin = 0;

const int GateInPin = 2;

const int AttackPin = 4;
const int InvAttackPin = 5;
const int GateOutPin = 6;

const int LedPin = 13;

#if (PIN_CHECK)
const int CheckPin = 10;
#endif

enum EG_STATE {
  ST_ATTACK,
  ST_DECAY,
  ST_RELEASE
};

volatile enum EG_STATE state = ST_RELEASE;

void GateIn()
{
  bool isGateOn = digitalRead(GateInPin);
  
#if (UART_TRACE)
  Serial.println(isGateOn);
#endif

  if (isGateOn) {
    state = ST_ATTACK;
    digitalWrite(LedPin, HIGH);
  }
  else {
    state = ST_RELEASE;
    digitalWrite(LedPin, LOW);
  }
}

void setup()
{
  pinMode(AttackPin, OUTPUT);
  pinMode(InvAttackPin, OUTPUT);
  pinMode(GateOutPin, OUTPUT);
  pinMode(LedPin, OUTPUT);

  pinMode(GateInPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(GateInPin), GateIn, CHANGE);

#if (PIN_CHECK)
  pinMode(CheckPin, OUTPUT);
#endif

#if (UART_TRACE)
  Serial.begin(115200);
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);
  delay(1000);
#endif
}

void loop()
{
  int th = analogRead(ThresholdPin);

  if (state == ST_ATTACK && th > 683) {
    state = ST_DECAY;
  }
  
#if (UART_TRACE)
  Serial.print(th);
  Serial.print("\t");
  Serial.println(state);
#endif

  switch (state) {
  case ST_ATTACK:
    digitalWrite(GateOutPin, HIGH);
    digitalWrite(InvAttackPin, LOW);
    digitalWrite(AttackPin, HIGH);
    break;
  case ST_DECAY:
    digitalWrite(AttackPin, LOW);
    digitalWrite(InvAttackPin, HIGH);
    break;
  case ST_RELEASE:
    digitalWrite(AttackPin, LOW);
    digitalWrite(GateOutPin, LOW);
    break;
  }
}


Github:
https://github.com/ryood/Arduino_EG

メモ:


Arduinoの処理能力と配線次第ですが、2系統エンベロープを出力できるかも?

エンベロープ・ジェネレーターの構想 その4 ステップ波形をLPFに通す。

NucleoでADSRの設定値を線形補間してDACから出力し、LPFを通したときはダメダメな波形になってしまいましたが、ステップ波形をLPFに通すとアナログADSR波形になりました。

シミュレーション回路図

過渡応答

この場合、1つのLPFを通しているので、Attack、Decay、Releaseの時定数が同じものになっています。(Susutain Levelは元のステップ波形で設定できています。)

これをADSRエンベロープ・ジェネレーターとして使うためには、A、D、Rでステートを分けて、それぞれ別のLPFを通してやる必要があります。

よくよく考えてみると、これは今実験中の「アナログ処理部」とほとんど同じ仕組みに帰着します。

簡易型のエンベロープ・ジェネレーターなら同じLPFを使ってもいいかもしれません。VST Plug-inでよくあるOne Knobみたいな(^q^;

2018年7月7日土曜日

エンベロープ・ジェネレーターの構想 その3 コンデンサの充電でAttack Timeを決める。

マイコン+Analog回路で検討しました。

Gate信号からADSRエンベロープを作り出すには、なんとかしてAttack Timeを決める必要があります。555タイマーを使ったエンベロープ・ジェネレーターはコンデンサの充放電でAttack Timeを決めています。

555タイマーのワン・ショットタイマー

555タイマーのブロック図

555の6ピンのTHRESに充電されるコンデンサをつなぎ、3本の抵抗で分圧された電位(2/3 * VCC)に達した時フリップ・フロップにリセットがかかり、7ピンのDISCHを通してコンデンサが放電されます。

Arduino Unoで実験


マイコン+Analog回路でこれと同じことができるかテストしました。

シミュレーション回路図

過渡解析

「Gate」にはGate信号を入力します。「GATE」からC1に充電され「OUT」の電圧が徐々に上がります。充電の速度は「Rattack」の抵抗値で決まります。

配線図


OUT(コンデンサの充電電圧)をArduinoのADC(A0)に接続して監視します。

Gate信号はプッシュスイッチで手動で発生させました。

10k/AのPOTを調節するとコンデンサの充電速度が変わり、Attack Timeを可変できます。

Arduinoのスケッチ <Arduino_EG_Charge_Test.ino>

/*
 * Arduino EG Charge Test
 *
 * 2018.07.05
 *
 */

#define TITLE_STR1  ("Arduino EG Charge Test")
#define TITLE_STR2  ("20180705")

const int ThresholdPin = 0;

const int GateInPin = 2;

const int GateOutPin = 4;
const int DischargePin = 5;

const int CheckPin = 7;

volatile bool isGateOn = false;

void GateIn()
{
  isGateOn = !digitalRead(GateInPin);
  //Serial.println(isGateOn);

  if (isGateOn) {
    // GateOutPin
    digitalWrite(GateOutPin, HIGH);

    // DischaregePin
    pinMode(DischargePin, INPUT); // Hi-Z
  }
  else {
    // GateOutPin
    digitalWrite(GateOutPin, LOW);

    // DischaregePin
    pinMode(DischargePin, OUTPUT);
    digitalWrite(DischargePin, LOW);  // Sink
  }
}

void setup()
{
  pinMode(GateOutPin, OUTPUT);
  pinMode(DischargePin, INPUT); // Hi-Z

  pinMode(GateInPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(GateInPin), GateIn, CHANGE);
  
  pinMode(CheckPin, OUTPUT);

  Serial.begin(115200);
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);
  delay(1000);
}

void loop()
{
  int th = analogRead(ThresholdPin);
  Serial.println(th);

  if (isGateOn && th < 683) {
    digitalWrite(CheckPin, HIGH);
  }
  else {
    digitalWrite(CheckPin, LOW);
  }
}

AVRのI/Oピンのバッファは3-State Bufferで、ArduinoだとpinModeをINPUTにするとHi-Z(スイッチOFF)になります。

loop()内でanalogRead()でOUT(コンデンサの充電電圧)を監視して、(2 / 3) * 1024 = 683に達したことが識別できているかどうかをCheckPinのH/Lで測定できるようにしました。

オシロで測定


ch1:CheckPin(D7) ch2:OUT

Gate信号がHになった時にコンデンサへの充電が開始され、ch2の電圧が徐々に上がります。このときch1:CheckPin(D7)がHになります。

ch2が3.5V付近になったときにch1がLに切り替わり、Gate信号からAttack Timeを作ることができました。

この実験ではGate信号がLになった時に放電していますが、これを前回のシミュレーションのように、Attack Timeの終了時に放電を開始し、Sustain Levelに向かって放電してやればADSRエンベロープを作ることができそうです。

Arduino Pro Miniなどを使えば、555タイマーを使うよりも(自作する)回路規模は小さくて済みそうです。

2018年7月3日火曜日

エンベロープ・ジェネレーターの構想 その2 アナログ回路の検討

「達人と作るアナログシンセサイザー自作入門」を参考にして、Analog2.0のエンベロープ・ジェネレーターを調べてみました。


基本的にはNE555を使ってGate信号から2つのH/Lの状態を作り出し、アナログ処理部の充放電によってADSRのエンベロープを生成しています。

EGのアナログ処理部のシミュレーション


シミュレーション回路図

「Attack」と論理反転した「InvAttack」と「Gate」の3つの論理値でアナログ処理部を制御します。

ADSRは「Rattack」「Rdecay」「Rsustain」「Rrelease」の4つのR値で設定します。

Q3は「InvAttack」信号によりスイッチング動作させるために入れています。NE555を使う場合はこのTrはNE555に内蔵されていて、Q3のコレクタがDischage端子に出ています。

入出力波形

NE555を使う場合は、V(attack)と、V(invattack)によるスイッチングがNE555から出力されます。

C1に流れる電流

I(R2)(緑の線)はAttack時にRattackからC1に流れ込む電流、I(R1)(青の線)はDecay時にC1からRdecayを通して流れ出す電流、I(R3)はRelease時にC1からRreleaseを通して流れ出す電流です。

Q3がONのときR4とR5の分圧でSustainレベルが設定されていて、Decay時に流れ出す電流I(R3)(赤色の線)はC1の電位がSustainレベルになるとストップします。

I(C1)(マゼンタの線)はC1の充放電のようすです。この充放電によってC1の電位V(out)の電位が変化しています。

作戦検討


このアナログ処理部に、Gate信号から、NE555か、楽をするためにマイコンで作った論理値を入力してもいけそうな気がします。

Nucleoで最初から最後までプログラムで処理して内蔵DACから出力する方法もあります。(時定数はプログラムで)

簡単に表で比較してみます。

エンベロープ・ジェネレーターの実装方法比較

方法 回路規模 クロックノイズ デジタル歪 柔軟性
555+Analog なし なし
マイコン+Analog あり なし
Nucleo あり あり

555は論理値を出力すると言ってもクロックを使わないのでクロックノイズは乗りません。マイコンの場合は実際の波形生成はアナログ回路で行うのでデジタル歪(波形のガタガタ)は乗りません。Nucleoの場合は出力にLPFを入れたりするぐらいで回路規模が小さく、プログラムを書き換えれば任意の波形が出せそうなので柔軟性は高いと思います。

悩ましいところです(^q^;;;

メモ:


検証はしていませんが、エンベロープ・ジェネレーターの回路は他にも、「フリップ・フロップとアナログスイッチを使ったもの」や「オペアンプを使ったもの」などがあるようです。Minimoogはなんとフルディスクリートでやっている(@@?

エンベロープ・ジェネレーターの構想

単体のLFOのArduino LFOが使い勝手がいいので、Envelope Generatorも単体で作ってみようと思います。

候補としては

  • 男らしくアナログ回路で組む
  • 少し男らしくArduino + MCP4922で組む
  • 使い慣れたNucleo+mbedで組む
  • 久しぶりにPSoC 5LPを使ってみる
  • 大人の世界(IoT)に対応するためにESP WROOM 32で組む

と考えてみましたが、Nucleoならmbedでサクッとプログラミングできるし、DACも内蔵されていて12bit精度なのでNucleoでやることにしました。

PSoc 5LP、ESP WROOM 32も内蔵DACがありますが8bit精度なので今回は候補から外しました。

理想とするエンベロープ・ジェネレーター



シーケンサーから出力される「Gate」信号はOn→Offに切り替わるだけで、いかにもデジタルっぽい波形です。

エンベロープ・ジェネレーターは「ADSR」の設定によって、Gate信号をなんとなく実際の楽器のようなでこぼこした振幅に加工する役目を果たします。

図の「ADSR Linear」は「ADSR」の設定に従って素直に線形補間したものです。理屈で考えると本来はこのような波形になるのですが、アナログ素子で実現すると「ADSR Analog」のようにぬるい波形になってしまうようです。

コンデンサの充放電を考えるとこんな感じかなと思える波形だし、自然現象を利用しているので「自然な」波形なんだと思います。


プログラムで書くとすれば「ADSR Linear」の波形が一番簡単ですが、できれば「ADSR Analog」のよう波形にしたい。

KIK01ではやむを得ない事情でプログラムで時定数カーブをつけましたが、単体のエンベロープ・ジェネレーターなら「ADSR Linear」の波形を出力して、後段にLPFを入れれば「ADSR Analog」のような波形になるのでは?と考えました。

Nucleo F446REでのプログラミング

配線図

ADSR設定用のPOTはBSM02で使っているPOTx8パネルを一時的に流用しました。

GATE信号はNucleoボード上のUSER_BUTTONで生成するようにしていて、同じPinに矩形波だけのファンクション・ジェネレーターをつないで一定間隔でGATE信号を出力するようにしました。

mbed repository
https://os.mbed.com/users/ryood/code/Nucleo_F446_EG/

出力波形

ch1:Gate信号(反転) ch2:NucleoからのADSR出力

USER_BUTTONの押し下げでGate Onにするために、Gate信号は反転論理にしています。

一次CR LPFを通す(R=10k C=1uF)fc≒15.9Hz

ch1:Gate信号(反転) ch2:CR LPFの出力


一次CR LPFを通す(R=100k C=1uF)fc≒1.59Hz

ch1:Gate信号(反転) ch2:CR LPFの出力

失敗です(^q^;

全体にダラッとなめらかになってはいますが、Attackの立ち上がり、AttackからDecay、SustainからReleaseに切り替わるとき、コンデンサの充放電開始のような尖った波形になっていません。

もう少し考えます。

Github
https://github.com/ryood/NucleoEG