Неверный формат с использованием hsndfile (libsndfile)

Я пытаюсь использовать hsndfile (привязка Haskell для libsndfile) для создания файла .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

person Oso    schedule 15.04.2011    source источник
comment
Я не использовал hsndfile, но документы для Sound.File.Sndfile.Buffer, кажется, указывают, что данные образца должны быть значениями с плавающей запятой [-1.0, 1.0]. Вы пытаетесь написать Word16.   -  person stephen tetley    schedule 15.04.2011
comment
Я изменил его, чтобы использовать двойники, но это ничего не изменило. : / Ошибка с format.   -  person Oso    schedule 15.04.2011
comment
Я почти уверен, что WAV имеет прямой порядок байтов - format утверждает, что это прямой порядок байтов.   -  person stephen tetley    schedule 15.04.2011
comment
@Stephen Изменил это, по-прежнему нет кубиков. :( Я все же получил ошибку! Exception {errorString = "Error : major format is 0."} Я не уверен на 100%, что с этим делать.   -  person Oso    schedule 16.04.2011
comment
Я изменил это на little-endian, и из ghci запустил *Main> writeWav [1,2,3,4,3,2,1,0]. Это вернуло 8, и файл temp.wav был создан правильно. Ошибка, связанная с основным форматом, означает, что, возможно, заголовок волны не сгенерирован должным образом. Создан ли какой-либо вывод?   -  person John L    schedule 16.04.2011
comment
@John Я звоню makeWavFile, который вызывает writeWav с данными из файла. Файл содержит последовательность C 4 D 4 E 4 F 4 G 4 A 4 B 4.   -  person Oso    schedule 16.04.2011
comment
@ Джон *Main> writeWav [1,2,3,4,3,2,1,0] *** Exception: Exception {errorString = "Error : major format is 0."}   -  person Oso    schedule 16.04.2011
comment
@ Андрей - я не могу воспроизвести эту ошибку. Может попробовать переустановить libsndfile и hsndfile?   -  person John L    schedule 16.04.2011
comment
@John Oy, это делает меня грустной пандой. Я переустановил оба безрезультатно. Есть ли какие-то зависимости, на которые я должен обратить внимание?   -  person Oso    schedule 16.04.2011
comment
Эндрю, я автор апстрима libsndfile, а также немного хакер Haskell. я пробовал   -  person Erik de Castro Lopo    schedule 07.05.2011


Ответы (2)


Эндрю,

Я главный автор 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

что явно неверно.

Я просмотрел код Interface.hsc hsndfile. Значение поля формата фактически заканчивается в поле каналов, а поле каналов заканчивается в поле частоты дискретизации.

Я испортил этот код, но ничего не добился. Я буду пинговать вышестоящего сопровождающего hsndfile.

person Erik de Castro Lopo    schedule 07.05.2011
comment
Сопровождающий hsndfile восходящего потока и я устранили проблему (странное поведение c2hs при отсутствии файла заголовка). github.com/kaoskorobase/hsndfile/pull/3 - person Erik de Castro Lopo; 11.05.2011

Благодаря Эрику эта ошибка исправлена ​​в версии 0.5.1 на сайте Hackage.

Из-за отсутствия включения в sndfile.h в Linux генератор привязок Haskell не мог определить, что размер счетчика выборок sf_count_t должен быть 64-битным, и, как следствие, структура Info была искажена при преобразовании в ее представление C.

Сообщайте о дальнейших действиях по этой проблеме в hsndfile tracker.

person Kaos Korobase    schedule 11.05.2011