AudioKit 入門教程

原文:AudioKit Tutorial: Getting Started
作者:Colin Eberhardt
同時(shí)感謝:kmyhy


近來手上有一些音頻相關(guān)的開發(fā)工作,搜搜基Hub,目前最為強(qiáng)大拉队,性能屌爆蔑水,編碼炫酷的開源庫也只有AudioKit了贰镣。Raywenderlich也能找到相關(guān)教程垦搬,介于作者是3.0+的教程皮钠,很多代碼都不能跑了,特此整理一趴火的。
本文不僅是一篇iOS開發(fā)教程壶愤,更是一篇精彩的科普文。關(guān)于編程與藝術(shù)的結(jié)合馏鹤,聲學(xué)物理與音樂的碰撞征椒,盡在此文。推薦所有程序員都好好讀一讀它湃累,讓我們的生活除了代碼勃救,還有藝術(shù),還有音樂治力。感謝作者Colin Eberhardt蒙秒。

iOS 設(shè)備提供了豐富的多媒體體驗(yàn),比如絢麗的視覺效果宵统、聲音和可觸摸的交互界面晕讲。盡管能夠使用各種各樣的特性,但作為開發(fā)者马澈,我們更多地關(guān)注了應(yīng)用的視覺設(shè)計(jì)瓢省,而忽略了用戶體驗(yàn)的聲學(xué)效果。

AudioKit是一個(gè)高級(jí)音頻框架痊班,由聲學(xué)設(shè)計(jì)師净捅、程序員和音樂家為 iOS 專門打造。AudioKit底層混合了Swift辩块、Objective-C蛔六、C++C,負(fù)責(zé)和蘋果音頻已硬件的Api打交道废亭。所有神奇(同時(shí)十分復(fù)雜的)技術(shù)都封裝成為極其友好的 Swift Api 国章,你甚至可以直接在XcodePlayground中使用它。

本文無法全面覆蓋 AudioKit 的知識(shí)點(diǎn)豆村。相反液兽,我們會(huì)通過介紹聲音合成和計(jì)算機(jī)聲頻的歷史,來帶你進(jìn)行一次有趣和時(shí)尚的 AudioKit 之旅掌动。通過這種方式四啰,你會(huì)學(xué)到基本的聲學(xué)物理,了解早期的合成器比如電子琴是如何工作的粗恢。最終來到現(xiàn)代柑晒,一個(gè)大混音時(shí)代。

請(qǐng)給自己來一杯咖啡眷射,拖過一張椅子匙赞,開始我們的旅程佛掖!

image

開始

原教程的原始內(nèi)容使用的是 3.4.0 版本,用的是Playground做案例涌庭,本文使用最新的 4.3.0 芥被,用Xcode創(chuàng)建的Swift項(xiàng)目做案例。

港真坐榆,教程的第一步并不是特別雞凍的拴魄。為了在 Playground 中使用 AudioKit,我們必須提前進(jìn)行一些準(zhǔn)備工作席镀。

我們需要先到AudioKit下源碼AudioKit-4.3匹中,或者自行clone:

git clone https://github.com/AudioKit/AudioKit.git

解壓好后用Terminal進(jìn)入AudioKit-4.3文件夾執(zhí)行編譯命令:

$ cd Frameworks
$ ./build_frameworks.sh

編譯時(shí)間較長(zhǎng)(14的頂配編譯的近十分鐘吧),你闊以繼續(xù)閱讀愉昆,或者擼一把职员,或者雞一把麻蹋。

一定要編譯完成跛溉,編譯好后進(jìn)入Playgrounds文件,打開下面的AudioKitPlaygrounds.xcodeproj扮授,這里我們就闊以看官方為我們寫好的示例代碼芳室,提前體驗(yàn)一把 AudioKit 的強(qiáng)大。體驗(yàn)完了別忘了紙巾刹勃,進(jìn)入正題開始教程之旅堪侯。

image

新建一個(gè)名為JorneryPlayground。錄入一下代碼:

import AudioKitPlaygrounds
import AudioKit


let oscillator = AKOscillator()

AudioKit.output = oscillator
try? AudioKit.start()

oscillator.start()

sleep(10)

編譯時(shí)荔仁,你會(huì)聽到大約 10 秒鐘的蜂鳴聲伍宦。你可以點(diǎn)擊 Playground 調(diào)試窗口左下角的 Play/Stop 按鈕停止或運(yùn)行 Playground。

注意:建議打開工程的時(shí)候就直接Command + B編譯一遍乏梁,再跑想看的代碼次洼。如果 Playground 執(zhí)行出錯(cuò),并在 Debug 窗口中出現(xiàn)錯(cuò)誤遇骑,你可以重啟 Xcode卖毁。不幸的是,當(dāng) Playground 和框架一起使用時(shí)落萎,總是容易出現(xiàn)一些小問題亥啦,并且無法預(yù)知。
另外建議把 Run 方式改為 Manually Run练链,Automatically Run太容易故障了翔脱。

