2015-04-26 28 views
0

如何正確創建AVAudioUnitSampler的音量信封?AVAudioUnitSampler AVFoundation的正確音量信封

基本上我想添加一個攻擊階段,其中的卷在幾毫秒內消失以避免點擊。另外,我想添加一個發佈階段,以便在停止播放音符後音量消失。

我做了什麼至今:

我成立了一個代表我的採樣率一個全球性的定時器。 爲此,我使用了一個間隔長度爲1個樣本的NSTimer(對於樣本率爲44100,一個樣本時間長度爲1/44100)。這樣一個人不應該聽到音量「跳躍」導致「點擊」。也許我需要過採樣兩倍的採樣率。

只要增益低於某個閾值,定時器就會調用增加masterGain的函數。通過將期望和當前增益的差值除以採樣率,然後將該值除以所需的衰減時間,計算出我增加的量。 (在下面的示例中,我使用固定值進行簡單讀取)

達到閾值後,移除計時器並停止筆記。

- >我認爲這種方法非常複雜,特別是當我使用釋放階段時。

請檢查我的示例代碼。我用UIButton touchDown來觸發播放的音符。請告訴我,如果你知道一個更簡單的方法來隨時間自動化一個值/建立一個淡入/淡出的體積。

感謝

import UIKit 
    import AVFoundation 

    class ViewController: UIViewController { 


    var engine: AVAudioEngine! 
    var sampler: AVAudioUnitSampler! 
    var error: NSError? 
    var timer: NSTimer = NSTimer() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     engine = AVAudioEngine() 
     sampler = AVAudioUnitSampler() 

     engine.attachNode(sampler) 
     engine.connect(sampler, to: engine.mainMixerNode, format: nil) 

     if !engine!.startAndReturnError(&error) { 
      if let error = error { 
       println("Engine could not start!") 
       println(error.localizedDescription) 
      } 
     } 
    } 

    @IBAction func touchDown(sender: UIButton, forEvent event: UIEvent) { 
     sampler.startNote(66, withVelocity: 66, onChannel: 0) 
     sampler.masterGain = -90 
     timer = NSTimer.scheduledTimerWithTimeInterval (1/44100, target: self, selector: "updateVolume", userInfo: nil, repeats: true) 
    } 

    func updateVolume() { 
     if sampler.masterGain <= -10 { 
      sampler.masterGain = sampler.masterGain + 0.1 
      println("volume updated") 
     } else { 
      timer.invalidate() 
     } 
    } 
} 

回答

2

哇!這是一個非常複雜的卷信封的方式!

你有沒有想過讓它變得更簡單?而不是試圖控制增益控制,我只會修改出來的實際音頻。首先我將我的樣本存儲爲一個向量。然後,我會創建另一個表示我的音量包絡的向量。然後,我會創建一個新的播放樣本,這將只是樣本和幅度向量的元素乘法。這是matlab中的一個例子。

%Load your sample 
[x, Fs] = audioread('filename.wav'); 

%get the length of your sample 
sampSize = length(x); 

%Create volume envelope. (Im just using a Gaussian window here. You can 
%replace it with any type of envelope you want so long as the values are 
%between 0 and 1.) 
ampEnv = gausswin(length(x)); 

%Multiply element by element of your two vectors. 
newSample = x.*ampEnv; 

%Plot your new sound. 
plot(newSample); 

另外,如果你想要實時做,它會變得更加棘手。如果延遲在這裏不是一個大問題,我建議有一個輕微的前瞻性窗口來儲存你的樣本,並開始在那裏應用幅度體積法。

+0

你好PicnicTripper!感謝你的回答。你有什麼指導如何在iOS中做到這一點?據我瞭解你的方法 - >你將文件的每個樣本乘以介於0和1之間的函數的幅度。音頻框架的文檔不是iOS中最好的。所以我總是歡迎進一步的輸入:) –

+0

