// ----------------------------------------------------------------------------
//
//	sound.cxx
//
// Copyright (C) 2006-2013
//			Dave Freese, W1HKJ
//
// Copyright (C) 2007-2010
//			Stelios Bounanos, M0GLD
//
// This file is part of fldigi.
//
// Fldigi 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.
//
// Fldigi 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.
//
// You should have received a copy of the GNU General Public License
// along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------

#include <config.h>

#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <algorithm>
#include <iterator>

#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <unistd.h>

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <limits.h>

#if USE_OSS
#  include <sys/ioctl.h>
#  if defined(__OpenBSD__)
#   include <soundcard.h>
#  else
#   include <sys/soundcard.h>
#  endif
#endif
#include <math.h>

#if HAVE_DLOPEN
#  include <dlfcn.h>
#endif

#include "gettext.h"
#include "sound.h"
#include "configuration.h"
#include "status.h"
#include "fileselect.h"
#include "trx.h"
#include "fl_digi.h"
#include "threads.h"
#include "timeops.h"
#include "ringbuffer.h"
#include "debug.h"
#include "qrunner.h"
#include "icons.h"
#include "macros.h"
#include "util.h"

#include "estrings.h"

#define SND_BUF_LEN	 65536
#define SND_RW_LEN	(8 * SND_BUF_LEN)

// We never write duplicate/QSK/PTT tone/PseudoFSK data to the sound files
#define SNDFILE_CHANNELS 1

int sndfile_samplerate[7] = {8000, 11025, 16000, 22050, 24000, 44100, 48000};

using namespace std;

LOG_FILE_SOURCE(debug::LOG_AUDIO);

SoundBase::SoundBase()
		: sample_frequency(0),
	  txppm(progdefaults.TX_corr), rxppm(progdefaults.RX_corr),
		  tx_src_state(0), rx_src_state(0),
		  wrt_buffer(new double[SND_BUF_LEN]),
#if USE_SNDFILE
		  ofCapture(0), ifPlayback(0), ofGenerate(0),
#endif
	  capture(false), playback(false), generate(false)
{
	memset(wrt_buffer, 0, SND_BUF_LEN * sizeof(*wrt_buffer));

#if USE_SNDFILE
	int err;
	writ_src_data = new SRC_DATA;
	play_src_data = new SRC_DATA;

	writ_src_state = src_new(progdefaults.sample_converter, 1, &err);
	if (writ_src_state == 0)
		throw SndException(src_strerror(err));

	play_src_state = src_new(progdefaults.sample_converter, 1, &err);
	if (play_src_state == 0)
		throw SndException(src_strerror(err));

	if (play_src_state == 0)
		throw SndException(src_strerror(err));
	src_out_buffer = new float [SND_RW_LEN];
	if (!src_out_buffer)
		throw SndException(src_strerror(err));
	src_inp_buffer = new float [SND_RW_LEN];
	if (!src_inp_buffer)
		throw SndException(src_strerror(err));
	modem_wr_sr = modem_play_sr = 0;
	inp_pointer = src_out_buffer;
#endif
}

SoundBase::~SoundBase()
{
	delete [] wrt_buffer;

#if USE_SNDFILE
	if (ofGenerate)
		sf_close(ofGenerate);
	if (ofCapture)
		sf_close(ofCapture);
	if (ifPlayback)
		sf_close(ifPlayback);
	delete writ_src_data;
	delete play_src_data;
	delete [] src_out_buffer;
	delete [] src_inp_buffer;
#endif
}

#if USE_SNDFILE
void SoundBase::get_file_params(const char* def_fname, const char** fname, int* format)
{
	std::string filters = _("Waveform Audio Format\t*.wav\n" "AU\t*.{au,snd}\n");
	int nfilt = 2;
	if (format_supported(SF_FORMAT_FLAC | SF_FORMAT_PCM_16)) {
		filters += _("Free Lossless Audio Codec\t*.flac");
		nfilt++;
	}

	int fsel;
	if (strstr(def_fname, "playback"))
		*fname = FSEL::select(_("Audio file"), filters.c_str(), def_fname, &fsel);
	else
		*fname = FSEL::saveas(_("Audio file"), filters.c_str(), def_fname, &fsel);
	if (!*fname)
		return;

	if (fsel >= nfilt) // "Default" save-as type on OS X
		fsel = 0;
	switch (fsel) {
	case 0:
		*format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
		break;
	case 1:
		*format = SF_FORMAT_AU | SF_FORMAT_FLOAT | SF_ENDIAN_CPU;
		break;
	case 2:
		*format = SF_FORMAT_FLAC | SF_FORMAT_PCM_16;
		break;
	}
}

int SoundBase::Capture(bool val)
{
	if (!val) {
		if (ofCapture) {
			int err;
			if ((err = sf_close(ofCapture)) != 0)
				LOG_ERROR("sf_close error: %s", sf_error_number(err));
			ofCapture = 0;
		}
		capture = false;
		return 1;
	}

	const char* fname;
	int format;
	get_file_params("capture.wav", &fname, &format);
	if (!fname)
		return 0;

	// frames (ignored), freq, channels, format, sections (ignored), seekable (ignored)
	SF_INFO info = { 0, sndfile_samplerate[progdefaults.wavSampleRate], SNDFILE_CHANNELS, format, 0, 0 };
	if ((ofCapture = sf_open(fname, SFM_WRITE, &info)) == NULL) {
		LOG_ERROR("Could not write %s:%s", fname, sf_strerror(NULL) );
		return 0;
	}
	if (sf_command(ofCapture, SFC_SET_UPDATE_HEADER_AUTO, NULL, SF_TRUE) != SF_TRUE)
		LOG_ERROR("ofCapture update header command failed: %s", sf_strerror(ofCapture));
	tag_file(ofCapture, "Captured audio");

	capture = true;
	return 1;
}

int SoundBase::Generate(bool val)
{
	if (!val) {
		if (ofGenerate) {
			int err;
			if ((err = sf_close(ofGenerate)) != 0)
				LOG_ERROR("sf_close error: %s", sf_error_number(err));
			ofGenerate = 0;
		}
		generate = false;
		return 1;
	}

	const char* fname;
	int format;
	get_file_params("generate.wav", &fname, &format);
	if (!fname)
		return 0;

	SF_INFO info = { 0, sndfile_samplerate[progdefaults.wavSampleRate], SNDFILE_CHANNELS, format, 0, 0 };
	if ((ofGenerate = sf_open(fname, SFM_WRITE, &info)) == NULL) {
		LOG_ERROR("Could not write %s", fname);
		return 0;
	}
	if (sf_command(ofGenerate, SFC_SET_UPDATE_HEADER_AUTO, NULL, SF_TRUE) != SF_TRUE)
		LOG_ERROR("ofGenerate update header command failed: %s", sf_strerror(ofGenerate));
	tag_file(ofGenerate, "Generated audio");

	generate = true;

	modem_wr_sr = sample_frequency;
	writ_src_data->src_ratio = 1.0 * sndfile_samplerate[progdefaults.wavSampleRate] / modem_wr_sr;
	src_set_ratio(writ_src_state, writ_src_data->src_ratio);

	return 1;
}


int SoundBase::Playback(bool val)
{
	if (!val) {
		if (ifPlayback) {
			int err;
			if ((err = sf_close(ifPlayback)) != 0)
				LOG_ERROR("sf_close error: %s", sf_error_number(err));
			ifPlayback = 0;
		}
		playback = false;
		return 1;
	}
	const char* fname;
	int format;
	get_file_params("playback.wav", &fname, &format);
	if (!fname)
		return -1;

	play_info.frames = 0;
	play_info.samplerate = 0;
	play_info.channels = 0;
	play_info.format = 0;
	play_info.sections = 0;
	play_info.seekable = 0;

	if ((ifPlayback = sf_open(fname, SFM_READ, &play_info)) == NULL) {
		LOG_ERROR("Could not read %s:%s", fname, sf_strerror(NULL) );
		return -2;
	}
LOG_VERBOSE("wav file stats:\n\
frames     : %d\n\
samplerate : %d\n\
channels   : %d\n\
format     : %d\n\
sections   : %d\n\
seekable   : %d",
static_cast<unsigned int>(play_info.frames),
play_info.samplerate,
play_info.channels,
play_info.format,
play_info.sections,
play_info.seekable);

// this restriction needs to be removed!
	if (play_info.channels != 1) {
		sf_close(ifPlayback);
		return -3;
	}

	modem_play_sr = sample_frequency;
	play_src_data->src_ratio = 1.0 * modem_play_sr / play_info.samplerate;
	src_set_ratio(play_src_state, play_src_data->src_ratio);
LOG_VERBOSE("src ratio %f", play_src_data->src_ratio);

	progdefaults.loop_playback = fl_choice2(_("Playback continuous loop?"), _("No"), _("Yes"), NULL);

	playback = true;
	new_playback = true;

	return 0;
}

// ---------------------------------------------------------------------
// read_file
//    can be simplified from the equivalent read audio stream
//   source sr is arbitrary, requested is either 8000 or 11025 depending
// on the modem in use
// read from file and resample until a "count" number of converted samples
// is available, or until at the end of the input file
// ---------------------------------------------------------------------
sf_count_t SoundBase::read_file(SNDFILE* file, float* buf, size_t count)
{
	sf_count_t r = 0, rd_count = 0;
	int err = 0;

	if (new_playback || modem_play_sr != sample_frequency) {
		modem_play_sr = sample_frequency;
		play_src_data->src_ratio = 1.0 * modem_play_sr / play_info.samplerate;
		src_set_ratio(play_src_state, play_src_data->src_ratio);
LOG_VERBOSE("src ratio %f", play_src_data->src_ratio);
		new_playback = true;
	}

	while ( static_cast<size_t>(inp_pointer - src_out_buffer) < count) {
		if (new_playback) {
			memset(src_inp_buffer, 0, 1024 * sizeof(float));
			new_playback = false;
			rd_count = 1024;
		} else
			rd_count = sf_readf_float(file, src_inp_buffer, 1024);
		if (!rd_count) break;
		play_src_data->data_in = src_inp_buffer;
		play_src_data->input_frames = rd_count;
		play_src_data->data_out = inp_pointer;
		play_src_data->output_frames = SND_RW_LEN - (inp_pointer - src_out_buffer);
		play_src_data->end_of_input = 0;

		if ((err = src_process(play_src_state, play_src_data)) != 0)
			throw SndException(src_strerror(err));

		inp_pointer += play_src_data->output_frames_gen;
	}
	if ( static_cast<size_t>(inp_pointer - src_out_buffer) >= count) {
		memcpy(buf, src_out_buffer, count * sizeof(float));
		memmove(src_out_buffer, src_out_buffer + count, (SND_RW_LEN - count) * sizeof(float));
		inp_pointer -= count;
		r = count;
	}

	if (r == 0) {
		src_reset (play_src_state);
		inp_pointer = src_out_buffer;
		if (!progdefaults.loop_playback) {
			Playback(0);
			bHighSpeed = false;
			REQ(reset_mnuPlayback);
		} else {
			memset(buf, count, sizeof(*buf));
			sf_seek(file, 0, SEEK_SET);
		}
	}
	return r;
}

// ---------------------------------------------------------------------
// write_file
// All sound buffer data is resampled to a specified sample rate
//    progdefaults.wavSampleRate
// resultant data (left channel only) is written to a wav file
//----------------------------------------------------------------------
void SoundBase::write_file(SNDFILE* file, float* buf, size_t count)
{
	int err;
	if (modem_wr_sr != sample_frequency) {
		modem_wr_sr = sample_frequency;
		writ_src_data->src_ratio = 1.0 * sndfile_samplerate[progdefaults.wavSampleRate] / modem_wr_sr;
		src_set_ratio(writ_src_state, writ_src_data->src_ratio);
	}
	writ_src_data->data_in = buf;
	writ_src_data->input_frames = count;
	writ_src_data->data_out = src_out_buffer;
	writ_src_data->output_frames = SND_RW_LEN;
	writ_src_data->end_of_input = 0;

	if ((err = src_process(writ_src_state, writ_src_data)) != 0) {
		throw SndException(src_strerror(err));
	}

	size_t output_size = writ_src_data->output_frames_gen;

	if (output_size) {
		for (size_t i = 0; i < output_size; i++)
			writ_src_data->data_out[i] *= 0.9;
		sf_writef_float(file, writ_src_data->data_out, output_size);
	}
	return;

}

void SoundBase::write_file(SNDFILE* file, double* buf, size_t count)
{
	float *outbuf = new float[count];
	for (size_t i = 0; i < count; i++)
		outbuf[i] = buf[i];
	write_file(file, outbuf, count);
	delete [] outbuf;
	return;
}

bool SoundBase::format_supported(int format)
{
	SF_INFO info = {
		0,
		sndfile_samplerate[progdefaults.wavSampleRate],
		SNDFILE_CHANNELS,
		format,
		0,
		0 };
	FILE* f;
	if ((f = tmpfile()) == NULL)
		return false;
	SNDFILE* sndf = sf_open_fd(fileno(f), SFM_WRITE, &info, SF_FALSE);
	fclose(f);
	if (sndf)
		sf_close(sndf);
	return sndf;
}

void SoundBase::tag_file(SNDFILE *sndfile, const char *title)
{
	int err;
	if ((err = sf_set_string(sndfile, SF_STR_TITLE, title)) != 0) {
		LOG_VERBOSE("sf_set_string STR_TITLE: %s", sf_error_number(err));
		return;
	}

	sf_set_string(sndfile, SF_STR_COPYRIGHT, progdefaults.myName.c_str());
	sf_set_string(sndfile, SF_STR_SOFTWARE, PACKAGE_NAME "-" PACKAGE_VERSION);
	sf_set_string(sndfile, SF_STR_ARTIST, progdefaults.myCall.c_str());

	char s[64];
	snprintf(s, sizeof(s), "%s freq=%s",
		 active_modem->get_mode_name(), inpFreq->value());
	sf_set_string(sndfile, SF_STR_COMMENT, s);

	time_t t = time(0);
	struct tm zt;
	(void)gmtime_r(&t, &zt);
	if (strftime(s, sizeof(s), "%Y-%m-%dT%H:%M:%Sz", &zt) > 0)
		sf_set_string(sndfile, SF_STR_DATE, s);
}
#endif // USE_SNDFILE


#if USE_OSS

#define MAXSC 32767.0f
#define maxsc 32000.0

