Trying to add an Input Filter, result is noisy

I am trying to add a Dalek type effect to my incoming audio via addInputFilter:. This is working, but it has some crackling, almost like an old vinyl record playing on a phonograph. It's hard to describe, but I'm sure it's something wrong with my math. Can anyone help with this?

I am using floats because I init AEAudioController with nonInterleavedFloatStereoAudioDescription.

Here is my code:

self.dalekEffect = [AEBlockFilter filterWithBlock:^(AEAudioControllerFilterProducer producer,
                                               void *producerToken,
                                               const AudioTimeStamp *time,
                                               UInt32 frames,
                                               AudioBufferList *audio) {
    // Pull audio
    OSStatus status = producer(producerToken, audio, &frames);
    if ( status != noErr ) return;
    // Now filter audio in 'audio'

    float sample = 0.f;

    for (int bufCount=0; bufCount<audio->mNumberBuffers; bufCount++) {

        AudioBuffer buf = audio->mBuffers[bufCount];
        for (int currentFrame = 0; currentFrame < frames; currentFrame++ ) {
            memcpy(&sample, buf.mData + (currentFrame * 4), sizeof(float));

            float theta = sinePhase * M_PI * 2.f;

            sample = (sinf(theta) * sample);

            memcpy(buf.mData + (currentFrame * 4), &sample, sizeof(float));

            sinePhase += 1.0 / (44100.f / sineFrequency);
            if (sinePhase > 1.0) {
                sinePhase -= 1.0;
            }
        }
    }
}];

Thanks in advance, any ideas are greatly appreciated. I love The Amazing Audio Engine and have found a lot of great help in these forums!

Comments

  • edited August 2015

    Hey @macguy =) I'd say the problem is that you're incrementing sinePhase for each sample, for each channel, so that for successive buffers, you're getting discontinuities. Rather, you should be incrementing sinePhase for each sample across all channels.

    Right now you're doing:

    for each channel:
      for each sample:
        do stuff
        increment sinePhase
    

    So say sinePhase is incrementing by 1 for each sample, and your filter callback is called with 2 channels, for 256 frames. For channel 1, your sinePhase is going from 0 to 256. Then for channel 2, it's going from 256 to 512.

    Then the next time you get a callback, values of sinePhase for channel 1 are going from 512 to 768, but you've got a discontinuity there (it was previously 256, now it's 512!).

    Instead, try setting a local sinePhase variable at the start of processing each channel, set to your instance/__block variable. Then only update your instance/__block variable once you've finished iterating each channel.

    var localSinePhase
    for each channel:
      localSinePhase = sinePhase
      for each sample:
        do stuff
        increment localSinePhase
    sinePhase = localSinePhase
    

    Now your values are going to be 0-256 for both channels for the first 256 frames, and 256-512 for both channels for the next 256 frames and so on - no discontinuities.

    One final note: the memcpy there is a tad inefficient - better to just use a pointer variable assignment and work with that:

    float * sample = ((float*)audio->mBuffers[bufCount]->mData) + currentFrame;
    ...
    *sample *= sinf(theta);
    
  • @Michael thanks! Can't believe I missed that. Are you using Swift more these days? I had looked into it before this project and it looked like it still lacked in some areas relating to audio. Anyhow, thanks again for the quick fix!

  • No worries! =)

    Nope, I'm not really using Swift - it's lovely for non-audio stuff, but it just isn't the right tool for the job when audio's involved, as far as I'm concerned. It has the same problems as ObjC when used on the realtime thread (priority inversion/glitches), but it's much harder to mix with plain C because you can't directly access members of Swift classes in C, so you need all kinds of mapping 'glue' code. It can be done, and some developers might argue it's worth it, but I personally don't see significant gains for the extra hassle.

Sign In or Register to comment.