【Arduino】Mozzi入門4 音を連続的に変化させる

7/01/2023

Arduino

t f B! P L

今回は、Mozziを使い、音の周波数を連続的に変化させてみます。

今まで音を鳴らすときは、一音ずつしか鳴らすことができませんでしたが、バイオリンのように、音の高さを連続的に変化させる必要がある場合があります。

今回は、Line、Smooth、Portamentoを使って、音の高さを連続的に変化させてみます。

前 => エンベロープ(ADSR)とEventDelay

Line

まずは、Lineです。Lineは、ある値から別の値まで、一定のステップ数で変化させることができます。いわゆる補間や内挿というやつです。

今回は、440Hzと880Hzの音を交互に鳴らしてみます。

コード全体

まずは、動くコードの提示から。

このコードを実行すると、440Hz(ラ4)と880Hz(ラ5)の音が交互に鳴ります。

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

#define CONTROL_RATE 64

Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);

Metronome toggle(1000);

Line<Q16n16> aInterpolate;

const unsigned int AUDIO_STEPS_PER_CONTROL = AUDIO_RATE / CONTROL_RATE;


Q16n16 freq;
Q16n16 freq1 = Q16n0_to_Q16n16(440);
Q16n16 freq2 = Q16n0_to_Q16n16(880);

void setup() {
    Serial.begin(9600);
    aSin.setFreq(440);
    startMozzi(CONTROL_RATE);
}

void updateControl() {
    if (toggle.ready()) {
        if (freq == freq1) {
            freq = freq2;
        } else {
            freq = freq1;
        }
    }
    aInterpolate.set(freq, AUDIO_STEPS_PER_CONTROL);
}

int updateAudio() {
    Q16n16 interpolatedFreq = aInterpolate.next();
    
    aSin.setFreq_Q16n16(interpolatedFreq);

    return aSin.next();
}

void loop() {
    audioHook();
}

コードの説明

まず、Line.hをインクルードします。

#include <Line.h>

次に、Lineクラスのインスタンスを生成します。

Lineのテンプレートに指定する型は、float(浮動小数点)を使用すると遅すぎるため、Q16n16(固定小数点)を指定します。

Line<Q16n16> aInterpolate;

そして、AUDIO_STEPS_PER_CONTROLを定義します。 今回はAudioRateが16384Hz、ControlRateが64Hzなので、AUDIO_STEPS_PER_CONTROLはAUDIO_RATE / CONTROL_RATE = 16384 / 64 = 256となります。

のちに解説する、updateControl()内のset()関数の呼び出される間隔とupdateAudio()内のnext()関数が呼び出される間隔が異なるため、補間された点の数が一致するようにこの値を設定します。

本来の使い方ではないかもしれませんが、この値を変更することで、音の変化の速さを変更することができます。

const unsigned int AUDIO_STEPS_PER_CONTROL = AUDIO_RATE / CONTROL_RATE;

周波数を切り替えるために、固定小数点の変数freq、freq1、freq2を定義します。

Q16n0_to_Q16n16は、Q16n0(整数)をQ16n16(固定小数点)に変換する関数です。

Q16n16 freq;
Q16n16 freq1 = Q16n0_to_Q16n16(440);
Q16n16 freq2 = Q16n0_to_Q16n16(880);

updateControl()内で、freq1とfreq2を交互に切り替えfreqに代入し、Lineのset()関数を呼び出します。

set()関数が2つの引数をとる場合、以下のような値を渡します。

  • 1つ目の引数:補間の終了点
  • 2つ目の引数:補間のステップ数

開始点は?と思うかもしれませんが、開始点は現在の値が設定されます。

set()関数が3つの引数をとる場合は第1引数に開始点を指定します。

aInterpolate.set(freq, AUDIO_STEPS_PER_CONTROL);

updateAudio()内でnext()関数を呼び出し、補間された値を取得します。

Q16n16 interpolatedFreq = aInterpolate.next();

aSinの周波数を補間された値に設定します。

aSin.setFreq_Q16n16(interpolatedFreq);

Smooth

次にSmoothです。Smoothは、ある値から別の値まで、一定のステップ数で変化させることができます。

しかし、Lineとは異なりSmoothはステップ数の指定ができないようです。(方法があれば教えてください)

その代わり、音のスムーズさを指定することができます。

コード全体

Lineの例と同じく、440Hzと880Hzの音を交互に鳴らしてみます。

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

#define CONTROL_RATE 1024

Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);

Metronome toggle(1000);

Smooth <int> smoothFreq(0.99f);

int freq;
int freq1 = 440;
int freq2 = 880;

void setup() {
    startMozzi(CONTROL_RATE);
    aSin.setFreq(440);
}

void updateControl() {
    if (toggle.ready()) {
        if (freq == freq1) {
            freq = freq2;
        } else {
            freq = freq1;
        }
    }
    aSin.setFreq(smoothFreq.next(freq));
}

int updateAudio() {
    return aSin.next();
}

void loop() {
    audioHook();
}

コードの説明

まず、Smooth.hをインクルードして、Smoothクラスのインスタンスを生成します。

#include <Smooth.h>

Smoothのテンプレートには今回intを指定し、引数としてスムーズさを指定します。スムーズさは0 <= x < 1の間で指定します。0を指定するとスムーズでなくなり、1に近づくほどスムーズになります。

Smooth <int> smoothFreq(0.99f);

updateControl()内で、smoothFreqのnext()関数を呼び出し、補間された値をそのままaSinの周波数に設定しています。

aSin.setFreq(smoothFreq.next(freq));

Portamento

最後にPortamento(ポルタメント)です。Portamentoは、ある値から別の値まで、一定の時間で変化させることができます。

Portamentoとは、滑らかに音程を変える演奏技法で、名前の通り、バイオリン等の音を再現したいのであれば、最適かもしれません。

コード全体

今までと同じく、440Hzと880Hzの音を交互に鳴らしてみます。

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

#define CONTROL_RATE 64

Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);

Metronome toggle(2000);

Portamento<CONTROL_RATE> aPortamento;

bool flag = false;

int noteNum;

void setup() {
    aSin.setFreq(440);
    aPortamento.setTime(1000);
    startMozzi(CONTROL_RATE);
}

void updateControl() {
    if (toggle.ready()) {
        noteNum = flag ? 69 : 81;
        flag = !flag;
        aPortamento.start((byte)noteNum);
    }

    aSin.setFreq_Q16n16(aPortamento.next());
}

int updateAudio() {
    return aSin.next();
}

void loop() {
    audioHook();
}

コードの説明

まず、Portamento.hをインクルードして、インスタンスを生成します。

#include <Portamento.h>
Portamento<CONTROL_RATE> aPortamento;

音を切り替えるための変数を定義します。

bool flag = false;

MIDIノートナンバーを格納する変数を定義します。

int noteNum;

setup()内で、Portamentoの時間を設定します。今回は1000msに設定しています。

aPortamento.setTime(1000);

updateControl()内で、flagの値に応じてnoteNumに69か81を代入し、flagを反転させます。

noteNum = flag ? 69 : 81;
flag = !flag;

そして、aPortamentoのstart()関数を呼び出します。start()関数は、引数にMIDIノートナンバーを指定します。

aPortamento.start((byte)noteNum);

aPortamentoのnext()関数を呼び出し、補間された値をそのままaSinの周波数に設定しています。

aSin.setFreq_Q16n16(aPortamento.next());

まとめ

今回は、Mozziを使い、音の周波数を連続的に変化させてみました。

次回は、FilterやEffectを使って様々な音を鳴らしてみます。

前 => エンベロープ(ADSR)とEventDelay