振蕩器和聲學(xué)基礎(chǔ)

人類通過物體制造音樂——通過擊打、拖拉或者彈奏等形式——有數(shù)千年的歷史媒鼓。我們的許多民族樂器碍侦,比如鼓粱坤、吉他,已經(jīng)發(fā)明幾個(gè)世紀(jì)了瓷产。電子樂器的第一個(gè)次使用記錄站玄,或者是第一次通過電路發(fā)聲,是 1874 年 Elisha Gray 創(chuàng)下的濒旦,他從事電信行業(yè)株旷。Elisha 發(fā)明了振蕩器,最原始的聲音合成裝置尔邓,你的探索將從這個(gè)東西開始晾剖。

右鍵點(diǎn)擊 Playground,選擇 New Playground Page梯嗽,創(chuàng)建一個(gè)新的 Playground 文件 Oscillators齿尽。

將 Xcode 產(chǎn)生的代碼替換為:

import AudioKitPlaygrounds
import AudioKit


// 1. Create an oscillator
let oscillator = AKOscillator()

// 2. Start the AudioKit 'engine'
AudioKit.output = oscillator
try? AudioKit.start()

// 3. Start the oscillator
oscillator.start()


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

這個(gè) Playground 將沒完沒了地發(fā)出嗶嗶聲——呃,有意思灯节。你可以按 Stop 來停止它循头。
這和前面創(chuàng)建的測(cè)試 Playground 差不多,但這次我們將深入討論細(xì)節(jié)炎疆。
代碼分成了幾個(gè)步驟:

  • 創(chuàng)建一個(gè) AudioKit 振蕩器卡骂,它是一個(gè) AKNode 子類。節(jié)點(diǎn)是構(gòu)成你的音頻序列的主要元素形入。
  • 將 AudioKit 引擎和你最終輸出的節(jié)點(diǎn)關(guān)聯(lián)起來全跨,在這里這個(gè)節(jié)點(diǎn)是你唯一的節(jié)點(diǎn)。音頻引擎就像物理引擎或游戲引擎:你必須啟動(dòng)它并讓它持續(xù)運(yùn)轉(zhuǎn)亿遂,這樣你的音頻序列才能被執(zhí)行浓若。
  • 最后,打開振蕩器蛇数,它會(huì)發(fā)出一條聲波挪钓。

一個(gè)振蕩器會(huì)創(chuàng)建一個(gè)重復(fù)的、或者周期性的無限延續(xù)的信號(hào)苞慢。在這個(gè) Playground 中诵原,AKOscillator 發(fā)出了一個(gè)正弦波。這個(gè)數(shù)字化的正弦波經(jīng)過 AudioKit 處理挽放,直接輸出到你的揚(yáng)聲器或者耳麥绍赛,導(dǎo)致真正的振蕩器以同樣的正弦波進(jìn)行振蕩。聲音通過壓縮你耳朵周圍的空氣傳播到你耳中辑畦,最終你就聽到了這個(gè)煩人的嘯叫聲吗蚌!

image

有兩個(gè)參數(shù)決定了振蕩器發(fā)出的聲音是什么樣子:amplitude - 振幅,它是正弦波的高度并決定聲音的大小纯出,以及 frequency - 頻率蚯妇,它決定了音高敷燎。
在你的 Playground 中,在創(chuàng)建振蕩器之后加入這兩句:

oscillator.frequency = 300
oscillator.amplitude = 0.5

傾聽一會(huì)箩言,你會(huì)發(fā)現(xiàn)現(xiàn)在的音量只是剛才的一半硬贯,而且音高也比剛才低了。頻率單位為赫茲(即每秒周期數(shù))陨收,決定了音符的音高饭豹。而振幅,范圍從 0-1 务漩,決定了音量拄衰。
Elisha Gray 在專利官司中輸給了Alexander Graham Bell,失去了成為電話機(jī)發(fā)明者的機(jī)會(huì)饵骨。但是翘悉,他的偶然發(fā)明振蕩器卻導(dǎo)致第一個(gè)電子樂器專利的產(chǎn)生。

image

許多年以后居触,Léon Theremin 發(fā)明了一個(gè)怪異的電子樂器妖混,至今仍然被使用著。使用特雷門琴饼煞,你可以在這個(gè)樂器上方揮舞手臂來改變電子振蕩器的頻率源葫。如果你不知道怎么形容這個(gè)樂器發(fā)出的聲音诗越,我建議你聽一聽 The Beach Boys 演唱的 Good Vibrations, 這首歌曲中特雷門琴所發(fā)出的獨(dú)特聲音令人記憶深刻砖瞧。

你可以在 Playground 的最后加入以下代碼模擬這種效果:

oscillator.rampDuration = 0.2
oscillator.frequency = 500


import AudioKitUI