SoundOSS::SoundOSS(const char *dev ) {
	device		  = dev;
	cbuff		   = 0;
	try {
		Open(O_RDONLY);
		getVersion();
		getCapabilities();
		getFormats();
		Close();
	}
	catch (const SndException& e) {
		LOG_ERROR("device %s error: %s", device.c_str(), e.what());
	}

	snd_buffer = new float [2 * SND_BUF_LEN];
	src_buffer = new float [2 * SND_BUF_LEN];
	cbuff	 = new unsigned char [4 * SND_BUF_LEN];

	memset(snd_buffer, 0, 2 * SND_BUF_LEN * sizeof(*snd_buffer));
	memset(src_buffer, 0, 2 * SND_BUF_LEN * sizeof(*src_buffer));
	memset(cbuff,	 0, 4 * SND_BUF_LEN * sizeof(*cbuff));

	tx_src_data = new SRC_DATA;
	rx_src_data = new SRC_DATA;

	int err;
	rx_src_state = src_new(progdefaults.sample_converter, 2, &err);
	if (rx_src_state == 0)
		throw SndException(src_strerror(err));

	tx_src_state = src_new(progdefaults.sample_converter, 2, &err);
	if (tx_src_state == 0)
		throw SndException(src_strerror(err));

	rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
	src_set_ratio ( rx_src_state, 1.0/(1.0 + rxppm/1e6));

	tx_src_data->src_ratio = 1.0 + txppm/1e6;
	src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
}

SoundOSS::~SoundOSS()
{
	Close();

	delete tx_src_data;
	delete rx_src_data;

	if (rx_src_state)
		src_delete(rx_src_state);
	if (tx_src_state)
		src_delete(tx_src_state);

	delete [] snd_buffer;
	delete [] src_buffer;
	delete [] cbuff;
}

void SoundOSS::setfragsize()
{
	int sndparam;
// Try to get ~100ms worth of samples per fragment
	sndparam = (int)log2(sample_frequency * 0.1);
// double since we are using 16 bit samples
	sndparam += 1;
// Unlimited amount of buffers for RX, four for TX
	if (mode == O_RDONLY)
		sndparam |= 0x7FFF0000;
	else
		sndparam |= 0x00040000;

	if (ioctl(device_fd, SNDCTL_DSP_SETFRAGMENT, &sndparam) < 0)
		throw SndException(errno);
}

int SoundOSS::Open(int md, int freq)
{
	Close();

	mode = md;
	try {
		int oflags = md;
#	   ifdef HAVE_O_CLOEXEC
			oflags = oflags | O_CLOEXEC;
#	   endif

		device_fd = open(device.c_str(), oflags, 0);
		if (device_fd == -1)
			throw SndException(errno);
		Format(AFMT_S16_LE);	// default: 16 bit little endian
		Channels(2);			//		2 channels
		Frequency(freq);
		setfragsize();
	}
	catch (...) {
		throw;
	}
	return 0;
}

void SoundOSS::Close(unsigned dir)
{
	if (device_fd == -1)
		return;
	close(device_fd);
	device_fd = -1;
}

void SoundOSS::getVersion()
{
	version = 0;
#ifndef __FreeBSD__
	if (ioctl(device_fd, OSS_GETVERSION, &version) == -1) {
		version = -1;
		throw SndException("OSS Version");
	}
#endif
}

void SoundOSS::getCapabilities()
{
	capability_mask = 0;
	if (ioctl(device_fd, SNDCTL_DSP_GETCAPS, &capability_mask) == -1) {
		capability_mask = 0;
		throw SndException("OSS capabilities");
	}
}

