提问背景 Question:
如果我想用 golang实现,下面的播放呢
X: 1
T: Summer Breeze
M: 4/4
L: 1/4
K: C
C D E C | C D E C | E F G2 | E F G2 | G/A/ G/F/ E C | G/A/ G/F/ E C | C G, C2 | C G, C2 |
Golang 实现 ABC 记谱法音频播放
在 Go 语言中,要播放你提供的 ABC 记谱法文本,最直接、不依赖外部重型 MIDI 播放器的方法是:解析 ABC 文本中的音符与节拍,通过 Go 音频库(如 faiface/beep)实时合成正弦波(Sine Wave)并输出到扬声器。
下面为你提供一个完整的、开箱即用的 Go 程序。它将解析你给出的《Summer Breeze》旋律,并将其转化为声波播放出来。
1. 准备工作
首先,你需要初始化一个 Go 项目并安装跨平台的音频基础库 github.com/faiface/beep:
mkdir go-abc-player
cd go-abc-player
go mod init go-abc-player
go get github.com/faiface/beep
系统依赖提示(Linux 用户):
如果你在 Linux 上运行,可能需要先安装 ALSA 开发库(Ubuntu/Debian 运行sudo apt-get install libasound2-dev)。Windows 和 macOS 无需额外依赖。
2. 完整 Go 源码
创建 main.go,将以下代码粘贴进去:
package main
import (
"fmt"
"math"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/speaker"
)
// 定义音符频率 Map (C3 到 A4)
var noteFreqs = map[string]float64{
"C,": 130.81, // 低音 C3
"G,": 196.00, // 低音 G3
"C": 261.63, // 中音 C4
"D": 293.66,
"E": 329.63,
"F": 349.23,
"G": 392.00,
"A": 440.00,
}
// Note 代表一个要播放的音符
type Note struct {
Pitch string // 音名,如 "C", "G,"
Duration time.Duration // 持续时间
}
// SineWave 是一个简单的正弦波生成器,实现了 beep.Streamer 接口
type SineWave struct {
freq float64
sample float64
sr beep.SampleRate
}
func NewSineWave(sr beep.SampleRate, freq float64) *SineWave {
return &SineWave{
freq: freq,
sr: sr,
}
}
func (s *SineWave) Stream(samples [][2]float64) (n int, ok bool) {
for i := range samples {
// 正弦波计算公式:sin(2 * pi * f * t)
val := math.Sin(2 * math.Pi * s.freq * s.sample / float64(s.sr))
// 降低一点音量,避免爆音,并输出到左右双声道
samples[i][0] = val * 0.3
samples[i][1] = val * 0.3
s.sample++
}
return len(samples), true
}
func (s *SineWave) Err() error {
return nil
}
func main() {
// 1. 初始化扬声器,采样率为 44100Hz
sr := beep.SampleRate(44100)
err := speaker.Init(sr, sr.N(time.Second/10))
if err != nil {
panic(err)
}
// 2. 将你的 ABC 旋律转化为简易的音符队列
// 根据:M:4/4 (四分音符为一拍), L:1/4 (基础音符长度为四分之一拍)
// 我们设定 1 拍 (L:1/4) 的时间为 400 毫秒
baseDuration := 400 * time.Millisecond
melody := []Note{
// C D E C | C D E C |
{"C", baseDuration}, {"D", baseDuration}, {"E", baseDuration}, {"C", baseDuration},
{"C", baseDuration}, {"D", baseDuration}, {"E", baseDuration}, {"C", baseDuration},
// E F G2 | E F G2 |
{"E", baseDuration}, {"F", baseDuration}, {"G", baseDuration * 2}, // G2 占两拍
{"E", baseDuration}, {"F", baseDuration}, {"G", baseDuration * 2},
// G/A/ G/F/ E C | G/A/ G/F/ E C |
{"G", baseDuration / 2}, {"A", baseDuration / 2}, // G/A/ 是半拍(八分音符)
{"G", baseDuration / 2}, {"F", baseDuration / 2},
{"E", baseDuration}, {"C", baseDuration},
{"G", baseDuration / 2}, {"A", baseDuration / 2},
{"G", baseDuration / 2}, {"F", baseDuration / 2},
{"E", baseDuration}, {"C", baseDuration},
// C G, C2 | C G, C2 |
{"C", baseDuration}, {"G,", baseDuration}, {"C", baseDuration * 2},
{"C", baseDuration}, {"G,", baseDuration}, {"C", baseDuration * 2},
}
fmt.Println("♪ 正在播放: Summer Breeze (ABC Notation)... 🎶")
// 3. 顺序播放音符
for _, note := range melody {
freq, exists := noteFreqs[note.Pitch]
if !exists {
// 如果遇到无法识别的音符,静音等同时间
time.Sleep(note.Duration)
continue
}
// 创建该音符的正弦波流
sine := NewSineWave(sr, freq)
// 限制该波形流的播放时长
limitSine := beep.Take(sr.N(note.Duration), sine)
// 播放音符
done := make(chan bool)
speaker.Play(beep.Seq(limitSine, beep.Callback(func() {
done <- true
})))
<-done
// 每个音符之间微调停顿 30ms,听起来更有颗粒感和乐感,而不是糊成一片
time.Sleep(30 * time.Millisecond)
}
fmt.Println("播放完毕!")
}
3. 如何运行?
保存代码后,在终端执行以下命令:
go run main.go
你会听到电脑扬声器中传出经典的 8-bit 电子合成器风格的《Summer Breeze》旋律!
4. 代码逻辑解析:
- 音高与频率转换:在
noteFreqsMap 中,我们定义了C为中音 C4 (261.63Hz),C,(带逗号)表示低八度的 C3 (130.81Hz)。 - 音符时长转换:
- 基础拍长
baseDuration(代表L: 1/4)设为400ms。 G2尾部带数字2,表示时长乘以 2(即baseDuration * 2)。G/(或G/A/)表示斜杠分音,时长减半(即baseDuration / 2)。
- 基础拍长
- 正弦波合成:利用物理公式
sin(2 * pi * freq * t),实时计算每个音频采样点(Sample)的振幅,生成人耳能听到的纯音。