more sox

人口知能の本を読んだ。と言ってもPythonあたりの技術書ではない。 幸田真音さんが書かれた、人工知能という小説。

自動運転技術が搭載された試験中の車が、人を轢いたというものだった……。

もし僕が犯人なら、人工知能を洗脳するな。

ね、面白そうでしょ。昔ちょっと人工知能をかじった者としては、まあ、恐いけどありえない事ではないねと納得しちゃう。一度読んでみるとよい。

wave to ascii

前回使ったsoxさん、普通は、 SoXチートシート - コマンドラインで音声編集なんてのに利用するのね。 トリムなんて機能を使って、イントロ当てクイズの音源を作ったりしてるのを見つけた。音のアーミーナイフですからね。

そんな中で、 gnuplotで音声データをプロットするなんてのに出会った。バイナリーデータになってる音データを人間可読にしましょってこと。

前回作った10秒間の掃引音源を変換してみた。soxにとっては簡単なお仕事。

ob$ ls -l sweep.wav z.dat
-rw-r--r--  1 sakae  wheel   200044 May 21 14:08 sweep.wav
-rw-r--r--  1 sakae  wheel  3601073 May 21 14:42 z.dat

めっぽう、ファイルサイズが大きくなった。

ob$ head z.dat
; Sample Rate 10000
; Channels 1
               0                0
          0.0001   0.019866943359
          0.0002   0.039672851562
          0.0003   0.059265136719
          0.0004   0.078643798828
          0.0005   0.097747802734
          0.0006    0.11645507812
          0.0007    0.13464355469
ob$ tail z.dat
           9.999    0.31604003906
          9.9991   -0.10705566406
          9.9992   -0.24996948242
          9.9993    0.26141357422
          9.9994   0.088592529297
          9.9995   -0.31610107422
          9.9996    0.10668945312
          9.9997    0.25021362305
          9.9998   -0.26126098633
          9.9999  -0.088714599609

経過時間とデータの組に変換されるのね。これ、このままgnuplotのデータになるな。 但し、冒頭のコメントは無視するように、gnuplotに教えてあげるのが吉。

set datafile commentschars ";"
plot "test.dat" every 100 with lines

大きなデータに供えて、間引きする事の大事だ。

sox for gnuplot (or octave)

soxのマニュアルを眺めていると、気になるオプション --plotが紹介されてた。 例が出てたので実行してみた。

ob$ sox --plot gnuplot sweep.wav -n lowpass -1 500 >aaa.plot
ob$ cat aaa.plot
# gnuplot file
set title 'SoX effect: lowpass gain=0 frequency=500 band-width(Hz)=0 (rate=10000)'
set xlabel 'Frequency (Hz)'
set ylabel 'Amplitude Response (dB)'
Fs=10000
b0=2.695973089513544e-01; b1=0.000000000000000e+00; b2=0.000000000000000e+00; a1=-7.304026910486456e-01; a2=0.000000000000000e+00
o=2*pi/Fs
H(f)=sqrt((b0*b0+b1*b1+b2*b2+2.*(b0*b1+b1*b2)*cos(f*o)+2.*(b0*b2)*cos(2.*f*o))/(1.+a1*a1+a2*a2+2.*(a1+a1*a2)*cos(f*o)+2.*a2*cos(2.*f*o)))
set logscale x
set samples 250
set grid xtics ytics
set key off
plot [f=10:Fs/2] [-35:25] 20*log10(H(f))
pause -1 'Hit return to continue'

こんなスクリプトが出力されたので、実行してみた。伝達関数のグラフが表示されたよ。上のsoxコマンドの意味は、カットオフ周波数500Hzのワンポールの伝達関数をグラフ化してねって意味だったのね。下記の詳細を見て、知った。

       highpass|lowpass [-1|-2] frequency[k] [width[q|o|h|k]]
              Apply a high-pass or low-pass filter with 3dB point frequency.
              The filter can be either single-pole (with -1), or double-pole
              (the default, or with -2).  width applies only to double-pole
              filters; the default is Q = 0.707 and gives a Butterworth
              response.  The filters roll off at 6dB per pole per octave (20dB
              per pole per decade).  The double-pole filters are described in
              detail in [1].

              These effects support the --plot global option.
              See also sinc for filters with a steeper roll-off.

