09: 和音

さて、さきほどの「サウンド生成の考え方」で、
音階処理にかかるArduinoの処理を減らしつつ、きれいな音を出せるようになりました。

ここからは「和音」の説明に入っていきます。

いままでの例では、同時に1つの音しか出ていませんでした。

Arduinoに入っている「tone」のサンプルでも、矩形波で同時に1つの音しか鳴らせず、
和音にする部分は、スピーカーを複数つけるような例が紹介されています。
これは、音を「和」にする部分は自分の耳ということです。

一応、これでも和音は出せるのですが、
小さくしたり、省エネにしたりする部分では、壁になってしまいます。

ここでは、スケッチ(プログラム)の中で和音を考えていくことにします。



そもそも「和音」とは何のことだろうか、と考えてみます。

和音はその名のとおり、「和」なので複数の音を足し算したものになります。
2つの音だったら、2つを足したもの。3つであれば3つを足したもの。

2つの音の場合、


赤い音と、青い音を同時に出したい場合、赤と青を足します。
ガタガタしてるのは手作業であわせたからです。。すみません。

analogWriteに指定できる0〜255の255を超えるので、
このままの数値ではノイズだらけになります。


そこで、全体を2で割って平均化します。




つまり、


このように「和音」とは、複数の音を数値化したものを、足し算して割り算します。

2つ足したら2で割って、3つ足したら3で割ることで、
指定できる最大値(ここでは255)を超えないようにします。

DAC用ICは12ビット解像度なので、0~4095まで指定できます。
このように256を3回足してもオーバーしない数値を指定できるのであれば、割り算しなくてもいいですが、
いずれにしても、きれいな音をつくるには、最大値を超えないことを確認しておきましょう。


では、スケッチの解説です。




■波形を処理する loop(和音)

まず、和音というからには、複数の音を同時並行してすすめる必要があります。

さきほどはサイン波にエンベロープをつけ、1つの音を出すことができましたが、
同じことを複数回行い、それぞれの音を足し「和」にしてスピーカーに出すことになります。

難しそうですが、ここでは必要な変数を配列として、for文で一気に処理しました。

波形の処理をするloop(和音)抜粋

//ループ処理
void loop(){
  
  //音の長さを処理
  //もし、ピッチのカウントが0だったら
  if (pitch == 0){
    
    //ピッチをVP_DLAYにする
    pitch = VP_DELAY;
    //音の長さカウントが0でないなら、
    //音の長さカウントをカウントダウン
    if (count)
      count--;
  }
  //もし、ピッチのカウントが0でなかったら、
  else{
    //ピッチをカウントダウン
    pitch--;
  }


  //スピーカーに出す波形を まず0にして、
  waveData = 0;

  //最大同時発音数だけくり返す
  for(int i=0; i<VOICENUM; i++){

    //もし、音がなってるフラグが1だったら、
    if(voice_on[i]){

      //自分のサイン波データに、サイン波テーブルのposの位置の値を設定
      sinewaveData[i] = sineTable[pos[i]];

      //エンベロープ処理
      //エンベロープ時間カウントが0だったら、
      if(envelopeCount[i]==0){
        //エンベロープの値を、エンベロープテーブルのenvの位置の値を設定
        //同時にenvは次へカウント
        envelopeValue[i] = envelopeTable[env[i]++];
        
        //エンベロープ時間を、ENV_DELAYにする
        envelopeCount[i] = ENV_DELAY; 
        
        //もしエンベロープテーブル位置が最後になったら、
        if (env[i] == 255) {
          //音がなってるフラグを0にする
          voice_on[i] = 0;
        }

      }
      //エンベロープ時間がつづいているとき、
      else{
        //エンベロープ時間をカウントダウン
        envelopeCount[i]--;
      }


      //サイン波を何個おきに読み出すかを計算するところ
      //整数部分は、そのまま足していく
      pos_d[i]  += dat_d[i];
      //小数部分も、そのまま足していく
      pos_f[i]  += dat_f[i];
      
      //最終的には、
      //足された整数部分と、足された小数を256で割った値が、
      //サイン波テーブルの位置になる
      pos[i] = pos_d[i] + (pos_f[i] /256);
      
      
    }
    
    //和音の計算
    //それぞれの音の波形に、エンベロープをつけたものを、足す。
    waveData += ( sinewaveData[i] * envelopeValue[i] /128 );
  }
  
  //DAC用ICに、音データを送ると、スピーカーから出る
  mcpDacSend(waveData);

  //時間調整
  delayMicroseconds(40);
}




■1つ音が鳴り止んだら(音の長さ処理が完了したら)次の音へすすめる callback

ここも、必要な変数を配列としたものを使っていますが、

最大同時発音数を監視していて、もしエンベロープが長くて余韻が残っている場合でも、
次の音に進むタイミングに鳴れば、強制的に前の音の余韻を断ち切って、新しい音を出すようになってます。

曲データから音の高さと長さを読みすすめるcallback

//タイマー2で1msごとに呼ばれるところ
//こっちで、音階を処理
void callback(){

  //もし、音の長さ処理を終えたら、
  if(count==0){
    
    //曲データを読みすすめる
    pchar =  pgm_read_byte_near(musicdata + musicDataCount); // 1バイト読み込み
    //出したい音の、音階テーブルを読み込み
    //整数部分
    dat_d[voiceCount] = onkai_d[pchar];
    //小数部分
    dat_f[voiceCount] = onkai_f[pchar];


    //曲データの位置を1つすすめる
    musicDataCount++;
    
    //曲データを読みすすめる
    pchar =  pgm_read_byte_near(musicdata + musicDataCount); // 1バイト読み込み
    //音の長さデータを設定
    count = pchar;

    //曲データの位置を1つすすめる
    musicDataCount++;

    //曲データの位置が、データ数を超えたら、データ位置を0にする
    if (musicDataCount >= sizeof( musicdata )) {
      musicDataCount = 0;
    }
    

    //エンベロープを最初にもどす
    env[voiceCount] = 0;
    envelopeCount[voiceCount] = 0;

    //音がなってるフラグを1にする
    voice_on[voiceCount]=1;

    //同時発音カウントをカウント
    voiceCount++;

    //同時発音数が、VOICENUMを超えたら、0にリセット
    //いままで同時に発音していた音が強制的に次の音に更新される
    if(voiceCount==VOICENUM){
      voiceCount=0;
    }

  }
}




■曲データと和音の関係


和音の処理は、エンベロープの余韻に、次の音が重なることでも表現できていますが、
曲データで、和音をつくることもできます。


1音だけ出るサンプルで紹介したように、
音の高さと長さは、曲のデータの中で、2つがセットになって連続しています。

C_3, 16,

とあれば、最初の「C_3」が音の高さ、次の「16」が音の長さを表しています。


C_3,    0,   C_3,   16,

とあれば、最初の「C_3」を音の長さ「0」で鳴らし、すぐさま次の「C_3」を音の長さ「16」で鳴らすので、
和音となります。

ここでは、同じ「C_3」が和音で出るので、強い音になります。