2011-04-14 48 views
1

我試圖用格式爲「Note Octave Note Octave」的格式的文件(例如A 4 F#1)在使用Data.WAVE庫的Haskell中編程生成.wav文件,而且我已經遇到了一個問題:我無法弄清楚如何計算作爲筆記存儲的內容。到目前爲止,我試圖將它們存儲爲從八音度音符的頻率計算出的正弦波,但是我從演講者中獲得的所有信息都是點擊。我做錯了什麼,這不是產生音調?在Haskell生成.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 

回答

2

下面是一些使用該庫生成音調的代碼,您應該希望能夠將代碼用於您自己的問題。首先檢查它是否爲給定的輸入產生正確的頻率 - 我從來沒有測試過。我沒有真正檢查你的代碼,因爲大多數代碼與聲音生成無關。有了這樣的問題,我通常會嘗試寫身邊我自己的抽象之前寫的必要得到外部庫工作的最簡單的代碼:

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的唯一函數是將它寫入文件的函數。

+1

另外,使用Audacity或類似的樣本編輯器/查看器在開發聲音軟件的早期階段對於「調試」非常有用。雖然您仍然有相對簡單的聲波,但Audacity中的波形圖形視圖通常會比注視源代碼更容易顯示出錯誤。 – 2011-04-14 07:12:51