Highlight
第二章的挑战是:用代码生成特定频率的音调(tone),通过设备扬声器播放出来。和第一章的「检测声音」不同,这一章要求你「生成声音」。
核心内容
Swan’s Quest 第一章让玩家接触 accessible interfaces。第二章把场景带回 Lizard 和 Swan 的卷轴谜题:玩家要为 Swan 播放一串 musical notes,才能通过挑战。
这件事的难点不是播放一个声音文件。挑战要求你按频率生成 tone,再按固定节奏依次播放。演讲把入口压缩成两个 API:ToneOutput 负责发声,Timer 负责按时间推进音符序列。
单个 440 Hz 的 middle A 很容易响起来。真正要完成挑战,还要在 400 毫秒一拍的循环里切换音符,结束时停止 ToneOutput、取消 timer,并调用 endPerformance() 告诉 Swan 表演完成。
最后的 side quest 继续沿用同一套代码。Swan 给的是 C-major scale,练习题要求改成 F-major scale。演讲给出的推理很具体:从 F4 开始,复用已有频率,需要升八度时把频率乘以 2,例如把 A4 的 440 Hz 变成 A5 的 880 Hz。
详细内容
ToneOutput 负责生成连续声音
(01:09)ToneOutput 是这场挑战的发声入口。演讲说明它每秒生成 44,100 个 samples,让人耳听到连续声音。
// ToneOutput.swift
public class ToneOutput : AURenderCallbackDelegate {
let sampleRate = 44100.0
public func play(tone: Tone) { /**/ }
public func stopTones() { /**/ }
// ...
}
关键点:
sampleRate = 44100.0对应 transcript 中的 44,100 samples a second。play(tone:)创建 tone 对应的信号。stopTones()停止正在播放的 tone。- 这段类型来自 Sonic Workshop,并被放进 Sonic Create,供玩家在自己的项目里使用。
(01:30)传给 ToneOutput 的值是 Tone。它只暴露两个核心参数:pitch 和 volume。
// ToneOutput.swift
public struct Tone: Codable {
public var pitch: Double
public var volume: Double
// ...
}
关键点:
pitch是频率的Double表示。volume也是Double。Codable让 tone 作为简单数据结构保存和传递。- 这让挑战代码不用直接处理底层音频 sample。
先播放一个 middle A
(01:45)第一段可运行代码只做一件事:创建 ToneOutput,构造 440 Hz、音量 0.3 的 tone,然后播放它。
// Play a middle A
import SPCAudio
let toneOutput = ToneOutput()
let middleA = Tone(pitch: 440.0, volume: 0.3)
toneOutput.play(tone: middleA)
关键点:
import SPCAudio引入示例所需的音频类型。ToneOutput()是发声对象。Tone(pitch: 440.0, volume: 0.3)对应 transcript 中的 440 Hz middle A。toneOutput.play(tone: middleA)会让 Playground 发出持续音调。
(02:21)演讲提醒:如果 Playground 代码不停止运行,这个 tone 会一直播放。示例用 DispatchQueue.main.asyncAfter 在 400 毫秒后调用 stopTones()。
// Play a middle A
import SPCAudio
let toneOutput = ToneOutput()
let a4 = Tone(pitch: 440.0, volume: 0.3)
toneOutput.play(tone: a4)
DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.milliseconds(400)) {
toneOutput.stopTones()
}
关键点:
a4仍然是 440 Hz 的 middle A。toneOutput.play(tone: a4)先启动声音。DispatchTimeInterval.milliseconds(400)给声音一个短时长。- 闭包里的
toneOutput.stopTones()负责结束持续播放。
用 Timer 串起多个音符
(02:51)单个延迟适合停止一个音。要连续播放多个音符,演讲推荐 Timer,因为它能按固定间隔重复执行同一段逻辑。
// Play more than one tone
let toneOutput = ToneOutput()
let tones = [
Tone(pitch: 440.00, volume: 0.3),
Tone(pitch: 493.88, volume: 0.3),
Tone(pitch: 523.25, volume: 0.3)
]
var toneIndex = 0
Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in
guard toneIndex < tones.count else {
toneOutput.stopTones()
timer.invalidate()
owner.endPerformance()
return
}
toneOutput.play(tone: tones[toneIndex])
toneIndex += 1
}
关键点:
tones保存 middle A、middle B、middle C 的频率。toneIndex记录下一次要播放数组里的哪一个 tone。withTimeInterval: 0.4对应 transcript 中每 400 毫秒播放下一个音符。guard toneIndex < tones.count到达数组末尾后进入收尾分支。toneOutput.stopTones()停止音频输出,timer.invalidate()停止重复 timer。owner.endPerformance()告诉挑战系统表演已经完成,玩家获得进度。
核心启发
-
做一个音阶练习器 做什么:用按钮选择 C-major、F-major 等音阶,然后按顺序播放每个音。 为什么值得做:session 已经给出 tone 数组、0.4 秒 timer 和升八度时频率乘以 2 的方法。 怎么开始:先复用官方的
tones数组和Timer.scheduledTimer,再把不同音阶映射成不同的Tone(pitch:volume:)列表。 -
做一个听音记忆挑战 做什么:播放三到五个 tone,让用户按顺序复现。 为什么值得做:这正好延伸 Swan’s Quest 中“为 Swan 播放一串 musical notes”的任务。 怎么开始:用
ToneOutput.play(tone:)播放题目,用toneIndex控制进度,用户完成后调用和挑战类似的结束逻辑。 -
做一个频率实验面板 做什么:提供 pitch 和 volume 输入,让用户听到不同频率、不同音量的结果。 为什么值得做:
Tone的公开参数只有pitch和volume,很适合用来理解频率和响度。 怎么开始:把Tone(pitch: 440.0, volume: 0.3)中的两个数字换成控件输入,播放前生成新的Tone。 -
做一个 side quest 辅助器 做什么:把 C-major scale 改写成 F-major scale,并标出需要新增的 B-flat。 为什么值得做:transcript 明确要求从 F4 开始、复用 C-major 代码、用频率翻倍得到高八度。 怎么开始:先保留原有播放循环,只替换
tones数组里的频率;遇到高八度音时用原频率乘以 2。
关联 Session
- Swan’s Quest, Chapter 1: Voices in the dark — Swan’s Quest 第一章,提供第二章之前的挑战背景和 accessible interfaces 入口。
- Swan’s Quest, Chapter 3: The notable scroll — 第三章继续处理音高与音符抽象,官方片段包括
NoteProtocol和多音播放 redux。 - Swan’s Quest, Chapter 4: The sequence completes — 第四章把序列播放推进到 sequencer、instrument、MIDI note 和 track。
- Build a SwiftUI view in Swift Playgrounds — 展示如何在 Swift Playgrounds 中构建和预览 SwiftUI view,并推荐继续体验 Swan’s Quest。
- Create Swift Playgrounds content for iPad and Mac — 讲解如何制作跨 iPad 和 Mac 的 Swift Playgrounds book 内容。
评论
GitHub Issues · utterances