サウンドプログラミング入門をMacでやる。 [プログラミング]
この本「サウンドプログラミング入門――音響合成の基本とC言語による実装」は、waveフォーマットを基本に音の作成を計算で出していく本です。完全に理解するには高校までの数学が必要です。でも、分からなくても全然大丈夫。数式がスゴく気になるけどね、パラメータを変えるだけだから。それとWindows上でBorland c++ compilerでソースをコンパイルして学習していく本です。それをMacでやるってだけ。でも、これがちょっと面倒。
普通の人は、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にコピーするだけ。コピーするだけだけど、ファイル自体をコピーするんじゃなくて、ヘッダの中身を読んで、必要な情報を構造体に格納するところまでやってる。
このファイルの最後まで、元のa.wavと同じ。だから、ヘッダ部分はきちんと読み取れているという事だ。だけど実際の音の実データ部分は読込めてない。
gdbを使おうと思ったけど、思いのほかに面倒だったので、Xcodeでコマンドラインアプリケーションとしてプロジェクトを作ってあげて、Xcode上でデバッグしてみた。まぁGDBとかのGUIラッパーなんだろうけど、コマンドラインでやるよりか楽できるし。実行ファイルをソースのあるディレクトリに変更して、a.wavを読込めるようにしておく。そうじゃないとXcodeが作る変なディレクトリで実行しちゃうから、ファイルのフルパス指定とか面倒な事になってしまうので。細かいところを知りたい人は言ってください。追記します。
ヘッダ部分の読み込みを見たら、こんな感じになっていました。
わかった事は、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とか使ってたらヤヴァいのかも。でも始めに0クリアしとけば大丈夫(なはず)。
サウンドプログラミング入門――音響合成の基本と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 | 0 | 88200 | |
実メモリの値 | 16 | 0 | 1 | 1 | 44100 | 0 | 88200 | |
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