Reproduction of a sinusoidal signal in Cocoa

( 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.

+5
source share
1 answer

The frequency part is simple: the literal 441.0f in your theta control calculation, so just change it to whatever you want.

For monaural, you can write only one data channel: p2[i] = value * amplitude; If you correctly set up the composition floatChannelData , then you want:

 float * const * floatChannelData = self.buffer.floatChannelData; float * const left = floatChannelData[0]; float * const right = floatChannelData[1]; //... // NB Changed the increment for (int i = 0; i < self.buffer.frameLength ; i++ ) { // ... left[i] = value * amplitude; right[i] = value * amplitude; } 

However, given the increment step in your for loop, it is possible that your buffer is alternating (left and right channels alternate in the same buffer). In this case, you will leave the loop increment, but write both on p2[i] and p2[i+1] at each step (just for stereo, if you have more channels, you will make an inner loop over them and write on p2[j] for j from 0 to $ NUM_CHANNELS).

+2
source

Source: https://habr.com/ru/post/1243606/


All Articles