我正在尝试用 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 来提供绑定。任何帮助将不胜感激。
简单的答案是
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
都有这样的实例,并且是返回类型的良好候选者。