Diese Klasse ermöglicht es Ihnen, ein akustisches Signal bei einer bestimmten Frequenz zu spielen, und mit einer bestimmten Amplitude. Es verwendet AudioQueues von AudioToolbox.framework. Es ist nur eine Skizze, viele Dinge sollten verfeinert werden, aber der Mechanismus zur Erzeugung des Signals funktioniert.
Die Verwendung ist ziemlich einfach, wenn Sie die @interface
sehen.
#import <AudioToolbox/AudioToolbox.h>
#define TONE_SAMPLERATE 44100.
@interface Tone : NSObject {
AudioQueueRef queue;
AudioQueueBufferRef buffer;
BOOL rebuildBuffer;
}
@property (nonatomic, assign) NSUInteger frequency;
@property (nonatomic, assign) CGFloat dB;
- (void)play;
- (void)pause;
@end
@implementation Tone
@synthesize dB=_dB,frequency=_frequency;
void handleBuffer(void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer);
#pragma mark - Initialization and deallocation -
- (id)init
{
if ((self=[super init])) {
_dB=0.;
_frequency=440;
rebuildBuffer=YES;
// TO DO: handle AudioQueueXYZ's failures!!
// create a descriptor containing a LPCM, mono, float format
AudioStreamBasicDescription desc;
desc.mSampleRate=TONE_SAMPLERATE;
desc.mFormatID=kAudioFormatLinearPCM;
desc.mFormatFlags=kLinearPCMFormatFlagIsFloat;
desc.mBytesPerPacket=sizeof(float);
desc.mFramesPerPacket=1;
desc.mBytesPerFrame=sizeof(float);
desc.mChannelsPerFrame=1;
desc.mBitsPerChannel=8*sizeof(float);
// create a new queue
AudioQueueNewOutput(&desc,
&handleBuffer,
self,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&queue);
// and its buffer, ready to hold 1" of data
AudioQueueAllocateBuffer(queue,
sizeof(float)*TONE_SAMPLERATE,
&buffer);
// create the buffer and enqueue it
handleBuffer(self, queue, buffer);
}
return self;
}
- (void)dealloc
{
AudioQueueStop(queue, YES);
AudioQueueFreeBuffer(queue, buffer);
AudioQueueDispose(queue, YES);
[super dealloc];
}
#pragma mark - Main function -
void handleBuffer(void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer) {
// this function takes care of building the buffer and enqueuing it.
// cast inUserData type to Tone
Tone *tone=(Tone *)inUserData;
// check if the buffer must be rebuilt
if (tone->rebuildBuffer) {
// precompute some useful qtys
float *data=inBuffer->mAudioData;
NSUInteger max=inBuffer->mAudioDataBytesCapacity/sizeof(float);
// multiplying the argument by 2pi changes the period of the cosine
// function to 1s (instead of 2pi). then we must divide by the sample
// rate to get TONE_SAMPLERATE samples in one period.
CGFloat unit=2.*M_PI/TONE_SAMPLERATE;
// this is the amplitude converted from dB to a linear scale
CGFloat amplitude=pow(10., tone.dB*.05);
// loop and simply set data[i] to the value of cos(...)
for (NSUInteger i=0; i<max; ++i)
data[i]=(float)(amplitude*cos(unit*(CGFloat)(tone.frequency*i)));
// inform the queue that we have filled the buffer
inBuffer->mAudioDataByteSize=sizeof(float)*max;
// and set flag
tone->rebuildBuffer=NO;
}
// reenqueue the buffer
AudioQueueEnqueueBuffer(inAQ,
inBuffer,
0,
NULL);
/* TO DO: the transition between two adjacent buffers (the same one actually)
generates a "tick", even if the adjacent buffers represent a continuous signal.
maybe using two buffers instead of one would fix it.
*/
}
#pragma - Properties and methods -
- (void)play
{
// generate an AudioTimeStamp with "0" simply!
// (copied from FillOutAudioTimeStampWithSampleTime)
AudioTimeStamp time;
time.mSampleTime=0.;
time.mRateScalar=0.;
time.mWordClockTime=0.;
memset(&time.mSMPTETime, 0, sizeof(SMPTETime));
time.mFlags = kAudioTimeStampSampleTimeValid;
// TO DO: maybe it could be useful to check AudioQueueStart's return value
AudioQueueStart(queue, &time);
}
- (void)pause
{
// TO DO: maybe it could be useful to check AudioQueuePause's return value
AudioQueuePause(queue);
}
- (void)setFrequency:(NSUInteger)frequency
{
if (_frequency!=frequency) {
_frequency=frequency;
// we need to update the buffer (as soon as it stops playing)
rebuildBuffer=YES;
}
}
- (void)setDB:(CGFloat)dB
{
if (dB!=_dB) {
_dB=dB;
// we need to update the buffer (as soon as it stops playing)
rebuildBuffer=YES;
}
}
@end
Die Klasse erzeugt eine oszillierende Wellenform cos bei der gegebenen ganzzahligen Frequenz (Amplitude * cos (2pi * Frequenz * t)); Der gesamte Job wird von void handleBuffer(...)
mit einer AudioQueue mit einem linearen PCM, Mono, Float @ 44,1 kHz-Format erledigt. Um die Signalform zu ändern, können Sie nur diese Linie ändern. Zum Beispiel wird der folgende Code, der eine Rechteckwellenform erzeugen:
float x = fmodf(unit*(CGFloat)(tone.frequency*i), 2 * M_PI);
data[i] = amplitude * (x > M_PI ? -1.0 : 1.0);
für Punktfrequenzen floating, sollte bedenken, dass es nicht necessarely eine ganzzahlige Anzahl von Schwingungen in einer Sekunde von Audiodaten ist, so dass das Signal repräsentiert ist an der Verbindung zwischen zwei Puffern diskontinuierlich und erzeugt ein merkwürdiges "Zecken". Zum Beispiel könnten Sie weniger Samples einstellen, so dass die Verbindung am Ende einer Signalperiode ist.
- Wie Paul R. darauf hingewiesen hat, sollten Sie zuerst die Hardware kalibrieren, um eine zuverlässige Konvertierung zwischen dem in Ihrer Implementierung eingestellten Wert und dem von Ihrem Gerät erzeugten Klang zu erhalten.Tatsächlich liegen die in diesem Code erzeugten Fließkomma-Samples im Bereich von -1 bis 1. Daher habe ich gerade den Amplitudenwert in dB umgerechnet (20 * log_10 (Amplitude)).
- Werfen Sie einen Blick auf die Kommentare für weitere Details in der Umsetzung und die "bekannten Einschränkungen" (all diese 'zu tun'). Die verwendeten Funktionen sind von Apple in ihrer Referenz gut dokumentiert.
Dezibel (dB) werden verwendet, um ein * Verhältnis * zwischen zwei Größen auszudrücken. Sie meinen wahrscheinlich "dB SPL" (dB Sound Pressure Level), was Leute normalerweise meinen, wenn sie darüber sprechen, wie laut ein Sound in Dezibel ist. Um einen Sound mit einer gegebenen dB SPL-Amplitude zu erzeugen, müssen Sie in der Lage sein, die Hardware in irgendeiner Weise zu kalibrieren. –
ich brauche auch das gleiche ..... muss einen beep asper frequency und Dezibel erstellen .... ich bin auf der Suche .. –