日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS播放/渲染/解析MIDI

發布時間:2023/12/8 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS播放/渲染/解析MIDI 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是MIDI

MIDI:樂器數字接口, Musical Instrument Digital Interface。
MIDI 是計算機能理解的樂譜,計算機和電子樂器都可以處理的樂器格式。
MIDI 不是音頻信號,不包含 pcm buffer。
通過音序器 sequencer,結合音頻數據 / 樂器 ,播放 MIDI Event 數據
( 通過音色庫 SoundFont,播放樂器的聲音。iOS上一般稱sound bank )。

通過 AVAudioEngine/AVAudioSequencer 播放

連接 AVAudioEngine 的輸入和輸出,
輸入 AVAudioUnitMIDIInstrument → 混頻器 engine.mainMixerNode → 輸出 engine.outputNode
用AVAudioEngine ,創建 AVAudioSequencer ,就可以播放 MIDI 了。

配置 AVAudioEngine 的輸入輸出

var engine = AVAudioEngine() var sampler = AVAudioUnitSampler() // AVAudioUnitMIDIInstrument的子類 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 {tryself.sampler.loadSoundBankInstrument(at: bankURL,program: 0,bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB),bankLSB: UInt8(kAUSampler_DefaultBankLSB))try engine.start() } catch { print(error) }

engine.mainMixerNode是AVAudioEngine自帶的node。負責混音,它有多路輸入,一路輸出。

用 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)") } // 指定每個track的dest AudioUnit for track in sequencer.tracks {track.destinationAudioUnit = self.sampler }sequencer.prepareToPlay() do {try sequencer.start() } catch {print("\(error)") }

加載多個音色庫

有這樣一種case,隨著業務發展,音色庫需要有更新,增加新的樂器,如果每次更新都要全量更新音色庫,流量消耗大。所以需要對音色庫做增量更新。這樣就會在客戶端出現多個音色庫,看前面的代碼,每次播放只能加載一個音色庫。那么有沒有什么辦法可以加載多個音色庫播放一個MIDI文件呢?
答案是可以的。
我們看前面的數據流圖,實際上,AudioEngine的mainMixerNode是有多路輸入的,那么它應該可以連接多個輸入Instrument。代碼類似下面:

engine.connect(midiSynth0, to: engine.mainMixerNode, format: nil) engine.connect(midiSynth1, to: engine.mainMixerNode, format: nil) engine.connect(midiSynth2, to: engine.mainMixerNode, format: nil) engine.connect(midiSynth3, to: engine.mainMixerNode, format: nil) engine.connect(midiSynth4, to: engine.mainMixerNode, format: nil) engine.connect(engine.mainMixerNode, to: engine.outputNode, format: nil)

這里,假定midiSynth0~midiSynth4是五個不同的AVAudioUnitSampler實例,他們分別加載不同的音色庫,假定為soundBank 0~4用他們來播放一個MIDI文件。我們期望,可以正常播放出MIDI中描述的所有軌道的音色。但是實際測試發現,只有midiSynth0掛載的音色庫里的音色被渲染了出來。奇怪,這是為什么呢?
實際上,答案就隱藏在之前的數據流圖上。這里,我們雖然創建了多個AVAudioUnitSampler作為input node,但是,因為我們沒有指定MIDI每一個track的destinationAudioUnit,MIDI默認所有的track都通過midiSynth0,而midiSynth0只掛載了soundBank0的音色,所以,只有soundBank0被渲染了出來。
那么,這個問題怎么解決呢?其實,之前的代碼里,我們已經給出了答案,即這一句:

for track in sequencer.tracks {track.destinationAudioUnit = self.sampler }

指定每一個track的destinationAudioUnit為對應的掛載了改track上instrument音色的音色庫的AVAudioUnitSampler,讓對應的track上的event通過對應AudioUnitSampler,則可以渲染出完整的音色。

MIDI文件渲染成音頻文件

