1

押されたボタンに応じて 4 つの異なるトーンを再生する非常に単純なプログラムがあります。複数のトーンまたは同じトーンを立て続けに再生すると、不快なクリック ノイズが発生することがわかりました。これらのクリックがオーディオ サンプルに存在しないことを確認しました。これは間違いなく、クリップを次々とすばやく再生することが原因です。

グーグルで調べた後、クリックはクリップ間のピッチの急激な変化によるものであると確信しています. 問題のあるオーディオからの再生の波形を見ると、次のクリップを開始する前に、クリップが最初に数分の 1 の間キャンセルされているように見えます。これが特に明白と思われるセクションを強調しました。

トーン間でクリック感のあるクリップの波形

これらのオーディオ クリックを紹介するクリップは、こちらからダウンロードすることもできます。

私のコードはとてもシンプルです。XInput を使用して、再生するトーンを決定する接続されたコントローラーから入力を読み取り、WinMM を使用して wav ファイルからサウンドを出力しています。これは D プログラミング言語で書かれていますが、できるだけ C に似せて混乱を避けるために、D 固有の機能を使用しないように変更しました。

SHORT keyPressed(int vkey)
{
    enum highBit { val = 0x8000 }

    return cast(SHORT)(GetKeyState(vkey) & highBit.val);
}

enum Button
{
    DPAD_UP    = 0x0001,
    DPAD_DOWN  = 0x0002,
    DPAD_LEFT  = 0x0004,
    DPAD_RIGHT = 0x0008,

    START = 0x0010,
    BACK  = 0x0020,

    LEFT_THUMB  = 0x0040,
    RIGHT_THUMB = 0x0080,

    LEFT_SHOULDER  = 0x0100,
    RIGHT_SHOULDER = 0x0200,

    A = 0x1000,
    B = 0x2000,
    X = 0x4000,
    Y = 0x8000,
}

struct XINPUT_GAMEPAD
{
    WORD  wButtons;
    BYTE  bLeftTrigger;
    BYTE  bRightTrigger;
    SHORT sThumbLX;
    SHORT sThumbLY;
    SHORT sThumbRX;
    SHORT sThumbRY;
}

struct XINPUT_STATE
{
    DWORD dwPacketNumber;
    XINPUT_GAMEPAD Gamepad;

    bool isPressed(int button)
    {
        return cast(bool)(Gamepad.wButtons & button);
    }
}

int main()
{
    HANDLE xinputDLL = initXinput();

    XINPUT_STATE oldState;
    XINPUT_STATE newState;

    while (!keyPressed(VK_ESCAPE))
    {
        oldState = newState;
        XInputGetState(0, &newState);

        enum flags { val = SND_ASYNC | SND_FILENAME | SND_NODEFAULT }

        if (newState.isPressed(Button.A) && !oldState.isPressed(Button.A))
        {
            PlaySoundA(toStringz("Piano.ff.A4.wav"), null, flags.val);
        }

        if (newState.isPressed(Button.B) && !oldState.isPressed(Button.B))
        {
            PlaySoundA(toStringz("Piano.ff.B4.wav"), null, flags.val);
        }

        if (newState.isPressed(Button.X) && !oldState.isPressed(Button.X))
        {
            PlaySoundA(toStringz("Piano.ff.C5.wav"), null, flags.val);
        }

        if (newState.isPressed(Button.Y) && !oldState.isPressed(Button.Y))
        {
            PlaySoundA(toStringz("Piano.ff.F4.wav"), null, flags.val);
        }
    }

    denitXinput(xinputDLL);

    return 0;
}

クリック音のソースに関して私が正しいと仮定すると、解決策は各サンプルを次のサンプルにフェードインさせることだと思います。ただし、WinMM のドキュメントは比較的まばらなようで、経験が浅いため、これを行う方法がわかりません。

各サンプルが次のサンプルにフェードインするようにオーディオ サンプルを再生するときのクリックの問題の解決策はありますか? もしそうなら、WinMM を使用してこれを達成するにはどうすればよいですか? そうでない場合、私が試すことができる別の解決策はありますか?

4

1 に答える 1

0

理論的にはこれを解決する方法はわかっていますが、すべてのケースで実際に機能するコードはまだありません。(気が向いたら編集します。)

まず、ちょっとうまくいく単純なケース: PlaySound を使用する代わりに、mciSendStringA を試してください:

    if(auto err = mciSendStringA("play test.wav", null, 0, null)) 
            writeln(err);     

私はそれをでっち上げているわけではありません.Windowsには実際にその機能があり、実際には多くの小さなコマンド文字列とファイル形式で動作します.ループするか、Sleep(something) を呼び出します)。

