4. ソフトウェア解説①

 HAT24のソフト開発で動作確認が必要な部分は、テストを繰り返しながら進めることになります。また、開発しながら仕様の細かい部分を詰めたり、機能不足があれば付け足していくことを前提にしているので、解説が前後するかも知れませんが悪しからず。
 テストに使用するハードウェアは写真のようにバラック状態で、Teensyは Windows10のノートパソコンに USB接続しています。パソコンの Arduino開発環境からスケッチをアップロードしたり、動作を指示、また状態をモニターします。
 前回でも述べましたが、マイクアンプにはステレオの ECMマイクを接続し、電源はオーディオ・アダプタの 3.3Vと GNDを使って供給します。そして出力は、オーディオ・アダプタのライン入力の左右(L/R)と GNDに接続します。いずれも配線はブレッドボード用のワイヤーです。音声出力はヘッドフォン出力プラグにイヤフォンジャックを差し込んでいます。




■ソフトウェア解説のあらすじ

 最初にスケッチをダウンロードしていただいて解説する方法もありますが、コード・サイズがかなり大きいことに加え、まだ修正の余地があることからダウンロードは後回し(次回に予定)にします。
 第1節では、まずスケッチの全体構成を取りまとめ、オーディオ・オブジェクトの操作について説明します。続いて初期化処理、モニタリング処理で何を行うかを述べます。第2、3節では初期化処理の具体的な内容を示し、モニタリング処理で必要な機能とHAT24の操作コマンドをまとめます。また第4節以降ではイコライザーの実現方法、自動音量制御、突発ノイズの回避方法などについて検討します。

1.スケッチの全体構成

 HAT24のスケッチは図のような構成で記述しています。
 1) ヘッダーファイルのインクルード

  1 #include <Audio.h>
  2 #include <Wire.h>
  3 #include <SPI.h>
  4 #include <SD.h>
  5 #include <SerialFlash.h>

 Audio.hは Teensy Audio libraryなど、Teensyとオーディオアダプターボードを使用するために必須です。4行目は SDカードを使用するためのもので、その他は使用するかどうかは別にしてほぼ定番のファイルです。


 2-1) 各種マクロの定義

 #define マクロ名  文字列
 〔例〕
   #define SAMPLE_RATE   44100

 2-2) オーディオ・オブジェクトの操作

 前章でも例示しましたが HAT24のオーディオ・オブジェクトの構成はとてもシンプルです。

 説明が重複しますが、i2s1はステレオライン入力で、音声は左右チャンネルそれぞれのアンプ amp1, amp2を通してヘッドフォン出力 i2s2に流れます。amp1, 2は左右の音量バランスを調整するためのものです。peak1, 2はピーク音量レベルを取得して撥音などの強烈なノイズの制御に用います。sgtl5000_1はどこにも接続されていませんが、SGTL5000オーディオ・シールドそのものであり、これによってイコライザーの設定や全体の音量調整などを行うのでこのように配置しています。これらは、オーディオ・システム設計ツールで簡単に描けることは前章で述べた通りです。
 この図からスケッチのコードを自動生成できますが、HAT24では i2s1や i2s2などの ID名を audioInputや audioOutputのようなわかりやすい名前に変えています。実際のコードは以下のようになっています。
// Create Audio objects and connections
AudioInputI2S           audioInput;
AudioOutputI2S          audioOutput;
AudioAnalyzePeak        peak_L;
AudioAnalyzePeak        peak_R;
AudioAmplifier          audioAmp_L;
AudioAmplifier          audioAmp_R;
AudioControlSGTL5000    audioShield;
AudioConnection         patchCord1(audioInput, 0, peak_L, 0);
AudioConnection         patchCord2(audioInput, 1, peak_R, 0);
AudioConnection         patchCord3(audioInput, 0, audioAmp_L, 0);
AudioConnection         patchCord4(audioInput, 1, audioAmp_R, 0);
AudioConnection         patchCord5(audioAmp_L, 0, audioOutput, 0);
AudioConnection         patchCord6(audioAmp_R, 0, audioOutput, 1);
 ここで、AudioInputI2Sや AudioAmplifierなどはデータ・タイプ名で、オブジェクトのクラス名のようなものです。オーディオ処理9行目以降の AudioConnectionは、オーディオ・オブジェクトの出力を別のオーディオ・オブジェクトの入力にルーティングしています。設計ツールで指定した接続関係がコード出力されますが、ID名を変更しています。コードから容易にわかるように、入出力が2つあるオブジェクトでは 0が左チャンネル、1が右チャンネルを表しています。


 2-3) イコライザー用構造体の定義

 イコライザーの実現方法については後で検討することになりますが、ここで以下のような構造体を定義しています。この位置に記述しているのは、後続の関数プロトタイプ定義で構造体名を参照する必要があるためです。
