Arduinoで和音を出力する方法 その3

#前回のあらすじ
DDSを実装した

https://qiita.com/electronic1155work/items/dca619a3cd3d291936c7

#さて
今回は、前回のできなかった和音を1つのピンから出力する方法を示したいと思います。

#今回のスケッチ

```C:multi_output

#define output_pin_A 11
#define output_pin_B 12
#define output_pin_C 13

#define DIV_1 1 //分周無し
#define DIV_8 2 //8分周
#define DIV_32 3 //32分周
#define DIV_64 4 //64分周
#define DIV_128 5 //128分周
#define DIV_256 6 //256分周
#define DIV_1024 7 //1024分周
#define WGM21 0b10 //タイマ2 動作モード設定
#define TOIE2 0b10 //割り込み許可

volatile int operator_A = 0;
volatile int operator_B = 0;
volatile int operator_C = 0;
volatile int operator_D = 0;

volatile int operator_A_add = 0;
volatile int operator_B_add = 0;
volatile int operator_C_add = 0;
volatile int operator_D_add = 0;

int add_arr[] = {
  274 ,//ド
  291 ,//ド#
  308 ,//レ
  326 ,//レ#
  346 ,//ミ
  366 ,//ファ
  388 ,//ファ#
  411 ,//ソ
  435 ,//ソ#
  461 ,//ラ
  489 ,//ラ#
  518 ,//シ
  549//ド
};

int white_note[] = {
  0, 2, 4, 5, 7, 9, 11, 12
};

void setup() {
  //ピンの設定
  pinMode(output_pin_A, OUTPUT);
  pinMode(output_pin_B, OUTPUT);
  pinMode(output_pin_C, OUTPUT);

  analogWrite(11, 127);
  TCCR2B = (TCCR2B & 0b11111000 ) | DIV_1;
  TIMSK2 = 0b1;

  sei();//割り込み許可
}



void loop() {
  int i = 0;
  sei();
  for (i = 0; i < 8; i++) {
    operator_A_add = add_arr[white_note[i]];
    delay(300);
  }
  cli();
  operator_A_add = 0;

  sei();
  for (i = 0; i < 8; i++) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;
    delay(300);
  }
  cli();
  operator_A_add = 0;
  operator_B_add = 0;

  sei();
  for (i = 0; i < 8; i++) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;
    operator_C_add = operator_A_add << 1;
    delay(300);
  }
  cli();
  operator_A_add = 0;
  operator_B_add = 0;
  operator_C_add = 0;

  sei();
  for (i = 0; i < 8; i++) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;
    operator_C_add = operator_A_add << 1;
    operator_D_add = operator_A_add << 2;
    delay(300);
  }
  delay(1500);
  for (i = 7; i >= 0; i--) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;
    operator_C_add = operator_A_add << 1;
    operator_D_add = operator_A_add << 2;
    delay(100);
  }
  delay(100);
  cli();
  operator_A_add = 0;
  operator_B_add = 0;
  operator_C_add = 0;
  operator_D_add = 0;
  sei();
  delay(1000);
}


ISR(TIMER2_OVF_vect) {
  //割り込み時に実行される関数
  char data = 0;
  digitalWrite(output_pin_C, !digitalRead(output_pin_C));

  int output_data = 0;

  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
  operator_B += operator_B_add;
  operator_B &= 0x7FFF;
  operator_C += operator_C_add;
  operator_C &= 0x7FFF;
  operator_D += operator_D_add;
  operator_D &= 0x7FFF;


  if (operator_A < 0x3FFF) {//矩形波　50%
    output_data += -31;
  } else {
    output_data += 31;
  }

  //  if (operator_B < 0x3FFF) {//矩形波　50%
  //    output_data += -31;
  //  } else {
  //    output_data += 31;
  //  }

  if (operator_B < 0x3FFF) {//三角波
    output_data += operator_B >> 9;
  } else {
    output_data += -31 + (63 - (operator_B >> 9));
  }

  if (operator_C < 0x1FFF) {//矩形波　25%
    output_data += -31;
  } else {
    output_data += 31;
  }

  output_data += -31 + operator_D >> 9;//のこぎり波

  //  if (operator_D < 0x3FFF) {//矩形波　50%
  //    output_data += -31;
  //  } else {
  //    output_data += 31;
  //  }

  if (output_data > 0) {
    output_data >>= 1;//output_data/=2;と同じ
  } else {
    output_data = -((-output_data) >> 1);
  }

  OCR2A = output_data + 127;

  digitalWrite(output_pin_C, !digitalRead(output_pin_C));
}

```

