忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

OpenAL Alutのエラー・ハンドリング [C#]

ALUTでのエラーの扱いについてメモしておきます


ALUTにはエラー関係の関数が2つあるようです。
1つはエラーの定数を取得する関数で、もう1つはその定数から説明をあらわす文字列を取得する関数です。

alutGetError
alutの関数は失敗したときにNULLを返し、どこかにあるグローバル変数にエラー定数をセットするそうです。
で、どのようなエラー定数がセットされたのかを調べる関数がこのalutGetErrorです。

ALenum alutGetError();

戻り値はエラーを表す定数で、以下のものがあるそうです:
ドキュメントを適当に訳しました(本当に適当ですよ)

定数名 説明
ALUT_ERROR_NO_ERROR エラーはありません。 0
ALUT_ERROR_OUT_OF_MEMORY メモリが足りません。 0x200
ALUT_ERROR_INVALID_ENUM Alutの関数に不正な定数が与えられました。 0x201
ALUT_ERROR_INVALID_VALUE Alutの関数に不正な値が与えられました。 0x202
ALUT_ERROR_INVALID_OPERATION 行った操作は現在のALUTの状態では不正です。 0x203
ALUT_ERROR_NO_CURRENT_CONTEXT 現在のコンテキストがセットされていません。(alutInitを呼べば自動的にコンテキストはセットされるのでこれはあまり気にしなくていいでしょう) 0x204
ALUT_ERROR_AL_ERROR_ON_ENTRY ALUT関数へのエントリーにすでにALエラーがあります。
(なんのこっちゃ)
0x205
ALUT_ERROR_ALC_ERROR_ON_ENTRY ALUT関数へのエントリーにすでにALCえらーがあります。 0x206
ALUT_ERROR_OPEN_DEVICE ALCデバイスを開く上でエラーがありました。 0x207
ALUT_ERROR_CLOSE_DEVICE ALCデバイスを閉じる上でエラーがありました。 0x208
ALUT_ERROR_CREATE_CONTEXT Contextを生成する上でエラーがありました。 0x209
ALUT_ERROR_MAKE_CONTEXT_CURRENT 現在のContextを変更できませんでした。 0x20A
ALUT_ERROR_DESTROY_CONTEXT Contextを破壊する上でエラーがありました。 0x20B
ALUT_ERROR_GEN_BUFFERS AL Bufferを生成するのにエラーがありました。 0x20C
ALUT_ERROR_BUFFER_DATA バッファ・データをALに送る途中でエラーがありました。 0x20D
ALUT_ERROR_IO_ERROR I/Oエラー。より詳しくはerrnoを。 0x20E
ALUT_ERROR_UNSUPPORTED_FILE_TYPE サポートされていないファイルタイプです。 0x20F
ALUT_ERROR_UNSUPPORTED_FILE_SUBTYPE ファイルタイプは大丈夫ですが、サポートされていないモードです。 0x210
ALUT_ERROR_CORRUPT_OR_TRUNCATED_DATA サウンドデータに間違いがあるか、欠けています 0x211


alutGetErrorString
alutGetErrorで返された定数はそのままでは人間には理解しがたいため、これをわかりやすい文字列に変換する必要があります。
数値の定数から文字列への変換をやってくれるのが、alutGetErrorString関数です。

const char *alutGetErrorString ( ALenum error);

引数はエラーを表す定数、戻り値はもちろん0で終わる文字列へのポインタです。

using System.Runtime.InteropServices;

enum AlutError
{
    ALUT_ERROR_NO_ERROR = 0,
    ALUT_ERROR_OUT_OF_MEMORY = 0x200,
    ALUT_ERROR_INVALID_ENUM,
    ALUT_ERROR_INVALID_VALUE,
    ALUT_ERROR_INVALID_OPERATION,
    ALUT_ERROR_NO_CURRENT_CONTEXT,
    ALUT_ERROR_AL_ERROR_ON_ENTRY,
    ALUT_ERROR_ALC_ERROR_ON_ENTRY,
    ALUT_ERROR_OPEN_DEVICE,
    ALUT_ERROR_CLOSE_DEVICE,
    ALUT_ERROR_CREATE_CONTEXT,
    ALUT_ERROR_MAKE_CONTEXT_CURRENT,
    ALUT_ERROR_DESTROY_CONTEXT,
    ALUT_ERROR_GEN_BUFFERS,
    ALUT_ERROR_BUFFER_DATA,
    ALUT_ERROR_IO_ERROR,
    ALUT_ERROR_UNSUPPORTED_FILE_TYPE,
    ALUT_ERROR_UNSUPPORTED_FILE_SUBTYPE,
    ALUT_ERROR_CORRUPT_OR_TRUNCATED_DATA
}

class Program
{
    [DllImport("alut")]
    static extern string alutGetErrorString(AlutError error);

    static void Main()
    {
        foreach (AlutError error in System.Enum.GetValues(typeof(AlutError)))
        {
            System.Console.WriteLine(error + " : " + alutGetErrorString(error));
        }

        System.Console.ReadLine();
    }
}


このサンプルを実行すると、定数とその説明をコンソールに表示します。

拍手[0回]

PR

OpenAL 音源とリスナーの位置の変更 [C#]

OpenALではSourceオブジェクト(音源)の3次元空間中の位置を変更して、音の大きさを変えることが出来ます。
リスナー(聞き手)に近づけば音は大きくなりますし、遠ざかれば小さくなります。
遠くの音は小さく、近くの音は大きく聞こえるものです。

デフォルトでは、SourceオブジェクトもListener(インスタンス(?)は一つだけでシングルトンの扱いです。SourceやBufferのような名前(Name)は持っていません。)も位置は{0, 0, 0}だそうです。
これを、OpenALの関数を使って変えることで音の強さによる遠近感が出せるようです。

alSource3f
Sourceオブジェクトの位置を変えるのにはalSource3f関数が使えます。(「使えます」というのは、別の関数、たとえばalSourcefv, alSource3i, alSourceivを使って位置をセットすることも出来るからです。しかし、alSource3fがもっとも使いやすさと柔軟性の面でバランスが取れています。ポインタを使わなくていいですし、位置を表すのにintではなくfloatを使うことが出来るからです。)

void alSource3f(
        int sourceName,
        int propertyType,
        float x, float y, float z
        );


引数はお決まりです。
sourceNameは値をセットするSourceオブジェクトの名前。
propertyTypeはセットする属性を表す定数(ここではAL_POSITION = 0x1004)。
x, y, zはそれぞれ位置をあらわします。

この関数を使うと、位置だけではなく速度(AL_VELOCITY = 0x1006)もセットできます。速度からはドップラー効果を再現できます。

using System;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    //OpenAL Utility Toolkit (Alut)の関数をインポート
    [DllImport("alut.dll")]
    static extern bool alutInit(IntPtr argcp, string[] argv);

    [DllImport("alut.dll")]
    static extern bool alutExit();

    [DllImport("alut.dll")]
    static extern int alutCreateBufferHelloWorld();



    //OpenALの関数をインポート
    [DllImport("OpenAL32.dll")]
    static extern void alGenSources(int resultSize, int[] result);

    [DllImport("OpenAL32.dll")]
    static extern void alDeleteSources(int nameCount, int[] sourceNames);

    [DllImport("OpenAL32.dll")]
    static extern void alSourcei(int sourceName, int propertyType, int value);
    const int AL_BUFFER = 0x1009;

    [DllImport("OpenAL32.dll")]
    static extern void alSourcePlay(int sourceName);

    [DllImport("OpenAL32.dll")]
    static extern void alSource3f(
        int sourceName, int propertyType,
        float v1, float v2, float v3
        );
    const int AL_POSITION = 0x1004;


    static void Main()
    {
        alutInit(IntPtr.Zero, null);

        int[] sources = new int[1];
        alGenSources(sources.Length, sources);
        alSourcei(sources[0], AL_BUFFER, alutCreateBufferHelloWorld());

        for (int i = 0; i < 4; i++)
        {
            alSource3f(sources[0], AL_POSITION, 0, 0, -i);
            alSourcePlay(sources[0]);
            Thread.Sleep(1500);
        }

        alDeleteSources(sources.Length, sources);

        alutExit();
    }
}


このサンプルでは、音源をどんどん聞き手から離していっています。
ですから、"Hello, World!" "Hello, World!" "Hello, World!", "Hello, World!"と小さくなるわけです。


alListener3f
ここまでは音源を表すSourceオブジェクトの位置を変えていましたが、リスナー(聞き手)の位置を変えることも出来ます。
聞き手が音から離れていっても音は小さくなるでしょうからね。

リスナーの位置を変えるには、alListener3f関数が使えます。(やはりalListener3ialListeneriv, alListenerfvみたいな別の関数も使えますが、位置のセットにはこれが一番適しています)

void alListener3f(int propertyType, float x, float y, float z);

この関数はalSource3fにとてもよく似ていますが、ただひとつ、第一引数にオブジェクトを表す名前がありません。
Listenerはシングルトンなので名前は必要ないのです。

その他の引数は全く同じです。
propertyTypeは設定する属性を表す定数(位置をセットしたいのならSourceのときと同じようにAL_POSITION)、
x, y, zは位置の成分です。


using System;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    //OpenAL Utility Toolkit (Alut)の関数をインポート
    [DllImport("alut.dll")]
    static extern bool alutInit(IntPtr argcp, string[] argv);

    [DllImport("alut.dll")]
    static extern bool alutExit();

    [DllImport("alut.dll")]
    static extern int alutCreateBufferHelloWorld();



    //OpenALの関数をインポート
    [DllImport("OpenAL32.dll")]
    static extern void alGenSources(int resultSize, int[] result);

    [DllImport("OpenAL32.dll")]
    static extern void alDeleteSources(int nameCount, int[] sourceNames);

    [DllImport("OpenAL32.dll")]
    static extern void alSourcei(int sourceName, int propertyType, int value);
    const int AL_BUFFER = 0x1009;

    [DllImport("OpenAL32.dll")]
    static extern void alSourcePlay(int sourceName);

    const int AL_POSITION = 0x1004;

    [DllImport("OpenAL32.dll")]
    static extern void alListener3f(int propertyType, float x, float y, float z);


    static void Main()
    {
        alutInit(IntPtr.Zero, null);

        int[] sources = new int[1];
        alGenSources(sources.Length, sources);
        alSourcei(sources[0], AL_BUFFER, alutCreateBufferHelloWorld());

        for (int i = 0; i < 4; i++)
        {
            alListener3f(AL_POSITION, 0, 0, i);
            alSourcePlay(sources[0]);
            Thread.Sleep(1500);
        }

        alDeleteSources(sources.Length, sources);

        alutExit();
    }
}


