音とは空気の振動であり、
振動数に音の高低が依存する
ドの音を鳴らすには
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
質問等ございましたら以下まで送ってください大歓迎です!