AKPlaygroundLoop(every: 0.5) {
    oscillator.frequency = (oscillator.frequency == 500 ? 100 : 500)
}

rampDuration 屬性允許振蕩器在屬性值之間平滑過渡(比如頻率或振幅)。AKPlaygroundLoop 是一個(gè)很有用的實(shí)用函數(shù)嚷狞,允許周期性地執(zhí)行 Playground 中的代碼块促。在這里,你簡(jiǎn)單滴每 0.5 秒就切換一次振蕩器的頻率床未,從 500Hz 到 100 Hz竭翠。

你制造了自己的特雷門琴!

簡(jiǎn)單的振蕩器可以發(fā)出音符薇搁,但是并不能令耳朵愉悅斋扰。真正的樂器還受許多別的因素的影響,比如鋼琴啃洋,它的聲音很獨(dú)特传货。在后面幾節(jié)中,你會(huì)繼續(xù)探索它們是如何形成的宏娄。
完整代碼:

import AudioKitPlaygrounds
import AudioKit


let oscillator = AKOscillator()
oscillator.frequency = 300
oscillator.amplitude = 0.5

AudioKit.output = oscillator
try? AudioKit.start()

oscillator.start()

oscillator.rampDuration = 0.2
oscillator.frequency = 500


import AudioKitUI

AKPlaygroundLoop(every: 0.5) {
    oscillator.frequency = (oscillator.frequency == 500 ? 100 : 500)
}


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

聲音封包

當(dāng)樂器演奏出一個(gè)音符時(shí)问裕,振幅(或音量)是會(huì)變化的,并且每個(gè)樂器都不相同孵坚。有一個(gè)能夠模擬這個(gè)效果的模型粮宛,叫做 Attack-Decay-Sustain-Release (ADSR) 封包:

image

這個(gè)封包由幾個(gè)部分構(gòu)成:

  • Attack - 上行: 在這個(gè)階段聲音上行至最大音量窥淆。
  • Decay - 下行: 這個(gè)時(shí)候聲音下滑到 Sustain 水平。
  • Sustain - 維持: 這個(gè)階段聲音會(huì)維持在釋放終止時(shí)的音量巍杈,一直到開始 Release忧饭。
  • Release - 釋放: 這個(gè)階段音量開始下滑到 0。

一臺(tái)鋼琴筷畦,當(dāng)琴弦被木錘敲擊眷昆,會(huì)發(fā)出一個(gè)非常短促的上行音然后迅速下降。一把小提琴則會(huì)發(fā)出比較長(zhǎng)的上行汁咏、下行和維持亚斋,因?yàn)檠葑鄷r(shí)琴弓不會(huì)離開琴弦。
電子琴是第一批電子樂器中使用 ADSR 封包的樂器之一攘滩。這種樂器發(fā)明于 1939 年帅刊,由 163 個(gè)電子管和 1000 多個(gè)特制的電容器構(gòu)成,重達(dá) 500 英磅(230 kg)漂问。但不幸的是赖瞒,只制造了 1000 臺(tái)電子琴,它沒有獲得商業(yè)上的成功蚤假。

image

圖片來自于 courtesy of Hollow Sun – 遵循 CC attribution 協(xié)議栏饮。

右鍵單擊 Playground 中的頂層元素,Journey磷仰,選擇 New Playground Page 袍嬉,創(chuàng)建一個(gè)新的 Playground 叫做ADSR。編輯文件內(nèi)容為:

import AudioKitPlaygrounds
import AudioKit


let oscillator = AKOscillator()

創(chuàng)建了一個(gè)振蕩器灶平,這個(gè)你已經(jīng)很熟悉了伺通。然后繼續(xù)加入代碼:

let envelope = AKAmplitudeEnvelope(oscillator)
envelope.attackDuration = 0.01
envelope.decayDuration = 0.1
envelope.sustainLevel = 0.1
envelope.releaseDuration = 0.3

這次創(chuàng)建了一個(gè) AKAmplitudeEnvelope 并定義了一個(gè) ADSR 封包。durantion 參數(shù)用秒為單位指定逢享,level 參數(shù)指定的是音量罐监,取值訪問 0-1 之間。
AKAmplitudeEnvelope 是 AKNode 子類瞒爬,同 AKOscillator 一樣弓柱。在上面的代碼中,你可以看到侧但,振蕩器作為參數(shù)被傳遞給了封包的構(gòu)造函數(shù)矢空,兩個(gè)節(jié)點(diǎn)連在了一起。
接著:

AudioKit.output = envelope
try? AudioKit.start()

oscillator.start()

AudioKit 引擎啟動(dòng)俊犯,這次將輸出改成 ADSR 封包妇多,然后打開振蕩器。

image

為了聽到封包效果燕侠,你必須重復(fù)播放封包者祖,然后停止封包:

import AudioKitUI

AKPlaygroundLoop(every: 0.5) {
    if (envelope.isStarted) {
        envelope.stop()
    } else {
        envelope.start()
    }
}


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

