音MIDI合成器:從UART到PWM的嵌入式音頻實(shí)踐)
簡(jiǎn)介Tiva LaunchPad TM4C123G是基于ARM Cortex-M4F的嵌入式開發(fā)板這份資源為它實(shí)現(xiàn)了一款功能齊全的MIDI合成器使用C編寫適合嵌入式開發(fā)者、電子音樂硬件愛好者也可作為高校嵌入式課程設(shè)計(jì)參考。壓縮包內(nèi)共有82個(gè)文件核心是32個(gè)C源文件和32個(gè)頭文件另有5個(gè)WAV音頻樣例、3張電路原理圖、2份Markdown說明文檔以及CCS工程配置總?cè)萘績(jī)H475KB目錄區(qū)分音頻生成、菜單系統(tǒng)、MIDI接口、LCD驅(qū)動(dòng)與測(cè)試等模塊便于按需定位代碼。目前已有170人學(xué)習(xí)資源難得地形成了從源碼、電路到測(cè)試的完整閉環(huán)并針對(duì)TM4C123G端口B/D的歷史綁定問題給出了明確處理辦法還附帶桌面端單元測(cè)試。讀者既可以按照電路圖搭建實(shí)體合成器也可以直接閱讀模塊化代碼理解MIDI解析、音色合成、菜單交互與音頻輸出之間的協(xié)作關(guān)系。對(duì)于調(diào)試嵌入式音頻系統(tǒng)這套項(xiàng)目還提供了可復(fù)用的模塊劃分與排錯(cuò)思路方便在此基礎(chǔ)上擴(kuò)展音色算法或交互方式。 把一塊Tiva LaunchPad TM4C123G開發(fā)板變成一臺(tái)能接MIDI鍵盤、能跑8復(fù)音、帶包絡(luò)和效果器的合成器這事聽起來像是不務(wù)正業(yè)但我還真把它做完了項(xiàng)目代號(hào)ArmCortexSynth。整個(gè)項(xiàng)目沒有用任何現(xiàn)成的音源芯片所有發(fā)聲邏輯全部跑在ARM Cortex-M4F這顆80MHz的MCU上。對(duì)于既玩嵌入式又玩音樂制作的人這類項(xiàng)目是最好的交叉訓(xùn)練場(chǎng)一邊是實(shí)時(shí)中斷、有限RAM、定時(shí)器精度另一邊是波形生成、包絡(luò)控制、效果器算法兩邊都不輕松。這篇把整個(gè)項(xiàng)目的關(guān)鍵決策和實(shí)現(xiàn)細(xì)節(jié)完整拆開講包括為什么選TM4C123G、MIDI接收鏈路怎么搭、合成引擎怎么寫、音頻輸出用什么方案、中斷怎么排優(yōu)先級(jí)以及大量實(shí)測(cè)過程中踩過的坑。內(nèi)容按“硬件選型 → MIDI鏈路 → 合成引擎 → 音頻輸出 → 調(diào)度結(jié)構(gòu) → 問題排查”這條線展開代碼片段都是TivaWare庫下的寫法可直接參考。1. 硬件選型為什么偏偏是TM4C123G1.1 M4F核心對(duì)合成器意味著什么MIDI合成器的核心計(jì)算負(fù)載是每個(gè)音頻采樣周期內(nèi)對(duì)所有活躍復(fù)音做振蕩器查表、包絡(luò)更新、混音和效果處理。以32kHz采樣率、8復(fù)音、每復(fù)音雙振蕩器來算每秒要完成約16次振蕩器更新乘以32000也就是50萬次以上的波形生成運(yùn)算。這個(gè)量級(jí)在8位MCU上完全不可行在72MHz的Cortex-M3上勉強(qiáng)能跑但余量少而在TM4C123G這顆Cortex-M4F上就比較從容了。M4F相比M3多了兩個(gè)關(guān)鍵東西第一個(gè)是硬件浮點(diǎn)單元FPU單精度浮點(diǎn)加減乘是一條指令包絡(luò)計(jì)算、LFO調(diào)頻這類控制率代碼用float寫起來很直觀不會(huì)被編譯器生成一堆軟浮點(diǎn)庫調(diào)用拖慢第二個(gè)是帶飽和運(yùn)算和SIMD風(fēng)格的DSP指令雖然我最終沒有重度依賴DSP指令但在做16位波形混音時(shí)用帶飽和加法的手寫代碼可以避免溢出截?cái)喈a(chǎn)生的爆音。TM4C123G的具體規(guī)格是80MHz主頻、256KB Flash、32KB RAM。這個(gè)RAM容量很關(guān)鍵后面效果器的延遲緩沖區(qū)就是從這里摳的同時(shí)它還有16路PWM輸出和8個(gè)UART資源非常充裕。1.2 板載外設(shè)與接口規(guī)劃把LaunchPad板子翻過來看TM4C123G其實(shí)已經(jīng)把項(xiàng)目需要的外設(shè)都準(zhǔn)備齊了。板載ICDI調(diào)試器負(fù)責(zé)下載和串口打印3個(gè)用戶LED可以用作MIDI活動(dòng)指示和音頻中斷心跳指示2個(gè)用戶按鍵可以臨時(shí)充當(dāng)音高上下調(diào)的測(cè)試按鈕這對(duì)沒有MIDI鍵盤的調(diào)試階段非常有用。接口上做如下規(guī)劃UART1接收MIDI數(shù)據(jù)位于PB1引腳定時(shí)器Timer0產(chǎn)生32kHz音頻采樣中斷PWM0模塊發(fā)生音頻載波從PF2輸出光耦MIDI輸入電路通過面包板連接到DIN5插座整個(gè)硬件搭建不需要任何外接DAC或音頻編解碼芯片面包板上只多了一個(gè)光耦和幾個(gè)電阻電容極大降低了復(fù)現(xiàn)門檻。這一點(diǎn)對(duì)于想照著做的朋友很重要TM4C123G的LaunchPad套件不到100元加上幾個(gè)被動(dòng)元件就能開工。2. MIDI接收鏈路從5針DIN到UART中斷2.1 MIDI協(xié)議與電氣接口要點(diǎn)MIDI協(xié)議本身是串行異步通信波特率固定為31.25kbps8個(gè)數(shù)據(jù)位、1個(gè)停止位、無校驗(yàn)位。和普通UART通信最大的區(qū)別在于MIDI消息是分狀態(tài)字節(jié)和數(shù)據(jù)字節(jié)的狀態(tài)字節(jié)的最高位是1數(shù)據(jù)字節(jié)最高位是0。一條Note On消息由3個(gè)字節(jié)組成狀態(tài)字節(jié)0x90代表通道1的Note On、音高字節(jié)、力度字節(jié)。設(shè)備必須解析這些字節(jié)并更新內(nèi)部音符狀態(tài)。電氣層面MIDI用的是5針DIN接口但傳輸方式是電流環(huán)而不是電壓信號(hào)。DIN4腳是電流源DIN5腳是電流匯數(shù)據(jù)通過有無20mA左右的電流來表示。設(shè)計(jì)接收端時(shí)絕對(duì)不能把MCU的UART RX直接接到DIN針腳上電流環(huán)必須通過光耦隔離常見方案是使用6N138高速光耦。DIN4經(jīng)220Ω電阻接光耦輸入陽極DIN5接光耦輸入陰極光耦輸出端上拉到3.3V后接UART RX。提示如果MIDI鍵盤插上后完全沒反應(yīng)先查光耦輸入方向很多第一次做MIDI接口的人會(huì)把DIN4和DIN5接反。用萬用表量光耦輸入兩端正常時(shí)待機(jī)狀態(tài)下應(yīng)有明顯壓差。2.2 UART配置與波特率計(jì)算TM4C123G的UART模塊用系統(tǒng)時(shí)鐘作為基準(zhǔn)配置波特率時(shí)需要寫兩個(gè)寄存器分別是整數(shù)分頻器UARTIBRD和小數(shù)分頻器UARTFBRD計(jì)算公式為BRD 時(shí)鐘頻率 / (16 × 波特率)代入80MHz和31250bpsBRD 80000000 / (16 × 31250) 160這個(gè)結(jié)果整數(shù)部分剛好是160小數(shù)部分為0意味著MIDI波特率在80MHz主頻下可以做到零誤差。如果主頻不是80MHz比如外部晶振直接運(yùn)行在16MHz算出來的BRD就會(huì)有小數(shù)部分需要用UARTFBRD配置但誤差仍然在MIDI規(guī)范允許范圍內(nèi)。TivaWare庫下初始化代碼很簡(jiǎn)潔#include inc/tm4c123gh6pm.h #include driverlib/uart.h #include driverlib/gpio.h #include driverlib/sysctl.h #include driverlib/pin_map.h void MidiUARTInit(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); SysCtlPeripheralEnable(SYSCTL_PERIPH_UART1); GPIOPinConfigure(GPIO_PB1_U1RX); GPIOPinTypeUART(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1); UARTConfigSetExpClk(UART1_BASE, SysCtlClockGet(), 31250, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); UARTFIFOEnable(UART1_BASE); UARTIntEnable(UART1_BASE, UART_INT_RX); UARTIntRegister(UART1_BASE, MidiUARTIntHandler); }UART1的RX在PB1引腳需要注意在GPIO配置時(shí)把PB0也配置成UART引腳否則引腳復(fù)用可能出問題。FIFO使能后RX有16字節(jié)硬件緩沖即使音頻中斷偶爾搶占也不容易丟字節(jié)。2.3 消息解析與實(shí)時(shí)性布局UART中斷里只做一件事情把讀到的字節(jié)塞進(jìn)一個(gè)環(huán)形緩沖區(qū)解析工作放到主循環(huán)。這樣做的好處是UART中斷保持極短不會(huì)和音頻中斷長(zhǎng)時(shí)間沖突。環(huán)形緩沖區(qū)設(shè)計(jì)為256字節(jié)對(duì)MIDI演奏場(chǎng)景綽綽有余。解析狀態(tài)機(jī)需要處理真實(shí)MIDI流中的一個(gè)關(guān)鍵細(xì)節(jié)運(yùn)行狀態(tài)。MIDI規(guī)范允許連續(xù)相同類型的消息省略重復(fù)的狀態(tài)字節(jié)比如連續(xù)Note On消息可以只發(fā)送音高和力度接收端必須記住上一個(gè)狀態(tài)字節(jié)。忽略這一點(diǎn)實(shí)際接MIDI鍵盤演奏時(shí)會(huì)發(fā)現(xiàn)音高和力度錯(cuò)亂。解析核心邏輯用一個(gè)簡(jiǎn)單狀態(tài)機(jī)static uint8_t midiStatus 0; static uint8_t midiData1 0; static int midiIndex 0; void MidiParseloop(void) { uint8_t byte; if (!RingBufRead(midiBuf, byte)) return; if (byte 0x80) { midiStatus byte; midiIndex 0; if ((byte 0xF0) 0xC0 || (byte 0xF0) 0xD0) { // Program Change和Channel Pressure只有1個(gè)數(shù)據(jù)字節(jié) midiIndex 1; midiData1 0; } } else { if (midiIndex 0) { midiData1 byte; midiIndex 1; } else { HandleMIDIMessage(midiStatus, midiData1, byte); midiIndex 0; } } }HandleMIDIMessage里根據(jù)狀態(tài)字節(jié)的高4位分發(fā)到音符開關(guān)、彎音、控制變更等處理函數(shù)。注意MCU端不關(guān)心通道信息如果想做成16通道同時(shí)演奏需要在每個(gè)voice結(jié)構(gòu)體里保存通道號(hào)。3. 合成引擎波形、包絡(luò)與效果器3.1 相位累加器與波形表數(shù)字振蕩器的核心是相位累加器法也叫DDFS直接數(shù)字頻率合成。每個(gè)采樣周期把一個(gè)固定步長(zhǎng)加到32位相位累加器上相位高于32位就自然溢出循環(huán)用高若干位作為波形表的查找索引。輸出頻率由步長(zhǎng)決定公式為步長(zhǎng) 目標(biāo)頻率 × 2^32 / 采樣率比如A4頻率440Hz32kHz采樣率下步長(zhǎng)等于440 × 4294967296 / 32000 ≈ 59055800。用32位相位累加器的好處是頻率分辨率極高任意音分偏移都能精確表達(dá)。波形表我用了1024點(diǎn)16位有符號(hào)表存儲(chǔ)sine、saw、square、triangle四種波形。查表時(shí)取相位高10位做主索引再用相位里隨后的中10位做線性插值。不要省略插值這一步?jīng)]有插值的話低頻段會(huì)聽到輕微量化噪聲加了插值后底噪改善非常明顯。typedef struct { uint32_t phase; uint32_t step; uint8_t wave; uint16_t level; } osc_t; static inline int32_t OscNext(osc_t *o) { o-phase o-step; uint32_t idx o-phase 22; uint32_t frac (o-phase 12) 0x3FF; int32_t y0 wavetable[o-wave][idx]; int32_t y1 wavetable[o-wave][(idx 1) 1023]; return y0 (((y1 - y0) * (int32_t)frac) 10); }這個(gè)函數(shù)的執(zhí)行時(shí)間大約只有幾十個(gè)周期8復(fù)音雙振蕩器全部跑一遍也只用掉16次調(diào)用在32kHz中斷里完全吃得消。3.2 雙振蕩器與ADSR包絡(luò)功能齊全的合成器不能只有單振蕩器我的實(shí)現(xiàn)里每個(gè)復(fù)音包含兩個(gè)主振蕩器和一個(gè)子振蕩器。子振蕩器固定為主振蕩器頻率的一半低一個(gè)八度音量小一些這樣經(jīng)典的厚實(shí)感就出來了。兩個(gè)主振蕩器可以獨(dú)立選擇波形并支持輕微失諧失諧量用CC控制范圍在±5音分內(nèi)最有感覺。每復(fù)音的音量包絡(luò)采用經(jīng)典的ADSR結(jié)構(gòu)四個(gè)階段分別為起音、衰減、延音、釋音。Attack時(shí)間10ms到2s可調(diào)Decay和Release也類似Sustain是0到1的電平值。實(shí)現(xiàn)上每采樣更新一次包絡(luò)值根據(jù)當(dāng)前所處的階段乘以一個(gè)系數(shù)系數(shù)由階段時(shí)長(zhǎng)和采樣率共同決定。包絡(luò)的狀態(tài)轉(zhuǎn)換要小心處理尤其是Note Off來時(shí)如果還沒觸發(fā)過Note On必須忽略同一個(gè)音符快速重復(fù)觸發(fā)時(shí)要先走Release再重新Attack否則會(huì)聽到明顯的點(diǎn)擊聲。這些邊界條件雖然簡(jiǎn)單但漏處理任何一個(gè)都會(huì)直接影響聽感。3.3 LFO與輕量效果器的取舍LFO在合成器里的作用非常關(guān)鍵我用了兩個(gè)LFO第一個(gè)默認(rèn)調(diào)制振蕩器頻率實(shí)現(xiàn)顫音效果第二個(gè)默認(rèn)調(diào)制音量實(shí)現(xiàn)顫音效果。LFO本質(zhì)也是相位累加器只是步長(zhǎng)極小輸出用低頻正弦表。每個(gè)控制周期比如每32個(gè)采樣更新一次調(diào)制量而不是每采樣更新避免重復(fù)計(jì)算。效果器選了合唱和回聲延遲沒有做混響。原因很直接TM4C123G的RAM只有32KB混響需要幾百毫秒的延遲線內(nèi)存消耗太大而合唱只需要兩路短延遲線回聲延遲只需要一個(gè)100ms左右的環(huán)形緩沖區(qū)內(nèi)存壓力小得多。合唱的實(shí)現(xiàn)是兩路延遲線分別用LFO調(diào)延遲時(shí)間再與原信號(hào)疊加能顯著加寬聲場(chǎng)?;芈曆舆t則提供反饋系數(shù)調(diào)節(jié)和時(shí)間調(diào)節(jié)映射到兩個(gè)CC上。效果器的輸出范圍需要做飽和處理否則兩層效果疊加后很容易超過int16范圍。M4F沒有原生飽和加法指令可以直接用但用飽和宏手動(dòng)判斷符號(hào)位也就幾條指令。4. 音頻輸出PWM調(diào)制與RC濾波實(shí)測(cè)4.1 幾種輸出方案對(duì)比MCU音頻輸出方案無外乎PWM、內(nèi)置DAC、外部DAC、I2S外接Codec這幾種。TM4C123G沒有內(nèi)置DAC所以直接排除外部DAC芯片如MCP4921通過SPI輸出音質(zhì)好但需要額外芯片而且SPI在高速更新下也會(huì)占用CPUI2S外接Codec是最理想的方案但TM4C123G沒有硬件I2S外設(shè)用GPIO模擬I2S時(shí)序在80MHz下做32kHz雙通道還能跑但要犧牲大量CPU時(shí)間后期調(diào)試麻煩。權(quán)衡下來PWM輸出是性價(jià)比最高的方案。TM4C123G的PWM模塊以系統(tǒng)時(shí)鐘80MHz計(jì)數(shù)如果PWM周期等于音頻采樣周期那么占空比分辨率就是80MHz除以32kHz等于2500級(jí)換算成有效位數(shù)約為11.3位。這個(gè)分辨率雖不如16位DAC但經(jīng)過低通濾波后聽感沒有明顯量化噪聲。4.2 PWM配置與濾波電路參數(shù)計(jì)算PWM配置的核心是讓載波頻率和采樣率完全一致這樣每個(gè)采樣周期只需要更新一次占空比寄存器天然形成零階保持輸出。SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM0); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); GPIOPinConfigure(GPIO_PF2_M0PWM0); GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_2); PWMGenConfigure(PWM0_BASE, PWM_GEN_0, PWM_GEN_MODE_DOWN); PWMGenPeriodSet(PWM0_BASE, PWM_GEN_0, 2500); PWMOutputState(PWM0_BASE, PWM_OUT_0_BIT, true); PWMGenEnable(PWM0_BASE, PWM_GEN_0);2500這個(gè)周期的計(jì)算是80MHz / 32kHz 2500。占空比更新在音頻中斷里完成int32_t duty (out 32768) * 2500 / 65536; if (duty 2499) duty 2499; if (duty 0) duty 0; PWMPulseWidthSet(PWM0_BASE, PWM_OUT_0, duty);PWM輸出是數(shù)字方波后面必須接低通濾波器。我用二階RC低通兩個(gè)RC節(jié)拍串聯(lián)R取1kΩC取10nF單節(jié)截止頻率約15.9kHz兩節(jié)串聯(lián)后對(duì)32kHz載波的衰減超過40dB而20kHz以下音頻幾乎不受影響。實(shí)測(cè)從PF2引腳引出到濾波器再進(jìn)音箱底噪明顯低于直接用耳機(jī)插開發(fā)板。注意RC低通只能用于驅(qū)動(dòng)高阻抗負(fù)載直接接8Ω喇叭肯定帶不動(dòng)。正確用法是接有源音箱的線路輸入或耳機(jī)放大器。面包板布線時(shí)濾波電容要盡量靠近PF2引腳減少寄生電感。5. 中斷調(diào)度與代碼結(jié)構(gòu)5.1 音頻中斷與MIDI中斷的競(jìng)爭(zhēng)這是一個(gè)嵌入式實(shí)時(shí)系統(tǒng)設(shè)計(jì)的經(jīng)典問題。音頻中斷每秒觸發(fā)32000次每次中斷必須在一個(gè)采樣周期內(nèi)完成所有合成計(jì)算周期是31.25微秒80MHz下對(duì)應(yīng)2500個(gè)CPU周期。如果此時(shí)UART中斷搶占音頻中斷被推遲聲音就會(huì)出現(xiàn)可聽見的毛刺。處理方法是給中斷設(shè)置嚴(yán)格優(yōu)先級(jí)讓音頻中斷優(yōu)先級(jí)高于UART中斷。當(dāng)音頻中斷執(zhí)行時(shí)UART收到的MIDI字節(jié)可以暫時(shí)停留在FIFO里。TM4C123G的UART FIFO深度是16字節(jié)音頻中斷最長(zhǎng)執(zhí)行時(shí)間假設(shè)為40微秒U(xiǎn)ART在31.25kbps下每字節(jié)約320微秒所以FIFO根本不會(huì)溢出。反過來如果讓UART中斷優(yōu)先級(jí)更高音頻中斷哪怕被延遲10微秒都會(huì)產(chǎn)生明顯的時(shí)序抖動(dòng)嚴(yán)重影響音質(zhì)。這里我沒有使用RTOS。音頻中斷要求完全確定性調(diào)度裸機(jī)配合中斷優(yōu)先級(jí)反而比RTOS的任務(wù)切換更可控也更容易看透整個(gè)系統(tǒng)的時(shí)序關(guān)系。5.2 代碼模塊劃分與關(guān)鍵實(shí)現(xiàn)整個(gè)固件按功能拆成幾個(gè)模塊方便獨(dú)立測(cè)試。midi_uart.cUART初始化和接收中斷midi_parser.cMIDI消息狀態(tài)機(jī)解析synth_voice.c復(fù)音管理、振蕩器、包絡(luò)synth_fx.c合唱和延遲效果audio_pwm.cPWM初始化和音頻中斷main.c全局初始化、主循環(huán)關(guān)鍵點(diǎn)是復(fù)音管理。我給每個(gè)voice分配一個(gè)固定的狀態(tài)槽分配策略采用簡(jiǎn)單的最老音符替換法新音符到來時(shí)優(yōu)先使用空閑槽如果沒有空閑槽替換掉最早按下的音符。實(shí)際演奏中這種策略比隨機(jī)替換聽感自然得多因?yàn)椴粫?huì)因?yàn)樘鎿Q一個(gè)剛按下的音導(dǎo)致旋律斷裂。音頻中斷里不能調(diào)用任何打印函數(shù)、不能操作Flash、不能進(jìn)入低功耗模式這些都會(huì)造成不確定延時(shí)。所有調(diào)試信息都通過一個(gè)全局事件計(jì)數(shù)器和主循環(huán)串口輸出主循環(huán)把計(jì)數(shù)器值打印出來用于確認(rèn)中斷沒有發(fā)生溢出。6. 常見問題與排查技巧6.1 硬件層問題實(shí)錄MIDI設(shè)備完全沒反應(yīng)是出現(xiàn)頻率最高的故障。第一優(yōu)先檢查光耦輸入端正常待機(jī)時(shí)DIN4和DIN5之間應(yīng)能測(cè)到壓差因?yàn)镸IDI鍵盤空閑時(shí)電流環(huán)始終有電流。第二檢查UART引腳復(fù)用TM4C123G的UART1 RX除了PB1還有PC4可復(fù)用確認(rèn)代碼和硬件連接一致。第三用LED做視覺反饋每次UART中斷翻轉(zhuǎn)一個(gè)GPIO能直觀判斷有沒有數(shù)據(jù)進(jìn)來。PWM輸出后有明顯高頻嘶嘶聲通常是濾波電路沒接好或RC參數(shù)不對(duì)。先用示波器看PF2波形正常應(yīng)該是清晰方波且占空比隨音符變化再看濾波后波形應(yīng)該是平滑的音頻信號(hào)。沒有示波器就用軟件把采樣值通過串口導(dǎo)出成WAV在電腦上檢查。6.2 軟件層問題實(shí)錄聲音中有周期性爆音我最先懷疑是復(fù)音混合溢出。int16范圍是-32768到327678個(gè)復(fù)音每個(gè)都可能到滿幅混音后必然溢出。解決方法是每個(gè)復(fù)音音量系數(shù)限制在最大值的八分之一以下同時(shí)混音輸出前再做一次飽和處理。另一個(gè)隱蔽問題是CC參數(shù)更新時(shí)產(chǎn)生的臺(tái)階噪聲。彎音輪快速推動(dòng)時(shí)如果直接修改振蕩器步長(zhǎng)相位會(huì)突變產(chǎn)生明顯的咔噠聲。解決方法是參數(shù)平滑每次控制參數(shù)變化后不是立刻更新到目標(biāo)值而是讓實(shí)際值以固定速率向目標(biāo)值爬升。這個(gè)處理在MIDI合成器里叫de-zipper必不可少。6.3 性能與音質(zhì)調(diào)優(yōu)心得最后分享幾個(gè)實(shí)測(cè)得出來的調(diào)優(yōu)經(jīng)驗(yàn)。首先波形表數(shù)組加const限定并放在Flash里不要放在RAM因?yàn)?0MHz下Flash讀取有緩存速度不比RAM慢多少而32KB的RAM要留給延遲線緩沖區(qū)。其次振蕩器的線性插值雖然只多花了幾條指令但對(duì)高頻段音質(zhì)的提升非常值得。第三合唱效果器雖然只用了兩路延遲線但每路用LFO調(diào)制延遲時(shí)間時(shí)調(diào)制深度不宜超過1ms否則會(huì)聽到明顯的金屬聲。我用一個(gè)MIDI鍵盤實(shí)際演湊測(cè)試延遲體感在10毫秒級(jí)別這個(gè)延遲主要來自MIDI鍵盤本身的掃描周期和音頻中斷的緩沖積累作為現(xiàn)場(chǎng)演奏已經(jīng)可以接受。整體CPU負(fù)載在啟用8復(fù)音和全部效果器后大約占75%剩余余量用于后續(xù)功能擴(kuò)展。如果想再加復(fù)音建議LFO改到64個(gè)采樣更新一次效果器里把延遲時(shí)間量化為整數(shù)采樣點(diǎn)避開插值都能省出不少周期。把LaunchPad當(dāng)合成器用了一個(gè)多月最讓我有感觸的是“用中斷去演奏”帶來的掌控感。MIDI數(shù)據(jù)進(jìn)來、相位累加器轉(zhuǎn)動(dòng)、PWM輸出驅(qū)動(dòng)音箱整個(gè)過程沒有任何現(xiàn)成音源芯片參與所有計(jì)算都在那顆80MHz的M4F上完成。這個(gè)項(xiàng)目后續(xù)如果想繼續(xù)擴(kuò)展可以換用更大的波形表做采樣回放或者加一個(gè)USB Host模塊把USB MIDI鍵盤也納入進(jìn)來。如果你也想做一個(gè)不靠現(xiàn)成方案的合成器TM4C123G確實(shí)是一個(gè)非常合適的起步平臺(tái)。本文還有配套的精品資源點(diǎn)擊獲取