今回は、Mozziを使い、音の周波数を連続的に変化させてみます。
今まで音を鳴らすときは、一音ずつしか鳴らすことができませんでしたが、バイオリンのように、音の高さを連続的に変化させる必要がある場合があります。
今回は、Line、Smooth、Portamentoを使って、音の高さを連続的に変化させてみます。
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を使って様々な音を鳴らしてみます。