Questa classe consente di riprodurre un segnale acustico ad una data frequenza, e con una data ampiezza. Utilizza AudioQueues da AudioToolbox.framework. È solo uno schizzo, molte cose dovrebbero essere raffinate, ma il meccanismo per creare il segnale funziona.
L'utilizzo è piuttosto semplice se si visualizza lo @interface
.
#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
La classe genera una forma d'onda oscillante cos a una data frequenza intero (ampiezza * cos (2pi * frequenza * t)); l'intero lavoro viene eseguito da void handleBuffer(...)
, utilizzando un AudioQueue con un formato PCM lineare, mono, float @ 44.1kHz. Per cambiare la forma del segnale, puoi semplicemente cambiare quella linea. Ad esempio, il codice seguente produrrà una forma d'onda quadra:
float x = fmodf(unit*(CGFloat)(tone.frequency*i), 2 * M_PI);
data[i] = amplitude * (x > M_PI ? -1.0 : 1.0);
per floating point frequenze, si dovrebbe considerare che non c'è necessarely un numero intero di oscillazioni in un secondo di dati audio, in modo che il segnale rappresentato è discontinuo all'incrocio tra due buffer e produce uno strano "tick". Ad esempio è possibile impostare meno campioni in modo che la giunzione si trovi alla fine di un periodo di segnale.
- Come ha sottolineato Paul R, è necessario prima calibrare l'hardware per ottenere una conversione affidabile tra il valore impostato nell'implementazione e il suono prodotto dal dispositivo.In realtà, i campioni in virgola mobile generati in questo codice vanno da -1 a 1, quindi ho appena convertito il valore dell'ampiezza in dB (20 * log_10 (ampiezza)).
- Dai un'occhiata ai commenti per ulteriori dettagli sull'implementazione e sui "limiti noti" (tutti quelli "DA FARE"). Le funzioni utilizzate sono ben documentate da Apple nel loro riferimento.
fonte
2012-11-11 02:13:52
I decibel (dB) vengono utilizzati per esprimere un * rapporto * tra due grandezze. Probabilmente vuoi dire "dB SPL" (dB Sound Pressure Level), che è ciò che le persone solitamente intendono quando parlano di quanto forte sia il suono in decibel. Per generare un suono con una determinata ampiezza di dB SPL, dovrai comunque essere in grado di calibrare l'hardware in qualche modo. –
anche io ho bisogno dello stesso ..... devo creare un beep asper freequency e decibel .... sto cercando .. –