SSブログ

サウンドプログラミング入門をMacでやる。 [プログラミング]

この本「サウンドプログラミング入門――音響合成の基本とC言語による実装」は、waveフォーマットを基本に音の作成を計算で出していく本です。完全に理解するには高校までの数学が必要です。でも、分からなくても全然大丈夫。数式がスゴく気になるけどね、パラメータを変えるだけだから。それとWindows上でBorland c++ compilerでソースをコンパイルして学習していく本です。それをMacでやるってだけ。でも、これがちょっと面倒。

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

  • 作者: 青木 直史
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/02/01
  • メディア: 単行本(ソフトカバー)



普通の人は、Windowsにbcc32.exeを入れてやっちゃった方がいいです。
Macでやるというより、gccでlibcを使うっていう方が汎用性があるかな。まぁ最終的には同じ事なんだろうけどね。


 
結局、リトルエンディアンが面倒なことを引き起こしている。Intelのx86はずっとlittle endianなのだけれど、bcc32とgccのfread()がちょっと違うみたいなのだ。だからMacがx86になっていようと関係なく互換性がない。

本を買えばわかると思うけど、サンプルソースコードがダウンロードできる。それをbcc32でコンパイルするぶんには全然問題ないんだけど、gccではちょっとダメダメだ。コンパイルすると文字はバイト単位で読み込めるんだけど、数値はfread()では、bcc32で読み込めるような形でコンパイルされない。ただgccが正常な動作をしているのは分かる。fread(), fwrite()の読み込み、書き込みでファイルのヘッダ部分がきちんと再生されるからです。

chapter01のex1_1から使えません。なので、根本的にwave.hが汎用的にできていないってことですね。そもそものstdioのライブラリが違うってことでしょう。この先の例は、ex1_1のものなので、それを前提にしてください。

gccって言ってるのは、MacがLLVMでGCCのラッパーを使っている事と、Linuxのgccで同じ挙動が見かけられるからです。ヘッダの実データ部分の長さを読み取れないがために(長さがマイナスの値になってた)、音のデータが全部飛んじゃって、44Bytesとヘッダである長さと同じになっていた。odでバイナリの中身を見ても、長さが違うだけでヘッダが同一のものであることがわかりました。

結局、fread()が読み込めてないってことは確かです。でも、文字列とかは問題なく読み込みができている。書き込みも実データ以外は書き戻せている。結局、long型の数値をどうやって読むかの問題になってきます。読み込みに手を加えた場合は、書き込みの時も気にしないとダメですね、基本的な動作はlibCとかは、恐らく間違っていないんですから。結局、WindowsのC言語の方言ということでしょう。


一番始めのサンプルは、「ex1_1.c」でa.wavという単音のファイルを、b.wavにコピーするだけ。コピーするだけだけど、ファイル自体をコピーするんじゃなくて、ヘッダの中身を読んで、必要な情報を構造体に格納するところまでやってる。
$ od -x b.wav 
0000000      4952    4646    58ac    0001    4157    4556    6d66    2074
0000020      0010    0000    0001    0001    ac44    0000    5888    0001
0000040      0002    0010    6164    6174    5888    0001                
0000054

このファイルの最後まで、元のa.wavと同じ。だから、ヘッダ部分はきちんと読み取れているという事だ。だけど実際の音の実データ部分は読込めてない。

gdbを使おうと思ったけど、思いのほかに面倒だったので、Xcodeでコマンドラインアプリケーションとしてプロジェクトを作ってあげて、Xcode上でデバッグしてみた。まぁGDBとかのGUIラッパーなんだろうけど、コマンドラインでやるよりか楽できるし。実行ファイルをソースのあるディレクトリに変更して、a.wavを読込めるようにしておく。そうじゃないとXcodeが作る変なディレクトリで実行しちゃうから、ファイルのフルパス指定とか面倒な事になってしまうので。細かいところを知りたい人は言ってください。追記します。

ヘッダ部分の読み込みを見たら、こんな感じになっていました。
0000000 4952 4646 58ac 0001 4157 4556 6d66 2074
入ってほしいデータ "RIFF" 88236 "WAVE" "fmt "
実メモリの値 'I' 'R' 'F' 'F' 不定 'A' 'W' 'E' 'V' 'm' 'f' ' ' 't'
catの表示RIFF?XWAVEfmt


0000020 0010 0000 0001 0001 ac44 0000 5888 0001
入ってほしいデータ 16 0 1 1 44100 088200
実メモリの値 16 0 1 1 44100 088200
catの表示D?


0000040 0002 0010 6164 6174 5888 0001
入ってほしいデータ 2 16 "data" 88200
実メモリの値 2 16 'a' 'd' 'a' 't' 不定
catの表示?Xdata?X


