Trees
Interactive sound installation
Making
2021年2月20日から28日、KOGANEI ART SPOT シャトー2Fで行ったインスタレーション作品"Trees"のプログラムソースコードと回路図、またその過程を紹介し作品の資料として公開致します。
Making, Hosts, MIDI out & in
今回展示に使用したセンサーはCDSセル、カラーセンサー、赤外線距離センサー、焦電センサー。
Arduinoを使いプログラムを組み、ATMEGA328-PUをセンサー側とそれを受けMIDIデータに変換するホスト側と二つに分け最終的にAbleton Liveへと繋がります。
センサーの値を受け取るホスト部分の構造は試行錯誤の中微妙な違いはあるものの基本的には同じで、値は全てMIDIメッセージに変換しDAW側に送信、また受信をしました。
驚いたのは5ピンあるMIDIケーブルのうち使用しているのはOutで2本、Inで3本とスカスカだったことです。
設計当時の汎用性のあるケーブルだったのか、将来を見込んでの5ピンケーブルだったのか、理由は不明ですが今回の制作で最も驚いたことの一つでした。
Arduinoのシリアル通信では一度に1バイトしか扱えず、1バイトずつに分けビット演算で合成をしながら送受信する必要があります。
しかしそもそもMIDI通信自体もそういう仕様で、そういう意味ではやりやすかったように思います。
方法を以下にまとめます。
Host schematic
MIDI Functions
//MIDIデータを送信する関数
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
uint8_t Data[3] = {byte1, byte2, byte3};
for (uint8_t i = 0; i < 3; i++) {
Serial.write(Data[i]);
Serial.flush();
}
}
//受信したMIDIデータを先頭4bitだけを返しデータの種類を判別する為の関数
uint8_t get_MSB(uint8_t incomingByte){
uint8_t value = (incomingByte & 0xF0) >> 4;//4bit右シフト
return value;
}
//受信したMIDIデータを一つにまとめる関数
uint32_t make_sum(uint8_t byte1 ,uint8_t byte2 ,uint8_t byte3){
uint32_t result;
result = byte1;
result = (result << 8) | byte2;
result = (result << 8) | byte3;
return result;
}
MIDI通信を行う上でのbaud rate、通信速度は31250bpsと定められているのでその様に設定します、安定性を取りソフトウェアシリアルは使わずハードウェアシリアルを使用しました。
MIDIメッセージは16進数で書かれ、構造は二つに分かれていて1バイトのステータスバイトと2バイトのデータバイトの計3バイト。
ステータスバイトは機能とチャンネル番号に4ビットずつに分かれていて、例えばノートオン、チャンネル2であれば0x91となります。
0xは16進数である事を表していてそれに続く9がノートオン、1がチャンネル2、もしこれが0x81ならノートオフ、0xB4であればチャンネル5のコントロールチェンジメッセージです。
続くデータバイトは何のメッセージかによりますがノートオンであれば2バイト目がノートメッセージ、3バイト目がベロシティです。
チャンネル2でC3の音を80のベロシティで鳴らしたいとすれば16進数に直しC3は0x60、ベロシティ80は0x50となり合わせたMIDIメッセージは0x916050となります。
そしてこれを1バイトずつ0x91、0x60、0x50と分けて送信、また受信時は順番に受信します。
ちなみに最低音C-2は0x00、最高音はG8で0x7f、10.5オクターブ程のレンジを使用する事が出来ます。
割り振りやMIDIの仕様は様々なサイトに書かれていますしMIDI1.0の仕様書に詳しく書かれています。
一般社団法人音楽電子事業協会
回路図まで書かれているのでArduinoを使った制作にも非常に役に立つと思います。
Making, Infrared proximity sensor
距離センサーに使用したのは赤外線距離センサー "GP2Y0A710K"、100cmから550cm程を計測できる物で実験段階では計測距離の短いGP2Y0A21YKも使用しました。
制作したものは人が動き、その距離に応じて音程やエフェクトのかかり具合が変わるといったもので完成後のイメージは付きやすいと思います、Rolandのシンセに付いているD-Beamなどで鍵盤奏者には馴染みがあるかもしれません。
動かしてみると数字がふらふらと動き動作が不安定で微細に動きすぎるという事から100回計測しその平均値を得るという方法を取り、また電解コンデンサー47μFをパスコンとして使う事で多少は落ち着いた動作になりました。
また付属ケーブルの色が赤がGND、黒がVccと通常の感覚からいくと逆さまで、間違えて接続してしまいセンサーが発熱、破損した事もありました。
最終的には4方向、そして人が近づいた時にだけ反応するように焦電センサーSB412Aも組み合わせた仕様に。
GP2Y0A710KにはVinとGNDが2本ずつあるのでそれはケーブルの先をハンダで1つにまとめ、ケーブルを三つ編みにして取り回ししやすいように工夫しました。
制作時には回路図を作らず閃きを頼りに作業をしていたのですが改めて図に直し、実際に作成したコードは長すぎるので核の部分を抜粋して掲載します。
実は展示終了後、SDカードのデータが破損し展示中に調整したソースコードが消えてしまい展示前のバックアップしか残っていませんでした。
記憶を頼りに修正しましたが再現できたかはあまり自信はありません...ピッチベンドメッセージは動作するもののいまいち思った感じにならず、実際の展示では使わずノートオンメッセージに変えたのですが修正前の状態で残しておこうと思います。
Proximity sensor schematic
Proximity sensor code
#include <SoftwareSerial.h>
SoftwareSerial Send(2, 3); //RX,TX
//LED初期設定、数値はレジスタ番号
#define LED_POWER 5 //D13 pin 電源
#define LED_L 4 //D4 pin 焦電センサーがHIGHで点灯
#define LED_R 5 //D5 pin
#define LED_F 6 //D6 pin
#define LED_B 7 //D7 pin
//赤外線距離センサー初期化
#define IR_L_PIN 0 //A0 pin 数値はレジスタ番号
#define IR_R_PIN 1 //A1 pin
#define IR_F_PIN 2 //A2 pin
#define IR_B_PIN 3 //A3 pin
//焦電センサー初期化
#define PES_L 0 //D8 pin 数値はレジスタ番号
uint8_t CheckPin_L;
#define PES_R 1 //D9 pin
uint8_t CheckPin_R;
#define PES_F 2 //D10 pin
uint8_t CheckPin_F;
#define PES_B 3 //D11 pin
uint8_t CheckPin_B;
//距離センサー用変数
uint16_t value_L[99];
uint16_t result_L;
uint16_t lastResult_L = 0;
uint32_t sum_L = 0;
uint16_t value_R[99];
uint16_t result_R;
uint16_t lastResult_R = 0;
uint32_t sum_R = 0;
uint16_t value_F[99];
uint16_t result_F;
uint16_t lastResult_F = 0;
uint32_t sum_F = 0;
uint16_t value_B[99];
uint16_t result_B;
uint16_t lastResult_B = 0;
uint32_t sum_B = 0;
//シリアル通信用フラグ
const char L = 'L';
const char R = 'R';
const char F = 'F';
const char B = 'B';
void setup() {
//配列初期化
for (uint8_t i = 0; i < 99; i++) {
value_L[i] = 0;
value_R[i] = 0;
value_F[i] = 0;
value_B[i] = 0;
}
Send.begin(38400);
DDRB |= _BV(LED_POWER);//D13をOUTPUTに
DDRD |= _BV(LED_L) | _BV(LED_R) | _BV(LED_F) | _BV(LED_B); //D4からD7をOUTPUTに
DDRB &= ~_BV(PES_L) & ~_BV(PES_R) & ~_BV(PES_F) & ~_BV(PES_B); ////D8からD11をINPUTに
delay(1000);
PORTB |= _BV(LED_POWER);//D13をHIGHにしLED点灯
}
void loop() {
//Sensor L side
//焦電センサーの状態をレジスタで確認
CheckPin_L = PINB & _BV(PES_L);
if (CheckPin_L == 0) {
PORTD &= ~_BV(LED_L);//D4のLED消灯
} else if (CheckPin_L > 0) {//焦電センサーがHIGHであれば距離測定を開始
PORTD |= _BV(LED_L);//D4のLED点灯
for (uint8_t i = 0; i < 99; i++) {//配列に測定値を代入
value_L[i] = analogRead(IR_L_PIN);
if (value_L[i] >= 280 && value_L[i] <= 512) {
sum_L += (28250 / (value_L[i] - 229.5));
}
}
//99回の測定と前回と結果を合わせ測定値100回分の平均値を算出
sum_L /= 100;
result_L = sum_L;
//測定結果が前回と違うものであれば数値とフラグを関数sendData()へ渡しホストへ送信
if (result_L != lastResult_L) {
sendData(result_L, L);
lastResult_L = result_L;
}
}
delay(5);
//以降同じ処理を繰り返す
//Sensor R side
CheckPin_R = PINB & _BV(PES_R);
if (CheckPin_R == 0) {
PORTD &= ~_BV(LED_R);
} else if (CheckPin_R > 0) {
PORTD |= _BV(LED_R);
for (uint8_t i = 0; i < 99; i++) {
value_R[i] = analogRead(IR_R_PIN);
if (value_R[i] >= 280 && value_R[i] <= 512) {
sum_R += (28250 / (value_R[i] - 229.5));
}
}
sum_R /= 100;
result_R = sum_R;
if (result_R != lastResult_R) {
sendData(result_R, R);
lastResult_R = result_R;
}
}
delay(5);
//Sensor Front side
CheckPin_F = PINB & _BV(PES_F);
if (CheckPin_F == 0) {
PORTD &= ~_BV(LED_F);
} else if (CheckPin_F > 0) {
PORTD |= _BV(LED_F);
for (uint8_t i = 0; i < 99; i++) {
value_F[i] = analogRead(IR_F_PIN);
if (value_F[i] >= 280 && value_F[i] <= 512) {
//To get distance in cm
sum_F += (28250 / (value_F[i] - 229.5));
}
}
sum_F /= 100;
result_F = sum_F;
if (result_F != lastResult_F) {
sendData(result_F, F);
lastResult_F = result_F;
}
}
delay(5);
//Sensor Back side
CheckPin_B = PINB & _BV(PES_B);
if (CheckPin_B == 0) {
PORTD &= ~_BV(LED_B);//to get D7 LOW
} else if (CheckPin_B > 0) {
PORTD |= _BV(LED_B);//to get D7 HIGH
for (uint8_t i = 0; i < 99; i++) {
value_B[i] = analogRead(IR_B_PIN);
if (value_B[i] >= 280 && value_B[i] <= 512) {
//To get distance in cm
sum_B += (28250 / (value_B[i] - 229.5));
}
}
sum_B /= 100;
result_B = sum_B;
if (result_B != lastResult_B) {
sendData(result_B, B);
lastResult_B = result_B;
}
}
delay(5);
}//loop end
//測定データを2バイトを1バイトずつに分けフラグと合わせ計3バイト送信する関数
void sendData(uint16_t average, char dir) {
uint8_t MSB, LSB;
MSB = average >> 8;
LSB = average & 0x00FF;
Send.write(dir);
Send.flush();
Send.write(MSB);
Send.flush();
Send.write(LSB);
Send.flush();
}
Proximity sensor Host code
#include <SoftwareSerial.h>
SoftwareSerial Parameters(2,3); // RX, TX
//LCDディスプレイ初期化
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
//LED
#define LED_POWER 5 //D13 pin 電源
//各数値格納用変数
uint16_t value_L,value_R,value_F,value_B;
uint16_t lastValue_L = 0;
uint16_t lastValue_R = 0;
uint16_t lastValue_F = 0;
uint16_t lastValue_B = 0;
//フラグ格納用
char flag;
//シリアル通信用配列
uint8_t secondByte[4] = {0, 0, 0, 0};//L, R, F, B
uint8_t lastSecondByte[4] = {0, 0, 0, 0};//L, R, F, B
uint8_t thirdByte[4] = {0, 0, 0,0 };//L, R, F, B
uint8_t lastThirdByte[4] = {0, 0, 0, 0};//L, R, F, B
//MIDI初期化
const uint8_t CH_NUM = 16;
//ノートオンメッセージ
const uint8_t PLAY_NOTE[CH_NUM] {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
//ノートオフメッセージ
const uint8_t STOP_NOTE[CH_NUM] {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F};
//MIDI CCメッセージ
const uint8_t MIDI_CC[CH_NUM] {0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF};
//ベロシティ
uint8_t velocity = 80;
//DAWからMIDIメッセージを受けるための変数
uint32_t transposition = 0x9F007F;
uint32_t MSB;
uint8_t first_byte = NULL;
uint8_t second_byte = NULL;
uint8_t third_byte = NULL;
//to count a repeat
uint8_t count = 0;
void setup() {
Serial.begin(31250);//MIDI baud rate、ハードウェアシリアル
Parameters.begin(38400);//センサーとの通信はソフトウェアシリアル
DDRB |= _BV(LED_POWER);//D13をOUTPUTに
u8g.setFont(u8g_font_unifont);
delay(1000);
PORTB |= _BV(LED_POWER);//D13をHIGHに
}
void loop() {
//送られてくるセンサーを特定する
flag = getFlag();
//センサーごとにMIDIメッセージに変換
switch(flag){
case 'L':
value_L = getParameter();
//CH1 ピッチベンドメッセージ、離れるほど数字は大きく
sendPitchbend(0xE0,map(value_L,100,550,0,16383));
break;
case 'R':
value_R = getParameter();
//CH 2 ピッチベンドメッセージ、離れるほど数字は小さく
sendPitchbend(0xE1,map(value_R,100,550,16383,0));
break;
case 'F':
//CH1 CC80のコントロールチェンジ
value_F = getParameter();
sendMIDI(MIDI_CC[0],0x50,map(value_F,100,550,0,127));
break;
case 'B':
//CH2 ノートオンメッセージ
value_B = getParameter();
secondByte[3] = map(value_B,100,550,55,64);
thirdByte[3] = map(value_B,100,550,127,0);
//前回の結果と200以上差がある場合にノートオンおよび一つ前の音をノートオフ
if(value_B > lastValue_B+200 || value_B < lastValue_B-200){
sendMIDI(STOP_NOTE[1],lastSecondByte[3],lastThirdByte[3]);
sendMIDI(PLAY_NOTE[1],secondByte[3],thirdByte[3]);
lastValue_B = value_B;
lastSecondByte[3] = secondByte[3];
lastThirdByte[3] = thirdByte[3];
}
break;
}
//OLEDディスプレイに値を表示
Display(value_L,value_R,value_F,value_B);
}//loop end
//フラグ受信関数
char getFlag(){
char value;
if(Parameters.available()){
value = Parameters.read();
}
return value;
}
//赤外線距離センサーから値を受信する関数
uint16_t getParameter(){
uint8_t value;
uint16_t sum;
if(Parameters.available()){
value = Parameters.read();
sum = value;
sum = sum<<8;//8bit左シフト
if(Parameters.available()){
value = Parameters.read();
sum |= value;
}
}
return sum;
}
//MIDIデータ送信関数
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3){
uint8_t data[3] = {byte1, byte2, byte3};
for(uint8_t i=0;i<3;i++){
Serial.write(data[i]);
Serial.flush();
}
}
//ピッチベンドメッセージを送る関数
//ステータスバイトは0xEn、値は14bitで0から16383、実際には8192を中心に-8192から8191としてピッチが上下する、LSBをMSBの前に送ることに注意
//最終的に7bitずつのデータを繋ぎ合わせて14bitのデータにしていると思われるが今回はあまり追及しておらず確信できない
void sendPitchbend(uint8_t stByte,uint16_t value){
uint8_t LSB,MSB;
LSB = value & 0b0000000001111111;
MSB = value >> 番号番号
Serial.write(stByte);
Serial.flush();
Serial.write(LSB);
Serial.flush();
Serial.write(MSB);
Serial.flush();
}
//OLEDディスプレイ表示用関数
void Display(uint16_t L,uint16_t R,uint16_t F,uint16_t B) {
u8g.firstPage();
do {
u8g.setPrintPos(0, 10);
u8g.print("L:");
u8g.print(L);
u8g.print("cm");
u8g.setPrintPos(68, 10);
u8g.print("R:");
u8g.print(R);
u8g.print("cm");
u8g.setPrintPos(0, 23);
u8g.print("F:");
u8g.print(F);
u8g.print("cm");
u8g.setPrintPos(68, 23);
u8g.print("B:");
u8g.print(B);
u8g.print("cm");
}while (u8g.nextPage());
}
焦電センサーが感知するとセンサーオン、反応した向きに応じてLEDも点灯、距離の計測を開始します。
GP2Y0A21YKの距離算出は調べると2通りほど出てきたのですが使用した計算式はこちらです。
if (Value >= 280 && Value <= 512) {
Result = (28250 / (Value - 229.5));
}
Valueがセンサーから得た値、Resultにcm単位で結果が代入され、最初のif文で検出不可範囲を除外しています。
何故この数値なのかというところまではデータシートを読み込んでいないので理解はしていませんが問題なく動作していたので良しとしました。
8つのセンサーを一つのICで受ける為少しでも動作を早くしようとポートレジスタを使い記述している部分があります。
PORTD &= ~_BV(LED_F);などがそれです。
ArduinoにはdigitalWrite()などの便利な関数が用意されていますが実は動作は軽くはなくそれなりに労力を割くようです。
可読性は悪くなりますがこのセンサーに限らずこの方式で記述する事にしpinMode()はDDR、digitalWrite()はPORT、dIgitalRead()はPIN &をそれぞれ使用しています。
分かりにくいですが理解してしまえばポート処理を一括で行う事も出来て大変に便利です。
Arduino Reference
Arduino 日本語リファレンス
1バイトのシリアル通信では255までしか扱えませんので例えば500cmを計測した時には2バイトの通信が必要になります。
その上4方向を計測しているのでどの方向からのデータなのかを分別する事に非常に手間取りました。
このコードではcharデータで一文字'L' 'R' 'F' 'B'を最初に送ることで今からこのセンサーのデータを送りますよという合図にして計3バイト送信しています。
完璧ではない気がしていますがとりあえず動いているようなので良しとしました。
多バイト通信は上記文字データ送信後1バイトずつに分けて順番に送りビット演算で元に戻すという流れになっていて、MIDIメッセージのやり取りと同じ方法で行っています。
例として456cmの計測データを2進数2バイトで表すと0000 0001 1100 1000ですのでこれをまず1バイトずつ0000 0001と1100 1000に分けて変数に代入。
その後受信側ではまず0000 0001を16ビット変数で受け、それをビット演算で8ビット左シフトさせて代入します。
そして2バイト目1100 1000をor演算で左シフトさせた1バイト目と合わせます。
uint8_t receive_8;
uint16_t receive_16;
if(Serial.available()) {
receive_16 = Serial.read(); //1バイト目受信
receive_16 = receive_16 << 8; //左シフト
receive_8 = Serial.read(); //2バイト目受信
receive_16 = receive_16 | receive_8; //or演算で2バイトを合わせる
}
最後のor演算で、
0000 0001 0000 0000 | 1100 1000 = 0000 0001 1100 1000 = 456
となりこれで2バイト通信の完成です。
自分としてはビット数が分かりやすいのでuint8_tなどを型に使っていますがintやlongを使用した方が一般的と思います。
動作確認と数値の確認のために小型のOLEDディスプレイも接続しました。
DSD TECH OLED 0.91" IIC I2C
Pinbotronix 0.91" IIC I2C OLED
二種類使用しましたが表示色が違うだけでどちらも差はなく、同じU8glib.hというライブラリで動作します。
ちなみに超音波距離センサーも試したのですが全く機能せず非常に残念でした。
最低測定距離が小さく、うまくいけばこちらの方が理想的だったのですが...。
Making, Color sensor & CDS photoresistor
CDSセルは11mmサイズのMI1116Cを使用し、カラーセンサーはS9706を使用しました。
CDSの方はスムーズに実装できたものの問題はカラーセンサーです。
プログラムよりも先に小さな配線との格闘が本当にストレスで、秋月電子の1.27mm8ピンDIP変換基板がなければ完成しませんでした。
そしてケーブルも6本必要、展示の関係で10mは必要で色は白でなければ...となり特殊な6芯の固定電話用ケーブルを購入し電話用のコネクタを外して使用しました。
そのケーブルも被膜を剥いてみれば電線部分が極細で苦労の連続...、プログラムは最終的にこちらのサイトを完全にコピーして動作させる事が出来ました。
建築発明工作ゼミ2008
CDS schematic
CDS code
//ソフトウェアシリアルライブラリ読み込み
#include <SoftwareSerial.h>
SoftwareSerial Send(2,3); //RX,TX
//CDSを接続するピンレジスタ番号
#define CDS_PIN_A 0
#define CDS_PIN_B 1
//LED
#define LED_POWER 5 //D13 pin
//計測用配列
uint16_t cds_A[99]={0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0};
uint16_t cds_B[99]={0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0};
uint16_t cds_A_last = 0;
uint16_t cds_B_last = 0;
uint32_t cds_A_sum = 0;
uint32_t cds_B_sum = 0;
uint8_t cds_A_flag = 0;
uint8_t cds_B_flag = 0;
void setup() {
Send.begin(38400);
DDRB |= _BV(LED_POWER);//to get D13 OUTPUT
delay(1000);
PORTB |= _BV(LED_POWER);//to get D13 HIGH
}
void loop() {
//一つ目のCDS
//99回の計測値と一つ前の結果合わせて100回の計測値から平均値を算出
for(uint8_t i=0;i<99;i++){
cds_A[i] = analogRead(CDS_PIN_A);
cds_A_sum += cds_A[i];
}
cds_A_sum /= 100;
//結果が前回と違うものであればホストへ送信する
if(cds_A_sum!=cds_A_last){
//送信データのbit7の値が0であればこのセンサーからの送信のフラグとなる
cds_A_flag = map(cds_A_sum,0,1023,0,127);
sendData(cds_A_flag);
cds_A_last=cds_A_sum;
}
//二つ目のセンサー
for(uint8_t i=0;i<99;i++){
cds_B[i] = analogRead(CDS_PIN_B);
cds_B_sum += cds_B[i];
}
cds_B_sum /= 100;
//結果が前回と違うものであればホストへ送信する
if(cds_B_sum!=cds_B_last){
//送信データのbit7の値が1であればこのセンサーからの送信のフラグとなり、送信先で数値を修正
cds_B_flag = map(cds_B_sum,0,1023,0,127);
sendData(cds_B_flag | 0b10000000);
cds_B_last=cds_B_sum;
}
}//loop end
//シリアル通信用関数
void sendData(uint8_t value){
Send.write(value);
Send.flush();
delay(50);
}
CDS host code
//ソフトウェアシリアルライブラリ読み込み
#include <SoftwareSerial.h>
SoftwareSerial Parameters(2, 3); // RX, TX
//OLED表示用ライブラリ
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
//LED
#define LED_POWER 5 //D13 pin
uint8_t parameter;
uint8_t temporary;
uint8_t value_A, value_B;
uint8_t CDS[2] = {0, 0}; //0=A,1=B
uint8_t lastCDS[2] = {0, 0}; //0=A,1=B
uint8_t c = 0;
uint8_t noteNum[2] = {0, 0};
uint8_t lastnoteNum[2] = {0, 0};
//MIDIメッセージ、ノートオン、ノートオフ
const uint8_t CH_NUM = 16;
const uint8_t PLAY_NOTE[CH_NUM] {0x90,0x91, 0x92, 0x93, 0x94, 0x95, 0x96,0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C,00x9D, 0x9E, 0x9F};
const uint8_t STOP_NOTE[CH_NUM] {0x80,0x81, 0x82, 0x83, 0x84, 0x85, 0x86,0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C,0x8D, 0x8E, 0x8F};
const uint8_t NOTE_OFF = 0;
//MIDI CC
const uint8_t CH = 1;
//MIDI CC sendMIDI(MIDICC[],CC num,value)
const uint8_t MIDI_CC[CH_NUM] {0xB0,0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6,0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC,0xBD, 0xBE, 0xBF};
uint8_t velocity = 80;
uint8_t receive;
uint32_t transposition = 0x000000;
uint8_t MSB;
uint32_t first_byte = NULL;
uint32_t second_byte = NULL;
uint32_t third_byte = NULL;
uint8_t count = 0;
uint8_t t = 0;
//MIDIノートナンバーを配列でスケールとして定義
//G major penta
const uint8_t GMpenta[5][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2, 3,4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43,
0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45,
0x51, 0x5D, 0x69, 0x75},
//B
{0x0B, 0x17, 0x23, 0x2F, 0x3B, 0x47,
0x53, 0x5F, 0x6B, 0x77},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E,
0x4A, 0x56, 0x62, 0x6E, 0x7A},
//E
{0x04, 0x10, 0x1C, 0x28, 0x34, 0x40,
0x4C, 0x58, 0x64, 0x70, 0x7C}
};
//C major penta
const uint8_t CMpenta[5][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2, 3,4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43,
0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45,
0x51, 0x5D, 0x69, 0x75},
//C
{0x0C, 0x18, 0x24, 0x30, 0x3C, 0x48,
0x54, 0x60, 0x6C, 0x78},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E,
0x4A, 0x56, 0x62, 0x6E, 0x7A},
//E
{0x04, 0x10, 0x1C, 0x28, 0x34, 0x40,
0x4C, 0x58, 0x64, 0x70, 0x7C}
};
//G lydian
const uint8_t Glydian[7][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2, 3,4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43,
0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45,
0x51, 0x5D, 0x69, 0x75},
//B
{0x0B, 0x17, 0x23, 0x2F, 0x3B, 0x47,
0x53, 0x5F, 0x6B, 0x77},
//C#
{0x01, 0x0D, 0x19, 0x25, 0x31, 0x3D,
0x49, 0x55, 0x61, 0x6D, 0x79},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E,
0x4A, 0x56, 0x62, 0x6E, 0x7A},
//E
{0x04, 0x10, 0x1C, 0x28, 0x34, 0x40,
0x4C, 0x58, 0x64, 0x70, 0x7C},
//F#
{0x06, 0x12, 0x1E, 0x2A, 0x36, 0x42,
0x4E, 0x5A, 0x66, 0x72, 0x7E}
};
//D Orient
const uint8_t D_orient[5][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2,3, 4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43,
0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45,
0x51, 0x5D, 0x69, 0x75},
//C
{0x0C, 0x18, 0x24, 0x30, 0x3C, 0x48,
0x54, 0x60, 0x6C, 0x78},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E,
0x4A, 0x56, 0x62, 0x6E, 0x7A},
//F#
{0x06, 0x12, 0x1E, 0x2A, 0x36, 0x42,
0x4E, 0x5A, 0x66, 0x72, 0x7E}
};
void setup() {
Serial.begin(31250);//MIDI baud rate
Parameters.begin(38400);//
DDRB |= _BV(LED_POWER);//D13をOUTPUTに
u8g.setFont(u8g_font_unifont);
delay(1000);
PORTB |= _BV(LED_POWER);//
}
void loop() {
//センサーからデータを受信
parameter = getParameter8bit();
//bit7の状態から2つのうちどちらのセンサーかを判断
temporary = parameter & 0b10000000;
if (temporary == 0b00000000) {
value_A = parameter;
} else if (temporary == 0b10000000) {
//1だった場合にbit7を0へ
value_B = parameter & 0b01111111;
}
//DAWからのMIDIメッセージを受信
while (Serial.available()) {
receive = Serial.read();
//ステータスバイトを判断する
MSB = get_MSB(receive);
if (MSB >= 0xA) {
break;
} else if (count == 0 && (MSB == 0x8 || MSB == 0x9)) {
first_byte = receive;
count = 1;
} else if (count == 1 || (count == 0 && MSB < 0x8)) {
second_byte = receive;
count = 2;
} else if (count == 2) {
third_byte = receive;
count = 0;
}
}//while end
//受信したMIDIメッセージを一つにする
transposition = make_sum(first_byte, second_byte, third_byte);
//受信したデータから演奏曲を変更
if (transposition >> 8 == 0x9F00 ||
transposition >> 8 == 0x8F00) {
t = 0;
//MIDI CCとして送信
//CC102
sendMIDI(0xB1, 0x66, value_A);
//CC103
sendMIDI(0xB1, 0x67, value_B);
} else if (transposition >> 8 == 0x9F01 || transposition >> 8 == 0x8F01) {
t = 1;
CDS[0] = map(value_A, 0, 127, 0, 4);
CDS[1] = map(value_B, 0, 127, 4, 7);
//ノートメッセージとして送信
if (CDS[0] != lastCDS[0] && CDS[1] != lastCDS[1]) {
sendMIDI(STOP_NOTE[0],
D_orient[lastCDS[0]][lastCDS[1]], velocity);
sendMIDI(PLAY_NOTE[0],
D_orient[CDS[0]][CDS[1]], velocity);
}
lastCDS[0] = CDS[0];
lastCDS[1] = CDS[1]
}
if (transposition >> 8 != 0x9F01 ||
transposition >> 8 != 0x8F01 && t == 1) {
sendMIDI(0xB0, 123, 0);
t = 2;
}
//OLEDディスプレイに値を表示
Display(value_A, value_B);
}//loop end
//センサーとのシリアル通信用関数
uint8_t getParameter8bit() {
uint8_t value;
if (Parameters.available()) {
value = Parameters.read();
}
return value;
}
//MIDIデータを送信する関数
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
uint8_t data[3] = {byte1, byte2, byte3};
for (uint8_t i = 0; i < 3; i++) {
Serial.write(data[i]);
Serial.flush();
}
}
//MIDIデータを受信する関数
uint8_t get_MSB(uint8_t incomingByte) {
uint8_t value = (incomingByte & 0xF0) >> 4;
return value;
}
//受信したMIDIデータを一つにまとめる関数
uint32_t make_sum(uint32_t byte1, uint32_t byte2, uint32_t byte3) {
uint32_t result;
result = byte1;
result = (result << 8) | byte2;
result = (result << 8) | byte3;
return result;
}
//OLEDディスプレイ用関数
void Display(uint8_t A, uint8_t B) {
u8g.firstPage();
do {
u8g.setPrintPos(0, 10);
u8g.print("Brightness");
u8g.setPrintPos(0, 23);
u8g.print("A:");
u8g.print(A);
u8g.setPrintPos(68, 23);
u8g.print("B:");
u8g.print(B);
} while (u8g.nextPage());
}
Color sensor schematic
Color sensor code
#include <SoftwareSerial.h>
SoftwareSerial RGB(2,4);//RX,TX
#define RANGE 0 // 8番ピンをRange端子に設定
#define GATE 1 // 9番ピンをGate端子に設定
#define CK 2 //10番ピンをCK端子に設定
#define DOUT 3 //11番ピンをDout端子に設定
int red,green,blue;//RGB三色の変数を用意
//LED initial
#define LED_POWER 5 //Power indicator on D13
uint16_t RGB16bit[3]={0,0,0};
const uint8_t startByte = 254;
const uint8_t stopByte = 255;
void setup(){
//シリアル通信設定
RGB.begin(38400);
//Range,Gate,CK端子、Power LEDをデジタル出力に設定
DDRB |= _BV(RANGE)|_BV(GATE)|_BV(CK)|_BV(LED_POWER);
//Dout端子をデジタル入力に設定
DDRB &= ~_BV(DOUT);
delay(1000);
PORTB |= _BV(LED_POWER);
}
void loop(){
//Gate,CK端子をLowに設定
PORTB &= ~_BV(GATE)|~_BV(CK);
delayMicroseconds(2000);//2000マイクロ秒待機
//感度設定(HIGH:高感度に設定)
PORTB |= _BV(RANGE);
//測光開始(光量の積算を開始)
PORTB |= _BV(GATE);
//測光時間(vrを代入し可変的に設定)
delay(getVR());
//測光終了(光量の積算を終了)
PORTB &= ~_BV(GATE);
delayMicroseconds(4);//4マイクロ秒待機
for(uint8_t i=0;i<3;i++){
//RGBの取得
RGB16bit[i]=getRGB();
}
//send the start byte 254
sendData(startByte);
for(uint8_t i=0;i<3;i++){
//R、G、Bの順番でデータを8bit(0~127)化し送信
sendRGB(RGB16bit[i]);
}
//send the stop byte 255
sendData(stopByte);
}//Loop end
//12ビット分のパルス送信と読み込み処理
uint16_t getRGB(){
uint16_t result=0;//検出結果用の変数を用意(0:初期化)
uint8_t ckPin;//To store status of CK pin
for(uint8_t i=0;i<12;i++){//12ビット分の繰り返し処理
PORTB |= _BV(CK);//1ビット分のクロックパルス出力(HIGH)
delayMicroseconds(1);//1マイクロ秒待機
ckPin = PINB & _BV(DOUT);
if(ckPin>0){//Dout端子からの出力がHighの場合
result+=(1<<i);//12ビットのi桁目に1を代入(i桁分だけ左にシフト)
}
PORTB &= ~_BV(CK);//1ビット分のクロックパルス出力(LOW)
delayMicroseconds(1);//1マイクロ秒待機
}
delayMicroseconds(3);//3マイクロ秒待機
return result;//結果を出力
}
void sendRGB(uint16_t byte1){
uint8_t RGB8bit = byte1/32;
RGB.write(RGB8bit);
RGB.flush();
}
void sendData(uint8_t byte1){
RGB.write(byte1);
RGB.flush();
}
//可変抵抗で測光時間を調整
uint16_t getVR(void){
uint16_t vr,result;
vr=analogRead(0);
if(vr <= 100){
result=25;
}else if(vr >= 101 && vr <= 200){
result=50;
}else if(vr >= 201 && vr <= 300){
result=100;
}else if(vr >= 301 && vr <= 400){
result=200;
}else if(vr >= 401 && vr <= 500){
result=300;
}else if(vr >= 501 && vr <= 600){
result=400;
}else if(vr >= 601 && vr <= 700){
result=500;
}else if(vr >= 701 && vr <= 800){
result=600;
}else if(vr >= 801 && vr <= 900){
result=700;
}else if(vr >= 901){
result=800;
}
return result;
}
Color sensor Host code
#include <SoftwareSerial.h>
SoftwareSerial Parameters(2, 3); // RX, TX
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
//LED initial
#define LED_POWER 5//pin 13をHIGHに
uint8_t value = 0;
uint8_t RGB[3] = {0, 0, 0}; //0=R,1=G,2=B
uint8_t LAST_RGB[3] = {0, 0, 0}; //0=R,1=G,2=B
uint8_t c = 0;
uint32_t receive;
const uint8_t startByte = 254;
const uint8_t endByte = 255;
uint8_t noteNum[3] = {0, 0, 0};
uint8_t lastnoteNum[3] = {0, 0, 0};
uint8_t resisterNum[3] = {0, 0, 0};
uint8_t lastresisterNum[3] = {0, 0, 0};
//MIDI初期化
const uint8_t CH_NUM = 16;
//ノートオンメッセージ
const uint8_t PLAY_NOTE[CH_NUM] {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
//ノートオフメッセージ
const uint8_t STOP_NOTE[CH_NUM] {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F};
//MIDI CCメッセージ
const uint8_t MIDI_CC[CH_NUM] {0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF};
//ベロシティ
uint8_t velocity = 80;
//DAWからのMIDIメッセージ受信用
uint32_t transposition = 0x9F007F;
uint32_t MSB;
uint32_t first_byte = NULL;
uint32_t second_byte = NULL;
uint32_t third_byte = NULL;
//リピートカウント用
uint8_t count = 0;
//G major pentatonic scale
//スケールをMIDIノートナンバーで定義
const uint8_t GMpenta[5][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43, 0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45, 0x51, 0x5D, 0x69, 0x75},
//B
{0x0B, 0x17, 0x23, 0x2F, 0x3B, 0x47, 0x53, 0x5F, 0x6B, 0x77},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E, 0x4A, 0x56, 0x62, 0x6E, 0x7A},
//E
{0x04, 0x10, 0x1C, 0x28, 0x34, 0x40, 0x4C, 0x58, 0x64, 0x70, 0x7C}
};
//G lydian scale
const uint8_t Glydian[7][11] = {
//Y:Pitch, X:Resister
// -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8
//G
{0x07, 0x13, 0x1F, 0x2B, 0x37, 0x43, 0x4F, 0x5B, 0x67, 0x73, 0x7F},
//A
{0x09, 0x15, 0x21, 0x2D, 0x39, 0x45, 0x51, 0x5D, 0x69, 0x75},
//B
{0x0B, 0x17, 0x23, 0x2F, 0x3B, 0x47, 0x53, 0x5F, 0x6B, 0x77},
//C#
{0x01, 0x0D, 0x19, 0x25, 0x31, 0x3D, 0x49, 0x55, 0x61, 0x6D, 0x79},
//D
{0x02, 0x0E, 0x1A, 0x26, 0x32, 0x3E, 0x4A, 0x56, 0x62, 0x6E, 0x7A},
//E
{0x04, 0x10, 0x1C, 0x28, 0x34, 0x40, 0x4C, 0x58, 0x64, 0x70, 0x7C},
//F#
{0x06, 0x12, 0x1E, 0x2A, 0x36, 0x42, 0x4E, 0x5A, 0x66, 0x72, 0x7E}
};
void setup() {
Serial.begin(31250);//MIDIボーレート
Parameters.begin(38400);//Parameters serial
DDRB |= _BV(5); //to get D13 OUTPUT
u8g.setFont(u8g_font_unifont);
delay(1000);
PORTB |= _BV(5); //D13をHIGHに
}
void loop() {
//センサーからのデータを受信
if (Parameters.available()) {
value = Parameters.read();
//スタートバイト254を確認
if (value == startByte) {
while (Parameters.available()) {
value = Parameters.read();
//エンドバイト255を受信したら終了
if (value == endByte) {
break;
//254を再受信したらやり直し
} else if (value == startByte) {
c = 0;
continue;
} else if (c >= 3) {
c = 0;
break;
//RGBデータ受信
} else {
RGB[c] = value;
c++;
}
}
}
}
//受信したデータをディスプレイに表示
Display(RGB[0], RGB[1], RGB[2], transposition);
value = 0;
c = 0;
//DAWからのMIDIメッセージの受信
while (Serial.available()) {
receive = Serial.read();
//データの先頭、MSBを調べる
MSB = get_MSB(receive);
//ノートオン(0x9n)かノートオフ(0x8n)メッセージのみ受け付ける
if (MSB >= 0xA) {
break;
} else if (count == 0 && (MSB == 0x8 || MSB == 0x9)) {
//ステータスバイトを代入
first_byte = receive;
count = 1;
} else if (count == 1 || (count == 0 && MSB < 0x8)) {
//データバイト、1バイト目を代入
second_byte = receive;
count = 2;
} else if (count == 2) {
//データバイト、2バイト目(LSB)を代入
third_byte = receive;
count = 0;
}
}//while end
//受信したデータを一つにまとめる
transposition = make_sum(first_byte, second_byte, third_byte);
//受信したノートナンバーに応じて演奏するスケールを変更する
if (transposition >> 8 == 0x9F00 || transposition >> 8 == 0x8F00) {
for (uint8_t i = 0; i < 3; i++) {
//ノートナンバーを持つ配列番号に直していく、反応をよくする為必要数の倍に分けた
noteNum[i] = map(RGB[i], 0, 127, 0, 9);
if (noteNum[i] > 4) {
noteNum[i] -= 5;
}
//オクターブに関わる配列番号に直していく、こちらも反応をよくする為必要数の倍に分けた
resisterNum[i] = map(RGB[i], 0, 127, 3 + i, 6 + i);
if (resisterNum[i] > 5 + i) {
resisterNum[i] -= 2;
}
//一つ前の音と違うものであればノートオン、およびノートオフを送信する
//G Major pentatoic scale
if (noteNum[i] != lastnoteNum[i] && resisterNum[i] != lastresisterNum[i]) {
sendMIDI(STOP_NOTE[0], lastnoteNum[i], velocity);
sendGMpenta(0, noteNum[i], resisterNum[i]); //CH, Note, Resister
lastnoteNum[i] = noteNum[i];
lastresisterNum[i] = resisterNum[i];
}
}
}
//To play G lydian scale
if (transposition >> 8 == 0x9F01 || transposition >> 8 == 0x8F01) {
for (uint8_t i = 0; i < 3; i++) {
noteNum[i] = map(RGB[i], 0, 127, 0, 13);
if (noteNum[i] > 6) {
noteNum[i] -= 7;
}
//Make a resister number. To better responding, divided it double too.
resisterNum[i] = map(RGB[i], 0, 127, 3 + i, 6 + i);
if (resisterNum[i] > 5 + i) {
resisterNum[i] -= 2;
}
if (noteNum[i] != lastnoteNum[i] && resisterNum[i] != lastresisterNum[i]) {
sendMIDI(STOP_NOTE[0], lastnoteNum[i], velocity);
sendGlydian(0, noteNum[i], resisterNum[i]); //CH, Note, Resister
lastnoteNum[i] = noteNum[i];
lastresisterNum[i] = resisterNum[i];
}
}
}
}//loop end
//G Major pentatonic, send note on message.
void sendGMpenta(uint8_t ch, uint8_t n, uint8_t r) {
//CH number
Serial.write(PLAY_NOTE[ch]);
Serial.flush();
//Note number
Serial.write(GMpenta[n][r]);
Serial.flush();
//Velocity
Serial.write(velocity);
Serial.flush();
}
//G lydian
void sendGlydian(uint8_t ch, uint8_t n, uint8_t r) {
//CH number
Serial.write(PLAY_NOTE[ch]);
Serial.flush();
//Note number
Serial.write(Glydian[n][r]);
Serial.flush();
//Velocity
Serial.write(velocity);
Serial.flush();
}
//MIDIメッセージ送信関数
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
uint8_t Data[3] = {byte1, byte2, byte3};
for (uint8_t i = 0; i < 3; i++) {
Serial.write(Data[i]);
Serial.flush();
}
}
//受信したMIDIデータを先頭4bitだけを返す
uint8_t get_MSB(uint8_t incomingByte){
uint8_t value = (incomingByte & 0xF0) >> 4;//4bit右シフト
return value;
}
//受信したMIDIデータを一つにまとめる関数
uint32_t make_sum(uint8_t byte1,uint8_t byte2,uint8_t byte3){
uint32_t result;
result = byte1;
result = (result << 8) | byte2;
result = (result << 8) | byte3;
return result;
}
//OLEDディスプレイ用関数
void Display(uint8_t r, uint8_t g, uint8_t b, uint32_t incoming) {
u8g.firstPage();
do {
u8g.setPrintPos(0, 10);
u8g.print("R:");
u8g.print(r);
u8g.setPrintPos(68, 10);
u8g.print("G:");
u8g.print(g);
u8g.setPrintPos(0, 23);
u8g.print("B:");
u8g.print(b);
u8g.setPrintPos(68, 23);
u8g.print(incoming, HEX);
} while (u8g.nextPage());
}
S9706はRGBそれぞれの値を0から4095の間で導き出します。
それをMIDIの仕様に合わせて0から127に圧縮、ホスト側に送信し距離センサーの時のようにシリアル通信時に合図として送信時に254、終了時に255を送信し誤作動を防止、MIDIデータの値とぶつかることも無いので安定して動作していたようでした。
CDSのデータは主にエフェクト、RGBデータは主にハーモニーへと変換。
配列にMIDIノートナンバーを特定のスケールに並べてその中の音をセンサーの数値から選択し音を鳴らす仕組みです。
シリアル通信ですがセンサーからホストへは送信することはあっても受信をすることは無いので最初は電源を別に取れば通信部はセンサー側TXからホスト側RXのみの配線にし本数をを省けるのではと期待しました。
実際に行ってみると動作がめちゃくちゃになってしまい、悩んだのですがやはり電気は行ったら帰ってこなければならず、送信だけではなく受信も繋げなくてはならないようです。
GNDをうまく共有すれば出来なくもないのですが展示ではセンサーとホストの距離もかなりあるのでそれも難しく、素直に配線することにしました。
ちなみにBluetoothも試みましたがこちらも不安定さから断念、次回以降実装を期待します。
ホスト側で曲が移り変わるときにそのタイミング、キィの変化などをDAW側とどうコミュニケーションをとるかが問題でした。
悩みましたがDAW側からIC側にMIDIメッセージを送りそのチャンネルは16に固定しノートナンバーから移り変わりを判断させる事にしました。
この方法はとても効果的に動作したように思います。
if (transposition >> 8 == 0x9F00 || transposition >> 8 == 0x8F00)
変数transpositionでMIDIメッセージを受け8bit左シフト、ノートオンかノートオフ、チャンネルとノートナンバーから現在の演奏曲を判断します。
ノートオンだけで良かったのですがDAWの都合と誤作動防止でノートオフも条件に含め、他で使う事もなさそうな最低音付近のノートメッセージを使用しました。
Making, to better work
センサーも動きMIDI通信もうまくいきDAW上での制作に入りMIDI CCを各所に設定しなければなりませんでした。
使用したDAWはAbleton Liveでその設定方法は動かしたいパラメーターを選択し、操作に使うノブやボタンを動かすことで割り当てられます。
センサーを割り当てる場合にそのセンサーを毎回設定時に動かすのが面倒で、複数接続している場合自分の思っているセンサーへ割り当てられないなど問題がありました。
その解決の為にMIDI CCを登録する為だけの装置を制作しました。
ノブを回して登録したいMIDI CCを設定し、ボタンを押すとメッセージが送信され番号が割り当てられ登録されます。
期限が迫る中この装置のおかげで制作スピードが飛躍的に向上し、ストレスも減り本当に正解でした。
MIDI CC Applier schematic
MIDI CC Applier code
//OLED表示用ライブラリ
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
//配列に各ボタンのピンレジスタ番号を代入
uint8_t pinButton[4] = {5, 6, 7, 0};
uint8_t checkButton[4];
//Buttons
uint8_t button[3] = {0, 0, 0}; //1st byte, 2nd byte, 3rd byte.
uint32_t sendButton = 0;
uint8_t stByte = 0;
uint32_t sum = 0x000000;
uint32_t incoming = 0x000000;
//MIDI initial
const uint8_t CH_NUM = 16;
const uint8_t PLAY_NOTE[CH_NUM] = {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
const uint8_t STOP_NOTE[CH_NUM] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F};
const uint8_t NOTE_OFF = 0;
//MIDI channel
const uint8_t CH = 1;
//MIDI CC sendMIDI(MIDICC[],CC num,value)
const uint8_t MIDI_CC[CH_NUM] {0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF};
uint8_t velocity = 80;
uint32_t receive;
uint32_t MSB;
uint32_t first_byte = NULL;
uint32_t second_byte = NULL;
uint32_t third_byte = NULL;
//to count a repeat
uint8_t count = 0;
void setup() {
Serial.begin(31250);//MIDI baud rate
DDRD &= ~_BV(pinButton[0]) & ~_BV(pinButton[1]) & ~_BV(pinButton[2]); //D5からD7をINPUTに
DDRB &= ~_BV(pinButton[3]);//D8をINPUTに
u8g.setFont(u8g_font_unifont);
delay(1000);
}
void loop() {
//ステータスバイト作成、ボタンを押している間ノブを回し数値変更
//1st byte Button.
checkButton[0] = PIND & _BV(pinButton[0]);
while (checkButton[0] != 0) {
stByte = map(analogRead(0), 0, 1023, 0, 31);
//16進数0xB0から0xEFの間に収まるように調整、今見るとmap関数だけで調整可能
//0xBnはMIDI CC、0xEnはピッチベンドメッセージ
if (stByte <= 15) {
button[0] = stByte + 176;
} else if (button[0] >= 16) {
button[0] = stByte + 208;
}
//3バイトを一つにまとめる
sum = make_sum(button[0], button[1], button[2]);
Display(sum, incoming);
checkButton[0] = PIND & _BV(pinButton[0]);
}
//以降同じ処理を繰り返す
//2nd byte Button.
checkButton[1] = PIND & _BV(pinButton[1]);
while (checkButton[1] != 0) {
button[1] = map(analogRead(0), 0, 1023, 0, 127);
sum = make_sum(button[0], button[1], button[2]);
Display(sum, incoming);
checkButton[1] = PIND & _BV(pinButton[1]);
}
//3rd byte Button.
checkButton[2] = PIND & _BV(pinButton[2]);
while (checkButton[2] != 0) {
button[2] = map(analogRead(0), 0, 1023, 0, 127);
sum = make_sum(button[0], button[1], button[2]);
Display(sum, incoming);
checkButton[2] = PIND & _BV(pinButton[2]);
}
//このボタンを押してデータ送信
//send Button.
checkButton[3] = PINB & _BV(pinButton[3]);
while (checkButton[3] != 0) {
//MIDIメッセージ送信、0xEのピッチベンドメッセージはMSB LSB 2nd byteの順に送信
if (button[0] >> 6 == 0xE) {
sendMIDI(button[0], button[2], button[1]);
}
//MIDIメッセージ送信
else {
sendMIDI(button[0], button[1], button[2]);
}
Display(sum, incoming);
checkButton[3] = PINB & _BV(pinButton[3]);
}
//DAWからのMIDIメッセージ受信
while (Serial.available()) {
receive = Serial.read();
MSB = get_MSB(receive);
if (MSB >= 0xA) {
break;
} else if (count == 0 && (MSB == 0x8 || MSB == 0x9)) {
first_byte = receive;
count = 1;
} else if (count == 1 || (count == 0 && MSB < 0x8)) {
second_byte = receive;
count = 2;
} else if (count == 2) {
third_byte = receive;
count = 0;
}
}//while end
incoming = make_sum(first_byte, second_byte, third_byte);
Display(sum, incoming);
}
//MIDIメッセージ送信関数
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
uint8_t data[3] = {byte1, byte2, byte3};
for (uint8_t i = 0; i < 3; i++) {
Serial.write(data[i]);
Serial.flush();
}
}
//MSBのみを取り出す関数
uint32_t get_MSB(uint32_t incomingByte) {
uint32_t value = (incomingByte & 0xF0) >> 4;
return value;
}
//3バイトを一つにまとめる関数
uint32_t make_sum(uint32_t byte1, uint32_t byte2, uint32_t byte3) {
uint32_t result;
result = byte1;
result = (result << 8) | byte2;
result = (result << 8) | byte3;
return result;
}
//ピッチベンドメッセージを送信する関数、送信順に注意
void sendPitchbend(uint8_t stByte, uint16_t value) {
uint8_t LSB, MSB;
LSB = value & 0b0000000001111111;
MSB = value >> 7;
Serial.write(stByte);
Serial.flush();
Serial.write(LSB);
Serial.flush();
Serial.write(MSB);
Serial.flush();
}
//OLEDディスプレイ表示用関数
void Display(uint32_t out, uint32_t in) {
u8g.firstPage();
do {
u8g.setPrintPos(0, 10);
u8g.print("Outgoing:");
u8g.print(out, HEX);
u8g.setPrintPos(0, 23);
u8g.print("Incoming:");
u8g.print(in, HEX);
} while (u8g.nextPage());
}
このMIDI CC割り当て機、展示中も活躍する事になりました。
いくつかの曲が時間経過で変わっていく様にする為にはLiveだけでは時間管理が微妙で、プログラムを新たに組んで一定時間経過で曲変更のCCを送信する物にしました。
ノブで時間と曲数を設定し、ボタンを押すことでスタートします。
上のコードと同じ個所の説明は省きます。
Time keeper code
#include <SoftwareSerial.h>
SoftwareSerial Parameters(2, 3); // RX, TX
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
//Pins
uint8_t pinButton[4] = {5, 6, 7, 0};
uint8_t checkButton[4];
//Buttons
uint8_t button[3] = {0, 0, 0}; //1st byte, 2nd byte, 3rd byte.
uint32_t sendButton = 0;
uint8_t stByte = 0;
uint32_t time1 = 0;
uint32_t time2 = 0;
//分、初期値10分
int8_t m = 10;
//秒
int8_t s = 0;
const uint8_t MIDI_CC[16] {0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF};
//曲数
uint8_t playtime = 5;
uint8_t count = 0;
uint16_t duration = 600;
uint8_t duration_m = 10;
uint8_t duration_s = 0;
//On Offスイッチ
uint8_t sw = 0
void setup() {
Serial.begin(31250);//MIDI baud rate
DDRD &= ~_BV(pinButton[0]) & ~_BV(pinButton[1]) & ~_BV(pinButton[2]);
DDRB &= ~_BV(pinButton[3]);
u8g.setFont(u8g_font_unifont);
delay(1000);
}
void loop() {
//曲数を設定、ボタンを押しながらノブを回す
//1st Button.
checkButton[0] = PIND & _BV(pinButton[0]);
while (checkButton[0] != 0) {
playtime = map(analogRead(0), 0, 1023, 1, 16);
count = 0;
Display(playtime, playtime - count, duration_m, duration_s, m, s);
checkButton[0] = PIND & _BV(pinButton[0]);
}
//曲が変化するまでの時間を設定、30秒から30分まで設定できる
//2nd byte Button.
checkButton[1] = PIND & _BV(pinButton[1]);
while (checkButton[1] != 0) {
//30秒から1800秒
duration = map(analogRead(0), 0, 1023, 30, 1800);
//60で割った整数部分が「分」に
m = duration / 60;
//60で割った余りの部分が「秒」に
s = duration % 60;
//表示を分けるために変数に代入
duration_m = m;
duration_s = s;
count = 0;
Display(playtime, playtime - count, duration_m, duration_s, m, s);
checkButton[1] = PIND & _BV(pinButton[1]);
}
Display(playtime, playtime - count, duration_m, duration_s, m, s);
//ボタンが押されたら演奏開始
//Start Button.
checkButton[3] = PINB & _BV(pinButton[3]);
if (!checkButton[3]) {
sw=1
}
while (sw == 1) {
//ms単位でプログラム起動時からの時間を取得
time1 = millis();
//前回から1000ms、一秒以上経過していたら一秒減らす
if (time1 > time2 && time1 - time2 >= 1000) {
s--;
//計測時の時間を代入
time2 = time1;
//計測可能時間を超えると0からになってしまい逆転してしまう為の処理
} else if (time1 < time2 && 4294967295 - time2 + time1 >= 1000) {
s--;
time2 = time1;
}
//0秒を下回ったら一分経過させ秒は59秒に
if (s < 0) {
s = 59;
m--;
}
//設定時間経過後MIDI CCを送信する、チャンネルは常に16、3バイト目は常に0x7F
//2バイト目の数値が曲番号になる
if (m <= 0 && s <= 0) {
sendMIDI(0xBF, count, 0x7F);
//曲を進める
count++;
//時間をリセット
m = duration / 60;
s = duration % 60;
//全ての曲が終了したら最初の曲へ
if (count == playtime) {
count = 0;
}
}
Display(playtime, playtime - count, duration_m, duration_s, m, s);
//ボタンが押されたら演奏停止し設定へ
checkButton[3] = PINB & _BV(pinButton[3]);
if (!checkButton[3]) {
sw=0
}
}
}
void sendMIDI(uint8_t byte1, uint8_t byte2, uint8_t byte3) { // I wasn't able to use 1st_byte, 2nd_byte and 3rd_byte for some
reason.
uint8_t data[3] = {byte1, byte2, byte3};
for (uint8_t i = 0; i < 3; i++) {
Serial.write(data[i]);
Serial.flush();
}
}
void Display(uint8_t rep, uint8_t ti, uint8_t du_m, uint8_t du_s, uint8_t mi, uint8_t se) {
u8g.firstPage();
do {
u8g.setPrintPos(0, 10);
u8g.print("Rep:");
u8g.print(rep);
u8g.setPrintPos(0, 23);
u8g.print("Rem:");
u8g.print(ti);
u8g.setPrintPos(68, 10);
//一桁の時に01、08と0を付けて表示するための処理
if (du_m < 10) {
u8g.print("0");
u8g.print(du_m);
} else {
u8g.print(du_m);
}
u8g.print(":");
if (du_s < 10) {
u8g.print("0");
u8g.print(du_s);
} else {
u8g.print(du_s);
}
u8g.setPrintPos(68, 23);
if (mi < 10) {
u8g.print("0");
u8g.print(mi);
} else {
u8g.print(mi);
}
u8g.print(":");
if (se < 10) {
u8g.print("0");
u8g.print(se);
} else {
u8g.print(se);
}
} while (u8g.nextPage());
}
Afterward
プログラミングはhtmlとBASICをほんの僅かに触った程度の経験しかない自分が2020年の1年弱の期間だけの勉強でこの展示を行う事が出来たのはネットに共有された世界中のプログラマーの情報のおかげです。
自分も微力ですが情報を残し、また作品の資料、制作過程を残しておく事にしました。
デジタルでありながらセンサーを組み込むことでアナログな世界と行き来する事が出来るのはArduinoの素晴らしい所で、今回の展示を通して得た沢山のインスピレーションを活かして通常の音楽活動もまた広げてゆこうと思います。
Donations
皆様からのチップ、ドネーションを歓迎します。
PaypalやPringを使った送金、Bandcampでの音源購入などで是非サポートお願い致します。
Donations and tips are welcome.
Sending via Paypal or pring, purchase my music on Bandcamp can help my project.