ローパス/ハイパスが有るなら、きっと他のタイプも有るだろう。

bandpass|bandreject [-c] frequency[k] width[h|k|o|q]
              Apply a two-pole Butterworth band-pass or band-reject filter
              with central frequency frequency, and (3dB-point) band-width
              width.  The -c option applies only to bandpass and selects a
              constant skirt gain (peak gain = Q) instead of the default:
              constant 0dB peak gain.  The filters roll off at 6dB per octave
              (20dB per decade) and are described in detail in [1].

              These effects support the --plot global option.

やっぱり有った。AF帯もRF帯も一緒だな。

% GNU Octave file (may also work with MATLAB(R) )
Fs=10000;minF=10;maxF=Fs/2;
sweepF=logspace(log10(minF),log10(maxF),200);
[h,w]=freqz([2.008336556421124e-02 4.016673112842248e-02 2.008336556421124e-02],[1 -1.561018075800718e+00 6.413515380575631e-01],sweepF,Fs);
semilogx(w,20*log10(h))
title('SoX effect: lowpass gain=0 frequency=500 Q=0.707107 (rate=10000)')
xlabel('Frequency (Hz)')
ylabel('Amplitude Response (dB)')
axis([minF maxF -35 25])
grid on
disp('Hit return to continue')
pause
ob$ octave-cli aaa.plot
Hit return to continue

gnuplotに変えてoctave用のやつも作ってみた。こちらの設定は2ポール用。フィルターの2段重ねで、切れがよくなっている。

他にplotで伝達関数を出せるようなものがないかと探したら、 音楽ファイル編集コマンド sox の compand オプションを調べてみた素晴らしい研究に出会ったよ。

spectrogram

そして、面白いものを発見。振り込み詐欺の電話がかかって来た時、犯人に向かって、この電話は録音されます。そして、声紋分析の結果を添えて警察に提出しますって言おうと思ってた。 やっと、声紋分析が手軽に出来るね。

これ、基本的な使い方。

ob$ sox sweep.wav -n spectrogram
ob$ gpicview spectrogram.png

こちらは、3種の波形を作成。そしてそれを1本に合成。それのスペクトログラムを取ってる。 カラー表示がいやなら、-m でモノクロ画も作成出来るぞ。

ob$ sox -n s6a.wav synth 3 sine 660-2640
ob$ sox -n s6b.wav synth 3 sine 1320-5280
ob$ sox -n s6c.wav synth 3 sine 1980-7920
ob$ sox -m s6a.wav s6b.wav s6c.wav s6.wav
ob$ sox s6.wav -n spectrogram -o s6_sp.png
ob$ sox s6.wav -n spectrogram -m -o s6_sp2.png

こちらは、ノコギリ波です。

ob$ sox -n hoge.wav synth 3 saw 100-800
ob$ sox hoge.wav -n spectrogram -o hoge.png

さすがノコギリ、画面が血塗られて真っ赤になりました。享禄なジェイソンがチェーンソーを 振り回していたんでしょうか?

声紋

