菜鸟教程小白 发表于 2022-12-12 20:06:49

objective-c - AudioUnit 音调发生器在生成的每个音调结束时都会给我一个啁啾声


                                            <p><p>我正在为旧的 GWBasic <code>PLAY</code> 命令创建一个老式的音乐模拟器。为此,我有一个音调发生器和一个音乐播放器。在演奏的每个音符之间,我都会听到唧唧喳喳的声音,把事情搞砸了。以下是我的两个类(class):</p>

<p><strong>ToneGen.h</strong></p>

<pre><code>#import &lt;Foundation/Foundation.h&gt;

@interface ToneGen : NSObject
@property (nonatomic) id delegate;
@property (nonatomic) double frequency;
@property (nonatomic) double sampleRate;
@property (nonatomic) double theta;
- (void)play:(float)ms;
- (void)play;
- (void)stop;
@end
</code></pre>

<p><strong>ToneGen.m</strong></p>

<pre><code>#import &lt;AudioUnit/AudioUnit.h&gt;
#import &#34;ToneGen.h&#34;

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


@interface ToneGen()
@property (nonatomic) AudioComponentInstance toneUnit;
@property (nonatomic) NSTimer *timer;
- (void)createToneUnit;
@end


@implementation ToneGen
@synthesize toneUnit = _toneUnit;
@synthesize timer = _timer;
@synthesize delegate = _delegate;
@synthesize frequency = _frequency;
@synthesize sampleRate = _sampleRate;
@synthesize theta = _theta;

- (id) init
{
    self = ;
    if (self)
    {
      self.sampleRate = 44100;
      self.frequency = 1440.0f;
      return self;
    }
    return nil;
}

