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:
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(); }