【Arduino】Mozzi入門1 様々な波形を出力する

7/01/2023

Arduino

t f B! P L

今回からArduinoの高機能ライブラリMozziを使って波形の出力など様々な音に関する処理を行っていきます。

次 => 和音の出力と周波数の計算

Mozziとは

Arduinoで様々な音に関する処理をするためのライブラリです。Arduinoの標準ライブラリであるtone()関数では、いわゆる電子音のような音しか出力できない上に単音のみの対応であり、和音の出力など高機能なことはできません。

Mozziでは、様々な波形の出力や和音の出力などが可能です。また、MIDIノート番号から周波数を計算して出力するといったこともできます。

インストール

ダウンロード:https://github.com/sensorium/Mozzi

GitHubからMozziのページにアクセスし、「Code」ボタンをクリックし、「Download ZIP」をクリックしてダウンロードします。

Arduino IDEを起動し、メニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式のライブラリをインストール...」をクリックしてダウンロードしたZIPファイルを選択すると、Mozziがインストールされます。

ハードウェアの準備

今回は、Arduino Unoとスピーカーを用意します。当然ですがブザーではなくスピーカーを使ってください

Arduino Unoの場合、D9ピンとGNDにスピーカーを接続します。

接続するピンはあらかじめ指定されています。Arduino Nanoの場合はD9、Arduino Megaの場合はD11などです。

くわしくはGitHubのQuick Startを参照してください。

正弦波の出力

まずはコードを提示してから、説明していきます。今回は基本として正弦波で440Hz(A4・ラ4)を出力します。

コード全体

// この3つは必須
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>

// updateControl()の呼び出し間隔を指定 デフォルトは64
#define CONTROL_RATE 128

// オシレーターの初期化
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);


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

// 使わない場合も必須
void updateControl(){
}


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

void loop(){
    // 実際に音の合成を行う関数
    audioHook();
}

コードの説明

Note

なんとなく理解していただければ良いので、適当に読み飛ばしてもらって構いません。

ライブラリをインクルード

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

まず、Mozziを使うために必要な3つのライブラリをインクルードします。

  • MozziGuts.h
    • Mozziの中核となるライブラリ
  • Oscil.h
    • オシレーターを扱うためのライブラリ
  • sin2048_int8.h
    • 正弦波の波形データを格納したテーブル

updateControl()の呼び出し間隔を指定

#define CONTROL_RATE 128

のちに説明するupdateControl()の呼び出し間隔を指定します。デフォルトは64ですが、今回は128にしています。

オシレーターの初期化

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);

オシレーターとは「発振器」のことで、音を発生させるためのものです。Mozziでは、Oscilクラスを使ってオシレーターを扱います。

テンプレート引数には、波形データの数とサンプリングレートを指定します。

SIN2048_NUM_CELLSsin2048_int8.hに定義されていて、今回は「2048」を指定しています。つまり、正弦波の波形データは1周期として2048個格納されています。

AUDIO_RATEMozziGuts.h(mozzi_config.h)に定義されていて、今回は「16384」を指定しています。

後述のnext()関数が呼び出されるたびに、オシレーターは次の波形データを返します。この波形データは、setFreq()関数で指定した周波数に応じて変化します。

Mozziとオシレーターの初期化

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

startMozzi()関数を呼び出すことで、Mozziの初期化を行います。

setFreq()関数で周波数を指定します。単位はHzです。今回は440Hzを指定してるのでラの音が出力されます。

updateControl()関数

void updateControl(){
}

updateControl()では、アナログ・デジタル入力検知や出力周波数変更など、比較的時間のかかる処理を行います。この関数はCONTROL_RATEで指定した間隔で呼び出されます。

今回は何も処理を行わないので、空の関数になっています。

updateAudio()関数

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

updateAudio()は波形を合成するコードを記述します。この関数はAUDIO_RATEで指定した間隔で呼び出されます。

