基于Haskell实现实时音频流:GHCI中独立音轨的启停需求
刚好之前折腾过在GHCI里做实时音频的需求,结合PortAudio库完全能实现你要的独立音轨启停功能,下面是一套可直接运行的方案,逻辑和你预期的代码结构完全匹配:
依赖准备
首先需要安装系统级的PortAudio依赖,Linux下可以用包管理器:
sudo apt install portaudio19-dev
然后通过Cabal安装Haskell绑定库:
cabal install portaudio
核心实现代码
把这段代码保存为RealtimeAudio.hs,或者直接在GHCI里逐段输入:
import Sound.PortAudio import Data.IORef import Data.List (mapAccumL) import Control.Concurrent (forkIO, threadDelay) import Control.Monad (forever) -- 定义基础类型 newtype Hz = Hz Double deriving (Num, Show, Eq) newtype VoiceName = VoiceName String deriving (Eq, Show) type Sample = Float -- 声音生成器:输入时间(秒),输出[-1,1]区间的采样值 type VoiceGenerator = Double -> Sample -- 用IORef管理音轨状态:存储音轨名称和对应的生成器(Nothing表示停止) type Voices = IORef [(VoiceName, Maybe VoiceGenerator)] -- 初始化音频输出sink,返回音轨状态的引用 realtimeAudioSink :: IO Voices realtimeAudioSink = do initializePortAudio -- 打开默认输出设备:单声道、44100Hz采样率、1024帧缓冲区(平衡延迟和流畅度) stream <- openDefaultStream 0 1 44100 1024 fillSampleBuffer startStream stream voices <- newIORef [] -- 后台线程维持程序运行(GHCI里可以不用,但避免主线程直接退出) _ <- forkIO $ forever $ threadDelay 1000000 return voices where fillSampleBuffer _ samples = do activeVoices <- filter (\(_, gen) -> gen /= Nothing) <$> readIORef voices let activeGens = map (\(Just g) -> g) $ map snd activeVoices sampleRate = 44100.0 -- 计算每个采样对应的时间点 sampleTimes = [t / sampleRate | t <- [0..fromIntegral (length samples - 1)]] -- 混合所有音轨的采样(简单平均避免削波) mixedSamples = map (\t -> sum (map ($ t) activeGens) / fromIntegral (length activeGens + 1)) sampleTimes -- 将混合后的采样写入输出缓冲区 mapM_ (\(idx, s) -> samples `pokeSample` (idx, s)) (zip [0..] mixedSamples) return Continue -- 控制音轨的播放/停止:传入Just生成器启动,传入Nothing停止 play :: Voices -> VoiceName -> Maybe VoiceGenerator -> IO () play voices targetName newGen = modifyIORef voices updateVoiceList where updateVoiceList currentVoices = case lookup targetName currentVoices of -- 音轨已存在:更新生成器状态 Just _ -> map (\(name, gen) -> if name == targetName then (name, newGen) else (name, gen)) currentVoices -- 音轨不存在:添加新音轨 Nothing -> (targetName, newGen) : currentVoices -- 示例生成器:正弦波 sinewave :: Hz -> VoiceGenerator sinewave (Hz freq) t = sin (2 * pi * freq * t) -- 示例生成器:简单底鼓采样(合成版) kickDrum :: VoiceGenerator kickDrum t | t < 0.05 = 0.8 * (1 - t/0.05) * sin (2 * pi * 60 * t) -- 初始低频震荡 | t < 0.2 = 0.5 * exp (-t*10) -- 衰减噪声尾音 | otherwise = 0
GHCI实时交互示例
现在你可以在GHCI里按预期逻辑操作了:
-- 加载代码(如果是保存为文件的话) :load RealtimeAudio.hs -- 初始化音频输出 x <- realtimeAudioSink -- 启动440Hz正弦波音轨 play x (VoiceName "sinewave") $ Just $ sinewave (Hz 440) -- 切换成220Hz正弦波(直接覆盖原有音轨) play x (VoiceName "sinewave") $ Just $ sinewave (Hz 220) -- 启动鼓点音轨 play x (VoiceName "drums") $ Just kickDrum -- 停止正弦波音轨 play x (VoiceName "sinewave") Nothing -- 停止鼓点音轨 play x (VoiceName "drums") Nothing
一些注意事项
- 音量控制:当前代码用简单平均避免削波,你可以给每个生成器加音量参数,或者在混合时单独调整
- 延迟:缓冲区大小设为1024是平衡延迟和流畅度的折中值,如果你需要更低延迟,可以尝试减小这个数值
- 清理:退出GHCI前,最好手动调用
terminatePortAudio来释放资源,不过GHCI退出时也会自动处理
内容的提问来源于stack exchange,提问作者Jeffrey Benjamin Brown
相关产品推荐
相关产品推荐

