在 Sonic Pi 中,旋律(Melody)不仅仅是一串 play 命令的列表,它是时间、音高与能量在二维坐标系上的函数。
要写出像样的旋律,你需要关注三个物理属性,并学会控制它们:
动机是音乐中最小的、具有独立性格的结构单元。 初学者的最大误区是试图写出长达 16 小节不重复的“流利旋律”,结果往往是一笔流水账。大师的做法是:写好 3 个音,然后重复它、折磨它、改变它。
假设你的种子动机是 C 大调的 [:c4, :d4, :e4] (Do-Re-Mi),节奏是 [0.5, 0.5, 1]。在 Sonic Pi 中,我们可以通过算法逻辑通过以下方式发展它:
+ 2(在调内移动)。[:e4, :f4, :g4]。这是推进情绪最稳妥的方法。.reverse。[:e4, :d4, :c4]。常用于句子的“回复”或“收尾”。+2 变成 -2。[:c4, :b3, :a3]。这种变化听起来有联系,但情绪截然不同(通常更阴郁)。sleep 时间 * 2 或 / 2。Rule of Thumb (动机法则) 一段 8 小节的旋律 = 动机 A (2小节) + 动机 A’ (模进/重复, 2小节) + 动机 B (发展/破坏, 2小节) + 结尾 (2小节)。
华语古风和戏腔旋律非常讲究“句法”。它不像欧美流行音乐那样一直用 16 分音符填满,而是像念诗一样,有逗号和句号。
Question (问) - 悬挂感 Answer (答) - 归属感
/ \ / \
/ "床前明月光..." \ / "疑是地上霜..." \
/ \ / \
---|--------------------------|-------|------------------------|--> Time
^ ^ ^ ^ ^ ^
起音 高潮点 半终止 起音 平稳区 全终止
(Do) (Sol/La) (Re/Sol) (Do/Mi) (Do)
sleep 总和要大于音符的 release 总和。为什么你的代码写出来的旋律像“机器人”?因为真实的乐器(笛子、二胡)和人声(戏腔)从来不准时、从来不直。
我们要用 Sonic Pi 的参数来模拟这些“不完美”。
在主音到达之前,极快地掠过一个装饰音。
# 模拟倚音:从 D (re) 快速“跌落”到 C (do)
play :d5, release: 0.1, amp: 0.4 # 极短、稍弱
sleep 0.05 # 几乎不占时间
play :c5, release: 2, amp: 1 # 主音
戏腔从不直着唱一个音,它总是“滑”向那个音,或者在尾音“滑”走。
note_slide 属性。
# 戏腔尾音下坠:从 A 唱起,慢慢滑向 E,同时音量消失
s = play :a4, sustain: 2, note_slide: 1, amp: 1, amp_slide: 2
control s, note: :e4, amp: 0 # 音高滑向 E,音量滑向 0
长音如果不动,就是死音。
control 手动制造波动。虽然 scale(:c, :pentatonic) 很方便,但要写出味道,你需要理解两种核心调式:
进阶:偏音的用 (4 和 7) 真的不能用 4 和 7 吗?可以,但要作为经过音(Passing Tone)。
- 4 (Fa):通常作为 3 和 5 之间的桥梁 (
3 -> 4 -> 5或5 -> 4 -> 3)。- 7 (Ti):通常作为 6 和 1 之间的桥梁,或者用于转调的瞬间。
- 切记:不要停在 4 或 7 上(除非你想表达极度的怪诞或异域风情)。
久石让(Joe Hisaishi)的旋律之所以动人,往往是因为他不做作。
Hans Zimmer 的逻辑完全不同。对他来说,旋律不是用来哼唱的,而是用来服务节奏和音响的。
tick 和 look 在一个紧凑的 live_loop 中循环 3-4 个音。Du... Du... Du...,靠的是背后的和声在变,从而改变这个音的色彩含义。在 Sonic Pi 中写旋律,建议把音高和时值分开管理。这比 play_pattern_timed 更灵活,允许你对“音高”做数学运算而不影响“节奏”。
# 推荐工程写法:分离数据与执行
use_bpm 68
# 1. 定义数据 (Rings)
# 一个典型的古风羽调式动机
m_notes = (ring :a4, :c5, :d5, :e5, :d5, :c5)
# 对应的时值 (注意最后是长音)
m_times = (ring 0.5, 0.5, 1.0, 0.5, 0.5, 3.0)
# 2. 定义演奏逻辑 (Function)
define :play_melody do |notes, times, transpose_amount|
# 获取当前需要播放的次数 (这里简单取 note 的长度)
notes.length.times do
# 获取音符和时间
n = notes.tick(:n)
t = times.tick(:t)
# 播放 (加入人性化随机)
if n != :r # 如果不是休符
use_synth :pluck
# 技巧:release 略长于 sleep,制造连奏感(Legato)
play n + transpose_amount, release: t * 1.2, amp: rrand(0.7, 0.9), cutoff: rrand(80, 110)
end
sleep t
end
end
# 3. 在 Live Loop 中组装结构
live_loop :main_theme do
# 第一次:原样播放 (A段)
play_melody(m_notes, m_times, 0)
# 第二次:模进,低八度重复 (A'段) - 增加厚度
play_melody(m_notes, m_times, -12)
# 第三次:移高五度,推向高潮 (B段)
play_melody(m_notes, m_times, 7)
stop # 演示用,播放完停止
end
[:c4, :e4, :f4, :g4]。
:a4, :c5, :d5, :e5, :g5 (A 小调五声) 写一段 4 小节旋律,要求最后一个音落在 :a4 上。:e5 之前,先快速播放一个 :g5 (时值 0.1),模拟笛子的装饰音。live_loop。:a4 (持续 4 拍)。control 命令,第 2 拍开始,让音高缓慢滑向 :e4,同时让音量 amp 缓慢滑向 0。模拟一句戏腔唱完后的叹息。:c3 重复 (Ostinato)。:c5 和 :d5 两个音交替。cutoff 从 60 增加到 120,让层 2 的 amp 从 0.2 增加到 1.0。体验这种非旋律性的能量推进。tick 与 look 的混乱:
live_loop 里写了 notes.tick 和 times.tick,它们会各自推进,这是对的。look。play notes.tick 接着 sleep times.tick。(这样会导致节奏列表跑得比音高列表快一格,或者两者错位)。n = notes.tick 然后 t = times.look。nil。建议用 :r 或 0 代表休止符,然后在 play 之前加 if n != :r 的判断。note_slide 的时间长于音符的实际时长 (sustain + release),那么滑音还没滑完,声音就没了。确保 sustain 足够长以容纳滑音效果。1 2 3 5 6 旋律,底下配上 IV 级和弦或 VI 级和弦,现代感和电影感马上就出来了。