Esta clase le permite reproducir un sonido a una frecuencia dada, y con una amplitud dada. Utiliza AudioQueues desde AudioToolbox.framework. Es solo un boceto, muchas cosas deben ser refinadas, pero el mecanismo para crear la señal funciona.
El uso es bastante sencillo si ve el @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 clase genera una forma de onda oscilante cos a la frecuencia entero dado (amplitud * cos (2pi * frecuencia t *)); todo el trabajo se realiza por void handleBuffer(...)
, usando un AudioQueue con formato PCM lineal, mono, flotante @ 44.1kHz. Para cambiar la forma de la señal, puedes cambiar esa línea. Por ejemplo, el siguiente código producirá una forma de onda cuadrada:
float x = fmodf(unit*(CGFloat)(tone.frequency*i), 2 * M_PI);
data[i] = amplitude * (x > M_PI ? -1.0 : 1.0);
Para frecuencias de punto flotante, se debe considerar que no hay necessarely un número entero de oscilaciones en un segundo de datos de audio, por lo que la señal representada es discontinuo en la unión entre dos memorias intermedias, y produce un extraño 'tic'. Por ejemplo, podría establecer menos muestras para que la unión se encuentre al final de un período de señal.
- Como señaló Paul R, primero debe calibrar el hardware para obtener una conversión confiable entre el valor que establece en su implementación y el sonido producido por su dispositivo.En realidad, las muestras de coma flotante generadas en este código varían de -1 a 1, así que acabo de convertir el valor de amplitud en dB (20 * log_10 (amplitud)).
- Eche un vistazo a los comentarios sobre otros detalles en la implementación y las "limitaciones conocidas" (todos esos 'HACER'). Las funciones utilizadas están bien documentadas por Apple en su referencia.
Los decibelios (dB) se utilizan para expresar una * relación * entre dos magnitudes. Probablemente se refiera a 'dB SPL' (Nivel de presión sonora dB), que es lo que las personas generalmente quieren decir cuando hablan sobre qué tan alto está el sonido en decibelios. Para generar un sonido con una amplitud dB SPL dada, necesitará calibrar el hardware de alguna manera. –
yo también necesito lo mismo ..... tengo que crear un beep asper frecuencia y decibelios .... estoy buscando .. –