// Base equalizer parameter table
struct PEQPARAM {
  int   band;           // Band number (0~6)
  int   type;           // Filter type
  float fc;             // Center frequency
  float gain;           // Gain
  float Q;              // Quality Factor
};
struct PEQPARAM ptable[NUM_BAND];


 2-4) 関数プロトタイプの定義

 このスケッチで記述する個別の処理関数のプロトタイプを記述しています。
 〔例〕
   void initParamTable(PEQPARAM source[], PEQPARAM target[]);
   void setupEqualizer(PEQPARAM ptable[]);
      :
      :

 2-5) グローバル変数の定義

 このスケッチで必要な最小限のグローバル変数を記述しています。
 〔例〕
   // Global variables
   bool inMute = false; // Mute ON/OFF
   bool autoVolume = false; // Auto volume control ON/OFF
      :
      :

3) 初期化処理で行うこと

 初期化処理については次節で詳細を説明しますが、以下のような処理を行います。
  ・シリアルモニターの準備
  ・オーディオ処理用メモリーの確保
  ・SDカードの準備
  ・HAT24制御情報の入出力
  ・オーディオ・オブジェクトの初期設定と起動
  ・イコライザーの設定と起動
  ・音量制御の有効化


4) モニタリング処理で行うこと

 スケッチの構造上、初期化処理が終わると Teensyは無限ループに突入します。オーディオ・オブジェクトを、オーディオ・ライブラリの既定値(あらかじめ定められた状態)で利用するのであれば、モニタリングでは何もすることがありません。
 しかし、聴覚補助ツールとしては突発的なノイズから耳を守るための処理は不可欠であり、そのための異常音量の監視と対応処理はここで実行させます。
 また、マイクロフォンの感度やオーディオ出力の調整、イコライザーの設定変更や状態監視などができるようにする必要もあります。HAT24では、これらの調整や状態監視の大半は PCに接続したシリアルモニタとの通信処理で行うため、少々複雑なコードを記述することになります。コードの記述に備えて、これらは第3節で「HAT24の操作コマンド」として仕様をとりまとめます。


2.初期化処理の詳細

①準備処理

 準備処理は Arduino IDEの既定関数 setup()に記述します。
 まず Serial.beginでシリアルモニターが利用できるようにします(5行目)。引数では通信速度として 9600bpsを指定しています。
 次の AudioMemory(12)で、Teensyがオーディオ処理に使用するメモリーの割当量を設定します。実際の使用量は AudioMemoryUsageMax()で確認することができますが、HAT24ではこの程度で十分です。
 7行目の pinMode()では、マクロ VOLSW_PINで定義した番号のデジタル入出力ピンを、入力モード 「INPUT」で使用するよう指定しています。このピンにスイッチを接続して ON/OFFの状態を確認できるようにするのが目的です。
 10行目で SGTL5000オーディオ・シールドを有効にし、以降の準備処理中に発生するノイズが音声出力しないようにミュートをかけています。
/*****************************
 *  Initialization Process   *
 *****************************/