現(xiàn)在你會(huì)聽到同一個(gè)音符被反復(fù)播放立莉,但這次帶上了聲音封包效果,聽起來有點(diǎn)鋼琴的味道了七问。
每秒播放兩次蜓耻,每個(gè)循環(huán)都以 ADSR 開始和結(jié)束。當(dāng)循環(huán)開始后械巡,快速上行到最大音量刹淌,這個(gè)過程大約 0.01 秒,緊接著是 0.1 秒的下行讥耗,到達(dá)維持水平有勾。這個(gè)過程約 0.5 秒,然后釋放 0.3 秒古程。
修改 ADSR 值蔼卡,嘗試創(chuàng)建其他聲音的效果。試試如何模擬小提琴挣磨?
從振蕩器發(fā)出正弦波開始到現(xiàn)在雇逞,已經(jīng)過去很長(zhǎng)時(shí)間了。當(dāng)你用振蕩器演奏音符的同時(shí)茁裙,會(huì)使用 ADSR 去讓聲音更加柔和塘砸,但你仍然不能把它稱之為真正的音樂!
下一節(jié)晤锥,你會(huì)學(xué)習(xí)如何創(chuàng)建更加豐富的聲音掉蔬。
完整代碼:

import AudioKitPlaygrounds
import AudioKit


let oscillator = AKOscillator()

let envelope = AKAmplitudeEnvelope(oscillator)
envelope.attackDuration = 0.01
envelope.decayDuration = 0.1
envelope.sustainLevel = 0.1
envelope.releaseDuration = 0.3

AudioKit.output = envelope
try? AudioKit.start()

oscillator.start()


import AudioKitUI

AKPlaygroundLoop(every: 0.5) {
    if (envelope.isStarted) {
        envelope.stop()
    } else {
        envelope.start()
    }
}


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

聲音疊加合成

每種樂器都有獨(dú)一無二的音質(zhì),并以其音色而得名查近。這就是為什么鋼琴的聲音和小提琴的聲音截然不同的原因眉踱,哪怕它們演奏同一個(gè)音符挤忙。音色的一個(gè)重要屬性是樂器所產(chǎn)生的聲譜霜威。聲譜表示樂器發(fā)出一個(gè)單音符時(shí)所組成的頻率范圍。你的 Playground 當(dāng)前所用的振蕩器只能發(fā)出單一的頻率册烈,所以聽起來非常假戈泼。
通過將一系列振蕩器合并在一起作為輸出并演奏同一個(gè)音符,你能夠真實(shí)地模擬出一個(gè)樂器赏僧。這就是“疊加合成”大猛。這是你的下一個(gè)課題。

右鍵單擊 Playground,選擇 New Playground Page 創(chuàng)建新的頁淀零,叫做Additive Synthesis挽绩,編輯如下代碼:

import AudioKitPlaygrounds
import AudioKit

func createAndStartOscillator(frequency: Double) -> AKOscillator {
    let oscillator = AKOscillator()
    oscillator.frequency = frequency
    return oscillator
}

對(duì)于疊加合成,你需要使用多個(gè)振蕩器驾中。createAndStartOscillator 方法用于創(chuàng)建它們唉堪。
然后寫入:

let frequencies = (1...5).map { $0 * 261.63 }

這里用了一個(gè) Range 操作來創(chuàng)建一個(gè)從 1 到 5 的序列模聋。然后對(duì)這個(gè)序列進(jìn)行 map 操作,將每個(gè)數(shù)字乘以 261.63唠亚。這個(gè)數(shù)字是標(biāo)注鍵盤上的中音 C 的音頻链方。將其他數(shù)字乘以這個(gè)值,這就是“和聲”灶搜。

然后繼續(xù)加入:

let oscillators = frequencies.map {
    createAndStartOscillator(frequency: $0)
}

再次進(jìn)行一個(gè) map 操作祟蚀,以創(chuàng)建多個(gè)振蕩器。
然后將它們合成在一起割卖。加入:

let mixer = AKMixer()
oscillators.forEach { mixer.connect(input: $0) }

AKMixer 類也是 AudioKit 中的節(jié)點(diǎn)前酿;
它將 1 個(gè)或多個(gè)節(jié)點(diǎn)作為輸出并將它們合成在一起。
然后:

let envelope = AKAmplitudeEnvelope(mixer)
envelope.attackDuration = 0.01
envelope.decayDuration = 0.1
envelope.sustainLevel = 0.1
envelope.releaseDuration = 0.3

AudioKit.output = envelope
try? AudioKit.start()

oscillators.map { $0.start() }


import AudioKitUI

AKPlaygroundLoop(every: 0.5) {
    if (envelope.isStarted) {
        envelope.stop()
    } else {
        envelope.start()
    }
}

