我正在尝试使用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
答案 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。