AudioUnit tone generator is giving me a chirp at the end of each tone generated

后端 未结 2 993
傲寒
傲寒 2021-02-04 22:48

I\'m creating a old school music emulator for the old GWBasic PLAY command. To that end I have a tone generator and a music player. Between each of the notes played

相关标签:
2条回答
  • 2021-02-04 23:03

    That little chirp is generally an artifact of mathematics. The ear essentially analyzes input signals in the frequency domain. A steady sine wave at frequency 220 Hz, for example, will sound like an A. However, when your sine wave is *un*steady, there are other frequencies that show up due to the boundary. In particular, you get a bit of a pop due to the very high frequency component of starting or stopping a sound abruptly.

    The way I solved this in my synthesizer (in Javascript, not Obj-C, but the concept here is the same) is to fade the sound in over 300 samples or so on note on, and fade the sound out over 300 samples or so on note off. There's no way to truly eliminate boundary effects other than not having a boundary at all, but even a small and imperceptible amount of fade will render the boundary effect imperceptible as well.

    0 讨论(0)
  • 2021-02-04 23:15

    I think that the bit where you stop audio output between notes is the culprit:

    if (self.toneUnit)
    {
        AudioOutputUnitStop(self.toneUnit);
        AudioUnitUninitialize(self.toneUnit);
        AudioComponentInstanceDispose(self.toneUnit);
        self.toneUnit = nil;
    }
    

    Just leave the tone unit active and you'll have less chirping. You'll need some other way to generate silence, probably by having RenderTone continue to run but generate amplitude zero.

    I was able to eliminate the slight chirps that remained by having it, on a frequency change, fade the amplitude down to nothing, update the frequenmcy, and fade back in again. This is of course what the old PC speaker couldn't do (except for a few people who rapidly switched it on again), but with a very rapid fade you can probably get the old-school effect without the chirps.

    Here's my fading RenderTone function (currently using evil global variables):

    double currentFrequency=0;
    double currentSampleRate=0;
    double currentAmplitude=0;
    
    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.5;
    
        // Get the tone parameters out of the view controller
        ToneGen *toneGen = (__bridge ToneGen *)inRefCon;
        double theta = toneGen.theta;
    
        BOOL fadingOut = NO;
        if ((currentFrequency != toneGen.frequency) || (currentSampleRate != toneGen.sampleRate))
        {
            if (currentAmplitude > DBL_EPSILON)
            {
                fadingOut = YES;
            }
            else
            {
                currentFrequency = toneGen.frequency;
                currentSampleRate = toneGen.sampleRate;
            }
        }
    
        double theta_increment = 2.0 * M_PI * currentFrequency /currentSampleRate;
    
        // 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) * currentAmplitude;
            //NSLog(@"amplitude = %f", currentAmplitude);
    
            theta += theta_increment;
            if (theta > 2.0 * M_PI)
            {
                theta -= 2.0 * M_PI;
            }
            if (fadingOut)
            {
                if (currentAmplitude > 0)
                {
                    currentAmplitude -= 0.001;
                    if (currentAmplitude < 0)
                        currentAmplitude = 0;
                }
            }
            else
            {
                if (currentAmplitude < amplitude)
                {
                    currentAmplitude += 0.001;
                    if (currentAmplitude > amplitude)
                        currentAmplitude = amplitude;
                }
            }
    
        }
    
        // Store the theta back in the view controller
        toneGen.theta = theta;
    
        return noErr;
    }
    
    0 讨论(0)
提交回复
热议问题