[PPA 05] Entradas de audio, modulación de anillo y streams en estéreo.

Aquí va el esqueleto de un programa que abre dos streams de audio, uno
de entrada y uno de salida y proporciona una función de callback en la
que podemos jugar con los streams:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <portaudio.h>
#include <math.h>

// Variables que especifican la configuración de la t de sonido
int audioOutDev = 1;
int audioInDev = 0;
int sampleRate = 44100;
int bufferLength = 64;

// El stream
PaStream *stream;

//=======================//
//  Port Audio Callback  //
//=======================//
static int audioCallback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ){

  // AQUI VA EL CÓDIGO PARA GENERAR/PROCESAR AUDIO

}


//==============//
//  Init Audio  //
//==============//
void initAudio(){
  PaError err;

  err = Pa_Initialize();
  if(err!= paNoError){
    printf("PortAudio error: %s", Pa_GetErrorText(err));
    exit(1);
  }

  // Abra los streams de audio
  PaStreamParameters outParameters;
  memset(&outParameters, '\0', sizeof(outParameters)); // Ponga la var outParameters en su estado inicial (todo ceros)
  outParameters.channelCount = 1;
  outParameters.device = audioOutDev;
  outParameters.sampleFormat = paFloat32;

  PaStreamParameters inParameters;
  memset(&inParameters, '\0', sizeof(inParameters));
  inParameters.channelCount = 1;
  inParameters.device = audioInDev;
  inParameters.sampleFormat = paFloat32;

  err = Pa_OpenStream(&stream, &inParameters, &outParameters, sampleRate, bufferLength, paNoFlag, audioCallback, NULL);
  if(err!= paNoError){
    printf("PortAudio error:%s", Pa_GetErrorText(err));
    exit(1);
  }

  err = Pa_StartStream( stream );
  if(err!= paNoError){
    printf("PortAudio error:%s", Pa_GetErrorText(err));
    exit(1);
  }
}

//========//
//  Main  //
//========//
int main(){
  initAudio();

  int termine = 0;
  printf("Entre cualquier caracter para terminar\n");
  while(!termine){
    scanf("%i",&termine);
    sleep(1);    
  }

  Pa_StopStream( stream );
  Pa_CloseStream( stream );
  Pa_Terminate();

  return 0;
}

El algoritmo de procesamiento mas sencillo que podemos tener es un echo,
es decir, enviar a la salida lo mismo que obtenemos en la entrada:

1
2
3
4
5
6
7
8
9
float *out = (float*)outputBuffer;      
float *in = (float*)inputBuffer;

unsigned int i;
for(i=0; i<framesPerBuffer; i++){
  *out = *in;
  out++;
  in++;
}

Ahora, vamos a implementar un algoritmo un poco mas interesante; la
modulación de anillo. La idea es multiplicar la señal de entrada por una
señal sinusoidal. Para hacerlo mas interesante todavía, vamos a hacer
que la frecuencia de la sinusoidal varíe en el tiempo.

Una implementación de esto en PureData:

ring.pd.zip

El primer paso para implementar esto en C es hacer un oscilador
sinusoidal que varíe su frecuencia:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Variables que definen la señal moduladora
float minFreq = 250.0;
float maxFreq = 450.0;
float frecuencia = 250.0;
int frecuenciaSubiendo = 1;
float deltaFrecuencia = 0.3;
int fase = 0;
int faseMax = 0;
float PI = 3.14159265358979323846;

//=======================//
//  Port Audio Callback  //
//=======================//
static int audioCallback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ){
  float *out = (float*)outputBuffer;        
  float *in = (float*)inputBuffer;

  unsigned int i;
  for(i=0; i<framesPerBuffer; i++){
    *out = sin(2*PI*frecuencia*(fase+i)/sampleRate);   // Ruidoso. porqué????
    out++;
    in++;
  }

  // Varíe la frecuencia del oscilador
  faseMax = sampleRate/frecuencia;
  fase += bufferLength;
  if(fase >= faseMax) fase -= faseMax;

  if(frecuenciaSubiendo){
    frecuencia += deltaFrecuencia;
  }
  else{
    frecuencia -= deltaFrecuencia;
  }

  if(frecuencia > maxFreq){
    frecuencia = maxFreq;
    frecuenciaSubiendo = 0;
  }
  else if(frecuencia < minFreq){
    frecuencia = minFreq;
    frecuenciaSubiendo = 1;
  }

  return 0;
}

Hay un ruido! Cuando variamos la frecuencia, se produce una
discontinuidad en la señal generada que se puede observar en esta gráfica:

La solución es cambiar la manera como se calcula la sinusoidal (ahora si
suena limpia):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Variables que definen la señal moduladora
float PI = 3.14159265358979323846;
double fase = 0.0;
float minFreq = 250.0;
float maxFreq = 450.0;
float frecuencia = 250.0;
int frecuenciaSubiendo = 1;
float deltaFrecuencia = 0.3;

//=======================//
//  Port Audio Callback  //
//=======================//
static int audioCallback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ){
  float *out = (float*)outputBuffer;        

  unsigned int i;
  for(i=0; i<framesPerBuffer; i++){
    *out = sin(fase);
    fase += 2*PI*frecuencia/sampleRate;
    out++;
  }

  if(frecuenciaSubiendo) frecuencia += deltaFrecuencia;
  else frecuencia -= deltaFrecuencia;

  if(frecuencia > maxFreq){
    frecuencia = maxFreq;
    frecuenciaSubiendo = 0;
  }
  else if(frecuencia < minFreq){
    frecuencia = minFreq;
    frecuenciaSubiendo = 1;
  }

  return 0;
}

Y ahora si, hacemos la modulación de anillo:

1
2
3
4
5
6
  for(i=0; i<framesPerBuffer; i++){
    *out = 0.5 * sin(fase) * *in;
    fase += 2*PI*frecuencia/sampleRate;
    out++;
    in++;
  }

Y en estéreo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//...
outParameters.channelCount = 2;

//...
inParameters.channelCount = 2;

//...
for(i=0; i<framesPerBuffer; i++){
  *out = 0.5 * sin(fase) * *in;
  out++;
  in++;
  fase += 2*PI*frecuencia/sampleRate;

  *out = *in;
  out++;
  in++;
}

5 thoughts on “[PPA 05] Entradas de audio, modulación de anillo y streams en estéreo.

  1. Tu código me ha venido de lujo, para comprender portaudio y comenzar la estructura de un programa que estoy desarrollando para mi proyecto final de carrera. Por su puesto quedas nombrado en el código. Espero poder mandarte una copia cuando lo termine
    Un saludo y gracias por tu trabajo!
    Fabio.

  2. Gracias por el comentario, Fabio y por la mención en tu trabajo. Me puedes enviar una copia en pdf?
    En que ciudad estás? Los jueves nos estamos reuniendo con un grupo de colegas en Medellín a hablar de programación para audio, si estás cerca, bienvenido. http://activata.org
    Saludos.

  3. hola una pregunta me sale el error "undefined reference to Pa_..." cuando trato de compilar el programa que detallaste arriba, ¿Sabes como puedo solucionarlo? Gracias

  4. Hello, i think that i saw you visited my website thus i came to
    “return the favor”.I'm trying to find things to enhance my web site!I suppose its
    ok to use some of your ideas!!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>