void setup() {
  Serial.begin(9600);
  AudioMemory(12);
  pinMode(VOLSW_PIN, INPUT);

  // Enable the audio shield.
  audioShield.enable();
  audioShield.muteHeadphone();

②SDカードの準備処理

14から 19行までは SDカードを使用する場合の定型パターンです。マクロ定義 SDCARD_CS_PINでは 10番ピンを指定しています。
 21から 24行では、まずマクロ EQ_PARAM_FILEで定義しているファイル名のパラメータファイルが存在するかどうかを確認しています。存在しなければ、処理関数 initParamTable()を呼んで動作環境の各種パラメータを初期化し、writeParameterFile()でその内容を SDカードに書き込みます。この処理はパラメータファイルが記録されてない SDカードを使用した最初の1回だけ実行します。
 26行では readParameterFile()によってパラメータファイルを読み込んで、動作環境の各種パラメータを設定します。イコライザーの設定と起動はこの時に行われます。パラメータファイルは任意の時点でシリアルモニターから書き込みを指示できるので、次回の起動時にはその時点の状態を復元して動作します。
   
  // Check SD drive
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Err: Unable to access the SD card!");
      delay(1000);
    }
  }
  // Write the default parameters to a file only once at the beginning of use.
  if (!SD.exists(EQ_PARAM_FILE)) {
    initParamTable(preset, ptable);
    writeParameterFile(currentVolume, ampGain_L, ampGain_R, ptable);
  }
  // Read parameter file and set the environment.
  if (!(readParameterFile(currentVolume, ampGain_L, ampGain_R, ptable))) {
    Serial.println("Err: Unable to load Equalizer parameter!");
    delay(1000);
  }
  Serial.println("Load Equalizer parameter, completed!");

③入力装置の指定とゲインの設定

 33から36行では、マクロ定義した値を使って audioShieldつまり SGTL5000の設定をしています。まずオーディオ入力にライン入力を指定し、続いて音量を設定。さらにライン入力とライン出力の信号レベルを設定しています。37と38行は左右のアンプ・ゲインの設定です。音量や信号レベル、アンプ・ゲインはシリアルモニターから調整できます。
   
  // Select input, and setup audio shield.
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  audioShield.volume(currentVolume);
  audioShield.lineInLevel(LINE_IN_LEVEL);
  audioShield.lineOutLevel(LINE_OUT_LEVEL);
  audioAmp_L.gain(ampGain_L);
  audioAmp_R.gain(ampGain_R);


④イコライザーと自動音量調整の設定

 41行では SGTL5000の「7バンドのパラメトリック・イコライザー」を使用することを宣言しています。次の行で、関数 setupEqualizer()を呼んでパラメトリック・イコライザーの設定を行い、デフォールト設定を使用できるようにします。46行では自動音量調整(AVC)を設定していますが、引数で指定しているマクロ定義値の意味と値については後で説明します。
   
  // Set the parametric equalizer.
  audioShield.eqSelect(PARAMETRIC_EQUALIZER);
  setupEqualizer(ptable);
  audioShield.audioPostProcessorEnable();

  // Prepare auto volume control
  audioShield.autoVolumeControl(AVC_MAXGAIN, AVC_RESPONSE, AVC_HARDLIMIT, 
                                    AVC_THRESHOLD, AVC_ATTACK, AVC_DECAY);


⑤終結処理

 グローバル変数 autoVolumeに自動音量調整が有効かどうかの状態を保持しています。初期化処理終了時にそれを確認して、自動音量調整を有効または無効に設定します。
 最後にミュートを解除して初期化処理を終了します。
  if (autoVolume)
    audioShield.autoVolumeEnable();
  else
    audioShield.autoVolumeDisable();
  audioShield.unmuteHeadphone();

以上が初期化処理コードのすべてです。


3.HAT24の操作コマンド

 モニタリング処理の大半は、シリアルモニターからキー入力されたコマンドを解析して、それに対応する処理を実行することです。したがってコードを記述する前にコマンドの種類・入力形式と機能を規定しておく必要があります。
 コードをわかりやすくするために各コマンドはマクロ定義しています。実際にキー入力するのはクォーテーションで囲まれた文字で、コマンドによってはさらに後続に文字または数値を伴います。
①ミュート

・マクロ: #define MUTE_ONOFF 'm'  // [m]ute switch
・機 能: 'm'を押すたびにミュートの「ON/OFF」を反転する。

②オートボリューム

・マクロ: #define MODE_AUTO_VOLUME 'a'  // [a]uto volume control mode
・機 能: 'a'を押すたびにオートボリュームの「有効/無効」を反転する。

③音量制御

