Gauche:MML->PCM変換
MML->PCM変換
MML(Music Macro Language) の文字列を解釈して、PCMデータに変換するモジュールを作りました。
結果をwavファイルとして出力できます。
https://github.com/Hamayama/mmlproc
特徴としては、@コマンドで指定する各音色を 数式で生成しています。
@500のピアノ(仮)、@501のオルガン(仮)、@502のギター(仮)は試行錯誤の末、以下の数式にしました。
(できればドラム等も作りたかったのですが、うまく数式が作れませんでした。。。)
;; 音色生成関数(内部処理用) (define (make-progfunc prog) (case prog ;; 方形波 ((0) (lambda (t phase) (if (> (sin phase) 0) 1 -1))) ;; 正弦波 ((1) (lambda (t phase) (sin phase))) ;; のこぎり波 ((2) (lambda (t phase) (- (* (mod phase (* 2 pi)) 1/pi) 1))) ;; 三角波 ((3) (lambda (t phase) (* 2 (asin (sin phase)) 1/pi))) ;; ホワイトノイズ ((4) (lambda (t phase) (- (* (mt-random-real0 mr-twister) 2) 1))) ;; ピアノ(仮) ((500) (lambda (t phase) (* 1.3 (if (> (sin phase) 0) 1 -1) (exp (* -5 t))))) ;; オルガン(仮) ((501) (lambda (t phase) (* (if (> (sin phase) 0) 1 -1) 13 t (exp (* -5 t))))) ;; ギター(仮) ((502) (lambda (t phase) (* 5 (cos (+ phase (cos (* phase 0.5)) (cos (* phase 2)))) (exp (* -5 t))))) ;; 方形波 (else (lambda (t phase) (if (> (sin phase) 0) 1 -1))))) 各パラメータは以下になる t : 時間(sec) phase = 2 * pi * freq * t pi : 円周率 freq : 音符の周波数(Hz)(オクターブ4のラが440Hzになる) 計算結果は以下になる wave : 波形(-1~1 (ピアノ(仮)とギター(仮)は範囲を超えてしまうのでクリッピングが必要))
また 苦労した点として、複数チャンネルの同時演奏で、テンポがずれるケースがありました。
1つの音符Xが鳴っている間に他のチャンネルでテンポを変えられてしまうと、音がずれてしまうのです。
この場合、Xの鳴っている残り時間を、新しいテンポで再計算しないといけないようでした。
最終的に2パス方式にして、パス1でテンポ変更情報を取り出して実時間テーブルを作ることで、
どうにか計算できるようになりました。
現状、変換にはけっこう時間がかかります(MMLが長くなると数10秒くらい)。
ほとんどの時間は add-note 手続きの do ループのところで費やしているので、
この部分を例えばC言語で計算するようにしたら 速くなるかもしれないと考えています。
hamayama(2014/11/03 14:42:44 UTC)
- Shiro(2014/11/03 23:36:51 UTC): ローカル変数へset!すると、最適化が阻害されるのと、その変数の参照ごとにペナルティが入るので、途中で変える必要がなければletでの束縛時に値を決めてしまった方が速くなります。
それとsin, cos, expなどは複素数チェックが入るので、inner loopで速度が必要な場合はrealのみ扱う%sin, %cos, %expなどにすると有利です。
- hamayama(2014/11/05 11:21:37 UTC):情報ありがとうございます。
test3001.scmを追加して、時間を測定しながら試してみました。
まず、add-note手続きのset!をやめて、let*で代入するようにしてみましたが、これは逆に少しだけ遅くなりました。
変数間に依存関係があるせいかとも思いましたが、ちょっとよく分かりませんでした。
一方、sin → %sin のように実数用の命令を使うようにすると、10%くらい速くなりました。
また、doループをやめてvector-tabulateを使うようにすると、さらに10%くらい速くなりました。
全体的には、test3001.scmの場合 22050(Hz) x 80(sec) x 3(ch) = 5292000 回の計算が必要になるので、
家の古いPCでは10秒くらいかかるのでないかと思います。
あとは時間を見つけて、ベクタ計算の部分をC言語のdllにしてみようと思います。
- Shiro(2014/11/05 20:26:33 UTC): ふーむ、set!をやめても速くならないっていうのはちと気になるので機会があったら見てみます。
その後、計算の部分をC言語のDLLで行うようにしてみました。
https://github.com/Hamayama/mmlproc
家のPCでは4倍くらい高速になりました。
Scheme側で確保したs16vectorの領域を、C言語側から上書きするようにしています。
また、DLLが存在しない場合は、今まで通りSchemeのみで計算します。
外部パッケージ作成手順のメモ(ただしGauche v0.9.4以上の場合)を作ったので、
参考用に以下に置いておきます。
https://gist.github.com/Hamayama/d20ab805b50756ce1d9d
hamayama(2014/11/08 03:09:24 UTC)(2014/11/27 10:46:55 UTC)