固定小数点演算化 timidity


現在の timidity は浮動小数点の演算部分の負荷が重く、 StrongARM/XScale では事実上動かない。 というわけで整数演算化したもの。
たいして面白いネタでもないんで、最後の timidity.cfg の注意書きまですっとばしてもいい。
gnugo の整数化にくらべりゃ timidity なんて ...

gprof

... というコマンドがある。速度チューニングのためのプロファイラだが、 A300 のプログラムをチューニングするにあたって gprof 自身が A300 の上で動く必要はない。 そのへんの unix マシンで動けば十分だ (cygwin で動くかどーかは知らん)。

素の timidity

コマンドラインアプリとして作るだけなら timidity はすぐに作れる。
% apt-get source timidity
% cd timidity-2.10.4
% ./configure  --without-x --enable-audio=oss --disable-dynamic --disable-gtktest --host=arm-linux
% make CC=arm-linux-gcc
... で終り。で、これを A300 に持っていって音色ファイル整えて適当に鳴らすと、 鳴るには鳴るが切れ切れになる。ためしに
# time timidity -Ow piano.mid -o /dev/null
などとして midi → wav 変換時間を測るとだいたい演奏時間の 12 倍ほどかかる。 そら鳴らんだろう ...

素の timidity の gprof 出力

% make clean
% make CC=arm-linux-gcc CFLAGS='-pg'
として prof 対応させたものを A300 に持ち込む。A300 側で、
# timidity -Ow piano.mid -o /dev/null
として gmon.out というファイルを得る。これを母艦に持ち帰り、
% gprof timidity gmon.out > timidity-arm.prof
としてプロファイルを手に入れる。こんな感じ:
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
 49.53   1002.27  1002.27     4261     0.24     0.24  do_ch_reverb
 32.69   1663.80   661.53    18684     0.04     0.04  set_ch_reverb
  5.52   1775.54   111.74  3423868     0.00     0.00  apply_envelope_to_amp
  4.99   1876.56   101.02    63577     0.00     0.00  rs_bidir
  2.18   1920.69    44.13    65156     0.00     0.00  rs_loop
  1.35   1948.08    27.39    56558     0.00     0.00  mix_mystery_signal
      ... 以下略 ...

一方、PC 上の timidity では:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 45.56      9.81     9.81    50833     0.19     0.19  rs_bidir
 17.60     13.60     3.79    57571     0.07     0.07  rs_loop
  6.64     15.03     1.43    33458     0.04     0.05  mix_mystery_signal
  5.11     16.13     1.10     4261     0.26     0.26  do_ch_reverb
  5.02     17.21     1.08    18653     0.06     0.06  set_ch_reverb
  4.60     18.20     0.99    33373     0.03     0.04  mix_single_signal
      ... 以下略 ...

rs_bidir, rs_loop が主要な負荷であることが正しい姿であるとするなら、 A300 で do_ch_reverb, set_ch_reverb, apply_envelope_to_amp の 3 関数が重いのは異常だ。 とくに do_ch_reverb, set_ch_reverb の二つで変換時間の 82% を消費しているが、 これは、この二つがもし (PC でのように) 10% 程度に収まるなら、 A300 の変換時間は 1/3 に縮まることを意味する。... これでもまだ重い (演奏時間の 4 倍ほどになる計算だ) が、 それくらいになればサンプリングレートを減らすなりして対応可能だろう (てゆーか、対応可能であってくれなければ困る)。

set_ch_reverb

くだんの set_ch_reverb だが、こんな関数:
#define REV_INP_LEV 0.55
void set_ch_reverb(register int32 *sbuffer, int32 n, int level)
{
    register int32  i;
    FLOAT_T send_level = (FLOAT_T)level * (REV_INP_LEV/127.0);
    for(i = 0; i < n; i++) {
        direct_buffer[i] += sbuffer[i];
        effect_buffer[i] += sbuffer[i] * send_level;
    }
}
これがむちゃくちゃ重いってんだから、浮動小数点の計算が重いとしか考えよーがない。 つーか、こんな計算に FLOAT_T (= double) を使うんだから、 すっかり FPU 前提のリソースに慣れてしまってるな。

