Copyright (C) Kevin Larke 2009-2020

This file is part of libcm.

libcm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

libcm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

See the GNU General Public License distributed with the libcm package or look here: .


cmAudioSysTest() demonstrates the audio system usage.



typedef struct 
{
  double   hz;        // current synth frq
  long     phs;       // current synth phase
  double   srate;     // audio sample rate
  unsigned cbCnt;     // DSP cycle count
  bool     synthFl;   // true=synth false=pass through
} _cmAsTestCbRecd;

typedef struct
{
  unsigned asSubIdx;  // asSubIdx must always be the first field in the msg
  unsigned id;    // 0 = set DSP Hz, 1 = report cbCount to host
  double   hz;   
  unsigned uint;
} _cmAsTestMsg;


long _cmAsSynthSine( _cmAsTestCbRecd* r, cmApSample_t* p, unsigned chCnt, unsigned frmCnt )
{
  long     ph = 0;
  unsigned i;
  
  
  for(i=0; i<chCnt; ++i)
  {
    unsigned      j;
    cmApSample_t* op = p + i;
    
    ph    = r->phs;
    for(j=0; j<frmCnt; j++, op+=chCnt, ph++)
      *op = (cmApSample_t)(0.9 * sin( 2.0 * M_PI * r->hz * ph / r->srate ));
  }
  
  return ph;
}

unsigned _cmAsTestChIdx = 0;

cmRC_t _cmAsTestCb( void* cbPtr, unsigned msgByteCnt, const void* msgDataPtr )
{
  cmRC_t              rc  = cmOkRC;
  cmAudioSysCtx_t*    ctx = (cmAudioSysCtx_t*)cbPtr;
  cmAudioSysSubSys_t* ss  = ctx->ss;
  _cmAsTestCbRecd*    r   = (_cmAsTestCbRecd*)ss->cbDataPtr;
  
  // update the calback counter
  ++r->cbCnt;
  
  // if this is an audio update request
  if( msgByteCnt == 0 )
  {
    unsigned      i;
    if( r->synthFl )
    {
      long phs = 0;
      if(0)
      {
        for(i=0; i<ctx->oChCnt; ++i)
          if( ctx->oChArray[i] != NULL )
            phs = _cmAsSynthSine(r, ctx->oChArray[i], 1, ss->args.dspFramesPerCycle );
      }
      else
      {
        if( _cmAsTestChIdx < ctx->oChCnt )
          phs = _cmAsSynthSine(r, ctx->oChArray[_cmAsTestChIdx], 1, ss->args.dspFramesPerCycle );
      }
      
      r->phs = phs;
    }
    else
    {
      // BUG BUG BUG - this assumes that the input and output channels are the same.
      unsigned chCnt = cmMin(ctx->oChCnt,ctx->iChCnt);
      for(i=0; i<chCnt; ++i)
        memcpy(ctx->oChArray[i],ctx->iChArray[i],sizeof(cmSample_t)*ss->args.dspFramesPerCycle);
    }
    
  }
  else  // ... otherwise it is a msg for the DSP process from the host
  {
    _cmAsTestMsg* msg = (_cmAsTestMsg*)msgDataPtr;
    
    msg->asSubIdx = ctx->asSubIdx;
    
    switch(msg->id)
    {
    case 0:
      r->hz = msg->hz;
      break;
      
    case 1:
      msg->uint = r->cbCnt;
      msgByteCnt = sizeof(_cmAsTestMsg);
      rc = ctx->dspToHostFunc(ctx,(const void **)&msg,&msgByteCnt,1);
      break;
    }
    
  }
  
  return rc;
}

// print the usage message for cmAudioPortTest.c
void _cmAsPrintUsage( cmRpt_t* rpt )
{
  char msg[] =
  "cmAudioSysTest() command switches:\n"
  "-r <srate> -c <chcnt> -b <bufcnt> -f <frmcnt> -i <idevidx> -o <odevidx> -m <msgqsize> -d <dspsize> -t -p -h \n"
  "\n"
  "-r <srate> = sample rate (48000)\n"
  "-c <chcnt> = audio channels (2)\n"
  "-b <bufcnt> = count of buffers (3)\n"
  "-f <frmcnt> = count of samples per buffer (512)\n"
  "-i <idevidx> = input device index (0)\n"
  "-o <odevidx> = output device index (2)\n"
  "-m <msgqsize> = message queue byte count (1024)\n"
  "-d <dspsize>  = samples per DSP frame (64)\n" 
  "-s = true: sync to input port false: sync to output port\n"
  "-t = copy input to output otherwise synthesize a 1000 Hz sine (false)\n"
  "-p = report but don't start audio devices\n"
  "-h = print this usage message\n";
  
  cmRptPrintf(rpt,"%s",msg);
}

// Get a command line option.
int _cmAsGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl )
{
  int i = 0;
  for(; i<argc; ++i)
    if( strcmp(label,argv[i]) == 0 )
  {
    if(boolFl)
      return 1;
    
    if( i == (argc-1) )
      return defaultVal;
    
    return atoi(argv[i+1]);
  }
  
  return defaultVal;
}

bool _cmAsGetBoolOpt( int argc, const char* argv[], const char* label, bool defaultVal )
{ return _cmAsGetOpt(argc,argv,label,defaultVal?1:0,true)!=0; }

int _cmAsGetIntOpt( int argc, const char* argv[], const char* label, int defaultVal )
{ return _cmAsGetOpt(argc,argv,label,defaultVal,false); }


