在 Haskell 中生成 .wav 声音数据
Posted
技术标签:
【中文标题】在 Haskell 中生成 .wav 声音数据【英文标题】:Generating .wav Sound Data in Haskell 【发布时间】:2011-04-14 04:03:34 【问题描述】:我正在尝试使用 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
【问题讨论】:
【参考方案1】:这是一些使用该库生成音调的代码,您应该希望能够使用该代码来解决您自己的问题。首先检查它是否为给定的输入产生了正确的频率——我从未测试过。我实际上并没有检查您的代码,因为大多数与声音生成无关。对于这类问题,我通常会尝试编写最简单的代码,以使外部库正常工作,然后再围绕它编写自己的抽象:
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 的函数是将其写入文件的函数。
【讨论】:
另外,使用 Audacity 或类似的样本编辑器/查看器对于开发声音软件的早期阶段的“调试”非常有用。虽然您仍然拥有相对简单的声波,但 Audacity 中的波形图形视图通常可以比盯着源代码更直接地告诉您出了什么问题。以上是关于在 Haskell 中生成 .wav 声音数据的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 pydub 库从 mp3 文件中生成带有 G.711alaw 的 wav?