归一化和弦频率,参数传递

时间:2011-04-19 03:47:40

标签: haskell

因此,在下面的代码中,我从笔记和组合和弦生成一个wav文件。我已经让它适用于单个音符和两个音符的和弦,但是对于超过2个音符的组合,我遇到了问题,因为我没有正常化频率。我知道我需要做什么<(将每帧的频率除以构成它的音符的数量),但不一定如何以优雅的方式(或以任何方式)。发生的事情是,我需要以某种方式获取notes''返回的列表的长度,直到buildChord,然后找出如何在输入到{{1 }}

我真的很茫然,在这里,所以任何意见都会非常感激。

buildChord

1 个答案:

答案 0 :(得分:1)

关键问题在于功能:

buildChord :: [[Double]] -> WAVESamples
buildChord freqs = map ((:[]) . round . sum) $ transpose freqs

transpose freqs的结果是每个正在播放的音符的特定时间点的音量列表(例如[45.2, 20, -10])。函数(:[] . round . sum)首先将它们一起添加(例如55.2),对其进行舍入(例如,到55),并将其包装在列表中(例如[55])。 map (:[] . round . sum)只是在所有时间实例中都这样做了。

问题是,如果您一次播放多个音符,则总和会产生一个音量太大的音符。更好的是取笔记的平均值,而不是总和。这意味着同时播放的10个音符不会太大声。令人惊讶的是,前奏中没有平均功能。所以我们可以编写自己的平均函数,或者只是将它嵌入传递给map的函数中。我做了后者,因为代码较少:

buildChord :: [[Double]] -> WAVESamples
buildChord freqs = map (\chord -> [round $ sum chord / genericLength chord]) $ transpose freqs

我猜你的问题是你正在写一个音乐制作程序,作为学习haskell的一种方式。我有一些想法可以使您的代码更容易调试,更多“像haskell”。

haskell中的代码通常被写为从输入到输出的一系列转换。那个buildChord函数就是一个很好的例子 - 首先输入被转置,然后用一个组合了多个声音幅度的函数进行映射。但是,您也可以用这种方式构建整个程序。

该程序的目的似乎是:“以某种格式从文件中读取注释,然后从读取的那些注释中创建一个wav文件”。我将解决这个问题的方法首先是将其分解为不同的纯转换(即不使用输入或输出),并将读取和写入作为最后一步。

我首先要将声波写入WAVE转换。我会使用类型:

data Sound = Sound { soundFreqs :: [Double]
                   , soundVolume :: Double
                   , soundLength :: Double
                   }

然后编写函数:

soundsToWAVE :: Int -> [Sound] -> WAVE
soundsToWAVE samplesPerSec sounds = undefined -- TODO

然后我可以编写函数writeSoundsToWavFiletestPlaySounds

writeSoundsToWavFile :: String -> Int -> [Sound] -> IO ()
writeSoundsToWavFile fileN samplesPerSec sounds = putWAVEFile $ soundsToWAVE fileN samplesPerSec sounds

testPlaySounds :: [Sound] -> IO ()
testPlaySounds sounds = do
  writeSoundsToWavFile "test.wav" 32000 sounds
  system("afplay test.wav") -- use aplay on linux, don't know for windows
  return ()

完成后,所有WAVE代码都完成了 - 其余代码不需要触摸它。将它放在自己的模块中可能是个好主意。

之后,我会在音乐笔记和声音之间进行转换。我会使用以下类型的笔记:

data Note = A | B | C | D | E | F | G
data NoteAugment = None | Sharp | Flat

data MusicNote = MusicNote { note :: Note, noteAugment :: NoteAugment, noteOctave :: Int }

data Chord = Chord { notes :: [MusicNote], chordVolume :: Double }

然后编写函数:

chordToSound :: Chord -> Sound
chordToSound = undefined -- TODO

然后您可以轻松编写函数musicNotesToWAVFile:

chordsToWAVFile fileName samplesPerSec notes = writeSoundsToWavFile 32000 fileName samplesPerSec (map chordToSound notes)

(函数testPlayChords可以以相同的方式完成)。你也可以将它放在一个新模块中。

最后我会写转换音符字符串 - &gt; [弦]。这只需要这个功能:

parseNoteFileText :: String -> [Chord]
parseNoteFileText noteText = undefined

然后可以连接最终的程序:

main = do
  putStrLn "Enter the name of the file: " 
  fileN <- getLine
  noteText <- readFile fileN
  chordsToWAVFile (parseNoteFileText noteText)