- 编程语言
- 音频
- CLI
【免费下载链接】alda
A music programming language for musicians. :notes:
本篇技术指南围绕 Alda 音乐编程语言的核心基础——音符(Notes)展开,系统讲解音符的三大组成要素:八度组、时值与音名/变音记号,并深入介绍 Alda 独有的高级节奏能力(非标准音符时值、十进制时值、毫秒/秒时长)。读完本篇,你将掌握用 Alda 编写可运行乐谱的基本功,能够独立写出从单音符到带调号的完整音阶与旋律片段,并理解其底层解析与事件生成原理。本文对应仓库中的官方文档 doc/doc_zh_cn/notes_zh_cn.md。
音符语法总览
Alda 的音符语法深受 MML(Music Macro Language) 启发。一个音符由以下部分组成:
- 从
a到g的一个音名(必选); - 任意数量的临时变音记号(可选);
- 一个音符时值标记(可选)。
即:
音名 [变音记号] [时值标记]例如c表示默认八度、默认时值的 C 音,c+4表示升 C 的四分音符。在扫描器层面,c会被识别为NoteLettertoken,+为Sharp,-为Flat,_为Natural,数字时值则生成NoteLength/NoteLengthMs/NoteLengthSecondstoken,具体实现见 client/parser/scanner.go。
八度组(Octave)
西方音乐理论将音高划分为每 12 个音一组(十二平均律)的重复八度组,例如升序的c c# d d# e f f# g g# a a# b(再往上是高八度的c c# d ...)。音名(如 C#)与八度的组合决定了音符的频率(Hz)。八度组用数字表示,通常在 1 到 7 之间,对应科学音高记号(也称国际音高记号)。例如中央 C 与 A440 都位于第 4 个八度组——这也是 Alda 的默认八度组。
与 MML 一样,八度组与音符本身是分开设置的:八度不是"附加"在音符上的,而是每个音符在解析时查看当前的八度组来确定自己的音高。
设置八度组有两种方式:
| 语法 | 含义 |
|---|---|
o5 | 将八度组设置为 5(任何整数都可以跟在o后面) |
< | 将当前八度组减少 1 |
> | 将当前八度组增加 1 |
o指令、<与>在扫描器中分别对应OctaveSet、OctaveDown、OctaveUp三种 token(见 client/parser/scanner.go),并最终转化为模型层OctaveSet/OctaveUp/OctaveDown这类属性更新(AttributeUpdate,见 client/model/attributes.go)。解析器的测试用例 client/parser/octaves_test.go 验证了包括>>>(连续上移三个八度)、><>(上移→下移→上移)、以及>c、c>这类"八度指令紧跟音符/音符紧跟八度指令"在内的各种写法。
音符时值(Duration)
在 Alda 中,音符时值通常以标准音乐符号中的音符长度表示,采用数字形式:4是四分音符,2是二分音符,1是全音符,以此类推。
附点(Dots)
音符后面可以添加任意数量的点.,其作用与标准音乐符号中的附点一致——将音符的时值延长到其原值的 1.5 倍(即延长其一半的时值)。后续每加一个点,延长的时值都是上一个点所延长时值的一半:
| 写法 | 含义 | 拍数 |
|---|---|---|
2 | 二分音符 | 2 拍 |
2. | 附点二分音符 | 3 拍(2 × 1.5) |
2.. | 复附点二分音符 | 3.5 拍(2 × 1.5 + 2 × 0.5 × 0.5) |
模型层通过NoteLength{Denominator, Dots}表达这一概念,拍数计算公式为(4 / Denominator) * (2 - 2^(-Dots)),见 client/model/duration.go 与Beats()实现(client/model/duration.go)。单元测试 client/model/duration_test.go 验证了这些换算:四分音符 = 1 拍、附点四分音符 = 1.5 拍、全音符 = 4 拍、附点全音符 = 6 拍、复附点全音符 = 7 拍。
连音线(Tie)
音符时值也可以用连音线语法~相加,例如4~4表示两个四分音符连在一起,总共 2 拍。在扫描器中~生成Tietoken(见 client/parser/scanner.go),在模型层,Duration由若干DurationComponent相加得到,Beats()与Ms()都会把所有组件求和(见 client/model/duration.go)。
默认时值的"粘性"跟踪
当音符在乐谱中按顺序解析时,Alda 会持续跟踪当前八度组和当前默认音符时值。每当指定新的八度组或音符时值,那些数据就成为新的默认值;后续未显式写明时值的音符,都会沿用这一默认时值。
在每个乐器部分的开头,默认八度组为4,默认音符时值为4(即四分音符,1 拍)。源码层面,"时值粘性"由 client/model/note.go 中的effectiveDuration与updateDefaultDuration实现:若音符未指定时值,则使用该乐器部分当前默认的part.Duration;若指定了时值,则该时值同时被写回为部分的新默认值。这意味着你可以在乐谱中先写一个c4,后面的d e f都会自动按四分音符演奏,直到下一个显式时值出现。
高级节奏(Advanced Rhythms)
Alda 在节奏表达上有几个超越传统记谱的独特能力:
1. 非标准数字时值。你可以使用非 2 的幂的数字作为音符时值标记。例如6表示在 4/4 拍中持续 1/6 小节的音符。在标准记谱中没有"六分音符"这种东西,这种时值通常会被记作四分音符三连音中的一个音符;但在 Alda 中,"六分音符"不一定是三连音的一部分,这为创造更多有趣的节奏提供了可能性。
2. 非整数(十进制)音符时值。扩展到上述概念,Alda 允许小数音符时值:例如c0.5(西方古典记谱中的双全音符)的时值是c1(全音符)的两倍。这些数字不必是 2 的幂,例如c2.4同样是合法的。扫描器的parseNoteLength会先消费整数部分,若遇到小数点后跟数字则继续消费小数部分,最终把整个数值作为denominator解析(见 client/parser/scanner.go)。
3. Cram 表达式。Alda 还提供了一种指定节奏的替代方法——cram 表达式,用于在一段时间内塞入指定数量的音符。
4. 毫秒与秒。音符时值还可以用毫秒(ms)和秒(s)表示,并且可以随意与标准音符长度混搭:
c350ms # 持续350毫秒的C音符 d2s # 持续2秒的D音符 e2s~200ms # 持续2秒+200毫秒的E音符 f300ms~4. # 持续300毫秒+附点四分音符的F音符扫描器在数字后遇到s且其后是合法分隔符时生成NoteLengthSecondstoken,遇到ms时生成NoteLengthMstoken(见 client/parser/scanner.go)。模型层对应NoteLengthMs与NoteLengthSeconds两种时值组件:毫秒组件直接返回自身毫秒数,秒组件乘以 1000 转换为毫秒(见 client/model/duration.go)。注意这两种组件无法直接换算为"拍数"(Beats()会直接 panic,见 client/model/duration.go),但它们可以自由地与标准音符时值通过~组合成同一个Duration。
音符格式与临时变音记号
一个音符的完整格式为:从a到g的一个字母 + 任意数量的临时变音记号(可选)+ 音符时值标记(可选)。
降号和升号分别使音高降低/升高半音。在 Alda 中,降号表示为-,升号表示为+。你可以使用多个升号或降号,甚至把它们组合起来使用,例如:
c+= 升 Cc++= 双升号 C = Db-= 降 B
解析器测试 client/parser/notes_test.go 中包含了c+(升音)、b-(降音)、c++(双升音)、b--(双降音)等用例,验证了任意数量变音记号的累积效果。在模型层,LetterAndAccidentals保存音名与变音记号列表,CalculateMidiNote会将每个升号 +1、每个降号 -1 得到最终的 MIDI 音高(见 client/model/pitch.go)。
用调号代替逐个升降号
对于非 C 大调的乐曲,如果每个不在调内的音都需要写升/降号,重复书写会非常繁琐。此时更推荐使用调号,为每个需要的音名预设升/降号以匹配调性,乐谱会按定义的升降号自动演奏。下文示例中演示了具体用法。
用临时变音记号覆盖调号
要覆盖调号预设的降号/升号,可以添加临时变音记号:+/-使音符临时升调/降调;_代表还原号,强制音符为自然音。例如c_是 C 自然音,无论调号预设中给 C 音名设置了什么升降号。这对应扫描器中的Naturaltoken 与模型层Accidental枚举中的Natural(见 client/model/pitch.go)。
完整示例:B 大调音阶
以下是 1 个八度的 B 大调音阶,从第 4 个八度组开始先上行后下行:
o4 b4 > c+8 d+ e f+ g+ a+ b4 a+8 g+ f+ e d+ c+ < b2.第二个示例在开头处预设了 B 大调中被升高的那些音名(f+ c+ g+ d+ a+),这样乐谱中就不必重复书写升降号,音符会自动以预设的升降号演奏:
(key-signature "f+ c+ g+ d+ a+") o4 b4 > c8 d e f g a b4 a8 g f e d c < b2.仓库示例 examples/key_signature.alda 展示了更完整的调号实战:先用字符串形式设置(key-signature "f+ c+ g+"),再用c_、f_、g_还原号覆盖个别音,随后切换到'(g minor)小调调号,最后使用列表形式'(e (flat) b (flat))指定降号。调号相关属性的完整说明(值类型、缩写key-sig、支持的调式等)见 doc/doc_zh_cn/attributes_zh_cn.md。
源码层面的实现原理
扫描与解析
从源码结构看,Alda 的音符解析分为两个阶段:扫描器 client/parser/scanner.go 先将源码文本切分为 token(NoteLetter、Sharp、Flat、Natural、NoteLength、OctaveSet、OctaveUp、OctaveDown、Tie等),再由语法解析器组合成 AST 更新(见 client/parser/parser.go)。isNoteLetter限定只有a到g是合法的音符字母(client/parser/scanner.go),followsNoteLetter决定一个字母后可以跟随什么字符(数字、变音记号、~连音线、</>八度移动等),从而避免歧义(client/parser/scanner.go)。
时值如何变成真实时长
在模型层,每个音符最终会被转换为带绝对时间信息的NoteEvent(MIDI 音符号、偏移、时长、可听时长、音量、声像等),见 client/model/note.go。Note.UpdateScore会为每个当前乐器部分计算时长:标准音符时值依据Beats()换算拍数,再结合part.Tempo得到毫秒数(60000 / tempo为每拍毫秒数,见 client/model/duration.go);未连音(非 slur)的音符还会乘以part.Quantization得到"可听时长"。这些换算关系可以在 client/model/duration_test.go 的测试用例中看到——例如附点音符、全音符到拍数/毫秒的转换都有一一对应的断言。
变音记号与 MIDI 音高
LetterAndAccidentals.CalculateMidiNote展示了完整求音高流程:先按(octave + 1) * 12 + 音名音程计算基准 MIDI 音符号,若音符未显式给出变音记号则取调号keySignature[NoteLetter]中的默认升降号,否则使用音符自带的变音记号列表逐项加减,最后叠加乐器移调(见 client/model/pitch.go)。这也印证了文档中"临时变音记号覆盖调号"的行为:只要音符自带+/-/_,调号即被忽略。
至此,你已经掌握了 Alda 音符语法的全部核心内容:八度组(o/</>)、标准与附点/连音时值、高级节奏(非标准数字、小数、ms/s)、变音记号与调号的配合使用。建议结合仓库中的 examples/key_signature.alda、examples/dynamics.alda 等示例,以及 cram 表达式、属性 文档继续深入,配合alda play命令(见 client/cmd/play.go)实际聆听效果,逐步构建完整的 Alda 作曲工作流。
- 编程语言
- 音频
- CLI
【免费下载链接】alda
A music programming language for musicians. :notes:
相关推荐
Alda 和弦(Chords)完全指南:同时发声音符的语法、时值规则与实战应用
Alda 和弦(Chords)完全指南:同时发声音符的语法、时值规则与实战应用 导读 在和弦是音乐编程语言 Alda 中最基础也最常用的结构之一,它允许你将多个
编程语言音频CLI使用 AWS SDK for Rust 操作 AWS Elemental MediaPackage:Channel 与 Origin Endpoint 的列举实战
使用 AWS SDK for Rust 操作 AWS Elemental MediaPackage:Channel 与 Origin Endpoint 的列举实
编程语言音频CLIAlda 和弦(Chord)语法完全指南:同时发声的音符组合与最短时值推进机制
Alda 和弦(Chord)语法完全指南:同时发声的音符组合与最短时值推进机制 和弦(chord) 是 Alda 音乐编程语言中表示多个音符在同一时刻开始发声的
编程语言音频CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考