このスケッチを、<font color="#00979c">**Arduino UNO**</font>(Nanoも可)に書き込むと<font color="DarkOrange">**D11**</font>から音が出力されます。


<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/17702f65-51a5-24b1-2be9-b5ca0721e97d.png">

上のようにスピーカにつなげてもいいですが、音が小さいのでアンプを用意してください。

https://akizukidenshi.com/catalog/g/gI-08402/

こちらのアンプICや、<font color="#F7008C">**ダイソー**</font>の300円スピーカセットがおすすめです。

###出力波形
<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/b02340c8-972f-d351-ff43-e3e76b5d8c43.png">

<font color="red">赤</font>が強い周波数成分です、次々と周波数成分が増えにぎやかになっているのがわかりますね。


##動作について
<font color="DarkOrange">**D11**</font>にスピーカを、つなぐと<font color="#E60012">ド</font><font color="#F39800">レ</font><font color="#DFD000">ミ</font><font color="#009944">ファ</font><font color="#0068B7">ソ</font><font color="#1D2088">ラ</font><font color="Blue">シ</font><font color="#920783">ド</font>が4回繰り返されるのが分かると思います。

それぞれ<font color="Chocolate">音階</font>は一緒でも聞こえ方、つまり<font color="SteelBlue">音色</font>は全く異なっています。

これは、出力している波形が変化したためです。

###<font color="SteelBlue">音色</font>について
音にはいくつかの、要素があります。
今回需要なのは、<font color="Chocolate">音階</font>、<font color="SteelBlue">音色</font>、<font color="Fuchsia">音量</font>
この3つです。

* <font color="Chocolate">**音階**</font>　いわゆる<font color="#E60012">ド</font><font color="#F39800">レ</font><font color="#DFD000">ミ</font><font color="#009944">ファ</font><font color="#0068B7">ソ</font><font color="#1D2088">ラ</font><font color="Blue">シ</font><font color="#920783">ド</font>　基本となる周波数
* <font color="SteelBlue">**音色**</font>　その音に含まれる周波数成分(倍音)
* <font color="Fuchsia">**音量**</font>　音の波高値

:::note warn
<b>ここで、詳しい説明はしません
気になる方は、矩形波や三角波、のこぎり波のフーリエ変換について調べてみてください。<b>
:::

##スケッチ解説
それでは、スケッチの解説に参ります。

###変数

```C
int add_arr[] = {
  274 ,//ド
  291 ,//ド#
  308 ,//レ
  326 ,//レ#
  346 ,//ミ
  366 ,//ファ
  388 ,//ファ#
  411 ,//ソ
  435 ,//ソ#
  461 ,//ラ
  489 ,//ラ#
  518 ,//シ
  549//ド
};

```

<font color="Blue">ド</font>から高い<font color="Red">ド</font>までの、Add値です。

<font color="#E60012">ド</font><font color="#F39800">レ</font><font color="#DFD000">ミ</font><font color="#009944">ファ</font><font color="#0068B7">ソ</font><font color="#1D2088">ラ</font><font color="Blue">シ</font><font color="#920783">ド</font>は、＃を含めて12音あり、12個上はちょうど周波数が<font color="red">2倍</font>になるという関係があります。
また、この関係は<font color="red">440Hz</font>を基準としています。




```math
f=440\times 2^\frac{n}{12}\
```

nには、<font color="red">440Hz</font>のラから、何音離れているかが入ります。(黒鍵を含みます。)

高いラは、12音上なので12/12=1で2倍になるという理屈です。

それをもとに、周波数を計算しAdd値に計算しなおしています。
周波数のままでは、すぐに使ことができないので<font color="#1D7044">Excell</font>等でこういった処理をしています。<br>


```math
\begin{align}
{Add} &=\frac{32767 \times f_{output}}{f_{sample}} \\
\end{align}
```

Add値への計算は、おなじみこの式です。
今回、サンプリング周波数は31.25kHz(後述)なのでラ<font color="red">440Hz</font>だと

```math
\begin{align}
{Add} &=\frac{32767 \times 440}{31.25\times 10^3} \\
&=461\\
\end{align}
```
となります。

