音とは空気の振動であり、
振動数に音の高低が依存する
ドの音を鳴らすには
261.6HzのSin波を出力すればよい
サンプリング周波数に基づいてデータを記録していく
*サンプリング周波数(大雑把)
アナログ信号をデジタルデータにするために
単位時間当たりに信号の値を記録する回数
(CDのサンプリング周波数は44.1kHzつまり
1sで44,100回アナログデータを採取している)
以降のプログラムではサンプリング周波数は44.1kHz
ドのsin波は261.6Hzであるから
1s間に261.6回振動している
以下は0s~0.1s間における波形
for文で繰り返しサンプリングを行う場合
1ループごとに1/サンプリング周波数s経過し、
サンプリングするデータが1sで261.6回振動 すなわち
1sで角度が2π×音階の周波数だけ増加する と考えると
1ループ(1/サンプリング周波数s)での角度の増加量は
(2π×音階の周波数) / サンプリング周波数
具体的に、ドの音を44.1kHzでサンプリングする場合は
各言語のsin関数に
((2 * 3.141592 * 261.6) / 44100) * count
を引数とした時の返り値を配列などで記録していく
*countは繰り返し回数
wavファイルに記述するビット数による
絶対値上限ギリギリよりは8,9割程度の振幅が良い
サンプリングしたデータを各言語の方法でoutputする
以下Javaプログラム
for(int i=0; i<sinData.length; i++){
//sinData[i]にサンプリングした値を保持(int型)
//16ビットなのでリトルエンディアンで書き込みを行うようにint型をbyte型に分割
data[2 * i] = (byte)(sinData[i] & 0x0000ffff);
data[2 * i + 1] = (byte)(sinData[i] / (16 * 16));
}
//以下wavファイルへの書き込み
AudioFormat format = new AudioFormat(SAMPLING, BIT, 1, true, false);
AudioInputStream ais = new AudioInputStream(
new ByteArrayInputStream(data), format, data.length/2);
try{
AudioSystem.write(ais, AudioFileFormat.Type.WAVE,
new File("./" + fileName + ".wav"));
}catch (IOException e){
e.printStackTrace();
}
SoundEngine Free:http://soundengine.softonic.jp/
Windows、Mac用の波形編集ソフト
wavファイルを波形として表示し、
トリミングなど編集を行えるソフトウェア
・個人的に最大のおすすめポイント
wavを再生しながらリアルタイムにFFTしてくれる!
ツールバー右から2番目「ヴィジュアル」をクリック 出てきたウインドウで右クリックし、スペクトラムアナライザーを選択先ほどのドは明らかに機械音でピアノ音には程遠い
なぜか?
それは自然の音には必ず倍音が含まれており
倍音のデシベル値の違いが音色の違いを形成しているから
倍音とは基本となる音の周波数を
自然数倍した周波数をもつ音のこと
先ほどのドであれば
基音(第1倍音):261.6 Hz 第2倍音 :261.6×2 Hz (523.2 Hz) 第3倍音 :261.6×3 Hz (784.8 Hz) 第4倍音 :261.6×4 Hz (1046.4 Hz) 第5倍音 :261.6×5 Hz (1308 Hz)・
・
・
倍音のデシベル値が音色を決めるので
再現したいピアノのデシベル値を用いる
原音をFTするのが良いが今回は
こちらのブログを参考にさせていただいた
基音(第1倍音):-25dB 第2倍音 :-33dB 第3倍音 :-30dB 第4倍音 :-44dB 第5倍音 :-46dB・
・
・
0dBで1倍率である
以下で倍率を計算可能(Java)
public static double calcMagFromDb(int db){
return (Math.pow(10, db / 20.0));
}
後は各倍音と倍率を適応したsin波を重ね合わせて
サンプリングを行うだけである
double sinSum = 0;
for(int j=0; j<sin.length; j++){
//dbは各倍音のデシベル値配列
//sinは自作クラスの配列、各倍音のパラメータを設定されている
//(1ループごとの角速度など)
//getAmplitudeメソッドで現時点の振幅を返す
sinSum += calcMagFromDb(db[j]) * sin[j].getAmplitude();
}
//以下でsinSumをintへキャストしたり
//前述の8もしくは16ビットの上限に収まるように値を縮小したりする
段々楽器のようになっていくのがわかってもらえるだろう
先ほどのドは48倍音までいくとだいぶ楽器のようだった
しかしまだピアノとはいえない
なぜか?
それはピアノの音が一定の強さでずっと鳴るわけがないから
ピアノは最初に大きく鳴った後すぐに小さくなり、
その後余韻のようになるのが普通である
(*ピアノ弾いたことも、ほとんど聴いたこともないので
間違ってたらすいません!)
音量を一定ではなくピアノのように変化させればよい
以下の線図のような変化をさせる
以下のプログラムで先ほどの変化を行う
//経過時間による音の減衰を行う(反比例計算)
double down = (Main.SAMPLING / 16) / (double)count;
//上記反比例の値が1.0までとそれ以降で分岐
if(down < 1.0){
//反比例と下に凸な2次関数を用いて減衰率を定める
if(count < Main.SAMPLING * 3)
down = (down + 0.8 / (9.0 * Main.SAMPLING * Main.SAMPLING) * Math.pow(count - Main.SAMPLING*13/4, 2) + 0.1) / 2;
//一定時間経過後は一定値を取る
else
down = (down + 0.1) / 2;
sinData = (int)(sinData * down);
}else {
//反比例値が1.0になる直前に1次関数で瞬時に音量を上げる
if(count > Main.SAMPLING*15/256)
down = 256.0 / Main.SAMPLING * count - 15.0;
//上記条件までは無音
else
down = 0;
sinData = (int)(sinData * down);
}
//コードミスから生まれた謎の処理 何故かdownをもう一度掛けると減衰率が良い感じになる
sinData *= down;
先ほどのプログラムによる実際の音量変化
Step3でかなりピアノに近づいた(電子ピアノの方が近い?)
しかしやはりピアノは和音を奏でてこそである!
なので実際に奏でてみた
(楽譜が読めないので音はずれはどうか目を瞑ってください)
雑に作ったので高音の再現率が
かなり低いのがFFの曲からわかるかと
ここにプログラムがあるので是非見てあげてください
Markdownとreveal.jsを使ったことがなかったので
手探りのスライド作りになりわかりづらかったと思います
すいませんm(__)m
質問等ございましたら以下まで送ってください大歓迎です!