外观
音频
audio 包从 Go 播放声音:解码进内存的音效、边播放边解码的流式音乐,以及 应用自己生成的声音。它在 Go 中混合所有正在播放的声音并送到系统的音频 输出,不使用 cgo,适用于原生 UI 或网页应用,支持 MyGo 运行的所有平台, 包括浏览器。
go
import (
"github.com/egoist/mygo/audio"
_ "github.com/egoist/mygo/audio/vorbis" // Ogg Vorbis
)
//go:embed sounds
var sounds embed.FS
data, _ := sounds.ReadFile("sounds/hoe.wav")
hoe, err := audio.DecodeSound(data) // decoded once
hoe.Play() // as often as it sounds, over itself
f, _ := sounds.Open("sounds/spring.ogg")
music, err := audio.OpenStream(f.(io.ReadSeeker)) // decoded as it plays
music.SetLoop(true).SetVolume(0.6).Play()声音与流
Sound 保存已解码的采样,供频繁播放的音效使用:Play 每次都新建一个 播放器,因此声音可以与自身重叠播放;NewPlayer 返回一个处于暂停状态的 播放器,以便在播放前进行设置:
go
hoe.NewPlayer().SetPan(-0.4).SetSpeed(0.95 + rand.Float64()*0.1).Play()OpenStream 在播放时预先解码约一秒的内容,适合音乐和其他长音频; NewPlayer 对任意 Decoder 也同样如此。流不再使用时要关闭它的播放器: 关闭播放器会同时关闭文件。声音的播放器无需关闭。
播放器
Player 可以播放、暂停(Pause)并回到开头(Stop);播放结束后再 Play,会从头重新播放。Seek 移动位置,Position 和 Duration 说明当前 位置和总时长,Playing 在播放结束时变为 false,游戏可以借此等待一个声音; OnEnd 届时在自己的 goroutine 上调用一个函数,如同播放列表播放下一首 (循环的播放器永不结束)。
Position 是被听到的位置。输出端会先于扬声器播放进行混音(见 输出),Position 相对已混入的内容滞后同样的时长,因此由它 绘制的歌词、进度条或可视化器能与声音保持同步:Pause 之后,它在输出播放 完已持有的内容之前会继续前进;Play 之后,它会等到播放器真正被听到才前 进。Seek 和 Stop 则立即移动它。
| 设置 | 效果 |
|---|---|
SetVolume(v) | 增益:1 为解码原样,0 为静音 |
SetPan(p) | 从左扬声器(-1)到右扬声器(1):单声道保持能量,立体声保持两个声道,如同 Web Audio 的 StereoPannerNode |
SetSpeed(s) | 同时改变速度和音高,如同唱片:2 表示快一倍、高一个八度 |
SetLoop(true) | 结束后立即重新开始,没有间隙 |
SetLoopRange(start, end) | 从 end 循环回 start:曲子的前奏播放一次,然后进入循环(end 为 0 表示结尾) |
这些设置方法返回播放器本身,便于链式调用。音量变化、暂停和恢复都在 5 毫秒 内渐变,因此不会产生爆音。audio.SetVolume 设置整体的音量。
媒体键与 Now Playing
mygo.NowPlaying.Control 让媒体键和系统的媒体控制(macOS 上的控制中心、 Windows 上的音量浮动窗口、Linux 上的 MPRIS 客户端)播放、暂停、停止和跳转 一个播放器,并在其中显示其状态;见 Now Playing:
go
release := mygo.NowPlaying.Control(music)
mygo.NowPlaying.Set(mygo.NowPlayingInfo{Title: "Spring", Artist: "Vivaldi", Commands: mygo.MediaNext})
music.Play()
mygo.NowPlaying.Sync() // after playing it from the app格式
WAV(8 到 32 位整数 PCM、32 或 64 位浮点)开箱即可解码。像 image 包那样, 为了副作用而导入某个格式的包:
| 包 | 格式 | 解码器 |
|---|---|---|
audio | WAV | 内置 |
audio/vorbis | Ogg Vorbis | jfreymuth/oggvorbis |
audio/mp3 | MP3 | hajimehoshi/go-mp3 |
循环播放的音乐优先选用 Ogg Vorbis:MP3 文件以编码器的填充开头和结尾,那是 几毫秒的静音,循环时也会被播放出来,除非用 SetLoopRange 将其排除。
go
// 前奏 12.5 秒,然后循环 12.5 秒到 1 分 40 秒之间的乐段,周而复始。
music.SetLoopRange(12500*time.Millisecond, 100*time.Second).Play()RegisterFormat 添加你自己的格式;你编写的 Decoder 可以在播放时实时生成 声音,例如合成器:
go
type tone struct{ pos int64 }
func (t *tone) SampleRate() int { return 48000 }
func (t *tone) Channels() int { return 1 }
func (t *tone) Len() int64 { return -1 } // endless
func (t *tone) SetPosition(f int64) error { t.pos = f; return nil }
func (t *tone) Read(buf []float32) (int, error) {
for i := range buf {
buf[i] = 0.2 * float32(math.Sin(2*math.Pi*440*float64(t.pos)/48000))
t.pos++
}
return len(buf), nil
}
audio.NewPlayer(&tone{}).Play()NewSound 接受应用一次性计算好的采样。
输出
输出在首次有内容播放时打开,在全部停止五秒后关闭,因此不播放声音的应用不 会占用音频设备。它以 48 kHz 立体声播放,提前约 40 毫秒;其他采样率的播放 器在播放时被重采样。
audio.SetLatency 设置提前混音的时长,如同 Web Audio 的 latencyHint:
| 延迟 | 提前量 | 适用 |
|---|---|---|
LatencyInteractive(默认) | 约 40 毫秒 | 游戏和界面:声音随叫随到,立即开始、停止和变化 |
LatencyPlayback | 约 250 毫秒 | 音乐播放器:播放能扛住系统让应用等待的时刻,例如 macOS 在外观切换期间最多让应用等待 0.2 秒,此时 40 毫秒会断供而被听出停顿 |
使用 LatencyPlayback 时,播放、暂停、跳转和音量变化要晚同样的时长才被 听到;播放器的 Position 跟随被听到的内容。应在播放前设置:已打开的输出会 重新打开,并丢弃其已持有的内容。
| 平台 | 输出 |
|---|---|
| macOS | 在默认输出上使用 Audio Queue,并跟随默认输出 |
| Linux | PulseAudio 的 simple API(PipeWire 同样提供),否则使用 ALSA 的默认设备 |
| Windows | 共享模式的 WASAPI,默认输出变化时重新打开 |
| 浏览器 | AudioWorklet;页面只能在用户与之交互后启动音频,因此它在首次点击或按键时启动 |
在无法打开输出的地方,例如没有音频设备的机器上,播放器仍会按时播放,只是 无人能听到,而 audio.Err 会说明原因。所有函数和方法都可以安全地从任意 goroutine 调用。
examples/piano 是一架合成琴键的钢琴,可用指针和键盘演奏,带有延音踏板、让琴弦渐隐的止音 器,以及几首它自己演奏的曲子。