上述代碼你已經(jīng)很熟悉了鹏溯;它用 mixer 創(chuàng)建了一個(gè) ADSR 封包薪者,將它提供給 AudioKit 引擎,然后不停地播放和停止它剿涮。
要真正能夠聽出合成的效果言津,你可以嘗試一下將這些頻率進(jìn)行不同的組合。當(dāng)你嘗試這樣做的時(shí)候取试,Playground 的 live-view 是一個(gè)不錯(cuò)的工具悬槽!
加入下列代碼:

class LiveView: AKLiveViewController {

    override func viewDidLoad() {
        addTitle("Mixer")

        oscillators.forEach { oscillator in
            let slider = AKSlider(property: "\(oscillator.frequency) Hz", value: oscillator.amplitude) { amplitude in
                oscillator.amplitude = amplitude
            }
            addView(slider)
        }
    }
}


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = LiveView()

AudioKit 有許多類允許你輕松創(chuàng)建交互式的 Playground;我們?cè)谶@里也使用了其中幾個(gè)瞬浓。
LiveView 類是 AKLiveViewController 的子類初婆,它由一系列垂直排列的 subview 組成。在 viewDidLoad 方法中猿棉,你遍歷每個(gè)振蕩器磅叛,為每個(gè)振蕩器創(chuàng)建一個(gè) AKSlider。每個(gè) Slider 用每個(gè)振蕩器的頻率和振幅進(jìn)行初始化萨赁,當(dāng)遍歷到一個(gè) slider 時(shí)弊琴,都可以為它設(shè)置一個(gè)回調(diào)塊,這樣當(dāng)你拖動(dòng) slider 時(shí)回調(diào)塊被執(zhí)行杖爽。尾隨閉包就是這個(gè)回調(diào)塊敲董,允許你修改每個(gè)振蕩器的頻率。通過這種簡(jiǎn)單的方式慰安,你可以和 Playground 進(jìn)行交互腋寨。

為了測(cè)試上述代碼,你必須開啟 live view化焕。點(diǎn)擊右上角的雙環(huán)圖標(biāo)萄窜,打開助手窗口。同時(shí)將 live view 設(shè)置為正確的 playground 文件。

image

你可以通過修改每個(gè) Slider 的振幅來改變樂器的音色查刻。為了獲得更加自然的音質(zhì)番宁,我建議你參照上圖來進(jìn)行設(shè)置。

最早的一種采用疊加合成的合成器是 200 噸重的電傳簧風(fēng)琴赖阻。如此巨大的體量蝶押,立即宣告了這種樂器的消亡。結(jié)局更好的電子管風(fēng)琴使用了類似的轉(zhuǎn)速脈沖輪技術(shù)火欧,但體積更小棋电,用同樣的加法合成實(shí)現(xiàn)了獨(dú)特的聲音。電傳簧風(fēng)琴在 1935 年發(fā)明苇侵,在前衛(wèi)搖滾時(shí)代仍然是一種廣為人知的流行樂器赶盔。

image

C3 電傳簧風(fēng)琴 – 圖片來自 public domain image

轉(zhuǎn)速脈沖輪上有旋轉(zhuǎn)的輪盤榆浓,輪沿上有許多光滑的隆起于未,旋轉(zhuǎn)輪盤附近有一個(gè)拾波器總成。電傳簧風(fēng)琴由許多這樣的轉(zhuǎn)速脈沖輪組成陡鹃,它們以不同的速度旋轉(zhuǎn)烘浦。音樂家通過拉桿來混合這些聲音并產(chǎn)生一個(gè)音符。這種發(fā)聲方式真的十分簡(jiǎn)陋萍鲸,嚴(yán)格地講闷叉,與其說是電子式的,不如說是機(jī)電式的脊阴。

要?jiǎng)?chuàng)建更真實(shí)的聲譜有許多別的技術(shù)握侧,比如 調(diào)頻技術(shù)(FM)和脈寬調(diào)制技術(shù)(PWM),這兩種在 AudioKit 中都可以通過AKFMOscillatorAKPWMOscillator類來實(shí)現(xiàn)嘿期。無疑品擎,我將鼓勵(lì)你去嘗試這兩者。為什么不在你的 Playground 中用這兩者將 AKOscillator 替換掉呢备徐?

復(fù)音

上個(gè)世紀(jì) 70 年代萄传,出現(xiàn)了一種偏離模塊化合成的理論,它使用單獨(dú)的振蕩器坦喘、封包和過濾器盲再,并使用了微處理器。替代模擬電路瓣铣,它使用了數(shù)字合成的方式發(fā)聲。它導(dǎo)致了價(jià)格極其低廉和便攜式合成器的出現(xiàn)贷揽,比如著名的雅馬哈電子合成器棠笑,被專業(yè)和業(yè)余音樂愛好者廣泛使用。

image

1983 年的 Yamaha DX7 – 圖片來自public domain image

