見出し画像

$5 ChatGPT LOGIC - DIY Eurorack Modular Synthesizer

背景

自作モジュラーシンセの65作品目。

Chat GPT

OpenAIのChatGPTが急に話題に上がり始めた。QA形式でAIが質問に答えてくれるらしい。簡単なプログラムも書いてくれるとのことで、試してみたいと思った。

Logicモジュール

LOGICモジュールはモジュラーシンセの代表的なモジュールの一つだ。シーケンサやCVと組み合わせることで、複雑なリズムやCVを作り出すことができる。

ChatGPTによる自動生成プログラムを使用したLOGICモジュールを企画に至った。

制作物のスペック

ユーロラック規格 3U 6HPサイズ
電源:45mA ( +12V ) 

10種類のロジックを備えた、2IN , 2OUTモジュール。
ロジックの種類は以下の通り
1.AND
2.NAND
3.OR
4.NOR
5.XOR
6.XNOR

7.コンパレータ:IN1>IN2のときOUT1=high。IN2>IN1のときOUT2=high。
8.MAX:IN1>IN2のとき、OUT1=IN1。IN2>IN1のとき、OUT1=IN2。
9.MIN:IN1>IN2のとき、OUT2=IN2。IN2>IN1のとき、OUT2=IN1。
10.FLIP-FLOP:IN1はOUT1と対応、IN2はOUT2と対応。INがlow->hightのときにOUT出力をlow <-> highで切り替える。

モードは6種類あり、プッシュスイッチで切り替える。
Mode1: OUT1/OUT2 = AND/NAND
Mode2: OUT1/OUT2 = OR/NOR
Mode3: OUT1/OUT2 = XOR/XNOR
Mode4: コンパレータ
Mode5: OUT1/OUT2 = MAX/MIN
Mode6: OUT1/OUT2 = FlipFlop ch1/FlipFlop ch2

入力はIN1,IN2 (0-5V)、閾値は2.5V。
出力はOUT1,OUT2(0-5V)、分解能は256。

製作費

総額約600円(PCBではなく、ユニバーサル基板を使う場合)
---------------------------------
Arduino nano 250円
フロントパネル 100円
プッシュSW 30円
ジャック 8円*4pcs
LED 3円*10pcs
他(汎用部品は下記リンク先参照)

PCBを発注する場合、100円/pcs+送料が追加となる。

ハードウェア

過去に私が作成したモジュールに登場する定番回路。
D9,D10pinは高速PWMを使用することで、ローパスフィルタの抵抗値を抑えながら、低リプルのアナログ出力を実現している。


基板はArduino nano用の汎用基板「Genaral board」使用している。下記のリンク先を参考してほしい。

CADファイルはpatreonで公開している。オープンソースプロジェクトの開発を継続するために支援いただけると嬉しい。

https://www.patreon.com/posts/dual-envelope-64079059

ソフトウェア

今回はChatGPTが生成したコードを組み合わせている。

ChatGPTでは小規模なコード(~50行)は正確に生成することができたが、複数の機能を組み合わせたコード生成は失敗した。

作成した小規模なコードは以下の通り
1. AND/NAND
2. OR/NOR
3. XOR/XNOR
4. コンパレータ
5. MAX/MIN
6. Flip-Flop
7. プッシュボタンによるmode選択(チャタリング対策を含む)

以上の7種類の小規模なコードを手入力により結合して、今回のモジュールのコードを作成した。

出力例1

例:AND/NANDの生成は以下の通り命令した。

Arduinoのプログラムを書いてください。
A6pinのアナログ入力値が511LSB以上の時のみ、変数A6をhighとし、それ以外をlowとします。
A7pinのアナログ入力値が511LSB以上の時のみ、変数A7をhighとし、それ以外をlowとします。
A6とA7のAND演算結果をD9pinに出力します。
A6とA7ののNAND演算結果をD10pinから出力します。
変数A6の値を、D3から出力します。
変数A7の値を、D11から出力します。

ChatGPTの出力結果は以下の通り

const int LSB_threshold = 511;
const int A6_pin = A6;
const int A7_pin = A7;
const int D9_pin = 9;
const int D10_pin = 10;
const int D3_pin = 3;
const int D11_pin = 11;

void setup() {
  pinMode(A6_pin, INPUT);
  pinMode(A7_pin, INPUT);
  pinMode(D9_pin, OUTPUT);
  pinMode(D10_pin, OUTPUT);
  pinMode(D3_pin, OUTPUT);
  pinMode(D11_pin, OUTPUT);
}

void loop() {
  int valueA6 = analogRead(A6_pin);
  int A6 = (valueA6 >= LSB_threshold) ? HIGH : LOW;
  int valueA7 = analogRead(A7_pin);
  int A7 = (valueA7 >= LSB_threshold) ? HIGH : LOW;
  int AND = A6 & A7;
  int NAND = !(A6 & A7);

  digitalWrite(D9_pin, AND);
  digitalWrite(D10_pin, NAND);
  digitalWrite(D3_pin, A6);
  digitalWrite(D11_pin, A7);
}