つまり、デフォルトで一秒間に平均16384回呼び出されるため、この関数内は軽い処理にする必要があります。

今回は、オシレーターのnext()関数を呼び出しています。この関数は、オシレーターの波形データを返します。

audioHook()関数

void loop(){
    audioHook();
}

audioHook()を実行して、実際に音の合成を行います。

他の波形の出力

他の波形を出力するには、sin2048_int8.hを別のファイルに変更する必要があります。

ソースコードのtablesの中を見ると、様々な波形データが記録されているのがわかります。

今回は矩形波、のこぎり波、三角波を出力してみます。

矩形波

矩形波は本来エイリアスノイズが発生するので、Mozziではエイリアスノイズが発生しづらい矩形波のデータを用意してくれていますが、どうしてもノイズは発生するようです。

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

#define CONTROL_RATE 128

Oscil <SQUARE_NO_ALIAS_2048_NUM_CELLS, AUDIO_RATE> aSquare(SQUARE_NO_ALIAS_2048_DATA);

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

void updateControl(){
}

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

void loop(){
    audioHook();
}

のこぎり波

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

#define CONTROL_RATE 128

Oscil <SAW2048_NUM_CELLS, AUDIO_RATE> aSaw(SAW2048_DATA);

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

void updateControl(){
}

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

void loop(){
    audioHook();
}

三角波

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

#define CONTROL_RATE 128

Oscil <TRIANGLE2048_NUM_CELLS, AUDIO_RATE> aTriangle(TRIANGLE2048_DATA);

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

void updateControl(){
}

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

void loop(){
    audioHook();
}

自分で波形を作る

前述のとおり、Mozziでは、波形データがtablesに格納されています。

この波形データを自分で作ることで様々な音を作ることができます。

今回はピアノの音を作ってみます。

このサイト(tomari.org)によると、ピアノの音は、基本周波数を0.63、2倍音を0.17、3倍音を0.1、4倍音を0.03、5倍音を0.04、7倍音を0.04とした正弦波の合成で表現できるそうです。

Pythonで適当に実装してみます。

import matplotlib.pyplot as plt
import numpy as np

sine1 = 0.63
sine2 = 0.17
sine3 = 0.10
sine4 = 0.03
sine5 = 0.04
sine7 = 0.04

sine_x = np.linspace(0, 2 * np.pi, 2048)

sine1_y = sine1 * np.sin(sine_x)
sine2_y = sine2 * np.sin(2 * sine_x)
sine3_y = sine3 * np.sin(3 * sine_x)
sine4_y = sine4 * np.sin(4 * sine_x)
sine5_y = sine5 * np.sin(5 * sine_x)
sine7_y = sine7 * np.sin(7 * sine_x)

sine_y = sine1_y + sine2_y + sine3_y + sine4_y + sine5_y + sine7_y

sine_y = np.round(sine_y * (127 / max(sine_y)))

plt.plot(sine_x, sine_y)
plt.show()

波形データをコピーして、他の波形ファイルを見ながら、ピアノの波形データpiano2048_int8.hを作成します。

#ifndef PIANO2048_H_
#define PIANO2048_H_

#if ARDUINO >= 100
    #include "Arduino.h"
#else
    #include "WProgram.h"
#endif
#include "mozzi_pgmspace.h"

#define PIANO2048_NUM_CELLS 2048
#define PIANO2048_SAMPLERATE 2048

CONSTTABLE_STORAGE(int8_t) PIANO2048_DATA []  =
        {
                // 0,1,2,3,4,5,7,8,9... 長くなりすぎるので割愛
        };

#endif

これを.inoファイルがあるディレクトリに保存します。

#include "piano2048_int8.h"

上記のようにファイルをインクルードして、他と同じようにOscilを使用してピアノ風の音を出力することができます。

まとめ

今回はMozziを使って様々な波形を出力する方法を学びました。次回は、和音を出力してみます。

次 => 和音の出力と周波数の計算