你所有的 Playground 都被死死限制在只能一次演奏一個(gè)音符禽绪。如果使用多個(gè)樂器蓖救,音樂家可以同時(shí)演奏多個(gè)音符洪规。這種演奏方式就叫做“復(fù)音”,相反循捺,如果一次只能演奏一個(gè)音符斩例,就像你的 Playground 一樣,則叫做“單音”从橘。
為了制造復(fù)音念赶,你需要?jiǎng)?chuàng)建多個(gè)振蕩器,每個(gè)振蕩器演奏不同的音符恰力,并通過一個(gè) mixer 節(jié)點(diǎn)播放出來叉谜。但是,我們還有一種更簡(jiǎn)單的 方法:使用 AudioKit 的振蕩器 bank踩萎。

右鍵單擊 Playground停局,選擇 New Playground Page 創(chuàng)建一個(gè)新的 page 就叫做Polyphony。寫入以下代碼:

import AudioKitPlaygrounds
import AudioKit

let bank = AKOscillatorBank()
AudioKit.output = bank
try? AudioKit.start()

這里創(chuàng)建了一個(gè)振蕩器 bank香府,并將它作為 AudioKit 的輸出董栽。如果你按下 Command 鍵點(diǎn)擊 AKOscilatorBank,你將看到它的類定義企孩,你會(huì)發(fā)現(xiàn)它其實(shí)繼承了 AKPolyphonicNode裆泳。如果你繼續(xù)深究下去,你會(huì)發(fā)現(xiàn)它又繼承了 AKNode 并采用了AKPolyphonic 協(xié)議柠硕。

因此工禾,振蕩器 bank 和其他 AudioKit 一樣,它的輸出也能夠被 mixer蝗柔、封包和其它濾鏡和效果所加工闻葵。AKPolyphonic 協(xié)議描述了你應(yīng)該如何在這個(gè)復(fù)音節(jié)點(diǎn)上演奏音符,等下你就知道了癣丧。

為了測(cè)試這個(gè)振蕩器槽畔,你需要設(shè)法和諧地播放多個(gè)音符。這聽起來好復(fù)雜胁编?
在 Playground 后面加入下列代碼厢钧,同時(shí)打開 live view:

import AudioKitUI

class LiveView: AKLiveViewController, AKKeyboardDelegate {

    override func viewDidLoad() {
        addTitle("Keyboard")
        let keyboard = AKKeyboardView(width: 440, height: 100)
        keyboard.delegate = self
        keyboard.polyphonicMode = true
        addView(keyboard)
    }

    func noteOn(note: MIDINoteNumber) {
        bank.play(noteNumber: note, velocity: 80)
    }

    func noteOff(note: MIDINoteNumber) {
        bank.stop(noteNumber: note)
    }
}


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = LiveView()

當(dāng) Playground 編譯成功,你會(huì)看到這個(gè):

image

這么酷嬉橙?一個(gè) Playground 居然畫出了一個(gè)音樂鍵盤早直?
AKKeyboardView 另外一個(gè) AudioKit 提供的實(shí)用工具,它使這個(gè)框架真的容易使用和研究里面的功能市框。
當(dāng)你按下一個(gè)鍵霞扬,鍵盤會(huì)調(diào)用 noteON 委托方法。方法的實(shí)現(xiàn)很簡(jiǎn)單,簡(jiǎn)單地播放了振蕩器 bank喻圃。noteOff 方法則調(diào)用對(duì)應(yīng)的 stop 方法萤彩。
點(diǎn)擊并在鍵盤上滑動(dòng),你會(huì)發(fā)現(xiàn)它演奏出了優(yōu)美的音階斧拍。振蕩器 bank 內(nèi)置了 ADSR 支持雀扶。因此,一個(gè)音符的下行會(huì)和另一個(gè)音符的上升肆汹、松開和保持混在了一起愚墓,發(fā)出了令人愉悅的聲音。

你可能注意到了县踢,鍵盤提供的音符不再以頻率的方式提供转绷,而是以 MIDINoteNumber 類型提供。如果你按住 Command 鍵并點(diǎn)擊左鼠鍵硼啤,查看它的定義议经,你會(huì)看到它只是一個(gè)整型:

public typealias MIDINoteNumber = Int

MIDI 標(biāo)準(zhǔn)全稱是 Musical Instrument Digital Interface(樂器數(shù)字接口),它在樂器間進(jìn)行通訊時(shí)廣泛使用。 音符數(shù)字和標(biāo)準(zhǔn)鍵盤上的音符一一對(duì)應(yīng)谴返。play 方法的第二個(gè)參數(shù) velocity 是另一個(gè) MIDI 屬性煞肾,用于描述一個(gè)音符的敲擊力度。值越小表明敲擊得越輕嗓袱,會(huì)發(fā)出一個(gè)更小的聲音籍救。

最后一步是將鍵盤設(shè)置為復(fù)音模式。在 setup 方法代碼最后中加入:

keyboard.polyphonicMode = true

你會(huì)發(fā)現(xiàn)現(xiàn)在可以同時(shí)演奏多個(gè)音符了渠抹,只需要這樣:

