07: エンベロープ

前回のスケッチで「音のもと」に強弱をつけることができました。

サイン波と矩形波の聞き比べで、音にゆがみがでてきたので、だんだん処理時間が間に合わなくなってきてますが・・
強引に続けます。

ここではさらにその音に、エンベロープ(わかりやすく言うと短い時間のフェードインやフェードアウト)をつけてみます。



マリヲな音ではゆがんできたので、「サイン波と矩形波の聞き比べ」をサイン波のみにして、エンベロープをつけてみました。が、ちょっと、音としてはひどいです・・。

_07_sample01

// Arduino + Sound WS
// 2011.11.26-27
//
// 07:エンベロープ
// "サイン波にエンベロープをつけた"


// 音階の周波数を、読みやすく定義したデータを読み込み
// Arduinoのサンプルスケッチ toneMelodyからコピーしています
#include "pitches.h"

//スピーカーのピンを9ピンにする
int speakerPin = 9;

//サインカーブの数値を256個
const unsigned char sineTable[] = {
  128,131,134,137,140,143,146,150,153,156,159,162,165,168,170,173,176,179,182,185,187,190,193,195,198,
  201,203,206,208,210,213,215,217,219,221,223,225,227,229,231,233,234,236,238,239,241,242,243,244,246,
  247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,
  249,249,248,247,246,244,243,242,241,239,238,236,234,233,231,229,227,225,223,221,219,217,215,213,210,
  208,206,203,201,198,195,193,190,187,185,182,179,176,173,170,168,165,162,159,156,153,150,146,143,140,
  137,134,128,125,122,119,116,113,110,106,103,100, 97, 94, 91, 88, 86, 83, 80, 77, 74, 71, 69, 66, 63,
  61, 58, 55, 53, 50, 48, 46, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 22, 20, 18, 17, 15, 14, 13,
  12, 10,  9,  8,  7,  7,  6,  5,  4,  4,  3,  3,  3,  2,  2,  2,  2,  2,  2,  2,  3,  3,  3,  4,  4,
  5,  6,  7,  7,  8,  9, 10, 12, 13, 14, 15, 17, 18, 20, 22, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41,
  43, 46, 48, 50, 53, 55, 58, 61, 63, 66, 69, 71, 74, 77, 80, 83, 86, 88, 91, 94, 97,100,103,106,110,
  113,116,119,122,125,127
};

//エンベロープの数値を256個
const unsigned char envelope[] = {
  127,124,121,119,116,113,111,109,106,104,102,100, 97, 95, 93, 91,
  89, 87, 85, 84, 82, 80, 78, 77, 75, 73, 72, 70, 69, 67, 66, 64,
  63, 62, 60, 59, 58, 56, 55, 54, 53, 52, 51, 50, 48, 47, 46, 45,
  44, 43, 42, 42, 41, 40, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32,
  31, 31, 30, 29, 29, 28, 27, 27, 26, 26, 25, 25, 24, 23, 23, 22,
  22, 21, 21, 21, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, 16, 16,
  15, 15, 15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 11, 11, 11,
  11, 10, 10, 10, 10, 10,  9,  9,  9,  9,  9,  8,  8,  8,  8,  8,
  7,  7,  7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  6,  5,  5,  5,
  5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,  2,  2,
  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
  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
};

//音階のデータ
int melody[]={
  //高い音でゆがむ例
  //NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6};
  //低い音ならまだ大丈夫
  NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3, NOTE_C4};
  
  
//音階きりかえの変数
int cnt=-1;



void setup(){

  //スピーカーのピンを出力モードに  
  pinMode(speakerPin, OUTPUT);

  //スピーカーのPWMをはやくする
  TCCR1B = 0x01;   // Timer 1: PWM 9 & 10 @ 32 kHz
}


void loop(){

   // 音の高さを決める時間
  int delayTime;

  // 音の長さ
  int soundTime;

  //音階きりかえ変数
  cnt++;
  if(cnt>=8)cnt=0;
  
  //1秒を周波数で割って、さらに256で割った:delayTime
  delayTime = (1000000/ melody[cnt] ) / 256;

  //0.125秒を、delayTimeが128回分で割った、音の長さ:soundTIme
  soundTime = (125000) / (delayTime*128);
  
  //音のもとがサイン波で、かつ、エンベロープをつける
  for(int i=0; i<soundTime-1; i++){
    
    for(int j=0; j<255; j++){
      
      //正の数の割り算をシフト演算で早く処理
      analogWrite(speakerPin, (sineTable[j]*envelope[i])>>7);
      
      //ふつうに割り算つかうと計算時間がかかり、音が歪む例
      //analogWrite(speakerPin, (sineTable[j]*envelope[i])/127);
      delayMicroseconds(delayTime);
      
    }  
    
    for(int j=0; j<255; j++){
      
      //正の数の割り算をシフト演算で早く処理
      analogWrite(speakerPin, (sineTable[j]*envelope[i])>>7);
      
      //ふつうに割り算つかうと計算時間がかかり、音が歪む例
      //analogWrite(speakerPin, (sineTable[j]*envelope[i])/127);
      delayMicroseconds(delayTime);
     
    }  
    
  }

  // 休
  delay(50);

}



そろそろ、音が耳障りになってきて、限界に近づいてきました。
なにが原因なのか、ちょっと実験してみます。

スケッチの中の以下のコメントアウト部分を入れ替えてみてください。

計算のしかたで音が変わる