嗨Tobias!這正是你如何將振幅包絡應用於聲音的問題。難道我不是iOS開發人員,但我可能會提供幫助。如果您希望實時執行此操作,也許可以在此處爲代碼添加另一圖層; engine.attachNode(採樣) engine.connect(採樣,以:engine.mainMixerNode,格式:無) 這將有可能到採樣器連接到這可能需要輸入幅度,並通過它們相乘處理部分的幅度包絡?如果你有一個幅度值流進來,你可以在那裏執行你的幅度包絡函數? – PicnicTripper

+0

是的,有一種方法可以在iOS中使用Audiobuffer對象。我需要檢查我可以用取樣器的輸出填充Audiobuffers,然後在按下的每個按鈕上重新啓動信封文件。不幸的是,Audiobuffer Classes不易掌握......我會在此更新任何進度。 –

0

最乾淨的方法是設置一個性能參數,查看AUSampler - Controlling the Settings of the AUSampler in Real Time。然後設置你的攻擊。您也可以在創建預設時硬編碼這些值,但參數更靈活。

編輯

的AULab在約塞米蒂正在打破,在此之前,我考察需要從我自己的預設的一個攻擊控制連接。預設格式相當具體,所以如果發生衝突,我不會感到驚訝,但這適用於基本設置。

這種方法只是一個臨時性的修復。它所做的是檢索預設(字典數組字典的字典數組的NSDictionary),然後將控件(NSDictionary)添加到「控件」數組中。然後你設置你的採樣器預設。這是在您從AVAudioUnitSampler的audioUnit屬性訪問的底層AUSampler上加載預設或樣本後完成的。

//this is the connection we will be adding 
NSMutableDictionary *attackConnection = [NSMutableDictionary dictionaryWithDictionary: 
             @{@"ID"  :@0, 
              @"control" :@0, 
              @"destination":@570425344, 
              @"enabled" :[NSNumber numberWithBool:1], 
              @"inverse" :[NSNumber numberWithBool:0], 
              @"scale"  :@10, 
              @"source" :@73, 
              @"transform" :@1, 
             }]; 

AVAudioUnitSampler *sampler;//already initialized and loaded with samples or this won't work 

CFPropertyListRef presetPlist; 
UInt32 presetSize = sizeof(CFPropertyListRef); 
AudioUnitGetProperty(sampler.audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &presetPlist, &presetSize); 
NSMutableDictionary *mutablePreset = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)presetPlist]; 
CFRelease(presetPlist); 
NSMutableDictionary *instrument = [NSMutableDictionary dictionaryWithDictionary: mutablePreset[@"Instrument"]]; 

NSArray *existingLayers = instrument[@"Layers"]; 
if (existingLayers.count) { 
    NSMutableArray  *layers  = [[NSMutableArray alloc]init]; 
    for (NSDictionary *layer in existingLayers){ 
     NSMutableDictionary *mutableLayer = [NSMutableDictionary dictionaryWithDictionary:layer]; 
     NSArray *existingConections = mutableLayer[@"Connections"]; 

     if (existingConections) { 
      attackConnection[@"ID"] = [NSNumber numberWithInteger:existingConections.count]; 
      NSMutableArray *connections = [NSMutableArray arrayWithArray:existingConections]; 
      [connections addObject:attackConnection]; 
      [mutableLayer setObject:connections forKey:@"Connections"]; 
     } 
     else{ 
      attackConnection[@"ID"] = [NSNumber numberWithInteger:0]; 
      [mutableLayer setObject:@[attackConnection] forKey:@"Connections"]; 
     } 
     [layers addObject:mutableLayer]; 
    } 
    [instrument setObject:layers forKeyedSubscript:@"Layers"]; 
} 
else{ 
    instrument[@"Layers"] = @[@{@"Connections":@[attackConnection]}]; 
} 
[mutablePreset setObject:instrument forKey:@"Instrument"]; 

CFPropertyListRef editedPreset = (__bridge CFPropertyListRef)mutablePreset; 
AudioUnitSetProperty(sampler.audioUnit,kAudioUnitProperty_ClassInfo,kAudioUnitScope_Global,0,&editedPreset,sizeof(presetPlist)); 

然後,在建立了這個連接之後,您可以像這樣設置攻擊。

uint8_t value = 100; //0 -> 127 
[sampler sendController:73 withValue:value onChannel:0];