```C
int white_note[] = {
  0, 2, 4, 5, 7, 9, 11, 12
};
```
単純に白鍵の音をまとめてるだけです。
全全半全全半のメジャースケールです。(シッタカ)

###setup
お次は、セットアップです。

```C
void setup() {
  //ピンの設定
  pinMode(output_pin_A, OUTPUT);
  pinMode(output_pin_B, OUTPUT);
  pinMode(output_pin_C, OUTPUT);

  analogWrite(11, 127);
  TCCR2B = (TCCR2B & 0b11111000 ) | 0b001;
  TIMSK2 = 0b1;

  sei();//割り込み許可
}
```

前半はピン設定ですが、後半は何やら様子が違います。

```C 
  analogWrite(11, 127);
  TCCR2B = (TCCR2B & 0b11111000 ) | 0b001;
  TIMSK2 = 0b1;
  sei();
```
前回同様、タイマ割り込みを使用するのですが
それと同時にPWMも利用したいです。

https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%AB%E3%82%B9%E5%B9%85%E5%A4%89%E8%AA%BF

PWMは、短い時間で見ると二値しかないけど、長い目で見ると様々な値になる
って感じです。詳しいことは、wikiさんが教えてくれますよ。

```C
analogWrite(11, 127);
```
まず、<font color="#00979c">**Arduino UNO**</font>のPWM出力関数であるanalogWrite()を実行します。
これによって、内部レジスタがPWMモードになります。

```C
  TCCR2B = (TCCR2B & 0b11111000 ) | 0b001;
  TIMSK2 = 0b1;
```
次に、前回同様タイマの分周器を変更し最も周波数が高い「分周無し」を選択します。
(TCCR2B & 0b11111000)は、他のレジスタを変更させないためのマスクです。
TIMSK2 = 0b1でタイマ割り込みを有効にしています。
ISRも、TIMER2_OVF_vectに変更しています。

周波数は、

```math
f=\frac{16\times10^6}{2\times256}=31250\\
```

この時に気が付いたんですが、<font color="#00979c">**Arduino UNO**</font>って両傾斜PWMにしてくれてたんですね。
ありがとう。

```C
  sei();//割り込み許可
  cli();//割り込み禁止
```
割り込みを許可するか、禁止するかのマクロです。
割り込み処理を停止する時に使います。

:::note warn
割り込み禁止をすると、time0の割り込みも禁止され
delayが効かなくなり、Serialもパケットロスが発生する可能性があります。
できるだけ、短い期間のみ割り込み禁止をするべきです。
:::

PWMに関する、詳しい説明は以下のサイト様をご参照ください。

https://garretlab.web.fc2.com/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_analog.c/analogWrite.html

https://usicolog.nomaki.jp/engineering/avr/avrPWM.html

###loop内

```C
int i = 0;
  
  sei();
  for (i = 0; i < 8; i++) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;//1オクターブ下へ
    operator_C_add = operator_A_add << 1;//1オクターブ上へ
    operator_D_add = operator_A_add << 2;//2オクターブ上へ
    delay(300);
  }
  delay(1500);
  for (i = 7; i >= 0; i--) {
    operator_A_add = add_arr[white_note[i]];
    operator_B_add = operator_A_add >> 1;
    operator_C_add = operator_A_add << 1;
    operator_D_add = operator_A_add << 2;
    delay(100);
  }
  delay(100);
  cli();
  operator_A_add = 0;
  operator_B_add = 0;
  operator_C_add = 0;
  operator_D_add = 0;
  sei();
  delay(1000);
```

基本的には

1. 発音周波数を、12音の白鍵分を選択
2. 発音時にAddを決定
3. 消音時にAddをゼロ

してるだけです。
それと同時に、発音数も増やしつつ、シフト演算によって<font color="red">オクターブを上下</font>しています(シフト演算については後述)。

:::note
消音時に、位相operator_Xもゼロにすると次の発音時に位相がそろい
発音時の違和感が減ります。
:::


###割り込み処理内
まず

```C
digitalWrite(output_pin_C, !digitalRead(output_pin_C));
```
時間計測用です。

```C
  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
  operator_B += operator_B_add;
  operator_B &= 0x7FFF;
  operator_C += operator_C_add;
  operator_C &= 0x7FFF;
  operator_D += operator_D_add;
  operator_D &= 0x7FFF;
```
位相加算です、前回と同様ですね