void SoundOSS::getFormats()
{
	format_mask = 0;
	if (ioctl(device_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1) {
		format_mask = 0;
		throw SndException("OSS formats");
	}
}

void SoundOSS::Format(int format)
{
	play_format = format;
	if (ioctl(device_fd, SNDCTL_DSP_SETFMT, &play_format) == -1) {
		device_fd = -1;
		formatok = false;
		throw SndException("Unsupported snd card format");
	}
	formatok = true;
}

void SoundOSS::Channels(int nuchannels)
{
	channels = nuchannels;
	if (ioctl(device_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
		device_fd = -1;
		throw "Snd card channel request failed";
	}
}

void SoundOSS::Frequency(int frequency)
{
	sample_frequency = frequency;
	if (ioctl(device_fd, SNDCTL_DSP_SPEED, &sample_frequency) == -1) {
		device_fd = -1;
		throw SndException("Cannot set frequency");
	}
}

int SoundOSS::BufferSize( int seconds )
{
	int bytes_per_channel = 0;
	switch (play_format) {
		case AFMT_MU_LAW:
		case AFMT_A_LAW:
		case AFMT_IMA_ADPCM:
			bytes_per_channel = 0; /* format not supported by this program */
			break;
		case AFMT_S16_BE:
		case AFMT_U16_LE:
		case AFMT_U16_BE:
		case AFMT_MPEG:
		case AFMT_S16_LE:
			bytes_per_channel = 2;
			break;
		case AFMT_U8:
		case AFMT_S8:
			bytes_per_channel = 1;
			break;
	}
  return seconds * sample_frequency * bytes_per_channel * channels;
}

bool SoundOSS::wait_till_finished()
{
	if (ioctl(device_fd, SNDCTL_DSP_POST, (void*)1) == -1 )
		return false;
	if (ioctl(device_fd, SNDCTL_DSP_SYNC, (void*)0) == -1)
		return false; /* format (or ioctl()) not supported by device */
	return true; /* all sound has been played */
}

bool SoundOSS::reset_device()
{
	if (ioctl(device_fd, SNDCTL_DSP_RESET, 0) == -1) {
		device_fd = -1;
		return false; /* format (or ioctl()) not supported by device */
	}
	return 1; /* sounddevice has been reset */
}

size_t SoundOSS::Read(float *buffer, size_t buffersize)
{
	short int *ibuff = (short int *)cbuff;
	int numread;

	numread = read(device_fd, cbuff, buffersize * 4);
	if (numread == -1)
		throw SndException(errno);

	for (size_t i = 0; i < buffersize * 2; i++)
		src_buffer[i] = ibuff[i] / MAXSC;

	for (size_t i = 0; i < buffersize; i++)
		buffer[i] = src_buffer[2*i + 
							   progdefaults.ReverseRxAudio ? 1 : 0];

#if USE_SNDFILE
	if (capture)
		write_file(ofCapture, buffer, buffersize);
	if (playback) {
		read_file(ifPlayback, buffer, buffersize);
		return buffersize;
	}
#endif

	if (rxppm != progdefaults.RX_corr) {
		rxppm = progdefaults.RX_corr;
		rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
		src_set_ratio ( rx_src_state, 1.0/(1.0 + rxppm/1e6));
	}

	if (rxppm == 0)
		return buffersize;

// process using samplerate library

	rx_src_data->data_in = src_buffer;
	rx_src_data->input_frames = buffersize;
	rx_src_data->data_out = snd_buffer;
	rx_src_data->output_frames = SND_BUF_LEN;
	rx_src_data->end_of_input = 0;

	if ((numread = src_process(rx_src_state, rx_src_data)) != 0)
		throw SndException(src_strerror(numread));

	numread = rx_src_data->output_frames_gen;

	for (int i = 0; i < numread; i++)
		buffer[i] = snd_buffer[2*i +
							   progdefaults.sig_on_right_channel ? 1 : 0];

	return numread;

}

size_t SoundOSS::Write(double *buf, size_t count)
{
	int retval;
	short int *wbuff;
	unsigned char *p;

#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, buf, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}


	if (txppm != progdefaults.TX_corr) {
		txppm = progdefaults.TX_corr;
		tx_src_data->src_ratio = 1.0 + txppm/1e6;
		src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
	}

	if (txppm == 0) {
		wbuff = new short int[2*count];
		p = (unsigned char *)wbuff;
		for (size_t i = 0; i < count; i++) {
			wbuff[2*i] = wbuff[2*i+1] = (short int)(buf[i] * maxsc);
		}
		count *= sizeof(short int);
		retval = write(device_fd, p, 2*count);
		delete [] wbuff;
		if (retval == -1)
			throw SndException(errno);
	}
	else {
		float *inbuf;
		inbuf = new float[2*count];
		size_t bufsize;
		for (size_t i = 0; i < count; i++)
			inbuf[2*i] = inbuf[2*i+1] = buf[i];
		tx_src_data->data_in = inbuf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = src_buffer;
		tx_src_data->output_frames = SND_BUF_LEN;
		tx_src_data->end_of_input = 0;

		retval = src_process(tx_src_state, tx_src_data);
		delete [] inbuf;
		if (retval != 0)
			throw SndException(src_strerror(retval));

		bufsize = tx_src_data->output_frames_gen;
		wbuff = new short int[2*bufsize];
		p = (unsigned char *)wbuff;

		for (size_t i = 0; i < 2*bufsize; i++)
			wbuff[i] = (short int)(src_buffer[i] * maxsc);
		int num2write = bufsize * 2 * sizeof(short int);

		retval = write(device_fd, p, num2write);
		delete [] wbuff;
		if (retval != num2write)
			throw SndException(errno);
		retval = count;
	}

	return retval;
}

size_t SoundOSS::Write_stereo(double *bufleft, double *bufright, size_t count)
{
	int retval;
	short int *wbuff;
	unsigned char *p;

#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, bufleft, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

	if (txppm != progdefaults.TX_corr) {
		txppm = progdefaults.TX_corr;
		tx_src_data->src_ratio = 1.0 + txppm/1e6;
		src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
	}

	if (txppm == 0) {
		wbuff = new short int[2*count];
		p = (unsigned char *)wbuff;
		for (size_t i = 0; i < count; i++) {
			if (progdefaults.ReverseAudio) {
				wbuff[2*i+1] = (short int)(bufleft[i] * maxsc);
				wbuff[2*i] = (short int)(bufright[i] * maxsc);
			} else {
				wbuff[2*i] = (short int)(bufleft[i] * maxsc);
				wbuff[2*i+1] = (short int)(bufright[i] * maxsc);
			}
		}
		count *= sizeof(short int);
		retval = write(device_fd, p, 2*count);
		delete [] wbuff;
		if (retval == -1)
			throw SndException(errno);
	}
	else {
		float *inbuf;
		inbuf = new float[2*count];
		size_t bufsize;
		for (size_t i = 0; i < count; i++) {
			if (progdefaults.ReverseAudio) {
				inbuf[2*i+1] = bufleft[i];
				inbuf[2*i] = bufright[i];
			} else {
				inbuf[2*i] = bufleft[i];
				inbuf[2*i+1] = bufright[i];
			}
		}
		tx_src_data->data_in = inbuf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = src_buffer;
		tx_src_data->output_frames = SND_BUF_LEN;
		tx_src_data->end_of_input = 0;

		retval = src_process(tx_src_state, tx_src_data);
		delete [] inbuf;
		if (retval != 0)
			throw SndException(src_strerror(retval));

		bufsize = tx_src_data->output_frames_gen;
		wbuff = new short int[2*bufsize];
		p = (unsigned char *)wbuff;

		for (size_t i = 0; i < 2*bufsize; i++)
			wbuff[i] = (short int)(src_buffer[i] * maxsc);

		int num2write = bufsize * 2 * sizeof(short int);
		retval = write(device_fd, p, num2write);
		delete [] wbuff;
		if (retval != num2write)
			throw SndException(errno);
		retval = count;
	}

	return retval;
}
#endif // USE_OSS


#if USE_PORTAUDIO

bool SoundPort::pa_init = false;
std::vector<const PaDeviceInfo*> SoundPort::devs;

static ostringstream device_text[2];
static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;

map<string, vector<double> > supported_rates[2];

void SoundPort::initialize(void)
{
	if (pa_init)
		return;

	init_hostapi_ext();

	int err;
	if ((err = Pa_Initialize()) != paNoError) {
#if __WIN32__
		LOG_PERROR(win_error_string(err).c_str());
#else
		LOG_PERROR("Portaudio Initialize error");
#endif
		throw SndPortException(err);
	}
	pa_init = true;

	PaDeviceIndex ndev;
	if ((ndev = Pa_GetDeviceCount()) < 0) {
		LOG_PERROR("Portaudio device count error");
		throw SndPortException(ndev);
	}
	if (ndev == 0) {
		LOG_PERROR("Portaudio, no audio devices");
		throw SndException(ENODEV, "No available audio devices");
	}

	devs.reserve(ndev);
	for (PaDeviceIndex i = 0; i < ndev; i++)
		devs.push_back(Pa_GetDeviceInfo(i));
}
void SoundPort::terminate(void)
{
	if (!pa_init)
		return;
	static_cast<void>(Pa_Terminate());
	pa_init = false;
	devs.clear();
	supported_rates[0].clear();
	supported_rates[1].clear();
}
const std::vector<const PaDeviceInfo*>& SoundPort::devices(void)
{
		return devs;
}
void SoundPort::devices_info(string& in, string& out)
{
	guard_lock devices_lock(&device_mutex);
	in = device_text[0].str();
	out = device_text[1].str();
}

const vector<double>& SoundPort::get_supported_rates(const string& name, unsigned dir)
{
	return supported_rates[dir][name];
}


SoundPort::SoundPort(const char *in_dev, const char *out_dev) : req_sample_rate(0)
{
	sd[0].device = in_dev;
	sd[0].params.channelCount = 2; // init_stream can change this to 0 or 1
	sd[0].stream = 0;
	sd[0].frames_per_buffer = paFramesPerBufferUnspecified;
	sd[0].dev_sample_rate = 0;
	sd[0].state = spa_continue;
	sd[0].rb = 0;
	sd[0].advance = 0;

	sd[1].device = out_dev;
	sd[1].params.channelCount = 2;
	sd[1].stream = 0;
	sd[1].frames_per_buffer = paFramesPerBufferUnspecified;
	sd[1].dev_sample_rate = 0;
	sd[1].state = spa_continue;
	sd[1].rb = 0;
	sd[1].advance = 0;

	sem_t** sems[] = { &sd[0].rwsem, &sd[1].rwsem };
#if USE_NAMED_SEMAPHORES
	char sname[32];
#endif
	for (size_t i = 0; i < sizeof(sems)/sizeof(*sems); i++) {
#if USE_NAMED_SEMAPHORES
		snprintf(sname, sizeof(sname), "%" PRIuSZ "-%u-%s", i, getpid(), PACKAGE_TARNAME);
		if ((*sems[i] = sem_open(sname, O_CREAT | O_EXCL, 0600, 0)) == (sem_t*)SEM_FAILED) {
			pa_perror(errno, sname);
			throw SndException(errno);
		}
#  if HAVE_SEM_UNLINK
		if (sem_unlink(sname) == -1) {
			pa_perror(errno, sname);
			throw SndException(errno);
		}
#  endif
#else
		*sems[i] = new sem_t;
		if (sem_init(*sems[i], 0, 0) == -1) {
#if __WIN32__
		int err = GetLastError();
		LOG_PERROR(win_error_string(err).c_str());
#endif
			pa_perror(errno, "sem_init error");
			throw SndException(errno);
		}
#endif // USE_NAMED_SEMAPHORES
	}

	for (size_t i = 0; i < 2; i++) {
		sd[i].cmutex = new pthread_mutex_t;
		pthread_mutex_init(sd[i].cmutex, NULL);
		sd[i].ccond = new pthread_cond_t;
		pthread_cond_init(sd[i].ccond, NULL);
	}

	tx_src_data = new SRC_DATA;
	src_buffer = new float[sd[1].params.channelCount * SND_BUF_LEN];
	fbuf = new float[2 * SND_BUF_LEN];

	memset(src_buffer, 0, sd[1].params.channelCount * SND_BUF_LEN * sizeof(*src_buffer));
	memset(fbuf, 0, 2 * SND_BUF_LEN * sizeof(*fbuf));
}

SoundPort::~SoundPort()
{
	Close();

	sem_t* sems[] = { sd[0].rwsem, sd[1].rwsem };
	for (size_t i = 0; i < sizeof(sems)/sizeof(*sems); i++) {
#if USE_NAMED_SEMAPHORES
		if (sem_close(sems[i]) == -1)
			LOG_PERROR("sem_close");
#else
		if (sem_destroy(sems[i]) == -1)
			LOG_PERROR("sem_destroy");
		delete sems[i];
#endif
	}

	for (size_t i = 0; i < 2; i++) {
		if (pthread_mutex_destroy(sd[i].cmutex) == -1) {
			pa_perror(errno, "pthread mutex destroy");
			throw SndException(errno);
		}
		delete sd[i].cmutex;
		if (pthread_cond_destroy(sd[i].ccond) == -1) {
			pa_perror(errno, "pthread cond destroy");
			throw SndException(errno);
		}
		delete sd[i].ccond;
	}

	delete sd[0].rb;
	delete sd[1].rb;

	if (rx_src_state)
		src_delete(rx_src_state);
	if (tx_src_state)
		src_delete(tx_src_state);

	delete tx_src_data;
	delete [] src_buffer;
	delete [] fbuf;
}

int SoundPort::Open(int mode, int freq)
{
	int old_sample_rate = (int)req_sample_rate;
	req_sample_rate = sample_frequency = freq;

	// do we need to (re)initialise the streams?
	int ret = 0;
	int sr[2] = { progdefaults.in_sample_rate, progdefaults.out_sample_rate };

	// initialize stream if it is a JACK device, regardless of mode
	device_iterator idev;
	int device_type = 0;
	if (mode == O_WRONLY && (idev = name_to_device(sd[0].device, 0)) != devs.end() &&
		(device_type = Pa_GetHostApiInfo((*idev)->hostApi)->type) == paJACK)
		mode = O_RDWR;
	if (mode == O_RDONLY && (idev = name_to_device(sd[1].device, 1)) != devs.end() &&
		(device_type = Pa_GetHostApiInfo((*idev)->hostApi)->type) == paJACK)
		mode = O_RDWR;

	size_t start = (mode == O_RDONLY || mode == O_RDWR) ? 0 : 1,
		end = (mode == O_WRONLY || mode == O_RDWR) ? 1 : 0;
	for (size_t i = start; i <= end; i++) {
		if ( !(stream_active(i) && (Pa_GetHostApiInfo((*sd[i].idev)->hostApi)->type == paJACK ||
						old_sample_rate == freq ||
						sr[i] != SAMPLE_RATE_AUTO)) ) {
			Close(i);
			init_stream(i);
			src_data_reset(i);

			// reset the semaphore
			while (sem_trywait(sd[i].rwsem) == 0);
			if (errno && errno != EAGAIN) {
				pa_perror(errno, "open");
				throw SndException(errno);
			}
			start_stream(i);

			ret = 1;
		}
		else {
			pause_stream(i);
			src_data_reset(i);
			sd[i].state = spa_continue;
		}
	}

	static char pa_open_str[500];
	snprintf(pa_open_str, sizeof(pa_open_str),
"\
Port Audio open mode = %s\n\
device type = %s\n\
device name = %s\n\
# input channels %d\n\
# output channels %d",
		mode == O_WRONLY ? "Write" : 
		mode == O_RDONLY ? "Read" : 
		mode == O_RDWR ? "Read/Write" : "unknown",
		device_type == 0 ? "paInDevelopment" :
		device_type == 1 ? "paDirectSound" :
		device_type == 2 ? "paMME" :
		device_type == 3 ? "paASIO" :
		device_type == 4 ? "paSoundManager" :
		device_type == 5 ? "paCoreAudio" :
		device_type == 7 ? "paOSS" :
		device_type == 8 ? "paALSA" :
		device_type == 9 ? "paAL" :
		device_type == 10 ? "paBeOS" :
		device_type == 11 ? "paWDMKS" :
		device_type == 12 ? "paJACK" :
		device_type == 13 ? "paWASAPI" :
		device_type == 14 ? "paAudioScienceHPI" : "unknown",
		mode == O_WRONLY ? sd[1].device.c_str() :
		mode == O_RDONLY ? sd[0].device.c_str() : "unknown",
		sd[0].params.channelCount,
		sd[1].params.channelCount );
	LOG_VERBOSE( "%s", pa_open_str);

	return ret;
}

void SoundPort::pause_stream(unsigned dir)
{
	if (sd[dir].stream == 0 || !stream_active(dir))
		return;

	pthread_mutex_lock(sd[dir].cmutex);
		sd[dir].state = spa_pause;
	if (pthread_cond_timedwait_rel(sd[dir].ccond, sd[dir].cmutex, 5.0) == -1 && errno == ETIMEDOUT)
		LOG_ERROR("stream %u wedged", dir);
	pthread_mutex_unlock(sd[dir].cmutex);
}

void SoundPort::Close(unsigned dir)
{
	unsigned start, end;
	if (dir == UINT_MAX) {
		start = 0;
		end = 1;
	}
	else
		start = end = dir;

	for (unsigned i = start; i <= end; i++) {
		if (!stream_active(i))
			continue;

		pthread_mutex_lock(sd[i].cmutex);
		sd[i].state = spa_complete;
		// first wait for buffers to be drained and for the
		// stop callback to signal us that the stream has
		// been stopped
		if (pthread_cond_timedwait_rel(sd[i].ccond, sd[i].cmutex, 5.0) == -1 &&
			errno == ETIMEDOUT)
			LOG_ERROR("stream %u wedged", i);
		pthread_mutex_unlock(sd[i].cmutex);
		sd[i].state = spa_continue;

		int err;
		if ((err = Pa_CloseStream(sd[i].stream)) != paNoError)
			pa_perror(err, "Pa_CloseStream");

		sd[i].stream = 0;
	}
}

void SoundPort::Abort(unsigned dir)
{
	unsigned start, end;
	if (dir == UINT_MAX) {
		start = 0;
		end = 1;
	}
	else
		start = end = dir;

	int err;
	for (unsigned i = start; i <= end; i++) {
		if (!stream_active(i))
			continue;
		if ((err = Pa_AbortStream(sd[i].stream)) != paNoError)
#if __WIN32__
		LOG_PERROR(win_error_string(err).c_str());
#endif
			pa_perror(err, "Pa_AbortStream");
		sd[i].stream = 0;
	}
}


#define WAIT_FOR_COND(cond, s, t) \
do { \
	while (!(cond)) { \
		if (sem_timedwait_rel(s, t) == -1) { \
			if (errno == ETIMEDOUT) { \
				timeout = true;	 \
				break; \
			} else if (errno == EINTR) { \
				continue; \
			} \
			LOG_PERROR("sem_timedwait"); \
			throw SndException(errno); \
		} \
	} \
} while (0)


size_t SoundPort::Read(float *buf, size_t count)
{
#if USE_SNDFILE
	if (playback) {
		read_file(ifPlayback, buf, count);
		if (!capture) {
			if (!bHighSpeed)
				MilliSleep((long)ceil((1e3 * count) / req_sample_rate));
			return count;
		}
	}
#endif

	if (rxppm != progdefaults.RX_corr)
		rxppm = progdefaults.RX_corr;

	sd[0].src_ratio = req_sample_rate / (sd[0].dev_sample_rate * (1.0 + rxppm / 1e6));
	src_set_ratio(rx_src_state, sd[0].src_ratio);

	size_t maxframes = (size_t)floor(sd[0].rb->length() * sd[0].src_ratio / sd[0].params.channelCount);

	if (unlikely(count > maxframes)) {
		size_t n = 0;
#define PA_TIMEOUT_TRIES 10
		int pa_timeout = PA_TIMEOUT_TRIES;
// possible to lock up in this while block if the Read(...) fails
		while (count > maxframes) {
			n += Read(buf, maxframes);
			buf += maxframes * sd[0].params.channelCount;
			count -= n;//maxframes;
			pa_timeout--;
			if (pa_timeout == 0) {
#if __WIN32__
				int err = GetLastError();
				LOG_PERROR(win_error_string(err).c_str());
#endif
				pa_perror(1, "Portaudio read error #1");
				throw SndException("Portaudio read error 1");
			}
		}
		if (count > 0)
			n += Read(buf, count);
		return n;
	}

	float* rbuf = fbuf;
	if (req_sample_rate != sd[0].dev_sample_rate || rxppm != 0) {
		long r;
		size_t n = 0;
		sd[0].blocksize = SCBLOCKSIZE;
		while (n < count) {
			if  ((r = src_callback_read(rx_src_state, sd[0].src_ratio,
							count - n, rbuf + n * sd[0].params.channelCount)) == 0) {
				pa_perror(2, "Portaudio read error #2");
				throw SndException("Portaudio read error 2");
			}
			n += r;
		}
	}
	else {
		bool timeout = false;
		WAIT_FOR_COND( (sd[0].rb->read_space() >= count * sd[0].params.channelCount / sd[0].src_ratio), sd[0].rwsem,
				   (MAX(1.0, 2 * count * sd[0].params.channelCount / sd->dev_sample_rate)) );
		if (timeout) {
			pa_perror(3, "Portaudio read error #3");
			throw SndException("Portaudio read error 3");
		}
		ringbuffer<float>::vector_type vec[2];
		sd[0].rb->get_rv(vec);
		if (vec[0].len >= count * sd[0].params.channelCount) {
			rbuf = vec[0].buf;
			sd[0].advance = vec[0].len;
		}
		else
			sd[0].rb->read(rbuf, count * sd[0].params.channelCount);
	}
	if (sd[0].advance) {
		sd[0].rb->read_advance(sd[0].advance);
		sd[0].advance = 0;
	}

	// transfer active input channel; left == 0, right == 1
	size_t n;
	for (size_t i = 0; i < count; i++) {
		n = sd[0].params.channelCount * i;
		if (sd[0].params.channelCount == 2)
			n += progdefaults.ReverseRxAudio;
		buf[i] = rbuf[n];
	}

#if USE_SNDFILE
	if (capture)
		write_file(ofCapture, buf, count);
#endif

		return count;
}

size_t SoundPort::Write(double *buf, size_t count)
{
#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, buf, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

// copy input to both channels if right channel enabled
	for (size_t i = 0; i < count; i++)
		if (progdefaults.sig_on_right_channel)
			fbuf[sd[1].params.channelCount * i] = fbuf[sd[1].params.channelCount * i + 1] = buf[i];
		else if (progdefaults.ReverseAudio) {
				fbuf[sd[1].params.channelCount * i + 1] = buf[i];
				fbuf[sd[1].params.channelCount * i] = 0;
		} else {
				fbuf[sd[1].params.channelCount * i] = buf[i];
				fbuf[sd[1].params.channelCount * i + 1] = 0;
		}

	return resample_write(fbuf, count);
}

size_t SoundPort::Write_stereo(double *bufleft, double *bufright, size_t count)
{
	if (sd[1].params.channelCount != 2)
		return Write(bufleft, count);

#if USE_SNDFILE
	if (generate)
		write_file(ofCapture, bufleft, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

// interleave into fbuf
	for (size_t i = 0; i < count; i++) {
		if (progdefaults.ReverseAudio) {
			fbuf[sd[1].params.channelCount * i + 1] = bufleft[i];
			fbuf[sd[1].params.channelCount * i] = bufright[i];
		} else {
			fbuf[sd[1].params.channelCount * i] = bufleft[i];
			fbuf[sd[1].params.channelCount * i + 1] = bufright[i];
		}
	}

	return resample_write(fbuf, count);
}


size_t SoundPort::resample_write(float* buf, size_t count)
{
	size_t maxframes = (size_t)floor((sd[1].rb->length() / sd[1].params.channelCount) / tx_src_data->src_ratio);
	maxframes /= 2; // don't fill the buffer

	if (unlikely(count > maxframes)) {
		size_t n = 0;
#define PA_TIMEOUT_TRIES 10
		int pa_timeout = PA_TIMEOUT_TRIES;
// possible to lock up in this while block if the resample_write(...) fails
		while (count > maxframes) {
			n += resample_write(buf, maxframes);
			buf += sd[1].params.channelCount * maxframes;
			count -= maxframes;
			pa_timeout--;
			if (pa_timeout == 0) {
				pa_perror(1, "Portaudio write error #1");
				throw SndException("Portaudio write error 1");
			}
		}
		if (count > 0)
			n += resample_write(buf, count);
		return n;
	}

	assert(count * sd[1].params.channelCount * tx_src_data->src_ratio <= sd[1].rb->length());

	ringbuffer<float>::vector_type vec[2];
	sd[1].rb->get_wv(vec);
	float* wbuf = buf;
	if (req_sample_rate != sd[1].dev_sample_rate || progdefaults.TX_corr != 0) {
		if (vec[0].len >= sd[1].params.channelCount * (size_t)ceil(count * tx_src_data->src_ratio))
			wbuf = vec[0].buf; // direct write in the rb
		else
			wbuf = src_buffer;

		if (txppm != progdefaults.TX_corr)
			txppm = progdefaults.TX_corr;

		tx_src_data->src_ratio = sd[1].dev_sample_rate * (1.0 + txppm / 1e6) / req_sample_rate;
		src_set_ratio(tx_src_state, tx_src_data->src_ratio);

		tx_src_data->data_in = buf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = wbuf;
		tx_src_data->output_frames = (wbuf == vec[0].buf ? vec[0].len : SND_BUF_LEN);
		tx_src_data->end_of_input = 0;
		int r;
		if ((r = src_process(tx_src_state, tx_src_data)) != 0) {
			pa_perror(2, "Portaudio write error #2");
			throw SndException("Portaudio write error 2");
		}
		if (tx_src_data->output_frames_gen == 0) // input was too small
			return count;

		count = tx_src_data->output_frames_gen;
		if (wbuf == vec[0].buf) { // advance write pointer and return
			sd[1].rb->write_advance(sd[1].params.channelCount * count);
			sem_trywait(sd[1].rwsem);
			return count;
		}
	}

	// if we didn't do a direct resample into the rb, or didn't resample at all,
	// we must now copy buf into the ringbuffer, possibly waiting for space first
	bool timeout = false;
	WAIT_FOR_COND( (sd[1].rb->write_space() >= sd[1].params.channelCount * count), sd[1].rwsem,
				   (MAX(1.0, 2 * sd[1].params.channelCount * count / sd[1].dev_sample_rate)) );
	if (timeout) {
		pa_perror(3, "Portaudio write error #3");
		throw SndException("Portaudio write error 3");
	}
	sd[1].rb->write(wbuf, sd[1].params.channelCount * count);

	return count;
}

void SoundPort::flush(unsigned dir)
{
	unsigned start, end;
	if (dir == UINT_MAX) {
		start = 0;
		end = 1;
	}
	else
		start = end = dir;

	for (unsigned i = start; i <= end; i++) {
		if (!stream_active(i))
			continue;

		pthread_mutex_lock(sd[i].cmutex);
		sd[i].state = spa_drain;
		if (pthread_cond_timedwait_rel(sd[i].ccond, sd[i].cmutex, 5.0) == -1
			&& errno == ETIMEDOUT)
			LOG_ERROR("stream %u wedged", i);
		pthread_mutex_unlock(sd[i].cmutex);
		sd[i].state = spa_continue;
	}
}

void SoundPort::src_data_reset(unsigned dir)
{
	size_t rbsize;

	int err;
	if (dir == 0) {
		if (rx_src_state)
			src_delete(rx_src_state);
			rx_src_state = src_callback_new(src_read_cb, progdefaults.sample_converter,
											sd[0].params.channelCount, &err, &sd[0]);
			if (!rx_src_state) {
				pa_perror(err, src_strerror(err));
				throw SndException(src_strerror(err));
			}
			sd[0].src_ratio = req_sample_rate / (sd[0].dev_sample_rate * (1.0 + rxppm / 1e6));
	}
	else if (dir == 1) {
		if (tx_src_state)
			src_delete(tx_src_state);
			tx_src_state = src_new(progdefaults.sample_converter, sd[1].params.channelCount, &err);
			if (!tx_src_state) {
				pa_perror(err, src_strerror(err));
				throw SndException(src_strerror(err));
			}
			tx_src_data->src_ratio = sd[1].dev_sample_rate * (1.0 + txppm / 1e6) / req_sample_rate;
	}

	rbsize = 2 * MAX(ceil2(
					(unsigned)(2 * sd[dir].params.channelCount * SCBLOCKSIZE *
					MAX(req_sample_rate, sd[dir].dev_sample_rate) /
					MIN(req_sample_rate, sd[dir].dev_sample_rate))),
					8192);
	LOG_VERBOSE("rbsize = %" PRIuSZ "", rbsize);
	if (sd[dir].rb) delete sd[dir].rb;
	sd[dir].rb = new ringbuffer<float>(rbsize);
}

long SoundPort::src_read_cb(void* arg, float** data)
{
	struct stream_data* sd = reinterpret_cast<stream_data*>(arg);

	// advance read pointer for previous read
	if (sd->advance) {
		sd->rb->read_advance(sd->advance);
		sd->advance = 0;
	}

	// wait for data
	bool timeout = false;
	WAIT_FOR_COND( (sd->rb->read_space() >= (size_t)sd[0].params.channelCount * SCBLOCKSIZE), sd->rwsem,
				   (MAX(1.0, 2 * sd[0].params.channelCount * SCBLOCKSIZE / sd->dev_sample_rate)) );
	if (timeout) {
		*data = 0;
		return 0;
	}

	ringbuffer<float>::vector_type vec[2];
	sd->rb->get_rv(vec);

	*data = vec[0].buf;
	sd->advance = vec[0].len;

	return vec[0].len / sd[0].params.channelCount;
}

SoundPort::device_iterator SoundPort::name_to_device(std::string &name, unsigned dir)
{
	device_iterator i;
	for (i = devs.begin(); i != devs.end(); ++i)
		if (name == (*i)->name && (dir ? (*i)->maxOutputChannels : (*i)->maxInputChannels))
			break;
	return i;
}

void SoundPort::init_stream(unsigned dir)
{
	const char* dir_str[2] = { "input", "output" };
	PaDeviceIndex idx = paNoDevice;

	LOG_DEBUG("looking for device \"%s\"", sd[dir].device.c_str());

	if ((sd[dir].idev = name_to_device(sd[dir].device, dir)) != devs.end())
		idx = sd[dir].idev - devs.begin();
		if (idx == paNoDevice) { // no match
		LOG_ERROR("Could not find device \"%s\", using default device", sd[dir].device.c_str());
		PaDeviceIndex def = (dir == 0 ? Pa_GetDefaultInputDevice() : Pa_GetDefaultOutputDevice());
		if (def == paNoDevice) {
			pa_perror(paDeviceUnavailable, "Portaudio device unavailable");
			throw SndPortException(paDeviceUnavailable);
		}
		sd[dir].idev = devs.begin() + def;
		idx = def;
		}
	else if (sd[dir].idev == devs.end()) // if we only found a near-match point the idev iterator to it
		sd[dir].idev = devs.begin() + idx;

	const PaHostApiInfo* host_api = Pa_GetHostApiInfo((*sd[dir].idev)->hostApi);
	int max_channels = dir ? (*sd[dir].idev)->maxOutputChannels :
		(*sd[dir].idev)->maxInputChannels;
	if ((host_api->type == paALSA || host_api->type == paOSS) && max_channels == 0) {
		pa_perror(EBUSY, "Portaudio device busy");
		throw SndException(EBUSY);
	}

	if (dir == 0) {
		sd[0].params.device = idx;
		sd[0].params.sampleFormat = paFloat32;
		sd[0].params.suggestedLatency = (*sd[dir].idev)->defaultHighInputLatency;
		sd[0].params.hostApiSpecificStreamInfo = NULL;
		if (max_channels < 2)
				sd[0].params.channelCount = max_channels;
		if (max_channels == 0) {
			pa_perror(EBUSY, "Portaudio device cannot open for read");
			throw SndException(EBUSY);
		}
	}
	else {
		sd[1].params.device = idx;
		sd[1].params.sampleFormat = paFloat32;
		if (host_api->type == paMME)
						sd[1].params.suggestedLatency = (*sd[dir].idev)->defaultLowOutputLatency;
				else
						sd[1].params.suggestedLatency = (*sd[dir].idev)->defaultHighOutputLatency;
		sd[1].params.hostApiSpecificStreamInfo = NULL;
	}

	const vector<double>& rates = supported_rates[dir][(*sd[dir].idev)->name];
	if (rates.size() <= 1)
		probe_supported_rates(sd[dir].idev);
	ostringstream ss;
	if (rates.size() > 1)
		copy(rates.begin() + 1, rates.end(), ostream_iterator<double>(ss, " "));
	else
		ss << "Unknown";

	{
	guard_lock devices_lock(&device_mutex);

	device_text[dir].str("");
		device_text[dir]
		<< "index: " << idx
		<< "\nname: " << (*sd[dir].idev)->name
		<< "\nhost API: " << host_api->name
		<< "\nmax input channels: " << (*sd[dir].idev)->maxInputChannels
		<< "\nmax output channels: " << (*sd[dir].idev)->maxOutputChannels
		<< "\ndefault sample rate: " << (*sd[dir].idev)->defaultSampleRate
		<< "\nsupported sample rates: " << ss.str()
		<< boolalpha
		<< "\ninput only: " << ((*sd[dir].idev)->maxOutputChannels == 0)
		<< "\noutput only: " << ((*sd[dir].idev)->maxInputChannels == 0)
		<< "\nfull duplex: " << full_duplex_device(*sd[dir].idev)
		<< "\nsystem default input: " << (idx == Pa_GetDefaultInputDevice())
		<< "\nsystem default output: " << (idx == Pa_GetDefaultOutputDevice())
		<< "\nhost API default input: " << (idx == host_api->defaultInputDevice)
		<< "\nhost API default output: " << (idx == host_api->defaultOutputDevice)
		<< "\ndefault low input latency: " << (*sd[dir].idev)->defaultLowInputLatency
		<< "\ndefault high input latency: " << (*sd[dir].idev)->defaultHighInputLatency
		<< "\ndefault low output latency: " << (*sd[dir].idev)->defaultLowOutputLatency
		<< "\ndefault high output latency: " << (*sd[dir].idev)->defaultHighOutputLatency
		<< "\n";
	}

	LOG_VERBOSE("using %s (%d ch) device \"%s\":\n%s", dir_str[dir], sd[dir].params.channelCount,
		sd[dir].device.c_str(), device_text[dir].str().c_str());

		sd[dir].dev_sample_rate = find_srate(dir);
		if (sd[dir].dev_sample_rate != req_sample_rate)
		LOG_DEBUG("%s: resampling %f <=> %f", dir_str[dir],
			  sd[dir].dev_sample_rate, req_sample_rate);

		if (progdefaults.PortFramesPerBuffer > 0) {
				sd[dir].frames_per_buffer = progdefaults.PortFramesPerBuffer;
		LOG_DEBUG("%s: frames_per_buffer=%u", dir_str[dir], sd[dir].frames_per_buffer);
	}
}

void SoundPort::start_stream(unsigned dir)
{
	int err;

	PaStreamParameters* sp[2];
	sp[dir] = &sd[dir].params;
	sp[!dir] = NULL;

	err = Pa_OpenStream(&sd[dir].stream, sp[0], sp[1],
			sd[dir].dev_sample_rate, sd[dir].frames_per_buffer,
			paNoFlag,
			stream_process, &sd[dir]);
	if (err != paNoError) {
		pa_perror(err, "Portaudio open stream error");
		throw SndPortException(err);
	}

	if ((err = Pa_SetStreamFinishedCallback(sd[dir].stream, stream_stopped)) != paNoError)
		throw SndPortException(err);

	if ((err = Pa_StartStream(sd[dir].stream)) != paNoError) {
		pa_perror(err, "Portaudio stream start stream error");
		Close();
		throw SndPortException(err);
	}
}


int SoundPort::stream_process(
			const void* in, void* out, unsigned long nframes,
			const PaStreamCallbackTimeInfo *time_info,
			PaStreamCallbackFlags flags, void* data)
{
	struct stream_data* sd = reinterpret_cast<struct stream_data*>(data);

#ifndef NDEBUG
	struct {
		PaStreamCallbackFlags f;
		const char* s;
	} fa[] = { { paInputUnderflow, "Input underflow" },
			   { paInputOverflow,  "Input overflow" },
			   { paOutputUnderflow, "Output underflow" },
			   { paOutputOverflow, "Output overflow" }
			};
	for (size_t i = 0; i < sizeof(fa)/sizeof(*fa); i++)
		if (flags & fa[i].f)
			LOG_DEBUG("%s", fa[i].s);
#endif

	if (unlikely(sd->state == spa_abort || sd->state == spa_complete)) // finished
		return sd->state;

	if (in) {
		switch (sd->state) {
			case spa_continue: // write into the rb, post rwsem if we wrote anything
				if (sd->rb->write(reinterpret_cast<const float*>(in), sd->params.channelCount * nframes))
					sem_post(sd->rwsem);
				break;
			case spa_drain: case spa_pause: // signal the cv
				pthread_mutex_lock(sd->cmutex);
				pthread_cond_signal(sd->ccond);
				pthread_mutex_unlock(sd->cmutex);
		}
	}
	else if (out) {
		float* outf = reinterpret_cast<float*>(out);
		// if we are paused just pretend that the rb was empty
		size_t nread = (sd->state == spa_pause) ? 0 : sd->rb->read(outf, sd->params.channelCount * nframes);
		memset(outf + nread, 0, (sd->params.channelCount * nframes - nread) * sizeof(float)); // fill rest with 0

		switch (sd->state) {
			case spa_continue: // post rwsem if we read anything
				if (nread > 0)
					sem_post(sd->rwsem);
				break;
			case spa_drain: // signal the cv when we have emptied the buffer
				if (nread > 0)
					break;
			// else fall through
			case spa_pause:
				pthread_mutex_lock(sd->cmutex);
				pthread_cond_signal(sd->ccond);
				pthread_mutex_unlock(sd->cmutex);
				break;
		}
	}

	return paContinue;
}

void SoundPort::stream_stopped(void* data)
{
	struct stream_data* sd = reinterpret_cast<struct stream_data*>(data);

	if (sd->rb)
		sd->rb->reset();
	pthread_mutex_lock(sd->cmutex);
	pthread_cond_signal(sd->ccond);
	pthread_mutex_unlock(sd->cmutex);
}


bool SoundPort::stream_active(unsigned dir)
{
	if (!sd[dir].stream)
		return false;

	int err;
	if ((err = Pa_IsStreamActive(sd[dir].stream)) < 0) {
		pa_perror(err, "Portaudio stream active error");
		throw SndPortException(err);
	}
	return err == 1;
}

bool SoundPort::full_duplex_device(const PaDeviceInfo* dev)
{
	return dev->maxInputChannels > 0 && dev->maxOutputChannels > 0;
}

bool SoundPort::must_close(int dir)
{
	return Pa_GetHostApiInfo((*sd[dir].idev)->hostApi)->type != paJACK;
}

double SoundPort::find_srate(unsigned dir)
{
	int sr = (dir == 0 ? progdefaults.in_sample_rate : progdefaults.out_sample_rate);
		switch (sr) {
		case SAMPLE_RATE_UNSET: case SAMPLE_RATE_AUTO:
				break;
		case SAMPLE_RATE_NATIVE:
				return (*sd[dir].idev)->defaultSampleRate;
		default:
				return sr;
		}

	const vector<double>& rates = supported_rates[dir][(*sd[dir].idev)->name];
	for (vector<double>::const_iterator i = rates.begin(); i != rates.end(); i++)
		if (req_sample_rate == *i || (*sd[dir].idev)->defaultSampleRate == *i)
			return *i;

	pa_perror(0, "Portaudio - no supported sample rate found");
	throw SndException("No supported sample rate found");
}

void SoundPort::probe_supported_rates(const device_iterator& idev)
{
	PaStreamParameters params[2];
	params[0].device = params[1].device = idev - devs.begin();
	params[0].channelCount = 2;
	params[1].channelCount = 2;
	params[0].sampleFormat = params[1].sampleFormat = paFloat32;
	params[0].suggestedLatency = (*idev)->defaultHighInputLatency;
	params[1].suggestedLatency = (*idev)->defaultHighOutputLatency;
	params[0].hostApiSpecificStreamInfo = params[1].hostApiSpecificStreamInfo = NULL;

	supported_rates[0][(*idev)->name].clear();
	supported_rates[1][(*idev)->name].clear();
	supported_rates[0][(*idev)->name].push_back((*idev)->defaultSampleRate);
	supported_rates[1][(*idev)->name].push_back((*idev)->defaultSampleRate);
	extern double std_sample_rates[];
	for (const double* r = std_sample_rates; *r > 0.0; r++) {
		if (Pa_IsFormatSupported(&params[0], NULL, *r) == paFormatIsSupported)
			supported_rates[0][(*idev)->name].push_back(*r);
		if (Pa_IsFormatSupported(NULL, &params[1], *r) == paFormatIsSupported)
			supported_rates[1][(*idev)->name].push_back(*r);
	}
}

void SoundPort::pa_perror(int err, const char* str)
{
	if (str)
	LOG_ERROR("%s: %s", str, Pa_GetErrorText(err));

	if (err == paUnanticipatedHostError) {
		const PaHostErrorInfo* hosterr = Pa_GetLastHostErrorInfo();
		PaHostApiIndex i = Pa_HostApiTypeIdToHostApiIndex(hosterr->hostApiType);

		if (i < 0) { // PA failed without setting its "last host error" info. Sigh...
			LOG_ERROR("Host API error info not available");
			if ( ((sd[0].stream && Pa_GetHostApiInfo((*sd[0].idev)->hostApi)->type == paOSS) ||
				(sd[1].stream && Pa_GetHostApiInfo((*sd[1].idev)->hostApi)->type == paOSS)) &&
				errno )
				LOG_ERROR("Possible OSS error %d: %s", errno, strerror(errno));
		}
		else
			LOG_ERROR("%s error %ld: %s", Pa_GetHostApiInfo(i)->name,
				  hosterr->errorCode, hosterr->errorText);
	}
}

void SoundPort::init_hostapi_ext(void)
{
#if HAVE_DLOPEN && !defined(__WOE32__)
	void* handle = dlopen(NULL, RTLD_LAZY);
	if (!handle)
		return;

	PaError (*set_jack_client_name)(const char*);
	const char* err = dlerror();
	set_jack_client_name = (PaError (*)(const char*))dlsym(handle, "PaJack_SetClientName");
	if (!(err = dlerror()))
		set_jack_client_name(main_window_title.c_str());
#  ifndef NDEBUG
		else
		LOG_VERBOSE("dlsym(PaJack_SetClientName) error: %s", err);
#  endif
#endif
}

#endif // USE_PORTAUDIO


#if USE_PULSEAUDIO

SoundPulse::SoundPulse(const char *dev)
{
	sd[0].stream = 0;
	sd[0].stream_params.channels = 2;
	sd[0].dir = PA_STREAM_RECORD; 
	sd[0].stream_params.format = PA_SAMPLE_FLOAT32LE;
	sd[0].buffer_attrs.maxlength = (uint32_t)-1;
	sd[0].buffer_attrs.minreq = (uint32_t)-1;
	sd[0].buffer_attrs.prebuf = (uint32_t)-1;
	sd[0].buffer_attrs.fragsize = SCBLOCKSIZE * sizeof(float);
	sd[0].buffer_attrs.tlength = (uint32_t)-1;

	sd[1].stream = 0;
	sd[1].dir = PA_STREAM_PLAYBACK;
	sd[1].stream_params.format = PA_SAMPLE_FLOAT32LE;
	sd[1].stream_params.channels = 2;
	sd[1].buffer_attrs.maxlength = (uint32_t)-1;
	sd[1].buffer_attrs.minreq = (uint32_t)-1;
	sd[1].buffer_attrs.prebuf = (uint32_t)-1;
	sd[1].buffer_attrs.fragsize = (uint32_t)-1;
	sd[1].buffer_attrs.tlength = SCBLOCKSIZE * sizeof(float);

	tx_src_data = new SRC_DATA;

	snd_buffer = new float[sd[0].stream_params.channels * SND_BUF_LEN];
	rbuf       = new float[sd[0].stream_params.channels * SND_BUF_LEN];

	src_buffer = new float[sd[1].stream_params.channels * SND_BUF_LEN];
	fbuf       = new float[sd[1].stream_params.channels * SND_BUF_LEN];

	memset(snd_buffer, 0, sd[0].stream_params.channels * SND_BUF_LEN * sizeof(*snd_buffer));
	memset(rbuf,       0, sd[0].stream_params.channels * SND_BUF_LEN * sizeof(*rbuf));

	memset(src_buffer, 0, sd[1].stream_params.channels * SND_BUF_LEN * sizeof(*src_buffer));
	memset(fbuf,       0, sd[1].stream_params.channels * SND_BUF_LEN * sizeof(*fbuf));
}

SoundPulse::~SoundPulse()
{
	Close();

	delete tx_src_data;
	if (rx_src_state)
		src_delete(rx_src_state);
	if (tx_src_state)
		src_delete(tx_src_state);

	delete [] snd_buffer;
	delete [] src_buffer;
	delete [] fbuf;
	delete [] rbuf;
}

int SoundPulse::Open(int mode, int freq)
{
	const char* server = (progdefaults.PulseServer.length() ?
				  progdefaults.PulseServer.c_str() : NULL);
	char sname[32];
	int err;

	sample_frequency = freq;
	for (int i = 0; i < 2; i++) {
		src_data_reset(1 << O_RDONLY | 1 << O_WRONLY);

		if ((unsigned)freq != sd[i].stream_params.rate)
			Close(i);
		if (sd[i].stream)
			continue;

		sd[i].stream_params.rate = freq;
		snprintf(sname, sizeof(sname), "%s (%u)", (i ? "playback" : "capture"), getpid());
		setenv("PULSE_PROP_application.icon_name", PACKAGE_TARNAME, 1);
		sd[i].stream = pa_simple_new(server, main_window_title.c_str(), sd[i].dir, NULL,
						 sname, &sd[i].stream_params, NULL,
						 &sd[i].buffer_attrs, &err);
		if (!sd[i].stream)
			throw SndPulseException(err);
	}

	return 0;
}

void SoundPulse::Close(unsigned dir)
{
	if (dir == 1 || dir == UINT_MAX)
		flush(1);
	Abort(dir);
}

void SoundPulse::Abort(unsigned dir)
{
		unsigned start, end;
		if (dir == UINT_MAX) {
				start = 0;
				end = 1;
		}
		else
				start = end = dir;

		for (unsigned i = start; i <= end; i++) {
				if (sd[i].stream) {
						pa_simple_free(sd[i].stream);
						sd[i].stream = 0;
				}
		}

}

void SoundPulse::flush(unsigned dir)
{
	int err = PA_OK;
	if ((dir == 1 || dir == UINT_MAX) && sd[1].stream) {
		// wait for audio to finish playing
//	  pa_simple_drain(sd[1].stream, &err);
		MilliSleep(SCBLOCKSIZE * 1000 / sd[1].stream_params.rate);
		pa_simple_flush(sd[1].stream, &err);
	}

	if ((dir == 0 || dir == UINT_MAX) && sd[0].stream) {
		// We need to flush the captured audio that PA has been
		// buffering for us while we were transmitting.  We will use
		// pa_simple_get_latency() which, contrary to the docs, also
		// works for capture streams.  It tells us how much earlier the
		// data that would be returned by pa_simple_read() was actually
		// captured, and we read and discard all that data.
		pa_usec_t t = pa_simple_get_latency(sd[0].stream, &err);
		if (t && err == PA_OK) {
			size_t bytes = pa_usec_to_bytes(t, &sd[0].stream_params);
			while (bytes > SND_BUF_LEN) {
				pa_simple_read(sd[0].stream, snd_buffer, SND_BUF_LEN, &err);
				if (err != PA_OK)
					break;
				bytes -= SND_BUF_LEN;
			}
			if (bytes)
				pa_simple_read(sd[0].stream, snd_buffer, bytes, &err);
		}
	}
}

size_t SoundPulse::Write(double* buf, size_t count)
{
#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, buf, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

// copy input to both channels
	for (size_t i = 0; i < count; i++)
		if (progdefaults.sig_on_right_channel)
			fbuf[sd[1].stream_params.channels * i] = fbuf[sd[1].stream_params.channels * i + 1] = buf[i];
		else if (progdefaults.ReverseAudio) {
			fbuf[sd[1].stream_params.channels * i + 1] = buf[i];
			fbuf[sd[1].stream_params.channels * i] = 0;
		} else {
			fbuf[sd[1].stream_params.channels * i] = buf[i];
			fbuf[sd[1].stream_params.channels * i + 1] = 0;
		}

	return resample_write(fbuf, count);
}

size_t SoundPulse::Write_stereo(double* bufleft, double* bufright, size_t count)
{
	if (sd[1].stream_params.channels != 2)
		return Write(bufleft, count);

#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, bufleft, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

	for (size_t i = 0; i < count; i++) {
		if (progdefaults.ReverseAudio) {
			fbuf[sd[1].stream_params.channels * i + 1] = bufleft[i];
			fbuf[sd[1].stream_params.channels * i] = bufright[i];
		} else {
			fbuf[sd[1].stream_params.channels * i] = bufleft[i];
			fbuf[sd[1].stream_params.channels * i + 1] = bufright[i];
		}
	}

	return resample_write(fbuf, count);
}

size_t SoundPulse::resample_write(float* buf, size_t count)
{
	int err;
		float *wbuf = buf;

	if (progdefaults.TX_corr != 0) {
		if (txppm != progdefaults.TX_corr) {
			txppm = progdefaults.TX_corr;
			tx_src_data->src_ratio = 1.0 + txppm / 1e6;
			src_set_ratio(tx_src_state, tx_src_data->src_ratio);
		}
		tx_src_data->data_in = wbuf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = src_buffer;
		tx_src_data->output_frames = SND_BUF_LEN;
		tx_src_data->end_of_input = 0;
		if ((err = src_process(tx_src_state, tx_src_data)) != 0)
			throw SndException(src_strerror(err));
		if (tx_src_data->output_frames_gen == 0) // input was too small
			return count;

		wbuf = tx_src_data->data_out;
		count = tx_src_data->output_frames_gen;
	}

	if (pa_simple_write(sd[1].stream, wbuf, count * sd[1].stream_params.channels * sizeof(float), &err) == -1)
		throw SndPulseException(err);

	return count;
}

long SoundPulse::src_read_cb(void* arg, float** data)
{
		SoundPulse* p = reinterpret_cast<SoundPulse*>(arg);

	int err;
	int nread = 0;
	if ((nread = pa_simple_read(p->sd[0].stream, p->snd_buffer, 
			p->sd[0].stream_params.channels * sizeof(float) * p->sd[0].blocksize, &err)) == -1) {
		LOG_ERROR("%s", pa_strerror(err));
		*data = 0;
		return 0;
	}

	*data = p->snd_buffer;
	return p->sd[0].blocksize;
}

size_t SoundPulse::Read(float *buf, size_t count)
{
#if USE_SNDFILE
	if (playback) {
		read_file(ifPlayback, buf, count);
		if (!capture) {
			flush(0);
			if (!bHighSpeed)
				MilliSleep((long)ceil((1e3 * count) / sample_frequency));
			return count;
		}
	}
#endif

	size_t n = 0;
	long r = 0;

	if (progdefaults.RX_corr != 0) {
		if (rxppm != progdefaults.RX_corr) {
			rxppm = progdefaults.RX_corr;
			sd[0].src_ratio = 1.0 / (1.0 + rxppm / 1e6);
			src_set_ratio(rx_src_state, sd[0].src_ratio);
		}
		sd[0].blocksize = SCBLOCKSIZE;
		while (n < count) {
			if ((r = src_callback_read(rx_src_state, sd[0].src_ratio, count - n, rbuf + n)) == 0)
				break;
			n += r;
		}
	}
	else {
		int err;
		if ((r = pa_simple_read(sd[0].stream, rbuf, 
				sd[0].stream_params.channels * sizeof(float) * count, &err)) == -1)
			throw SndPulseException(err);
	}

	// transfer active input channel; left == 0, right == 1
	size_t i = 0;
	if (sd[0].stream_params.channels == 2) n = progdefaults.ReverseRxAudio;
	else n = 0;
	while (i < count) {
		buf[i] = rbuf[n];
		i++;
		n += sd[0].stream_params.channels;
	}

#if USE_SNDFILE
	if (capture)
				write_file(ofCapture, buf, count);
#endif

	return count;
}

void SoundPulse::src_data_reset(int mode)
{
		int err;
		if (mode & 1 << O_RDONLY) {
				if (rx_src_state)
						src_delete(rx_src_state);
				rx_src_state = src_callback_new(src_read_cb, progdefaults.sample_converter,
						sd[0].stream_params.channels, &err, this);
				if (!rx_src_state)
						throw SndException(src_strerror(err));
				sd[0].src_ratio = 1.0 / (1.0 + rxppm / 1e6);
		}
		if (mode & 1 << O_WRONLY) {
				if (tx_src_state)
						src_delete(tx_src_state);
				tx_src_state = src_new(progdefaults.sample_converter, sd[1].stream_params.channels, &err);
				if (!tx_src_state)
						throw SndException(src_strerror(err));
				tx_src_data->src_ratio = 1.0 + txppm / 1e6;
		}
}

#endif // USE_PULSEAUDIO


////////////////////////////////////////////////////////////////////////////////////////
#if USE_SDR


#include "socket.h"

#define CAN_TRANSMIT		0

#define QTRADIO_COMMANDS	1			// mimic (portions of) QtRadio control

#define RIG_RCVR_MIN		0
#define RIG_RCVR_MAX		80

#define SDR_PORTBASE		8000
#define RIG_PORTBASE		19090

// Used by fldigi to normalize samples to +-1.0f
#define NORM_8BIT		((float)(128.0))
#define NORM_16BIT		((float)(32768.0))

#define FREQUENCY_MAX		60000000		// 60.0MHz max, 0MHz min assumed
#define SDR_FILT_LO		0
#define SDR_FILT_HI		7000
#define SDR_AUDIO_GAIN		25			// received audio gain
/// XXX #define SDR_RF_GAIN	1.0f			// received rf gain
#define ADJUSTED_RF		(sdr_divisor / sdr_rf_gain)

// Adjust offset between hydra transmitter and hydra receiver(s).
#define HYDRA_FREQUENCY_OFFSET	1

#define USE_PCM			1
#if USE_PCM
#define ENCODING		1
#else
#define ENCODING		0
#endif

// XXX [explain this crap...]
#define EXPECTED_SPECTRUM	975

string	sdrbuffer = "";
string	sdr_address =  "localhost";	// default to localhost
Socket	*sdr_tcpip = 0;
string	sdr_port = "";			// tcp port for commands/samples
string	rig_port = "";			// tcp port for rig control

int	sdr_receiver = 0;
int	sdr_filt_lo = SDR_FILT_LO;
int	sdr_filt_hi = SDR_FILT_HI;

#if USE_PCM
float	sdr_divisor = NORM_16BIT;
#else
float	sdr_divisor = NORM_8BIT;
#endif

bool	socket_closed = true;

SoundSDR::SoundSDR(const char *dev)
{
	fprintf(stderr, "##################################### SoundSDR::SoundSDR(%s)\n", dev);

	device		  = dev;
	cbuff		  = 0;
#if 1
        need		  = HEADER_SIZE;
#endif
	char msg[80];
	try {
		sdr_receiver = progdefaults.SdrReceiver;
		if ((sdr_receiver < 0) || (sdr_receiver > RIG_RCVR_MAX)) {
			snprintf(msg, sizeof(msg), ">>>>>>>> invalid receiver %d", sdr_receiver);
			throw SndException(msg);
		}

		sdr_frequency = progdefaults.SdrFrequency;
		if ((sdr_frequency < 0) || (sdr_frequency > FREQUENCY_MAX)) {
			snprintf(msg, sizeof(msg), ">>>>>>>> invalid frequency %d", sdr_frequency);
			throw SndException(msg);
		}

		sdr_audio_gain = progdefaults.SdrAudioGain;
		if (sdr_audio_gain < 1) {
			LOG_ERROR("Audio gain of less than 1 disables Audio\n"
				  "\t\tedit <config-dir>/fldigi_def.xml, I suggest 25-40");
		}
		if ((sdr_audio_gain < 0) || (sdr_audio_gain > 100)) {
			snprintf(msg, sizeof(msg),
				 ">>>>>>>> invalid audio gain %d, I suggest 25-40", sdr_audio_gain);
			throw SndException(msg);
		}

		sdr_rf_gain = progdefaults.SdrRFGain;
		if ((sdr_rf_gain < 0.1) || (sdr_rf_gain > 10.0)) {
			snprintf(msg, sizeof(msg),
				 ">>>>>>>> invalid RF gain %2.1f, I suggest 1.0", sdr_rf_gain);
			throw SndException(msg);
		}

		sdr_dither = progdefaults.SdrDither;
		sdr_random = progdefaults.SdrRandom;
		sdr_preamp = progdefaults.SdrPreAmp;

#if 0
		progStatus.noCATfreq = 0;
#else
		progStatus.noCATfreq = sdr_frequency;
#endif

		sdr_address = progdefaults.SdrDspHost;
		char tmp[10];
		snprintf(tmp, 9, "%d", SDR_PORTBASE + sdr_receiver);
		sdr_port.assign(tmp);
		snprintf(tmp, 9, "%d", RIG_PORTBASE + sdr_receiver);
		rig_port.assign(tmp);
		LOG_INFO("\n##########################################################################\n"
			 "  Dsp: %d\n  DSP Host: %s\n  DSP Control Port: %s\n"
			 "  Rig Control Port: %s\n"
			 "##########################################################################",
	  		 sdr_receiver, sdr_address.c_str(), sdr_port.c_str(), rig_port.c_str());
	}
	catch (const SndException& e) {
		LOG_INFO("\n#################################################################\n"
			 "%s, edit <config-dir>/fldigi_def.xml\n"
			 "#################################################################",
			 e.what());
		exit(-1);
	}

	// open a socket to 8000 + dsp#
	device_fd = open_sdr();

	try {
		Open(O_RDONLY, 0);
		getVersion();
		getCapabilities();
		getFormats();
		Close();
	}
	catch (const SndException& e) {
		LOG_ERROR("device %s error: %s", device.c_str(), e.what());
	}

	snd_buffer = new float [2 * SND_BUF_LEN];		// enuff for 2 channels
	src_buffer = new float [2 * SND_BUF_LEN];
	up_buffer = new float [2 * SND_BUF_LEN];

	cbuff = new unsigned char [4 * SND_BUF_LEN];		// 2 channels * 2 bytes per sample

	havebuff   = 0;
	haveoffset = 0;
	offset_adjusted = false;

	memset(snd_buffer, 0, 2 * SND_BUF_LEN * sizeof(*snd_buffer));
	memset(src_buffer, 0, 2 * SND_BUF_LEN * sizeof(*src_buffer));
	memset(up_buffer, 0, 2 * SND_BUF_LEN * sizeof(*up_buffer));

	memset(cbuff,	 0, 4 * SND_BUF_LEN * sizeof(*cbuff));

	tx_src_data = new SRC_DATA;
	rx_src_data = new SRC_DATA;
	up_src_data = new SRC_DATA;

	int err;
	rx_src_state = src_new(progdefaults.sample_converter, 2, &err);
	if (rx_src_state == 0)
		throw SndException(src_strerror(err));

	tx_src_state = src_new(progdefaults.sample_converter, 2, &err);
	if (tx_src_state == 0)
		throw SndException(src_strerror(err));

	up_src_state = src_new(SRC_LINEAR, 1, &err);	// XXX best???
	if (up_src_state == 0)
		throw SndException(src_strerror(err));

	rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
	src_set_ratio (rx_src_state, 1.0/(1.0 + rxppm/1e6));

	tx_src_data->src_ratio = 1.0 + txppm/1e6;
	src_set_ratio (tx_src_state, 1.0 + txppm/1e6);

	up_src_data->src_ratio = 1.0 * (11025.0f / 8000.0f);
	src_set_ratio (up_src_state, up_src_data->src_ratio);
}

SoundSDR::~SoundSDR()
{
	fprintf(stderr, "##################################### SoundSDR::~SoundSDR()\n");

	// 
	close_sdr();
	device_fd = -1;
	Close();

	delete tx_src_data;
	delete rx_src_data;
	delete up_src_data;

	if (rx_src_state)
		src_delete(rx_src_state);
	if (tx_src_state)
		src_delete(tx_src_state);
	if (up_src_state)
		src_delete(up_src_state);

	delete [] snd_buffer;
	delete [] src_buffer;
	delete [] up_buffer;

	delete [] cbuff;
}

int SoundSDR::open_sdr()
{
fprintf(stderr, "SoundSDR::open_sdr()\n");
#if 1 // XXX XXX XXX
  if (socket_closed == false) {
    return (sdr_tcpip->fd());
  }
#endif
	try {
		sdr_tcpip = new Socket(Address(sdr_address.c_str(), sdr_port.c_str()));
		sdr_tcpip->set_timeout(0.01);
		sdr_tcpip->connect();
		socket_closed = false;
		return (sdr_tcpip->fd());
	}
	catch (...) {
		sdr_tcpip = 0;
		socket_closed = true;

		LOG_ERROR(">>> DSP server @ ip address %s, port %s for receiver #%d is not available.",
			  sdr_address.c_str(), sdr_port.c_str(), sdr_receiver);
		LOG_ERROR(">>>   Hardware must be powered & connected.");
		LOG_ERROR(">>>   Alex hpsdr server and dspserver must be running.");
		LOG_ERROR(">>>   If ip addr: '%s' is incorrect,\n"
			  "\t\tedit [config-dir]/fldigi_def.xml:\n"
			  "\t\t<SDRDSPHOST>[correct ip addr here]</SDRDSPHOST>",
			  sdr_address.c_str());
#if 0
		exit(-1);
#else
		LOG_INFO(">>> continuing without DSP server...");
		return (-1);
#endif
	}
}

void SoundSDR::close_sdr()
{
  fprintf(stderr, ">>>>>>>>>>>>>>>>>>>>> closing sdr\n");
	if (sdr_tcpip) {
		sdr_tcpip->close();
		delete sdr_tcpip;
		sdr_tcpip = 0;
		socket_closed = true;
	}
}

void SoundSDR::setfragsize()
{
	int sndparam;
	// Try to get ~100ms worth of samples per fragment
	sndparam = (int)log2(sample_frequency * 0.1);
#if USE_PCM
	// double since we are using 16 bit samples
	sndparam += 1;
#endif
	// Unlimited amount of buffers for RX, four for TX
	if (mode == O_RDONLY)
		sndparam |= 0x7FFF0000;
	else
		sndparam |= 0x00040000;
#if 0
	if (ioctl(device_fd, SNDCTL_DSP_SETFRAGMENT, &sndparam) < 0)
		throw SndException(errno);
#else
	/// XXX
	LOG_INFO(">>>>>>>>>>>>>>>>>>> what do we need to do for 'set SNDCTL_DSP_SETFRAGMENT: 0x%08x'", sndparam);
#endif
}

// From: ghpsdr3-alex/trunk/src/QtRadio/Connection.h
#define SPECTRUM_BUFFER     0
#define AUDIO_BUFFER        1
#define BANDSCOPE_BUFFER    2
#define RTP_REPLY_BUFFER    3
#define ANSWER_BUFFER       '4'

// minimum supported header version
#define HEADER_VERSION 2
#define HEADER_SUBVERSION 0
#define HEADER_SUBVERSION2 1

// g0orx binary header
#define AUDIO_HEADER_SIZE 5

// length of command buffers are fixed:
#define SEND_BUFFER_SIZE 64

/*
 * fldigi makes 512 byte sample requests,
 *  ghpsdr returns minimum of 2000 byte blocks.
 * round up from 2000 to 2048 (4 * 512), then add some slop
 *  to (attempt to) guarantee a complete ghpsdr block in 1 read.
 * Crude, and it (mostly) DOES NOT work...
 */
#if USE_PCM
#define MAGIC_SIZE	(2096 * 2)			// 2 bytes per sample
#else
#define MAGIC_SIZE	2096
#endif

static bool first_time = true;
int SoundSDR::Open(int md, int freq)
{
	Close();

	mode = md;
	try {
#if USE_PCM
		Format(AFMT_S16_LE);			// default: 16 bit little endian
#else
		Format(AFMT_A_LAW);			// default: 8 bit signed/alaw
#endif
		Channels(1);				// 1 channel
#if 0
		setfragsize();				// XXX is this necessary?
#endif
		// If this is a preliminary open() we don't setup the sdr side.
		if (freq == 0)
			return (0);
		fprintf(stderr, "sample_frequency: %d\n", freq);
		char cmdbuf[SEND_BUFFER_SIZE];
		Sample_frequency(freq);

		if (first_time) {
#if 0
                        // Drain the swamp.
                        uint8_t buff[MAGIC_SIZE];
                        sdr_tcpip->recv(buff, MAGIC_SIZE);
#endif
			sprintf(cmdbuf, "q-version");
			Write_cmd(cmdbuf, true);
			sprintf(cmdbuf, "q-loffset");
			Write_cmd(cmdbuf, true);
			sprintf(cmdbuf, "q-protocol3");
			Write_cmd(cmdbuf, true);
			sprintf(cmdbuf, "q-master");
			Write_cmd(cmdbuf, true);

			// Seems that dspserver appreciates a one-time frequency change at startup.
			// Write it directly as DialFrequency doesn't actually write in all cases.
#if 0 /// XXX 03112016
			sprintf(cmdbuf, "setFrequency %d", sdr_frequency + 1);
#else
                        sprintf(cmdbuf, "setFrequency %d", sdr_frequency);
#endif
			Write_cmd(cmdbuf, false);
		}

		qsoFreqDisp->value(sdr_frequency);
		DialFrequency(sdr_frequency);
#if defined(HYDRA_FREQUENCY_OFFSET)
		LOG_INFO("hydra frequency: %d, ppm offset: %1.20lf",
			 sdr_frequency, progdefaults.SdrPpmHos);
#endif // HYDRA_FREQUENCY_OFFSET

#if 1
		if (first_time) {
			sprintf(cmdbuf, "setMode 7");  // DIGU
			Write_cmd(cmdbuf, false);
			sprintf(cmdbuf, "setFilter %d %d", sdr_filt_lo, sdr_filt_hi);
			Write_cmd(cmdbuf, false);
			sprintf(cmdbuf, "setRXOutputGain %d", sdr_audio_gain);
			Write_cmd(cmdbuf, false);
			sprintf(cmdbuf, "setencoding %d", ENCODING);
			Write_cmd(cmdbuf, false);

			sprintf(cmdbuf, "*dither %s", sdr_dither ? "on" : "off");
			Write_cmd(cmdbuf, true);
			sprintf(cmdbuf, "*random %s", sdr_random ? "on" : "off");
			Write_cmd(cmdbuf, true);
			sprintf(cmdbuf, "*preamp %s", sdr_preamp ? "on" : "off");
			Write_cmd(cmdbuf, true);
		}
#endif
#if QTRADIO_COMMANDS
		if (first_time) {
			// XXX commands used bt QtRadio used as reference
			sprintf(cmdbuf, "SetPWSMode %d", 0);
			Write_cmd(cmdbuf, false);
			sprintf(cmdbuf, "SetRXDCBlock %d", 0);
			Write_cmd(cmdbuf, false);
			sprintf(cmdbuf, "*setattenuator %d", 0);
			Write_cmd(cmdbuf, true);
		}
#endif // QTRADIO_COMMANDS

#if 1
		if (first_time) {
			// XXX this seems to be necessary, why?
			///sprintf(cmdbuf, "setFPS 0 0");
			sprintf(cmdbuf, "setFPS 960 15"); // XXX
			Write_cmd(cmdbuf, false);
		}
#endif
#if 1 /// XXX MUST be done with stock dspserver!
		if (first_time) {
			// fldigi requests 512 bytes per read.
			sprintf(cmdbuf, "startAudioStream 2048 %d 1 0", sample_frequency);
			Write_cmd(cmdbuf, true);
		}
#endif
		if (first_time) {
	  		LOG_INFO("####################################################################");
			LOG_INFO("Write_cmd(%s)", cmdbuf);
			LOG_INFO("Frequency: %8d, Audio Gain: %3d, RF Gain: %2.1f, Divisor: %5.1f",
				  sdr_frequency, sdr_audio_gain, sdr_rf_gain, sdr_divisor);
			LOG_INFO("####################################################################");
		}

		first_time = false;
	}
	catch (...) {
		throw;
	}

	return 0;
}

void SoundSDR::Close(unsigned dir)
{
#if 1
	// XXX XXX XXX
	usleep(10000);
#endif
}

void SoundSDR::getVersion()
{
	version = 1;
}

void SoundSDR::getCapabilities()
{
	/// XXX punt
	capability_mask = 0;
}

void SoundSDR::getFormats()
{
	// XXX is alaw same as s8?
	format_mask = (AFMT_S16_LE | AFMT_A_LAW | AFMT_S8);
}

void SoundSDR::Format(int format)
{
	switch (format) {
		case AFMT_S16_LE:
		case AFMT_S8:
		case AFMT_A_LAW:	// XXX is alaw same as s8?
			break;
		case AFMT_MU_LAW:
		case AFMT_IMA_ADPCM:
		case AFMT_S16_BE:
		case AFMT_U16_LE:
		case AFMT_U16_BE:
		case AFMT_MPEG:
		case AFMT_U8:
		default:
			//device_fd = -1;
			formatok = false;
			char buf[80];
			sprintf(buf, "Unsupported snd card format: %d", format);
			throw SndException(buf);
			return;
	}

	play_format = format;
	// XXX change between ALAW and PSM when that becomes programatic (currently compile-time)
	formatok = true;
}

void SoundSDR::Channels(int nuchannels)
{
	channels = nuchannels;
}

void SoundSDR::Sample_frequency(int frequency)
{
	///LOG_INFO("sample_frequency of %d requested", frequency);
	sample_frequency = frequency;

	if ((sample_frequency != 8000) && (sample_frequency != 11025))
		LOG_ERROR("sample_frequency UNAVAILABLE: %d", frequency);
}

int SoundSDR::BufferSize( int seconds )
{
	int bytes_per_channel = 0;
	switch (play_format) {
		case AFMT_MU_LAW:
		case AFMT_IMA_ADPCM:
		case AFMT_S16_BE:
		case AFMT_U16_LE:
		case AFMT_U16_BE:
		case AFMT_MPEG:
		case AFMT_U8:
		case AFMT_S8:
			bytes_per_channel = 0; /* format not supported by this program */
			break;
		case AFMT_S16_LE:
			bytes_per_channel = 2;
			break;
		case AFMT_A_LAW:
			bytes_per_channel = 1;
			break;
	}

      	return (seconds * sample_frequency * bytes_per_channel * channels);
}

// XXX XXX XXX Pacing!!!
#define USE_WAIT_TIME	0
#if USE_WAIT_TIME
#define WAIT_TIME_PER_WRITE	300
static int wait_time = 0;
#endif // USE_WAIT_TIME
bool SoundSDR::wait_till_finished()
{
#if 0
	if (ioctl(device_fd, SNDCTL_DSP_POST, (void*)1) == -1 )
		return false;
	if (ioctl(device_fd, SNDCTL_DSP_SYNC, (void*)0) == -1)
		return false; /* format (or ioctl()) not supported by device */
	return true; /* all sound has been played */
#elif USE_WAIT_TIME
	int t = wait_time * WAIT_TIME_PER_WRITE;
	fprintf(stderr, "wait_till_finished(%d)\n", t);
	usleep(t);
	wait_time = 0;

	return (true);	// all samples played
#else
	return (false);
#endif
}

bool SoundSDR::reset_device()
{
#if 0
	if (ioctl(device_fd, SNDCTL_DSP_RESET, 0) == -1) {
		device_fd = -1;
		return false; /* format (or ioctl()) not supported by device */
	}
	return 1; /* sounddevice has been reset */
#else
	return false; /* format (or ioctl()) not supported by device */
#endif
}

/*
 * XXX When fldigi calls this read it is only interested in sound packets.
 * Once we build the shim between fldigi and dspserver only sound packets will be
 * seen.  Till then we must cope with other (unwanted) data.
 *
 * called from:
 *
 * #define SCBLOCKSIZE 512
 * trx_trx_receive_loop()
 * {
 *  ...
 *			while (numread < SCBLOCKSIZE && trx_state == STATE_RX)
 *				numread += scard->Read(fbuf + numread, SCBLOCKSIZE numread);
 */
size_t SoundSDR::Read(float *buffer, size_t buffersize)
{
	if (socket_closed)
		return (0);

	// Get raw samples.
	while (havebuff == 0)						// raw buffer empty...
		ReadMore();						//  ...fill the raw buffer

	int available = 0;
	if (havebuff > buffersize) {					// no more than buffersize
		available = buffersize;
		havebuff -= available;
	}
	else {								// no fewer than available
		available = havebuff;
		havebuff = 0;
	}
	in_buffer = &src_buffer[haveoffset];
	offset_adjusted = false;

#if USE_SNDFILE
	XXX // this code has never been tested...
	for (int i = 0; i < available; i++)
		buffer[i] = in_buffer[i];

	if (capture) {
		write_file(ofCapture, buffer, available);
	}

	if (playback) {
		read_file(ifPlayback, buffer, available);
		if (progdefaults.EnableMixer)
			for (int i = 0; i < available; i++)
				buffer[i] *= progStatus.RcvMixer;

		haveoffset += available;

		return (available);
	}
#endif

	// Do receiver correction if necessary.
	if (rxppm != progdefaults.RX_corr) {
		rxppm = progdefaults.RX_corr;
		rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
		src_set_ratio(rx_src_state, 1.0/(1.0 + rxppm/1e6));
	}
	if (rxppm) {
		LOG_ERROR("> XXX >>>>>>>>>> UNEXPECTED CODE PATH, MAY NOT WORK! <<<<<<<<<<<<<<<<<<<<<<");
		available = resample(rx_src_state, rx_src_data, snd_buffer, available, buffersize);
	}

	// Upsample to 11025 if necessary.
	if (sample_frequency == 11025) {
		available = resample(up_src_state, up_src_data, up_buffer, available, buffersize);
	}

	// Move samples to the caller's buffer.
	for (int i = 0; i < available; i++)
		buffer[i] = in_buffer[i];

	// Adjust haveoffset to reflect samples used.
	if (offset_adjusted == false)
		haveoffset += available;

	return (available);
}

/*
 * XXX:EXTREMELY ugly code, cleanup!
 * Get sound samples from the SDR.
 * XXX: throws away everything BUT sound samples, FIX!
 */
#if 0
#define HEADER_SIZE	5
static uint8_t	header[2 * HEADER_SIZE];
static int	need = HEADER_SIZE;
#endif
#if 1
int SoundSDR::getHeader()
{
    int number = HEADER_SIZE - need;
    if (number)
        memmove(&header[0], &header[need], number);

    int numread = sdr_tcpip->recv(&header[number], need);
    if (numread == -1)
        throw SndException(errno);
#if 1
    if (need < 5)
        fprintf(stderr, "numread: %d, header: %02x %02x %02x %02x %02x\n",
                numread, header[0], header[1], header[2], header[3], header[4]);
#endif
    return (numread);
}

int SoundSDR::getData(int count)
{
    int numread;
    int total = 0;

    while (count) 
    {
        numread = sdr_tcpip->recv(&cbuff[total], count);
        if (numread == -1)
            throw SndException(errno);
        total += numread;
        count -= numread;
    }

    return (total);
}


size_t SoundSDR::ReadMore(void)
{
	if (socket_closed)
		return (0);

	// The binary sound sample header is HEADER_SIZE bytes long.
again:
	int numread = getHeader();

	int index;
        int end;
        for (index = 0; index < HEADER_SIZE; ++index) {
            int length;
            switch (header[index]) {
            case AUDIO_BUFFER:
            case SPECTRUM_BUFFER:
                if (index) {		// not 1st byte,
                    need = index;	// 
                    getHeader();	// refill header
                }
                if ((header[1] != HEADER_VERSION) || (header[2] != HEADER_SUBVERSION2)) {
                    need = 1;		// refill header starting at next byte
                    goto again;
                }
                length = ((int)header[3] << 8) & 0xFF00;
                length |= ((int)header[4] & 0x00FF);
                if (header[0] == SPECTRUM_BUFFER) {
                    numread = getData(length + 10);
                    need = HEADER_SIZE;
                    goto again;
                }
                numread = getData(length);
                goto done;

            case ANSWER_BUFFER:
                if (index) {		// not 1st byte,
                    need = index;	// 
                    getHeader();	// refill header
                }
                if ((!isdigit(header[1])) || (!isdigit(header[2]))) {
                    need = 1;		// refill header starting at next byte
                    goto again;
                }
                end = ((header[1] - '0') * 10) + (header[2] - '0') - 2;  // already have 2 in header
                numread = getData(end);
                cbuff[end] = '\0';
                LOG_INFO("answer: %c%c%s", header[3], header[4], cbuff);
                need = HEADER_SIZE;	// refill header
                goto again;

            case BANDSCOPE_BUFFER:
            case RTP_REPLY_BUFFER:
            default:
                need = 1;		// refill header starting at next byte
                goto again;
            }
        }

done:
#if USE_PCM
	static int rf_hi_clip = 0;
	static int rf_lo_clip = 0;

	int numsamples = numread / 2;		// 2 byte samples
	int16_t *ibuff = (int16_t *)&cbuff[0];
	for (int i = 0; i < numsamples; i++) {
		src_buffer[i] = ((float)ibuff[i] / ADJUSTED_RF);
		if ((src_buffer[i] > 1.0) || (src_buffer[i] < -1.0)) {
			if (src_buffer[i] > 1.0) {
				src_buffer[i] = 1.0;
				++rf_hi_clip;
				LOG_INFO("hi clip: %d", rf_hi_clip);
			}
			else if (src_buffer[i] < -1.0) {
				src_buffer[i] = -1.0;
				++rf_lo_clip;
				LOG_INFO("lo clip: %d", rf_lo_clip);
			}
		}
	}
#else // ALAW
	int numsamples = numread;		// 1 byte samples
	int8_t *ibuff = (int8_t *)(&cbuff[0]);
	for (int i = 0; i < numsamples; i++) {
		src_buffer[i] = (float)ibuff[i] / (sdr_divisor);
		if (((float)ibuff[i] > 128.0) || ((float)ibuff[i] < -128.0)) {
			LOG_INFO(">>>>>>>>>>>>>>>> sample out of range: %f", (float)ibuff[i]);
		}
		if ((src_buffer[i] > 1.0) || (src_buffer[i] < -1.0)) {
			LOG_INFO(">>>>>>>>>>>>>>>> sample out of range: %f", src_buffer[i]);
		}
	}
#endif // USE_PCM/ALAW

	havebuff = numsamples;
	haveoffset = 0;
	return (numsamples);
}

#else

size_t SoundSDR::ReadMore(void)
{
#if 1
	int index = 1;
        int totalRead = 0;
#endif
	if (socket_closed)
		return (0);

	static int rf_hi_clip = 0;
	//static int rf_lasthi_clip = 0;
	static int rf_lo_clip = 0;
	//static int rf_lastlo_clip = 0;

	// The binary sound sample header is 5 bytes long.
	// We start read at odd byte to insure that 2 byte
	// samples (ie int16_t) are aligned to even bytes.
#if 1
again:
#endif
	int numread = sdr_tcpip->recv(&cbuff[index], MAGIC_SIZE);
	if (numread == -1)
		throw SndException(errno);
#if 0
        fprintf(stderr, "more samples, numread: %d\n", numread);
#endif

	// XXX for now we ONLY process AUDIO buffers...
	switch (cbuff[1])
	{
	case AUDIO_BUFFER:
#if 1
            if ((totalRead += numread) < 4101) {
                index += numread;
                goto again;
            }
            else
                numread = totalRead;
#endif
		break;

	case ANSWER_BUFFER:
		LOG_INFO("answer buffer: %02x %02x %02x", cbuff[1], cbuff[2], cbuff[3]);
		return (-1);

	case SPECTRUM_BUFFER:
#if 0
		LOG_INFO("spectrum buffer: %d bytes", numread);
		if (numread != EXPECTED_SPECTRUM) {
	  		LOG_INFO("numread: %d, next data: %02x", numread, cbuff[EXPECTED_SPECTRUM + 1]);
		}
#endif
#if 1
		if (numread != EXPECTED_SPECTRUM) {
	  		LOG_INFO("numread: %d, next data: %02x", numread, cbuff[EXPECTED_SPECTRUM + 1]);
                        return (-1);
		}
        	goto again;
#else
		return (-1);
#endif

	case BANDSCOPE_BUFFER:
	case RTP_REPLY_BUFFER:
	default:
#if 1
            if ((totalRead += numread) >= 4101)
                break;
#endif
	  LOG_INFO("unexpected buffer: %d, %d bytes %d", cbuff[1], numread, totalRead);
		return (-1);
	}

	numread -= AUDIO_HEADER_SIZE;

#if USE_PCM
#if 0
        fprintf(stderr, "header: %02x %02x %02x %02x %02x\n",
                cbuff[1], cbuff[2], cbuff[3], cbuff[4], cbuff[5]);
#endif
	int numsamples = numread / 2;		// 2 byte samples
	int16_t *ibuff = (int16_t *)&cbuff[6];
	for (int i = 0; i < numsamples; i++) {
		src_buffer[i] = ((float)ibuff[i] / ADJUSTED_RF);
		if ((src_buffer[i] > 1.0) || (src_buffer[i] < -1.0)) {
			if (src_buffer[i] > 1.0) {
				src_buffer[i] = 1.0;
				++rf_hi_clip;
				LOG_INFO("hi clip: %d", rf_hi_clip);
			}
			else if (src_buffer[i] < -1.0) {
				src_buffer[i] = -1.0;
				++rf_lo_clip;
				LOG_INFO("lo clip: %d", rf_lo_clip);
			}
		}
	}
#else // ALAW
	int numsamples = numread;		// 1 byte samples
	int8_t *ibuff = (int8_t *)(&cbuff[6]);
	for (int i = 0; i < numsamples; i++) {
		src_buffer[i] = (float)ibuff[i] / (sdr_divisor);
		if (((float)ibuff[i] > 128.0) || ((float)ibuff[i] < -128.0)) {
			LOG_INFO(">>>>>>>>>>>>>>>> sample out of range: %f", (float)ibuff[i]);
		}
		if ((src_buffer[i] > 1.0) || (src_buffer[i] < -1.0)) {
			LOG_INFO(">>>>>>>>>>>>>>>> sample out of range: %f", src_buffer[i]);
		}
	}
#endif // USE_PCM/ALAW

	havebuff = numsamples;
	haveoffset = 0;
#if 0
        fprintf(stderr, "more samples: %d\n", numsamples);
#endif
	return (numsamples);
}
#endif // 1

int SoundSDR::resample(SRC_STATE *state, SRC_DATA *data,
		       float *out_buffer, int in_frames, int out_frames)
{
	data->data_in = in_buffer;			// source
	data->data_out = out_buffer;			// destination
	data->input_frames = in_frames;			// samples requested
	data->output_frames = out_frames;		// available buffer space
	data->end_of_input = 0;				// ?

	int err;
	if ((err = src_process(state, data)) != 0)
		throw SndException(src_strerror(err));

	// Adjust haveoffset/havebuff to reflect samples actually used.
	if (offset_adjusted == false) {
		haveoffset += data->input_frames_used;
		havebuff += (in_frames - data->input_frames_used);
		offset_adjusted = true;
	}

	int frames = data->output_frames_gen;		// samples in this intermediate buffer
	if ((frames > (int)out_frames)) {
		LOG_ERROR("* out_frames: %d, frames: %d, input frames used: %d, output frames generated: %d ",
			  (int)out_frames, (int)frames,
			  (int)data->input_frames_used, (int)data->output_frames_gen);
		throw SndException("resample: TOO MANY FRAMES");
	}

	// Make this the new input buffer.
	in_buffer = out_buffer;

	return (frames);
}

/*
 * XXX This routine is entirely UNTESTED!
 */
#if CAN_TRANSMIT
static uint8_t lin2Alaw(int16_t pcm_val);
#endif

#define maxsc 32000.0
size_t SoundSDR::Write(double *buf, size_t count)
{
	if (socket_closed)
		return (0);

	int retval = 0;
#if CAN_TRANSMIT
	uint8_t* wbuff;

#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, buf, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

	if (txppm != progdefaults.TX_corr) {
		txppm = progdefaults.TX_corr;
		tx_src_data->src_ratio = 1.0 + txppm/1e6;
		src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
	}

#define HDR_VALUE	"mic "	// the ' ' is necessary for dspserver to parse properly
#define HDR_LENGTH	4	// include the ' ' byte
#define MIC_ALAW_SIZE	58	// MIC_ALAW_SIZE
#define MSG_SIZE	64

	if (txppm == 0) {
		wbuff = new uint8_t[80];		// room for header + 1 channel + slop

		// XXX build the correct header in 1st n bytes...
		strncpy((char*)wbuff, HDR_VALUE, HDR_LENGTH);

		// XXX appears we HAVE to send in 64 byte chunks (verify!)
		// XXX even bigger issue is that nothing paces the output data... XXX
		int iCount = count;  // deal with signed vs. unsigned
		while (iCount > 0) {
			size_t i;
			for (i = 0; i < MIC_ALAW_SIZE; i++) {
				int16_t pcm = (int16_t)(buf[i] * maxsc);  // adjust +-1.0 to +-32000.0f
				wbuff[i + HDR_LENGTH] = lin2Alaw(pcm);
				if (iCount-- <= 0) {
					break;
				}
			}

			// XXX fill remaining portion with 0, need better solution!!!
			for (int ii = i; ii < MIC_ALAW_SIZE; ii++) {
				wbuff[ii + HDR_LENGTH] = 0;
			}

			// Encode the data count @ end of packet (VERY non-standard!)
			sprintf((char *)&wbuff[62], "%02ld", i);
			///fprintf(stderr, "sending %ld bytes\n", i);

			/// XXX dspserver client disconnection doesn't appear to stop txthread properly,
			/// see results at end of this file.
			///			fprintf(stderr, ".");

			retval = sdr_tcpip->send(wbuff, MSG_SIZE);
#if USE_WAIT_TIME
			++wait_time;
#else
			usleep(4000);	// XXX punt...
#endif
		}

		delete [] wbuff;
		if (retval == -1)
			throw SndException(errno);
	}
	else {
# if 1  // txppm
		retval = -1;
		throw SndException("SoundSDR::Write()::txppm UNTESTED!");
# else
		// XXX I don't think the following code from cloned from OSS::Write is correct.
		// XXX  1: the resample should be done BEFORE stuffing dup bytes into
		// XXX     into inbuf (done to create left & right channels?)
		// XXX  2: the resample use 'count' samples, but inbuf has 'count + 2' samples
		// XXX
		float *inbuf;
		inbuf = new float[2*count];				// room for 2 channels
		size_t bufsize;
		for (size_t i = 0; i < count; i++)
			inbuf[2*i] = inbuf[2*i+1] = buf[i];		// fill left + right channel
		tx_src_data->data_in = inbuf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = src_buffer;
		tx_src_data->output_frames = SND_BUF_LEN;
		tx_src_data->end_of_input = 0;

		retval = src_process(tx_src_state, tx_src_data);	// resample
		delete [] inbuf;
		if (retval != 0)
			throw SndException(src_strerror(retval));

		bufsize = tx_src_data->output_frames_gen;		// samples generated
		wbuff = new int16_t[2*bufsize];			// ???
		p = (uint8_t *)wbuff;

		for (size_t i = 0; i < 2*bufsize; i++)
			wbuff[i] = (int16_t)(src_buffer[i] * maxsc);
		int num2write = bufsize * 2 * sizeof(int16_t);

		retval = write(device_fd, p, num2write);
		delete [] wbuff;
		if (retval != num2write)
			throw SndException(errno);
		retval = count;
# endif // txppm
	}

#else // CAN_TRANSMIT

	retval = -1;
	throw SndException("SoundSDR::Write() UNTESTED!");

#endif // CAN_TRANSMIT

	return retval;
}


/*
 * XXX This routine not yet available.
 */
size_t SoundSDR::Write_stereo(double *bufleft, double *bufright, size_t count)
{
	int retval;
#if 1
	retval = -1;
	throw SndException("SoundSDR::Write_stereo() UNAVAILABLE!");
#else
	short int *wbuff;
	unsigned char *p;

#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, bufleft, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

	if (txppm != progdefaults.TX_corr) {
		txppm = progdefaults.TX_corr;
		tx_src_data->src_ratio = 1.0 + txppm/1e6;
		src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
	}

	if (txppm == 0) {
		wbuff = new short int[2*count];
		p = (unsigned char *)wbuff;
		for (size_t i = 0; i < count; i++) {
			if (progdefaults.ReverseAudio) {
				wbuff[2*i+1] = (short int)(bufleft[i] * maxsc);
				wbuff[2*i] = (short int)(bufright[i] * maxsc);
			} else {
				wbuff[2*i] = (short int)(bufleft[i] * maxsc);
				wbuff[2*i+1] = (short int)(bufright[i] * maxsc);
			}
		}
		count *= sizeof(short int);
		retval = write(device_fd, p, 2*count);
		delete [] wbuff;
		if (retval == -1)
			throw SndException(errno);
	}
	else {
		float *inbuf;
		inbuf = new float[2*count];
		size_t bufsize;
		for (size_t i = 0; i < count; i++) {
			if (progdefaults.ReverseAudio) {
				inbuf[2*i+1] = bufleft[i];
				inbuf[2*i] = bufright[i];
			} else {
				inbuf[2*i] = bufleft[i];
				inbuf[2*i+1] = bufright[i];
			}
		}
		tx_src_data->data_in = inbuf;
		tx_src_data->input_frames = count;
		tx_src_data->data_out = src_buffer;
		tx_src_data->output_frames = SND_BUF_LEN;
		tx_src_data->end_of_input = 0;

		retval = src_process(tx_src_state, tx_src_data);
		delete [] inbuf;
		if (retval != 0)
			throw SndException(src_strerror(retval));

		bufsize = tx_src_data->output_frames_gen;
		wbuff = new short int[2*bufsize];
		p = (unsigned char *)wbuff;

		for (size_t i = 0; i < 2*bufsize; i++)
			wbuff[i] = (short int)(src_buffer[i] * maxsc);

		int num2write = bufsize * 2 * sizeof(short int);
		retval = write(device_fd, p, num2write);
		delete [] wbuff;
		if (retval != num2write)
			throw SndException(errno);
		retval = count;
	}
#endif // 1

	return retval;
}


int SoundSDR::Write_cmd(const char *buf, bool answer)
{
	if (socket_closed)
		return (0);

	uint8_t buff[MAGIC_SIZE];
	int retval = sdr_tcpip->send(buf, SEND_BUFFER_SIZE);

	LOG_INFO(" %s", buf);

	if (answer == true) {
		sdr_tcpip->recv(buff, MAGIC_SIZE);
		if (buff[0] == '4') {
			int end = ((buff[1] - '0') * 10) + (buff[2] - '0') + 3;
			uint8_t b = buff[end];
			buff[end] = '\0';
			LOG_INFO("answer: %s", &buff[3]);
			buff[end] = b;
		}
	}

	return retval;
}

int SoundSDR::DialFrequency(int i)
{
	char cmdbuf[80];
	sdr_frequency = i;
#if 1
	if (!progdefaults.chkUSEHPSDRis) {
		// We only came to set sdr_frequency.
		return (0);
	}
#endif
#if defined(HYDRA_FREQUENCY_OFFSET)
	i += ((double)i * progdefaults.SdrPpmHos);
	fprintf(stderr, ">>>> setting magic frequency of %d from %d\n", i, sdr_frequency);
#endif // HYDRA_FREQUENCY_OFFSET
	sprintf(cmdbuf, "setFrequency %d", i);
	return Write_cmd(cmdbuf, false);
}

int SoundSDR::AudioGain(int i)
{
	char cmdbuf[80];
	sdr_audio_gain = i;
	sprintf(cmdbuf, "setRXOutputGain %d", sdr_audio_gain);
	return Write_cmd(cmdbuf, false);
}

void SoundSDR::Dither(bool b)
{
	if (b == true) {
		Write_cmd("*dither on");
		LOG_INFO("don");
	}
	else {
		Write_cmd("*dither off");
		LOG_INFO("doff");
	}

	sdr_dither = b;
}

void SoundSDR::Random(bool b)
{
	if (b == true) {
		Write_cmd("*random on");
		LOG_INFO("ron");
	}
	else {
		Write_cmd("*random off");
		LOG_INFO("roff");
	}
	sdr_random = b;
}

void SoundSDR::PreAmp(bool b)
{
	if (b == true) {
		Write_cmd("*preamp on");
		LOG_INFO("pon");
	}
	else {
		Write_cmd("*preamp off");
		LOG_INFO("poff");
	}

	sdr_preamp = b;
}

// 
void SoundSDR::PTT(bool ptt)
{
	if (ptt == true)
		Write_cmd("mox on");
	else
		Write_cmd("mox off");
}

#if CAN_TRANSMIT
/*
 * This source code is a product of Sun Microsystems, Inc. and is provided
 * for unrestricted use.  Users may copy or modify this source code without
 * charge.
 *
 * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING
 * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 * Sun source code is provided with no support and without any obligation on
 * the part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 *
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE
 * OR ANY PART THEREOF.
 *
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 *
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

/*
 * modified g711.c
 */

#define	QUANT_MASK	(0x0f)		// quantization field mask
#define	SEG_SHIFT	(4)		// left shift for segment number

static int16_t seg_aend[8] = {0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff};

static inline int16_t
search(int16_t val, int16_t* table, int16_t size)
{
	for (int16_t i = 0; i < size; i++) {
		if (val <= *table++)
			return (i);
	}

	return (size);
}

/*
 * lin2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
 *
 *		Linear Input Code	Compressed Code
 *	------------------------	---------------
 *	0000000wxyza			000wxyz
 *	0000001wxyza			001wxyz
 *	000001wxyzab			010wxyz
 *	00001wxyzabc			011wxyz
 *	0001wxyzabcd			100wxyz
 *	001wxyzabcde			101wxyz
 *	01wxyzabcdef			110wxyz
 *	1wxyzabcdefg			111wxyz
 *
 * For further information see John C. Bellamy's Digital Telephony, 1982,
 * John Wiley & Sons, pps 98-111 and 472-476.
 */

static uint8_t
lin2Alaw(int16_t pcm_val)	// 2's complement (16-bit range)
{
	int16_t mask;
	int16_t seg;
	uint8_t	aval;

	pcm_val >>= 3;

	if (pcm_val >= 0) {
		mask = 0xd5;		// sign (7th) bit = 1
	}
	else {
		mask = 0x55;		// sign bit = 0
		pcm_val = -pcm_val - 1;
	}

	// Convert the scaled magnitude to segment number.
	seg = search(pcm_val, seg_aend, 8);

	// Combine the sign, segment, and quantization bits.
	if (seg >= 8)		// out of range, return maximum value.
		return (uint8_t) (0x7f ^ mask);
	else {
		aval = (uint8_t) seg << SEG_SHIFT;
		if (seg < 2)
			aval |= (pcm_val >> 1) & QUANT_MASK;
		else
			aval |= (pcm_val >> seg) & QUANT_MASK;

		return (aval ^ mask);
	}
}
#endif // CAN_TRANSMIT

#endif // USE_SDR


////////////////////////////////////////////////////////////////////////////////////////


size_t SoundNull::Write(double* buf, size_t count)
{
#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, buf, count);
#endif

	if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
		return count;
	}

	MilliSleep((long)ceil((1e3 * count) / sample_frequency));

	return count;
}

size_t SoundNull::Write_stereo(double* bufleft, double* bufright, size_t count)
{
#if USE_SNDFILE
	if (generate)
		write_file(ofGenerate, bufleft, count);
#endif

	MilliSleep((long)ceil((1e3 * count) / sample_frequency));

	return count;
}

size_t SoundNull::Read(float *buf, size_t count)
{
#if USE_SNDFILE
	if (playback) {
		read_file(ifPlayback, buf, count);
	}
	else
#endif
		memset(buf, 0, count * sizeof(*buf));
#if USE_SNDFILE
	if (capture)
		write_file(ofCapture, buf, count);
#endif
	if (!bHighSpeed)
		MilliSleep((long)ceil((1e3 * count) / sample_frequency));

	return count;

}

#if RESULTS
[2015-01-14 13:50:44] RX0: client disconnection from 127.0.0.1:38384
[2015-01-14 13:50:44] RX0: client connection from 127.0.0.1:38385
[2015-01-14 13:50:44] encoding changed to 1
HARDWARE DIRECTED: message: '*dither on'
response to STAR message: [OK]
HARDWARE DIRECTED: message: '*random on'
response to STAR message: [OK]
HARDWARE DIRECTED: message: '*preamp on'
response to STAR message: [OK]
[2015-01-14 13:50:44] SetPWSMode 0
[2015-01-14 13:50:44] SetRXDCBlock 0
HARDWARE DIRECTED: message: '*setattenuator 0'
response to STAR message: [OK]
[2015-01-14 13:50:44] Spectrum fps set to = '15'
[2015-01-14 13:50:45] starting audio stream at rate 8000 channels 1 bufsize 2048 encoding 0 micEncoding 0
*** Error in `/home/src/ghpsdr/ghpsdr3-alex/trunk/src/dspserver/dspserver: free(): invalid size: 0x00007fbb1c030d10 ***
Aborted (core dumped)

Core was generated by `/home/src/ghpsdr/ghpsdr3-alex/trunk/src/dspserver/dspserver --receiver 0 --lo 0.
Program terminated with signal SIGABRT, Aborted.
(gdb) bt
#0  0x00007fbb2912dbb9 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007fbb29130fc8 in __GI_abort () at abort.c:89
#2  0x00007fbb2916ae14 in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7fbb29279668 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007fbb291770ee in malloc_printerr (ptr=<optimized out>, str=0x7fbb29275771 "free(): invalid size", action=1) at malloc.c:4996
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3840
#5  0x00000000004073d5 in tx_thread (arg=<optimized out>) at client.c:643
#6  0x00007fbb297cb182 in start_thread (arg=0x7fbb21ece700) at pthread_create.c:312
#7  0x00007fbb291f1fbd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

#endif // RESULTS
