在以前有关Core Audio的文章中,我们正在使用原始音频和音频缓冲区。 能够使用MIDI(最流行的音乐符号协议)同样重要。

MIDI文件一目了然,可以包含乐谱所包含的信息-有关乐曲的常规信息(速度,拍号),还包含有关乐曲中每个音符的信息。 今天,我们将不详细讨论MIDI文件格式,而是专注于创建乐器。
与现实生活中的乐器一样,我们需要能够解释我们的音乐理念并再现某些声音的东西。 有许多方法可以实现这一点-我们可以尝试使用不同的声音生成器(振荡器是最简单的生成器之一)来合成这种声音-或可以尝试使用样本。 之前录制的声音。 实际上,许多现代专业音乐图书馆都采用了这种想法:它们以很高的质量录制真实的乐器(通常有许多样本用于提供不同的动态表达)。 而且这些声音会被播放。
复制这些样本的机制称为采样器 ,今天我们很幸运,因为我们可以为此使用AVAudioUnitSampler 。 在最简单的情况下,采样器会根据请求播放一种声音,而不是“内插”声音。 有时对于某些乐器来说已经足够好了,但对于某些乐器来说却是完全不能接受的,例如鼓组采样器通常需要为每个音符提供不同的采样。 对于其他高质量的乐器,通常每5-6个半音有一个采样就足够了。
实际上, AVAudioUnitSampler支持这两种想法。 最近添加了一个非常方便的API,您可以在其中简单地指定要播放的每个音符的声音的URL数组。 除此之外,还有一个API,可以每隔一定的声音间隔提供一种声音(也可以在某些音乐范围上禁用乐器-这在现实世界中通常是正确的:您不能在短笛上以相反的八度演奏)。
如果要使用后者,则需要创建aupreset ,可以使用Apple的AU Lab工具来完成(可以在撰写本文时使用此链接下载该工具)。 有关如何执行此操作的大量演练。
我们今天要做的只是尝试加载一种声音。 让我们从平常的业务开始:初始化我们的引擎,在这种情况下,仅初始化一个节点。 而且比起将采样器连接到主混音器,是因为我们要播放音乐。
让引擎= AVAudioEngine()
让采样器= AVAudioUnitSampler()
engine.attach(采样器)
engine.connect(采样器,至:engine.mainMixerNode,格式:engine.mainMixerNode.outputFormat(forBus:0))
在我们应用程序的项目文件夹中,我们有一个Yamaha Rhodes声音样本。 让我们将其用作我们的主要示例。
做{
如果让url = Bundle.main.url(forResource:“ d5”,withExtension:“ wav”){
尝试sampler.loadAudioFiles(at:[url])
}
尝试engine.start()
} {
打印(“无法启动引擎”)
}
如您所见,sampler接收到一个URL数组。 因此,如果您想同时播放多个声音,则可以这样做。
引擎正在运行,但目前我们听不到任何声音。 如前所述,我们需要告诉我们的乐器弹奏一些东西。 有一个方便的方法: startNote 。 它接收音符,速度和通道。 所有参数的类型均为UInt8,那么我们需要什么样的数字? 首先很简单-它只是音符的MIDI键-您总是可以像这样在图像上查找它。

而且速度不是此音符的初始力或音量,在这种方法中,它是介于0到127之间的数字-从最安静到最大。 因此,为了根据表来收听C2,我们可以执行以下操作:
sampler.startNote(36,withVelocity:90,onChannel:0)
构建仪器的另一个有趣的部分是它的界面。 实际上,我们可以模拟任何乐器-画钢琴的琴键或吉他的琴键。 但是,让我们开始一些更简单但仍然很酷的事情。 让我们向这个人寻求帮助(他看起来像是一位重要的独立音乐家,所以应该没事)。

如果您没有识别出设备或演奏者,那就是Lev Sergeyevich Termen,他站在他自己的发明旁边: Theremin或termenvox。 首批批量生产的电子仪器之一。 您只需要用手就可以在termenvox上玩游戏,而无需触摸任何东西。 一只手控制频率(音高),另一只手控制振幅(音量)。 但是让我们自己问一下发明人。
它也广泛用于更现代的音乐中。 我刚刚在斯德哥尔摩音乐博物馆(Terminalvox)上玩过termenvox(看起来好像他们最近重新命名了),但是几年前,这是一个博物馆,里面有三层楼的乐器可以玩,我在那里花了好几个小时。 他们在那里有Termenvox,玩它很有趣。
因此,让我们构建类似的东西。 让我们将应用程序窗口的整个视图视为可以玩的物理空间。 如果要在Mac上支持“鼠标”单击,我们需要执行以下操作:
内部重写函数mouseDown(带有事件:NSEvent){
让间距=浮动(event.locationInWindow.x / view.bounds.size.width)
让体积=浮动(event.locationInWindow.y / view.bounds.size.height)
samplerEngine.send(pitch:pitch,volume:volume)
}
因此,我们将有效地将0…1值发送到我们的采样器接口,现在我们只需要解释它即可。
我将仅使用假想钢琴的黑键进行一点作弊,所以无论我们演奏什么音符,它在任何顺序上都听起来很好。
func send(pitch:Float,volume:Float){
let pentatonicKeys = [22、25、27、30、32、34、37、39、42、44、46、49、51、54、56、58、61、63、66、68、70、73、75]
//这里我们根据传入的0 ... 1音调值在此数组中获取最接近的索引
让noteIndex = Int(0 + Float(pentatonic.count-1)* pitch)
让间距= UInt8(pentatonicKeys [noteIndex])
让速度= UInt8(30 + 60 *体积)
sampler.startNote(pitch,withVelocity:velocity,onChannel:0)
}
就是这样,它听起来应该像这样。
而且,如果您曾经在iPhone或iPad上使用GarageBand玩耍,那么这里有一个不错的采样器工具,您可以在其中录制自己的声音,然后再进行播放。 我们可以做到这一点! 在上一篇文章中,我们已经学习了如何将声音录制到文件中,所以让我们做到这一点,并为采样器提供新的文件url!
let path = URL(fileURLWithPath:“ \(NSTemporaryDirectory())sample.caf”)
//开始录音
做{
engine.stop()
让format = engine.inputNode.inputFormat(forBus:0)
让文件=尝试AVAudioFile(forWriting:path,settings:format.settings)
engine.inputNode.installTap(onBus:0,bufferSize:2048,format:format,block:{
(buffer:AVAudioPCMBuffer !, when:AVAudioTime!)在
做{
尝试file.write(来自:缓冲区)
} {
打印(“无法写入文件”)
}
})
尝试engine.start()
} {
打印(“无法记录文件”)
}
//停止录制
engine.inputNode.removeTap(onBus:0)
做{
尝试sampler.loadAudioFiles(at:[path])
} {
打印(“无法加载样本”)
}
现在是这样的:
和往常一样,您可以在此处下载代码。 如果您想尝试使用采样创建自己的乐器,那将是一个好的开始。 谢谢!