我們可以把MIDI文件渲染為wav/caf等音頻文件,這需要開啟AVAudioEngine的離線渲染模式。代碼如下:

/*** 渲染midi到音頻文件(wav格式)。耗時操作。* @param midiPath 要渲染的midi文件路徑* @param audioPath 輸出的音頻文件路徑*/public func render(midiPath: String, audioPath: String) {let renderEngine = AVAudioEngine()let renderMidiSynth = AVAudioUnitMIDISynth()loadSoundFont(midiSynth: renderMidiSynth, path:soundFontPath)renderEngine.attach(renderMidiSynth)renderEngine.connect(renderMidiSynth, to: renderEngine.mainMixerNode, format: nil)let renderSequencer = AVAudioSequencer(audioEngine: renderEngine)if renderSequencer.isPlaying {renderSequencer.stop()}renderSequencer.currentPositionInBeats = TimeInterval(0)do {let format = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 2)!do {let maxFrames: AVAudioFrameCount = 4096if #available(iOS 11.0, *) {try renderEngine.enableManualRenderingMode(.offline, format: format,maximumFrameCount: maxFrames)} else {fatalError("Enabling manual rendering mode failed")}} catch {fatalError("Enabling manual rendering mode failed: \(error).")}if (!renderEngine.isRunning) {do {try renderEngine.start()} catch {print("start render engine failed")}}setupSequencerFile(sequencer: renderSequencer, midiPath:midiPath)print("attempting to play")do {try renderSequencer.start()print("playing")} catch {print("cannot start \(error)")}if #available(iOS 11.0, *) {// The output buffer to which the engine renders the processed data.let buffer = AVAudioPCMBuffer(pcmFormat: renderEngine.manualRenderingFormat,frameCapacity: renderEngine.manualRenderingMaximumFrameCount)!let avChannelLayoutKey: [UInt8] = [0x02, 0x00, 0x65, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]let avChannelLayoutData = NSData.init(bytes: avChannelLayoutKey, length: 12)let settings: [String: Any] = ["AVLinearPCMIsFloatKey": 0, "AVFormatIDKey":1819304813, "AVSampleRateKey": 44100, "AVLinearPCMBitDepthKey": 16,"AVLinearPCMIsNonInterleaved": 0, "AVLinearPCMIsBigEndianKey": 1, "AVChannelLayoutKey": avChannelLayoutData, "AVNumberOfChannelsKey": 2]let outputFile: AVAudioFiledo {let outputURL = URL(fileURLWithPath: audioPath)outputFile = try AVAudioFile(forWriting: outputURL, settings: settings)} catch {fatalError("Unable to open output audio file: \(error).")}var totalFrames: Int = 0;for track in renderSequencer.tracks {let trackFrames = track.lengthInSeconds * 1000 * 44100 / (1024 / 1.0)if (Int(trackFrames) > totalFrames) {totalFrames = Int(trackFrames)}}let totalFramesCount = AVAudioFramePosition(totalFrames)while renderEngine.manualRenderingSampleTime < totalFrames {do {let frameCount = totalFramesCount - renderEngine.manualRenderingSampleTimelet framesToRender = min(AVAudioFrameCount(frameCount), buffer.frameCapacity)let status = try renderEngine.renderOffline(framesToRender, to: buffer)switch status {case .success:try outputFile.write(from: buffer)case .insufficientDataFromInputNode:breakcase .cannotDoInCurrentContext:breakcase .error:fatalError("The manual rendering failed.")}} catch {fatalError("The manual rendering failed: \(error).")}}print("isPlaying 2: " + String(renderSequencer.isPlaying) + ", " + String(renderSequencer.currentPositionInBeats) + ", " + String(renderSequencer.currentPositionInSeconds))} else {// Fallback on earlier versions}}renderEngine.stop()}

渲染的流程其實和播放完全一致,只是需要為渲染單獨創建AVAudioEngine和AVAudioSequencer的實例。通過AVAudioEngine的enableManualRenderingMode開啟離線渲染模式。代碼中的settings是一個字典,存儲輸出文件相關的參數。其中各個參數的具體含義可以在蘋果開發者網站查到了。比如AVSampleRateKey表示采樣率,AVLinearPCMBitDepthKey表示采樣深度。
循環遍歷MIDI的所有track,計算出總幀數。秒數與幀的換算公式track.lengthInSeconds * 1000 * 44100 / (1024 / 1.0)。然后,循環輸出所有幀到音頻文件。

此外,還可以解析MIDI文件,獲取每個track,以及每個track對應的,編輯MIDI文件,重新生成一個新的MIDI文件。
可以從這里獲取源碼:iOS MIDI播放

總結

以上是生活随笔為你收集整理的iOS播放/渲染/解析MIDI的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 久操久 | av久久久久久 | 在线播放亚洲 | 国产日韩精品电影 | 中文字幕在线观看的网站 | 91高跟黑色丝袜呻吟动态图 | 久久久久久国产精品免费 | 亚州欧美在线 | 国产成人一区二区三区视频 | 亚洲欧美日本另类 | 天天久久久 | 色屁屁一区二区三区视频 | 欧美特黄一区二区三区 | 国产精品美女久久久久久 | 一级片久久久 | 五月天色站 | 午夜精品毛片 | 成人91av | 国产激情久久久久 | 在线播放91灌醉迷j高跟美女 | 黑人粗进入欧美aaaaa | 97在线免费观看 | 国产一区二区在线看 | 国产午夜片 | 国产美女久久久 | 日韩黄色一级视频 | 国内自拍区 | 精品乱码一区内射人妻无码 | 在厨房拨开内裤进入毛片 | 一本色道久久综合亚洲精品小说 | 国产在线最新 | 国产女主播一区二区三区 | 国产在线视频二区 | 艳妇臀荡乳欲伦交换在线播放 | 国产欧美久久久精品免费 | 伊人影音 | 另类视频在线观看+1080p | 中文字幕一区三区 | 国产这里只有精品 | 97精品视频 | 精品国模一区二区三区 | 国产精品自拍网站 | 无码aⅴ精品一区二区三区浪潮 | 理论片琪琪午夜电影 | 激情四月 | 国产一区亚洲一区 | 99re这里只有精品66 | 毛片网页 | 日韩www在线观看 | www.波多野结衣.com | 伊人精品在线观看 | 亚洲第一黄色 | 精品一区二区日韩 | 麻豆人妻少妇精品无码专区 | 亚洲欧美在线成人 | 亚洲精品国产成人 | 日韩伊人久久 | av黄色国产| 日韩精品一区二区免费视频 | 97色在线视频 | av在线小说 | 麻豆传媒网 | www.毛片| 男人的天堂伊人 | 亚洲成人午夜影院 | av免费观看不卡 | 2021天天操 | 九九热在线视频 | 爱爱小视频网站 | 国产亚洲精品美女久久久久 | 黑人大群体交免费视频 | 日韩视频播放 | 明里柚番号 | 国产做爰全过程免费视频 | 91精品久久久久久久99蜜桃 | 色噜噜狠狠成人中文 | 日日夜夜操操操 | 国产精品一区二区在线免费观看 | 日批免费看 | 成人午夜久久 | 免费网站观看www在线观看 | 色婷婷av一区二区三区之红樱桃 | 五月婷婷七月丁香 | 麻豆视频在线看 | 色乱码一区二区三区 | 黄色a级片网站 | 男人与雌性宠物交啪啪 | 茄子爱啪啪| 天天综合天天添夜夜添狠狠添 | 国产三级三级看三级 | 色噜噜在线播放 | 中文字幕乱码无码人妻系列蜜桃 | 日本特级黄色片 | 国产精品国产三级国产aⅴ无密码 | 天堂视频在线免费观看 | 99视频这里有精品 | 国产真人做爰毛片视频直播 | 日韩中文av | 一本一道人人妻人人妻αv 九一在线视频 |