( 変更履歴:
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
実際の信号から倍音列を検出するには その他にも計算が必要です。
参考文献: