( 変更履歴:
v0.2 [11/10/30] f2cの計算式を変更しました。)
Wave信号を合成してみます。
function makeWave(key, multi) sf = 44100; # サンプリング周波数 time = 2.0; # 時間・秒 n = fix(time*sf); t = linspace(0, time, n); omega = 2*pi*t; mul = 1:multi; # 倍音数 f0 = getFrequ(key)*mul; f = zeros(1,n); for m = mul f += sin(omega*f0(m))./multi; end wavwrite_f(f, sf, 16, 'test.wav'); end
function wavwrite_f(s, fs, bits, wavefile) fid = fopen(wavefile, 'wb', 'ieee-le'); length_of_s = length(s); if bits == 8 dataChunkSize=length_of_s; elseif bits == 16 dataChunkSize=length_of_s*2; end RiffChunkID = 'RIFF'; RiffChunkSize = dataChunkSize+36 # RiffFormType = 'WAVE'; fmtChunkID = 'fmt '; fmtChunkSize = 16; fmtWaveFormatType = 1; fmtChannel = 1; fmtSamplesPerSec = fs; fmtBytesPerSec = fs*bits/8; fmtBlockSize = bits/8; fmtBitsPerSample = bits; dataChunkID = 'data'; % 'RIFF' chunk fwrite(fid,RiffChunkID,'uchar'); fwrite(fid,RiffChunkSize,'uint32'); fwrite(fid,RiffFormType,'uchar'); % 'fmt ' chunk fwrite(fid,fmtChunkID,'uchar'); fwrite(fid,fmtChunkSize,'uint32'); fwrite(fid,fmtWaveFormatType,'uint16'); fwrite(fid,fmtChannel,'uint16'); fwrite(fid,fmtSamplesPerSec,'uint32'); fwrite(fid,fmtBytesPerSec,'uint32'); fwrite(fid,fmtBlockSize,'uint16'); fwrite(fid,fmtBitsPerSample,'uint16'); % 'data' chunck fwrite(fid,dataChunkID,'uchar'); fwrite(fid,dataChunkSize,'uint32'); if bits==8 s = round((s+1)*255/2); fwrite(fid,s,'uchar'); elseif bits==16 s = round((s+1)*65535/2)-32768; fwrite(fid,s,'int16'); end fclose(fid); end
wavwrite_f.mは Octaveにある wavwrite.mを改造したものです。
(参考文献:「ディジタル・サウンド処理入門」
青木 直史:著 CQ出版 2006年)
> makeWave(37, 5)
始めは 平均律です。
キー番号 37A・5倍音までの test.wavを作成します。
減衰しない 2秒間の Wave信号です。
その FFTスペクトルです。
ではまず 打弦点の特性を作ります。
function val = stPoint(multi, sp) mp = multi.*pi; val = abs(sin(mp.*sp)./mp); end
> sp = stPoint(1:25, 1/8) > stem(sp, '-@') > grid on
打弦点 1/8で 25倍音までです。
響板の振動特性を作ります。
実際は 凸凹のある特性ですが
ここではシミュレーションと云う事で フラットとしています。
function gain = kyoban(freq) cutoff = 20; # 最下限周波数 band = 170; # 減衰幅 freq -= cutoff; gain = freq/band; if (gain < 0) gain = 0; elseif (gain > 1.0) gain = 1; end end
190[Hz]以下で減衰して 20[Hz]で 0になります。
> kyoban(200) ans = 1 > kyoban(150) ans = 0.76471 > kyoban(10) ans = 0
0から1000[Hz]までを見てみます。
キー毎の打弦位置を設定します。
function sp = strikP(key) a49 = 8; c88 = 22; if (key > 49) sa = 88-49+1; points = logspace(log10(a49), log10(c88), sa); sp = 1/points(key-48); else sp = 1/a49; end end
49Aまでは 1/8, 49Aから88Cまでは 1/8から 1/22へと変化させています。
> strikP(49) ans = 0.12500
以上に Tuningのシミュレーションを加えて
インハーモニシティのある Wave信号を作ります。
実際にピアノの音をシミュレーションするには
ハンマーの特性なども係わってきますが
取り合えず必要な倍音成分のみとしています。
function makeWave1(key, multi) global Inha Cent KB; KB = 88; grade = 0.087; # 傾き Inha = makeCurve(28, 0.55, grade); Cent = tuning_c(Inha, 1.0, grade); sf = 44100; # サンプリング周波数 time = 2.0; # 時間・秒 n = fix(time*sf); t = linspace(0, time, n); omega = 2*pi*t; mul = 1:multi; # 倍音数 f0 = getIFrequ(key, mul); sp = strikP(key); f = zeros(1, n); for m = mul f += sin(omega*f0(m))*stPoint(m, sp)*kyoban(f0(m)); end wavwrite_f(f,sf,16,'test.wav'); end
1Aキーで 10倍音までの Wave信号を作成します。
> makeWave1(1, 10)
その Wave信号です。
その FFTスペクトルです。
その Wave信号を検証してみます。
peaks.mは FFTと「補間公式」を使ってピークを検出します。
function ps = peaks(wave_file) n = pow2(13); # サンプル数 n2 = n/2; [s,fs] = wavread(wave_file); delf = fs/n; hn = hanning(n); # 窓関数 sx = s(1:n).*hn; pks = abs(fft(sx,n)/n2); # FFT maxkey = 88+12*3; peak = zeros(maxkey,1); for x = 2:n2-1 if (pks(x) > 0.0005) # クリッピング if ((abs(pks(x)) >= abs(pks(x-1))) &\ (abs(pks(x)) >= abs(pks(x+1)))) bet = 3/(1+pks(x)/pks(x+1))-1; frq = (x+bet)*delf-delf; key = f2k(frq); peak(key) = frq; x += 2; end end end ps = peak(peak > 0); end
function key = f2k(freq) key = round(12*log(freq/27.5)/log(2))+1; end
f2k.mは 周波数からキー番号を得ます。
> f2k([110.3 220.5 440.7]) ans = 25 37 49
makeWave1 で作った Wave信号からピークの周波数を取り出します。
> peaks('test.wav') ans = 27.026 54.084 81.329 108.823 136.649 164.947 193.753 253.314 284.310
その周波数をセント値に変換して 補正値を加えて見てみます。
function f2pc(freq, malti) Ladder = [0.0 0.0 -1.955 0.0 13.6863 -1.955 31.1741 0.0 -3.91 13.6863\ 48.6821 -1.955 -40.5277 31.1741 11.7313 0.0 -4.9554 -3.91\ 2.487 13.6863 -29.2191 -48.6821 28.2743 1.9550 -27.3726]; k = f2k(freq); f = f2c(k, freq); pcent = f'+Ladder(malti); plot(malti, pcent, '-@;;') grid on end
function cent = f2c(key, freq) cent = 1200*log2(freq./getFrequ(key)); end # キー番号と周波数から cent値に変更します
> pk = peaks('test.wav') > f2pc(pk, [1:7 9 10])
倍音が少しずつずれているのが分かると思います。
試しに peaks.mの以下の2行を
bet = 3/(1+pks(x)/pks(x+1))-1; frq = (x+bet)*delf-delf;
以下の1行のみに変更すると
frq = x*delf-delf;
「補間公式」を使わない FFTのみでの結果となります。
> peaks('test.wav') ans = 26.917 53.833 80.750 107.666 134.583 166.882 193.799 253.015 285.315
実際の信号から倍音列を検出するには その他にも計算が必要です。
参考文献: