objective-c - 在 Cocoa 中播放正弦波音调

标签 objective-c macos cocoa audio

(更新:找到答案,见下文。)

我正在尝试在 Objective-C Cocoa 应用程序中播放 1 kHz 正弦波音调;我已经(尝试)将一个 Swift 示例转换为 Objective-C,但一定有什么地方出错了,因为生成的音调大约是 440 Hz 而不是 1 kHz,而且只在左声道上。

代码:

@property (nonatomic, strong) AVAudioEngine *audioEngine;
@property (nonatomic, strong) AVAudioPlayerNode *player;
@property (nonatomic, strong) AVAudioMixerNode *mixer;
@property (nonatomic, strong) AVAudioPCMBuffer *buffer;

// -----

self.audioEngine = [[AVAudioEngine alloc] init];
self.player = [[AVAudioPlayerNode alloc] init];
self.mixer = self.audioEngine.mainMixerNode;
self.buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[self.player outputFormatForBus:0] frameCapacity:100];
self.buffer.frameLength = 100;

float amplitude = 0.4;
float frequency = 1000;
float sampleRate = [[self.mixer outputFormatForBus:0] sampleRate];
NSInteger channelCount = [[self.mixer outputFormatForBus:0] channelCount];


float *const *floatChannelData = self.buffer.floatChannelData;
float *p2 = *floatChannelData;

NSLog(@"Sine generator: sample rate = %.1f, %ld channels, frame length = %u.", sampleRate, (long)channelCount, self.buffer.frameLength);

for (int i = 0; i < self.buffer.frameLength ; i += channelCount) {

    // a = Amplitude
    // n = current sample
    // r = Sample rate (samples / sec.)
    //
    // f(n) = a * sin( theta(n) )
    // where theta(n) = 2 * M_PI * n / r

    float theta = 441.0f * i * 2.0 * M_PI / sampleRate;
    float value = sinf(theta);

    p2[i] = value * amplitude;
}


[self.audioEngine attachNode:self.player];
[self.audioEngine connect:self.player to:self.mixer format:[self.player outputFormatForBus:0]];
[self.audioEngine startAndReturnError:nil];

[self.player play];
[self.player scheduleBuffer:self.buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];

我怀疑 float theta=... 行中存在数学错误,或者我对 floatChannelData 缓冲区有误。原始的 Swift 行如下:

buffer.floatChannelData.memory[i] = val * 0.5

不确定 float *const * 类型的 floatChannelData 究竟是什么。我的理解是,这是一个指向 2 x float * const 数组的指针。 (2 因为 channel 数,左/右。)

Swift 代码的来源在这里:http://www.tmroyal.com/playing-sounds-in-swift-audioengine.html

如果有人能向我解释缓冲区结构,那就太好了。

找到解决方案

问题有两个。首先,值 441.0 确实控制了频率。但是仅仅改变它并不能解决问题;产生的音调比正弦波更像锯齿波,并找出原因。

使用因子 441 和 44.1 kHz 的采样率,这些值的比率为 1:100 - 正好是缓冲区中的样本数。将 441 更改为不是其整数倍的值会导致“不完整”正弦波:最后一个样本帧 (#100) 中的值不为零,这会在循环再次开始时导致急剧下降 -这听起来像锯齿波。

我必须将帧缓冲区长度更改为正好(或倍数)频率与采样率的比率,以便最后的样本值(接近于)零。

更新后的代码:

self.audioEngine = [[AVAudioEngine alloc] init];
self.player = [[AVAudioPlayerNode alloc] init];
self.mixer = self.audioEngine.mainMixerNode;

float sampleRate = [[self.mixer outputFormatForBus:0] sampleRate];
AVAudioFrameCount frameBufferLength = floor(sampleRate / self.frequency) * 1;

self.buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[self.player outputFormatForBus:0] frameCapacity:frameBufferLength];
self.buffer.frameLength = frameBufferLength;

NSInteger channelCount = [[self.mixer outputFormatForBus:0] channelCount];

float *const *floatChannelData = self.buffer.floatChannelData;    

NSLog(@"Sine generator: sample rate = %.1f, %ld channels, frame length = %u.", sampleRate, (long)channelCount, self.buffer.frameLength);

for (int i = 0; i < self.buffer.frameLength ; i ++) {
    float theta = self.frequency * i * 2.0 * M_PI / sampleRate;
    float value = sinf(theta);
    for (int channelNumber = 0; channelNumber < channelCount ; channelNumber++) {
        float * const channelBuffer = floatChannelData[channelNumber];
        channelBuffer[i] = value * self.amplitude;
    }   
}

这样也可以正确处理任意数量的 channel 。

最佳答案

频率部分很简单:文字 441.0f在你的计算中 theta控制它,所以只需将其更改为您想要的任何内容即可。

对于单声道问题,您似乎只写入了一个数据通道:p2[i] = value * amplitude;如果你对 floatChannelData 的组成是正确的,那么你想要这个:

float * const * floatChannelData = self.buffer.floatChannelData;
float * const left = floatChannelData[0];
float * const right = floatChannelData[1];

//...

// N.B. Changed the increment
for (int i = 0; i < self.buffer.frameLength ; i++ ) {

    // ...

    left[i] = value * amplitude;
    right[i] = value * amplitude;
}

但是,鉴于您的 for 循环中的递增步骤,您的缓冲区可能是交错的(左右 channel 在同一缓冲区中交替)。在这种情况下,您保留循环增量,但同时写入 p2[i]p2[i+1]在每一步上(立体声很容易;如果你有更多的 channel ,你会在这些 channel 上做一个内部循环并写入 p2[j] 以获得从 0 到 $NUM_CHANNELS 的 j)。

关于objective-c - 在 Cocoa 中播放正弦波音调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35561741/

相关文章:

iphone - 如何动态设置UIScrollView的内容大小

c# - iOS 版 SQLite - RANDOM() 不够好

objective-c - 从 Cocoa 中的文件中只读取 "N"字节

ios - 在 Xcode 5.1.1 中打开 Storyboard后屏幕变黑

swift - 在 Swift macOS 中在 NSButton 旁边显示 NSMenu

ios - 使用两个以上的参数在 Objective-C 中执行动态类方法调用

macos - 如何制作均匀分布的NSSegmentedControl?

macos - 将界面生成器连接到 Xcode 4 中的代码

cocoa - 如何在 obj-c 中拖放 '.txt' 文件

macos - 订阅 Spotify 事件