0

I'm completely a beginner when it comes to audio programming and right now I'm playing around with AudioUnit. I'm following http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html and I've ported over the code to work with iOS7. The problem is that I only want it to play the generated sine wave once and not keep on playing the sound wave. I am not sure how to accomplish this though.

Generating audio samples:

    OSStatus RenderTone(
        void *inRefCon, 
        AudioUnitRenderActionFlags *ioActionFlags, 
        const AudioTimeStamp *inTimeStamp, 
        UInt32 inBusNumber, 
        UInt32 inNumberFrames, 
        AudioBufferList *ioData)

    {
        // Fixed amplitude is good enough for our purposes
        const double amplitude = 0.25;

        // Get the tone parameters out of the view controller
        ToneGeneratorViewController *viewController =
            (ToneGeneratorViewController *)inRefCon;
        double theta = viewController->theta;
        double theta_increment =
            2.0 * M_PI * viewController->frequency / viewController->sampleRate;

        // This is a mono tone generator so we only need the first buffer
        const int channel = 0;
        Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;

        // Generate the samples
        for (UInt32 frame = 0; frame < inNumberFrames; frame++) 
        {
            buffer[frame] = sin(theta) * amplitude;

            theta += theta_increment;
            if (theta > 2.0 * M_PI)
            {
                theta -= 2.0 * M_PI;
            }
        }

        // Store the updated theta back in the view controller
        viewController->theta = theta;

        return noErr;
    }

Creating AudioUnit:

// Configure the search parameters to find the default playback output unit
// (called the kAudioUnitSubType_RemoteIO on iOS but
// kAudioUnitSubType_DefaultOutput on Mac OS X)
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;

// Get the default playback output unit
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
NSAssert(defaultOutput, @"Can't find default output");

// Create a new unit based on this that we'll use for output
OSErr err = AudioComponentInstanceNew(defaultOutput, &toneUnit);
NSAssert1(toneUnit, @"Error creating unit: %ld", err);

// Set our tone rendering function on the unit
AURenderCallbackStruct input;
input.inputProc = RenderTone;
input.inputProcRefCon = self;
err = AudioUnitSetProperty(toneUnit, 
    kAudioUnitProperty_SetRenderCallback, 
    kAudioUnitScope_Input,
    0, 
    &input, 
    sizeof(input));
NSAssert1(err == noErr, @"Error setting callback: %ld", err);

// Set the format to 32 bit, single channel, floating point, linear PCM
const int four_bytes_per_float = 4;
const int eight_bits_per_byte = 8;
AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = sampleRate;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
    kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
streamFormat.mBytesPerPacket = four_bytes_per_float;
streamFormat.mFramesPerPacket = 1;    
streamFormat.mBytesPerFrame = four_bytes_per_float;        
streamFormat.mChannelsPerFrame = 1;    
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
err = AudioUnitSetProperty (toneUnit,
    kAudioUnitProperty_StreamFormat,
    kAudioUnitScope_Input,
    0,
    &streamFormat,
    sizeof(AudioStreamBasicDescription));
NSAssert1(err == noErr, @"Error setting stream format: %ld", err);

Thanks!

1 Answers1

0

The problem is that I only want it to play the generated sine wave once

What you should do is stopping the audio unit after a certain time.

You could, e.g., set an NSTimer when you call AudioOutputUnitStart and then when the timer fires, you call AudioOutputUnitStop (actually, your audio unit disposal code). Even simpler, you could use performSelector:withObject:afterDelay: and call your audio unit disposal method.

Hope this helps.

sergio
  • 68,819
  • 11
  • 102
  • 123
  • Thanks Sergio! So there's no way for me to feed it an array of floats and tell it to stop after that? The best way is to calculate the amount of time that would elapse, and then call AudioOutputUnitStop after that time? – user3476839 Jun 26 '14 at 17:34
  • well, you could modify your `RenderTone` callback so that it just produces the desired amount of packets (it seems to me that it now produces an infinite sine wave), then provides the audio unit a NULL-filled buffer: you would hear your generated sine wave once and nothing more thereafter -- but the audio would be still on, cpu etc. I think it is much cleaner (and easier) if you calculate how long your sine wave should last and then stop the sound. A nice touch would be also fading the sound out, instead of stopping it abruptly... – sergio Jun 26 '14 at 20:57
  • Ahh.. so would I have some variable to track if `RenderTone` has been called before? If it has, then I would make `mBuffers` null padded? Correct me if I'm wrong here, but the reason it is looping over and over again is because I register `RenderTone` as a callback and it's just calling itself repeatedly? Thanks a lot @sergio ! – user3476839 Jun 26 '14 at 21:46
  • as you say, RenderTone is called repeatedly as the audio unit requires. It is not the case that one calls corresponds to one full sine wave -- absolutely not. Indeed, one full sine wave would be not audible. You would have to modify RenderTone so that it produces you sine wave as you like and then returns zero. But, as I was suggesting is so much easier to just stop the audio unit -- try out the `performSelector…:afterDelay:`, it will be a snap... – sergio Jun 27 '14 at 07:26