私は多くの Win32 を使用してきましたが、その機能の多さに驚くことがあります。プロトタイプ:

    extern(Windows) uint mciSendStringA(in char*,char*,uint,void*); 

で見つかりましたwinmm.lib

これは基本的に機能しますが、私のテストでは、同じファイルを同時に 2 回再生しても効果はありません。ただし、異なるファイルを一緒に再生すると、それらが混在します。したがって、それは部分的な解決策です。

その次のステップは、mciSendCommand 関数を使用することです。これは、送信文字列よりも少し低いレベルであるため、複数のデバイスを開いて、その方法でより多くのオーバーラップを取得しようとすることができます。

http://msdn.microsoft.com/en-us/library/windows/desktop/dd743675%28v=vs.85%29.aspx

私はまだこれを試していませんが、かなり単純に見えます。ボタンごとにいくつかのデバイスを開いて、それらを数回すばやく押すと、それらを循環して、必要に応じて同じサウンドを複数回ミックスできるようにします。

そのプロトタイプは次のとおりです。

extern(Windows) uint /*MCIERROR*/ mciSendCommandA(MCIDEVICEID,UINT,DWORD,DWORD);

はい、msdn の例では void* にキャストしてから DWORD にキャストします。ぶらぶら。関連する構造体:

struct MCI_OPEN_PARMSA { 
    DWORD dwCallback; 
    MCIDEVICEID wDeviceID; // aka uint
    LPCSTR lpstrDeviceType; 
    LPCSTR lpstrElementName; 
    LPCSTR lpstrAlias; 
}   

struct MCI_PLAY_PARMS { 
    DWORD dwCallback; 
    DWORD dwFrom; 
    DWORD dwTo; 
} 

また、ここからいくつかの定数を借りることもできます。

https://github.com/AndrejMitrovic/DWinProgramming/blob/master/WindowsAPI/win32/mmsystem.d#L693

(すでに win32 バインディングを使用している場合は、素晴らしいです! しかし、それらはちょっとしたことで面倒だと思うので、必要に応じてプロトタイプ + 構造体 + 定数を MSDN からコピー/貼り付けすることを好み、避けるようにしています。)

これらの定義と core.sys.windows.windows で動作する MSDN の例を取得できるはずです。こちらもお忘れpragma(lib, "winmm");なく。

確かに機能する完全なソリューションだと思いますが、かなり難しいですが、低レベルのインターフェイスを使用して、発生したサウンドを自分でミックスし、その結果をデバイスに送信します。これはまだ機能しておらず、今日は時間がありませんが、明日何かお届けできることを願っています。

基本的な手順は次のとおりです。

1) waveOutOpen を呼び出してデバイスを取得します。さらにデータが必要なときに呼び出すコールバック関数を設定します。

2) waveOutPrepareHeader を使用して、1 つまたは複数のバッファーを準備します。

3) コールバックによって要求された場合 (別のスレッドでこれが必要な場合があります)、現在のメモを使用して waveOutWrite でデータをフィードします。2 つのサンプルのミキシングは、単純に値を加算するケースです (値がオーバーフローした場合はクリッピングします - ひどく聞こえますが、実際には起こらないことを願っています)。

コールバック関数で extern(Windows) を忘れないでください!

4) サンプルをロードするということは、おそらく .wav ファイルを読むことを意味します。それはそれほど難しいことではありません.Windowsにはヘルパー機能があり、自分で行うこともできます. これについてもコードを示します。

これまでのところ、simpleaudio.d https://github.com/adamdruppe/arsd/blob/master/simpleaudio.d find struct AudioOutput と WinMM バージョンにあります。現在、根本的に変更しなければならないひどい API があります。Linux では受け入れられましたが、Windows ではうまくいきません。write(data) の代わりにコールバック フィーダーを使用すると、両方のプラットフォームでうまく機能するはずなので、それを実行します。

私が現在デモで抱えている問題は、バッファ間のギャップです...クリック音が発生します。うん。しかし、適切なコールバック アプローチとバッファ サイジングで解決する必要があるのは、単なるレイテンシだと確信しています。

ただし、その MCI 機能は次のステップとして機能する可能性があります。複数のデバイスが機能する場合は、最終ステップでさえあるかもしれません。


ところで: また、wav を再生する代わりに MIDI コマンドを実行させて、あらゆる種類のクールなものを取得することもできます。Simpleaudio.d の低レベルの midi は既に機能しています。デモのメインにはピアノの音階も表示されています。Xboxコントローラーにリギングするのはそれほど難しいことではありません...ボタンが押されたときに注意し、離されたときに注意し、タイミングについても考えません..質問に対する答えではありませんが、遊ぶのはクールなことです.同じように!

于 2014-12-26T04:57:02.090 に答える