実行結果は先ほどと同じになるはずです。
ただ違うのは、このサンプルでは音源ではなく聞き手のほうを動かしているということです。















拍手[0回]


OpenALで音を生成 [C#]

プログラムから音の波のデータを作って、それを再生してみます。
(実はこれをやりたくてOpenALをはじめたんです!)

音は波ですから、波の高さをByteか何かの配列として表し、それを再生することが出来るわけです。

OpenALでは、8bitと16bitのデータの配列を使えるようなので、使うとしたらSystem.ByteかSysytem.UInt16の配列でしょうね。

alGenBuffers
前回と違うのは、音のデータを表すオブジェクト、Bufferをまず自分で作らなければいけないということです。(そしてそのBufferオブジェクトに波のデータ、byte[]をセットします。) Bufferオブジェクトを作るには、alGenBuffers関数を使います。

void alGenBuffers (ALsizei n, ALuint *bufferNames);

これはSourceオブジェクトを生成するalGenSources関数にとても似ています。実際、使い方は同じです。この関数は一度に複数のBufferオブジェクトを作ることが出来ます。引数のnは生成するBufferオブジェクトの数で、bufferNamesに生成されたBufferオブジェクト(の名前:Name)が格納されます。

alBufferData
こうして生成したBufferオブジェクトに音の波の高さを表すbyteかUInt16の配列をセットするには、alBufferData関数を使います。引数がたくさんありますが、C#でラッパークラスを作れば2つまで絞り込めます。

void alBufferData(
        ALuint bufferName,
        ALenum format,
        const ALvoid *data
        ALsizei size,
        ALsizei frequency
        );


この関数はいろいろなタイプの波のデータをセットすることが出来ます(全部で4つ)。モノラルかステレオかという選択と、8-bitか16-bitかという選択で、2 x 2 = 4とおりです。

  モノラル ステレオ
8-bit AL_FORMAT_MONO8
(0x1100)
AL_FORMAT_STEREO8
(0x1102)
16-bit AL_FORMAT_MONO16
(0x1101)
AL_FORMAT_STEREO16
(0x1103)

このフラグを、2つ目の引数formatに入れます。

最初の引数bufferNameはもちろんデータをセットするBufferオブジェクトの名前です。C#でラッパークラスを作ったなら、間違いなく(引数ではなく)メンバ変数になるでしょうね。

3番目の引数dataは音の波データです。具体的にこれがどのようなデータであるかは、引数formatによります。8bitを選んだのならbyte[]になるでしょうし、16bitを選んだらUInt16[]になるでしょう。ステレオを指定した場合は、左のチャンネルが先に来て、後に右のチャンネルが来ます(わかりにくい表現ですが、このようなことがドキュメントに書いてあります)。

4番目の引数sizeはdataのサイズですが、気をつけなければいけないのはこれはbyte単位だということです。配列の長さではありません。

5番目の引数frequency(日本語では周波数)は、波データの密度のようですね。1秒間にいくつdataの中の要素を使うかを表しているようです(ドキュメントには説明がありません・・・)。たとえばこれが20000なら一秒間に2万個の要素を使って音を出します。dataの長さが10000なら0.5秒で再生し終わるでしょう。(たぶん)

alDeleteBuffers
使い終わったBufferオブジェクトを閉じるにはalDeleteBuffers関数を使います。

void alDeleteBuffers(ALsizei n, const ALuint *bufferNames);

これを呼んだら、そのBufferの名前は使えなくなります。

nは配列bufferNamesの長さで、bufferNamesはデリートするBufferオブジェクトが入っている配列です。

using System; using System.Runtime.InteropServices; using System.Threading;  class Program {     //OpenAL Utility Toolkit (Alut)の関数をインポート     [DllImport("alut.dll")]     static extern bool alutInit(IntPtr argcp, string[] argv);      [DllImport("alut.dll")]     static extern bool alutExit();        //OpenALの関数をインポート     //Source関連     [DllImport("OpenAL32.dll")]     static extern void alGenSources(int resultSize, int[] result);      [DllImport("OpenAL32.dll")]     static extern void alSourcei(int sourceName, int propertyType, int value);     const int AL_BUFFER = 0x1009;      [DllImport("OpenAL32.dll")]     static extern void alSourcePlay(int sourceName);      [DllImport("OpenAL32.dll")]     static extern void alDeleteSources(int nameCount, int[] sourceNames);      //Buffer関連     [DllImport("OpenAL32.dll")]     static extern void alGenBuffers(int resultSize, int[] result);      [DllImport("OpenAL32.dll")]     static extern void alBufferData(         int bufferName,         int soundDataFormat,         byte[] data,         int size,         int samplePerSecond         );     const int AL_FORMAT_MONO8 = 0x1100;      [DllImport("OpenAL32.dll")]     static extern void alDeleteBuffers(int nameCount, int[] bufferNames);        static void Main()     {         alutInit(IntPtr.Zero, null);          //Bufferオブジェクト(音のデータ)の初期化         int[] buffers = new int[1];         alGenBuffers(buffers.Length, buffers);         byte[] waveData = createAWave(22000);         alBufferData(buffers[0], AL_FORMAT_MONO8, waveData, waveData.Length, 22000);           //Sourceオブジェクト(音源)の初期化         int[] sources = new int[1];         alGenSources(sources.Length, sources);         alSourcei(sources[0], AL_BUFFER, buffers[0]);          //再生         alSourcePlay(sources[0]);         Thread.Sleep(3000);           alDeleteBuffers(buffers.Length, buffers);         alDeleteSources(sources.Length, sources);          alutExit();     }      //3秒間の"ラ"の音を作る     //"ラ"の周波数:440ヘルツ     static byte[] createAWave(int samplePerSecond)     {         byte[] soundData = new byte[samplePerSecond * 3];          for (int i = 0; i < soundData.Length; i++)         {             soundData[i] = (byte)(                 255 * Math.Sin((2 * Math.PI * 440) * i / samplePerSecond)                 );         }          return soundData;     } }


これを実行すると、3秒間周波数440の”ラ”の音が聞こえるはずです。
波の形は単純なサイン・カーブなので「プー」という無機質な音です。
これに音色を加えるには、(周波数はそのままで)波の形を変えてやればいいそうです。


※訂正:たぶんこれはサイン波ではありません。サイン波を生み出す正しい方法はこちら









 

拍手[0回]


「OpenALでHelloWorld」の補足

前回の補足として、OpenALの関数の解説をしておきますね。

ALUTの関数

(alut.dll)

alutInit
OpenALをより簡単に使うためのライブラリとしてALUT(OpenAL Utility Library)というのがあるのですが、それを初期化するのにalutInit関数というのを使います。

ALboolean alutInit ( int *argcp, char **argv);

これを呼ぶとALUTライブラリを初期化してALUTが使えるようになります。

引数は何を意味しているのかというと……コマンドラインの引数のようですね。
最初のargcpはたぶんargvの数を表す変数へのポインタで、argvはコマンドラインの引数そのもののようです。

ドキュメントには用途が詳しく書いていないのでどう使えばいいのかよくわかりませんが、この両方にNULLを渡してもかまわないそうです。(何のためにあるんだろ・・・)

戻り値は成功したならtrue、失敗したならfalseです。

alutExit
また、ALUTライブラリを使って音を出した後、アプリケーション終了前に後始末としてalutExit関数を呼ばなければいけません。(もちろん、この関数を呼ばずにアプリケーションを終了して、クリーンアップはOSに任せるというのも一つの手です)

ALboolean alutExit();

この関数を呼んだあと、またalutInitを使ってALUT初期化するのもアリだそうです。

戻り値は成功したならtrue、失敗したならfalseです。

alutCreateBufferHelloWorld
ALUTはまた、初学者のために"Hello, World!"という音声データをあらかじめ用意してくれています。

この音声データを取得するにはalutCreateBufferHelloWorld関数を使います。

ALuint alutCreateBufferHelloWorld();

Bufferというのは音声データを表すオブジェクトです。これを音源を表すSourceオブジェクトにセットし、再生することでスピーカーから音を出します。Bufferは純粋な音声データだけであるのに対し、Sourceは音源の位置、速度、向きなどから物理法則にしたがって音声データを加工する役割を持っているようです。Sourceが遠くにあれば音は小さくなりますし、こちらへ近づいてきているのならドップラー効果で音が高くなります。

戻り値は"Hello, World!"という音声データのBufferを表すハンドルのようなもの(整数)です。(ただし、OpenALではこれをハンドルではなくNameと表現するようですね。今後はNameという用語を使うことにします)


 

OpenALの関数

(OpenAL32.dll)

alGenSources
音源の物理的性質(?)を表すSourceオブジェクトを作るにはalGenSources関数を使います。

void alGenSources(ALsizei n, ALuint *srcNames);

きれいなオブジェクト指向になれたプログラマには気持ち悪いかもしれません。
この関数は複数のSourceオブジェクトを一度に作れるようになっています。

引数のnは作るSourceオブジェクトの数です。つまり、srcNamesのサイズを意味するのです。
srcNamesはこの関数が結果を出力する整数の配列です。作られたSourceの名前(Name)はこの配列の中に書き込まれます。

alSourcei
こうして作ったSourceオブジェクトに、音声データを表すBufferをセットして再生できるようにするにはalSourcei関数を使います。

void alSourcei(ALuint sourceName, ALenum paramName, int value);

厄介なことに、この関数には色々なはたらきがあります。Bufferをセットすることはもちろん出来ますが、別の属性をセットすることも出来るのです。

どんな属性をセットするかは、paramName引数で決めます。Bufferをセットする場合ならこの値はAL_BUFFER(=0x1009)です。
sourceNameは主体となるSourceの名前(Name)で、valueはもちろん、セットする値です。BufferをセットしたいのならBufferの名前(Name)です。

この関数には似たような仲間にalSourcefやalSourcefvなどがあります。最後に付くiとかfとかいう文字はセットする属性の型を表します。iならint、fならfloatといったぐあいです。また、一番最後にvが付く場合がありますが、その場合には属性の型が配列になるようです。
ドキュメントには以下のように書かれています:

void alSource {n} {if} (ALuint sourceName, ALenum paramName, T value);
void alSource {n} {if}v (ALuint sourceName, ALenum paramName, T *value);


alSourcePlay
Sourceを再生するには、alSourcePlay関数を使います。

void alSourcePlay (ALuint sourceName);

いうまでもなく、sourceNameは再生するSourceの名前(Name)です。

alDeleteSources
なお、alGenSources関数で作ったSourceをデリートするにはalDeleteSources関数を使います。

void alDeleteSources(ALsizei n, ALuint *sources);

再生中にデリートすると、自動的に再生がストップします。











拍手[0回]


OpenALでHello World [C#]

OpenAL
突然ですがOpenALなるものでHello Worldをしてみました。
OpenALというのはOpenGLのオーディオ版のようなものなのだそうです。
(Open Audio Library)

OpenGLがグラフィックス・ライブラリ(GL)なのにたいし、OpenALはオーディオ・ライブラリ(AL)というわけです。

ではなぜこのようなことをするのかというと、4gamer.netのこの記事を読んだからです。
これを読むと、なんとなくOpenALが出来たほうがいいような気がしてきます。

もちろん、無駄骨になる可能性もあります。
Microsoft.Xna.Framework.Audioがあるから知る必要は無いのではないかという気もします。
でもこっちの方が柔軟そうですし、好奇心がこれをやりたいと言っているのです。

というわけで、C#のInteropサービスを利用してOpenALを使ってみることにしました。
最初はC++/CLIでやろうと思っていたのですが、挫折しました(笑)。
全部C#で行きます。

インストール
OpenALのインストールは、まずここの次の2つをダウンロードします。

OpenAL 1.1 Installer for Windows
freealut Binary ZIP

上のほうは実行、下は展開してlibの中にあるalut.dllをC#で作る実行ファイルと一緒に置いてやります(Visual Studioを使う場合は単にプロジェクトに追加+右クリック > プロパティ > でアウトプットディレクトリにコピーするように設定すればいいでしょう)

Hello World

では単純にスピーカーから”Hello World”と鳴らすプログラムを書いてみましょう。

using System;
using System.Runtime.InteropServices;

class Program
{
    //OpenAL Utility Toolkit (Alut) の関数をインポート
    const string AlutDll = "alut.dll";

    [DllImport(AlutDll)]
    static extern bool alutInit(IntPtr argc, string[] argv);

    [DllImport(AlutDll)]
    static extern bool alutExit();

    [DllImport(AlutDll)]
    static extern int alutCreateBufferHelloWorld();


    //OpenALの関数をインポート
    const string OpenALDll = "OpenAL32.dll";


    [DllImport(OpenALDll)]
    static extern void alSourcePlay(int sourceHandle);

    [DllImport(OpenALDll)]
    static extern void alGenSources(int resultSize, int[] result);

    const int AL_BUFFER = 0x1009;
    [DllImport(OpenALDll)]
    static extern void alSourcei(int sourceHandle, int propertyType, int value);


    static void Main()
    {
        alutInit(IntPtr.Zero, null);

        //"Hello World!"と喋る音声データを作る
        int bufferHandle = alutCreateBufferHelloWorld();

        //音源を作り、音声データをセットする
        int[] sources = new int[1];
        alGenSources(sources.Length, sources);
        alSourcei(sources[0], AL_BUFFER, bufferHandle);


        //音源から音を鳴らす
        alSourcePlay(sources[0]);
        System.Threading.Thread.Sleep(1300);


        alutExit();
    }
}


これを実行すると、"ヘローワール ―プッ"という音がスピーカーから出てくるはずです。
(どうも音が小さいので耳を澄ましてみてください)



 

拍手[0回]


        
  • 1
  • 2