2012-05-22 13:09:48 -04:00
|
|
|
#include "soundout.h"
|
|
|
|
|
2013-05-24 08:36:41 -04:00
|
|
|
//#define FRAMES_PER_BUFFER 1024
|
2012-05-22 13:09:48 -04:00
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <portaudio.h>
|
|
|
|
}
|
|
|
|
|
|
|
|
extern float gran(); //Noise generator (for tests only)
|
2013-07-08 09:17:22 -04:00
|
|
|
extern int itone[126]; //Audio tones for all Tx symbols
|
2013-03-24 21:24:47 -04:00
|
|
|
extern int icw[250]; //Dits for CW ID
|
2013-05-24 08:36:41 -04:00
|
|
|
extern int outBufSize;
|
2012-11-28 18:55:32 -05:00
|
|
|
extern bool btxok;
|
|
|
|
extern bool btxMute;
|
2012-05-22 13:09:48 -04:00
|
|
|
extern double outputLatency;
|
|
|
|
|
|
|
|
typedef struct //Parameters sent to or received from callback function
|
|
|
|
{
|
2012-10-26 10:03:43 -04:00
|
|
|
double txsnrdb;
|
2013-07-08 09:17:22 -04:00
|
|
|
double dnsps;
|
2012-10-26 10:03:43 -04:00
|
|
|
int ntrperiod;
|
|
|
|
int ntxfreq;
|
2013-07-08 09:17:22 -04:00
|
|
|
int xit;
|
2012-11-13 15:23:03 -05:00
|
|
|
int ncall;
|
2013-07-08 09:17:22 -04:00
|
|
|
int nsym;
|
2012-10-26 10:03:43 -04:00
|
|
|
bool txMute;
|
|
|
|
bool bRestart;
|
2013-04-14 10:11:20 -04:00
|
|
|
bool btune;
|
2012-05-22 13:09:48 -04:00
|
|
|
} paUserData;
|
|
|
|
|
|
|
|
//--------------------------------------------------------------- d2aCallback
|
|
|
|
extern "C" int d2aCallback(const void *inputBuffer, void *outputBuffer,
|
|
|
|
unsigned long framesToProcess,
|
|
|
|
const PaStreamCallbackTimeInfo* timeInfo,
|
|
|
|
PaStreamCallbackFlags statusFlags,
|
|
|
|
void *userData )
|
|
|
|
{
|
|
|
|
paUserData *udata=(paUserData*)userData;
|
|
|
|
short *wptr = (short*)outputBuffer;
|
2012-10-05 13:13:21 -04:00
|
|
|
|
2012-10-05 13:17:37 -04:00
|
|
|
static double twopi=2.0*3.141592653589793238462;
|
2012-10-28 11:47:43 -04:00
|
|
|
static double baud;
|
2012-10-05 13:13:21 -04:00
|
|
|
static double phi=0.0;
|
|
|
|
static double dphi;
|
|
|
|
static double freq;
|
2012-10-26 10:03:43 -04:00
|
|
|
static double snr;
|
|
|
|
static double fac;
|
2013-04-10 11:50:40 -04:00
|
|
|
static double amp;
|
2013-03-24 21:24:47 -04:00
|
|
|
static int ic=0,j=0;
|
2013-07-08 09:17:22 -04:00
|
|
|
static int isym0=-999;
|
2012-10-05 13:13:21 -04:00
|
|
|
static short int i2;
|
2013-03-24 21:39:20 -04:00
|
|
|
int isym,nspd;
|
2012-10-05 13:13:21 -04:00
|
|
|
|
2012-11-13 15:23:03 -05:00
|
|
|
udata->ncall++;
|
2012-10-05 13:13:21 -04:00
|
|
|
if(udata->bRestart) {
|
|
|
|
// Time according to this computer
|
|
|
|
qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
|
|
|
int mstr = ms % (1000*udata->ntrperiod );
|
2013-04-10 11:50:40 -04:00
|
|
|
if(mstr<1000) return paContinue;
|
2012-10-29 09:42:09 -04:00
|
|
|
ic=(mstr-1000)*48;
|
2012-10-05 13:13:21 -04:00
|
|
|
udata->bRestart=false;
|
2012-11-13 15:23:03 -05:00
|
|
|
srand(mstr); //Initialize random seed
|
2012-10-05 13:13:21 -04:00
|
|
|
}
|
2013-07-08 09:17:22 -04:00
|
|
|
isym=ic/(4.0*udata->dnsps); //Actual fsample=48000
|
2013-04-14 10:11:20 -04:00
|
|
|
if(udata->btune) isym=0; //If tuning, send pure tone
|
2012-10-26 10:03:43 -04:00
|
|
|
if(udata->txsnrdb < 0.0) {
|
2012-11-12 16:30:00 -05:00
|
|
|
snr=pow(10.0,0.05*(udata->txsnrdb-6.0));
|
2012-10-26 10:03:43 -04:00
|
|
|
fac=3000.0;
|
|
|
|
if(snr>1.0) fac=3000.0/snr;
|
|
|
|
}
|
|
|
|
|
2013-07-08 09:17:22 -04:00
|
|
|
if(isym>=udata->nsym and icw[0]>0) { //Output the CW ID
|
|
|
|
freq=udata->ntxfreq - udata->xit;
|
2013-03-24 21:24:47 -04:00
|
|
|
dphi=twopi*freq/48000.0;
|
2013-07-08 09:17:22 -04:00
|
|
|
|
2013-03-24 21:24:47 -04:00
|
|
|
// float wpm=20.0;
|
|
|
|
// int nspd=1.2*48000.0/wpm;
|
2013-03-26 12:23:40 -04:00
|
|
|
// nspd=3072; //18.75 WPM
|
|
|
|
nspd=2048 + 512; //22.5 WPM
|
2013-07-08 09:17:22 -04:00
|
|
|
int ic0=udata->nsym*4*udata->dnsps;
|
2013-05-23 13:11:10 -04:00
|
|
|
for(uint i=0 ; i<framesToProcess; i++ ) {
|
2013-03-24 21:24:47 -04:00
|
|
|
phi += dphi;
|
|
|
|
if(phi>twopi) phi -= twopi;
|
|
|
|
i2=32767.0*sin(phi);
|
2013-04-10 11:50:40 -04:00
|
|
|
j=(ic-ic0)/nspd + 1;
|
2013-03-24 21:24:47 -04:00
|
|
|
if(icw[j]==0) i2=0;
|
|
|
|
if(udata->txsnrdb < 0.0) {
|
|
|
|
int i4=fac*(gran() + i2*snr/32768.0);
|
|
|
|
if(i4>32767) i4=32767;
|
|
|
|
if(i4<-32767) i4=-32767;
|
|
|
|
i2=i4;
|
|
|
|
}
|
|
|
|
if(!btxok or btxMute) i2=0;
|
|
|
|
*wptr++ = i2; //left
|
2013-05-29 12:31:05 -04:00
|
|
|
#ifdef UNIX
|
2013-03-24 21:24:47 -04:00
|
|
|
*wptr++ = i2; //right
|
|
|
|
#endif
|
|
|
|
ic++;
|
|
|
|
}
|
2013-04-10 11:50:40 -04:00
|
|
|
if(j>icw[0]) return paComplete;
|
2013-05-29 09:41:57 -04:00
|
|
|
if(statusFlags==999999 and timeInfo==NULL and
|
|
|
|
inputBuffer==NULL) return paContinue; //Silence compiler warning:
|
2013-03-24 21:24:47 -04:00
|
|
|
return paContinue;
|
|
|
|
}
|
|
|
|
|
2013-07-08 09:17:22 -04:00
|
|
|
baud=12000.0/udata->dnsps;
|
2013-04-10 11:50:40 -04:00
|
|
|
amp=32767.0;
|
2013-07-08 09:17:22 -04:00
|
|
|
int i0=(udata->nsym-0.017)*4.0*udata->dnsps;
|
|
|
|
int i1=udata->nsym*4.0*udata->dnsps;
|
2013-04-14 10:11:20 -04:00
|
|
|
if(udata->btune) { //If tuning, no ramp down
|
2013-07-08 09:17:22 -04:00
|
|
|
i0=999*udata->dnsps;
|
2013-04-14 10:11:20 -04:00
|
|
|
i1=i0;
|
|
|
|
}
|
2012-10-26 10:03:43 -04:00
|
|
|
for(uint i=0 ; i<framesToProcess; i++ ) {
|
2013-07-08 09:17:22 -04:00
|
|
|
isym=ic/(4.0*udata->dnsps); //Actual fsample=48000
|
|
|
|
if(udata->btune) isym=0; //If tuning, send pure tone
|
|
|
|
if(isym!=isym0) {
|
|
|
|
freq=udata->ntxfreq + itone[isym]*baud - udata->xit;
|
|
|
|
dphi=twopi*freq/48000.0;
|
|
|
|
isym0=isym;
|
|
|
|
}
|
2012-10-05 13:13:21 -04:00
|
|
|
phi += dphi;
|
|
|
|
if(phi>twopi) phi -= twopi;
|
2013-04-10 11:50:40 -04:00
|
|
|
if(ic>i0) amp=0.98*amp;
|
|
|
|
if(ic>i1) amp=0.0;
|
2013-04-09 11:16:34 -04:00
|
|
|
i2=amp*sin(phi);
|
2012-10-26 10:03:43 -04:00
|
|
|
if(udata->txsnrdb < 0.0) {
|
|
|
|
int i4=fac*(gran() + i2*snr/32768.0);
|
|
|
|
if(i4>32767) i4=32767;
|
|
|
|
if(i4<-32767) i4=-32767;
|
|
|
|
i2=i4;
|
|
|
|
}
|
2012-11-28 18:55:32 -05:00
|
|
|
if(!btxok or btxMute) i2=0;
|
2012-07-04 12:27:57 -04:00
|
|
|
*wptr++ = i2; //left
|
2013-05-29 12:31:05 -04:00
|
|
|
#ifdef UNIX
|
2013-03-24 18:34:57 -04:00
|
|
|
*wptr++ = i2; //right
|
|
|
|
#endif
|
2012-07-04 12:27:57 -04:00
|
|
|
ic++;
|
2012-05-22 13:09:48 -04:00
|
|
|
}
|
2013-04-10 11:50:40 -04:00
|
|
|
if(amp==0.0) {
|
|
|
|
if(icw[0]==0) return paComplete;
|
|
|
|
phi=0.0;
|
2013-04-09 16:59:53 -04:00
|
|
|
}
|
2013-03-24 21:24:47 -04:00
|
|
|
return paContinue;
|
2012-05-22 13:09:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void SoundOutThread::run()
|
|
|
|
{
|
|
|
|
PaError paerr;
|
|
|
|
PaStreamParameters outParam;
|
|
|
|
PaStream *outStream;
|
|
|
|
paUserData udata;
|
|
|
|
quitExecution = false;
|
|
|
|
|
|
|
|
outParam.device=m_nDevOut; //Output device number
|
2012-07-04 12:27:57 -04:00
|
|
|
outParam.channelCount=1; //Number of analog channels
|
2013-05-29 11:09:04 -04:00
|
|
|
#ifdef UNIX
|
2013-05-28 15:16:25 -04:00
|
|
|
outParam.channelCount=2; //Number of analog channels
|
|
|
|
#endif
|
2012-05-22 13:09:48 -04:00
|
|
|
outParam.sampleFormat=paInt16; //Send short ints to PortAudio
|
|
|
|
outParam.suggestedLatency=0.05;
|
|
|
|
outParam.hostApiSpecificStreamInfo=NULL;
|
|
|
|
|
2012-10-29 09:42:09 -04:00
|
|
|
paerr=Pa_IsFormatSupported(NULL,&outParam,48000.0);
|
2012-05-22 13:09:48 -04:00
|
|
|
if(paerr<0) {
|
|
|
|
qDebug() << "PortAudio says requested output format not supported.";
|
2012-07-02 16:52:56 -04:00
|
|
|
qDebug() << paerr << m_nDevOut;
|
2012-05-22 13:09:48 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-26 10:03:43 -04:00
|
|
|
udata.txsnrdb=99.0;
|
2013-07-08 09:17:22 -04:00
|
|
|
udata.dnsps=m_nsps;
|
|
|
|
udata.nsym=85;
|
|
|
|
if(m_modeTx=="JT65") {
|
|
|
|
udata.dnsps=4096.0*12000.0/11025.0;
|
|
|
|
udata.nsym=126;
|
|
|
|
}
|
2012-10-05 13:13:21 -04:00
|
|
|
udata.ntrperiod=m_TRperiod;
|
2012-10-05 15:14:45 -04:00
|
|
|
udata.ntxfreq=m_txFreq;
|
2013-07-08 09:17:22 -04:00
|
|
|
udata.xit=m_xit;
|
2012-11-13 15:23:03 -05:00
|
|
|
udata.ncall=0;
|
2012-10-05 13:13:21 -04:00
|
|
|
udata.txMute=m_txMute;
|
|
|
|
udata.bRestart=true;
|
2013-04-14 10:11:20 -04:00
|
|
|
udata.btune=m_tune;
|
2012-05-22 13:09:48 -04:00
|
|
|
|
|
|
|
paerr=Pa_OpenStream(&outStream, //Output stream
|
|
|
|
NULL, //No input parameters
|
|
|
|
&outParam, //Output parameters
|
2012-10-29 09:42:09 -04:00
|
|
|
48000.0, //Sample rate
|
2013-05-24 08:36:41 -04:00
|
|
|
outBufSize, //Frames per buffer
|
2012-05-22 13:09:48 -04:00
|
|
|
paClipOff, //No clipping
|
|
|
|
d2aCallback, //output callbeck routine
|
|
|
|
&udata); //userdata
|
|
|
|
|
|
|
|
paerr=Pa_StartStream(outStream);
|
|
|
|
if(paerr<0) {
|
|
|
|
qDebug() << "Failed to start audio output stream.";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const PaStreamInfo* p=Pa_GetStreamInfo(outStream);
|
|
|
|
outputLatency = p->outputLatency;
|
|
|
|
bool qe = quitExecution;
|
2012-11-13 15:23:03 -05:00
|
|
|
qint64 ms0 = QDateTime::currentMSecsSinceEpoch();
|
2012-05-22 13:09:48 -04:00
|
|
|
|
|
|
|
//---------------------------------------------- Soundcard output loop
|
|
|
|
while (!qe) {
|
|
|
|
qe = quitExecution;
|
|
|
|
if (qe) break;
|
2012-10-05 13:13:21 -04:00
|
|
|
|
2012-10-26 10:03:43 -04:00
|
|
|
udata.txsnrdb=m_txsnrdb;
|
2013-07-08 09:17:22 -04:00
|
|
|
udata.dnsps=m_nsps;
|
|
|
|
udata.nsym=85;
|
|
|
|
if(m_modeTx=="JT65") {
|
|
|
|
udata.dnsps=4096.0*12000.0/11025.0;
|
|
|
|
udata.nsym=126;
|
|
|
|
}
|
2012-10-05 13:13:21 -04:00
|
|
|
udata.ntrperiod=m_TRperiod;
|
2012-10-05 15:14:45 -04:00
|
|
|
udata.ntxfreq=m_txFreq;
|
2012-10-05 13:13:21 -04:00
|
|
|
udata.txMute=m_txMute;
|
2013-04-15 19:42:42 -04:00
|
|
|
udata.btune=m_tune;
|
2012-11-13 15:23:03 -05:00
|
|
|
|
|
|
|
m_SamFacOut=1.0;
|
|
|
|
if(udata.ncall>400) {
|
|
|
|
qint64 ms = QDateTime::currentMSecsSinceEpoch();
|
2013-05-24 08:36:41 -04:00
|
|
|
m_SamFacOut=udata.ncall*outBufSize*1000.0/(48000.0*(ms-ms0-50));
|
2012-11-13 15:23:03 -05:00
|
|
|
}
|
2012-05-22 13:09:48 -04:00
|
|
|
msleep(100);
|
|
|
|
}
|
|
|
|
Pa_StopStream(outStream);
|
|
|
|
Pa_CloseStream(outStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SoundOutThread::setOutputDevice(int n) //setOutputDevice()
|
|
|
|
{
|
|
|
|
if (isRunning()) return;
|
|
|
|
this->m_nDevOut=n;
|
|
|
|
}
|
2012-09-24 15:11:31 -04:00
|
|
|
|
2012-09-25 20:48:49 -04:00
|
|
|
void SoundOutThread::setPeriod(int ntrperiod, int nsps)
|
2012-09-24 15:11:31 -04:00
|
|
|
{
|
2012-09-25 20:48:49 -04:00
|
|
|
m_TRperiod=ntrperiod;
|
|
|
|
m_nsps=nsps;
|
2012-09-24 15:11:31 -04:00
|
|
|
}
|
2012-10-05 15:14:45 -04:00
|
|
|
|
|
|
|
void SoundOutThread::setTxFreq(int n)
|
|
|
|
{
|
|
|
|
m_txFreq=n;
|
|
|
|
}
|
2012-10-26 10:03:43 -04:00
|
|
|
|
2013-07-08 09:17:22 -04:00
|
|
|
void SoundOutThread::setXIT(int n)
|
|
|
|
{
|
|
|
|
m_xit=n;
|
|
|
|
}
|
2012-10-26 10:03:43 -04:00
|
|
|
|
|
|
|
void SoundOutThread::setTxSNR(double snr)
|
|
|
|
{
|
|
|
|
m_txsnrdb=snr;
|
|
|
|
}
|
2012-11-13 15:23:03 -05:00
|
|
|
|
2013-04-14 10:11:20 -04:00
|
|
|
void SoundOutThread::setTune(bool b)
|
|
|
|
{
|
|
|
|
m_tune=b;
|
|
|
|
}
|
|
|
|
|
2012-11-13 15:23:03 -05:00
|
|
|
double SoundOutThread::samFacOut()
|
|
|
|
{
|
|
|
|
return m_SamFacOut;
|
|
|
|
}
|