farbrauschと言えば、そのintroは圧倒的な物量で見るものを唖然とさせてくれるのだけど、中でも64kbとは思えないリッチな音楽も当時は驚愕ものだった。特にfarbrausch衝撃の出世作であった2000年のfr-08は、64kbでありながら、本編前のローディング中にも専用の曲が流れるという意味不明な豪華さ。正直、当時はどうなってんじゃこりゃ?と笑うしかなかったわけだけど、あれからもう早12年近くが経つのだ。当時岐阜の高専生だった僕も、今や上京し社畜8年目。時の流れは無情である。
そんなfarbrauschが彼らのintro/demoのコアのコードを先日公開した。当時僕らを熱狂させてくれた魔術的コードが白日の下に晒されたのだから、これは何か読むしかないってわけで、とりあえずサウンド部分を見ることにした。最近全然デモシーンにもグラフィック周りにもご無沙汰だったので、たぶん他は読んでも解らんからね(汗)
V2 synthesizerとは何か
その摩訶不思議なfarbrauschのintroに使われているシンセサイザがV2 synthesizerというシステム。farbrauschのkb(Tammo Hinrichs)作。
3オペ16チャンネル 64音同時発声のシンセサイザ。内、スピーチ合成用に1ch。
楽曲作成用にVSTiプラグインやBuzzプラグインが提供されている。
バイナリは1.0が2004年に公開されて、1.5が2008年に公開されている。
http://1337haxorz.de/products.html
ソースは先日から
https://github.com/farbrausch/fr_public/tree/master/v2
で手に入るようになった。
3オペ16チャンネル 64音同時発声のシンセサイザ。内、スピーチ合成用に1ch。
楽曲作成用にVSTiプラグインやBuzzプラグインが提供されている。
バイナリは1.0が2004年に公開されて、1.5が2008年に公開されている。
http:/
ソースは先日から
https:/
で手に入るようになった。
基本スペック
基本スペック自体は普通のシンセかな、という気がする。ただ、この普通といえるレベルのシンセを当時の環境でリアルタイムにデモの裏で動かすという速度面の最適化をすると同時に、64kbの片隅に押し込めるサイズ面での最適化を行っていたというのが凄いのだ。
YM2151 好きとしては4オペ(OSC)で複雑なFM変調がかけられるアルゴリズムを持っていると二重丸なんだけどね。kbさんはどうやらFM変調はあまり使わず、VCFやLFO等を駆使したテクノっぽい音作りがメインのようなので、3オペで割り切った構成なんだと思われる。
- チャンネル
- 3オシレータ
- 正弦波、鋸波、三角波、正弦波の(前段オシレータによる)周波数変調、ノイズ、等
- LFO*2
- EG(Envelope Generator)*2
- VCF*2
- LPF,BPF,HPF,Notch,MoogLPF,MoogHPF
- VelocityやNote、LFOやEGの出力は殆どあらゆるパラメータに結線可能。
- 16ch(最終ch)はスピーチシンセサイザに割り振られている。
- 3オシレータ
- グローバル
- リバーブ
- ディレイ
- コンプレッサ
- バスブースト
YM2151 好きとしては4オペ(OSC)で複雑なFM変調がかけられるアルゴリズムを持っていると二重丸なんだけどね。kbさんはどうやらFM変調はあまり使わず、VCFやLFO等を駆使したテクノっぽい音作りがメインのようなので、3オペで割り切った構成なんだと思われる。
V2を使ったintro/demo
farbrauschのwerkkzeug3を使ったintroは全てと思われる。てことで、幾つか紹介。個人的にはfr08は当然として、MS2001 inv.の曲が何故か印象に残ってたり。
kbが作曲したintro
kbは作曲していないがv2を使った有名なintro
あと、まだV2はない時代だけどkbが作曲した有名なdemoだと
elitegroup - Kasparov
がある(当時はこれ、賛否両論あったよなあ)。V2のソースコードを見るとelitegroupの文字が見受けられるので、当時からのコードを使いまわしているようだ。
kbが作曲したintro
- fr-08 .the .product
- fr-011 Picknick in the Meadows (Mekka & Symposium 2001 invitation)
- fr-013 Flybye (The Party 2001 invitation)
- fr-024 welcome to (Breakpoint 2003 invitation)
- fr-030 Candytron
kbは作曲していないがv2を使った有名なintro
あと、まだV2はない時代だけどkbが作曲した有名なdemoだと
elitegroup - Kasparov
がある(当時はこれ、賛否両論あったよなあ)。V2のソースコードを見るとelitegroupの文字が見受けられるので、当時からのコードを使いまわしているようだ。
曲の作り方から再生まで
- VSTiプラグインとして起動
例えば、OpenMPTを使う。
InstrumentにVSTiプラグインを読み込ませると、下記のような画面が出てくる。ソフトシンセに詳しければ、これで大体何ができるかは分る…のかな?DTMやらない僕は最初はよく分りませんでした。大まかには左側が各種モジュールで、右側が各種入力やそれらのモジュール、あるいはモジュール間の結線となります。EGやLFOはここで結線しないと意味ないです。
ということで、これを弄り回して音色を作っていきます。
- 音ができたらVSTホストで適当に曲を作る
- 曲が完成したら、recordボタンを押してから曲を再生すると、V2 synthesizerに投げ込まれたコマンドがダンプされてv2mファイルが出来上がり
- v2mファイルをlibv2で読み込ませて再生
これで、君のintroにも立派な曲が付けれるぞ、っと。
ファイル構成
v2/以下のファイル構成を洗い出してみた。
これ以外にもファイルは多数あるけど、読まなくても本質的には問題ない。たぶん。
synth.h
synth.asm
実際のレンダリングを行う中核。
フルアセンブラ。
v2mplayer.h
v2mplayer.cpp
v2mのロード、レンダリング。
werkkzeug3にはこれのコピーが_viruz2.hpp,.cppとして入っている。
dsio.h
dsio.asm
DirectSound制御モジュール
何故かフルアセンブラ。
ronan.h
ronan.cpp
スピーチシンセサイザ。
これ以外にもファイルは多数あるけど、読まなくても本質的には問題ない。たぶん。
synth.h
synth.asm
実際のレンダリングを行う中核。
フルアセンブラ。
v2mplayer.h
v2mplayer.cpp
v2mのロード、レンダリング。
werkkzeug3にはこれのコピーが_viruz2.hpp,.cppとして入っている。
dsio.h
dsio.asm
DirectSound制御モジュール
何故かフルアセンブラ。
ronan.h
ronan.cpp
スピーチシンセサイザ。
v2mから再生までの流れ
実際のコードを深堀りする前に、全体の流れをざっくり抑えておこう。
v2m形式
まずはv2mがどんな形式なのかを説明する。
ざっくり言えば、MIDIメッセージのProgram Change,Control Change,Pitch Bend,Note On/Offを記録したもの。
ただし、圧縮率を高めるためにSMF(Standard Midi File)とは格納方式がかなり異なる。
SMFの場合、(Format0,1,2で異なる面もあるが)基本的にはデルタタイム(可変バイト長)+MIDIメッセージを順番に並べるだけである。
v2mでもデルタタイム(ただし固定長)+MIDIメッセージを並べるという点では同じ。ただ、並べ方に工夫がある。
v2mの場合はチャンネル毎、メッセージ種類毎、パラメータのバイト単位で並べなおす。例えば、最初はch.1のProgram Changeのパラメータだけが並び、次にそのデルタタイムの先頭バイトだけが並び、次にそのデルタタイムの2バイト目が並ぶといった具合。SMFがAoSで、v2mはSoAと言えば近いのかな。
これによる利点は2つあって、ひとつは、チャンネルやメッセージの種類を表すMIDIメッセージの最初の1バイトがメッセージ毎に省略できること。次に似た値が並びやすいので圧縮が期待できる点。
このあたりの概略はsoundsys.cppの頭のコメントや、vsti/v2mrecoder.cppのExport関数をみると分りやすい。
ざっくり言えば、MIDIメッセージのProgram Change,Control Change,Pitch Bend,Note On/Offを記録したもの。
ただし、圧縮率を高めるためにSMF(Standard Midi File)とは格納方式がかなり異なる。
SMFの場合、(Format0,1,2で異なる面もあるが)基本的にはデルタタイム(可変バイト長)+MIDIメッセージを順番に並べるだけである。
v2mでもデルタタイム(ただし固定長)+MIDIメッセージを並べるという点では同じ。ただ、並べ方に工夫がある。
v2mの場合はチャンネル毎、メッセージ種類毎、パラメータのバイト単位で並べなおす。例えば、最初はch.1のProgram Changeのパラメータだけが並び、次にそのデルタタイムの先頭バイトだけが並び、次にそのデルタタイムの2バイト目が並ぶといった具合。SMFがAoSで、v2mはSoAと言えば近いのかな。
これによる利点は2つあって、ひとつは、チャンネルやメッセージの種類を表すMIDIメッセージの最初の1バイトがメッセージ毎に省略できること。次に似た値が並びやすいので圧縮が期待できる点。
このあたりの概略はsoundsys.cppの頭のコメントや、vsti/v2mrecoder.cppのExport関数をみると分りやすい。
v2mのデコード
v2m形式の概要は分ったので、デコードからシンセサイザ本体までの流れを見てみる。このあたりは、v2mplayer.cppを見れば分る。
v2mを読み込むと最初に従いパッチのセットアップやグローバルな設定を行う。
そしてtick毎に、V2MPlayer::Tick()によってv2mに従い、MIDIメッセージを生成へ渡す。具体的には、メッセージを1Tick分書き溜めてから、synth.asmのsynthProcessMIDIを呼ぶ。
生成するMIDIメッセージは以下のみ。
synth.asmはNote Off(1000nnnn)は受け付けない。代わりにNote Onのvelocityを0としてNote Off扱いとする。
MIDIメッセージの詳細は
http://www.midi.org/techspecs/midimessages.php
を参照。
synth.asmのインタフェイスがMIDIメッセージなのは、VSTi対応のためと思われ。
v2mを読み込むと最初に従いパッチのセットアップやグローバルな設定を行う。
そしてtick毎に、V2MPlayer::Tick()によってv2mに従い、MIDIメッセージを生成へ渡す。具体的には、メッセージを1Tick分書き溜めてから、synth.asmのsynthProcessMIDIを呼ぶ。
生成するMIDIメッセージは以下のみ。
Cn pp | Program Change |
Bn cc vv | Control Change |
En ll mm | Pitch Wheel Change |
9n kk vv | Note On |
MIDIメッセージの詳細は
http:/
を参照。
synth.asmのインタフェイスがMIDIメッセージなのは、VSTi対応のためと思われ。
synthでレンダリング
では、このMIDIメッセージからどう波形をレンダリングしているのか?
その答えがsyhth.asmにある。ここが肝心要なのであるが、FPU命令だらけのフルアセンブラであり、高校生のころx86逆アセンブラを自作していたような自分でもなかなか読むのが辛い。はっきりいってCで書いてあれば、なんてことはないコードのはずなんだが…
その答えがsyhth.asmにある。ここが肝心要なのであるが、FPU命令だらけのフルアセンブラであり、高校生のころx86逆アセンブラを自作していたような自分でもなかなか読むのが辛い。はっきりいってCで書いてあれば、なんてことはないコードのはずなんだが…
データ構造を大まかに捉える
まずはどんなデータ構造があるのかを大雑把に掴んでおく。
SYNが基点となるsynthesizer本体の構造体となる。ポイントとなるプロパティだけを見てみる。
patchmapはpatch(v2soundで定義される構造体。いわゆるinstrument)への配列ポインタ。
voicesとchans(チャンネル)は見れば分るが、末尾のvとwはなんだろう?
コードを読むと分るが、vがv2sound(値をBYTEで持っている)と互換を持っていて最初にv2soundから展開される構造体で、値をfloatで持つ。wは実際に発声するときに使うworkspaceで、Vが持っているプロパティに加え、各種テンポラリ変数を持っている。
v2soundとVとWの間でどのようにコピーされるかはstoreChanvaluesやsyV2Set、syChanSet等を読むと分る。
syVV2,syWV2の中にあるsyVOsc,syWOscや、syVLFO,syWLFOなども、同様の関係。
chanmapはどのボイスをどのチャンネルが使っているかを示してる。
大まかに各構造体の関係をまとめると、
SYNが基点となるsynthesizer本体の構造体となる。ポイントとなるプロパティだけを見てみる。
struc SYN .patchmap resd 1 .chanmap resd POLY .chans resb 16*CHANINFO.size .voicesv resb POLY*syVV2.size .voicesw resb POLY*syWV2.size .chansv resb 16*syVChan.size .chansw resb 16*syWChan.size
patchmapはpatch(v2soundで定義される構造体。いわゆるinstrument)への配列ポインタ。
voicesとchans(チャンネル)は見れば分るが、末尾のvとwはなんだろう?
コードを読むと分るが、vがv2sound(値をBYTEで持っている)と互換を持っていて最初にv2soundから展開される構造体で、値をfloatで持つ。wは実際に発声するときに使うworkspaceで、Vが持っているプロパティに加え、各種テンポラリ変数を持っている。
v2soundとVとWの間でどのようにコピーされるかはstoreChanvaluesやsyV2Set、syChanSet等を読むと分る。
syVV2,syWV2の中にあるsyVOsc,syWOscや、syVLFO,syWLFOなども、同様の関係。
chanmapはどのボイスをどのチャンネルが使っているかを示してる。
大まかに各構造体の関係をまとめると、
- SYN has Patches(v2sound), Voices(syVV2), Channels(syVChan), channel map(voiceとchannelの関係)
- Voice has 3 OSCs(syVOsc), 2 VCFs(syVFlt), 2 LFOs(syVLFO), 2 Envelope Generators(syVEnv), 1 Distorter(syVDist)
- Channel has 1 BassBoost(syVBoost), 1 Distorter(syVDist), 1 Chorus(syVModDel)
- syVxxxが最初にv2soundを元にパラメータ設定される構造体で、syWxxxが実際に発生する際に使われる構造体(workspace)
ノートONを追う
ではそろそろv2mplayer.cppとの繋ぎから見ていこう。
v2mplayerで作られたMIDIメッセージは
_synthProcessMIDI
を経由してシンセサイザ本体に取り込まれる。ここでは代表的なProcessNoteOnの挙動を見てみる。
ざっくり見ていくと、空いてるvoiceを探したら(.foundfv)、storeV2Valuesでpatchをvoiceへ設定して、syV2NoteOnでOSCやEGの初期化を行う。先で述べたVとWがややこしいけど、意味が分っていれば読めるはず。
他のMIDIメッセージの処理も同じ感じで追える。と思う。
v2mplayerで作られたMIDIメッセージは
_synthProcessMIDI
を経由してシンセサイザ本体に取り込まれる。ここでは代表的なProcessNoteOnの挙動を見てみる。
ざっくり見ていくと、空いてるvoiceを探したら(.foundfv)、storeV2Valuesでpatchをvoiceへ設定して、syV2NoteOnでOSCやEGの初期化を行う。先で述べたVとWがややこしいけど、意味が分っていれば読めるはず。
他のMIDIメッセージの処理も同じ感じで追える。と思う。
レンダリングを追う
チャンネルのレンダリング自体は大まかに2つのパートに分かれる。
EGやLFOの更新を行うsyV2Tick(EGやLFOからOSC等へのパラメータ反映はstoreChanValuesのmodmatrixで行う)と実際にレンダリングを行うsyV2Renderだ。
前者はEGとLFOの更新だから、特殊なことはしていないので解説は省く。
後者もそういう意味ではそこまで特殊なことはしていないと思うが、OSCとVCF(Filter)の代表的な実装だけを見ていこう。
EGやLFOの更新を行うsyV2Tick(EGやLFOからOSC等へのパラメータ反映はstoreChanValuesのmodmatrixで行う)と実際にレンダリングを行うsyV2Renderだ。
前者はEGとLFOの更新だから、特殊なことはしていないので解説は省く。
後者もそういう意味ではそこまで特殊なことはしていないと思うが、OSCとVCF(Filter)の代表的な実装だけを見ていこう。
OSC(Oscillator)
まず、音の基本となるOSCから。これはsyOscRenderでレンダリングしている。
modeによってtri/saw(三角波、鋸波),pulse(矩形波),sin,noise,FM変調sin,auxa,auxbが切り替わる。
auxa/auxbは外部入力を想定しているのだけど、v2mでは関係ないっぽい。
modeによってtri/saw(三角波、鋸波),pulse(矩形波),sin,noise,FM変調sin,auxa,auxbが切り替わる。
auxa/auxbは外部入力を想定しているのだけど、v2mでは関係ないっぽい。
鋸波/三角波
てことで、まずはシンセサイザの基本、鋸波から見ていく。
これは
syOscRender.mode0
にコードがある。
なぜ、たかが鋸波で8種(実質6種)も分岐があるのか疑問に思うかもしれないけど、この時点ではよく分らないので次へ。
まずebpにsyWOsc構造体へのポインタが入っている前提。
はじめに定数値などをFPUのスタックに積んでいく。
ここで分りづらいのは、calc float helper valuesの部分だろう。
syWOsc.cntは毎ステップfreqの値が加算される小数部32bitの固定小数点だ。
この上位23ビットをfloatの仮数部に入れて、符号と指数は決めうちにして、1.0から2.0までのfloatを作っている。
その後、そこから1を引いて0.0から1.0にしている。FPUで割り算とかが発生するのを嫌ってこうしてるのかな。
あと、コメントにFPUのスタックの状況が書いてあるが、この<f>がそうやって作った値となる。
なお、スタックは左からst0 st1 st2 st3... という順で書いてある。正直これがないとマジでFPUのコードは解読不能。あっても辛いけど。
おそらく、breakpointの略ではないかと思うが、これが鋸波、三角波の頂点の位置を決めている。つまり、colorの値を変えると鋸波~三角波~逆鋸波へと変化していく。mode1の矩形波ではduty比になる。
<b>が先ほどと同様の方法で1.0から2.0へfloatへcastしたもので、<col>がそれから1.0引いたものとなる。この値とgainを使って鋸波・三角波の上りと下りの係数を決める。
3bitをocnとすると、oは1個前のループのcntがbrpt未満か、cが前回cntが1周したか(overflowしたか)、nが現在のcntがbrpt未満か、を表す。
三角波の前半の上り坂をphase1として下り坂をphase2とすると、ようやく最初のジャンプテーブルの意味が分ってくる。
syOscRender.m0c2は前回も今回もphase2、
syOscRender.m0c121は前回phase1で、cntが1周してまたphase1になったことを表す。
しかしなぜこんなに場合分けが必要なのかよく分らんな。普通だったら2パターン、多くても4パターンで実装すればいいんじゃね?と素人の僕は思うが、ここまで境界条件に拘る理由は何だろうね。
場合分け後の計算は、コメントに書いてある式の通りなので飛ばす。
これは
syOscRender.mode0
にコードがある。
.m0casetab dd syOscRender.m0c2, ; ... dd syOscRender.m0c1, ; ..n , INVALID! dd syOscRender.m0c212, ; .c. dd syOscRender.m0c21, ; .cn dd syOscRender.m0c12, ; o.. dd syOscRender.m0c1, ; o.n dd syOscRender.m0c2, ; oc. , INVALID! dd syOscRender.m0c121 ; ocnまず、ジャンプテーブルの定義から。
なぜ、たかが鋸波で8種(実質6種)も分岐があるのか疑問に思うかもしれないけど、この時点ではよく分らないので次へ。
section .text .mode0 ; tri/saw mov eax, [ebp + syWOsc.cnt] mov esi, [ebp + syWOsc.freq] ; calc float helper values mov ebx, esi shr ebx, 9 or ebx, 0x3f800000 mov [ebp + syWOsc.tmp], ebx fld dword [ebp + syWOsc.gain] ; <g> fld1 ; 1 <g> fsubr dword [ebp + syWOsc.tmp] ; <f> <g> fld1 ; <1> <f> <g> fdiv st0, st1 ; <1/f> <f> <g> fld st2 ; <g> <1/f> <f> <g>ここから実際に波形をレンダリングするコードが始まる。
まずebpにsyWOsc構造体へのポインタが入っている前提。
はじめに定数値などをFPUのスタックに積んでいく。
ここで分りづらいのは、calc float helper valuesの部分だろう。
syWOsc.cntは毎ステップfreqの値が加算される小数部32bitの固定小数点だ。
この上位23ビットをfloatの仮数部に入れて、符号と指数は決めうちにして、1.0から2.0までのfloatを作っている。
その後、そこから1を引いて0.0から1.0にしている。FPUで割り算とかが発生するのを嫌ってこうしてるのかな。
あと、コメントにFPUのスタックの状況が書いてあるが、この<f>がそうやって作った値となる。
なお、スタックは左からst0 st1 st2 st3... という順で書いてある。正直これがないとマジでFPUのコードは解読不能。あっても辛いけど。
mov ebx, [ebp + syWOsc.brpt] shr ebx, 9 or ebx, 0x3f800000 mov [ebp + syWOsc.tmp], ebx fld dword [ebp + syWOsc.tmp] ; <b> <g> <1/f> <f> <g> fld1 ; <1> <b> <g> <1/f> <f> <g> fsubr st0, st1 ; <col> <b> <g> <1/f> <f> <g> ; m1=2/col ; m2=-2/(1-col) ; c1=gain/2*m1 = gain/col ; c2=gain/2*m2 = -gain/(1-col) fld st0 ; <col> <col> <b> <g> <1/f> <f> <g> fdivr st0, st3 ; <c1> <col> <b> <g> <1/f> <f> <g> fld1 ; <1> <c1> <col> <b> <g> <1/f> <f> <g> fsubrp st2, st0 ; <c1> <1-col> <b> <g> <1/f> <f> <g> fxch st1 ; <1-col> <c1> <b> <g> <1/f> <f> <g> fdivp st3, st0 ; <c1> <b> <g/(1-col)> <1/f> <f> <g> fxch st2 ; <g/(1-col)> <b> <c1> <1/f> <f> <g> fchs ; <c2> <b> <c1> <1/f> <f> <g>次に、syWOsc.brptという謎のプロパティが出てくるが、これはVSTiプラグイン上ではcolorとして設定できる値だ。
おそらく、breakpointの略ではないかと思うが、これが鋸波、三角波の頂点の位置を決めている。つまり、colorの値を変えると鋸波~三角波~逆鋸波へと変化していく。mode1の矩形波ではduty比になる。
<b>が先ほどと同様の方法で1.0から2.0へfloatへcastしたもので、<col>がそれから1.0引いたものとなる。この値とgainを使って鋸波・三角波の上りと下りの係数を決める。
; calc state mov ebx, eax sub ebx, esi ; ................................ c rcr edx, 1 ; c............................... . cmp ebx, [ebp + syWOsc.brpt] ; c............................... n rcl edx, 2 ; ..............................nc . .m0loop mov ebx, eax shr ebx, 9 or ebx, 0x3f800000 mov [ebp + syWOsc.tmp], ebx fld dword [ebp + syWOsc.tmp] ; <p+b> <c2> <b> <c1> <1/f> <f> <g> fsub st0, st2 ; <p> <c2> <b> <c1> <1/f> <f> <g> cmp eax, [ebp + syWOsc.brpt] ; ..............................oc n rcl edx, 1 ; .............................ocn . and edx, 7 ; 00000000000000000000000000000ocn jmp dword [cs: .m0casetab + 4*edx]次にcntとbrptから3bitの状態を作り、最初に定義したジャンプテーブルで分岐する。
3bitをocnとすると、oは1個前のループのcntがbrpt未満か、cが前回cntが1周したか(overflowしたか)、nが現在のcntがbrpt未満か、を表す。
三角波の前半の上り坂をphase1として下り坂をphase2とすると、ようやく最初のジャンプテーブルの意味が分ってくる。
syOscRender.m0c2は前回も今回もphase2、
syOscRender.m0c121は前回phase1で、cntが1周してまたphase1になったことを表す。
しかしなぜこんなに場合分けが必要なのかよく分らんな。普通だったら2パターン、多くても4パターンで実装すればいいんじゃね?と素人の僕は思うが、ここまで境界条件に拘る理由は何だろうね。
場合分け後の計算は、コメントに書いてある式の通りなので飛ばす。
.m0pl fadd st0, st6 ; <out> <c2> <b> <c1> <1/f> <f> <g> add eax, esi ; ...............................n c rcl edx, 1 ; ..............................nc . test byte [ebp + syWOsc.ring], 1 jz .m0noring fmul dword [edi + 4*ecx] ; <out'> <c2> <b> <c1> <1/f> <f> <g> jmp .m0store .m0noring fadd dword [edi + 4*ecx] ; <out'> <c2> <b> <c1> <1/f> <f> <g> .m0store fstp dword [edi + 4*ecx] ; <c2> <b> <c1> <1/f> <f> <g> inc ecx jz .m0end jmp .m0loop条件分岐後に.m0plに合流する。最後にgainを足して(丸めのため?)、次のジャンプテーブルのために3bitの状態を更新する。出力先はedi+4*ecxの指すアドレスとなる。ring modulationが有効なら、既に書き込まれた値と乗算、そうでなければ加算する。
sin波/fm sin波
鋸波がかなり難解な作りになっているので、これが分ればあとは簡単。sin波はfsinを使わず7次の多項式で近似してるのがちょっとしたポイント。
http://www.agner.org/optimize/instruction_tables.pdf
によると最近のCPUでもfsinは100clk程度は見ておく必要があるようなので、今でもこれは有効だと思う。fsinだと倍精度で計算しちゃうだろうから、オーバースペックだしね。
ちなみに、さらに高速化・高精度にしたいなら、区間分割して(cntの上位ビットで振り分ける)それごとに係数を変える、さらに係数をテイラー展開ではなくミニマックス近似を行うのが定石。
fm sin波では、前段までのOSCの出力をcntに足す、いわゆるFM変調を行う。
あとnoiseとか矩形波があるけど省略。OSCはそんな感じ。
http:/
によると最近のCPUでもfsinは100clk程度は見ておく必要があるようなので、今でもこれは有効だと思う。fsinだと倍精度で計算しちゃうだろうから、オーバースペックだしね。
ちなみに、さらに高速化・高精度にしたいなら、区間分割して(cntの上位ビットで振り分ける)それごとに係数を変える、さらに係数をテイラー展開ではなくミニマックス近似を行うのが定石。
fm sin波では、前段までのOSCの出力をcntに足す、いわゆるFM変調を行う。
あとnoiseとか矩形波があるけど省略。OSCはそんな感じ。
VCF(Filter)
次にフィルタを見ていく。
チャンネル単位で掛けられるLPFなどのフィルタはsyFltRenderに書かれている。
ここには普通のLPF/HPF/BPF/NotchとMoog LPF/HPFがある。
LPF/HPF/BPF/Notchは基本の構成っぽいので説明を割愛。アナログ回路でもこれらのフィル対は単純化して言えば、RLC回路のどこの電圧を測るかだけの問題なので、プログラム上も同じ処理をしてどの数値を拾うだけかの問題。
フィルタ自体の原理は僕が大昔書いた
http://kmkz.jp/mtm/mag/lab/digitalfilter.htm
と似たようなもんです。今見ると数式が多いな。難しくないはずだけど、数式があると難しく感じる不思議。
moog filterが気になるけど、moogは全然分らないので説明できないな。資料をまず探さなきゃ。スピーチシンセのronanも見てないし(これはCだし読むのは容易いけど)。ということで、時間がなくなってきたのでまた続く!かも。
チャンネル単位で掛けられるLPFなどのフィルタはsyFltRenderに書かれている。
ここには普通のLPF/HPF/BPF/NotchとMoog LPF/HPFがある。
LPF/HPF/BPF/Notchは基本の構成っぽいので説明を割愛。アナログ回路でもこれらのフィル対は単純化して言えば、RLC回路のどこの電圧を測るかだけの問題なので、プログラム上も同じ処理をしてどの数値を拾うだけかの問題。
フィルタ自体の原理は僕が大昔書いた
http:/
と似たようなもんです。今見ると数式が多いな。難しくないはずだけど、数式があると難しく感じる不思議。
moog filterが気になるけど、moogは全然分らないので説明できないな。資料をまず探さなきゃ。スピーチシンセのronanも見てないし(これはCだし読むのは容易いけど)。ということで、時間がなくなってきたのでまた続く!かも。