このコードをArduino IDEで書き込んだ結果、問題なく意図した動作をしてくれた。生成したコードは、手入力による修正は一切していない。
ChatGPTを用いてシンプルな機能を実装することは可能である。

出力例2

例:プッシュボタンを押すたび、モードを切り替える

Arduinoのプログラムを書いてください。
D7pinはinput pullupに設定します。
D7にはGNDに設置されたプッシュボタンが接続されていて、ボタンを1度押すたびにmodeが0から5まで増えます。mode5でボタンを押すとmode0に戻ります。
buttonがhighからlowになった時だけ、modeを繰り上げてください
buttonのチャタリング対策をしてください。

ChatGPTの出力結果は以下の通り

int button = 7;
int mode = 0;
int previousButtonState = HIGH;
unsigned long debounceTime = 0;
const int debounceDelay = 50;

void setup() {
  pinMode(button, INPUT_PULLUP);
}

void loop() {
  int buttonState = digitalRead(button);
  if (buttonState == LOW && previousButtonState == HIGH) {
    if (millis() - debounceTime > debounceDelay) {
      mode = (mode + 1) % 6;
      debounceTime = millis();
    }
  }
  previousButtonState = buttonState;

  switch (mode) {
    case 0:
      // Do something in mode 0
      break;
    case 1:
      // Do something in mode 1
      break;
    case 2:
      // Do something in mode 2
      break;
    case 3:
      // Do something in mode 3
      break;
    case 4:
      // Do something in mode 4
      break;
    case 5:
      // Do something in mode 5
      break;
  }
}

こちらも、問題なく動作するコードが出力された。「チャタリング対策」という抽象的な命令をしても、機能するコードが出力された。

ChatGPTの欠点1

変数の定義は、生成するコードによって異なる。
例えば「pinの名称」を定義する際、「D3Pin=3」「D3=3」「D3pin=3」といった感じに、変数が異なるコードが生成される。

後から複数のコードを結合するさいに、変数が重複したり、過剰に生成されてしまう。

ChatGPTの欠点2

分解能や閾値は具体的な値を指示する必要がある。

例えば、「Duty=50%のPWM出力をする」とした際に、ChatGPTは「analogWrite(D3,511);」と誤った回答する。Arduino nanoのanalogWriteは255までの値しか受け付けないので、511の部分は127なのが正解である。

総括

今回作成したコードを結合した際に、重複した変数名は手入力で修正した。

コードを新規に追加したのは約200行のうち、14行だ。
「//hand coding」とコメントを入れた行が、手入力を行だ。
複雑なコードの入力はしておらず、いずれもdigitalWriteやpinModeの設定といった基本的なコードのみ、追加している。
また、高速PWMのレジスタ設定も手入力をしている。

今回のモジュールはChatGPTだけで生成することは出来なかったが、9割以上のコードは自動生成されたものを流用できた。

ただし、MCUの仕様や、使用するプログラミング言語をあるていど理解したうえでChatGPTに命令する必要がある。ビギナーが使用するには、少し難しいかもしれない。

ソースコード

粗末だが公開する。
特に問題が発生しない限り、ChatGPTが自動生成したコードを使用しているので、不必要なコードや不可解なコードがあるかもしれないが、あえて残している。

#include <avr/io.h>//for fast PWM
const int LSB_threshold = 511;
const int A6_pin = A6;
const int A7_pin = A7;
const int D9_pin = 9;
const int D10_pin = 10;
const int D3_pin = 3;
const int D11_pin = 11;
int valueA6 = 0;
int outA6 = 0;
int valueA7 = 0;
int outA7 = 0;
int AND = 0;
int NAND = 0;
int OR = 0;
int NOR = 0;
int XOR = 0;
int XNOR = 0;
int high = 0;
int low = 0;
int D10 = 10;
int D9 = 9;
int D11 = 11;
int D3 = 3;
const int A6Pin = 6;
const int A7Pin = 7;
const int D3Pin = 3;
const int D9Pin = 9;
const int D10Pin = 10;
const int D11Pin = 11;
const int pinD9 = 9;
const int pinD10 = 10;
const int pinD3 = 3;
const int pinD11 = 11;
const int D9pin = 9;
const int D10pin = 10;
const int D3pin = 3;
const int D11pin = 11;
const int LSB_resolution = 256;
int A7Value = 0;
int A6Value = 0;
int A7LastValue = 0;
int A6LastValue = 0;
int button = 7;
int mode = 0;
int previousButtonState = HIGH;
unsigned long debounceTime = 0;
const int debounceDelay = 200;

