使用hsndfile(libsndfile)格式错误

时间:2011-04-15 17:05:57

标签: haskell wav libsndfile

我正在尝试使用hsndfile(用于libsndfile的Haskell绑定)来生成.wav文件,而且我已经达到了另一个我无法通过的驼峰。以下代码引发错误“格式错误”。 (以openWavHandle编写)。我已经尝试了与我认为存在的HeaderFormatWav和SampleFormatPcm16的每种endianness组合,但无济于事。有谁知道如何解决这个问题?

import qualified Sound.File.Sndfile as Snd
import qualified Graphics.UI.SDL.Mixer.Channels as SDLC
import qualified Graphics.UI.SDL.Mixer.General as SDLG
import qualified Graphics.UI.SDL.Mixer.Samples as SDLS

import Control.Applicative
import Foreign.Marshal.Array
import Data.List.Split (splitOn)
import Data.Word (Word16)
import System.IO (hGetContents, Handle, openFile, IOMode(..))

a4 :: Double
a4 = 440.0

frameRate :: Int
frameRate = 16000

noteLength :: Double
noteLength = 5.0

volume = maxBound `div` 2 :: Word16

noteToFreq :: (String, Int) -> Double
noteToFreq (note, octave) =
    if octave >= -1 && octave < 10 && n /= 12.0
    then a4 * 2 ** ((o - 4.0) + ((n - 9.0) / 12.0))
    else undefined
    where o = fromIntegral octave :: Double
          n = case note of
                "B#" -> 0.0
                "C"  -> 0.0
                "C#" -> 1.0
                "Db" -> 1.0
                "D"  -> 2.0
                "D#" -> 3.0
                "Eb" -> 3.0
                "E"  -> 4.0
                "Fb" -> 4.0
                "E#" -> 5.0
                "F"  -> 5.0
                "F#" -> 6.0
                "Gb" -> 6.0
                "G"  -> 7.0
                "G#" -> 8.0
                "Ab" -> 8.0
                "A"  -> 9.0
                "A#" -> 10.0
                "Bb" -> 10.0
                "B"  -> 11.0
                "Cb" -> 11.0
                _    -> 12.0

notesToFreqs :: [(String, Int)] -> [Double]
notesToFreqs = map noteToFreq 

noteToSample :: Double -> [Word16]
noteToSample freq =
    take (round $ noteLength * fromIntegral frameRate) $
    map ((round . (* fromIntegral volume)) . sin) 
    [0.0, (freq * 2 * pi / fromIntegral frameRate)..]

notesToSamples :: [Double] -> [Word16]
notesToSamples = concatMap noteToSample 

getFileName :: IO FilePath
getFileName = putStr "Enter the name of the file: " >> getLine

openMFile :: FilePath -> IO Handle
openMFile fileName = openFile fileName ReadMode

getNotesAndOctaves :: IO String
getNotesAndOctaves = getFileName >>= openMFile >>= hGetContents 

noteValuePairs :: String -> [(String, Int)]
noteValuePairs = pair . splitOn " "
    where pair (x:y:ys) = (x, read y) : pair ys
          pair []       = []

getWavSamples :: IO [Word16]
getWavSamples = (notesToSamples . notesToFreqs . noteValuePairs) <$>
                getNotesAndOctaves 

extendNotes :: [Word16] -> [Word16]
extendNotes = concatMap (replicate 1000)

format :: Snd.Format
format = Snd.Format Snd.HeaderFormatWav Snd.SampleFormatPcm16 Snd.EndianBig

openWavHandle :: [Word16] -> IO Snd.Handle
openWavHandle frames =
    let info = Snd.Info (length frames) frameRate 1 format 1 False
    in if Snd.checkFormat info
       then Snd.openFile "temp.wav" Snd.WriteMode info
       else error "Bad format."

writeWav :: [Word16] -> IO Snd.Count
writeWav frames = openWavHandle frames >>= \h ->
                  newArray frames >>= \ptr ->
                  Snd.hPutBuf h ptr (length frames) >>= \c ->
                  return c

makeWavFile :: IO ()
makeWavFile = getWavSamples >>= \s ->
              writeWav s >>= \c ->
              putStrLn $ "Frames written: " ++ show c

2 个答案:

答案 0 :(得分:1)

安德鲁,

我是libsndfile的主要作者,也是一些Haskell黑客。我已经看过这个,就我而言,下面的最小示例代码应该可以工作。

import qualified Sound.File.Sndfile as Snd
import Control.Applicative
import Foreign.Marshal.Array
import Data.Word (Word16)
import System.IO (hGetContents, Handle, openFile, IOMode(..))

format :: Snd.Format
format = Snd.Format Snd.HeaderFormatWav Snd.SampleFormatPcm16 Snd.EndianFile

openWavHandle :: [Word16] -> IO Snd.Handle
openWavHandle frames =
    let info = Snd.Info (length frames) 441000 1 format 1 False
    in Snd.openFile "temp.wav" Snd.WriteMode info

writeWav :: [Word16] -> IO Snd.Count
writeWav frames = openWavHandle frames >>= \h ->
              newArray frames >>= \ptr ->
              Snd.hPutBuf h ptr (length frames) >>= \c ->
              return c

makeWavFile :: IO ()
makeWavFile = writeWav [1..256] >>= \c ->
          putStrLn $ "Frames written: " ++ show c


main :: IO ()
main = makeWavFile

它没有在hsndfile中提出问题。为了证明这一点,我将C源代码破解为libsndfile以打印出SF_INFO结构的值(hsndfile调用Info)并执行此操作:

samplerate : 1
channels   : 65538
format     : 0x1

这显然是错误的。

我看过hsndfile的Interface.hsc代码。 format字段的值实际上在channels字段中结束,channels字段最终在samplerate字段中。

我搞砸了这段代码,但我没有得到任何结果。我将ping上游的hsndfile维护者。

答案 1 :(得分:1)

感谢Erik,version 0.5.1 on Hackage已修复此错误。

由于Linux上的sndfile.h中缺少包含,因此Haskell绑定生成器无法确定样本计数sf_count_t的大小应为64位,因此{{1转换为C表示时,struct是乱码。

请将有关此问题的后续内容直接发送至hsndfile tracker