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

Posted

技术标签:

【中文标题】带有 AVFoundation 的 AVAudioUnitSampler 的正确音量包络【英文标题】:Proper Volume Envelopes for AVAudioUnitSampler with AVFoundation 【发布时间】:2015-04-26 16:49:48 【问题描述】:

如何为 AVAudioUnitSampler 正确创建音量包络?

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

到目前为止我做了什么:

我设置了一个代表我的采样率的全局计时器。 为此,我使用了间隔长度为 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()
        
    

【问题讨论】:

【参考方案1】:

哇!这是制作体积包络的一种非常复杂的方法!

你有没有想过让它更简单一点?而不是试图控制增益控制,我只会修改实际输出的音频。首先,我将我的样本存储为向量。然后,我将创建另一个向量来代表我的体积包络。然后,我将创建一个新的回放样本,它只是样本和幅度向量的逐个元素相乘。这是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); 

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

【讨论】:

你好 PicnicTripper!感谢你的回答。您对如何在 ios 中执行此操作有一些指导吗?据我了解您的方法-> 将文件的每个样本与范围在 0 和 1 之间的函数的幅度相乘。音频框架的文档在 iOS 中并不是最好的。所以我总是欢迎进一步的意见:) 嗨托拜厄斯!这正是您将振幅包络应用于声音的方式。遗憾的是,我不是 iOS 开发人员,但我可能能够提供帮助。如果您希望实时执行此操作,也许您可​​以在此处为您的代码添加另一层; engine.attachNode(sampler) engine.connect(sampler, to: engine.mainMixerNode, format: nil) 是否可以将采样器附加到处理部分,该处理部分可以获取传入的幅度并将它们乘以幅度包络?如果你有一个幅度值流进来,你可以在那里执行你的幅度包络函数吗? 是的,有一种方法可以在 iOS 中使用 Audiobuffer 对象。我需要检查我是否可以用采样器的输出填充音频缓冲区,然后在按下每个按钮时重新启动包络文件。不幸的是,Audiobuffer 类并不容易掌握......我会在这里更新任何进展。 这里有什么进展吗?我试图至少让我的钢琴应用程序工作的信封的释放部分。 您可以尝试使用加速框架vDSP。它有很好的乘法向量转换。我还没有尝试过,但是您可以对音频缓冲区数据执行 vDSP_vsmul developer.apple.com/library/prerelease/ios/documentation/…【参考方案2】:

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

编辑

优胜美地的 AULab 目前已损坏,与此同时,我从我自己的一个预设中检查了攻击所需的控制连接。预设格式是相当具体的,所以如果有一些冲突我不会感到惊讶,但这适用于基本设置。

这种方法只是临时修复。它所做的是检索预设(字典数组的字典数组的 NSDictionary),然后将控件(NSDictionary)添加到“控件”数组。然后你设置你的采样器预设。这是在您将预设或样本加载到基础 AUSampler 上后完成的,该 AUSampler 可从 AVAudioUnitSampler 的 audioUnit 属性访问。

//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];

【讨论】:

以上是关于带有 AVFoundation 的 AVAudioUnitSampler 的正确音量包络的主要内容,如果未能解决你的问题,请参考以下文章

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

带有 UIImagePickerController 和 AVFoundation 的自定义相机

带有文本层的视频导出AVFoundation崩溃

何时以及如何正确停用您的音频会话?

SKScene 中的 AVAudio 播放器出错

使用 AVAudio 循环播放 mp3