image

……太不可思議了蝙昙,C-大調(diào)。這個(gè)項(xiàng)目使用了 Soundpipe梧却,代碼來自于 CSound奇颠,一個(gè)起始于 1985 年的 MIT 開源項(xiàng)目。令人不可思議的是它可以在 Playground 中運(yùn)行并添加到你的 App 中放航,而它竟然擁有超過 30 年的歷史了烈拒!

抽樣

你已經(jīng)學(xué)習(xí)了半天的聲音合成技術(shù)了,在這個(gè)過程中你嘗試用非常原始的方式制造擬真的聲音:振蕩器广鳍、過濾器和混合器荆几。早在上世紀(jì) 70 年代,隨著計(jì)算機(jī)處理能力和存儲(chǔ)的增長(zhǎng)赊时,一種完全不同的方法出現(xiàn)了——聲音取樣——目標(biāo)是制造聲音的數(shù)字復(fù)制品吨铸。

取樣是相對(duì)簡(jiǎn)單的概念,它和數(shù)字影像技術(shù)中的原理相同蛋叼。自然聲音是光滑的波形焊傅,取樣只是在固定的時(shí)間間隔內(nèi)簡(jiǎn)單地記錄聲波的震動(dòng):

image

在抽樣過程中剂陡,有兩個(gè)因素直接影響了記錄的擬真度:

  • Bit depth - 位深: 表示一個(gè)取樣器能夠復(fù)制的離散振幅數(shù)狈涮。
  • Sample rate - 速率: 表示多久進(jìn)行一次振幅測(cè)量狐胎,單位是 Hz。

你將用另一個(gè) Playground 來學(xué)習(xí)這些屬性歌馍。
在 Playground 上右鍵握巢,選擇 New Playground Page 并創(chuàng)建新的 page 名為Samples。編輯如下代碼:

import AudioKitPlaygrounds
import AudioKit

let file = try AKAudioFile(readFileName: "climax-disco-part2.wav", baseDir: .resources)
let player = try AKAudioPlayer(file: file)
player.looping = true

這段代碼載入了一個(gè)示例音頻松却,創(chuàng)建了一個(gè)聲音播放器暴浦,并設(shè)置它的循環(huán)播放這個(gè)聲音。
這個(gè)波表文件放在這個(gè)zip文件中晓锻。解壓縮這個(gè) zip 文件歌焦,將 WAV 文件拖到 Playground 的 resources 文件夾中。

然后砚哆,在 Playground 文件最后繼續(xù)加入:

AudioKit.output = player
try? AudioKit.start()

player.play()


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

這會(huì)將你的聲頻播放器傳遞給 AudioKit 引擎并開始播放独撇。調(diào)大音量,注意聽躁锁。
這個(gè)簡(jiǎn)單的例子重復(fù)播放各種聲音纷铣,這些聲音很難用基本的振蕩器來模擬。
正在使用的音頻有一個(gè)比較高的位深和取樣率战转,能夠產(chǎn)生清脆和清晰的聲音搜立。為了試驗(yàn)這兩個(gè)參數(shù),在創(chuàng)建音頻播放器之后槐秧,加入如下代碼:

let bitcrusher = AKBitCrusher(player)
bitcrusher.bitDepth = 16
bitcrusher.sampleRate = 40000
AudioKit.output = bitcrusher

現(xiàn)在播放的聲音就截然不同了:仍然是同一個(gè)抽樣文件啄踊,但聲音變得非常尖銳。
AKBitCrusher 是一種 AudioKit 音效刁标,用于模擬低位深低取樣率的效果颠通。使用它,你可以制造出這種效果命雀,就像是早期用 ZX Spectrum 或 BBC Micro 進(jìn)行抽樣的聲音蒜哀,這些電腦僅有幾 Kb 的內(nèi)存和處理器,比起如今的電腦來說要慢上幾百萬倍吏砂!

最后的實(shí)驗(yàn)撵儿,是將許多節(jié)點(diǎn)組合在一起,制造出立體聲延遲效果狐血。刪除代碼中用于創(chuàng)建和配置 bitcrusher 的三行代碼淀歇。然后添加:

let delay = AKDelay(player)
delay.time = 0.1
delay.dryWetMix = 1

這會(huì)用你的抽樣文件創(chuàng)建出大約 0.1 秒的延遲效果。干/濕混合值讓你將延遲聲音和未延遲的聲音進(jìn)行混合匈织,設(shè)置為 1 表示只有經(jīng)過延遲的聲音被節(jié)點(diǎn)輸出浪默。
然后渔期,加入代碼:

let leftPan = AKPanner(player, pan: -1)
let rightPan = AKPanner(delay, pan: 1)

