在Haskell中生成.wav声音数据

| 我正在尝试使用
Data.WAVE
库在Haskell中通过格式为\“ Note Octave Note Octave \”(例如A 4 F#1)的文件以编程方式生成.wav文件,但是我遇到了一个问题:我可以没有弄清楚如何精确计算要存储的内容。到目前为止,我正在尝试将它们存储为根据八度音符频率计算出的正弦波,但是我从扬声器中得到的只有咔嗒声。我不会做错什么,因为这不会产生声音?
import Data.WAVE
import Graphics.UI.SDL.Mixer.Samples

import Control.Applicative
import Data.List.Split (splitOn)
import Data.Char
import Data.Int (Int32)
import Data.List (group)
import System.IO (hGetContents, Handle, openFile, IOMode(..))

a4 = 440.0

frameRate = 16000

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

notesToSamples :: [(String, Int)] -> [WAVESample]
notesToSamples ns =
    map doubleToSample [sin $ pi * i * (f/fr) | i <- [0,0.1..len], f <- freqs]
    where freqs = map noteToFreq ns
          fr = fromIntegral frameRate :: Double
          len = fromIntegral (length ns) :: Double

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

openMFile :: IO Handle
openMFile = getFileName >>= \\path -> 
            openFile path ReadMode

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

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

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

constructWAVE :: IO WAVE
constructWAVE = do
  samples <- map (:[]) . concatMap (replicate 1000) <$> getWavSamples
  let channels      = 1
      bitsPerSample = 32
      frames        = Just (length samples)
      header        =
          WAVEHeader channels frameRate bitsPerSample frames
  return $ WAVE header samples

makeWavFile :: IO ()
makeWavFile = constructWAVE >>= \\wav -> putWAVEFile \"temp.wav\" wav
    
已邀请:
这是一些使用该库生成声音的代码,希望您能够将代码用于自己的问题。首先,请检查它是否为给定的输入产生了正确的频率-我从未测试过。我实际上没有检查您的代码,因为大多数与声音生成无关。面对这种问题,我通常会尝试编写最简单的代码,以使外部库正常工作,然后再编写自己的抽象方法:
module Sound where
import Data.WAVE
import Data.Int (Int32)
import Data.List.Split (splitOn)

samplesPS = 16000
bitrate = 32

header = WAVEHeader 1 samplesPS bitrate Nothing

sound :: Double  -- | Frequency
      -> Int -- | Samples per second
      -> Double -- | Lenght of sound in seconds
      -> Int32 -- | Volume, (maxBound :: Int32) for highest, 0 for lowest
      -> [Int32]
sound freq samples len volume = take (round $ len * (fromIntegral samples)) $ 
                         map (round . (* fromIntegral volume)) $
                         map sin [0.0, (freq * 2 * pi / (fromIntegral samples))..]

samples :: [[Int32]]
samples = map (:[]) $ sound 600 samplesPS 3 (maxBound `div` 2)

samples2 :: [[Int32]] -- play two tones at once
samples2 = map (:[]) $ zipWith (+) (sound 600 samplesPS 3 (maxBound `div` 2)) (sound 1000 samplesPS 3 (maxBound `div` 2))

waveData = WAVE header samples


makeWavFile :: WAVE -> IO ()
makeWavFile wav = putWAVEFile \"temp.wav\" wav

main = makeWavFile waveData
一旦完成该工作,就可以围绕它编写更好的抽象。您应该能够对该库获得一个很好的纯抽象,因为使用IO的唯一函数是将其写入文件的函数。     

要回复问题请先登录注册