こいつの内部計算は FLOAT_T だが、 引数と結果 (direct_buffer, effect_buffer) は整数なので、整数化は容易:

#define REV_INP_LEV 0.55
void set_ch_reverb(register int32 *sbuffer, int32 n, int level)
{
    register int32  i;
    int32 send_level = level / (127.0 / REV_INP_LEV);
    for(i = 0; i < n; i++) {
        direct_buffer[i] += sbuffer[i];
        effect_buffer[i] += sbuffer[i] * send_level;
    }
}
send_level の値域的に REV_INP_LEV = 0.548 ... くらいの計算をしてることに相当する (うろおぼえ)。

do_ch_reverb のほうは長いので略。こちらも 引数と結果は整数で、浮動小数点演算はほぼ関数内部に閉じた問題なので固定小数点化は 各変数の値域に注意するだけでいい。おおむね整数部 29bit、小数部 3bit にしたんだったかな。

apply_envelope_to_amp は global なデータを触ってるので、こいつの固定小数点化は あっちこっちに影響がでる。しかも値域が色々で、変数名の互換性ゼロ。 ともあれ 3 つとも固定小数点演算化して prof とると:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 28.26     59.14    59.14    42408     1.39     1.39  rs_bidir
 20.86    102.79    43.65  1953529     0.02     0.02  apply_envelope_to_amp
  8.36    120.29    17.50    42061     0.42     0.42  rs_loop
  6.01    132.87    12.58    27385     0.46     1.40  mix_mystery_signal
  5.72    144.84    11.97     3604     3.32     3.32  do_ch_reverb
  5.43    156.20    11.36    11879     0.96     1.63  rs_vib_loop
  4.76    166.16     9.96    15845     0.63     0.63  set_ch_reverb
     ... 以下略 ...
これで変換時間は演奏時間の 2 倍強。 あとは SDL の最適化の時と同じく (つーか、順番的にはこちらが先) LOOKUP_SINE などの調整で サンプリングレート 22.05 kHz 時に演奏時間の 5 割に追いこんだ。

なお、サンプリングレート 44.1kHz 時だと変換時間は演奏時間の9 割に達する。 なんとかリアルタイム演奏可能にみえるが、あくまで平均レートでの話であって 瞬間的に間に合わないことがある。 44.1kHz レートで演奏させるなら十分に大きいバッファを用意しなければならない。

SDL-mixer の timidity

ところで SDL-mixer 内の timidity は別に整数化とかされてるわけではない。 ではなぜ計算が (重いにしても) なんとか間に合うか? ... いや、実はとうてい間に合ってない状況だったみたいなんだが、 ともかく計算時間が演奏時間の 12 倍とかいった目茶苦茶な状態ではなかった。

理由は単純だった。timidity++ で計算時間の 80% 以上を占めた do_ch_reverb, set_ch_reverb の 2 関数が SDL-mixer の timidity には存在しない!

... そーか、かつて(SDL-mixer のは timidity 0.2 由来) は timidity が reverb をサポートしてない時代もあったのだな ... 当然か。
ためしに Timidity++ でも do_ch_reverb, set_reverb を外して演奏させる:

# timidity -Breverb=0 piano0.mid 
と、かなり軽くなった。

User Interface

いまんとこ
% ./configure  --without-x --enable-audio=oss --disable-dynamic --disable-gtktest \
     --enable-ncurses --enable-network --host=arm-linux --prefix=/home/QtPalmtop
で make している。つまり、UI としては ncurses のものだけ入っている。 S-lang I/F くらいはすぐに入れられるけど、なんか無駄な抵抗という気がするので入れてない。 qte 対応くらいは考えてたが、 qte アプリの立ち上がりのあまりの遅さと、新 ROM で qpe が quickexec 対応されるらしいという噂を聞いて、とりあえず Qt I/F は新 ROM 待ち。

スクリーンショット

ともかく。こんな感じ (curses interface)。
Timidity

パッケージ

元ソースは Debian woody の timidity 2.10.4-2.2 だが、 素の timidity 2.10.4 に対してもパッチは当たると思う。

なお、timidity++ 本来のデフォルトのレートは 32kHz だが、このレートは A300 はサポートしてないうえにトラブルを起こす。 上のバイナリではデフォルトレートは 22.05kHz になっている。

