Audio 快速连续播放剪辑时发出咔哒声

Audio 快速连续播放剪辑时发出咔哒声,audio,d,winmm,Audio,D,Winmm,我有一个非常简单的程序,根据按下的按钮播放4种不同的音调。我发现,如果我快速连续播放多个音调或同一音调,会产生令人不快的咔嗒声。我已确保这些点击不存在于我的音频样本中;这肯定是一个接一个快速播放剪辑造成的 在谷歌搜索之后,我很确定点击是由于剪辑之间音高的快速变化造成的。从令人不快的音频中查看回放波形,似乎在开始下一个剪辑之前,某个剪辑先被取消了几分之一秒。我强调了这一点特别明显的部分 也可以下载显示这些音频点击的剪辑 我的代码非常简单。我使用XInput从连接的控制器读取输入,该控制器决定播放

我有一个非常简单的程序,根据按下的按钮播放4种不同的音调。我发现,如果我快速连续播放多个音调或同一音调,会产生令人不快的咔嗒声。我已确保这些点击不存在于我的音频样本中;这肯定是一个接一个快速播放剪辑造成的

在谷歌搜索之后,我很确定点击是由于剪辑之间音高的快速变化造成的。从令人不快的音频中查看回放波形,似乎在开始下一个剪辑之前,某个剪辑先被取消了几分之一秒。我强调了这一点特别明显的部分

也可以下载显示这些音频点击的剪辑

我的代码非常简单。我使用XInput从连接的控制器读取输入,该控制器决定播放的音调,我使用WinMM从wav文件输出声音。它是用D编程语言编写的,但我修改了它,使其不使用D特定的特性,以使其尽可能类似于C,并避免混淆

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实现这一点?如果没有,还有其他解决方案可以尝试吗?

理论上我知道如何解决这个问题,但我还没有适用于所有情况的实际工作代码。(当我这样做时,我将编辑此。)

首先是一个简单的例子:不要使用PlaySound,试试mciSendStringA:

    if(auto err = mciSendStringA("play test.wav", null, 0, null)) 
            writeln(err);     
我不是在编造,Windows实际上有这个功能,它实际上可以处理很多小的命令字符串和文件格式(虽然如果你的程序终止,所有声音都会停止,所以请确保程序保持运行,例如,保持在控制器循环中或调用Sleep(某种东西))

我用过很多Win32,有时我会惊讶于它有这么多东西。原型:

    extern(Windows) uint mciSendStringA(in char*,char*,uint,void*); 
winmm.lib
中找到

这基本上是可行的,但在我的测试中,同时播放同一文件两次没有效果。但是,将不同的文件放在一起会使它们混合在一起。因此,这是一个局部解决方案

下一步是使用mciSendCommand函数-比send string低一点,因此您可以打开多个设备,并尝试通过这种方式获得更多的重叠:

我还没有试过这个,但它看起来相当简单,我怀疑它对你来说可能足够好了。为每个按钮打开一些设备,这样你就可以快速点击它们几次,它就会在它们之间循环,希望在需要时能多次混合相同的声音

这方面的原型是:

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; 
} 
你也可以从这里借用一些常数:

(如果您已经在使用win32绑定,那就太好了!但我认为它们对于小事情来说有点麻烦,所以我尽量避免使用它们,因为我更喜欢根据需要从MSDN复制/粘贴原型+结构+常量。)

您应该能够让MSDN示例使用这些定义和core.sys.windows.windows。别忘了
pragma(lib,“winmm”)也是

我认为一个完整的解决方案肯定会起作用,但也有点困难,将使用低级接口在声音发生时自己混合声音,并将结果发送到设备。我还没有工作,我今天没时间,但希望明天我能给你一些东西

基本步骤是:

1) 调用waveOutOpen以获取设备。设置一个回调函数,在需要更多数据时调用该函数

2) 使用WaveOutprepreHeader准备一个或多个缓冲区

3) 当回调请求时(可能希望在单独的线程中)使用waveOutWrite和当前notes提供数据。混合两个样本只是将这些值相加(如果它们溢出,则进行剪裁-顺便说一句,听起来很糟糕,但希望不会发生这种情况),因此,如果您正在执行多个声音,只需在执行过程中添加它们

不要忘记任何回调函数上的外部(Windows)

4) 加载示例可能意味着读取.wav文件。这不是很难,Windows有助手功能,或者你可以自己做。我也会显示这个的代码

到目前为止,我在simpleudio.d find struct AudioOutput和WinMM版本中找到了。它现在有一个可怕的API,必须彻底改变——它在Linux上是可以接受的,但在Windows上很糟糕。在这两种平台上,回调馈送器而不是写(数据)应该工作得更好,所以这就是我要做的

我现在在演示中遇到的问题是缓冲区之间的间隙。。。导致咔哒声。是 啊但我确信,应该通过适当的回调方法和缓冲区大小来解决延迟问题

不过,MCI功能可能会作为下一步对您起作用,如果多个设备正常工作,甚至可能是最后一步



顺便说一句:你也可以让它做MIDI命令,而不是玩WAV,并得到各种很酷的东西。Simpleaudio.d的低阶midi已经开始运行了——主演示甚至还显示了一个钢琴音阶。把它装进xbox控制器应该不会太难。。。按下按钮时注意开启,松开时注意关闭,甚至不要考虑计时。。不是对这个问题的真正回答,而是一个很酷的东西,可以用同样的方式玩

我不熟悉WinMM,但如果你想让它听起来像钢琴,我认为你应该以某种方式将音符混合在一起,使它们在同时演奏时相互重叠。如果你