void setup() {
 //fast pwm setting
TCCR1B &= B11111000;//hand coding
 TCCR1B |= B00000001;//hand coding
  
  pinMode(button, INPUT_PULLUP);
  pinMode(A6_pin, INPUT);
  pinMode(A7_pin, INPUT);
  pinMode(D9_pin, OUTPUT);
  pinMode(D10_pin, OUTPUT);
  pinMode(D3_pin, OUTPUT);
  pinMode(D11_pin, OUTPUT);
}

void loop() {
  int buttonState = digitalRead(button);
  if (buttonState == LOW && previousButtonState == HIGH) {
    if (millis() - debounceTime > debounceDelay) {
      mode = (mode + 1) % 6;
      debounceTime = millis();

    }
  }
  previousButtonState = buttonState;

  switch (mode) {
    case 0://AND
      digitalWrite(14, HIGH);//hand coding
      digitalWrite(19, LOW);//hand coding
      valueA6 = analogRead(A6_pin);
      outA6 = (valueA6 >= LSB_threshold) ? HIGH : LOW;
      valueA7 = analogRead(A7_pin);
      outA7 = (valueA7 >= LSB_threshold) ? HIGH : LOW;
      AND = outA6 & outA7;
      NAND = !(outA6 & outA7);

      digitalWrite(D9_pin, AND);
      digitalWrite(D10_pin, NAND);
      digitalWrite(D3_pin, outA6);
      digitalWrite(D11_pin, outA7);
      break;

    case 1://OR
      digitalWrite(15, HIGH);//hand coding
      digitalWrite(14, LOW);//hand coding
      valueA6 = analogRead(A6);
      outA6 = (valueA6 >= LSB_threshold) ? HIGH : LOW;

      valueA7 = analogRead(A7);
      outA7 = (valueA7 >= LSB_threshold) ? HIGH : LOW;

      OR = outA6 | outA7;
      digitalWrite(D9Pin, OR);

      NOR = !(outA6 | outA7);
      digitalWrite(D10Pin, NOR);

      digitalWrite(D3Pin, outA6);
      digitalWrite(D11Pin, outA7);
      break;

    case 2://XOR
      digitalWrite(16, HIGH);//hand coding
      digitalWrite(15, LOW);//hand coding

      outA6 = (analogRead(6) >= LSB_threshold) ? HIGH : LOW;
      outA7 = (analogRead(7) >= LSB_threshold) ? HIGH : LOW;

      XOR = outA6 ^ outA7;
      XNOR = !(outA6 ^ outA7);

      digitalWrite(9, XOR);
      digitalWrite(10, XNOR);

      digitalWrite(3, outA6);
      digitalWrite(11, outA7);
      break;

    case 3://compare
      digitalWrite(17, HIGH);//hand coding
      digitalWrite(16, LOW);//hand coding

      outA6 = analogRead(6);
      outA7 = analogRead(7);

      if (outA6 > outA7) {
        digitalWrite(pinD9, HIGH);
        digitalWrite(pinD10, LOW);
      } else if (outA6 < outA7) {
        digitalWrite(pinD9, LOW);
        digitalWrite(pinD10, HIGH);
      }

      outA6 = map(outA6, 0, 1023, 0, 256);
      outA7 = map(outA7, 0, 1023, 0, 256);

      analogWrite(pinD3, outA6);
      analogWrite(pinD11, outA7);
      break;

    case 4://min max
      digitalWrite(18, HIGH);//hand coding
      digitalWrite(17, LOW);//hand coding

      outA6 = analogRead(6);
      outA7 = analogRead(7);

      high = max(outA6, outA7);
      low = min(outA6, outA7);

      analogWrite(D9pin, map(high, 0, 1023, 0, LSB_resolution - 1));
      analogWrite(D10pin, map(low, 0, 1023, 0, LSB_resolution - 1));
      analogWrite(D3pin, map(outA6, 0, 1023, 0, LSB_resolution - 1));
      analogWrite(D11pin, map(outA7, 0, 1023, 0, LSB_resolution - 1));
      break;

    case 5:
      digitalWrite(19, HIGH);//hand coding
      digitalWrite(18, LOW);//hand coding

      A7Value = analogRead(7);
      A6Value = analogRead(6);

      if (A7Value >= 511) {
        A7Value = HIGH;
      } else {
        A7Value = LOW;
      }

      if (A6Value >= 511) {
        A6Value = HIGH;
      } else {
        A6Value = LOW;
      }

      if (A7Value != A7LastValue && A7Value == HIGH) {
        digitalWrite(D10, !digitalRead(D10));
      }
      if (A6Value != A6LastValue && A6Value == HIGH) {
        digitalWrite(D9, !digitalRead(D9));
      }

      digitalWrite(D11, A7Value);
      digitalWrite(D3, A6Value);

      A7LastValue = A7Value;
      A6LastValue = A6Value;
  }
}

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?