共计 6210 个字符,预计需要花费 16 分钟才能阅读完成。
MIDI,MIDI 不是音频数据
MIDI 乐器数字接口, Musical Instrument Digital Interface
MIDI 计算机能了解的乐谱,计算机和电子乐器都能够解决的乐器格局
MIDI 不是音频信号,不蕴含 pcm buffer
通过音序器 sequencer,联合音频数据 / 乐器,播放 MIDI Event 数据
(通过音色库 SoundFont,播放乐器的声音)
通过 AVAudioSequencer,简略播放
连贯 AVAudioEngine 的输出和输入,
输出 AVAudioUnitSampler → 混频器 engine.mainMixerNode
→ 输入 engine.outputNode
拿 AVAudioEngine,创立 AVAudioSequencer,就能够播放 MIDI 了
配置 AVAudioEngine 的输入输出
- 输出 AVAudioUnitSampler → 混频器
engine.mainMixerNode
// 连贯输出、输入
var engine = AVAudioEngine()
var sampler = AVAudioUnitSampler()
engine.attach(sampler)
// 节点 node 的 bus 0 是输入,// bus 1 是输出
let outputHWFormat = engine.outputNode.outputFormat(forBus: 0)
engine.connect(sampler, to: engine.mainMixerNode, format: outputHWFormat)
guard let bankURL = Bundle.main.url(forResource: soundFontMuseCoreName, withExtension: "sf2") else {fatalError("\(self.soundFontMuseCoreName).sf2 file not found.")
}
// 载入资源
do {
try
self.sampler.loadSoundBankInstrument(at: bankURL,
program: 0,
bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB),
bankLSB: UInt8(kAUSampler_DefaultBankLSB))
try engine.start()} catch {print(error) }
- 混频器
engine.mainMixerNode
→ 输入engine.outputNode
,不须要解决,就用 AVAudioEngine 默认的
用 AVAudioSequencer,播放 MIDI
AVAudioSequencer 能够用不同的音频轨道 track,对应不同的乐器声音
tracks[index]
指向不同的音频产生节点
var sequencer = AVAudioSequencer(audioEngine: engine)
guard let fileURL = Bundle.main.url(forResource: "sibeliusGMajor", withExtension: "mid") else {fatalError("\"sibeliusGMajor.mid\"file not found.")
}
do {try sequencer.load(from: fileURL, options: .smfChannelsToTracks)
print("loaded \(fileURL)")
} catch {fatalError("something screwed up while loading midi file \n \(error)")
}
// 这里解决的,比较简单
for track in sequencer.tracks {track.destinationAudioUnit = self.sampler}
sequencer.prepareToPlay()
do {try sequencer.start()
} catch {print("\(error)")
}
AudioKit 源代码中,用的是 MusicSequence 和 MusicPlayer
MIDI 让枯燥的音频,赋有节奏感
调用:
// 音频播放引擎
let engine = AudioEngine()
// 音频输出,采纳 AVAudioUnitSampler
let drums = MIDISampler(name: "鼓点")
// 音序器 sequencer,播放 MIDI Event
let sequencer = AppleSequencer(filename: "4tracks")
// 连贯输入输出
engine.output = drums
do {try engine.start()
} catch {// ...}
do {let bassDrumURL = Bundle.main.resourceURL?.appendingPathComponent("Samples/bass_drum_C1.wav")
let bassDrumFile = try AVAudioFile(forReading: bassDrumURL!)
let clapURL = Bundle.main.resourceURL?.appendingPathComponent("Samples/clap_D#1.wav")
let clapFile = try AVAudioFile(forReading: clapURL!)
// ...
// 初始化其余音频文件
// 给音频输出,调配音频资源
try drums.loadAudioFiles([bassDrumFile,
clapFile,
// ... 其余音频文件 ])
} catch {//...}
以上,一次简略的音频文件播放,就成了
音频文件上退出 MIDI 成果,调整每个音轨 track 上的音效,
音轨 track 不是通道 channel, 一个 channel 能够有多个 track
MIDI 成果,播放开始工夫、播放持续时间、播放音量(velocity 速度)和音高 note
- 音高 note 的范畴是 0 ~ 127
- 播放音量 volume,应用 velocity 来形容,他的值也在 0 ~ 127 之间
有时候,不同的 velocity,在乐器上,产生不同的音色
sequencer.clearRange(start: Duration(beats: 0), duration: Duration(beats: 100))
// 音序器的输入,指向音频输出的 MIDI 入口
sequencer.setGlobalMIDIOutput(drums.midiIn)
// 循环播放,MIDI 的播放工夫,能够按秒,也能够按 beat 拍子
sequencer.enableLooping(Duration(beats: 4))
// 这里设置播放速度
sequencer.setTempo(150)
sequencer.tracks[0].add(noteNumber: 24, velocity: 80, position: Duration(beats: 0), duration: Duration(beats: 1))
sequencer.tracks[0].add(noteNumber: 24, velocity: 80, position: Duration(beats: 2), duration: Duration(beats: 1))
sequencer.tracks[1].add(noteNumber: 26, velocity: 80, position: Duration(beats: 2), duration: Duration(beats: 1))
// ...
// 配置 sequencer.tracks[2],和 sequencer.tracks[3]
// 播放
sequencer.play()
AudioKit 源代码实现:
AppleSequencer 是对 MusicSequence、MusicTrack(MusicTrackManager)和 MusicPlayer 的封装,
MusicTrack 通过 MusicTrackManager 的封装,应用
初始化
新建 sequence,加载 MIDI 文件,提供播放资源
新建 MusicPlayer,来播放 MIDI 文件
class AppleSequencer: NSObject {
/// Music sequence
open var sequence: MusicSequence?
/// Array of AudioKit Music Tracks
open var tracks = [MusicTrackManager]()
/// Music Player
var musicPlayer: MusicPlayer?
/// 初始化
override public init() {
// 初始化音序器
NewMusicSequence(&sequence)
// setup and attach to musicplayer
// 初始化音乐播放器
NewMusicPlayer(&musicPlayer)
if let existingMusicPlayer = musicPlayer {
// 把播放器,关联到音序器
MusicPlayerSetSequence(existingMusicPlayer, sequence)
}
}
}
初始化调用,怎么走
// 通过文件名,实例化
public convenience init(filename: String) {
// 这个就是,上一步
self.init()
loadMIDIFile(filename)
}
// 通过文件名,加载 MIDI
public func loadMIDIFile(_ filename: String) {
// 文件名,转包 url
let bundle = Bundle.main
guard let file = bundle.path(forResource: filename, ofType: "mid") else {Log("No midi file found")
return
}
let fileURL = URL(fileURLWithPath: file)
loadMIDIFile(fromURL: fileURL)
}
// 通过包 url,加载 MIDI
public func loadMIDIFile(fromURL fileURL: URL) {
// ...
// 重置状态
if let existingSequence = sequence {
// 把 MIDI 文件,加载到音序器
let status: OSStatus = MusicSequenceFileLoad(existingSequence,
fileURL as CFURL,
.midiType,
MusicSequenceLoadFlags())
if status != OSStatus(noErr) {
// 谬误日志
// ...
}
}
initTracks()}
初始化音轨,
实现 open var tracks = [MusicTrackManager]()
的初始化
func initTracks() {
var count: UInt32 = 0
if let existingSequence = sequence {MusicSequenceGetTrackCount(existingSequence, &count)
}
for i in 0 ..< count {
var musicTrack: MusicTrack?
if let existingSequence = sequence {
// 通过音序器 sequence,创立音轨 MusicTrack
MusicSequenceGetIndTrack(existingSequence, UInt32(i), &musicTrack)
}
if let existingMusicTrack = musicTrack {tracks.append(MusicTrackManager(musicTrack: existingMusicTrack, name: "InitializedTrack"))
}
}
// ...
// 循环播放管制
}
给音轨增加播放资源
调用局部
sequencer.setGlobalMIDIOutput(drums.midiIn)
class AppleSequencer
外面,
对立设置,最简略,
就是把音频输出,塞给每一个音轨
public func setGlobalMIDIOutput(_ midiEndpoint: MIDIEndpointRef) {
for track in tracks {track.setMIDIOutput(midiEndpoint)
}
}
class MusicTrackManager
外面,
给音乐音轨,增加播放资源
public func setMIDIOutput(_ endpoint: MIDIEndpointRef) {
if let track = internalMusicTrack {MusicTrackSetDestMIDIEndpoint(track, endpoint)
}
}
播放音序器解决后的音频,很简略
/// Play the sequence
public func play() {
if let existingMusicPlayer = musicPlayer {MusicPlayerStart(existingMusicPlayer)
}
}
MIDISampler, 音频输出
MIDISampler 继承自 AppleSampler,
AppleSampler 封装了一个 AVAudioUnitSampler
, 次要做 3 件事,
- 音频资源文件加载
- 根底的播放性能,play / stop
- 根底的播放成果管制,音量、左右声道
初始化音频采样
open class MIDISampler: AppleSampler, NamedNode {
/// MIDI 输出,也就是他采样的输入
open var midiIn = MIDIEndpointRef()
/// 起个名字
open var name = "MIDI Sampler"
/// 初始化
public init(name midiOutputName: String? = nil) {super.init()
name = midiOutputName ?? name
enableMIDI(name: name)
// ...
// 其余事件
}
/// 提供数据给输入 midiIn(MIDIEndpointRef)public func enableMIDI(_ midiClient: MIDIClientRef = MIDI.sharedInstance.client,
name: String = "MIDI Sampler") {CheckError(MIDIDestinationCreateWithBlock(midiClient, name as CFString, &midiIn) { packetList, _ in
// 音频数据处理
for e in packetList.pointee {e.forEach { (event) in
if event.length == 3 {
do {try self.handle(event: event)
} catch let exception {// 谬误日志}
}
}
}
})
}
}
MIDI 数据,就是 event
private func handle(event: MIDIEvent) throws {try self.handleMIDI(data1: event.data[0],
data2: event.data[1],
data3: event.data[2])
}
剩下的,见 github repo