void cmAudioSysTest( cmRpt_t* rpt, int argc, const char* argv[] )
{
  cmAudioSysCfg_t    cfg;
  cmAudioSysSubSys_t ss;
  cmAudioSysH_t      h      = cmAudioSysNullHandle;
  cmAudioSysStatus_t status;
  _cmAsTestCbRecd    cbRecd = {1000.0,0,48000.0,0};
  
  cfg.ssArray = &ss;
  cfg.ssCnt   = 1;
  //cfg.afpArray= NULL;
  //cfg.afpCnt  = 0;  
  cfg.meterMs = 50;
  
  if(_cmAsGetBoolOpt(argc,argv,"-h",false))
    _cmAsPrintUsage(rpt);
  
  cbRecd.srate   = _cmAsGetIntOpt(argc,argv,"-r",48000);
  cbRecd.synthFl = _cmAsGetBoolOpt(argc,argv,"-t",false)==false;
  
  ss.args.rpt               = rpt;
  ss.args.inDevIdx          = _cmAsGetIntOpt( argc,argv,"-i",0); 
  ss.args.outDevIdx         = _cmAsGetIntOpt( argc,argv,"-o",2); 
  ss.args.syncInputFl       = _cmAsGetBoolOpt(argc,argv,"-s",true);
  ss.args.msgQueueByteCnt   = _cmAsGetIntOpt( argc,argv,"-m",8192);
  ss.args.devFramesPerCycle = _cmAsGetIntOpt( argc,argv,"-f",512);
  ss.args.dspFramesPerCycle = _cmAsGetIntOpt( argc,argv,"-d",64);;
  ss.args.audioBufCnt       = _cmAsGetIntOpt( argc,argv,"-b",3);       
  ss.args.srate             = cbRecd.srate;
  ss.cbFunc                 = _cmAsTestCb;                             // set the DSP entry function
  ss.cbDataPtr              = &cbRecd;                                 // set the DSP function argument record
  
  cmRptPrintf(rpt,"in:%i out:%i syncFl:%i que:%i fpc:%i dsp:%i bufs:%i sr:%f\n",ss.args.inDevIdx,ss.args.outDevIdx,ss.args.syncInputFl,
  ss.args.msgQueueByteCnt,ss.args.devFramesPerCycle,ss.args.dspFramesPerCycle,ss.args.audioBufCnt,ss.args.srate);
  
  if( cmApNrtAllocate(rpt) != kOkApRC )
    goto errLabel;
  
  if( cmApFileAllocate(rpt) != kOkApRC )
    goto errLabel;
  
  // initialize the audio device system
  if( cmApInitialize(rpt) != kOkApRC )
    goto errLabel;
  
  cmApReport(rpt);
  
  // initialize the audio buffer
  if( cmApBufInitialize( cmApDeviceCount(), cfg.meterMs ) != kOkApRC )
    goto errLabel;
  
  // initialize the audio system
  if( cmAudioSysAllocate(&h,rpt,&cfg) != kOkAsRC )
    goto errLabel;
  
  // start the audio system
  cmAudioSysEnable(h,true);
  
  char c = 0;
  printf("q=quit a-g=note n=ch r=rqst s=status\n");
  
  // simulate a host event loop
  while(c != 'q')
  {
    _cmAsTestMsg msg = {0,0,0,0};
    bool         fl  = true;
    
    // wait here for a key press
    c =(char)fgetc(stdin);
    fflush(stdin);
    
    
    switch(c)
    {
    case 'c': msg.hz = cmMidiToHz(60); break;
    case 'd': msg.hz = cmMidiToHz(62); break;
    case 'e': msg.hz = cmMidiToHz(64); break;
    case 'f': msg.hz = cmMidiToHz(65); break;
    case 'g': msg.hz = cmMidiToHz(67); break;
    case 'a': msg.hz = cmMidiToHz(69); break;
    case 'b': msg.hz = cmMidiToHz(71); break;
      
    case 'r': msg.id = 1; break;  // request DSP process to send a callback count
      
    case 'n': ++_cmAsTestChIdx; printf("ch:%i\n",_cmAsTestChIdx); break;
      
    case 's':  
      // report the audio system status
      cmAudioSysStatus(h,0,&status);
      printf("phs:%li cb count:%i (upd:%i wake:%i acb:%i msgs:%i)\n",cbRecd.phs, cbRecd.cbCnt, status.updateCnt, status.wakeupCnt, status.audioCbCnt, status.msgCbCnt);
      //printf(&quot%f \n&quot,status.oMeterArray[0]);
      fl = false;
      break;
      
    default:
      fl=false;
      
    }
    
    if( fl )
    {
      // transmit a command to the DSP process
      cmAudioSysDeliverMsg(h,&msg, sizeof(msg), cmInvalidId);
    }
    
    
    // check if messages are waiting to be delivered from the DSP process
    unsigned msgByteCnt;
    if((msgByteCnt = cmAudioSysIsMsgWaiting(h)) > 0 )
    {
      char buf[ msgByteCnt ];
      
      // rcv a msg from the DSP process
      if( cmAudioSysReceiveMsg(h,buf,msgByteCnt) == kOkAsRC )
      {
        _cmAsTestMsg* msg = (_cmAsTestMsg*)buf;
        switch(msg->id)
        {
        case 1:
          printf("RCV: Callback count:%i\n",msg->uint);
          break;
        }
        
      }
    }
    
    // report the audio buffer status
    //cmApBufReport(ss.args.rpt);    
  }
  
  // stop the audio system
  cmAudioSysEnable(h,false);
  
  
  goto exitLabel;
  
  errLabel:
  printf("AUDIO SYSTEM TEST ERROR\n");
  
  exitLabel:
  
  cmAudioSysFree(&h);
  cmApFinalize();
  cmApFileFree();
  cmApNrtFree();
  cmApBufFinalize();
  
}