You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:41:38