〜ブラウザだけで音を出す入門〜



※ここではhtml5のaudioを使っています。ブラウザによっては音が出ません。

※ブラウザのバージョンによっても音が出ません

※音が割れやすいですが何度か鳴らすとマシになります

背景が緑のテキストエリアの中身は改変して結果を試せます

ここを参考にしましたhttp://d.hatena.ne.jp/yanagia/20100323/1269334226



〜とりあえず正弦波を作る〜



sinを使って0を中心とした波で音の配列を作ります

sinは円周率×2で一周するので最初に円周率×2/周期を求め

その値を変数に足していきながらsinしていきます



len・・・配列の長さ syuu・・・周期 puku・・・波の大きさ

正弦波の作り方はいろいろあるので興味がある人は自分で調べてください



〜バイトデータにする〜



こんな感じで配列を渡すと0〜255の整数に直す関数を作ります

波の中心が128になるようにします





〜WAVEヘッダーをつける〜



WAVEファイルとして読み込ませるためにWAVEファイルの頭にある情報を配列にくっつけて返す関数を作ります



ほとんど決まってる数字なので何も考えずに数字を入れます

ただ40〜43個目は音の配列のサイズ、4〜7個目は全体のサイズの情報なので4バイトに分解して入れます

& 0xFF は %256 のようなものです

他の数字の意味についてはWAVEファイルフォーマットでググるとよいでしょう



〜文字列化する〜



ブラウザに読み取ってもらうためにbase64エンコードという方法で配列を64進数を表す文字列にする関数を作ります



まず3つのバイトデータを4つの文字にするため0をpush()して配列の長さを強引に3の倍数にしてしまいます

ループで三つずつ配列を見て一個目<<16(65536倍)+二個目<<8(256倍)+三個目を計算し

その値を>>18(÷262144)、>>12&63(÷4096%64)、>>6&63(÷64%64)、&63(%64)したものに

対応した文字をくっつけていきます

演算子 | はビットが重ならない時の高速足し算です

最後に0をプッシュした存在しないデータの部分を"="に変えます



〜HTMLAudioElementオブジェクトを作る〜

ここまでの関数も使って波形の配列を渡すとAudioオブジェクトにして返す関数を作ります



波形を渡すと

バイト化→waveヘッダーをつける→base64エンコード→頭に"data:audio/wav;base64,"をつける

を行いnew Audio()でAudioオブジェクトにして返します

ボリュームはデフォルトでMAXなので適当に下げときます



〜正弦波を鳴らしてみる〜

では正弦波を実際に鳴らしてみます

鳴らすを押すとテキストエリア内の処理が実行されます

ここは書き換えると結果も変化します




aの代わりにグローバル変数をオブジェクトにすれば一度作れば後はplay()だけで同じ音が鳴ります



〜頭とケツの音量を下げてプチプチ対策〜



いきなり音が鳴ったりいきなり音が止まるとその部分はプチプチ音として知覚されるためなめらかにします