AKPanner 節(jié)點(diǎn)允許你將音頻進(jìn)行移動(dòng)牛曹,左移、右移或者之間的某個(gè)地方。上述代碼將延遲過的音頻左移产镐,為延遲的聲音右移猜敢。
最后一個(gè)步驟是將兩者混合在一起脑漫,并設(shè)置 AudioKit 的輸出秸仙,用下面的代碼替換掉原來設(shè)置 AudioKit 的輸出為 bitcrusher 的代碼:

let mix = AKMixer(leftPan, rightPan)
AudioKit.output = mix

這將播放同一個(gè)文件,但在左右揚(yáng)聲器之間有一個(gè)非常短的延遲胜榔。
完整代碼:

import AudioKitPlaygrounds
import AudioKit

let file = try AKAudioFile(readFileName: "climax-disco-part2.wav", baseDir: .resources)
let player = try AKAudioPlayer(file: file)
player.looping = true

//let bitcrusher = AKBitCrusher(player)
//bitcrusher.bitDepth = 16
//bitcrusher.sampleRate = 40000
//AudioKit.output = bitcrusher

let delay = AKDelay(player)
delay.time = 0.1
delay.dryWetMix = 1

let leftPan = AKPanner(player, pan: -1)
let rightPan = AKPanner(delay, pan: 1)
let mix = AKMixer(leftPan, rightPan)

AudioKit.output = mix
try? AudioKit.start()

player.play()


import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
image

結(jié)語

在本教程中胳喷,你對(duì)“使用 AudioKit 能干什么”有了一個(gè)大致的理解了。開始探險(xiǎn)吧——嘗試一下穆格過濾夭织,升降調(diào)吭露、混響,或者圖像均衡器的效果怎么樣尊惰?
只需要一小點(diǎn)創(chuàng)意讲竿,你就可以制造出自己的聲音、電子樂器或者游戲音效择浊。

你可以下載最終項(xiàng)目戴卜。當(dāng)然,你仍然還需要像最開始那樣編譯動(dòng)態(tài)庫琢岩,并將最終代碼拖入進(jìn)去即可投剥。

最后,感謝 AudioKit 項(xiàng)目的Lead担孔,Aurelius Prochazka江锨,審閱了本文。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末糕篇,一起剝皮案震驚了整個(gè)濱河市啄育,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拌消,老刑警劉巖挑豌,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異墩崩,居然都是意外死亡氓英,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門鹦筹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铝阐,“玉大人,你說我怎么就攤上這事铐拐∨羌” “怎么了练对?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吹害。 經(jīng)常有香客問我螟凭,道長(zhǎng),這世上最難降的妖魔是什么赠制? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任赂摆,我火速辦了婚禮挟憔,結(jié)果婚禮上钟些,老公的妹妹穿的比我還像新娘。我一直安慰自己绊谭,他們只是感情好政恍,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著达传,像睡著了一般篙耗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宪赶,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天宗弯,我揣著相機(jī)與錄音,去河邊找鬼搂妻。 笑死蒙保,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的欲主。 我是一名探鬼主播邓厕,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扁瓢!你這毒婦竟也來了详恼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤引几,失蹤者是張志新(化名)和其女友劉穎昧互,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伟桅,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敞掘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贿讹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渐逃。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖民褂,靈堂內(nèi)的尸體忽然破棺而出茄菊,到底是詐尸還是另有隱情疯潭,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布面殖,位于F島的核電站竖哩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脊僚。R本人自食惡果不足惜相叁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辽幌。 院中可真熱鬧增淹,春花似錦、人聲如沸乌企。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽加酵。三九已至拳喻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猪腕,已是汗流浹背冗澈。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陋葡,地道東北人亚亲。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像脖岛,于是被迫代替她去往敵國(guó)和親朵栖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1柴梆、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評(píng)論 3 119
  • 我特喜歡吃野菜陨溅,做湯蘸醬咸可大快朵頤。每年清明前绍在,大地還是一片荒蕪的時(shí)候门扇,我就能從草窠里找到婆婆丁和苦苣的嫩芽。 ...
    格爾黑閱讀 789評(píng)論 8 18
  • 剛刷朋友圈,看到有朋友在體育中心打羽毛球溜宽。 這么說吉拳,在市區(qū)的體育中,可能不下雨适揉,也可能是下雨留攒,朋友卻冒雨去打羽毛球...
    梁超文閱讀 337評(píng)論 3 4
  • 變量是一種使用方便的占位符煤惩,用于引用計(jì)算機(jī)內(nèi)存地址,變量創(chuàng)建后會(huì)占用一定的內(nèi)存空間炼邀。 是占位符魄揉,這種解釋我喜歡
    你說你要一場(chǎng)閱讀 197評(píng)論 0 0
  • 邊上最胖的就是我。 減肥拭宁,是這個(gè)世界上最難的一件事洛退,然而,我必須勇敢地迎戰(zhàn)杰标。 朱苒分享她的減肥秘籍兵怯,我印象最深的是...
    小壞蛋格瑞特閱讀 479評(píng)論 11 6