- (void)play:(float)ms
{
    ;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:(ms / 100)
                                                target:self
                                                selector:@selector(stop)
                                                userInfo:nil
                                                 repeats:NO];
    [ addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)play
{
    if (!self.toneUnit)
    {
      ;

      // Stop changing parameters on the unit
      OSErr err = AudioUnitInitialize(self.toneUnit);
      if (err)
            DLog(@&#34;Error initializing unit&#34;);

      // Start playback
      err = AudioOutputUnitStart(self.toneUnit);
      if (err)
            DLog(@&#34;Error starting unit&#34;);
    }
}

- (void)stop
{
    ;
    self.timer = nil;

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

    if(self.delegate &amp;&amp; ) {
      ;
    }
}

- (void)createToneUnit
{
    AudioComponentDescription defaultOutputDescription;
    defaultOutputDescription.componentType = kAudioUnitType_Output;
    defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
    defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    defaultOutputDescription.componentFlags = 0;
    defaultOutputDescription.componentFlagsMask = 0;

    // Get the default playback output unit
    AudioComponent defaultOutput = AudioComponentFindNext(NULL, &amp;defaultOutputDescription);
    if (!defaultOutput)
      DLog(@&#34;Can&#39;t find default output&#34;);

    // Create a new unit based on this that we&#39;ll use for output
    OSErr err = AudioComponentInstanceNew(defaultOutput, &amp;_toneUnit);
    if (err)
      DLog(@&#34;Error creating unit&#34;);

    // Set our tone rendering function on the unit
    AURenderCallbackStruct input;
    input.inputProc = RenderTone;
    input.inputProcRefCon = (__bridge void*)self;
    err = AudioUnitSetProperty(self.toneUnit,
                               kAudioUnitProperty_SetRenderCallback,
                               kAudioUnitScope_Input,
                               0,
                               &amp;input,
                               sizeof(input));
    if (err)
      DLog(@&#34;Error setting callback&#34;);

    // 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 = self.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 (self.toneUnit,
                              kAudioUnitProperty_StreamFormat,
                              kAudioUnitScope_Input,
                              0,
                              &amp;streamFormat,
                              sizeof(AudioStreamBasicDescription));
    if (err)
      DLog(@&#34;Error setting stream format&#34;);
}

@end


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
    ToneGen *toneGen = (__bridge ToneGen *)inRefCon;
    double theta = toneGen.theta;
    double theta_increment = 2.0 * M_PI * toneGen.frequency / toneGen.sampleRate;

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

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

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

    // Store the theta back in the view controller
    toneGen.theta = theta;

    return noErr;
}

void ToneInterruptionListener(void *inClientData, UInt32 inInterruptionState)
{
    ToneGen *toneGen = (__bridge ToneGen *)inClientData;
    ;
}
</code></pre>

<p><strong>Music.h</strong></p>

<pre><code>#import &lt;Foundation/Foundation.h&gt;

@interface Music : NSObject
- (void) play:(NSString *)music;
- (void) stop;
@end
</code></pre>

<p><strong>Music.m</strong></p>

<pre><code>#import &#34;Music.h&#34;
#import &#34;ToneGen.h&#34;

@interface Music()
@property (nonatomic, readonly) ToneGen *toneGen;
@property (nonatomic, assign) int octive;
@property (nonatomic, assign) int tempo;
@property (nonatomic, assign) int length;

@property (nonatomic, strong) NSData *music;
@property (nonatomic, assign) int dataPos;
@property (nonatomic, assign) BOOL isPlaying;

- (void)playNote;
@end

@implementation Music
@synthesize toneGen = _toneGen;
- (ToneGen*)toneGen
{
    if (_toneGen == nil)
    {
      _toneGen = [ init];
      _toneGen.delegate = self;
    }
    return _toneGen;
}
@synthesize octive = _octive;
- (void)setOctive:(int)octive
{
    // Sinity Check
    if (octive &lt; 0)
      octive = 0;
    if (octive &gt; 6)
      octive = 6;
    _octive = octive;
}
@synthesize tempo = _tempo;
- (void)setTempo:(int)tempo
{
    // Sinity Check
    if (tempo &lt; 30)
      tempo = 30;
    if (tempo &gt; 255)
      tempo = 255;
    _tempo = tempo;
}
@synthesize length = _length;
- (void)setLength:(int)length
{
    // Sinity Check
    if (length &lt; 1)
      length = 1;
    if (length &gt; 64)
      length = 64;
    _length = length;
}
@synthesize music = _music;
@synthesize dataPos = _dataPos;
@synthesize isPlaying = _isPlaying;


- (id)init
{
    self = ;
    if (self)
    {
      self.octive = 4;
      self.tempo = 120;
      self.length = 1;
      return self;
    }
    return nil;
}

- (void) play:(NSString *)music
{
    DLog(@&#34;%@&#34;, music);
    self.music = [
                  dataUsingEncoding: NSASCIIStringEncoding];
    self.dataPos = 0;
    self.isPlaying = YES;
    ;
}

- (void)stop
{
    self.isPlaying = NO;
}

- (void)playNote
{
    if (!self.isPlaying)
      return;

    if (self.dataPos &gt; self.music.length || self.music.length == 0) {
      self.isPlaying = NO;
      return;
    }

    unsigned char *data = (unsigned char*);
    unsigned int code = (unsigned int)data;
    self.dataPos++;

    switch (code) {
      case 65: // A
      case 66: // B
      case 67: // C
      case 68: // D
      case 69: // E
      case 70: // F
      case 71: // G
            {
                // Peak at the next char to look for sharp or flat
                bool sharp = NO;
                bool flat = NO;
                if (self.dataPos &lt; self.music.length) {
                  unsigned int peak = (unsigned int)data;
                  if (peak == 35) // #
                  {
                        self.dataPos++;
                        sharp = YES;
                  }
                  else if (peak == 45)// -
                  {
                        self.dataPos++;
                        flat = YES;
                  }
                }

                // Peak ahead for a length changes
                bool look = YES;
                int count = 0;
                int newLength = 0;
                while (self.dataPos &lt; self.music.length &amp;&amp; look) {
                  unsigned int peak = (unsigned int)data;
                  if (peak &gt;= 48 &amp;&amp; peak &lt;= 57)
                  {
                        peak -= 48;
                        int n = (count * 10);
                        if (n == 0) { n = 1; }
                        newLength += peak * n;
                        self.dataPos++;
                  } else {
                        look = NO;
                  }
                }

                // Pick the note length
                int length = self.length;
                if (newLength != 0)
                {
                  DLog(@&#34;InlineLength: %d&#34;, newLength);
                  length = newLength;
                }


                // Create the note string
                NSString *note = ;
                if (sharp)
                  note = ;
                else if (flat)
                  note = ;

                // Set the tone generator freq
                ];

                // Play the note
                ;
            }
            break;

      case 76: // L (length)
      {
            bool look = YES;
            int newLength = 0;
            while (self.dataPos &lt; self.music.length &amp;&amp; look) {
                unsigned int peak = (unsigned int)data;
                if (peak &gt;= 48 &amp;&amp; peak &lt;= 57)
                {
                  peak -= 48;
                  newLength = newLength * 10 + peak;
                  self.dataPos++;
                } else {
                  look = NO;
                }
            }
            self.length = newLength;
            DLog(@&#34;Length: %d&#34;, self.length);
            ;
      }
            break;

      case 79: // O (octive)
            {
                bool look = YES;
                int newOctive = 0;
                while (self.dataPos &lt; self.music.length &amp;&amp; look) {
                  unsigned int peak = (unsigned int)data;
                  if (peak &gt;= 48 &amp;&amp; peak &lt;= 57)
                  {
                        peak -= 48;
                        newOctive = newOctive * 10 + peak;
                        self.dataPos++;
                  } else {
                        look = NO;
                  }
                }
                self.octive = newOctive;
                DLog(@&#34;Octive: %d&#34;, self.self.octive);
                ;
            }
            break;

      case 84: // T (tempo)
            {
                bool look = YES;
                int newTempo = 0;
                while (self.dataPos &lt; self.music.length &amp;&amp; look) {
                  unsigned int peak = (unsigned int)data;
                  if (peak &gt;= 48 &amp;&amp; peak &lt;= 57)
                  {
                        peak -= 48;
                        newTempo = newTempo * 10 + peak;
                        self.dataPos++;
                  } else {
                        look = NO;
                  }
                }
                self.tempo = newTempo;
                DLog(@&#34;Tempo: %d&#34;, self.self.tempo);
                ;
            }
            break;

      default:
            ;
            break;
    }
}


- (int)getNoteNumber:(NSString*)note
{
    note = ;
    DLog(@&#34;%@&#34;, note);

    if ()
      return 0;
    else if ( || )
      return 1;
    else if ( || )
      return 2;
    else if ( || )
      return 3;
    else if ( || )
      return 4;
    else if ()
      return 5;
    else if ( || )
      return 6;
    else if ( || )
      return 7;
    else if ( || )
      return 8;
    else if ( || )
      return 9;
    else if ()
      return 10;
    else if ()
      return 11;
}

- (void)setFreq:(int)note
{
    float a = powf(2, self.octive);
    float b = powf(1.059463, note);
    float freq = roundf((275.0 * a * b) / 10);
    self.toneGen.frequency = freq;
}

- (void)toneStop
{
    ;
}

@end
</code></pre>

<p>小玩<a href="http://glind.customer.netspace.net.au/gwbas-7.html" rel="noreferrer noopener nofollow">tune</a>创建一个 <code>Music</code> 对象并播放...</p>

<pre><code>;
</code></pre>

<p>知道如何消除音符之间的啁啾声吗?</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>我认为你在音符之间停止音频输出的那一点是罪魁祸首:</p>

<pre><code>if (self.toneUnit)
{
    AudioOutputUnitStop(self.toneUnit);
    AudioUnitUninitialize(self.toneUnit);
    AudioComponentInstanceDispose(self.toneUnit);
    self.toneUnit = nil;
}
</code></pre>

<p>只要让音调单元处于事件状态,您的啁啾声就会减少。您将需要其他一些方法来产生静音,可能是让 RenderTone 继续运行但生成幅度为零。</p>

<p>我能够消除残留的轻微啁啾声,方法是在频率变化时将幅度衰减到零,更新频率,然后再次淡入。这当然是旧 PC 扬声器无法做到的(除了少数人迅速再次打开它),但通过非常快速的衰减,您可能会获得老式的效果,而不会发出唧唧声。</p>

<p>这是我的褪色 <code>RenderTone</code> 函数(目前使用邪恶的全局变量):</p>

<pre><code>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 &gt; 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-&gt;mBuffers.mData;

    // Generate the samples
    for (UInt32 frame = 0; frame &lt; inNumberFrames; frame++)
    {
      buffer = sin(theta) * currentAmplitude;
      //NSLog(@&#34;amplitude = %f&#34;, currentAmplitude);

      theta += theta_increment;
      if (theta &gt; 2.0 * M_PI)
      {
            theta -= 2.0 * M_PI;
      }
      if (fadingOut)
      {
            if (currentAmplitude &gt; 0)
            {
                currentAmplitude -= 0.001;
                if (currentAmplitude &lt; 0)
                  currentAmplitude = 0;
            }
      }
      else
      {
            if (currentAmplitude &lt; amplitude)
            {
                currentAmplitude += 0.001;
                if (currentAmplitude &gt; amplitude)
                  currentAmplitude = amplitude;
            }
      }

    }

    // Store the theta back in the view controller
    toneGen.theta = theta;

    return noErr;
}
</code></pre></p>
                                   
                                                <p style="font-size: 20px;">关于objective-c - AudioUnit 音调发生器在生成的每个音调结束时都会给我一个啁啾声,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/10051215/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/10051215/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: objective-c - AudioUnit 音调发生器在生成的每个音调结束时都会给我一个啁啾声