・マクロ: #define MODE_VOLUME_CONTROL 'v'  // [v]olume control mode
・機 能: 'v'を押すたびに音量制御状態の「有効/無効」を反転する。

④左右の音量微調整

・マクロ: #define MODE_TUNING_VOLUME 't'  // [t]une up volume for each channel
・機 能: 't'を押すたびに音量微調整状態の「有効/無効」を反転する。
      't'または'tb'なら左右同時に調整、'tl'と'tr'で調整対象が左か右かを指定できる。
      再度't'を入力すると音量微調整状態を解除(無効に)する。

⑤音量アップ

・マクロ: #define DO_UP 'u'  // Count [u]p current variable
・機 能: 'u'を押すと「音量制御状態」か「音量微調整状態」が有効なら、対象の音量を増加する。

⑥音量ダウン

・マクロ: #define DO_DOWN 'd'  // Count [d]own current variable
・機 能: 'd'を押すと「音量制御状態」か「音量微調整状態」が有効なら、対象の音量を減少する。

⑦ライン入力レベルの変更

・マクロ: #define LINE_INOUT 'l'  // [l]ine in/out
・機 能: 'li,0'~'li,15'で入力レベルを 0~15に変更する。既定値は 5。

⑧ライン出力レベルの変更

・マクロ: #define LINE_INOUT 'l'  // [l]ine in/out
・機 能: 'lo,13'~'lo,31'で出力レベルを 13~31に変更する。既定値は 29。

⑨特定バンドのフィルター・タイプの変更

・マクロ: #define CHANGE_FILTER 'F'  // change the [F]ilter type of the target band
・機 能: 'F%,<filter-type&gr'で指定する。%はバンドで 0~9。
      <filter-type>は { LPF/HPF/BPF/NOTCH/PARAEQ/LSHELF/HSHELF }のいずれか。
      ただし filter-typeの意味は次のとおり。
        LPF: Lowpass filter
        HPF: Highpass filter
        BPF: Bandpass filter
        NOTCH: Notch filter
        PARAEQ: Parametric filter
        LSHELF: Lowshelf filter
        HSHELF: Highshelf filter
・入力例: F3,PARAEQ

⑩特定バンドの Fcを変更

・マクロ: #define CHANGE_FREQUENCY 'f' // Change the [f]requency of the target band
・機 能: f%,_'で指定。 ただし%はバンドで0~9。_で周波数の値をHzで指定する。
・入力例: f4,4200

⑪特定バンドのゲインを変更

・マクロ: #define CHANGE_GAIN 'g' // Change the [g]ain of the target band
・機 能: 'g%,_'で指定。 ただし%はバンドで0~9。_でゲイン値(±が可能)を指定する。
・入力例: g5,-2.5

⑫特定バンドの Q値を変更

・マクロ: #define CHANGE_Q 'q' // Change the [q] value of the target band
・機 能: 'q%,_'で指定。 ただし%はバンドで0~9。_で Q値(少数以下2桁まで)を指定する。
・入力例: q2,1.5

⑬特定バンドのパラメータ1式を変更

・マクロ: #define CHANGE_BAND_PARAM 'c' // [c]hange PEQ parameters for each band
・機 能: 'c%,<1>,<2>,<3>,<4>'で指定。
      ただし%はバンドで0~6。<1>はフィルター・タイプ、<2>はFc、<3>はゲイン値、<4>はQ値。
・入力例: c3,PARAEQ,2000,1.0,0.4

⑭環境設定を初期状態に戻す

・マクロ: #define SET_INITIAL_STATE 's' // [s]et to initial state
・機 能: 's'を入力すると環境設定を初期状態に戻す。

⑮動作環境のSD書き込み

・マクロ: #define WRITE_PRMFILE 'w' // [w]rite parameter fille
・機 能: 'w'を入力すると現在の動作環境をパラメータファイルに書き込む。

⑯現在の動作環境表示

・マクロ: #define TELL_STATUS '?' // Display status
・機 能: '?'を入力するとシリアルモニタに現在の動作環境を表示する。

⑰コマンド・ヘルプの表示

・マクロ: #define HELP_ME 'h' // Display help information
・機 能: 'h'を入力するとシリアルモニターにコマンド一覧を表示する。


