2011-04-14 3 views
1

Data.WAVE 라이브러리를 사용하는 Haskell의 "Note Octave Note Octave"형식 (예 : A 4 F # 1)의 파일에서 .wav 파일을 프로그래밍 방식으로 생성하려고합니다. 문제가 발생했습니다. 메모로 저장할 항목을 정확히 계산하는 방법을 파악할 수 없습니다. 지금은 옥타브에서 음표의 주파수를 계산하여 사인파로 저장하려고 시도하고 있지만 스피커에서 나오는 것은 모두 클릭 수입니다. 이것이 내가 톤을 생성하지 않는다는 것을 잘못하고있는 것은 무엇입니까?하스켈에서 .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에서 파형의 그래픽보기는 소스 코드를 쳐다 보는 것보다 문자 그대로 잘못되는 부분을 종종 보여줍니다. –

관련 문제