```C
  if (operator_A < 0x3FFF) {//矩形波　50%
    output_data += -31;
  } else {
    output_data += 31;
  }

  //  if (operator_B < 0x3FFF) {//矩形波　50%
  //    output_data += -31;
  //  } else {
  //    output_data += 31;
  //  }

  if (operator_B < 0x3FFF) {//三角波
    output_data += operator_B >> 9;
  } else {
    output_data += -31 + (63 - (operator_B >> 9));
  }

  if (operator_C < 0x1FFF) {//矩形波　25%
    output_data += -31;
  } else {
    output_data += 31;
  }

  output_data += -31 + operator_D >> 9;//のこぎり波

  //  if (operator_D < 0x3FFF) {//矩形波　50%
  //    output_data += -31;
  //  } else {
  //    output_data += 31;
  //  }
```
ここから、様子が変わってきます。
output_data は出力用の変数です。

出力用の波形を作っていくのですが、まず
各波形共に、正負の値を取るようにします。(この方が楽なので)

```C
 if (operator_A < 0x3FFF) {//矩形波　50%
    output_data += -31;
  } else {
    output_data += 31;
  }
```
前回から、いますがこれは矩形波用です。
±31の矩形波です。

位相は単調に増加し0にもどるので、ある値を境に0,1を取れば矩形波ができます。

0x3FFFは16383、つまり最大値32767の半分<font color="red">Duty比が50%</font>の矩形波となります。
ちなみに、オペレータCは0x1FFFで<font color="red">Dutyを25%</font>にしています。

矩形波は、Duty比によって聞こえ方(<font color="SteelBlue">音色</font>)が変化します。

```C
output_data += -31 + operator_D >> 9;//のこぎり波
```
のこぎり波です、オペレータの大きさを小さくして正負の値を取るようにしています。

<font color="DeepSkyBlue">＞＞</font>はビットシフトで、<font color="DeepSkyBlue">＞＞</font>N でN回右にシフトするという意味です。
右シフトは、2で割るのと同義で通常の／で記述するのより高速です。(所説あり)

<font color="OrangeRed">＜＜</font>は、2をかけるの意味です。

https://www.weblio.jp/content/%E3%82%B7%E3%83%95%E3%83%88%E6%BC%94%E7%AE%97

ここでは、<font color="DeepSkyBlue">＞＞</font>9　つまり　

```math
2^{15} \div 2^{9}=2^6\\
```
となります。

0~32767のオペレータを、<font color="crimson">0~63</font>にダウンサイズしたわけですね。
-31しているので、±31

```C
  if (operator_B < 0x3FFF) {//三角波
    output_data += operator_B >> 9;
  } else {
    output_data += -31 + (63 - (operator_B >> 9));
  }
```
オペレータの値をうまいこと計算して三角波にしています。
いろいと、やってたらできました。

のこぎり波と似たような処理です。

```C
if (output_data > 0) {
    output_data >>= 1;//output_data/=2;と同じ
  } else {
    output_data = -((-output_data) >> 1);
  }
```
レベルリミッタです、31が波高値の最大なので
全音合わせると、124になります。
念のため、2で割って音割れしないようにしています。

:::note warn
出力音数を増やす場合、音割れに注意
:::


```C
OCR2A = output_data + 127;
```
OCR2Aは,タイマ2のレジスタです。
analogWriteを使った時も、最終的にはこのレジスタに格納されます。

https://www.arduino.cc/en/Hacking/PinMapping168

<font color="#00979c">**Arduino UNO**</font>の公式サイトには、このようなタイマのOCRXXに対応するピンをまとめた便利な画像があります。

output_dataは,正負の値なので127を足しています。

:::note warn
常に、2.5V(電源電圧/2)のオフセットが出るので注意してください。
出力にコンデンサを入れれば大丈夫です。
:::

##まとめ
今回は、ついに和音を1つのピンで出力することができました。

#おわりに
駆け足で、スケッチの説明をしましたが
おそらく、よくわからんところがあると思います。
大丈夫です。書いてる人の正直よくわかっていません。

毎回スケッチを、先に持ってきているのは
とりあえずそれで遊んでもらいたいからです。
ひとしきりあそんでから記事の内容に目を通してもらえば結構です。

#次回
未定