ata・・頭の音量を下げる長さが全体の何割か ket・・ケツの(同



頭の1割、ケツの2割を小さくして鳴らしてみます






〜加算合成〜



二つの配列を足し合わせてみます

単純に値を足すだけです

最大値が±128を超えないようにしましょう



r1,r2・・・配列 ata・・・r2のスタート位置(マイナスはダメ)



周期の違う正弦波をずらして足してみます






〜FM変調〜



波形を利用して別の波形を発生させます

波形のプラマイのぶんずらした位置の正弦波を求めます



retu・・利用する波形 syuu・・周期 puku・・波の大きさ op・・影響度



同じ周期の波形でFM変調してみます




FM変調だけでさまざまな楽器の音を作ることができるようです

詳しくはここを参照してください

DX7 FM音源講座
http://fmdx7.music.coocan.jp/



〜ホワイトノイズ〜

疑似乱数を使ってホワイトノイズを作ってみます

seed=(641×seed+3313)%100000

を100000回繰り返すとseedの値は0〜99999が一回ずつ出現して元に戻ります

この値を利用して波形を作ります

641,3313,100000の値はこれじゃなきゃいけないわけではありません

詳しくは線形合同法でググってみてください



len・・長さ seed・・最初のseedの値 puku・・波の大きさ






〜無限インパルス応答〜

このかっこいい名前の式を使うことでいろいろなフィルタを作ることができます

ググったらコードが出たので使ってますが意味はまったく理解していません



retu・・フィルタをかける配列 

b0,b1,b2,a0,a1,a2の値によってフィルタのタイプが決定します

詳しくはこちらで
http://www.wa.commufa.jp/~obaqq75/study/digital_filter/biquad.html

でもローパスフィルタ以外はほとんど使いません



〜ローパスフィルタ〜

無限インパルス応答を使ってローパスフィルタを作ります

このフィルタを通すと一定より高い音が消えます



retu・・フィルタをかける配列 syuu・・これ以下の周期を消す

syuuは7以下だと変になるので7以下にならないようにしてます








〜オーバードライブ〜

配列に一定の値をかけて絶対値が一定以上にならないようにします

音圧があがる効果があります



retu・・配列 bai・・かける倍率 puku・・上限下限



LPFをかけたホワイトノイズをオーバードライブしてみます






〜音階を作る〜

A4(高いラ)がちょうど440キロヘルツなのが有名ですがこの情報だけで音階を作れます

グローバルな配列onkai(96)の60個目をA4とします

1オクターブ(半音12個)で周波数が半分になるのでonkai[0]=440/16キロヘルツです

これをループで2の1/12乗倍していけば各周波数がわかります

44100/周波数がその周波数の波形を作る周期になります

この44100という数字(サンプリングレート)はwaveヘッダーの数字をいじれば変えることも可能です



↑これをwindow.onload時にやっときます



ドレミファソラシドを正弦波で鳴らしてみます






〜波形を見る〜

視覚で波形をチェックできるようにcanvasに波形を描画させる関数を作っておきます

ctxはグローバルな配列にしてあります



retu・・チェックする配列 ban・・描画するcanvasの番号 ata・・チェックする位置



3倍の周期でFM変調した波形を見てみましょう








〜波形全体を見る〜

波形全体の強弱も見れるようにします



retu・・チェックする配列 ban・・描画するcanvasの番号 wid・・ラインの太さ










〜エンベロープを作る〜

DTMにおいてエンペロープとは音量を変化させる曲線のことを指します

配列上に直線を引いて作ります



引数をそのまま配列に見立てて 値、長さ、値、長さ、値・・・と指定します










〜ボリュームエンベロープ〜

エンベロープに合わせてボリュームを変化させます



retu・・音の配列 en・・エンベロープの配列

線形補間しているのでエンベロープの長さは気にする必要はありません








全体の形が前回のエンベロープの形になってるのが確認できますね

さらにエンベロープをかけてからFM変調することでFM変調のかかり具合にも変化が出ています



〜エンベロープフィルター〜

エンベロープに合わせてローパスフィルターのかかり具合を変えてみます

無限インパルス応答の引数が常に変化する状態なので

ローパスフィルターと無限インパルス応答を合体させて作り変えます



retu・・音の配列 en・・エンベロープの配列 syuu・・これ以下の周期を消す

単純に合体させただけでもちろん式の意味はわかってません



ホワイトノイズにエンベロープに合わせたローパスフィルタをかけてみます










〜フランジャー〜

波形に自分を遅延させた波形を加算します



retu・・音の配列 bai・・遅延させる倍率(1以上はエラー) ritu・・加算する率










〜音量をまんべんなくする〜

ローパスフィルタでは低音成分がないと波が小さくなってしまいますが

これを一定範囲の絶対値の最大に合わせることで波の大きさを揃えてみます

波形が若干変わってしまうのでノイズ以外ではお勧めしません



retu・・音の配列 han・・チェックする範囲 puku・・揃える大きさ






manbを外して違いをみてください





〜ハニングウィンドウ〜

巨大な正弦波でボリュームエンベロープをかけます

後の周波数解析で必要になる処理です



retu・・音の配列










〜周波数解析〜

aを円周率×2/周期として

配列[0]×cos(a)+配列[1]×cos(a×2)+配列[2]×cos(a×3)+・・を一定の区間繰り返すと

その区間にその周期の正弦波がどれくらい含まれているかを調べることができます(離散フーリエ変換)

同様にsinで同じことをすると虚部と呼ばれる成分が出ます(よくわからないので絶対値で足し合わせます)

その際、区間にハニングウィンドウをかけることで区間の長さによる誤差が消えます

一定の周期ごとに周波数を調べると同じcos()を計算することになるので

精度を800と決めうちしてwindow.onloade時に周期800のcos・sinの配列を計算してグローバルcostank・sintankに入れときます

グローバル変数を何度も参照するときはローカルに移したほうが早くなります



retu・・音の配列 ban・・描画するcanvasの番号 ata・・調べる開始位置 han・・調べる範囲

bai・・グラフの長さ倍率 syuu・・目安になる周期








赤いところがsyuuで設定した基音となる周期です

FM変調一回で倍音成分がたくさん発生してるのがわかりますね

誤差を減らす処理はハニングウィンドウ以外にもあります

詳しくは窓関数でググってください



〜残響効果っぽい処理1〜

音というものは低音ほど残りやすいそうです

そこで波形を予定より少し長めにつくりそれをsliceしたものとLPFをかけたもので分け

sliceしたもののケツを衰退させてふたつを加算すれば残響っぽくなります



retu・・音の配列 syuu・・LPFをかける周期 naga・・sliceする長さ

ket・・sliceしたほうのケツの何割を衰退させるか ritu・・LPFをかけたほうを加算する率(1以下










〜ギターっぽい音〜

変調させまくってディストーションギターっぽくしてみます

与えられた周期の何倍で変調するかを関数にすれば

違う高さで同じ音色が作れます



len・・長さ syuu・・基音の周期 puku・・波の大きさ






前の音と少し重ねると自然になります



〜無音時間をつける〜

某ブラウザはバージョンによっては一定以下の長さの音が確実に割れます

これは0,0,0,0・・の配列をくっつけて無理やり長くすれば防げます



len・・長さ nam・・初期値










〜楽譜を読む〜

文字列から演奏できるようにします

グローバルotolistにドレミに対応した文字列を連想配列にしてonkaiの番号を入れときます



↑これをwindow.onload時にやっときます



そして一文字ずつ見てその表記を実行します

一度作った波形は保存しておき、同じ長さ同じ高さの音で再利用します



引数

bun・・・演奏する文字列 naga・・・一音の長さ hami・・・次の音と重なる長さ

puku・・・波の大きさ func・・・音色を作る関数 max・・・おおよその全体の長さ

maxが正確なほど高速になります

楽譜の表記

cdefgab・・ドレミファソラシ あいうえおかき・・1オクターブ上 まみむめもらり・・1オクターブ下

大文字・カタカタ・・半音あげ  y・・無音  数字・・伸ばし(省略で1)

ln(1じゃなくてエル)・・一音の長さを最初の1/nにする  Ln・・・一音の長さを最初のn倍にする










〜楽譜をランダムに作る〜

与えられた文字を適当に組み立てます



bun・・文字列 naga・・出来上がる音の数 nobi・・伸ばす上限

nritu・・伸ばす率(0〜1) yritu・・休む率(0〜1)






↓ここに結果を表示します
あああ




〜スケールについて〜

一般的にスケールというとピアノの白鍵、ドレミファソラシの音の間隔の並びを指します

これを全て同じ度数でずらしてもスケールです

一音ごとの間隔が一定でないため半音4個あげてミファソラシドレとかにはなりません

基本的な1個以外はどこかに黒鍵が混じります

ここでの表記で表すと次の12種類となります



まみむめもらりcdefgabあいうえおかき
マミむメモラりCDeFGAbアイうエオカき
まみミめもらラcdDfgaAあいイえおかカ
マみむメモらりCdeFGabアいうエオかき
まマミめもモラcCDfgGAあアイえおオカ
まみむメもらりcdeFgabあいうエおかき
マミめメモラりCDfFGAbアイえエオカき
まみむめもらラcdefgaAあいうえおかカ
マミむメモらりCDeFGabアイうエオかき
まみミめもモラcdDfgGAあいイえおオカ
マみむメもらりCdeFgabアいうエおかき
まマミめメモラcCDfFGAあアイえエオカ



童謡などは同じスケール内の音だけで構成されてることが多いため

スケール内の音をランダムに鳴らすだけでそれっぽくなります



〜コードっぽいものをランダムに作る〜

コードとは曲の伴奏で使う特定の組み合わせの音です

コードの構成音に含まれる音(内音)でメロディーを作っていくとそれっぽくなるそうです

コードはたくさんあって一から覚えるのは大変ですが何も知らなくてもコードっぽいものを作る方法があります

それは スケール内の音を一個とばしで組み合わせる です(ちゃんとコードを知りたい人はググって調べてね)



bun・・スケールの文字列 kazu・・組み合わせる音の数(3か4)



スケールからランダムに作ったコードをランダムに組み合わせて鳴らします



あああ


同じ音が続きやすくなってしまうのはご自分で改変してください



〜残響効果っぽい処理2(リバーブ?)〜

波形にLPFをかけたものをずらして小さくしながら何度も加算します

その際、ずらす間隔自体が音程になってしまうため間隔に疑似乱数を少し加算します



retu・・音の配列 time・・最初にずらす量 syuu・・LPFをかける周期

bai・・最初に加算する率 kan・・ずらしていく間隔 kai・・加算する回数 seed・・乱数のシード値








シード値を変えると響き方が変わります

計算量が多いため一音だけの処理なら残響っぽい処理1のほうがいいです



〜高音質のステレオにする〜

わかりやすいように1データあたり1バイトで処理してましたが

waveヘッダーの数字を変えれば2バイト、3バイトにすることが可能です

ブラウザが認識するのは3(24bit)までです

さらにステレオにすることも可能です

まずsoroeをマルチバイト用に改造してみます



配列を与えると複数バイトに分けてbit倍の長さの配列を返します



atamasetを引数からヘッダーをいじれるように改造します



retu・・音の配列 bit・・データあたりのバイト数(1か2) ste・・モノラル(1)かステレオ(2)

rate・・サンプリングレート数

22個目がモノラルかステレオかなのでここにsteを入れます

24〜27がサンプリングレートなのでここにrateを4バイトに分けて入れます

28〜31はよくわかりませんがrate*bit*steなのでこれを4バイトに分けて入れます

32個目はよくわかりませんがbit*steを入れます

34個目はデータあたりのビット数なのでbit*8を入れます



これらをまとめてrendering2を作ります





同じ長さの二つの配列をくっつけてステレオにして返す関数を作ります

ステレオ音源はデータが左右左右左・・となります



r1・・左の音の配列 r2・・右の音の配列



右から正弦波、左からギターで鳴らしてみます






〜残響効果っぽい処理3(逆位相リバーブ?)〜

dirでLPFをかけたものを何度も加算していましたが

これを右で加算、左で減算したステレオにして返すように改造すると

すごく響いた感じがします



引数はdirと同じ






シード値によって音のする方向が変わります



〜音を保存する〜

controlsをつけたaudioタグを用意します

audio id="hozon"


これの.srcに音の文字列を入れます

コントロールから再生できるのを確認したら

再生マークの上で右クリック→名前をつけてオーディオを保存 で保存できます

24bitの音源はまだ一般的に読み取られないことが多いです



引数・・rendering2と同じ








〜読み込んだファイルの波形を取得する〜

canvasではtodataurl()とかありますが

なんとaudioでは読み込んだ音声データの中身を見るすべがありません

そこでfile選択ダイアログで指定してFileAPIのreadAsBinaryString()でバイナリの文字列を取得します

そこから頭44文字(ヘッダーの長さ)をとったものをcharCodeAtで数字の配列にします

サンプリングレートとデータあたり何バイトかとステレオかも調べときます



id・・fileタグのid func・・次に実行する処理

読み込みは非同期で行われるためonloadを使って読み込み終わってから処理します

なんとonloadはいつ終わるかわからないため戻り値を使うことができません

そこで次に実行する処理をクロージャで渡してむこうで実行してもらいます



解析用にステレオをモノラルにする関数も作っときます



retu・・ステレオの配列 lr・・左なら0 右なら1



読み込んだ音声を解析して鳴らしてみます

変なものを鳴らしてしまった時のためにグローバル変数で鳴らして止めれるようにしてます

↓id="file1"
  function stop(){player.pause();};








注意!

※すべてのファイルを無理やり読み込んでしまいます。適正でなかった場合どんな音がするかわからないので注意してください







まだ製作中

苦情は じゃがりきん まで