具有AVFoundation的AVAudioUnitSampler的正确音量包络

时间:2015-04-26 16:49:49

标签: ios swift audio avfoundation

如何为AVAudioUnitSampler正确创建卷信封?

基本上我想添加一个攻击阶段,其中音量会在几毫秒内消失以避免点击。另外,我想添加一个发布阶段,以便在停止播放音符后音量消失。

到目前为止我做了什么:

我设置了一个代表我的采样率的全局计时器。 为此,我使用间隔长度为1个样本的NSTimer(对于44100的采样率,一个采样持续时间为1/44100)。那样一个人不应该听到音量"跳跃"导致"点击"。也许我需要过采样到两倍的采样率。

只要增益低于某个阈值,定时器就会调用一个增加masterGain的函数。通过将期望和当前增益的差除以采样率然后将该量除以期望的渐变时间来计算I增量。 (在下面的示例中,我使用固定值以便于阅读)

达到阈值后,我取下计时器并停止记录。

- >我认为这种方法非常复杂,特别是当我使用发布阶段时。

请检查我的示例代码。我使用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 个答案:

答案 0 :(得分: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); 

或者,如果您希望实时进行,则会变得有点棘手。如果延迟在这里不是一个大问题,我建议有一个轻微的前瞻窗口,它会存储你声音的输出样本并开始在那里应用振幅音量方法。

答案 1 :(得分:0)

最简单的方法是设置性能参数,查看AUSampler - Controlling the Settings of the AUSampler in Real Time。然后设置你的攻击。您也可以在创建预设时对这些值进行硬编码,但参数更灵活。

修改

优胜美地的AULab目前已被打破,与此同时,我从我自己的预设中检查了攻击所需的控制连接。预设格式非常具体,所以如果发生冲突我不会感到惊讶,但这适用于基本设置。

这种方法只是一个临时修复。它的作用是检索预设(字典数组字典数组的NSDictionary),然后将一个控件(一个NSDictionary)添加到&#34;控件&#34;阵列。然后设置您的采样器预设。在从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];