1.追加する新機能
まず、今回追加する機能について簡単に取りまとめておきましょう。
①ミュート機能
テスト中に再生を一時的に中断したり再開するためのミュート機能を追加します。大文字キー[M]を押すことで中断と再開を反転させます。
②音量調整機能
再生中の音量を、左右同時にアップ/ダウンさせる機能です。大文字キー[U]でアップ、[D]でダウンさせます。
③イコライザーチャンネル選択機能
イコライザーは左右を独立して設定できるようにします。イコライザーモードへの切り替えは小文字キー[e]で行いますが、切り替えた直後は左チャンネルが選択されます。これを右チャンネルに切り替えるには大文字キー[R]を、もう一度左へ戻すには大文字キー[L]を押します。
左右どちらかのチャンネルが選択された状態で数値キー[1]~[9]を押すと、そのたびに対応するチャンネルのバンド(周波数帯域)1~9の感度(音量レベル)を1レベルずつアップさせます。[Shift]キーを押さえた状態で数値キーを押すと、対応したバンドの感度を1レベルずつダウンさせます。
イコライザーチャンネル選択状態で数値キーが押されると感度に影響を与えるので、設定完了後はチャンネル選択完了を指示して数値キーを無効にする必要があります。大文字キー[Q]でこれを指示します。
④イコライザーバンド感度の表示
数値キーでバンドの感度を調整すると、その都度バンドの感度状態を表示させます。
⑤その他の機能拡張
これまで小文字キーの[w]と[r]で適用中のフィルター種別やカットオフ周波数を一時ファイルに書き込み/入力させていましたが、これにイコライザーのバンド別周波数情報を追加します。また小文字キー[d]で表示していたカットオフ周波数等の情報に、イコライザーのバンド別ノッチ周波数、フィルター係数、左右の感度を追加します。
⑥リミッターとノイズ抑制処理の追加
今回のテストをしている最中に原因不明のアンダーフローが連続発生し、猛烈なノイズに襲われました。音量を上げている場合は耳に危険を及ぼすことにもなりかねません。これを防ぐためにリミッターを設けて、出力シグナルがある音量レベルを超えた場合はそのレベルで抑えることにしました。あわせて、入力信号に含まれる微小なノイズを抑制するための閾値とロジックを設定します。
2.音声信号処理モジュールの改版
前回作成したイコライザー関係のコードや今回の機能追加により、音声信号処理モジュールは大幅に変わります。
①ヘッダーファイル
従来のfilter.hにわずかな#define文の追加といくつもの関数プロトタイプ定義を記述します。ファイル名をfilter2.hと改称しました。#define文の追加は以下のハイライト部分で、18行目以降の関数プロトタイプ部分は省略します。
891011121314151617 /*
処理モード(キー文字と一致させる)
*/
#define MODE_LOWPASS 'l' // ローパス
#define MODE_HIGHPASS 'h' // ハイパス
#define MODE_BANDPASS 'b' // バンドパス
#define MODE_EQUALIZER 'e' // イコライザー
#define MODE_THROUGH 'x' // フィルターなし
#define EQ_BANDS 9 // イコライザー・バンド数
8 9 10 11 12 13 14 15 16 17 | /* 処理モード(キー文字と一致させる) */ #define MODE_LOWPASS 'l' // ローパス #define MODE_HIGHPASS 'h' // ハイパス #define MODE_BANDPASS 'b' // バンドパス #define MODE_EQUALIZER 'e' // イコライザー #define MODE_THROUGH 'x' // フィルターなし #define EQ_BANDS 9 // イコライザー・バンド数 |
②音声信号処理関数モジュール
コード前半の次のハイライト部分を追加しました。ファイル名がfilter.cからfilter2.cに変わります。
910111213141516171819202122232425262728 #include <stdio.h>
#include <math.h>
#include "filter2.h"
// 音量調整用
int
mute_sw = 0;
// ミュート・スイッチ
float
mute_cst = 0.0;
// ミュート定数
float
volume = 0.5;
// 音量係数
float
volume_cst = 0.1;
// 音量増減定数
float
volume_min = 0.0;
// 音量最小値
float
volume_max = 1.0;
// 音量最大値
double
limit_val = 0.95;
// リミッター閾値
int
noise_sw = 0;
// ノイズ除去スイッチ
double
noise_val = 0.005;
// ノイズ除去閾値
// イコライザー用
double
fc[EQ_BANDS+1] = {0};
// ノッチ周波数
double
Ql[EQ_BANDS+1] = {0};
// 感度・左チャンネル
double
Qr[EQ_BANDS+1] = {0};
// 感度・右チャンネル
double
a[EQ_BANDS+1], r[EQ_BANDS+1];
// フィルター係数
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <stdio.h> #include <math.h> #include "filter2.h" // 音量調整用 int mute_sw = 0; // ミュート・スイッチ float mute_cst = 0.0; // ミュート定数 float volume = 0.5; // 音量係数 float volume_cst = 0.1; // 音量増減定数 float volume_min = 0.0; // 音量最小値 float volume_max = 1.0; // 音量最大値 double limit_val = 0.95; // リミッター閾値 int noise_sw = 0; // ノイズ除去スイッチ double noise_val = 0.005; // ノイズ除去閾値 // イコライザー用 double fc[EQ_BANDS+1] = {0}; // ノッチ周波数 double Ql[EQ_BANDS+1] = {0}; // 感度・左チャンネル double Qr[EQ_BANDS+1] = {0}; // 感度・右チャンネル double a[EQ_BANDS+1], r[EQ_BANDS+1]; // フィルター係数 |
第20章の『デジタルフィルターの作成』で作成した2つのフィルター適用処理関数、applyPassFilterとapplyThroughFilterは、ミュートと音量調整さらにノイス抑制処理とリミッター機能の追加で次のように変わりました。
なお、音声処理でアンダーフローやオーバーフローエラーが発生したことを知らせるため、コンソールに'*'と'$'を表示していますが、同様に、音声信号がリミッターの値を超えたら'X'を表示させています。
102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 /*
applyPassFilter : ローパス、ハイパス、バンドパスフィルター信号処理関数
[引数] int order : フィルターの次数
int frames : バッファのフレーム数
float *inptr : 入力バッファへのポインター
double *h : フィルター係数配列へのポインター
float *outptr : 出力バッファへのポインター
*/
void
applyPassFilter(
int
order,
int
frames,
float
*inptr,
double
*h,
float
*outptr)
{
double
sl, sr, yl, yr, zl, zr;
int
over, done;
over = done = 0;
for
(
int
i=0; i<frames; i++) {
if
(mute_sw == 0) {
sl = (
double
)*inptr++;
sr = (
double
)*inptr++;
if
(noise_sw !=0) {
// ノイズ除去を処理する
if
(sl >= -noise_val && sl <= noise_val)
sl = 0;
if
(sr >= -noise_val && sr <= noise_val)
sr = 0;
}
yl = yr = 0;
for
(
int
j=0; j<=order; j++) {
yl = yl + sl * h[j];
yr = yr + sr * h[j];
}
zl = (
float
)(yl * volume);
if
(zl > limit_val) {
// リミッターをかける
zl = limit_val; over = 1;
}
else
if
(zl < -limit_val) {
zl = -limit_val; over = 1;
}
zr = (
float
)(yr * volume);
if
(zr > limit_val) {
zr = limit_val; over = 1;
}
else
if
(zr < -limit_val) {
zr = -limit_val; over = 1;
}
if
(over == 1 && done == 0) {
printf
(
"X"
); done = 1;
}
*outptr++ = (
float
)zl;
*outptr++ = (
float
)zr;
}
else
{
*outptr++ = mute_cst;
*outptr++ = mute_cst;
}
}
}
/*
applyThroughFilter : フィルターなし(素通し)信号処理関数
[引数] int frames : バッファのフレーム数
float* inptr : 入力バッファへのポインター
float* outptr : 出力バッファへのポインター
*/
void
applyThroughFilter(
int
frames,
float
*inptr,
float
*outptr)
{
float
sl, sr, zl, zr;
int
over, done;
over = done = 0;
for
(
int
i=0; i<frames; i++) {
if
(mute_sw == 0) {
sl = *inptr++;
sr = *inptr++;
if
(noise_sw !=0) {
// ノイズ除去を処理する
if
(sl >= -noise_val && sl <= noise_val)
sl = 0;
if
(sr >= -noise_val && sr <= noise_val)
sr = 0;
}
zl = (
float
)(sl * volume);
if
(zl > limit_val) {
// リミッターをかける
zl = limit_val; over = 1;
}
else
if
(zl < -limit_val) {
zl = -limit_val; over = 1;
}
zr = (
float
)(sr * volume);
if
(zr > limit_val) {
zr = limit_val; over = 1;
}
else
if
(zr < -limit_val) {
zr = -limit_val; over = 1;
}
if
(over == 1 && done == 0) {
printf
(
"X"
); done = 1;
}
*outptr++ = (
float
)zl;
*outptr++ = (
float
)zr;
}
else
{
*outptr++ = mute_cst;
*outptr++ = mute_cst;
}
}
}
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | /* applyPassFilter : ローパス、ハイパス、バンドパスフィルター信号処理関数 [引数] int order : フィルターの次数 int frames : バッファのフレーム数 float *inptr : 入力バッファへのポインター double *h : フィルター係数配列へのポインター float *outptr : 出力バッファへのポインター */ void applyPassFilter( int order, int frames, float *inptr, double *h, float *outptr) { double sl, sr, yl, yr, zl, zr; int over, done; over = done = 0; for ( int i=0; i<frames; i++) { if (mute_sw == 0) { sl = ( double )*inptr++; sr = ( double )*inptr++; if (noise_sw !=0) { // ノイズ除去を処理する if (sl >= -noise_val && sl <= noise_val) sl = 0; if (sr >= -noise_val && sr <= noise_val) sr = 0; } yl = yr = 0; for ( int j=0; j<=order; j++) { yl = yl + sl * h[j]; yr = yr + sr * h[j]; } zl = ( float )(yl * volume); if (zl > limit_val) { // リミッターをかける zl = limit_val; over = 1; } else if (zl < -limit_val) { zl = -limit_val; over = 1; } zr = ( float )(yr * volume); if (zr > limit_val) { zr = limit_val; over = 1; } else if (zr < -limit_val) { zr = -limit_val; over = 1; } if (over == 1 && done == 0) { printf ( "X" ); done = 1; } *outptr++ = ( float )zl; *outptr++ = ( float )zr; } else { *outptr++ = mute_cst; *outptr++ = mute_cst; } } } /* applyThroughFilter : フィルターなし(素通し)信号処理関数 [引数] int frames : バッファのフレーム数 float* inptr : 入力バッファへのポインター float* outptr : 出力バッファへのポインター */ void applyThroughFilter( int frames, float *inptr, float *outptr) { float sl, sr, zl, zr; int over, done; over = done = 0; for ( int i=0; i<frames; i++) { if (mute_sw == 0) { sl = *inptr++; sr = *inptr++; if (noise_sw !=0) { // ノイズ除去を処理する if (sl >= -noise_val && sl <= noise_val) sl = 0; if (sr >= -noise_val && sr <= noise_val) sr = 0; } zl = ( float )(sl * volume); if (zl > limit_val) { // リミッターをかける zl = limit_val; over = 1; } else if (zl < -limit_val) { zl = -limit_val; over = 1; } zr = ( float )(sr * volume); if (zr > limit_val) { zr = limit_val; over = 1; } else if (zr < -limit_val) { zr = -limit_val; over = 1; } if (over == 1 && done == 0) { printf ( "X" ); done = 1; } *outptr++ = ( float )zl; *outptr++ = ( float )zr; } else { *outptr++ = mute_cst; *outptr++ = mute_cst; } } } |
209行目以降に以下のコードを追加しています。今回の機能追加のうち、機能選択やイコライザーのバンド別感度調整などキー入力を伴う部分以外の大半がここに記述されています。
209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 /*
changeMuteMode : ミュートをON/OFFする
*/
void
changeMuteMode()
{
mute_sw==0 ? (mute_sw=1) : (mute_sw=0);
}
/*
tellMuteMode : ミュートの状態を通知する
[返却] int mute_sw
*/
int
tellMuteMode()
{
return
mute_sw;
}
/*
raiseVolume : 音量係数をアップする
*/
void
raiseVolume()
{
if
(volume < volume_max && mute_sw == 0)
volume += volume_cst;
}
/*
reduceVolume : 音量係数をダウンするする
*/
void
reduceVolume()
{
if
(volume > (volume_min+0.01) && mute_sw == 0)
volume -= volume_cst;
}
/*
setVolume : 音量係数を直接設定する(Web連携用)
*/
void
setVolume(
float
val)
{
volume = val;
}
/*
音量係数を通知する
[返却] float volume ミュート中ならマイナス値
*/
float
tellVolumeRate()
{
return
volume;
}
/*
initEqualizer : イコライザーの初期化処理
[引数] double Fs : サンプリング周波数
int level : 感度(0~9)
*/
void
initEqualizer(
double
Fs)
{
double
Ke;
// 帯域幅
double
w;
fc[0] = 16.0;
for
(
int
i=1; i<=EQ_BANDS; i++) {
Ke = fc[i-1];
// 帯域幅は低域側ノッチ周波数を使用
w = 2.0*M_PI * Ke / Fs;
r[i] = (1 +
cos
(w) -
sin
(w)) / (1 +
cos
(w) +
sin
(w));
// 帯域幅を設定
fc[i]= fc[i-1] * 2;
a[i] = -(1 + r[i]) *
cos
(2.0*M_PI*fc[i]/Fs);
// フィルタ係数
}
}
/*
adjustQl : 左側感度を設定する
[引数] int band : 対象バンド
int q : 感度(0~9)
*/
void
adjustQl(
int
band,
int
q)
{
if
(band >= 0 && band <= EQ_BANDS)
Ql[band] = (
double
)q;
}
/*
adjustQr : 右側感度を設定する
[引数] int band : 対象バンド
int q : 感度(0~9)
*/
void
adjustQr(
int
band,
int
q)
{
if
(band >= 0 && band <= EQ_BANDS)
Qr[band] = (
double
)q;
}
/*
tellQl : 左側感度を通知する
[引数] int band : 対象バンド
[返却] int Ql[・] : 感度(0~9)
*/
int
tellQl(
int
band)
{
if
(band >= 0 && band <= EQ_BANDS)
return
(
int
)Ql[band];
else
return
0;
}
/*
tellQr : 左側感度を通知する
[引数] int band : 対象バンド
[返却] int Qr[・] : 感度(0~9)
*/
int
tellQr(
int
band)
{
if
(band >= 0 && band <= EQ_BANDS)
return
(
int
)Qr[band];
else
return
0;
}
/*
applyEqualizer : イコライザー信号処理関数
[引数] int frames : バッファのフレーム数
float *inptr : 入力バッファへのポインター
float *outptr : 出力バッファへのポインター
*/
void
applyEqualizer(
int
frames,
float
*inptr,
float
*outptr)
{
static
double
u0l[EQ_BANDS+1] = {0};
// u0~u2は静的変数でないとノイズが発生する
static
double
u1l[EQ_BANDS+1] = {0};
static
double
u2l[EQ_BANDS+1] = {0};
static
double
u0r[EQ_BANDS+1] = {0};
static
double
u1r[EQ_BANDS+1] = {0};
static
double
u2r[EQ_BANDS+1] = {0};
double
z = 0.3;
//(感度の調整値)
double
xl, xr, sl, sr, yl, yr, zl, zr, ql, qr;
int
over, done;
over = done = 0;
for
(
int
i=0; i<frames; i++) {
sl = (
double
)*inptr++;
sr = (
double
)*inptr++;
if
(noise_sw !=0) {
// ノイズ除去を処理する
if
(sl >= -noise_val && sl <= noise_val)
sl = 0;
if
(sr >= -noise_val && sr <= noise_val)
sr = 0;
}
yl = yr = 0;
for
(
int
j=1; j<=EQ_BANDS; j++) {
u0l[j] = sl - a[j] * u1l[j] - r[j] * u2l[j];
// 逆ノッチフィルタの内部信号計算
u0r[j] = sr - a[j] * u1r[j] - r[j] * u2r[j];
xl = r[j] * u0l[j] + a[j] * u1l[j] + u2l[j];
// オールパスフィルタ出力計算
xr = r[j] * u0r[j] + a[j] * u1r[j] + u2r[j];
yl = yl + (Ql[j]*z) * (sl - xl)/2.0;
// 逆ノッチ出力に感度をつけて加算
yr = yr + (Qr[j]*z) * (sr - xr)/2.0;
u2l[j] = u1l[j]; u1l[j] = u0l[j];
// 信号遅延
u2r[j] = u1r[j]; u1r[j] = u0r[j];
}
if
(mute_sw == 0) {
ql = zl = yl * volume;
if
(zl > limit_val) {
// リミッターをかける
zl = limit_val; over = 1;
}
else
if
(zl < -limit_val) {
zl = -limit_val; over = 1;
}
qr = zr = yr * volume;
if
(zr > limit_val) {
zr = limit_val; over = 1;
}
else
if
(zr < -limit_val) {
zr = -limit_val; over = 1;
}
if
(over == 1 && done == 0) {
printf
(
"X"
); done = 1;
}
*outptr++ = (
float
)
atan
(zl / (M_PI/2.0));
// クリップ防止で出力バッファ転送
*outptr++ = (
float
)
atan
(zr / (M_PI/2.0));
}
else
{
*outptr++ = mute_cst;
*outptr++ = mute_cst;
}
}
}
/*
changeNoiseSwitch : ノイズスイッチをON/OFFする
*/
void
changeNoiseSwitch(
void
)
{
noise_sw==0 ? (noise_sw=1) : (noise_sw=0);
}
/*
setNoiseSwitch : ノイズスイッチを設定する
[引数] int stat : 状態(0/1)
*/
void
setNoiseSwitch(
int
stat)
{
noise_sw = stat;
}
/*
tellNoiseSwitch : ノイズスイッチの状態を通知する
[返却] int noise_switch
*/
int
tellNoiseSwitch(
void
)
{
return
noise_sw;
}
/*
displayEqualizerTable : イコライザー情報を表示する
*/
void
displayEqualizerTable()
{
printf
(
"\n[Equalizer]\n"
);
printf
(
" # r[・], fc[・], a[・], Q[L=R]\n"
);
for
(
int
i=0; i<=EQ_BANDS; i++) {
printf
(
"%2d : %6f, %7.0f, %9.6f, %4.1f=%.1f\n"
, i, r[i], fc[i], a[i], Ql[i], Qr[i]);
}
printf
(
"\n"
);
}
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 | /* changeMuteMode : ミュートをON/OFFする */ void changeMuteMode() { mute_sw==0 ? (mute_sw=1) : (mute_sw=0); } /* tellMuteMode : ミュートの状態を通知する [返却] int mute_sw */ int tellMuteMode() { return mute_sw; } /* raiseVolume : 音量係数をアップする */ void raiseVolume() { if (volume < volume_max && mute_sw == 0) volume += volume_cst; } /* reduceVolume : 音量係数をダウンするする */ void reduceVolume() { if (volume > (volume_min+0.01) && mute_sw == 0) volume -= volume_cst; } /* setVolume : 音量係数を直接設定する(Web連携用) */ void setVolume( float val) { volume = val; } /* 音量係数を通知する [返却] float volume ミュート中ならマイナス値 */ float tellVolumeRate() { return volume; } /* initEqualizer : イコライザーの初期化処理 [引数] double Fs : サンプリング周波数 int level : 感度(0~9) */ void initEqualizer( double Fs) { double Ke; // 帯域幅 double w; fc[0] = 16.0; for ( int i=1; i<=EQ_BANDS; i++) { Ke = fc[i-1]; // 帯域幅は低域側ノッチ周波数を使用 w = 2.0*M_PI * Ke / Fs; r[i] = (1 + cos (w) - sin (w)) / (1 + cos (w) + sin (w)); // 帯域幅を設定 fc[i]= fc[i-1] * 2; a[i] = -(1 + r[i]) * cos (2.0*M_PI*fc[i]/Fs); // フィルタ係数 } } /* adjustQl : 左側感度を設定する [引数] int band : 対象バンド int q : 感度(0~9) */ void adjustQl( int band, int q) { if (band >= 0 && band <= EQ_BANDS) Ql[band] = ( double )q; } /* adjustQr : 右側感度を設定する [引数] int band : 対象バンド int q : 感度(0~9) */ void adjustQr( int band, int q) { if (band >= 0 && band <= EQ_BANDS) Qr[band] = ( double )q; } /* tellQl : 左側感度を通知する [引数] int band : 対象バンド [返却] int Ql[・] : 感度(0~9) */ int tellQl( int band) { if (band >= 0 && band <= EQ_BANDS) return ( int )Ql[band]; else return 0; } /* tellQr : 左側感度を通知する [引数] int band : 対象バンド [返却] int Qr[・] : 感度(0~9) */ int tellQr( int band) { if (band >= 0 && band <= EQ_BANDS) return ( int )Qr[band]; else return 0; } /* applyEqualizer : イコライザー信号処理関数 [引数] int frames : バッファのフレーム数 float *inptr : 入力バッファへのポインター float *outptr : 出力バッファへのポインター */ void applyEqualizer( int frames, float *inptr, float *outptr) { static double u0l[EQ_BANDS+1] = {0}; // u0~u2は静的変数でないとノイズが発生する static double u1l[EQ_BANDS+1] = {0}; static double u2l[EQ_BANDS+1] = {0}; static double u0r[EQ_BANDS+1] = {0}; static double u1r[EQ_BANDS+1] = {0}; static double u2r[EQ_BANDS+1] = {0}; double z = 0.3; //(感度の調整値) double xl, xr, sl, sr, yl, yr, zl, zr, ql, qr; int over, done; over = done = 0; for ( int i=0; i<frames; i++) { sl = ( double )*inptr++; sr = ( double )*inptr++; if (noise_sw !=0) { // ノイズ除去を処理する if (sl >= -noise_val && sl <= noise_val) sl = 0; if (sr >= -noise_val && sr <= noise_val) sr = 0; } yl = yr = 0; for ( int j=1; j<=EQ_BANDS; j++) { u0l[j] = sl - a[j] * u1l[j] - r[j] * u2l[j]; // 逆ノッチフィルタの内部信号計算 u0r[j] = sr - a[j] * u1r[j] - r[j] * u2r[j]; xl = r[j] * u0l[j] + a[j] * u1l[j] + u2l[j]; // オールパスフィルタ出力計算 xr = r[j] * u0r[j] + a[j] * u1r[j] + u2r[j]; yl = yl + (Ql[j]*z) * (sl - xl)/2.0; // 逆ノッチ出力に感度をつけて加算 yr = yr + (Qr[j]*z) * (sr - xr)/2.0; u2l[j] = u1l[j]; u1l[j] = u0l[j]; // 信号遅延 u2r[j] = u1r[j]; u1r[j] = u0r[j]; } if (mute_sw == 0) { ql = zl = yl * volume; if (zl > limit_val) { // リミッターをかける zl = limit_val; over = 1; } else if (zl < -limit_val) { zl = -limit_val; over = 1; } qr = zr = yr * volume; if (zr > limit_val) { zr = limit_val; over = 1; } else if (zr < -limit_val) { zr = -limit_val; over = 1; } if (over == 1 && done == 0) { printf ( "X" ); done = 1; } *outptr++ = ( float ) atan (zl / (M_PI/2.0)); // クリップ防止で出力バッファ転送 *outptr++ = ( float ) atan (zr / (M_PI/2.0)); } else { *outptr++ = mute_cst; *outptr++ = mute_cst; } } } /* changeNoiseSwitch : ノイズスイッチをON/OFFする */ void changeNoiseSwitch( void ) { noise_sw==0 ? (noise_sw=1) : (noise_sw=0); } /* setNoiseSwitch : ノイズスイッチを設定する [引数] int stat : 状態(0/1) */ void setNoiseSwitch( int stat) { noise_sw = stat; } /* tellNoiseSwitch : ノイズスイッチの状態を通知する [返却] int noise_switch */ int tellNoiseSwitch( void ) { return noise_sw; } /* displayEqualizerTable : イコライザー情報を表示する */ void displayEqualizerTable() { printf ( "\n[Equalizer]\n" ); printf ( " # r[・], fc[・], a[・], Q[L=R]\n" ); for ( int i=0; i<=EQ_BANDS; i++) { printf ( "%2d : %6f, %7.0f, %9.6f, %4.1f=%.1f\n" , i, r[i], fc[i], a[i], Ql[i], Qr[i]); } printf ( "\n" ); } |
3.聴覚補助ツールHAT21の改版
従来のhat21a.cに対して大幅な修正や追加が必要なことから、新たにhat21b.cファイルにコードを記述します。以下ではhat21a.cから変わった部分をハイライト表示しています。
①関数プロトタイプ宣言、データ定義部
イコライザー信号処理用のバンド別感度は音声信号処理関数モジュール内に定義していますが、それを表示するためにHAT21側でもQL[EQ_BANDS+1],QR[EQ_BANDS+1]として領域を確保していて、感度の変更が指定されるたびにadjustQl(),adjustQr()関数で同期をとっています。
32333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 #define ID "HAT21 Version 0.20"
#define SAMPLE_RATE (96000) // サンプリングレート
#define FRAMES_PER_BUFFER (512) // フレームバッファ・サイズ
#define FILTER_ORDER 64 // フィルターの次数
#define LOGSIZE 1000 // ログファイルの最大レコード数
#define PA_SAMPLE_TYPE paFloat32
#define SAMPLE_SIZE (sizeof(float))
#define SAMPLE_SILENCE (0.0f)
// Function prototype
int
main(
void
);
void
raiseFrequency(
void
);
void
reduceFrequency(
void
);
void
raiseBandFrequency(
void
);
void
reduceBandFrequency(
void
);
void
initBasicInfo(
void
);
int
getBasicInfo(
char
*);
void
putBasicInfo(
char
*);
void
displayBasicInfo(
void
);
char
getFilterLogid(
char
);
char
* getFilterName(
char
);
int
getSoftwareVolumeDevice(
void
);
void
displayQuantity(
char
);
int
kbhit(
void
);
int
openLog(LOGCTRL*);
void
closeLog(LOGCTRL*);
int
writeLog(LOGCTRL*,
char
);
// Monitoring switch & control
int
test_mode = 1;
int
logging = 1;
LOGCTRL logctrl;
LOGREC logrec;
// Basic cutoff frequency
const
double
cst_hi_cutoff = 8000.0;
// バンドパス:高域側遮断周波数 [Hz]
const
double
cst_lo_cutoff = 400.0;
// バンドパス:低域側遮断周波数 [Hz]
const
double
cst_low_pass = 8000.0;
// ローパス:カットオフ周波数 [Hz]
const
double
cst_high_pass = 800.0;
// ハイパス:カットオフ周波数 [Hz]
const
double
cst_step_diff = 100.0;
// ステップ毎の調整値[Hz]
// Current cutoff frequency
double
fe_hi_cutoff;
// バンドパス:高域側遮断周波数
double
fe_lo_cutoff;
// バンドパス:低域側遮断周波数
double
fe_low_pass;
// ローパス:カットオフ周波数
double
fe_high_pass;
// ハイパス:カットオフ周波数
double
fe_differ;
// ステップ毎の調整値
// Volume control
float
volumeCtrl = 0.5;
// 音量コントロール
// Range of frequency
const
double
cst_fe_upper = 15000.0;
// 上限周波数
const
double
cst_fe_lower = 100.0;
// 下限周波数
// For signal processing
char
process_mode;
// 処理モード(処理中のフィルタリング種別)
double
h[FILTER_ORDER+1] = {0};
// フィルター係数
float
buffer[FRAMES_PER_BUFFER][2];
// Playback buffer
// For Equalizer
int
QL[EQ_BANDS+1] = {0};
// 感度・左チャンネル
int
QR[EQ_BANDS+1] = {0};
// 感度・右チャンネル
char
channel =
' '
;
// イコライザー設定チャンネル('L'/'R'/' ')
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #define ID "HAT21 Version 0.20" #define SAMPLE_RATE (96000) // サンプリングレート #define FRAMES_PER_BUFFER (512) // フレームバッファ・サイズ #define FILTER_ORDER 64 // フィルターの次数 #define LOGSIZE 1000 // ログファイルの最大レコード数 #define PA_SAMPLE_TYPE paFloat32 #define SAMPLE_SIZE (sizeof(float)) #define SAMPLE_SILENCE (0.0f) // Function prototype int main( void ); void raiseFrequency( void ); void reduceFrequency( void ); void raiseBandFrequency( void ); void reduceBandFrequency( void ); void initBasicInfo( void ); int getBasicInfo( char *); void putBasicInfo( char *); void displayBasicInfo( void ); char getFilterLogid( char ); char * getFilterName( char ); int getSoftwareVolumeDevice( void ); void displayQuantity( char ); int kbhit( void ); int openLog(LOGCTRL*); void closeLog(LOGCTRL*); int writeLog(LOGCTRL*, char ); // Monitoring switch & control int test_mode = 1; int logging = 1; LOGCTRL logctrl; LOGREC logrec; // Basic cutoff frequency const double cst_hi_cutoff = 8000.0; // バンドパス:高域側遮断周波数 [Hz] const double cst_lo_cutoff = 400.0; // バンドパス:低域側遮断周波数 [Hz] const double cst_low_pass = 8000.0; // ローパス:カットオフ周波数 [Hz] const double cst_high_pass = 800.0; // ハイパス:カットオフ周波数 [Hz] const double cst_step_diff = 100.0; // ステップ毎の調整値[Hz] // Current cutoff frequency double fe_hi_cutoff; // バンドパス:高域側遮断周波数 double fe_lo_cutoff; // バンドパス:低域側遮断周波数 double fe_low_pass; // ローパス:カットオフ周波数 double fe_high_pass; // ハイパス:カットオフ周波数 double fe_differ; // ステップ毎の調整値 // Volume control float volumeCtrl = 0.5; // 音量コントロール // Range of frequency const double cst_fe_upper = 15000.0; // 上限周波数 const double cst_fe_lower = 100.0; // 下限周波数 // For signal processing char process_mode; // 処理モード(処理中のフィルタリング種別) double h[FILTER_ORDER+1] = {0}; // フィルター係数 float buffer[FRAMES_PER_BUFFER][2]; // Playback buffer // For Equalizer int QL[EQ_BANDS+1] = {0}; // 感度・左チャンネル int QR[EQ_BANDS+1] = {0}; // 感度・右チャンネル char channel = ' ' ; // イコライザー設定チャンネル('L'/'R'/' ') |
②主処理:初期化処理部
ここでイコライザーの初期化処理と感度の設定をしています。
114115116117118119120121122123124125126127128129130131132133134 /* == 準備処理 == */
/* (ログファイルの準備) */
logctrl.max_rec = LOGSIZE;
if
(openLog(&logctrl) != 0) {
printf
(
"*ERR : ログファイルの準備に失敗しました\n"
);
exit
(-1);
}
/* イコライザー初期化処理 */
initEqualizer(SAMPLE_RATE);
/* (遮断周波数等の準備) */
if
(getBasicInfo(
"base.ctrl"
) != 0) {
printf
(
"*ERR : 基本制御情報の準備に失敗しました\n"
);
exit
(-2);
}
/* (イコライザー音量係数の設定) */
for
(
int
i=0; i<=EQ_BANDS; i++) {
adjustQl(i, QL[i]);
adjustQr(i, QR[i]);
}
if
(logging == 1) writeLog(&logctrl, LOGID_BEGIN);
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | /* == 準備処理 == */ /* (ログファイルの準備) */ logctrl.max_rec = LOGSIZE; if (openLog(&logctrl) != 0) { printf ( "*ERR : ログファイルの準備に失敗しました\n" ); exit (-1); } /* イコライザー初期化処理 */ initEqualizer(SAMPLE_RATE); /* (遮断周波数等の準備) */ if (getBasicInfo( "base.ctrl" ) != 0) { printf ( "*ERR : 基本制御情報の準備に失敗しました\n" ); exit (-2); } /* (イコライザー音量係数の設定) */ for ( int i=0; i<=EQ_BANDS; i++) { adjustQl(i, QL[i]); adjustQr(i, QR[i]); } if (logging == 1) writeLog(&logctrl, LOGID_BEGIN); |
③主処理:音声入力再生処理部
イコライザーモードの時はここでイコライザー信号処理関数を実行させています。
241242243244245246247248249250251252253254255 /* (フィルター信号処理) */
switch
(process_mode) {
case
MODE_THROUGH:
applyThroughFilter(FRAMES_PER_BUFFER, (
float
*)sampleBlock, (
float
*)buffer);
break
;
case
MODE_LOWPASS:
case
MODE_HIGHPASS:
case
MODE_BANDPASS:
applyPassFilter(FILTER_ORDER, FRAMES_PER_BUFFER,
(
float
*)sampleBlock, h, (
float
*)buffer);
break
;
case
MODE_EQUALIZER:
applyEqualizer(FRAMES_PER_BUFFER, (
float
*)sampleBlock, (
float
*)buffer);
break
;
}
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | /* (フィルター信号処理) */ switch (process_mode) { case MODE_THROUGH: applyThroughFilter(FRAMES_PER_BUFFER, ( float *)sampleBlock, ( float *)buffer); break ; case MODE_LOWPASS: case MODE_HIGHPASS: case MODE_BANDPASS: applyPassFilter(FILTER_ORDER, FRAMES_PER_BUFFER, ( float *)sampleBlock, h, ( float *)buffer); break ; case MODE_EQUALIZER: applyEqualizer(FRAMES_PER_BUFFER, ( float *)sampleBlock, ( float *)buffer); break ; } |
④主処理:フィルター係数取得処理部
イコライザーモードを指示されると左右の感度を表示して、処理モードをイコライザーに設定します。
269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 /* (キー判定とフィルター係数取得処理等) */
switch
(key) {
case
MODE_LOWPASS:
if
(process_mode != MODE_LOWPASS) {
process_mode = MODE_LOWPASS;
printf
(
"\nApplied : [LPF]"
);
if
(logging == 1) writeLog(&logctrl, LOGID_MODELPF);
getCoefficientLPF(fe_low_pass, SAMPLE_RATE, FILTER_ORDER, h);
}
break
;
case
MODE_HIGHPASS:
if
(process_mode != MODE_HIGHPASS) {
process_mode = MODE_HIGHPASS;
printf
(
"\nApplied : [HPF]"
);
if
(logging == 1) writeLog(&logctrl, LOGID_MODEHPF);
getCoefficientHPF(fe_high_pass, SAMPLE_RATE, FILTER_ORDER, h);
}
break
;
case
MODE_BANDPASS:
if
(process_mode != MODE_BANDPASS) {
process_mode = MODE_BANDPASS;
printf
(
"\nApplied : [BPF]"
);
if
(logging == 1) writeLog(&logctrl, LOGID_MODEBPF);
getCoefficientBPF(fe_lo_cutoff, fe_hi_cutoff, SAMPLE_RATE, FILTER_ORDER, h);
}
break
;
case
MODE_THROUGH:
if
(process_mode != MODE_THROUGH) {
process_mode = MODE_THROUGH;
printf
(
"\nApplied : [NON]"
);
if
(logging == 1) writeLog(&logctrl, LOGID_MODENON);
}
break
;
case
MODE_EQUALIZER:
printf
(
"\n"
);
displayQuantity(
'L'
);
printf
(
"\n"
);
displayQuantity(
'R'
);
printf
(
"\n"
);
if
(process_mode != MODE_EQUALIZER) {
process_mode = MODE_EQUALIZER;
printf
(
"\nApplied : [EQU]"
);
if
(logging == 1) writeLog(&logctrl, LOGID_EQUALIZER);
channel =
'L'
;
printf
(
"\nEqualizer: Left channel selected.\n"
);
// 左チャンネルを選択させる
}
break
;
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | /* (キー判定とフィルター係数取得処理等) */ switch (key) { case MODE_LOWPASS: if (process_mode != MODE_LOWPASS) { process_mode = MODE_LOWPASS; printf ( "\nApplied : [LPF]" ); if (logging == 1) writeLog(&logctrl, LOGID_MODELPF); getCoefficientLPF(fe_low_pass, SAMPLE_RATE, FILTER_ORDER, h); } break ; case MODE_HIGHPASS: if (process_mode != MODE_HIGHPASS) { process_mode = MODE_HIGHPASS; printf ( "\nApplied : [HPF]" ); if (logging == 1) writeLog(&logctrl, LOGID_MODEHPF); getCoefficientHPF(fe_high_pass, SAMPLE_RATE, FILTER_ORDER, h); } break ; case MODE_BANDPASS: if (process_mode != MODE_BANDPASS) { process_mode = MODE_BANDPASS; printf ( "\nApplied : [BPF]" ); if (logging == 1) writeLog(&logctrl, LOGID_MODEBPF); getCoefficientBPF(fe_lo_cutoff, fe_hi_cutoff, SAMPLE_RATE, FILTER_ORDER, h); } break ; case MODE_THROUGH: if (process_mode != MODE_THROUGH) { process_mode = MODE_THROUGH; printf ( "\nApplied : [NON]" ); if (logging == 1) writeLog(&logctrl, LOGID_MODENON); } break ; case MODE_EQUALIZER: printf ( "\n" ); displayQuantity( 'L' ); printf ( "\n" ); displayQuantity( 'R' ); printf ( "\n" ); if (process_mode != MODE_EQUALIZER) { process_mode = MODE_EQUALIZER; printf ( "\nApplied : [EQU]" ); if (logging == 1) writeLog(&logctrl, LOGID_EQUALIZER); channel = 'L' ; printf ( "\nEqualizer: Left channel selected.\n" ); // 左チャンネルを選択させる } break ; |
⑤主処理:キー判定処理部
270行目のキー判定の続きに、342行目以降のコードを追加しています。追加機能のキー判定と関数呼び出しはすべてここで行っています。処理内容はコメントのとおりです。
338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
case
'w'
:
// カットオフ周波数等を一時ファイルに書き出す
putBasicInfo(
"temp.ctrl"
);
if
(logging == 1) writeLog(&logctrl, LOGID_WRITE);
printf
(
" (WRITE) "
);
break
;
case
'M'
:
// ミュートをON/OFFする
changeMuteMode();
if
(tellMuteMode() == 0)
printf
(
"\n Mute:OFF\n"
);
else
printf
(
"\n Mute:ON\n"
);
break
;
case
'N'
:
// ノイズ除去スイッチをON/OFFする
changeNoiseSwitch();
if
(tellNoiseSwitch() == 0)
printf
(
"\n Noise eraser: OFF\n"
);
else
printf
(
"\n Noise eraser: ON\n"
);
break
;
case
'U'
:
// 音量アップ
raiseVolume();
volumeCtrl = tellVolumeRate();
if
(volumeCtrl >= 0.0)
printf
(
"(%3.2f)\n"
, volumeCtrl);
break
;
case
'D'
:
// 音量ダウン
reduceVolume();
volumeCtrl = tellVolumeRate();
if
(volumeCtrl >= 0.0)
printf
(
"(%3.2f)\n"
, volumeCtrl);
break
;
case
'L'
:
// イコライザー左チャンネル設定指示
process_mode = MODE_EQUALIZER;
channel =
'L'
;
printf
(
"\nEqualizer: Left channel selected.\n"
);
break
;
case
'R'
:
// イコライザー右チャンネル設定指示
process_mode = MODE_EQUALIZER;
channel =
'R'
;
printf
(
"\nEqualizer: Right channel selected.\n"
);
break
;
case
'Q'
:
// イコライザー設定終了
channel =
' '
;
printf
(
"\nEqualizer: Channel selector is disabled.\n"
);
default
:
if
(process_mode == MODE_EQUALIZER) {
if
(channel ==
' '
)
// チャンネル未指定ならスキップ
break
;
if
(key >= 49 && key <= 49+EQ_BANDS-1) {
// 数字キー押下時
int
pos = key-48;
if
(channel ==
'L'
) {
QL[pos] = QL[pos] + 1;
// クオリティファクタを1増加
if
(QL[pos] > 9)
// 9を超えると9に変更
QL[pos] = 9;
adjustQl(pos, QL[pos]);
displayQuantity(channel);
}
else
{
QR[pos] = QR[pos] + 1;
// クオリティファクタを1増加
if
(QR[pos] > 9)
// 9を超えると9に変更
QR[pos] = 9;
adjustQr(pos, QR[pos]);
displayQuantity(channel);
}
}
else
if
(key >= 33 && key <= 33+EQ_BANDS-1) {
// [SHIFT] + 数字キー押下時
int
pos = key-32;
if
(channel ==
'L'
) {
QL[pos] = QL[pos] - 1;
// クオリティファクタを1減少
if
(QL[pos] < 0)
QL[pos] = 0;
// 0を割り込むと0に変更
adjustQl(pos, QL[pos]);
displayQuantity(channel);
}
else
{
QR[pos] = QR[pos] - 1;
// クオリティファクタを1減少
if
(QR[pos] < 0)
QR[pos] = 0;
// 0を割り込むと0に変更
adjustQr(pos, QR[pos]);
displayQuantity(channel);
}
}
key = 0;
}
else
printf
(
"\r"
);
break
;
}
}
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 | case 'w' : // カットオフ周波数等を一時ファイルに書き出す putBasicInfo( "temp.ctrl" ); if (logging == 1) writeLog(&logctrl, LOGID_WRITE); printf ( " (WRITE) " ); break ; case 'M' : // ミュートをON/OFFする changeMuteMode(); if (tellMuteMode() == 0) printf ( "\n Mute:OFF\n" ); else printf ( "\n Mute:ON\n" ); break ; case 'N' : // ノイズ除去スイッチをON/OFFする changeNoiseSwitch(); if (tellNoiseSwitch() == 0) printf ( "\n Noise eraser: OFF\n" ); else printf ( "\n Noise eraser: ON\n" ); break ; case 'U' : // 音量アップ raiseVolume(); volumeCtrl = tellVolumeRate(); if (volumeCtrl >= 0.0) printf ( "(%3.2f)\n" , volumeCtrl); break ; case 'D' : // 音量ダウン reduceVolume(); volumeCtrl = tellVolumeRate(); if (volumeCtrl >= 0.0) printf ( "(%3.2f)\n" , volumeCtrl); break ; case 'L' : // イコライザー左チャンネル設定指示 process_mode = MODE_EQUALIZER; channel = 'L' ; printf ( "\nEqualizer: Left channel selected.\n" ); break ; case 'R' : // イコライザー右チャンネル設定指示 process_mode = MODE_EQUALIZER; channel = 'R' ; printf ( "\nEqualizer: Right channel selected.\n" ); break ; case 'Q' : // イコライザー設定終了 channel = ' ' ; printf ( "\nEqualizer: Channel selector is disabled.\n" ); default : if (process_mode == MODE_EQUALIZER) { if (channel == ' ' ) // チャンネル未指定ならスキップ break ; if (key >= 49 && key <= 49+EQ_BANDS-1) { // 数字キー押下時 int pos = key-48; if (channel == 'L' ) { QL[pos] = QL[pos] + 1; // クオリティファクタを1増加 if (QL[pos] > 9) // 9を超えると9に変更 QL[pos] = 9; adjustQl(pos, QL[pos]); displayQuantity(channel); } else { QR[pos] = QR[pos] + 1; // クオリティファクタを1増加 if (QR[pos] > 9) // 9を超えると9に変更 QR[pos] = 9; adjustQr(pos, QR[pos]); displayQuantity(channel); } } else if (key >= 33 && key <= 33+EQ_BANDS-1) { // [SHIFT] + 数字キー押下時 int pos = key-32; if (channel == 'L' ) { QL[pos] = QL[pos] - 1; // クオリティファクタを1減少 if (QL[pos] < 0) QL[pos] = 0; // 0を割り込むと0に変更 adjustQl(pos, QL[pos]); displayQuantity(channel); } else { QR[pos] = QR[pos] - 1; // クオリティファクタを1減少 if (QR[pos] < 0) QR[pos] = 0; // 0を割り込むと0に変更 adjustQr(pos, QR[pos]); displayQuantity(channel); } } key = 0; } else printf ( "\r" ); break ; } } |
⑥いくつかの関数の追加と修正
・カットオフ周波数等をファイルから設定する。
567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 int
getBasicInfo(
char
*fname)
{
FILE
*fp =
fopen
(fname,
"rb"
);
if
(fp == NULL ) {
// ファイルが存在しなければ、
if
(
strcmp
(fname,
"base.ctrl"
) == 0) {
// base.ctrなら遮断周波数等の初期値を設定する
process_mode = MODE_THROUGH;
initBasicInfo();
displayBasicInfo();
}
else
{
process_mode = MODE_THROUGH;
initBasicInfo();
putBasicInfo(
"temp.ctrl"
);
}
}
else
{
// 遮断周波数等をファイルから読み込んで設定する
int
noise_stat;
if
(
fread
(&process_mode,
sizeof
(
char
), 1, fp) < 1)
return
-1;
if
(
fread
(&volumeCtrl,
sizeof
(
float
), 1, fp) < 1)
return
-1;
if
(
fread
(&noise_stat,
sizeof
(
int
), 1, fp) < 1)
return
-1;
if
(
fread
(&fe_hi_cutoff,
sizeof
(
double
), 1, fp) < 1)
return
-1;
if
(
fread
(&fe_lo_cutoff,
sizeof
(
double
), 1, fp) < 1)
return
-1;
if
(
fread
(&fe_low_pass,
sizeof
(
double
), 1, fp) < 1)
return
-1;
if
(
fread
(&fe_high_pass,
sizeof
(
double
), 1, fp) < 1)
return
-1;
if
(
fread
(&fe_differ,
sizeof
(
double
), 1, fp) < 1)
return
-1;
for
(
int
i=0; i<=EQ_BANDS; i++) {
if
(
fread
(&QL[i],
sizeof
(
int
), 1, fp) < 1)
return
-1;
if
(
fread
(&QR[i],
sizeof
(
int
), 1, fp) < 1)
return
-1;
}
setVolume(volumeCtrl); setNoiseSwitch(noise_stat);
displayBasicInfo();
fclose
(fp);
}
return
0;
}
・カットオフ周波数等をファイルに書き込む。
629630631632633634635636637638639640641642643644645646647 void
putBasicInfo(
char
*fname)
{
int
noise_stat = tellNoiseSwitch();
FILE
*fp =
fopen
(fname,
"wb"
);
fwrite
(&process_mode,
sizeof
(
char
), 1, fp);
fwrite
(&volumeCtrl,
sizeof
(
float
), 1, fp);
fwrite
(&noise_stat,
sizeof
(
int
), 1, fp);
fwrite
(&fe_hi_cutoff,
sizeof
(
double
), 1, fp);
fwrite
(&fe_lo_cutoff,
sizeof
(
double
), 1, fp);
fwrite
(&fe_low_pass,
sizeof
(
double
), 1, fp);
fwrite
(&fe_high_pass,
sizeof
(
double
), 1, fp);
fwrite
(&fe_differ,
sizeof
(
double
), 1, fp);
for
(
int
i=0; i<=EQ_BANDS; i++) {
fwrite
(&QL[i],
sizeof
(
int
), 1, fp);
fwrite
(&QR[i],
sizeof
(
int
), 1, fp);
}
fclose
(fp);
}
・フィルター種別からログIDを取得する。
654655656657658659660661662663664665666667 char
getFilterLogid(
char
mode)
{
char
logid;
switch
(mode) {
case
MODE_LOWPASS: logid = LOGID_MODELPF;
break
;
case
MODE_HIGHPASS: logid = LOGID_MODEHPF;
break
;
case
MODE_BANDPASS: logid = LOGID_MODEBPF;
break
;
case
MODE_THROUGH: logid = LOGID_MODENON;
break
;
case
MODE_EQUALIZER: logid = LOGID_EQUALIZER;
break
;
default
: logid =
'?'
;
break
;
}
return
logid;
}
・フィルター種別からフィルター名を取得する。
674675676677678679680681682683684685686 char
* getFilterName(
char
mode)
{
static
char
filter_name[10];
switch
(mode) {
case
MODE_LOWPASS:
strcpy
(filter_name,
"LPF"
);
break
;
case
MODE_HIGHPASS:
strcpy
(filter_name,
"HPF"
);
break
;
case
MODE_BANDPASS:
strcpy
(filter_name,
"BPF"
);
break
;
case
MODE_THROUGH:
strcpy
(filter_name,
"NON"
);
break
;
case
MODE_EQUALIZER:
strcpy
(filter_name,
"EQU"
);
break
;
default
:
strcpy
(filter_name,
""
);
break
;
}
return
filter_name;
}
・帯域別音量を表示する。
708709710711712713714715716717718719720721 /*
displayQuantity : 帯域別音量を表示する
[引数] char ch : チャンネル('L'または'R')
*/
void
displayQuantity(
char
ch) {
printf
(
" Lv(%c) "
, ch);
for
(
int
i=1; i<=EQ_BANDS; i++){
if
(ch ==
'L'
)
printf
(
" %d,"
,(
int
)(QL[i]));
else
printf
(
" %d,"
,(
int
)(QR[i]));
}
printf
(
" \r"
);
}
・ログ情報をファイルに書き出す。
811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871 int
writeLog(LOGCTRL* ctrl,
char
type)
{
char
kind[21] =
""
;
struct
sysinfo info;
ulong recpos;
LOGREC rec;
// ログ出力が指定されていなければ即復帰する
if
(logging == 0)
return
0;
// レコード数が最大なら出力位置をファイル先頭にセットする
if
(ctrl->rec_cnt >= ctrl->max_rec) {
ctrl->rec_cnt = 0;
fseek
(ctrl->log_ptr, 0L, SEEK_SET);
}
// ファイルのカレント位置から1レコード分読み込んで
if
(
fread
(&rec,
sizeof
(LOGREC), 1, ctrl->log_ptr) < 1)
return
-1;
// 書き出し位置を調整する
fseek
(ctrl->log_ptr, -
sizeof
(LOGREC), SEEK_CUR);
memset
(&rec, 0x00,
sizeof
(LOGREC));
// メモリー使用状態を取得する
sysinfo(&info);
unsigned
int
allmem = info.totalram/1024;
unsigned
int
freemem = info.freeram/1024;
// メッセージ種別からメッセージを編集する
switch
(type) {
case
LOGID_ERROVF:
strcpy
(kind,
"/Input Overflow "
);
break
;
case
LOGID_ERRUDF:
strcpy
(kind,
"/Output Underflow "
);
break
;
case
LOGID_ABEND:
strcpy
(kind,
"/Abend PORTAUDIO "
);
break
;
case
LOGID_BEGIN:
strcpy
(kind,
"==== Begin ====== "
);
break
;
case
LOGID_STRAT:
strcpy
(kind,
"==== Start ====== "
);
break
;
case
LOGID_END:
strcpy
(kind,
"==== End ====== "
);
break
;
case
LOGID_FINAL:
strcpy
(kind,
"==== Final ====== "
);
break
;
case
LOGID_CLEAR:
strcpy
(kind,
"* Clear CutoffFe "
);
break
;
case
LOGID_READ:
strcpy
(kind,
"* Read temp.ctrl "
);
break
;
case
LOGID_WRITE:
strcpy
(kind,
"* Write temp.ctrl "
);
break
;
case
LOGID_MODENON:
strcpy
(kind,
">> Applied : NON "
);
break
;
case
LOGID_MODELPF:
strcpy
(kind,
">> Applied : LPF "
);
break
;
case
LOGID_MODEHPF:
strcpy
(kind,
">> Applied : HPF "
);
break
;
case
LOGID_MODEBPF:
strcpy
(kind,
">> Applied : BPF "
);
break
;
case
LOGID_EQUALIZER:
strcpy
(kind,
">> Applied : EQU "
);
break
;
default
:
strcpy
(kind,
"????????????????? "
);
break
;
}
// メッセージと日時・メモリー状態をカレント・ログファイルへ書き込んで
time_t
timer =
time
(NULL);
struct
tm
*date =
localtime
(&timer);
sprintf
(rec.daytime,
"%04d/%02d/%02d %02d:%02d:%02d"
,
date->tm_year+1900, date->tm_mon+1, date->tm_mday,
date->tm_hour, date->tm_min, date->tm_sec);
memcpy
(rec.message, kind,
sizeof
(rec.message));
sprintf
(rec.note,
"%d/%d KB"
, freemem, allmem);
rec.note[39] = 0x0d;
fwrite
(&rec,
sizeof
(LOGREC), 1, ctrl->log_ptr);
// ログのレコードカウンターをインクリメントする
ctrl->rec_cnt = ctrl->rec_cnt + 1;
return
0;
}
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 | int getBasicInfo( char *fname) { FILE *fp = fopen (fname, "rb" ); if (fp == NULL ) { // ファイルが存在しなければ、 if ( strcmp (fname, "base.ctrl" ) == 0) { // base.ctrなら遮断周波数等の初期値を設定する process_mode = MODE_THROUGH; initBasicInfo(); displayBasicInfo(); } else { process_mode = MODE_THROUGH; initBasicInfo(); putBasicInfo( "temp.ctrl" ); } } else { // 遮断周波数等をファイルから読み込んで設定する int noise_stat; if ( fread (&process_mode, sizeof ( char ), 1, fp) < 1) return -1; if ( fread (&volumeCtrl, sizeof ( float ), 1, fp) < 1) return -1; if ( fread (&noise_stat, sizeof ( int ), 1, fp) < 1) return -1; if ( fread (&fe_hi_cutoff, sizeof ( double ), 1, fp) < 1) return -1; if ( fread (&fe_lo_cutoff, sizeof ( double ), 1, fp) < 1) return -1; if ( fread (&fe_low_pass, sizeof ( double ), 1, fp) < 1) return -1; if ( fread (&fe_high_pass, sizeof ( double ), 1, fp) < 1) return -1; if ( fread (&fe_differ, sizeof ( double ), 1, fp) < 1) return -1; for ( int i=0; i<=EQ_BANDS; i++) { if ( fread (&QL[i], sizeof ( int ), 1, fp) < 1) return -1; if ( fread (&QR[i], sizeof ( int ), 1, fp) < 1) return -1; } setVolume(volumeCtrl); setNoiseSwitch(noise_stat); displayBasicInfo(); fclose (fp); } return 0; } |
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 | void putBasicInfo( char *fname) { int noise_stat = tellNoiseSwitch(); FILE *fp = fopen (fname, "wb" ); fwrite (&process_mode, sizeof ( char ), 1, fp); fwrite (&volumeCtrl, sizeof ( float ), 1, fp); fwrite (&noise_stat, sizeof ( int ), 1, fp); fwrite (&fe_hi_cutoff, sizeof ( double ), 1, fp); fwrite (&fe_lo_cutoff, sizeof ( double ), 1, fp); fwrite (&fe_low_pass, sizeof ( double ), 1, fp); fwrite (&fe_high_pass, sizeof ( double ), 1, fp); fwrite (&fe_differ, sizeof ( double ), 1, fp); for ( int i=0; i<=EQ_BANDS; i++) { fwrite (&QL[i], sizeof ( int ), 1, fp); fwrite (&QR[i], sizeof ( int ), 1, fp); } fclose (fp); } |
654 655 656 657 658 659 660 661 662 663 664 665 666 667 | char getFilterLogid( char mode) { char logid; switch (mode) { case MODE_LOWPASS: logid = LOGID_MODELPF; break ; case MODE_HIGHPASS: logid = LOGID_MODEHPF; break ; case MODE_BANDPASS: logid = LOGID_MODEBPF; break ; case MODE_THROUGH: logid = LOGID_MODENON; break ; case MODE_EQUALIZER: logid = LOGID_EQUALIZER; break ; default : logid = '?' ; break ; } return logid; } |
674 675 676 677 678 679 680 681 682 683 684 685 686 | char * getFilterName( char mode) { static char filter_name[10]; switch (mode) { case MODE_LOWPASS: strcpy (filter_name, "LPF" ); break ; case MODE_HIGHPASS: strcpy (filter_name, "HPF" ); break ; case MODE_BANDPASS: strcpy (filter_name, "BPF" ); break ; case MODE_THROUGH: strcpy (filter_name, "NON" ); break ; case MODE_EQUALIZER: strcpy (filter_name, "EQU" ); break ; default : strcpy (filter_name, "" ); break ; } return filter_name; } |
708 709 710 711 712 713 714 715 716 717 718 719 720 721 | /* displayQuantity : 帯域別音量を表示する [引数] char ch : チャンネル('L'または'R') */ void displayQuantity( char ch) { printf ( " Lv(%c) " , ch); for ( int i=1; i<=EQ_BANDS; i++){ if (ch == 'L' ) printf ( " %d," ,( int )(QL[i])); else printf ( " %d," ,( int )(QR[i])); } printf ( " \r" ); } |
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 | int writeLog(LOGCTRL* ctrl, char type) { char kind[21] = "" ; struct sysinfo info; ulong recpos; LOGREC rec; // ログ出力が指定されていなければ即復帰する if (logging == 0) return 0; // レコード数が最大なら出力位置をファイル先頭にセットする if (ctrl->rec_cnt >= ctrl->max_rec) { ctrl->rec_cnt = 0; fseek (ctrl->log_ptr, 0L, SEEK_SET); } // ファイルのカレント位置から1レコード分読み込んで if ( fread (&rec, sizeof (LOGREC), 1, ctrl->log_ptr) < 1) return -1; // 書き出し位置を調整する fseek (ctrl->log_ptr, - sizeof (LOGREC), SEEK_CUR); memset (&rec, 0x00, sizeof (LOGREC)); // メモリー使用状態を取得する sysinfo(&info); unsigned int allmem = info.totalram/1024; unsigned int freemem = info.freeram/1024; // メッセージ種別からメッセージを編集する switch (type) { case LOGID_ERROVF: strcpy (kind, "/Input Overflow " ); break ; case LOGID_ERRUDF: strcpy (kind, "/Output Underflow " ); break ; case LOGID_ABEND: strcpy (kind, "/Abend PORTAUDIO " ); break ; case LOGID_BEGIN: strcpy (kind, "==== Begin ====== " ); break ; case LOGID_STRAT: strcpy (kind, "==== Start ====== " ); break ; case LOGID_END: strcpy (kind, "==== End ====== " ); break ; case LOGID_FINAL: strcpy (kind, "==== Final ====== " ); break ; case LOGID_CLEAR: strcpy (kind, "* Clear CutoffFe " ); break ; case LOGID_READ: strcpy (kind, "* Read temp.ctrl " ); break ; case LOGID_WRITE: strcpy (kind, "* Write temp.ctrl " ); break ; case LOGID_MODENON: strcpy (kind, ">> Applied : NON " ); break ; case LOGID_MODELPF: strcpy (kind, ">> Applied : LPF " ); break ; case LOGID_MODEHPF: strcpy (kind, ">> Applied : HPF " ); break ; case LOGID_MODEBPF: strcpy (kind, ">> Applied : BPF " ); break ; case LOGID_EQUALIZER: strcpy (kind, ">> Applied : EQU " ); break ; default : strcpy (kind, "????????????????? " ); break ; } // メッセージと日時・メモリー状態をカレント・ログファイルへ書き込んで time_t timer = time (NULL); struct tm *date = localtime (&timer); sprintf (rec.daytime, "%04d/%02d/%02d %02d:%02d:%02d" , date->tm_year+1900, date->tm_mon+1, date->tm_mday, date->tm_hour, date->tm_min, date->tm_sec); memcpy (rec.message, kind, sizeof (rec.message)); sprintf (rec.note, "%d/%d KB" , freemem, allmem); rec.note[39] = 0x0d; fwrite (&rec, sizeof (LOGREC), 1, ctrl->log_ptr); // ログのレコードカウンターをインクリメントする ctrl->rec_cnt = ctrl->rec_cnt + 1; return 0; } |
4.HAT21のチューニング
これでソフトウェアも準備が整い、コンパイルして実行させるばかりの状態になりました。イコライザーを有効にすると周波数帯域別の感度を細かく調整できるので、とても聴きやすくなると思います。
<注意!>
チューニングでは最初にヘッドフォンアンプの音量を絞って、耳を傷めないよう注意してください!
①コンパイルと実行
出来上がった聴覚補助ツールHAT21(Hearing Assist Tool 2021)Version 0.20は、次のファイルで構成されています。
・hat.h(聴覚補助ツールHAT21用ヘッダーファイル)
・hat21b.c(聴覚補助ツールHAT21 Version 0.20ソースコード)
・filter2.o(フィルター係数計算・音声信号処理オブジェクトモジュール)
ダウンロード解凍後のフォルダーには、実行形式のプログラムを含むすべてのファイルが入っているのですぐに実行できますが、あらためてコンパイルする場合は次のようにします。
$ gcc hat21b.c -o hat21 filter2.o -lportaudio -lm |
このプログラムは次のようにして実行します。
$ ./hat21 |
・filter2.h(フィルター係数計算・音声信号処理モジュール用ヘッダーファイル)
・filter2.c(フィルター係数計算・音声信号処理モジュール ソースコード)
$ gcc -c filter2.c |
②基本的なチューニング
HAT21には次のような音量の可変要素があります。
・マイクアンプの左右各2段の半固定抵抗器
・ADコンバーターの左右の半固定抵抗器
・ヘッドフォンアンプの左右の半固定抵抗器
・ソフトウェアボリューム
・HAT21の音量調整機能
・HAT21のイコライザーのバンド別感度調整機能
これらを適当に調整すると混乱してしまいます。そこで、一例として以下のような進め方を提案します。なお、ここではマイクアンプにコンデンサーマイクを接続した場合を述べていますが、従前の高感度アンプ付きコンデンサーマイクをADコンバーターに接続する場合は、適宜手順を省略してください。
Step-1: システムの既定値を設定する
・HAT21の音量調整機能は0.0から0.1刻みで1.0まで調整でき、既定値は0.5です。
これは当面は変更しないことにします。
・次にソフトウェアボリュームの再生レベルを85、録音レベルを80に設定します。
第16章『ADCで試すアナログマイク①』の「5.ソフトウェアボリュームのテスト」を参考にしてください。
Step-2: マイクアンプのチューニング
・マイクアンプにマイクを繋ぎ、出力側をヘッドフォンアンプに直結します。
・アナログマイク側の半固定抵抗器は4つとも右いっぱいまで回します。
・ヘッドフォンアンプの半固定抵抗器は2つとも左いっぱいに回して音量を完全に絞ります。
・ヘッドフォンアンプにイヤフォンを繋いで、両アンプの電源を入れます。
・イヤフォンから十分な音量が再生できるまで、ヘッドフォンアンプの左右の半固定抵抗器を右に回します。
・この状態でマイクアンプの出力側の半固定抵抗器を少し左に回転させながら、音質の良好な位置で止めます。
Step-3: システム全体のバランス調整
・すべての電源をOFFにします。
・マイクアンプの出力を、Raspberry Piに接続されたADコンバーターの入力端子に繋ぎます。
・ヘッドフォンアンプを以前の方法でRaspberry Piに接続します。
第16章『ADCで試すアナログマイク①』の「2.テスト環境の配線」に従ってください。
なお、ADコンバーターの半固定抵抗器は以前のままとします。
・HAT21を起動して「フィルターなしモード」(コンソールにApplied : [NON]と表示)にします。
つまり、フィルターを素通しの状態にします。
・ヘッドフォンアンプの半固定抵抗器は2つとも左いっぱいに回して音量を完全に絞ります。
・すべての電源を入れます。
・ヘッドフォンアンプの半固定抵抗器を少しずつ回して、高音質で再生されることを確認します。
もし音質に問題があるようなら、ADコンバーターの半固定抵抗器も調整してみましょう。
以上で調整した音量と音質がHAT21のベースになります。
③イコライザーの調整
小文字のeキーを押すと、イコライザーモードに切り替わって次の内容が表示されます。左右のバンド別感度レベルLv(L)とLv(R)はすべてゼロであることがわかります。イヤフォンを耳につけると何も聞こえません。
この状態で1~9までの数値キーを押すと、そのキーに対応したバンドの感度の値が1ずつ増加して、左イヤフォンから音声が再生されてきます。大文字キーRを押すと右チャンネルが選択されて、同様にバンド別感度を設定することができます。
このように9つの周波数帯域別感度を調整して、利用者がもっとも聴き取りやすい状態を探るのがイコライザーの調整です。感度が強すぎる場合は、[Shift]キーを押さえたままで該当する帯域の数字キーを押すと低減できます。
調整が終われば大文字キーQを押して、不用意な数値キー操作で設定値が変わらないようにします。
実際に調整をやってみると、なかなか微妙であることが実感できます。イコライザーの設定状態は、小文字キーdを押せば、他のフィルター係数の状態と合わせていつでも確認することができます。またその状態は[w]や[r]キーでファイル'temp.ctrl'に書いたり読み込んだりすることもできます。また、HAT21を終了するとその時点の設定情報がファイルに退避されます。



④制御キーと機能
使用できるキーの種類が多くなったので、あらためてキーと機能の対応をまとめておきます。
キー | 機 能 |
l | ローパスフィルター(LPF)を選ぶ |
h | ハイパスフィルター(HPF)を選ぶ |
b | バンドパスフィルター(BPF)を選ぶ |
x | フィルターなしを選ぶ |
+ | 使用中のフィルター(LPF/HPF/BPFの上限)のカットオフ周波数を上げる |
- | 使用中のフィルター(LPF/HPF/BPFの上限)のカットオフ周波数を下げる |
> | BPFの下限カットオフ周波数を上げる |
< | BPFの下限カットオフ周波数を下げる |
d | 現在のフィルター種別とカットオフ周波数等を表示する |
c | 現在のカットオフ周波数を初期値に戻す |
r | 現在のフィルター種別とカットオフ周波数等を退避する |
w | 現在のフィルター種別とカットオフ周波数等を復元する |
e | イコライザーモードを選ぶ |
M | ミュートをON/OFFする |
N | ノイズ抑制をON/OFFする |
U | 音量をアップする |
D | 音量をダウンする |
L | イコライザーの左チャンネルを選択する |
R | イコライザーの右チャンネルを選択する |
Q | イコライザーのチャンネル選択を無効にする |
1~9 | 対応するバンドの感度を1レベルアップする |
[Shift] + 1~9 | 対応するバンドの感度を1レベルダウンする |
[space] | 処理終了 |
5.いくつかの課題と今後の対応
ここまで聴覚補助ツールに必要なハードを検討・製作し、デジタルフィルターの作成やテストを行いながら、ついにイコライザーも組み込むことができました。これで聴覚補助ツールとしての道具立ては揃ったわけですが、これを完成させるためにはなお大きな課題があります。プロジェクトも終盤に差しかかったので、以降に備えて残す課題を整理しておきす。
①リモートアプリケーションへの移行
現在のHAT21はパソコンのSSHコンソールに繋がれた状態で動作しています。開発途中のテストやチューニングにはきわめて好都合なのですが、完成した聴覚補助ツールは単独で動かす必要があります。現時点でもっとも簡単な方法は、Raspberry Pi起動時に自動起動させる方法でしょう。
まず/usr/local/bin/にシェルスクリプトファイルautostart.shを作成して、次の内容を書き込みます。
$ sudo nano /usr/local/bin/autostart.sh |
手元の環境では、実行ファイルhat21が/home/share/testAudio/hatにあるので。まずカレントディレクトリーをその位置に変更して(これをしないとHAT21内での制御情報等のファイル操作が失敗します)、少しだけ待ってHAT21を起動しています。
#!/bin/sh cd /home/share/testAudio/hat wait /home/share/testAudio/hat/hat21 |
次に、スクリプトファイルに実行権を与えます。
$ sudo chmod +x autostart.sh |
そして、例えばcrontabを使って自動起動を指示します。
$ crontab -e |
下のような画面が開くので(カラー表示が見にくいので背景色と文字色を変更しています)、最終行に、先に作成したシェルスクリプトをフルパスで指定します。

$ sudo reboot |
しかし、これは糸が切れた凧状態で音量調整もチューニングも何もできません。再チューニングをするには、以下のように実行中のhat21プロセスを検索して、当該プロセスを強制終了させなければなりません。停止後に、コンソールから手動でhat21を起動してチューニングを行い再起動することになります。

これではとても簡単に使用することはできません。いちど設定すればほとんど変更がないローパスフィルターやバンドパスフィルターなどは良しとしても、中核になるはずのイコライザーの設定や音量調整などはもっと簡単にできなければなりません。
これを可能にするには、スマートフォンなどからHAT21に接続して調整できるような仕組みを考える必要があります。たとえば、HAT21に外部通信機能を追加してWebアプリケーションと連係動作させるのがいいかも知れません。いずれにしても、なお新たな仕組みの追加や開発が必要になりそうです。
②適切な電源の検討
これは当初から想定されているテーマですが、ノイズが少なくて安定した電圧が確保できる電源をどうするかが大きな課題です。現時点では、Raspberry Piにはモバイルバッテリーの利用を考えています。アンプ類など他のユニットには同じバッテリーからDC-DCコンバーターを経由して給電する予定ですが、ノイズの問題が気がかりです。
③小型化とマイクの再検討
現在の自作マイクアンプとエレクトレットコンデンサーマイクの組み合わせは、ノイズがほとんど無く素直な音質なので満足していますが、ボードの枚数が増えて大きくなってしまいました。もっと小型化とシンプル化をはかるために、MEMSマイクロフォンを再検討したいと考えています。十分なゲインが得られるようであれば、マイクアンプもADコンバーターも不要になるので、大幅に小型化・シンプル化することができます。また、ヘッドフォンアンプも低電圧動作の小型なものがほしいところです。
④その他
PortAudioのブロックI/O方式を使うことに本質的な問題が無いかどうか、もうしばらく観察しなければなりません。音声データのストリーム出力関数Pa_WriteStream()実行時に、不規則に発生するアンダーフローを完全に防ぐことができません。また、出力ストリームのレイテンシーoutputParameters.suggestedLatencyの設定も、推奨されているdefaultLowOutputLatencyを指定すると、アンダーフローが多発して使いものになりません。現時点では、さまざまな値を試してその結果0.04という値を設定しています。リアルタイムサウンド処理の適用事例が見当たらず、この関係の情報が少ないのは困りものです。
ただ一方で、Raspberry Pi Zeroがヘビーな音声信号処理にどこまで対応できるかを心配していたのですが、この点はまったく問題ないようです。
これで実験機はほぼ完成といったところですが、実用化までにはまだ課題が多いことを再認識している次第です。これによって後続のテーマに追加・変更が発生します。さて次回はどうしようかな。Web連携制御には少し予備的な実験が必要になりそうだし、先に電源の検討に着手しようかな・・・・。