( Update : found the answer, see below.)
I am trying to reproduce a 1 kHz sine wave in an Cocoa Objective-C application; I tried to translate the Swift example to Objective-C, but somewhere the error should be somewhere, since the resulting tone is about 440 Hz instead of 1 kHz and only on the left channel.
Code:
@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];
I suspect that there is either a mathematical error in the float theta=... line, or I am mistaken in the floatChannelData buffer. The original Swift line reads:
buffer.floatChannelData.memory[i] = val * 0.5
Not sure what to do with the type float *const * floatChannelData sure. I understand that this is a pointer to a 2 x float * const array. (2 due to the number of channels, left / right.)
The source of the Swift code is here: http://www.tmroyal.com/playing-sounds-in-swift-audioengine.html
It would be very nice if someone could explain the structure of the buffer to me.
Solution found
The problem was doubled. First, a value of 441.0 really controlled the frequency. But changing this alone did not solve the problem; the resulting tone was more like a sawtooth than a sine, and found out why.
With a coefficient of 441 and a sampling frequency of 44.1 kHz, the ratio of these values ββis 1: 100 - exactly the number of samples in the buffer. Changing 441 to a value that is not a multiple of what results in an βincompleteβ sine wave: the value in the last frame of the sample (No. 100) is not equal to zero, which causes a sharp drop when you restart the cycle - and it sounds like a sawtooth wave.
I had to change the frame buffer length as an exact (or multiple) ratio of the frequency to the sample, so the last sample value was (close to zero).
Updated code:
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; } }
Thus, any number of channels is processed correctly.