如何在Haskell中生成OpenAL缓冲区?

cvxl0en2  于 5个月前  发布在  其他
关注(0)|答案(1)|浏览(74)

我正在尝试用Haskell写一个输出音频的程序,我决定使用OpenAL作为音频后端,考虑到我使用OpenAL的经验很少,我想我要做的第一件事就是在Haskell中复制OpenAL programmer's guide中的示例代码,这是用C编写的。
下面是该指南中的C代码:

// Initialization
Device = alcOpenDevice(NULL); // select the "preferred device"

if (Device) {
    Context=alcCreateContext(Device,NULL);
    alcMakeContextCurrent(Context);
}

// Check for EAX 2.0 support
g_bEAX = alIsExtensionPresent("EAX2.0");

// Generate Buffers
alGetError(); // clear error code

alGenBuffers(NUM_BUFFERS, g_Buffers);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alGenBuffers :", error);
    return;
}

// Load test.wav
loadWAVFile("test.wav",&format,&data,&size,&freq,&loop);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alutLoadWAVFile test.wav : ", error);
    alDeleteBuffers(NUM_BUFFERS, g_Buffers);
    return;
}

// Copy test.wav data into AL Buffer 0
alBufferData(g_Buffers[0],format,data,size,freq);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alBufferData buffer 0 : ", error);
    alDeleteBuffers(NUM_BUFFERS, g_Buffers);
    return;
}

// Unload test.wav
unloadWAV(format,data,size,freq);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alutUnloadWAV : ", error);
    alDeleteBuffers(NUM_BUFFERS, g_Buffers);
    return;
}

// Generate Sources
alGenSources(1,source);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alGenSources 1 : ", error);
    return;
}

// Attach buffer 0 to source
alSourcei(source[0], AL_BUFFER, g_Buffers[0]);
if ((error = alGetError()) != AL_NO_ERROR)
{
    DisplayALError("alSourcei AL_BUFFER 0 : ", error);
}

// Exit
Context=alcGetCurrentContext();
Device=alcGetContextsDevice(Context);
alcMakeContextCurrent(NULL);
alcDestroyContext(Context);
alcCloseDevice(Device);

字符串
这是我目前为止得到的:

module Main where

import Data.Maybe ( fromJust )
import Data.StateVar ( ($=) )
import Sound.OpenAL ( genObjectNames )
import qualified Sound.OpenAL.AL as AL
import qualified Sound.OpenAL.ALC as ALC

main :: IO ()
main = do
    -- Initialization
    maybeDevice <- ALC.openDevice Nothing  -- select the "preferred device"
    let device = fromJust maybeDevice

    maybeContext <- ALC.createContext device streamAttributes
    ALC.currentContext $= maybeContext

    -- Generate buffers
    errors <- AL.alErrors  -- clear error code
    [g_buffers] <- genObjectNames n_buffers

    -- Exit
    deviceClosed <- ALC.closeDevice device
    return ()

    where streamAttributes = [ ALC.Frequency 44100,
                               ALC.MonoSources 1,
                               ALC.StereoSources 0 ]
          n_buffers = 3


不幸的是,我在尝试生成缓冲区时遇到了一点障碍。目前,当我编译上述Haskell代码时,我得到以下错误:

app/Main.hs:19:20: error:
    • No instance for (Data.ObjectName.GeneratableObjectName a0)
        arising from a use of ‘genObjectNames’
    • In a stmt of a 'do' block:
        [g_buffers] <- genObjectNames n_buffers
      In the expression:
        do maybeDevice <- ALC.openDevice Nothing
           let device = fromJust maybeDevice
           maybeContext <- ALC.createContext device streamAttributes
           ALC.currentContext $= maybeContext
           ....
      In an equation for ‘main’:
          main
            = do maybeDevice <- ALC.openDevice Nothing
                 let device = ...
                 maybeContext <- ALC.createContext device streamAttributes
                 ....
            where
                streamAttributes = [ALC.Frequency 44100, ....]
                n_buffers = 3
   |
19 |     [g_buffers] <- genObjectNames n_buffers
   |                    ^^^^^^^^^^^^^^


我对Haskell不是很有经验,所以我发现很难准确地指出我在这里做错了什么。我使用https://hackage.haskell.org/package/OpenAL来提供绑定。任何帮助都将不胜感激。

ss2ws0br

ss2ws0br1#

快速回答是genObjectNames是一个重载函数,它可以生成缓冲区(通过C API alGenBuffers函数)和源(通过C API alGenSources函数)。程序中没有任何东西告诉GHC您想要哪一个,因为您没有在让GHC确定所需类型的上下文中使用g_buffers
一种解决方法是添加一个显式类型。有几种方法可以做到这一点。一种方法不需要扩展,但有点迂回,因为它需要您提供<-语句右侧的类型,所以您必须指定完整的monadic类型:

g_buffers <- genObjectNames n_buffers :: IO [AL.Buffer]

字符串
使用扩展,您可以直接指定返回类型,或者使用:

{-# LANGUAGE ScopedTypeVariables #-}
...
g_buffers :: [AL.Buffer] <- genObjectNames n_buffers


否则:

{-# LANGUAGE TypeApplications #-}
...
g_buffers <- genObjectNames @AL.Buffer n_buffers


或者,您可以不指定类型,并在提供足够类型信息的上下文中使用g_buffers

g_buffers <- genObjectNames n_buffers
let data1 = AL.bufferData (g_buffers !! 0)


作为对您收到的错误的更详细的解释,如果您从GHCi查询genObjectNames的类型,您将看到它的类型:

λ> :t genObjectNames
genObjectNames
  :: (GeneratableObjectName a, Control.Monad.IO.Class.MonadIO m) =>
     Int -> m [a]


这意味着genObjectNames在支持IO的monad中运行,并为任何具有GeneratableObjectName示例的a类型返回列表[a]
错误消息的这一部分:

• No instance for (Data.ObjectName.GeneratableObjectName a0)
    arising from a use of ‘genObjectNames’


告诉你GHC找不到某个未指定类型a0GenerateableObjectName示例。这是一个提示,表明GHC对所需类型的了解不够,无法找到你实际需要的GeneratableObjectName示例。如果它说GeneratableObjectName Int没有示例,那么你就会知道它 * 确实 * 有足够的类型信息来找到示例,但是由于代码中的一些错误,类型信息实际上是错误的,所以它正在寻找一个不可能存在的示例。
您可以查询GHCi以确定哪些示例可用:

λ> :i GeneratableObjectName
...
instance [safe] GeneratableObjectName Buffer
  -- Defined in ‘OpenAL-1.7.0.5:Sound.OpenAL.AL.BufferInternal’
instance [safe] GeneratableObjectName Source
  -- Defined in ‘Sound.OpenAL.AL.Source’


这表明BufferSource都有这样的示例,并且将是返回类型的良好候选。

相关问题