【Arduino】Mozzi入門2 和音の出力と周波数の計算

7/01/2023

Arduino

t f B! P L

今回は、Mozziを使って和音を出力してみます。 コードや文章にミス等があればご指摘ください。

前 => 様々な波形を出力する
次 => エンベロープ(ADSR)とEventDelay

和音の出力

#define C5 523
#define E5 659
#define G5 784

#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>

#define CONTROL_RATE 64

Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aOscil(SIN2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> bOscil(SIN2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> cOscil(SIN2048_DATA);

void setup() {
    startMozzi(CONTROL_RATE);

    aOscil.setFreq(C5);
    bOscil.setFreq(E5);
    cOscil.setFreq(G5);
}

void updateControl() {
}

int updateAudio() {
    return (aOscil.next() + bOscil.next() + cOscil.next()) / 3;
}

void loop() {
    audioHook();
}

このコードで行っていることは非常に単純です。

まず、オシレーターを3つ用意して、それぞれの周波数を設定しています。

次に、updateAudio()関数で、3つのオシレーターのその瞬間の出力を平均しているだけです。

周波数の計算

前述のように、和音を流すのは簡単ですが、このままではC5、E5、G5の周波数のみの定義しかされていません。

理論に基づき計算

Mozziが出力できる音の周波数をそのまま#defineで定義しても良いのですが、今回は理論を知るために、周波数を計算する関数を作ってみます。

詳しい理論については音階の周波数一覧と求め方(Python)を参照してください。

struct ToneMap {
    String note;
    int toneIndex;
};
  
float calcFreq(String note, int octave) {
    ToneMap toneMap[] = {
      { "A", 0 },
      { "B", 2 },
      { "C", -9 },
      { "D", -7 },
      { "E", -5 },
      { "F", -4 },
      { "G", -2 }
    };
  
    int toneIndex = 0;
  
    for (int i = 0; i < sizeof(toneMap) / sizeof(toneMap[0]); i++) {
      if (note.substring(0, 1) == toneMap[i].note) {
        toneIndex = toneMap[i].toneIndex;
        break;
      }
    }
  
    if (note.substring(1, 2) == "#") {
      toneIndex++;
    } else if (note.substring(1, 2) == "b") {
      toneIndex--;
    }
  
    return (float) 440.0 * pow(2.0, (octave - 4) + (toneIndex / 12.0));
}

試しに呼び出してみます。

Serial.println(calcFreq("A", 4));   // 440.00
Serial.println(calcFreq("C", 3));   // 130.81
Serial.println(calcFreq("Eb", 5));  // 622.25
Serial.println(calcFreq("D#", 5));  // 622.25

これで、任意の音階の周波数を計算できるようになりました。

MIDIノート番号から計算

上記の通り、理論に基づき周波数を計算する関数を作りましたが、MIDIノート番号から周波数を求める関数はMozziに用意されています。

以下は、MIDIノート番号から周波数を求める例です。

#include <mozzi_midi.h>

// 省略

aSin.setFreq(mtof(69)); // 69はA4

この関数がどのように実装されているかというと以下の様になっているようです。

float mtof(float noteNumber){
	float f = 0.0;
	if (noteNumber) {
        f = 8.1757989156 * pow(2.0, noteNumber / 12.0);
    } 
	return f;
}

この関数ではMIDIノート番号0番を基準としているようです。個人的には、MIDIノート番号69番(A4・ラ4)を基準としている方が好きです。

この辺は好みですが、MIDIノート番号69番を基準として周波数を計算する関数を作ってみます。

float myMtof(int noteNumber) {
    return (float) 440.0 * pow(2.0, ((noteNumber - 69.0) / 12.0));
}

この関数を実行してみます。

Serial.println(myMtof(69)); // 440.00
Serial.println(myMtof(72)); // 523.25
Serial.println(myMtof(80)); // 830.61
Serial.println(myMtof(88)); // 1318.51

MIDIノート番号から周波数を計算することができました。

まとめ

今回は、Mozziを使って和音を出力してみました。 また、周波数を計算する関数を作ってみました。

次回はADSRエンベロープを使用して、時間の経過に伴う音の変化を試してみます。

前 => 様々な波形を出力する
次 => エンベロープ(ADSR)とEventDelay