PortAudio  2.0
paex_ocean_shore.c
Go to the documentation of this file.
1 
21 /*
22  * $Id$
23  *
24  * This program uses the PortAudio Portable Audio Library.
25  * For more information see: http://www.portaudio.com
26  * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
27  *
28  * Permission is hereby granted, free of charge, to any person obtaining
29  * a copy of this software and associated documentation files
30  * (the "Software"), to deal in the Software without restriction,
31  * including without limitation the rights to use, copy, modify, merge,
32  * publish, distribute, sublicense, and/or sell copies of the Software,
33  * and to permit persons to whom the Software is furnished to do so,
34  * subject to the following conditions:
35  *
36  * The above copyright notice and this permission notice shall be
37  * included in all copies or substantial portions of the Software.
38  *
39  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
40  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
42  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
43  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
44  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46  */
47 
48 /*
49  * The text above constitutes the entire PortAudio license; however,
50  * the PortAudio community also makes the following non-binding requests:
51  *
52  * Any person wishing to distribute modifications to the Software is
53  * requested to send the modifications to the original developer so that
54  * they can be incorporated into the canonical version. It is also
55  * requested that these non-binding requests be included along with the
56  * license above.
57  */
58 
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <math.h>
63 #include <time.h>
64 
65 #include "portaudio.h"
66 #include "pa_ringbuffer.h"
67 #include "pa_util.h"
68 
69 #define PINK_MAX_RANDOM_ROWS (30)
70 #define PINK_RANDOM_BITS (24)
71 #define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS)
72 
73 typedef struct
74 {
75  long pink_Rows[PINK_MAX_RANDOM_ROWS];
76  long pink_RunningSum; /* Used to optimize summing of generators. */
77  int pink_Index; /* Incremented each sample. */
78  int pink_IndexMask; /* Index wrapped by ANDing with this mask. */
79  float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */
80 }
81 PinkNoise;
82 
83 typedef struct
84 {
85  float bq_b0;
86  float bq_b1;
87  float bq_b2;
88  float bq_a1;
89  float bq_a2;
90 } BiQuad;
91 
92 typedef enum
93 {
94  State_kAttack,
95  State_kPreDecay,
96  State_kDecay,
97  State_kCnt,
98 } EnvState;
99 
100 typedef struct
101 {
102  PinkNoise wave_left;
103  PinkNoise wave_right;
104 
105  BiQuad wave_bq_coeffs;
106  float wave_bq_left[2];
107  float wave_bq_right[2];
108 
109  EnvState wave_envelope_state;
110  float wave_envelope_level;
111  float wave_envelope_max_level;
112  float wave_pan_left;
113  float wave_pan_right;
114  float wave_attack_incr;
115  float wave_decay_incr;
116 
117 } OceanWave;
118 
119 /* Prototypes */
120 static unsigned long GenerateRandomNumber( void );
121 void InitializePinkNoise( PinkNoise *pink, int numRows );
122 float GeneratePinkNoise( PinkNoise *pink );
123 unsigned GenerateWave( OceanWave* wave, float* output, unsigned noOfFrames);
124 
125 /************************************************************/
126 /* Calculate pseudo-random 32 bit number based on linear congruential method. */
127 static unsigned long GenerateRandomNumber( void )
128 {
129  /* Change this seed for different random sequences. */
130  static unsigned long randSeed = 22222;
131  randSeed = (randSeed * 196314165) + 907633515;
132  return randSeed;
133 }
134 
135 /************************************************************/
136 /* Setup PinkNoise structure for N rows of generators. */
137 void InitializePinkNoise( PinkNoise *pink, int numRows )
138 {
139  int i;
140  long pmax;
141  pink->pink_Index = 0;
142  pink->pink_IndexMask = (1<<numRows) - 1;
143  /* Calculate maximum possible signed random value. Extra 1 for white noise always added. */
144  pmax = (numRows + 1) * (1<<(PINK_RANDOM_BITS-1));
145  pink->pink_Scalar = 1.0f / pmax;
146  /* Initialize rows. */
147  for( i=0; i<numRows; i++ ) pink->pink_Rows[i] = 0;
148  pink->pink_RunningSum = 0;
149 }
150 
151 /* Generate Pink noise values between -1.0 and +1.0 */
152 float GeneratePinkNoise( PinkNoise *pink )
153 {
154  long newRandom;
155  long sum;
156  float output;
157  /* Increment and mask index. */
158  pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask;
159  /* If index is zero, don't update any random values. */
160  if( pink->pink_Index != 0 )
161  {
162  /* Determine how many trailing zeros in PinkIndex. */
163  /* This algorithm will hang if n==0 so test first. */
164  int numZeros = 0;
165  int n = pink->pink_Index;
166  while( (n & 1) == 0 )
167  {
168  n = n >> 1;
169  numZeros++;
170  }
171  /* Replace the indexed ROWS random value.
172  * Subtract and add back to RunningSum instead of adding all the random
173  * values together. Only one changes each time.
174  */
175  pink->pink_RunningSum -= pink->pink_Rows[numZeros];
176  newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT;
177  pink->pink_RunningSum += newRandom;
178  pink->pink_Rows[numZeros] = newRandom;
179  }
180 
181  /* Add extra white noise value. */
182  newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT;
183  sum = pink->pink_RunningSum + newRandom;
184  /* Scale to range of -1.0 to 0.9999. */
185  output = pink->pink_Scalar * sum;
186  return output;
187 }
188 
189 float ProcessBiquad(const BiQuad* coeffs, float* memory, float input)
190 {
191  float w = input - coeffs->bq_a1 * memory[0] - coeffs->bq_a2 * memory[1];
192  float out = coeffs->bq_b1 * memory[0] + coeffs->bq_b2 * memory[1] + coeffs->bq_b0 * w;
193  memory[1] = memory[0];
194  memory[0] = w;
195  return out;
196 }
197 
198 static const float one_over_2Q_LP = 0.3f;
199 static const float one_over_2Q_HP = 1.0f;
200 
201 unsigned GenerateWave( OceanWave* wave, float* output, unsigned noOfFrames )
202 {
203  unsigned retval=0,i;
204  float targetLevel, levelIncr, currentLevel;
205  switch (wave->wave_envelope_state)
206  {
207  case State_kAttack:
208  targetLevel = noOfFrames * wave->wave_attack_incr + wave->wave_envelope_level;
209  if (targetLevel >= wave->wave_envelope_max_level)
210  {
211  /* Go to decay state */
212  wave->wave_envelope_state = State_kPreDecay;
213  targetLevel = wave->wave_envelope_max_level;
214  }
215  /* Calculate lowpass biquad coeffs
216 
217  alpha = sin(w0)/(2*Q)
218 
219  b0 = (1 - cos(w0))/2
220  b1 = 1 - cos(w0)
221  b2 = (1 - cos(w0))/2
222  a0 = 1 + alpha
223  a1 = -2*cos(w0)
224  a2 = 1 - alpha
225 
226  w0 = [0 - pi[
227  */
228  {
229  const float w0 = 3.141592654f * targetLevel / wave->wave_envelope_max_level;
230  const float alpha = sinf(w0) * one_over_2Q_LP;
231  const float cosw0 = cosf(w0);
232  const float a0_fact = 1.0f / (1.0f + alpha);
233  wave->wave_bq_coeffs.bq_b1 = (1.0f - cosw0) * a0_fact;
234  wave->wave_bq_coeffs.bq_b0 = wave->wave_bq_coeffs.bq_b1 * 0.5f;
235  wave->wave_bq_coeffs.bq_b2 = wave->wave_bq_coeffs.bq_b0;
236  wave->wave_bq_coeffs.bq_a2 = (1.0f - alpha) * a0_fact;
237  wave->wave_bq_coeffs.bq_a1 = -2.0f * cosw0 * a0_fact;
238  }
239  break;
240 
241  case State_kPreDecay:
242  /* Reset biquad state */
243  memset(wave->wave_bq_left, 0, 2 * sizeof(float));
244  memset(wave->wave_bq_right, 0, 2 * sizeof(float));
245  wave->wave_envelope_state = State_kDecay;
246 
247  /* Deliberate fall-through */
248 
249  case State_kDecay:
250  targetLevel = noOfFrames * wave->wave_decay_incr + wave->wave_envelope_level;
251  if (targetLevel < 0.001f)
252  {
253  /* < -60 dB, we're done */
254  wave->wave_envelope_state = 3;
255  retval = 1;
256  }
257  /* Calculate highpass biquad coeffs
258 
259  alpha = sin(w0)/(2*Q)
260 
261  b0 = (1 + cos(w0))/2
262  b1 = -(1 + cos(w0))
263  b2 = (1 + cos(w0))/2
264  a0 = 1 + alpha
265  a1 = -2*cos(w0)
266  a2 = 1 - alpha
267 
268  w0 = [0 - pi/2[
269  */
270  {
271  const float v = targetLevel / wave->wave_envelope_max_level;
272  const float w0 = 1.5707963f * (1.0f - (v*v));
273  const float alpha = sinf(w0) * one_over_2Q_HP;
274  const float cosw0 = cosf(w0);
275  const float a0_fact = 1.0f / (1.0f + alpha);
276  wave->wave_bq_coeffs.bq_b1 = (float)(- (1 + cosw0) * a0_fact);
277  wave->wave_bq_coeffs.bq_b0 = -wave->wave_bq_coeffs.bq_b1 * 0.5f;
278  wave->wave_bq_coeffs.bq_b2 = wave->wave_bq_coeffs.bq_b0;
279  wave->wave_bq_coeffs.bq_a2 = (float)((1.0 - alpha) * a0_fact);
280  wave->wave_bq_coeffs.bq_a1 = (float)(-2.0 * cosw0 * a0_fact);
281  }
282  break;
283 
284  default:
285  break;
286  }
287 
288  currentLevel = wave->wave_envelope_level;
289  wave->wave_envelope_level = targetLevel;
290  levelIncr = (targetLevel - currentLevel) / noOfFrames;
291 
292  for (i = 0; i < noOfFrames; ++i, currentLevel += levelIncr)
293  {
294  (*output++) += ProcessBiquad(&wave->wave_bq_coeffs, wave->wave_bq_left, (GeneratePinkNoise(&wave->wave_left))) * currentLevel * wave->wave_pan_left;
295  (*output++) += ProcessBiquad(&wave->wave_bq_coeffs, wave->wave_bq_right, (GeneratePinkNoise(&wave->wave_right))) * currentLevel * wave->wave_pan_right;
296  }
297 
298  return retval;
299 }
300 
301 
302 /*******************************************************************/
303 
304 /* Context for callback routine. */
305 typedef struct
306 {
307  OceanWave* waves[16]; /* Maximum 16 waves */
308  unsigned noOfActiveWaves;
309 
310  /* Ring buffer (FIFO) for "communicating" towards audio callback */
311  PaUtilRingBuffer rBufToRT;
312  void* rBufToRTData;
313 
314  /* Ring buffer (FIFO) for "communicating" from audio callback */
315  PaUtilRingBuffer rBufFromRT;
316  void* rBufFromRTData;
317 }
318 paTestData;
319 
320 /* This routine will be called by the PortAudio engine when audio is needed.
321 ** It may called at interrupt level on some machines so don't do anything
322 ** that could mess up the system like calling malloc() or free().
323 */
324 static int patestCallback(const void* inputBuffer,
325  void* outputBuffer,
326  unsigned long framesPerBuffer,
327  const PaStreamCallbackTimeInfo* timeInfo,
328  PaStreamCallbackFlags statusFlags,
329  void* userData)
330 {
331  int i;
332  paTestData *data = (paTestData*)userData;
333  float *out = (float*)outputBuffer;
334  (void) inputBuffer; /* Prevent "unused variable" warnings. */
335 
336  /* Reset output data first */
337  memset(out, 0, framesPerBuffer * 2 * sizeof(float));
338 
339  for (i = 0; i < 16; ++i)
340  {
341  /* Consume the input queue */
342  if (data->waves[i] == 0 && PaUtil_GetRingBufferReadAvailable(&data->rBufToRT))
343  {
344  OceanWave* ptr = 0;
345  PaUtil_ReadRingBuffer(&data->rBufToRT, &ptr, 1);
346  data->waves[i] = ptr;
347  }
348 
349  if (data->waves[i] != 0)
350  {
351  if (GenerateWave(data->waves[i], out, framesPerBuffer))
352  {
353  /* If wave is "done", post it back to the main thread for deletion */
354  PaUtil_WriteRingBuffer(&data->rBufFromRT, &data->waves[i], 1);
355  data->waves[i] = 0;
356  }
357  }
358  }
359  return paContinue;
360 }
361 
362 #define NEW_ROW_SIZE (12 + (8*rand())/RAND_MAX)
363 
364 OceanWave* InitializeWave(double SR, float attackInSeconds, float maxLevel, float positionLeftRight)
365 {
366  OceanWave* wave = NULL;
367  static unsigned lastNoOfRows = 12;
368  unsigned newNoOfRows;
369 
370  wave = (OceanWave*)PaUtil_AllocateMemory(sizeof(OceanWave));
371  if (wave != NULL)
372  {
373  InitializePinkNoise(&wave->wave_left, lastNoOfRows);
374  while ((newNoOfRows = NEW_ROW_SIZE) == lastNoOfRows);
375  InitializePinkNoise(&wave->wave_right, newNoOfRows);
376  lastNoOfRows = newNoOfRows;
377 
378  wave->wave_envelope_state = State_kAttack;
379  wave->wave_envelope_level = 0.f;
380  wave->wave_envelope_max_level = maxLevel;
381  wave->wave_attack_incr = wave->wave_envelope_max_level / (attackInSeconds * (float)SR);
382  wave->wave_decay_incr = - wave->wave_envelope_max_level / (attackInSeconds * 4 * (float)SR);
383 
384  wave->wave_pan_left = sqrtf(1.0f - positionLeftRight);
385  wave->wave_pan_right = sqrtf(positionLeftRight);
386  }
387  return wave;
388 }
389 
390 static float GenerateFloatRandom(float minValue, float maxValue)
391 {
392  return minValue + ((maxValue - minValue) * rand()) / RAND_MAX;
393 }
394 
395 /*******************************************************************/
396 int main(void);
397 int main(void)
398 {
399  PaStream* stream;
400  PaError err;
401  paTestData data = {0};
402  PaStreamParameters outputParameters;
403  double tstamp;
404  double tstart;
405  double tdelta = 0;
406  static const double SR = 44100.0;
407  static const int FPB = 128; /* Frames per buffer: 2.9 ms buffers. */
408 
409  /* Initialize communication buffers (queues) */
410  data.rBufToRTData = PaUtil_AllocateMemory(sizeof(OceanWave*) * 256);
411  if (data.rBufToRTData == NULL)
412  {
413  return 1;
414  }
415  PaUtil_InitializeRingBuffer(&data.rBufToRT, sizeof(OceanWave*), 256, data.rBufToRTData);
416 
417  data.rBufFromRTData = PaUtil_AllocateMemory(sizeof(OceanWave*) * 256);
418  if (data.rBufFromRTData == NULL)
419  {
420  return 1;
421  }
422  PaUtil_InitializeRingBuffer(&data.rBufFromRT, sizeof(OceanWave*), 256, data.rBufFromRTData);
423 
424  err = Pa_Initialize();
425  if( err != paNoError ) goto error;
426 
427  /* Open a stereo PortAudio stream so we can hear the result. */
428  outputParameters.device = Pa_GetDefaultOutputDevice(); /* Take the default output device. */
429  if (outputParameters.device == paNoDevice) {
430  fprintf(stderr,"Error: No default output device.\n");
431  goto error;
432  }
433  outputParameters.channelCount = 2; /* Stereo output, most likely supported. */
434  outputParameters.hostApiSpecificStreamInfo = NULL;
435  outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output. */
436  outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
437  err = Pa_OpenStream(&stream,
438  NULL, /* No input. */
439  &outputParameters,
440  SR, /* Sample rate. */
441  FPB, /* Frames per buffer. */
442  paDitherOff, /* Clip but don't dither */
443  patestCallback,
444  &data);
445  if( err != paNoError ) goto error;
446 
447  err = Pa_StartStream( stream );
448  if( err != paNoError ) goto error;
449 
450  printf("Stereo \"ocean waves\" for one minute...\n");
451 
452  tstart = PaUtil_GetTime();
453  tstamp = tstart;
454  srand( (unsigned)time(NULL) );
455 
456  while( ( err = Pa_IsStreamActive( stream ) ) == 1 )
457  {
458  const double tcurrent = PaUtil_GetTime();
459 
460  /* Delete "waves" that the callback is finished with */
461  while (PaUtil_GetRingBufferReadAvailable(&data.rBufFromRT) > 0)
462  {
463  OceanWave* ptr = 0;
464  PaUtil_ReadRingBuffer(&data.rBufFromRT, &ptr, 1);
465  if (ptr != 0)
466  {
467  printf("Wave is deleted...\n");
468  PaUtil_FreeMemory(ptr);
469  --data.noOfActiveWaves;
470  }
471  }
472 
473  if (tcurrent - tstart < 60.0) /* Only start new "waves" during one minute */
474  {
475  if (tcurrent >= tstamp)
476  {
477  double tdelta = GenerateFloatRandom(1.0f, 4.0f);
478  tstamp += tdelta;
479 
480  if (data.noOfActiveWaves<16)
481  {
482  const float attackTime = GenerateFloatRandom(2.0f, 6.0f);
483  const float level = GenerateFloatRandom(0.1f, 1.0f);
484  const float pos = GenerateFloatRandom(0.0f, 1.0f);
485  OceanWave* p = InitializeWave(SR, attackTime, level, pos);
486  if (p != NULL)
487  {
488  /* Post wave to audio callback */
489  PaUtil_WriteRingBuffer(&data.rBufToRT, &p, 1);
490  ++data.noOfActiveWaves;
491 
492  printf("Starting wave at level = %.2f, attack = %.2lf, pos = %.2lf\n", level, attackTime, pos);
493  }
494  }
495  }
496  }
497  else
498  {
499  if (data.noOfActiveWaves == 0)
500  {
501  printf("All waves finished!\n");
502  break;
503  }
504  }
505 
506  Pa_Sleep(100);
507  }
508  if( err < 0 ) goto error;
509 
510  err = Pa_CloseStream( stream );
511  if( err != paNoError ) goto error;
512 
513  if (data.rBufToRTData)
514  {
515  PaUtil_FreeMemory(data.rBufToRTData);
516  }
517  if (data.rBufFromRTData)
518  {
519  PaUtil_FreeMemory(data.rBufFromRTData);
520  }
521 
522  Pa_Sleep(1000);
523 
524  Pa_Terminate();
525  return 0;
526 
527 error:
528  Pa_Terminate();
529  fprintf( stderr, "An error occured while using the portaudio stream\n" );
530  fprintf( stderr, "Error number: %d\n", err );
531  fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
532  return 0;
533 }
PaError Pa_Initialize(void)
void PaStream
Definition: portaudio.h:635
PaError Pa_OpenStream(PaStream **stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate, unsigned long framesPerBuffer, PaStreamFlags streamFlags, PaStreamCallback *streamCallback, void *userData)
PaError Pa_StartStream(PaStream *stream)
void * hostApiSpecificStreamInfo
Definition: portaudio.h:579
The portable PortAudio API.
#define paFloat32
Definition: portaudio.h:487
PaSampleFormat sampleFormat
Definition: portaudio.h:559
int PaError
Definition: portaudio.h:121
PaError Pa_IsStreamActive(PaStream *stream)
PaTime suggestedLatency
Definition: portaudio.h:572
#define paDitherOff
Definition: portaudio.h:666
unsigned long PaStreamCallbackFlags
Definition: portaudio.h:712
#define paNoDevice
Definition: portaudio.h:220
const PaDeviceInfo * Pa_GetDeviceInfo(PaDeviceIndex device)
PaDeviceIndex Pa_GetDefaultOutputDevice(void)
PaDeviceIndex device
Definition: portaudio.h:546
void Pa_Sleep(long msec)
const char * Pa_GetErrorText(PaError errorCode)
PaError Pa_CloseStream(PaStream *stream)
PaError Pa_Terminate(void)