わかった事は、fread()で4バイトを読んでいるとき、ダメだったり大丈夫だったりしている事。特にサイズを扱っているところが変。2バイトは問題なく読めていて、4バイトになるときちんと読めたり読めなかったり、意味不明である。gccでそんなメジャーなバグはないだろうなぁと思っていたんだけど、実際そうなのでした。

catの表示はそのまま貼ったので、文字列以外のところはあんまり意味がありません。基本的にcatじゃ示せないもんね。


最終的な問題を言ってしまうと、変数を初期化してなかったので、その中に入っているゴミデータが混入していた模様。Windowsだと変数を作ると、大体ゼロクリアしちゃうのね。それはVisual C++とかでも同じだったし、Borland C++ Compilerでも同じみたい。そもそものOSの仕様がそうなのかもね。まぁそこのところはコンパイラの設計思想なんだろう。

だけどgcc系だと、変数を作る時に特に0クリアはしない。まぁ面倒だから普通はしないよね。だから、全く意図しない不定値が入っちゃうのだ。そりゃゴミデータが入るよな。んで、fread()する時に、ポインタで代入する時に、読込めてはいるんだけど、特定ビットにゴミが残っちゃうらしい。マイナスになったりプラスになったりしてたので、恐らく上位ビットのいくつか。面倒なのでそこまで調べへんかった。

何にしても、読み取る時の変数を0で初期化してあげればいいだけなのでした。変数のゼロクリアってのも長短あって、作ったままの変数で割ると、0除算の例外が出てアプリごと落ちちゃったりする。例外をcatchすればいいだけなんだけど、特にWindowsで割り算をする時には気をつけた方がいい。いきなり落ちるバグとしては、この例外はありがちだから。

そんなこんなでgccでも使える事がわかったのでした。そりゃfread()やfwrite()みたいなメジャーな関数にバグはないよね。




今回の問題とは違うんだけど、リトルエンディアンって気持ち悪い。何で逆にするんだよって感じなのだけれど、そのまま通信でデータ流したりするのがしんどい。その関連で、上の図を見てもわかる通り、ASCII文字が2バイト単位で入れ替わっている。"WAVE"だったら、'A' 'W' 'E' 'V'みたいに。何か気持ち悪い。文字列全体でひっくり返すとかされるとまた厄介なのだけれど、何で2バイトなのかってのが気になる。

単なる予想なんだけど、2バイト文字対策なのかもなと思ったりはした。4バイトの中身が逆転しているのは、4バイト全部で一つの数なので想像はつくけど、文字列のバイトは文字列として認識されているにしても、1バイト単位なのが普通だ。でも、日本語とかは2バイト文字なので、ひとくくりとして扱うのかなぁと思った。UNICODEにしても基本2バイトで何とかするみたいだし(例外はあるらしいが良く知らん)。UTF-8とかはASCII文字は1バイトで示しているはずだから、どういう規格になっているのかなぁ。今は扱える文字列は大抵UTF-8に準拠しているような感じみたいだし。まぁいいや、必要になってからで。

何にしてもIntelのx86はx86_64になっても、リトルエンディアンなのでした。そのうちARMでしかC言語プログラミングした事ねーという新人類が登場しそうで色々恐いw。ARMってビッグエンディアンだったよね。あ〜どっちもイケるバイインディアンなのか。移植性の良さを考えると切り替えられた方がいいのかも。

http://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3#.E3.83.9F.E3.83.89.E3.83.AB.E3.82.A8.E3.83.B3.E3.83.87.E3.82.A3.E3.82.A2.E3.83.B3

でもx86はミドルエンディアンではないはず。文字列はそれっぽい感じだけど、ハードウェアのアーキテクチャ自体がそうなっているわけじゃないだろうし。GNUのgccというかLibCのソースで確かめてみればいいんだよね。とはいえ、MacだとLLVMになっているらしいから、libc使ってるかどうか知らん。色々知らない上でやってる事って多いなぁ。まぁこの世の中全部そうだけど。

人間的にはビッグエンディアンの方が明快だよね。設定ファイルがバイナリで特定ツールを使うよりも、テキストみたいな傾向? XMLってのもそれで流行ったのだろうし、JSONだって同じことだろう。結局、人間の手間を省いて、ある程度の負担はコンピュータへって方向性?


まぁ、これでgccでも出来るようになりました。全部確かめてないけど、初めのソースでwave.hを
long riff_chunk_size = 0;
long data_chunk_size = 0;

って最低すれば動くようになります。他のソースは知らない。ポインタを設定して読み込む部分で、longとか使ってたらヤヴァいのかも。でも始めに0クリアしとけば大丈夫(なはず)。

コメント(0) 
共通テーマ:

コメント 0