【发布时间】:2011-08-06 12:13:10
【问题描述】:
我正在尝试使用 hsndfile(libsndfile 的 Haskell 绑定)来生成一个 .wav 文件,但我又遇到了一个无法克服的难题。以下代码引发错误“格式错误”。 (如 openWavHandle 中所写)。我已经尝试了我认为存在的所有字节顺序与 HeaderFormatWav 和 SampleFormatPcm16 的组合,但无济于事。有谁知道如何解决这个问题?
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
【问题讨论】:
-
我没有使用 hsndfile - 但 Sound.File.Sndfile.Buffer 的文档似乎表明样本数据应该是浮点值 [-1.0, 1.0]。您正在尝试编写 Word16 的。
-
我修改它以使用双打,但这并没有改变任何东西。 :/ 错误在于
format。 -
我很确定 WAV 是小端 -
format声称它是大端。 -
@Stephen 改变了,仍然没有骰子。 :( 不过,我得到它抛出一个错误!
Exception {errorString = "Error : major format is 0."}我不是 100% 确定它是什么。 -
我将其更改为 little-endian,并从 ghci 运行
*Main> writeWav [1,2,3,4,3,2,1,0]。这返回“8”,并且文件“temp.wav”已正确创建。有关“主要格式”的错误意味着可能未正确生成波头。是否创建了任何输出?
标签: haskell wav libsndfile