timidity.cfg

Configuration file は /etc 直下のもの (/etc/timidity.cfg) を参照する。

ogapee さんのところtimidity-patch_1.0.1-sd_arm.ipk を使う場合で言えば、 MIDIパッチファイル自体は /mnt/card/QtPalmtop/etc/timidity/ 下に置かれ、 /home/QtPalmtop/etc/timidity.cfg から /home/QtPalmtop/etc/timidity/ として参照されることになっているはずだ。 この timidity.cfg/etc/ にコピーしておく。

Debian の timidity-patches を使う場合は /usr/share/timidity/ 下に置かれ、 /etc/timidity.cfg から参照される。こちらはそのまま使える。

ちなみに。 Debian のが展開後 12MB, ogapee さん家のが 18MB, 自分とこの PC で使ってる eawpats12 に至っては 38MB と とてつもなくでかい。

手元の A300 ではソフト工房乾から Timidi95 の 3MB音色ファイル + gm差分 を使っている。こちらはあわせて 5MB ほどだ。 3MB の標準ファイルだけではさすがに品質がちと悲しいが、5MB のほうはそこそこである。 Debian のを使うくらいなら ogapee さん家の 18MB のほうが遥かに良いし、 できれば timidity のデファクトスタンダード化してしまった eawpats12 のがいいにきまってるが、 ... さすがにそんなでかいのを SD に入れるくらいなら midi なんて使わず PC で mp3 化してから SD にもってくるってば。

追記 (Dec. 30)

2.11.2 移行とともに、config file を /etc/timidity.cfg, /home/QtPalmtop/etc/timidity.cfg を この順に参照するように変更。 これで、たいていの音色ファイルで、手で timidity.cfg を触ることなく動くはずだ。

また、バッファオプションを "-B 20,12" をデフォルトに ── つまり 20 x 4096sample/sec のバッファをカーネルにセットするようにした。 オプションなしで動かしても音が切れることはないと思う。

追記、その 2

あう、2.10 → 2.11 で 3 割がた重くなった ...。system time がハネあがったから、 またどっか浮動小数点演算(kernel 内の emulator なので time 的には system 側に属す) がネックになってるらしい。

追記 (Jul, 13)

k2 → k3 での変更点は以下。
% diff timidity.h~  timidity.h   
163c172
< #define CSPLINE_INTERPOLATION
---
> /* #define CSPLINE_INTERPOLATION */
184c193
< #define SMOOTH_MIXING
---
> /* #define SMOOTH_MIXING */

デフォルト出力はやっぱり 22kHz になっている。C7x0 では 44kHz を指定 (-s 44100) したほうがおそらく軽い。

なお、prof 出力は↓な感じ。

 time   seconds   seconds    calls  ms/call  ms/call  name    
 53.85    238.92   238.92  8867384     0.03     0.03  apply_envelope_to_amp
 15.16    306.17    67.25    19203     3.50     3.50  do_ch_reverb
  9.49    348.28    42.11   111978     0.38     1.48  mix_mystery_signal
  7.45    381.33    33.05   112021     0.30     1.40  mix_single_signal
  6.25    409.04    27.71    38400     0.72     0.72  set_ch_reverb
  2.68    420.91    11.87     4467     2.66     2.66  s32tos16
       ...
軽くしたはずの apply_envelope_to_amp が重い。なんでやねん。

追記 (Jul, 14)

timidity 2.11.2 において変換時間の 20% を消費していた compute_mix_smoothing() を固定小数点化(つーても trivial な 1 行である ...) して SMOOTH_MIXING を #define に戻す。 また、あまりにもアレなバグみっけたので以前のものをディスコンに。

これで SL-A300 の 48kHz 時で CPU usage 80% 弱、 SL-C750 の 48kHz 時で CPU usage 30% 弱、22kHz で 20% ほど ── 既出だがなるほど C750 の 22kHz では音が切れることがあるな。カーネル内バッファの使い方が下手?

つーとこで、ようやく使ってた timidity 2.10 を 2.11 のに移行(今頃かい)。

追記 (Jul, 17)

C7x0 向けにデフォルトを 44.1kHz にしただけのバイナリ。


[日記へ] [目次へ]