モニタリング処理では、上記のキー入力データの判定と対応した処理関数の実行を記述することになります。


4.イコライザーの実装

 前章で述べたように、イコライザーは SGTL5000が備えている「7バンドのパラメトリック・イコライザー」を使用します。サンプリングレートは 44.1kHzで、理論的には直流から 22.05kHzまでの音声波形を損失なくデジタル化できます。このレートは音楽CDと同じで、同程度の音質が期待できることになります。
①グローバル変数の定義

 サンプリングレートとバンド数は HAT24の処理コードの中で使用するため、下記のようにマクロ定義しています。Q_UNITは量子化ユニット(quantization unit)と呼ばれる値で、フィルター係数を計算するときに使うのでマクロにしておきます。
// Basic attributes
#define SAMPLE_RATE           44100
#define NUM_BAND              (7)
#define Q_UNIT                524288
 パラメトリック・イコライザーは各バンドにフィルター係数を設定することで動作します。フィルター係数の計算については後で述べますが、計算にあたっては上記のサンプリングレートと量子化ユニットの他に、フィルター・タイプ、カットオフ周波数(または中心周波数)、ゲイン、Q値が必要になります。バンド単位にこれらのパラメータをまとめて PEQPARAM構造体として定義します。実際のパラメータは、起動時に SDカードに記録されたパラメータファイルを呼んで、バンド数分の構造体型の変数配列 ptableに格納します。
 これらの初期値として preset配列に 7バンド分の適当な値を準備しました。初回の起動時にはこの値を ptableに設定して動作しますが、このままでは聞くに堪えない音質がサウンド出力されます。HAT24のチューニングとは、HAT24の操作コマンドを使ってこれらの値を変更・調整して、自身に最適な音質・音量を求めるということです。
// Base equalizer parameter table
struct PEQPARAM {
  int   band;           // Band number (0~6)
  int   type;           // Filter type
  float fc;             // Center frequency
  float gain;           // Gain
  float Q;              // Quality Factor
};
struct PEQPARAM preset[NUM_BAND] = {  // Default setting initial value
  { 0, FILTER_PARAEQ,    50,  3.0, 1.0 },
  { 1, FILTER_PARAEQ,   150,  3.0, 1.0 },
  { 2, FILTER_PARAEQ,   400,  3.0, 1.0 },
  { 3, FILTER_PARAEQ,   800,  3.0, 1.0 },
  { 4, FILTER_PARAEQ,  1600,  3.0, 1.0 },
  { 5, FILTER_PARAEQ,  4000,  3.0, 1.0 },
  { 6, FILTER_PARAEQ,  8000,  3.0, 1.0 }
};
struct PEQPARAM ptable[NUM_BAND];


②イコライザーの実装

 イコライザーの機能自体は SGTL5000の DAP(Digital Audio Processor)が装備しているので、内部処理の詳細はいっさい気にする必要はありません。「7バンドのパラメトリック・イコライザー」の使用を指示し、パラメータを設定して起動するだけです。
 スケッチの中でこれらのコードは隔たった場所に記述されていますが、関連する部分をピックアップすると以下のようになります。
/*****************************
 *  Initialization Process   *
 *****************************/
void setup() {
   :
  // Set the parametric equalizer.
  audioShield.eqSelect(PARAMETRIC_EQUALIZER);
  setupEqualizer(ptable);
  audioShield.audioPostProcessorEnable();
   :
}

/*
    Configures the parametric equalizer.
 */
void setupEqualizer(PEQPARAM ptable[])
{
  for (int i=0; i<NUM_BAND; i++) {
    setFilter(i, ptable[i]);
  }
  audioShield.eqFilterCount(NUM_BAND);
}

/*
    Calculate filter coefficients and set the parametric equalizer.
 */