上でやったのは、機械が勝手に作った味気ない音。やっぱり生声を見たい。 ってんで、パソコン内を漁ったら、ほとんどがwma形式のファイルで、soxでは扱えない。 (作者さんは、M$からいちゃもんをつけられるのを嫌って、わざとサポートしなかったのでしょう。

そんな訳で、僅かに入っていた、mp3の声紋を取ってみた。いえ、取る前にplayコマンドで演奏させてみた。演奏につれてインジケーターがピコピコ動くんだけど、どんな意味あるの?

       -S, --show-progress
              Display input file format/header information, and processing
              progress as input file(s) percentage complete, elapsed time, and
              remaining time (if known; shown in brackets), and the number of
              samples written to the output file.  Also shown is a peak-level
              meter, and an indication if clipping has occurred.  The peak-
              level meter shows up to two channels and is calibrated for
              digital audio as follows (right channel shown):

                            dB FSD   Display   dB FSD   Display
                             -25     -          -11     ====
                             -23     =           -9     ====-
                             -21     =-          -7     =====
                             -19     ==          -5     =====-
                             -17     ==-         -3     ======
                             -15     ===         -1     =====!
                             -13     ===-

              A three-second peak-held value of headroom in dBs will be shown
              to the right of the meter if this is below 6dB.

ふーん。headroomって、歪む限界って意味なのね。と、オーディオの知識が一つ増えました。 それはいいんだけど、mp3のファイルって、帯域制限が16kHzになってるのね。これも知らなかったぞ。

で、楽器の音が五月蝿くて、音声がマスキングぎみ。はて、どうしてくれよう。ってんで、CDライブラリィーを眺めてみましたよ。そして、いいものを発見。

女房が昔勤めていた時、忘年会だかでカラオケに行く事になってて、音痴と悩んで、からおけCDで練習してたのを発見。早速それをリッピング。答えは、wmaファイルで得られました。いまいましいな。

WMAtoWAV

ここのWeb画面にファイルをドロップして、変換結果をネットからダウンロードした。サイズが10倍ぐらいに膨らんでいたぞ。

早速聞いてみると、左チャンネルに楽器が居て、右チャンネルが模範音声になってた。右だけ聞くと、アカペラって事でしょうか。

マニュアルに載ってた例。右チャンネルの30秒後から20秒だけ声紋を作ってってやつ。

ob6$ sox 16.wav −n remix 2 trim 30 20 spectrogram -o right.png
sox FAIL formats: can't determine type of `−n'

見事にエラーになったんで、下記のようにゆるやかに指定した。

ob6$ sox 16.wav RR.wav remix 2
ob6$ sox RR.wav -n spectrogram -o RR.png

上のは全体像なんで詳細は分からない。そこで、下記のようにして、X軸、Y軸を絞ってみた。

ob$ sox RR.wav NA.wav trim 30 10
ob$ sox NA.wav -n rate 6k spectrogram -o NA.png

X軸は経過時間、Y軸は周波数、Z軸は色表現による信号の強度になる。 全体像の声紋と 拡大表示の声紋をあげておく。拡大表示の3秒前後にみられる、第一フォルマント及び第二、第三が綺麗な正弦波になっている。こういうのがプロの技なんでしょうね。

なんと言う唱法かな。時間に経過と共に、周波数の強度が変化してる。ビブラート唱法? 周波数変調の図と捉えるのが正解? 無線の教科書にはFM波の例として、波形の粗密が載っているけど、それを周波数領域でみれば、今回の例のようになるはず。

余談だけど、トレモロってのもあるな。振幅変調になるのかな。搬送波の包絡線を辿ると、非変調波が見えるって教科書に出てる。AM波をスペクトログラムで見れば、周波数一定で、Z軸の色が変化するって事だろうね。

Karaoke

日経パソコンなんて言う雑誌をまとめて10冊近く借りてきた。図書館の新刊コーナーに冒頭であげた人工知能本しか無かったから。で、やけになって普段は読まないWindowsとスマホの雑誌を借りてみたしだい。

フリーソフト大集合って特集の所に、楽器のボーカル部分を削除してカラオケ音源を自作出来ると銘うった、ボーカルリデューサー:ねおんを紹介してたぞ。

どんな原理? 音のプログラミングって本に理屈が紹介されてた。ボーカルは普通中央から聞こえるように配置する。ドラムは右側だったり、ピアノは左に配置したりするのが普通。 (若いロッカーとかのライブだと、ステージ一杯に動き回ったりするんで、こういう仮定は成り立たない)

平たく言えば、ボーカルを雑音とみなして、キャンセリングするんだな。ノイズキャンセリング機能付きのヘッドフォンみたいなものだな。

そうすると、ボーカルの音量はステレオの左チャンネルも右チャンネルも、ほぼ同じになるであろう。だったら、左ちゃんねるから右チャンネルを引き算すれば、ボーカル音は消えるはず。 (ボーカリストの真後ろで演奏されてる楽器音も消えてしまうのはご容赦。機械は、音声も楽器音も区別出来ないですから。まて、そこで人工知能なんて言い出すなよ。話がややこしくなるから)

丁度soxしてるんで、ちょこっとでっち上げられないかな?

考えるに、引き算なんて無いよな。でも、引き算って、 L + (-R) だよな。

(2つの)足し算は、

sox -m L.wav R.wav out.wav

で、良いはず。

そうすると、次は波形の反転だな。と言う事は、位相を反転だな。チートシートによれば、

sox -v -1 input.wav output.wav

膨大なマニュアルの中から、よくぞ見つけるな。

       -v, --volume FACTOR
               :
              volume and a number greater than 1 increases it.  If a negative
              number is given then in addition to the volume adjustment, the
              audio signal will be inverted.

負数を与えると信号が反転するって、最初、反転する事に意義を見出して、別のオプションに しておいた方が良いんじゃと考えていた。でも、散歩中にふと思い付いたんだ。世の中には、反転増幅器があるよ、とね。してそのゲインGは、極性が反転する事に意義を込めて、頭にマイナス記号をつける約束とな。思い出してよかったわい。

そんじゃ、今考えた事が正しいか実証実験してみるか。実験計画を立案する。

ステレオの左チャンネルに見立てたファイルに500Hzと1000Hzのサイン波を割り当てる。同様に右チャンネルに1000Hzと1500Hzを割り当てよう。両チャンネルに含まれる1000Hzは、ボーカル代表と思ってくれ。それ以外の周波数は、ドラムなりピアノを代表してるとしよう。

そんな設定で、差分を取って、ボーカルの1000Hzが消えるか確かめたい。周波数成分の確認を楽にやるには、上で出てきたスペクトラグラムを使うのが簡単だけど別な方法を試す。

別な方法ってのは、前回やった、急峻なフィルターだ。どう転んでもsoxでは不可能だろう。まてよ、soxには

       fir [coefs-file|coefs]
              Use SoX's FFT convolution engine with given FIR filter
              coefficients.  If a single argument is given then this is
              treated as the name of a file containing the filter coefficients
                :

こんな機能があるんで、ひょっとして、、、、

色々御託を並べていないで、いざ実験。

ob$ sox -r 10000 -b 16 -n  500.wav synth 0.1 sine  500 gain -10
ob$ sox -r 10000 -b 16 -n 1000.wav synth 0.1 sine 1000 gain -10
ob$ sox -r 10000 -b 16 -n 1500.wav synth 0.1 sine 1500 gain -10

音のファイルを3種作成しました。

ob$ sox -m  500.wav 1000.wav L.wav
ob$ sox -m 1000.wav 1500.wav R.wav
ob$ sox -v -1 R.wav mR.wav
ob$ sox -m L.wav R.wav all.wav
ob$ sox -m L.wav mR.wav kara.wav

その波形を組み合わせて、LとRのチャンネルファイルを作成。続いて、Rチャンネルを反転したファイルmR.wavを作成。左右チャンネルを合成したファイルを作成。このファイルが現実社会ではCD等から持ってきた元ファイルになります。 最後は、カラオケバージョンのファイルを作成します。

続いて、それらの検証です。

ob$ ./a.out < all.wav
    1295.3     2590.6     1295.3
ob$ ./a.out < kara.wav
    1295.3        0.0     1295.3

最初は元ファイル相当。ボーカル相当の振幅が楽器のそれの倍になってるのは、左右に含まれるのが足されているからです。 後の結果はボーカル相当の1000Hzをキャンセルしたカラオケバージョンです。

3つのデータが並んでますが、左側から、500Hz、1000Hz、1500Hzの振幅です。見事にボーカル周波数が消えていますね。実験大成功ですよ。

使った特性確認プログラムは、前回のものを改造しました。

#include <stdio.h>
#include <math.h>

float sampling_freq=10000.0;
#define N 200                 //  BW = sampling_freq / N
short int Data[N];
float omega,coeff,mag2;
float Q0,Q1,Q2,cosine;
int i,k;

float f(float target_freq){
  k = (int) (0.5 + ((N * target_freq) / sampling_freq));
  omega = (2.0 * M_PI * k) / N;
  cosine = cos(omega);
  coeff = 2.0 * cosine;

    Q1 = 0; Q2 = 0;
    for (i = 0; i < N; i++){
      Q0 = coeff * Q1 - Q2 + (float) Data[i];
      Q2 = Q1; Q1 = Q0;
    }
    mag2 = (Q1*Q1) + (Q2*Q2) - Q1*Q2*coeff;
    return (sqrt(mag2)/N);
}

int main(){
    fread(&Data, sizeof(short int), N, stdin);  // dummy read (skip header)
    fread(&Data, sizeof(short int), N, stdin);
    printf("%10.1f %10.1f %10.1f\n", f(500), f(1000), f(1500));
}

読み込んだ波形データに対して、3回その周波数が存在するか確認してるだけです。

尚、このプログラムに直接wavファイルを入力できる様に、空読みをいれてます。これって、健康診断で、検尿する時、最初に出る尿は雑菌が含まれているから、捨てて下さいと一緒の事だな。

無線の世界では、ウォーターフォールと呼ばれる、縦に流れてくる声紋表示が一般的になりました。そのデータから、複数の断続波(モールス信号ですな)を検出して、自動的に解読してくれるようになってます。

上記の特製プログラムは、その足がかりになるものです。なんたって急峻なフィルターがいとも簡単に実現できるのですから。

実験ばかりでは意味が無いので、実使用に耐えるかも知れない、スクリプトを書いてみた。 ステレオサウンドファイルを入力すると、(多分)ボーカルが消えたカラオケバージョンの ファイル、KARA.xxxが出来る。xxxは入力ファイルのサフィックスね。スクリプトの中でも このサフィックスを取り出して、大事に使ってる。

#!/bin/sh
# Usage: kara.sh  stereo-sound-file

sux=${1##*.}
## or sux=`echo $1 | sed 's/^.*\.\([^\.]*\)$/\1/'`

sox $1 LL.${sux} remix 1
sox $1 RR.${sux} remix 2
sox -v -1 RR.${sux} mRR.${sux}
sox -m LL.${sux} mRR.${sux} KARA.${sux}

気になる結果だが、楽曲によってまちまち。レーベルによる差の方が大きいかな。ボーカルが消えると言っても完全に消える訳ではなく、例えて言うと、ボーカリストが毛布にくるまって謳っているような感じになる。

色々試してみればいいんだろうけど、2.3Mなwmaファイルをwav形式にすると50Mぐらいのサイズになる事がザラで、余り気楽に変換するのも憚られる。

sox pipe

       sox [global-options] [format-options] infile1
            [[format-options] infile2] ... [format-options] outfile
            [effect [effect-options]] ...

soxのパラメータの並べ方は、上記のようになっている。n個の入力ファイルを受け取って、一つの出力ファイルに変換(effect)結果を吐き出すって形式。

じゃ、ファイルしか許さないかと言うと、ちゃんとパイプも用意されている。

sox in.wav -t wav - equalizer 100 1.0q 13 | sox -t wav - out.wav equalizer 380 1.0q -7

二種類のイコライザーを施したい時は、上記のようにする。左側soxはin.wavを入力ファイルとして、出力はstdoutに流れる。右側のsoxはstdinを入力として、出力はout.wavファイルになる。

パイプは生の音データしか流れないので、データ形式は、-t format-optionを使ってsoxに伝達される。

この方法って、ftpコマンドの、コントロールは21番ポートで、データは20番ポートでやり取りするって方式を周到しているな。なかなか佳き方法と思うぞ。 ファイル指定の特例として、-n ヌルファイルってのが有る。アーギュメントの並びとして、入出力ファイルは必須だけど、意味を成さない場合がある。(上で出て来た、スペクトログラムを作るような場合、出力ファイルは不要)そんな時に、ダミーファイルとして使う。