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

问题描述 投票:0回答:1

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

这是该指南中的 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 来提供绑定。任何帮助将不胜感激。

haskell openal
1个回答
0
投票

简单的答案是

genObjectNames
是一个重载函数,可以生成缓冲区(通过 C API
alGenBuffers
函数)和源(通过 C API
alGenSources
函数)。您的程序中没有任何内容告诉 GHC 您想要哪一种,因为您没有在上下文中使用
g_buffers
让 GHC 确定所需的类型。

一个修复方法是添加显式类型。有几种方法可以做到这一点。一种方法不需要扩展,但有点迂回,因为它要求您提供

<-
语句右侧的类型,因此您必须指定完整的单子类型:

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 中运行,并为具有
[a]
实例的任何类型
a
返回列表
GeneratableObjectName

这部分错误信息:

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

告诉您 GHC 找不到某些未指定类型

GenerateableObjectName
a0
实例。这暗示 GHC 对所需的类型了解不够,无法找到您真正想要的
GeneratableObjectName
实例。如果它说没有
GeneratableObjectName Int
的实例,那么您就会知道它 did 有足够的类型信息来查找实例,但由于代码中存在一些错误,类型信息实际上是错误的,因此它正在寻找一个不可能存在的实例。

您可以查询 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’

这表明

Buffer
Source
都有这样的实例,并且是返回类型的良好候选者。

© www.soinside.com 2019 - 2024. All rights reserved.