//正の数の割り算をシフト演算で早く処理
analogWrite(speakerPin, (sineTable[j]*envelope[i])>>7);

//ふつうに割り算つかうと計算時間がかかり、音が歪む例
//analogWrite(speakerPin, (sineTable[j]*envelope[i])/127);

delayMicroseconds(delayTime);

こんな感じに。
//正の数の割り算をシフト演算で早く処理
//analogWrite(speakerPin, (sineTable[j]*envelope[i])>>7);


//ふつうに割り算つかうと計算時間がかかり、音が歪む例
analogWrite(speakerPin, (sineTable[j]*envelope[i])/127);
delayMicroseconds(delayTime);

これだけで、音が変わるのがわかるでしょうか?

何を意味しているのかというと、
1つの音階の中をさらに256分割してanalogWrite するだけで時間がかかって音がゆがんできているのに、
そこで、サイン波にエンベロープをかけ算して、さらに割り算までやってます。

エンベロープは、127~0まで変わるカーブなのですが、
127で割ることで、127/127〜0/127まで、つまり1.0〜0.0まで変化します。

これをサイン波にかけ算することで、サイン波の全体を徐々に減衰させていこうという魂胆です。

で、コメントアウトで入れ替えたところは、計算結果だけみれば同じことですが、
「シフト演算」という方法で割るか、「割り算」するかで、処理のスピードが異なるので、
理論値で音を出すことができず、ゆがみを大きくしてしまいました。

また、低い音か、高い音か、でもゆがみが異なってきます。

低い音では、まだ、周波数が低いので、1つの「音のもと」は時間をかけて変化します。
高い音では、周波数が高いので、1つの「音のもと」を早く変化させなくてはいけません。
さらにその中を256分割して、かけ算と割り算を行うので、低い音のほうが計算に時間をかけることができ、
音のゆがみが少なくなります。




今は音がゆがんでしまって、聞くに耐えないことになっていますが、
うまくエンベロープを表現できれば、この「エンベロープ」という要素は、音色を決めるのに重要です。
いろいろな音にこの「エンベロープ」、つまり短い時間での音の強弱がついていて、

ピアノを例にすると、

鍵盤を叩いた時の最初にでる大きな音がでる時間(アタック)、
その音が弱まっていく時間(ディケイ)、
余韻が続く時間(サスティン)、
余韻が終わって消えて行く時間(リリース)

という感じに4つのポイントがあります。

波でいうとこんなイメージ。
「音のもと」もゆらゆらと強弱があり、さらに全体の「音のおおきさ」にも強弱がある感じです。



弦楽器等も同じようにエンベロープに4つのポイントがあって、その「時間」を短くしたり長くしたりすることで、音色が変わって聞こえます。

たとえば、こんな感じのイメージ。


音の出だしで、フワ〜っとなってる感じ。
(弦楽器系に近い。あんまりやりすぎるとそうでもないです)



音の出だしが一番強くて、減衰していく感じ。
(オルゴールとかの音に近い)



「音のもと」をサイン波にして、エンベロープをつけたので、音がゆがんできたのですが、
シンプルにオンオフで音を出したものに、エンベロープをつけたものが、こちら。

一応、聞いてみてください。

矩形波のエンベロープ

// Arduino + Sound
// 2011.11.26-27
//
// 07: エンベロープ
// "矩形波のエンベロープ"


// 音階の周波数を、読みやすく定義したデータを読み込み
#include "pitches.h"

int speakerPin = 9;

int melody[]={ 
  NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6};

void setup(){

  //スピーカーのピンを出力モードに  
  pinMode(speakerPin, OUTPUT);

  //High Speed "analogWrite"
  TCCR1B = 0x01;   // Timer 1: PWM 9 & 10 @ 32 kHz

}

int cnt;

void loop(){

  // 音の長さ
  int soundTime;
  // 音の高さを決める時間
  int delayTime;

  if(cnt>=8)cnt=0;

  delayTime = (1000000/ melody[cnt] ) / 2;
  soundTime = (500000) / (delayTime*2);

  cnt++;

  // アタック
  for(int j=0; j<64; j++){

    analogWrite(speakerPin, (64/64)*j+(255-64));
    delayMicroseconds(delayTime);

    analogWrite(speakerPin, 0);
    delayMicroseconds(delayTime);

  }  

  // ディケイ
  for(int j=0; j<64; j++){

    analogWrite(speakerPin, 255-(64/64)*j);
    delayMicroseconds(delayTime);

    analogWrite(speakerPin, 0);
    delayMicroseconds(delayTime);

  }  

  // サスティン
  for(int j=0; j<10; j+=2){

    analogWrite(speakerPin, 255-64);
    delayMicroseconds(delayTime);

    analogWrite(speakerPin, 0);
    delayMicroseconds(delayTime);

  }

  // リリース
  for(int j=0; j<(255-64); j++){
    for(int i=0; i<10; i++){

      analogWrite(speakerPin, (255-64)-j);
      delayMicroseconds(delayTime);

      analogWrite(speakerPin, 0);
      delayMicroseconds(delayTime);

    }
  }


  // 休
  delay(100);
}















サイン波のエンベロープも、計算させて作ることもできますが、
さらに処理時間がかかるので、いったん数値化しておいた値を読み出すだけにしています。

そろそろ、音の生成について、Arduinoの計算速度を意識していかなくてはいけません。
次は、これまでと考え方を変えて、さらに突っ込んでいきたいと思います。