Поиск причины потрескивания и всплывания в пользовательских AUAudioUnit - PullRequest
0 голосов
/ 11 февраля 2020

Я пытаюсь изучить Core Audio и для начала я попытался создать базовый c аудиоустройство, которое просто генерирует синусоидальную волну с регулируемой частотой. Проблема, с которой я столкнулся сейчас, заключается в том, что у меня возникают сильные потрескивания и треск, когда я настраиваю частоту, и я не уверен, где искать причину этого.

Я даже не могу сказать, если это только потому, что я Мне нужно сделать некоторое сглаживание при переходе между частотами, хотя я пока не видел этого ни в одном примере.

В любом случае, вот реализация моего аудиоустройства:

#import "VCOUnit.h"

@interface VCOUnit () {
    AUParameterTree *_parameterTree;
    AUAudioUnitBusArray *_outputBusses;
    AVAudioPCMBuffer *_outputBuffer;
}

@end

@implementation VCOUnit

- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription options:(AudioComponentInstantiationOptions)options error:(NSError *__autoreleasing  _Nullable *)outError {
    self = [super initWithComponentDescription:componentDescription options:options error:outError];

    return self;
}

- (AUParameterTree *)parameterTree {
    if (!_parameterTree) {
        AUParameter *frequency = [AUParameterTree createParameterWithIdentifier:@"frequency"
                                                                           name:@"Frequency"
                                                                        address:0
                                                                            min:10
                                                                            max:500
                                                                           unit:kAudioUnitParameterUnit_Hertz
                                                                       unitName:nil
                                                                          flags:0
                                                                   valueStrings:nil
                                                            dependentParameters:nil];

        frequency.value = 220.0;

        _parameterTree = [AUParameterTree createTreeWithChildren:@[frequency]];
    }

    return _parameterTree;
}

- (AUAudioUnitBusArray *)outputBusses {
    if (!_outputBusses) {
        AVAudioFormat *format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:1];
        AUAudioUnitBus *bus = [[AUAudioUnitBus alloc] initWithFormat:format error:nil];
        _outputBusses = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[bus]];
    }

    return _outputBusses;
}

- (BOOL)allocateRenderResourcesAndReturnError:(NSError *__autoreleasing  _Nullable *)outError {
    if (![super allocateRenderResourcesAndReturnError:outError]) {
        return NO;
    }

    AVAudioFormat *format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:1];
    _outputBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:1024];

    return YES;
}

- (AUInternalRenderBlock)internalRenderBlock {
    AUParameter *frequencyParam = [self.parameterTree parameterWithAddress:0];
    float deltaTime = 1.0 / 44100.0;
    __block float time = 0;

    return ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList *outputData, const AURenderEvent *realtimeEventListHead, AURenderPullInputBlock pullInputBlock) {

        AudioBufferList *abl = outputData;

        for (int frame = 0; frame < frameCount; frame++) {
            float value = sin(2.0 * M_PI * frequencyParam.value * time);

            for (int i = 0; i < abl->mNumberBuffers; i++) {
                float *output = abl->mBuffers[i].mData;
                output[frame] = value;
            }

            time += deltaTime;
        }

        return noErr;
    };
}

@end

У меня есть класс Swift, который обертывает его и который я использую для привязки частоты к ползунку SwiftUI:

class VCO: ObservableObject {
    @Published var frequency = 220.0 {
        didSet {
            guard
                let parameterTree = unit.auAudioUnit.parameterTree,
                let freqParam = parameterTree.parameter(withAddress: 0) else {
                    return
            }
            freqParam.setValue(AUValue(frequency), originator: nil)
        }
    }

    var unit: AVAudioUnit!

    private let componentDescription = AudioComponentDescription(componentType: kAudioUnitType_Generator,
        componentSubType: OSType(1),
        componentManufacturer: 0x666f6f20,
        componentFlags: 0,
        componentFlagsMask: 0
    )

    init() {
        AUAudioUnit.registerSubclass(VCOUnit.self, as: componentDescription, name: "VCO", version: 1)
    }

    func setup(sampleRate: Double) {
        let opts = AudioComponentInstantiationOptions.init(rawValue: 0)
        AVAudioUnit.instantiate(with: componentDescription, options: opts) { [weak self] (unit, error) in
            guard let unit = unit else {
                print(error!)
                return
            }

            self?.unit = unit
        }
    }
}

Конечно, на данном этапе это не рабочий код, но я подозреваю причину появления трещин и всплывающих окон. это нечто вполне конкретное c. Я даже начинал думать, связано ли это с тем, что я использую привязки SwiftUI для обновления или что-то в этом роде, но это звучит немного надуманным. Скорее всего, проблема в блоке рендеринга, но я пока не вижу его. :)

В любом случае, любой вклад приветствуется.

1 Ответ

1 голос
/ 11 февраля 2020

Это типичная ошибка, вызванная увеличением времени и вычислением новой фазы (для параметра функции синуса) на каждой итерации l oop, которая приводит к разрывам фазы при изменении частоты.

Одним простым решением является фаза приращения вместо времени. Легко вычислить дельту фазы в единицу времени для данной частоты и выполнить это вычисление за пределами внутреннего l oop. Поскольку эта дельта фазы невелика, не будет больших разрывов фазы для образования трещин и треск.

Вычисление новой фазы таким образом, что каждая итерация требует только сложения, а не умножения (на 2,0 * M_PI, et c .).

Возможно, вы захотите ограничить диапазон фазы внутри + - M_PI или + -2pi, вместо того, чтобы позволить ему расти произвольно большим, что может вызвать ошибки уменьшения диапазона внутри функции синуса.

...