void setFilter(int band, PEQPARAM param) {
  int coefficients[5];
  if (band >= 0 && band < NUM_BAND) {
    calcBiquad(param.type, param.fc, param.gain, param.Q, Q_UNIT, SAMPLE_RATE, coefficients);
    audioShield.eqFilter(band, coefficients);
  }
}
 
 7行目の eqSelect()でパラメトリック・イコライザーの使用を選択(指示)しています。 PARAMETRIC_EQUALIZERは「7バンドのパラメトリック・イコライザー」を示す既定のマクロです。次に処理関数 setupEqualizer(ptable)でイコライザーのパラメータを設定します。
 16行目からがそのコードで、引数で受けたパラメータ配列に格納された 7バンド分のパラメータを、処理関数 setFilte()で設定します。最後の eqFilterCount(NUM_BAND)で、設定が完了している NUM_BAND個のパラメトリック・フィルターを有効にします。これによってイコライザーが起動します。
 27行からの setFilter関数はバンドNo.とパラメータのセットを受けて、バンド単位のパラメータを処理します。パラメトリック・イコライザーは 7つの Biquad filterで実装されていて、31行目の eqFilter(band, coefficients)で該当バンドにフィルター係数(coefficients)を設定します。(少し細かいことになりますが、フィルター係数 int coefficients[5]には、Biquad filter係数の [b0, b1, b2, a1, a2]が格納されます)。
 Biquad filter係数は 30行目の calcBiquad()関数で計算します。ついでながら、この関数コードは次のファイルに記述されています。
  C:\Users\[user_name]\AppData\Local\Arduino15\packages\teensy\hardware\avr
        \[version_number]\libraries\Audio\control_sgtl5000.cpp
    ※ [ ]内は個々の環境による。
 関数プロトタイプは次のとおりです。
  void calcBiquad(uint8_t filtertype, float fC, float dB_Gain, float Q,
           uint32_t quantization_unit, uint32_t fS, int *coef);
   filtertype: フィルター種別(下記のいずれかを指定)
      FILTER_LOPASS / FILTER_HIPASS / FILTER_BANDPASS / FILTER_NOTCH /
      FILTER_PARAEQ / FILTER_LOSHELF / FILTER_HISHELF
   fC: カットオフ周波数(または中心周波数)
   dB_Gain: ゲイン(dB)
   Q: Q値
   quantization_unit: 量子化ユニット  // if(SGTL5000_PEQ) quantization_unit=524288
   fS: サンプリングレート
   *coef: フィルター係数へのポインタ
 filtertypeから fsまでの値をセットし、フィルター係数 coefの位置を指定して calcBuquad()を呼べば、coefの整数配列に計算結果が求められます。


5.ノイズ対策と自動音量調整

①ノイズ対策

 拍手やものの落下など強烈な破裂音にどう対処するかは耳を守る上で大変重要です。検討の結果、「大きな音がしたら入力ラインレベルを落としてミュートをかける」という、きわめてシンプルな方法を採ることにしました。以下はそのテストプログラムです。
#include <Audio.h>

// Sound volume control factors
#define VOLUME_INIT           0.72      // Sound volume initial value
#define AMP_BASEGAIN          1.0       // Tune up volume initial value
#define LINE_IN_LEVEL         5         // Line in  default voltage level
#define LINE_OUT_LEVEL        29        // Line out default voltage level

// Sound limiter control factors
#define LIMITER_PEAK_GAIN    0.85      // Limiter sound peak gain
#define LIMITER_ESCAPE_GAIN  0.40      // Limiter control escape value

// Create Audio objects and connections
AudioInputI2S           audioInput;
AudioOutputI2S          audioOutput;
AudioAnalyzePeak        peak_L;
AudioAnalyzePeak        peak_R;
AudioAmplifier          audioAmp_L;
AudioAmplifier          audioAmp_R;
AudioControlSGTL5000    audioShield;
AudioConnection         patchCord1(audioInput, 0, peak_L, 0);
AudioConnection         patchCord2(audioInput, 1, peak_R, 0);
AudioConnection         patchCord3(audioInput, 0, audioAmp_L, 0);
AudioConnection         patchCord4(audioInput, 1, audioAmp_R, 0);
AudioConnection         patchCord5(audioAmp_L, 0, audioOutput, 0);
AudioConnection         patchCord6(audioAmp_R, 0, audioOutput, 1);

float ampGain_L = AMP_BASEGAIN;         // Left channel gain
float ampGain_R = AMP_BASEGAIN;         // Right channel gain

