$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の生成は以下の通り命令した。
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
例:プッシュボタンを押すたび、モードを切り替える
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;
}
}
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?