// Function prototype
void limiterProcess(float, float);

// Global variables
float currentVolume = VOLUME_INIT;      // Current volume

void setup() {
  Serial.begin(9600);
  AudioMemory(12);

  // Enable the audio shield.
  audioShield.enable();
  audioShield.muteHeadphone();

  // Select input, and setup audio shield.
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  audioShield.volume(currentVolume);
  audioShield.lineInLevel(LINE_IN_LEVEL);
  audioShield.lineOutLevel(LINE_OUT_LEVEL);
  audioAmp_L.gain(ampGain_L);
  audioAmp_R.gain(ampGain_R);
  audioShield.eqSelect(PARAMETRIC_EQUALIZER);
  audioShield.audioPostProcessorEnable();
  audioShield.unmuteHeadphone();
}

void loop() {
  limiterProcess(LIMITER_PEAK_GAIN, LIMITER_ESCAPE_GAIN);
}

void limiterProcess(float peak, float escape)
{
  if (peak_L.available() && peak_R.available()) {
    float leftPeak=peak_L.read();
    float rightPeak=peak_R.read();
    if (leftPeak > peak || rightPeak > peak) {
      audioShield.muteHeadphone();
      audioShield.lineInLevel(0);
      Serial.print("L:"); Serial.print(leftPeak);
      Serial.print(",  R:"); Serial.println(rightPeak);
      delay(10);
      while (peak_L.read() > escape || peak_R.read() > escape) {
        Serial.print("+");
        delay(10);
      }
      Serial.println(">>");
      audioShield.lineInLevel(LINE_IN_LEVEL);
      delay(30);
      audioShield.unmuteHeadphone();
    }
  }
}
 limiterProcess()が目的のリミッター関数です。モニタリング処理 loop()ではリミッター関数だけを処理しています。
 音量のピーク値は AudioAnalyzePeakの read()で取得します。ピーク値が0.0~1.0の値で通知されます。ポイントは LIMITER_PEAK_GAINと LIMITER_ESCAPE_GAINの値の設定です。ピーク値が 0.85を超えれば音声を遮断し、0.40に下がるまで待って元に戻すようにしています。HAT24ではこの関数とゲイン値をそのまま使うことにします。


②自動音量調整

 Teensy Audio libraryには自動音量調整(AVC: Auto Volume Control)機能があり、大きな信号を低減し、低レベルの信号を増幅して聞きやすくすることができます。ライブラリで AVCメソッドの引数は次のように説明されています。
--------------------------------------------------------------------------------------
 Valid values for dap_avc parameters
   maxGain; Maximum gain that can be applied
    0 - 0 dB
    1 - 6.0 dB
    2 - 12 dB
   lbiResponse; Integrator Response
    0 - 0 mS
    1 - 25 mS
    2 - 50 mS
    3 - 100 mS
   hardLimit
    0 - Hard limit disabled. AVC Compressor/Expander enabled.
    1 - Hard limit enabled. The signal is limited to the programmed threshold 
      (signal saturates at the threshold)
   threshold
    floating point in range 0 to -96 dB
   attack
    floating point figure is dB/s rate at which gain is increased
   decay
    floating point figure is dB/s rate at which gain is reduced
--------------------------------------------------------------------------------------
 スケッチ内ではこのメソッドを次のように呼んでいます。
  autoVolumeControl(AVC_MAXGAIN, AVC_RESPONSE, AVC_HARDLIMIT, AVC_THRESHOLD, AVC_ATTACK, AVC_DECAY);
 これらの引数の値を上記の説明に沿っていくらに設定するか、試行錯誤の結果、次の値としました。
 // Auto volume control factors
   #define AVC_MAXGAIN           1         // 6.0dB
   #define AVC_RESPONSE          3         // 100ms
   #define AVC_HARDLIMIT         0         // unused
   #define AVC_THRESHOLD         -10       // (dBFS)
   #define AVC_ATTACK            6         // (dB/s)
   #define AVC_DECAY             8         // (dB/s)


 今回はかなり長くなってしまいました。次回までに手動音量調整機能の追加を検討、テストしてスケッチを仕上げダウンロードできるようにする予定です。お楽しみに!