diff --git a/channels/chan_usbradio.c b/channels/chan_usbradio.c new file mode 100644 index 0000000000000000000000000000000000000000..b3801aec4c6e4755317bb61eb05fc721d367691b --- /dev/null +++ b/channels/chan_usbradio.c @@ -0,0 +1,2813 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * Copyright (C) 2007, Jim Dixon + * + * Jim Dixon, WB6NIL <jim@lambdatel.com> + * Steve Henke, W9SH <w9sh@arrl.net> + * Based upon work by Mark Spencer <markster@digium.com> and Luigi Rizzo + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Channel driver for CM108 USB Cards with Radio Interface + * + * \author Jim Dixon <jim@lambdatel.com> + * \author Steve Henke <w9sh@arrl.net> + * + * \par See also + * \arg \ref Config_usbradio + * + * \ingroup channel_drivers + */ + +/*** MODULEINFO + <depend>alsa</depend> + <depend>usb</depend> + <defaultenabled>no</defaultenabled> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <stdio.h> +#include <ctype.h> +#include <math.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <sys/time.h> +#include <stdlib.h> +#include <errno.h> +#include <usb.h> +#include <alsa/asoundlib.h> + +#define CHAN_USBRADIO 1 + +#define DEBUG_USBRADIO 0 +#define DEBUG_CAPTURES 1 + +#define DEBUG_CAP_RX_OUT 0 +#define DEBUG_CAP_TX_OUT 0 + +#define DEBUG_FILETEST 0 + +#define RX_CAP_RAW_FILE "/tmp/rx_cap_in.pcm" +#define RX_CAP_TRACE_FILE "/tmp/rx_trace.pcm" +#define RX_CAP_OUT_FILE "/tmp/rx_cap_out.pcm" + +#define TX_CAP_RAW_FILE "/tmp/tx_cap_in.pcm" +#define TX_CAP_TRACE_FILE "/tmp/tx_trace.pcm" +#define TX_CAP_OUT_FILE "/tmp/tx_cap_out.pcm" + +#define MIXER_PARAM_MIC_PLAYBACK_SW "Mic Playback Switch" +#define MIXER_PARAM_MIC_PLAYBACK_VOL "Mic Playback Volume" +#define MIXER_PARAM_MIC_CAPTURE_SW "Mic Capture Switch" +#define MIXER_PARAM_MIC_CAPTURE_VOL "Mic Capture Volume" +#define MIXER_PARAM_MIC_BOOST "Auto Gain Control" +#define MIXER_PARAM_SPKR_PLAYBACK_SW "Speaker Playback Switch" +#define MIXER_PARAM_SPKR_PLAYBACK_VOL "Speaker Playback Volume" + +#include "./xpmr/xpmr.h" + +#if 0 +#define traceusb1(a) {printf a;} +#else +#define traceusb1(a) +#endif + +#if 0 +#define traceusb2(a) {printf a;} +#else +#define traceusb2(a) +#endif + +#ifdef __linux +#include <linux/soundcard.h> +#elif defined(__FreeBSD__) +#include <sys/soundcard.h> +#else +#include <soundcard.h> +#endif + +#include "asterisk/lock.h" +#include "asterisk/frame.h" +#include "asterisk/logger.h" +#include "asterisk/callerid.h" +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/endian.h" +#include "asterisk/stringfields.h" +#include "asterisk/abstract_jb.h" +#include "asterisk/musiconhold.h" +#include "asterisk/dsp.h" + +/* ringtones we use */ +#include "busy.h" +#include "ringtone.h" +#include "ring10.h" +#include "answer.h" + +#define C108_VENDOR_ID 0x0d8c +#define C108_PRODUCT_ID 0x000c +#define C108_HID_INTERFACE 3 + +#define HID_REPORT_GET 0x01 +#define HID_REPORT_SET 0x09 + +#define HID_RT_INPUT 0x01 +#define HID_RT_OUTPUT 0x02 + +/*! Global jitterbuffer configuration - by default, jb is disabled */ +static struct ast_jb_conf default_jbconf = +{ + .flags = 0, + .max_size = -1, + .resync_threshold = -1, + .impl = "", +}; +static struct ast_jb_conf global_jbconf; + +/* + * usbradio.conf parameters are +START_CONFIG + +[general] + ; General config options, with default values shown. + ; You should use one section per device, with [general] being used + ; for the device. + ; + ; + ; debug = 0x0 ; misc debug flags, default is 0 + + ; Set the device to use for I/O + ; devicenum = 0 + ; Set hardware type here + ; hdwtype=0 ; 0=limey, 1=sph + + ; rxboostset=0 ; no rx gain boost + ; rxctcssrelax=1 ; reduce talkoff from radios w/o CTCSS Tx HPF + ; rxctcssfreq=100.0 ; rx ctcss freq in floating point. must be in table + ; txctcssfreq=100.0 ; tx ctcss freq, any frequency permitted + + ; carrierfrom=dsp ;no,usb,usbinvert,dsp,vox + ; ctcssfrom=dsp ;no,usb,dsp + + ; rxdemod=flat ; input type from radio: no,speaker,flat + ; txprelim=yes ; output is pre-emphasised and limited + ; txtoctype=no ; no,phase,notone + + ; txmixa=composite ;no,voice,tone,composite,auxvoice + ; txmixb=no ;no,voice,tone,composite,auxvoice + + ; invertptt=0 + + ;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- + ; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an + ; USBRADIO channel. Defaults to "no". An enabled jitterbuffer will + ; be used only if the sending side can create and the receiving + ; side can not accept jitter. The USBRADIO channel can't accept jitter, + ; thus an enabled jitterbuffer on the receive USBRADIO side will always + ; be used if the sending side can create jitter. + + ; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds. + + ; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is + ; resynchronized. Useful to improve the quality of the voice, with + ; big jumps in/broken timestamps, usualy sent from exotic devices + ; and programs. Defaults to 1000. + + ; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of an USBRADIO + ; channel. Two implementations are currenlty available - "fixed" + ; (with size always equals to jbmax-size) and "adaptive" (with + ; variable size, actually the new jb of IAX2). Defaults to fixed. + + ; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no". + ;----------------------------------------------------------------------------------- + + +END_CONFIG + + */ + +/* + * Helper macros to parse config arguments. They will go in a common + * header file if their usage is globally accepted. In the meantime, + * we define them here. Typical usage is as below. + * Remember to open a block right before M_START (as it declares + * some variables) and use the M_* macros WITHOUT A SEMICOLON: + * + * { + * M_START(v->name, v->value) + * + * M_BOOL("dothis", x->flag1) + * M_STR("name", x->somestring) + * M_F("bar", some_c_code) + * M_END(some_final_statement) + * ... other code in the block + * } + * + * XXX NOTE these macros should NOT be replicated in other parts of asterisk. + * Likely we will come up with a better way of doing config file parsing. + */ +#define M_START(var, val) \ + char *__s = var; char *__val = val; +#define M_END(x) x; +#define M_F(tag, f) if (!strcasecmp((__s), tag)) { f; } else +#define M_BOOL(tag, dst) M_F(tag, (dst) = ast_true(__val) ) +#define M_UINT(tag, dst) M_F(tag, (dst) = strtoul(__val, NULL, 0) ) +#define M_STR(tag, dst) M_F(tag, ast_copy_string(dst, __val, sizeof(dst))) + +/* + * The following parameters are used in the driver: + * + * FRAME_SIZE the size of an audio frame, in samples. + * 160 is used almost universally, so you should not change it. + * + * FRAGS the argument for the SETFRAGMENT ioctl. + * Overridden by the 'frags' parameter in usbradio.conf + * + * Bits 0-7 are the base-2 log of the device's block size, + * bits 16-31 are the number of blocks in the driver's queue. + * There are a lot of differences in the way this parameter + * is supported by different drivers, so you may need to + * experiment a bit with the value. + * A good default for linux is 30 blocks of 64 bytes, which + * results in 6 frames of 320 bytes (160 samples). + * FreeBSD works decently with blocks of 256 or 512 bytes, + * leaving the number unspecified. + * Note that this only refers to the device buffer size, + * this module will then try to keep the lenght of audio + * buffered within small constraints. + * + * QUEUE_SIZE The max number of blocks actually allowed in the device + * driver's buffer, irrespective of the available number. + * Overridden by the 'queuesize' parameter in usbradio.conf + * + * Should be >=2, and at most as large as the hw queue above + * (otherwise it will never be full). + */ + +#define FRAME_SIZE 160 +#define QUEUE_SIZE 20 + +#if defined(__FreeBSD__) +#define FRAGS 0x8 +#else +#define FRAGS ( ( (6 * 5) << 16 ) | 0xc ) +#endif + +/* + * XXX text message sizes are probably 256 chars, but i am + * not sure if there is a suitable definition anywhere. + */ +#define TEXT_SIZE 256 + +#if 0 +#define TRYOPEN 1 /* try to open on startup */ +#endif +#define O_CLOSE 0x444 /* special 'close' mode for device */ +/* Which device to use */ +#if defined( __OpenBSD__ ) || defined( __NetBSD__ ) +#define DEV_DSP "/dev/audio" +#else +#define DEV_DSP "/dev/dsp" +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +static char *config = "usbradio.conf"; /* default config file */ +static char *config1 = "usbradio_tune.conf"; /* tune config file */ + +static FILE *frxcapraw = NULL, *frxcaptrace = NULL, *frxoutraw = NULL; +static FILE *ftxcapraw = NULL, *ftxcaptrace = NULL, *ftxoutraw = NULL; + +static int usbradio_debug; +#if 0 //maw asdf sph +static int usbradio_debug_level = 0; +#endif + +enum {RX_AUDIO_NONE,RX_AUDIO_SPEAKER,RX_AUDIO_FLAT}; +enum {CD_IGNORE,CD_XPMR_NOISE,CD_XPMR_VOX,CD_HID,CD_HID_INVERT}; +enum {SD_IGNORE,SD_HID,SD_HID_INVERT,SD_XPMR}; // no,external,externalinvert,software +enum {RX_KEY_CARRIER,RX_KEY_CARRIER_CODE}; +enum {TX_OUT_OFF,TX_OUT_VOICE,TX_OUT_LSD,TX_OUT_COMPOSITE,TX_OUT_AUX}; +enum {TOC_NONE,TOC_PHASE,TOC_NOTONE}; + +/* DECLARE STRUCTURES */ + +/* + * Each sound is made of 'datalen' samples of sound, repeated as needed to + * generate 'samplen' samples of data, then followed by 'silencelen' samples + * of silence. The loop is repeated if 'repeat' is set. + */ +struct sound { + int ind; + char *desc; + short *data; + int datalen; + int samplen; + int silencelen; + int repeat; +}; + +static struct sound sounds[] = { + { AST_CONTROL_RINGING, "RINGING", ringtone, sizeof(ringtone)/2, 16000, 32000, 1 }, + { AST_CONTROL_BUSY, "BUSY", busy, sizeof(busy)/2, 4000, 4000, 1 }, + { AST_CONTROL_CONGESTION, "CONGESTION", busy, sizeof(busy)/2, 2000, 2000, 1 }, + { AST_CONTROL_RING, "RING10", ring10, sizeof(ring10)/2, 16000, 32000, 1 }, + { AST_CONTROL_ANSWER, "ANSWER", answer, sizeof(answer)/2, 2200, 0, 0 }, + { -1, NULL, 0, 0, 0, 0 }, /* end marker */ +}; + + +/* + * descriptor for one of our channels. + * There is one used for 'default' values (from the [general] entry in + * the configuration file), and then one instance for each device + * (the default is cloned from [general], others are only created + * if the relevant section exists). + */ +struct chan_usbradio_pvt { + struct chan_usbradio_pvt *next; + + char *name; + /* + * cursound indicates which in struct sound we play. -1 means nothing, + * any other value is a valid sound, in which case sampsent indicates + * the next sample to send in [0..samplen + silencelen] + * nosound is set to disable the audio data from the channel + * (so we can play the tones etc.). + */ + int sndcmd[2]; /* Sound command pipe */ + int cursound; /* index of sound to send */ + int sampsent; /* # of sound samples sent */ + int nosound; /* set to block audio from the PBX */ + + int total_blocks; /* total blocks in the output device */ + int sounddev; + enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex; + i16 cdMethod; + int autoanswer; + int autohangup; + int hookstate; + unsigned int queuesize; /* max fragments in queue */ + unsigned int frags; /* parameter for SETFRAGMENT */ + + int warned; /* various flags used for warnings */ +#define WARN_used_blocks 1 +#define WARN_speed 2 +#define WARN_frag 4 + int w_errors; /* overfull in the write path */ + struct timeval lastopen; + + int overridecontext; + int mute; + + /* boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must + * be representable in 16 bits to avoid overflows. + */ +#define BOOST_SCALE (1<<9) +#define BOOST_MAX 40 /* slightly less than 7 bits */ + int boost; /* input boost, scaled by BOOST_SCALE */ + char devicenum; + int spkrmax; + int micmax; + + pthread_t sthread; + pthread_t hidthread; + + int stophid; + struct ast_channel *owner; + char ext[AST_MAX_EXTENSION]; + char ctx[AST_MAX_CONTEXT]; + char language[MAX_LANGUAGE]; + char cid_name[256]; /*XXX */ + char cid_num[256]; /*XXX */ + char mohinterpret[MAX_MUSICCLASS]; + + /* buffers used in usbradio_write, 2 per int by 2 channels by 6 times oversampling (48KS/s) */ + char usbradio_write_buf[FRAME_SIZE * 2 * 2 * 6]; + char usbradio_write_buf_1[FRAME_SIZE * 2 * 2* 6]; + + int usbradio_write_dst; + /* buffers used in usbradio_read - AST_FRIENDLY_OFFSET space for headers + * plus enough room for a full frame + */ + char usbradio_read_buf[FRAME_SIZE * (2 * 12) + AST_FRIENDLY_OFFSET]; + char usbradio_read_buf_8k[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET]; + int readpos; /* read position above */ + struct ast_frame read_f; /* returned by usbradio_read */ + + + char debuglevel; + char radioduplex; // + + char lastrx; + char rxhidsq; + char rxcarrierdetect; // status from pmr channel + char rxctcssdecode; // status from pmr channel + + char rxkeytype; + char rxkeyed; // indicates rx signal present + + char lasttx; + char txkeyed; // tx key request from upper layers + char txchankey; + char txtestkey; + + time_t lasthidtime; + struct ast_dsp *dsp; + + t_pmr_chan *pmrChan; + + char rxcpusaver; + char txcpusaver; + + char rxdemod; + float rxgain; + char rxcdtype; + char rxsdtype; + int rxsquelchadj; /* this copy needs to be here for initialization */ + char txtoctype; + + char txprelim; + float txctcssgain; + char txmixa; + char txmixb; + + char invertptt; + + char rxctcssrelax; + float rxctcssgain; + float rxctcssfreq; + float txctcssfreq; + + int rxmixerset; + int rxboostset; + float rxvoiceadj; + float rxctcssadj; + int txmixaset; + int txmixbset; + int txctcssadj; + + int hdwtype; + int hid_gpio_ctl; + int hid_gpio_ctl_loc; + int hid_io_cor; + int hid_io_cor_loc; + int hid_io_ctcss; + int hid_io_ctcss_loc; + int hid_io_ptt; + int hid_gpio_loc; + + struct { + unsigned rxcapraw:1; + unsigned txcapraw:1; + unsigned txcap2:1; + unsigned rxcap2:1; + }b; +}; + +// maw add additional defaults !!! +static struct chan_usbradio_pvt usbradio_default = { + .cursound = -1, + .sounddev = -1, + .duplex = M_UNSET, /* XXX check this */ + .autoanswer = 1, + .autohangup = 1, + .queuesize = QUEUE_SIZE, + .frags = FRAGS, + .ext = "s", + .ctx = "default", + .readpos = AST_FRIENDLY_OFFSET, /* start here on reads */ + .lastopen = { 0, 0 }, + .boost = BOOST_SCALE, +}; + +/* DECLARE FUNCTION PROTOTYPES */ + +static void store_txtoctype(struct chan_usbradio_pvt *o, char *s); +static int hidhdwconfig(struct chan_usbradio_pvt *o); +static int set_txctcss_level(struct chan_usbradio_pvt *o); +static void pmrdump(struct chan_usbradio_pvt *o); +static void mult_set(struct chan_usbradio_pvt *o); +static int mult_calc(int value); +static void mixer_write(struct chan_usbradio_pvt *o); +static void tune_rxinput(struct chan_usbradio_pvt *o); +static void tune_rxvoice(struct chan_usbradio_pvt *o); +static void tune_rxctcss(struct chan_usbradio_pvt *o); +static void tune_txoutput(struct chan_usbradio_pvt *o, int value); +static void tune_write(struct chan_usbradio_pvt *o); + +static char *usbradio_active; /* the active device */ + +static int setformat(struct chan_usbradio_pvt *o, int mode); + +static struct ast_channel *usbradio_request(const char *type, int format, void *data +, int *cause); +static int usbradio_digit_begin(struct ast_channel *c, char digit); +static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration); +static int usbradio_text(struct ast_channel *c, const char *text); +static int usbradio_hangup(struct ast_channel *c); +static int usbradio_answer(struct ast_channel *c); +static struct ast_frame *usbradio_read(struct ast_channel *chan); +static int usbradio_call(struct ast_channel *c, char *dest, int timeout); +static int usbradio_write(struct ast_channel *chan, struct ast_frame *f); +static int usbradio_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen); +static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); + +#if DEBUG_FILETEST == 1 +static int RxTestIt(struct chan_usbradio_pvt *o); +#endif + +static char tdesc[] = "USB (CM108) Radio Channel Driver"; + +static const struct ast_channel_tech usbradio_tech = { + .type = "Radio", + .description = tdesc, + .capabilities = AST_FORMAT_SLINEAR, + .requester = usbradio_request, + .send_digit_begin = usbradio_digit_begin, + .send_digit_end = usbradio_digit_end, + .send_text = usbradio_text, + .hangup = usbradio_hangup, + .answer = usbradio_answer, + .read = usbradio_read, + .call = usbradio_call, + .write = usbradio_write, + .indicate = usbradio_indicate, + .fixup = usbradio_fixup, +}; + +/* Call with: devnum: alsa major device number, param: ascii Formal +Parameter Name, val1, first or only value, val2 second value, or 0 +if only 1 value. Values: 0-99 (percent) or 0-1 for baboon. + +Note: must add -lasound to end of linkage */ + +static int amixer_max(int devnum,char *param) +{ +int rv,type; +char str[100]; +snd_hctl_t *hctl; +snd_ctl_elem_id_t *id; +snd_hctl_elem_t *elem; +snd_ctl_elem_info_t *info; + + sprintf(str,"hw:%d",devnum); + if (snd_hctl_open(&hctl, str, 0)) return(-1); + snd_hctl_load(hctl); + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, param); + elem = snd_hctl_find_elem(hctl, id); + if (!elem) + { + snd_hctl_close(hctl); + return(-1); + } + snd_ctl_elem_info_alloca(&info); + snd_hctl_elem_info(elem,info); + type = snd_ctl_elem_info_get_type(info); + rv = 0; + switch(type) + { + case SND_CTL_ELEM_TYPE_INTEGER: + rv = snd_ctl_elem_info_get_max(info); + break; + case SND_CTL_ELEM_TYPE_BOOLEAN: + rv = 1; + break; + } + snd_hctl_close(hctl); + return(rv); +} + +/* Call with: devnum: alsa major device number, param: ascii Formal +Parameter Name, val1, first or only value, val2 second value, or 0 +if only 1 value. Values: 0-99 (percent) or 0-1 for baboon. + +Note: must add -lasound to end of linkage */ + +static int setamixer(int devnum,char *param, int v1, int v2) +{ +int type; +char str[100]; +snd_hctl_t *hctl; +snd_ctl_elem_id_t *id; +snd_ctl_elem_value_t *control; +snd_hctl_elem_t *elem; +snd_ctl_elem_info_t *info; + + sprintf(str,"hw:%d",devnum); + if (snd_hctl_open(&hctl, str, 0)) return(-1); + snd_hctl_load(hctl); + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, param); + elem = snd_hctl_find_elem(hctl, id); + if (!elem) + { + snd_hctl_close(hctl); + return(-1); + } + snd_ctl_elem_info_alloca(&info); + snd_hctl_elem_info(elem,info); + type = snd_ctl_elem_info_get_type(info); + snd_ctl_elem_value_alloca(&control); + snd_ctl_elem_value_set_id(control, id); + switch(type) + { + case SND_CTL_ELEM_TYPE_INTEGER: + snd_ctl_elem_value_set_integer(control, 0, v1); + if (v2 > 0) snd_ctl_elem_value_set_integer(control, 1, v2); + break; + case SND_CTL_ELEM_TYPE_BOOLEAN: + snd_ctl_elem_value_set_integer(control, 0, (v1 != 0)); + break; + } + if (snd_hctl_elem_write(elem, control)) + { + snd_hctl_close(hctl); + return(-1); + } + snd_hctl_close(hctl); + return(0); +} + +static void hid_set_outputs(struct usb_dev_handle *handle, + unsigned char *outputs) +{ + usb_control_msg(handle, + USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, + HID_REPORT_SET, + 0 + (HID_RT_OUTPUT << 8), + C108_HID_INTERFACE, + (char*)outputs, 4, 5000); +} + +static void hid_get_inputs(struct usb_dev_handle *handle, + unsigned char *inputs) +{ + usb_control_msg(handle, + USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE, + HID_REPORT_GET, + 0 + (HID_RT_INPUT << 8), + C108_HID_INTERFACE, + (char*)inputs, 4, 5000); +} + +static struct usb_device *hid_device_init(void) +{ + struct usb_bus *usb_bus; + struct usb_device *dev; + usb_init(); + usb_find_busses(); + usb_find_devices(); + for (usb_bus = usb_busses; + usb_bus; + usb_bus = usb_bus->next) { + for (dev = usb_bus->devices; + dev; + dev = dev->next) { + if ((dev->descriptor.idVendor + == C108_VENDOR_ID) && + (dev->descriptor.idProduct + == C108_PRODUCT_ID)) + return dev; + } + } + return NULL; +} + +static int hidhdwconfig(struct chan_usbradio_pvt *o) +{ + if(o->hdwtype==1) //sphusb + { + o->hid_gpio_ctl = 0x08; /* set GPIO4 to output mode */ + o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */ + o->hid_io_cor = 4; /* GPIO3 is COR */ + o->hid_io_cor_loc = 1; /* GPIO3 is COR */ + o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */ + o->hid_io_ctcss_loc = 1; /* is GPIO 2 */ + o->hid_io_ptt = 8; /* GPIO 4 is PTT */ + o->hid_gpio_loc = 1; /* For ALL GPIO */ + } + else if(o->hdwtype==0) //dudeusb + { + o->hid_gpio_ctl = 0x0c; /* set GPIO 3 & 4 to output mode */ + o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */ + o->hid_io_cor = 2; /* VOLD DN is COR */ + o->hid_io_cor_loc = 0; /* VOL DN COR */ + o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */ + o->hid_io_ctcss_loc = 1; /* is GPIO 2 */ + o->hid_io_ptt = 4; /* GPIO 3 is PTT */ + o->hid_gpio_loc = 1; /* For ALL GPIO */ + } + else if(o->hdwtype==3) // custom version + { + o->hid_gpio_ctl = 0x0c; /* set GPIO 3 & 4 to output mode */ + o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */ + o->hid_io_cor = 2; /* VOLD DN is COR */ + o->hid_io_cor_loc = 0; /* VOL DN COR */ + o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */ + o->hid_io_ctcss_loc = 1; /* is GPIO 2 */ + o->hid_io_ptt = 4; /* GPIO 3 is PTT */ + o->hid_gpio_loc = 1; /* For ALL GPIO */ + } + + return 0; +} + + +static void *hidthread(void *arg) +{ + unsigned char buf[4],keyed; + char lastrx, txtmp; + struct usb_device *usb_dev; + struct usb_dev_handle *usb_handle; + struct chan_usbradio_pvt *o = (struct chan_usbradio_pvt *) arg; + + usb_dev = hid_device_init(); + if (usb_dev == NULL) { + ast_log(LOG_ERROR,"USB HID device not found\n"); + pthread_exit(NULL); + } + usb_handle = usb_open(usb_dev); + if (usb_handle == NULL) { + ast_log(LOG_ERROR,"Not able to open USB device\n"); + pthread_exit(NULL); + } + if (usb_claim_interface(usb_handle,C108_HID_INTERFACE) < 0) + { + if (usb_detach_kernel_driver_np(usb_handle,C108_HID_INTERFACE) < 0) { + ast_log(LOG_ERROR,"Not able to detach the USB device\n"); + pthread_exit(NULL); + } + if (usb_claim_interface(usb_handle,C108_HID_INTERFACE) < 0) { + ast_log(LOG_ERROR,"Not able to claim the USB device\n"); + pthread_exit(NULL); + } + } + memset(buf,0,sizeof(buf)); + buf[2] = o->hid_gpio_ctl; + buf[1] = 0; + hid_set_outputs(usb_handle,buf); + traceusb1(("hidthread: Starting normally!!\n")); + lastrx = 0; + while(!o->stophid) + { + buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl; + hid_get_inputs(usb_handle,buf); + keyed = !(buf[o->hid_io_cor_loc] & o->hid_io_cor); + if (keyed != o->rxhidsq) + { + if(o->debuglevel)printf("chan_usbradio() hidthread: update rxhidsq = %d\n",keyed); + o->rxhidsq=keyed; + } + + /* if change in tx stuff */ + txtmp=0; + if(o->txkeyed || o->txchankey || o->txtestkey || o->pmrChan->txPttOut) txtmp=1; + + if (o->lasttx != txtmp) + { + o->lasttx = txtmp; + if(o->debuglevel)printf("hidthread: tx set to %d\n",txtmp); + buf[o->hid_gpio_loc] = 0; + if (txtmp) buf[o->hid_gpio_loc] = o->hid_io_ptt; + buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl; + hid_set_outputs(usb_handle,buf); + } + + time(&o->lasthidtime); + usleep(50000); + } + buf[o->hid_gpio_loc] = 0; + if (o->invertptt) buf[o->hid_gpio_loc] = o->hid_io_ptt; + buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl; + hid_set_outputs(usb_handle,buf); + pthread_exit(0); +} + +/* + * returns a pointer to the descriptor with the given name + */ +static struct chan_usbradio_pvt *find_desc(char *dev) +{ + struct chan_usbradio_pvt *o = NULL; + + if (!dev) + ast_log(LOG_WARNING, "null dev\n"); + + for (o = usbradio_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next); + + if (!o) + ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--"); + + return o; +} + +/* + * split a string in extension-context, returns pointers to malloc'ed + * strings. + * If we do not have 'overridecontext' then the last @ is considered as + * a context separator, and the context is overridden. + * This is usually not very necessary as you can play with the dialplan, + * and it is nice not to need it because you have '@' in SIP addresses. + * Return value is the buffer address. + */ +#if 0 +static char *ast_ext_ctx(const char *src, char **ext, char **ctx) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + + if (ext == NULL || ctx == NULL) + return NULL; /* error */ + + *ext = *ctx = NULL; + + if (src && *src != '\0') + *ext = ast_strdup(src); + + if (*ext == NULL) + return NULL; + + if (!o->overridecontext) { + /* parse from the right */ + *ctx = strrchr(*ext, '@'); + if (*ctx) + *(*ctx)++ = '\0'; + } + + return *ext; +} +#endif + +/* + * Returns the number of blocks used in the audio output channel + */ +static int used_blocks(struct chan_usbradio_pvt *o) +{ + struct audio_buf_info info; + + if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) { + if (!(o->warned & WARN_used_blocks)) { + ast_log(LOG_WARNING, "Error reading output space\n"); + o->warned |= WARN_used_blocks; + } + return 1; + } + + if (o->total_blocks == 0) { + if (0) /* debugging */ + ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments); + o->total_blocks = info.fragments; + } + + return o->total_blocks - info.fragments; +} + +/* Write an exactly FRAME_SIZE sized frame */ +static int soundcard_writeframe(struct chan_usbradio_pvt *o, short *data) +{ + int res; + + if (o->sounddev < 0) + setformat(o, O_RDWR); + if (o->sounddev < 0) + return 0; /* not fatal */ + /* + * Nothing complex to manage the audio device queue. + * If the buffer is full just drop the extra, otherwise write. + * XXX in some cases it might be useful to write anyways after + * a number of failures, to restart the output chain. + */ + res = used_blocks(o); + if (res > o->queuesize) { /* no room to write a block */ + if (o->w_errors++ == 0 && (usbradio_debug & 0x4)) + ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors); + return 0; + } + o->w_errors = 0; + + return write(o->sounddev, ((void *) data), FRAME_SIZE * 2 * 12); +} + +/* + * Handler for 'sound writable' events from the sound thread. + * Builds a frame from the high level description of the sounds, + * and passes it to the audio device. + * The actual sound is made of 1 or more sequences of sound samples + * (s->datalen, repeated to make s->samplen samples) followed by + * s->silencelen samples of silence. The position in the sequence is stored + * in o->sampsent, which goes between 0 .. s->samplen+s->silencelen. + * In case we fail to write a frame, don't update o->sampsent. + */ +static void send_sound(struct chan_usbradio_pvt *o) +{ + short myframe[FRAME_SIZE]; + int ofs, l, start; + int l_sampsent = o->sampsent; + struct sound *s; + + if (o->cursound < 0) /* no sound to send */ + return; + + s = &sounds[o->cursound]; + + for (ofs = 0; ofs < FRAME_SIZE; ofs += l) { + l = s->samplen - l_sampsent; /* # of available samples */ + if (l > 0) { + start = l_sampsent % s->datalen; /* source offset */ + if (l > FRAME_SIZE - ofs) /* don't overflow the frame */ + l = FRAME_SIZE - ofs; + if (l > s->datalen - start) /* don't overflow the source */ + l = s->datalen - start; + bcopy(s->data + start, myframe + ofs, l * 2); + if (0) + ast_log(LOG_WARNING, "send_sound sound %d/%d of %d into %d\n", l_sampsent, l, s->samplen, ofs); + l_sampsent += l; + } else { /* end of samples, maybe some silence */ + static const short silence[FRAME_SIZE] = { 0, }; + + l += s->silencelen; + if (l > 0) { + if (l > FRAME_SIZE - ofs) + l = FRAME_SIZE - ofs; + bcopy(silence, myframe + ofs, l * 2); + l_sampsent += l; + } else { /* silence is over, restart sound if loop */ + if (s->repeat == 0) { /* last block */ + o->cursound = -1; + o->nosound = 0; /* allow audio data */ + if (ofs < FRAME_SIZE) /* pad with silence */ + bcopy(silence, myframe + ofs, (FRAME_SIZE - ofs) * 2); + } + l_sampsent = 0; + } + } + } + l = soundcard_writeframe(o, myframe); + if (l > 0) + o->sampsent = l_sampsent; /* update status */ +} + +static void *sound_thread(void *arg) +{ + char ign[4096]; + struct chan_usbradio_pvt *o = (struct chan_usbradio_pvt *) arg; + + /* + * Just in case, kick the driver by trying to read from it. + * Ignore errors - this read is almost guaranteed to fail. + */ + read(o->sounddev, ign, sizeof(ign)); + for (;;) { + fd_set rfds, wfds; + int maxfd, res; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(o->sndcmd[0], &rfds); + maxfd = o->sndcmd[0]; /* pipe from the main process */ + if (o->cursound > -1 && o->sounddev < 0) + setformat(o, O_RDWR); /* need the channel, try to reopen */ + else if (o->cursound == -1 && o->owner == NULL) + { + setformat(o, O_CLOSE); /* can close */ + } + if (o->sounddev > -1) { + if (!o->owner) { /* no one owns the audio, so we must drain it */ + FD_SET(o->sounddev, &rfds); + maxfd = MAX(o->sounddev, maxfd); + } + if (o->cursound > -1) { + FD_SET(o->sounddev, &wfds); + maxfd = MAX(o->sounddev, maxfd); + } + } + /* ast_select emulates linux behaviour in terms of timeout handling */ + res = ast_select(maxfd + 1, &rfds, &wfds, NULL, NULL); + if (res < 1) { + ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno)); + sleep(1); + continue; + } + if (FD_ISSET(o->sndcmd[0], &rfds)) { + /* read which sound to play from the pipe */ + int i, what = -1; + + read(o->sndcmd[0], &what, sizeof(what)); + for (i = 0; sounds[i].ind != -1; i++) { + if (sounds[i].ind == what) { + o->cursound = i; + o->sampsent = 0; + o->nosound = 1; /* block audio from pbx */ + break; + } + } + if (sounds[i].ind == -1) + ast_log(LOG_WARNING, "invalid sound index: %d\n", what); + } + if (o->sounddev > -1) { + if (FD_ISSET(o->sounddev, &rfds)) /* read and ignore errors */ + read(o->sounddev, ign, sizeof(ign)); + if (FD_ISSET(o->sounddev, &wfds)) + send_sound(o); + } + } + return NULL; /* Never reached */ +} + +/* + * reset and close the device if opened, + * then open and initialize it in the desired mode, + * trigger reads and writes so we can start using it. + */ +static int setformat(struct chan_usbradio_pvt *o, int mode) +{ + int fmt, desired, res, fd; + char device[100]; + + if (o->sounddev >= 0) { + ioctl(o->sounddev, SNDCTL_DSP_RESET, 0); + close(o->sounddev); + o->duplex = M_UNSET; + o->sounddev = -1; + } + if (mode == O_CLOSE) /* we are done */ + return 0; + if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000) + return -1; /* don't open too often */ + o->lastopen = ast_tvnow(); + strcpy(device,"/dev/dsp"); + if (o->devicenum) + sprintf(device,"/dev/dsp%d",o->devicenum); + fd = o->sounddev = open(device, mode | O_NONBLOCK); + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to re-open DSP device %d: %s\n", o->devicenum, strerror(errno)); + return -1; + } + if (o->owner) + o->owner->fds[0] = fd; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + fmt = AFMT_S16_LE; +#else + fmt = AFMT_S16_BE; +#endif + res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n"); + return -1; + } + switch (mode) { + case O_RDWR: + res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); + /* Check to see if duplex set (FreeBSD Bug) */ + res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt); + if (res == 0 && (fmt & DSP_CAP_DUPLEX)) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Console is full duplex\n"); + o->duplex = M_FULL; + }; + break; + case O_WRONLY: + o->duplex = M_WRITE; + break; + case O_RDONLY: + o->duplex = M_READ; + break; + } + + fmt = 1; + res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt); + if (res < 0) { + ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); + return -1; + } + fmt = desired = 48000; /* 8000 Hz desired */ + res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt); + + if (res < 0) { + ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); + return -1; + } + if (fmt != desired) { + if (!(o->warned & WARN_speed)) { + ast_log(LOG_WARNING, + "Requested %d Hz, got %d Hz -- sound may be choppy\n", + desired, fmt); + o->warned |= WARN_speed; + } + } + /* + * on Freebsd, SETFRAGMENT does not work very well on some cards. + * Default to use 256 bytes, let the user override + */ + if (o->frags) { + fmt = o->frags; + res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt); + if (res < 0) { + if (!(o->warned & WARN_frag)) { + ast_log(LOG_WARNING, + "Unable to set fragment size -- sound may be choppy\n"); + o->warned |= WARN_frag; + } + } + } + /* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */ + res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; + res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res); + /* it may fail if we are in half duplex, never mind */ + return 0; +} + +/* + * some of the standard methods supported by channels. + */ +static int usbradio_digit_begin(struct ast_channel *c, char digit) +{ + return 0; +} + +static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration) +{ + /* no better use for received digits than print them */ + ast_verbose(" << Console Received digit %c of duration %u ms >> \n", + digit, duration); + return 0; +} + +static int usbradio_text(struct ast_channel *c, const char *text) +{ + /* print received messages */ + ast_verbose(" << Console Received text %s >> \n", text); + return 0; +} + +/* Play ringtone 'x' on device 'o' */ +static void ring(struct chan_usbradio_pvt *o, int x) +{ + write(o->sndcmd[1], &x, sizeof(x)); +} + +/* + * handler for incoming calls. Either autoanswer, or start ringing + */ +static int usbradio_call(struct ast_channel *c, char *dest, int timeout) +{ + struct chan_usbradio_pvt *o = c->tech_pvt; + + time(&o->lasthidtime); + ast_pthread_create_background(&o->hidthread, NULL, hidthread, o); + ast_setstate(c, AST_STATE_UP); + return 0; +} + +/* + * remote side answered the phone + */ +static int usbradio_answer(struct ast_channel *c) +{ + struct chan_usbradio_pvt *o = c->tech_pvt; + + ast_setstate(c, AST_STATE_UP); + o->cursound = -1; + o->nosound = 0; + return 0; +} + +static int usbradio_hangup(struct ast_channel *c) +{ + struct chan_usbradio_pvt *o = c->tech_pvt; + + o->cursound = -1; + o->nosound = 0; + c->tech_pvt = NULL; + o->owner = NULL; + ast_module_unref(ast_module_info->self); + if (o->hookstate) { + if (o->autoanswer || o->autohangup) { + /* Assume auto-hangup too */ + o->hookstate = 0; + setformat(o, O_CLOSE); + } else { + /* Make congestion noise */ + ring(o, AST_CONTROL_CONGESTION); + } + } + o->stophid = 1; + pthread_join(o->hidthread,NULL); + return 0; +} + + +/* used for data coming from the network */ +static int usbradio_write(struct ast_channel *c, struct ast_frame *f) +{ + int src,datalen; + struct chan_usbradio_pvt *o = c->tech_pvt; + + traceusb2(("usbradio_write() o->nosound= %i\n",o->nosound)); //sph maw asdf + + /* Immediately return if no sound is enabled */ + if (o->nosound) + return 0; + /* Stop any currently playing sound */ + o->cursound = -1; + /* + * we could receive a block which is not a multiple of our + * FRAME_SIZE, so buffer it locally and write to the device + * in FRAME_SIZE chunks. + * Keep the residue stored for future use. + */ + + if(o->txkeyed||o->txtestkey)o->pmrChan->txPttIn=1; + else o->pmrChan->txPttIn=0; + + #if DEBUG_CAPTURES == 1 // to write input data to a file datalen=320 + if (ftxcapraw && o->b.txcapraw) + { + i16 i, tbuff[f->datalen]; + for(i=0;i<f->datalen;i+=2) + { + tbuff[i]= ((i16*)(f->data))[i/2]; + tbuff[i+1]= o->txkeyed*M_Q13; + } + fwrite(tbuff,2,f->datalen,ftxcapraw); + //fwrite(f->data,1,f->datalen,ftxcapraw); + } + #endif + + PmrTx(o->pmrChan,(i16*)f->data,(i16*)o->usbradio_write_buf_1); + + #if 0 // to write 48KS/s stereo data to a file + if (!ftxoutraw) ftxoutraw = fopen(TX_CAP_OUT_FILE,"w"); + if (ftxoutraw) fwrite(o->usbradio_write_buf_1,1,f->datalen * 2 * 6,ftxoutraw); + #endif + + #if DEBUG_CAPTURES == 1 + if (o->b.txcap2 && ftxcaptrace) fwrite((o->pmrChan->ptxDebug),1,FRAME_SIZE * 2 * 16,ftxcaptrace); + #endif + + src = 0; /* read position into f->data */ + datalen = f->datalen * 12; + while (src < datalen) { + /* Compute spare room in the buffer */ + int l = sizeof(o->usbradio_write_buf) - o->usbradio_write_dst; + + if (datalen - src >= l) { /* enough to fill a frame */ + memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l); + soundcard_writeframe(o, (short *) o->usbradio_write_buf); + src += l; + o->usbradio_write_dst = 0; + } else { /* copy residue */ + l = datalen - src; + memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l); + src += l; /* but really, we are done */ + o->usbradio_write_dst += l; + } + } + return 0; +} + +static struct ast_frame *usbradio_read(struct ast_channel *c) +{ + int res; + struct chan_usbradio_pvt *o = c->tech_pvt; + struct ast_frame *f = &o->read_f,*f1; + struct ast_frame wf = { AST_FRAME_CONTROL }; + time_t now; + + traceusb2(("usbradio_read()\n")); //sph maw asdf + + if (o->lasthidtime) + { + time(&now); + if ((now - o->lasthidtime) > 3) + { + ast_log(LOG_ERROR,"HID process has died or something!!\n"); + return NULL; + } + } + if (o->lastrx && (!o->rxkeyed)) + { + o->lastrx = 0; + wf.subclass = AST_CONTROL_RADIO_UNKEY; + ast_queue_frame(o->owner, &wf); + } else if ((!o->lastrx) && (o->rxkeyed)) + { + o->lastrx = 1; + wf.subclass = AST_CONTROL_RADIO_KEY; + ast_queue_frame(o->owner, &wf); + } + /* XXX can be simplified returning &ast_null_frame */ + /* prepare a NULL frame in case we don't have enough data to return */ + bzero(f, sizeof(struct ast_frame)); + f->frametype = AST_FRAME_NULL; + f->src = usbradio_tech.type; + + res = read(o->sounddev, o->usbradio_read_buf + o->readpos, + sizeof(o->usbradio_read_buf) - o->readpos); + if (res < 0) /* audio data not ready, return a NULL frame */ + return f; + + o->readpos += res; + if (o->readpos < sizeof(o->usbradio_read_buf)) /* not enough samples */ + return f; + + if (o->mute) + return f; + + #if DEBUG_CAPTURES == 1 + if (o->b.rxcapraw && frxcapraw) fwrite((o->usbradio_read_buf + AST_FRIENDLY_OFFSET),1,FRAME_SIZE * 2 * 2 * 6,frxcapraw); + #endif + + #if 1 + PmrRx( o->pmrChan, + (i16 *)(o->usbradio_read_buf + AST_FRIENDLY_OFFSET), + (i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET)); + + #else + static FILE *hInput; + i16 iBuff[FRAME_SIZE*2*6]; + + o->pmrChan->b.rxCapture=1; + + if(!hInput) + { + hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm","r"); + if(!hInput) + { + printf(" Input Data File Not Found.\n"); + return 0; + } + } + + if(0==fread((void *)iBuff,2,FRAME_SIZE*2*6,hInput))exit; + + PmrRx( o->pmrChan, + (i16 *)iBuff, + (i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET)); + + #endif + + #if 0 + if (!frxoutraw) frxoutraw = fopen(RX_CAP_OUT_FILE,"w"); + if (frxoutraw) fwrite((o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET),1,FRAME_SIZE * 2,frxoutraw); + #endif + + #if DEBUG_CAPTURES == 1 + if (frxcaptrace && o->b.rxcap2) fwrite((o->pmrChan->prxDebug),1,FRAME_SIZE * 2 * 16,frxcaptrace); + #endif + + if(o->rxcdtype==CD_HID && (o->pmrChan->rxExtCarrierDetect!=o->rxhidsq)) + o->pmrChan->rxExtCarrierDetect=o->rxhidsq; + if(o->rxcdtype==CD_HID_INVERT && (o->pmrChan->rxExtCarrierDetect==o->rxhidsq)) + o->pmrChan->rxExtCarrierDetect=!o->rxhidsq; + + if( (o->rxcdtype==CD_HID && o->rxhidsq) || + (o->rxcdtype==CD_HID_INVERT && !o->rxhidsq) || + (o->rxcdtype==CD_XPMR_NOISE && o->pmrChan->rxCarrierDetect) || + (o->rxcdtype==CD_XPMR_VOX && o->pmrChan->rxCarrierDetect) + ) + { + res=1; + } + else res=0; + + if(res!=o->rxcarrierdetect) + { + o->rxcarrierdetect=res; + if(o->debuglevel)printf("rxcarrierdetect = %i\n",res); + } + + if(o->pmrChan->rxCtcss->decode!=o->rxctcssdecode) + { + if(o->debuglevel)printf("rxctcssdecode = %i\n",o->pmrChan->rxCtcss->decode); + o->rxctcssdecode=o->pmrChan->rxCtcss->decode; + } + + if ( + ( o->rxctcssfreq && (o->rxctcssdecode == o->pmrChan->rxCtcssIndex)) || + ( !o->rxctcssfreq && o->rxcarrierdetect) + ) + { + o->rxkeyed = 1; + } + else o->rxkeyed = 0; + + + o->readpos = AST_FRIENDLY_OFFSET; /* reset read pointer for next frame */ + if (c->_state != AST_STATE_UP) /* drop data if frame is not up */ + return f; + /* ok we can build and deliver the frame to the caller */ + f->frametype = AST_FRAME_VOICE; + f->subclass = AST_FORMAT_SLINEAR; + f->samples = FRAME_SIZE; + f->datalen = FRAME_SIZE * 2; + f->data = o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET; + if (o->boost != BOOST_SCALE) { /* scale and clip values */ + int i, x; + int16_t *p = (int16_t *) f->data; + for (i = 0; i < f->samples; i++) { + x = (p[i] * o->boost) / BOOST_SCALE; + if (x > 32767) + x = 32767; + else if (x < -32768) + x = -32768; + p[i] = x; + } + } + + f->offset = AST_FRIENDLY_OFFSET; + if (o->dsp) + { + f1 = ast_dsp_process(c,o->dsp,f); + if ((f1->frametype == AST_FRAME_DTMF_END) || + (f1->frametype == AST_FRAME_DTMF_BEGIN)) + { + if ((f1->subclass == 'm') || (f1->subclass == 'u')) + f1->frametype = AST_FRAME_DTMF_BEGIN; + if (f1->frametype == AST_FRAME_DTMF_END) + ast_log(LOG_NOTICE,"Got DTMF char %c\n",f1->subclass); + return(f1); + } + } + return f; +} + +static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) +{ + struct chan_usbradio_pvt *o = newchan->tech_pvt; + ast_log(LOG_WARNING,"usbradio_fixup()\n"); + o->owner = newchan; + return 0; +} + +static int usbradio_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen) +{ + struct chan_usbradio_pvt *o = c->tech_pvt; + int res = -1; + + switch (cond) { + case AST_CONTROL_BUSY: + case AST_CONTROL_CONGESTION: + case AST_CONTROL_RINGING: + res = cond; + break; + + case -1: + o->cursound = -1; + o->nosound = 0; /* when cursound is -1 nosound must be 0 */ + return 0; + + case AST_CONTROL_VIDUPDATE: + res = -1; + break; + case AST_CONTROL_HOLD: + ast_verbose(" << Console Has Been Placed on Hold >> \n"); + ast_moh_start(c, data, o->mohinterpret); + break; + case AST_CONTROL_UNHOLD: + ast_verbose(" << Console Has Been Retrieved from Hold >> \n"); + ast_moh_stop(c); + break; + case AST_CONTROL_PROCEEDING: + ast_verbose(" << Call Proceeding... >> \n"); + ast_moh_stop(c); + break; + case AST_CONTROL_PROGRESS: + ast_verbose(" << Call Progress... >> \n"); + ast_moh_stop(c); + break; + case AST_CONTROL_RADIO_KEY: + o->txkeyed = 1; + if(o->debuglevel)ast_verbose(" << Radio Transmit On. >> \n"); + break; + case AST_CONTROL_RADIO_UNKEY: + o->txkeyed = 0; + if(o->debuglevel)ast_verbose(" << Radio Transmit Off. >> \n"); + break; + default: + ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name); + return -1; + } + + if (res > -1) + ring(o, res); + + return 0; +} + +/* + * allocate a new channel. + */ +static struct ast_channel *usbradio_new(struct chan_usbradio_pvt *o, char *ext, char *ctx, int state) +{ + struct ast_channel *c; + char device[100]; + + strcpy(device,"dsp"); + if (o->devicenum) sprintf(device,"dsp%d",o->devicenum); + c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, 0, "usbRadio/%s", device); + if (c == NULL) + return NULL; + c->tech = &usbradio_tech; + if (o->sounddev < 0) + setformat(o, O_RDWR); + c->fds[0] = o->sounddev; /* -1 if device closed, override later */ + c->nativeformats = AST_FORMAT_SLINEAR; + c->readformat = AST_FORMAT_SLINEAR; + c->writeformat = AST_FORMAT_SLINEAR; + c->tech_pvt = o; + + if (!ast_strlen_zero(o->language)) + ast_string_field_set(c, language, o->language); + /* Don't use ast_set_callerid() here because it will + * generate a needless NewCallerID event */ + c->cid.cid_num = ast_strdup(o->cid_num); + c->cid.cid_ani = ast_strdup(o->cid_num); + c->cid.cid_name = ast_strdup(o->cid_name); + if (!ast_strlen_zero(ext)) + c->cid.cid_dnid = ast_strdup(ext); + + o->owner = c; + ast_module_ref(ast_module_info->self); + ast_jb_configure(c, &global_jbconf); + if (state != AST_STATE_DOWN) { + if (ast_pbx_start(c)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", c->name); + ast_hangup(c); + o->owner = c = NULL; + /* XXX what about the channel itself ? */ + /* XXX what about usecnt ? */ + } + } + + return c; +} + +static struct ast_channel *usbradio_request(const char *type, int format, void *data, int *cause) +{ + struct ast_channel *c; + struct chan_usbradio_pvt *o = find_desc(data); + + if (0) + { + ast_log(LOG_WARNING, "usbradio_request ty <%s> data 0x%p <%s>\n", type, data, (char *) data); + } + if (o == NULL) { + ast_log(LOG_NOTICE, "Device %s not found\n", (char *) data); + /* XXX we could default to 'dsp' perhaps ? */ + return NULL; + } + if ((format & AST_FORMAT_SLINEAR) == 0) { + ast_log(LOG_NOTICE, "Format 0x%x unsupported\n", format); + return NULL; + } + if (o->owner) { + ast_log(LOG_NOTICE, "Already have a call (chan %p) on the usb channel\n", o->owner); + *cause = AST_CAUSE_BUSY; + return NULL; + } + c = usbradio_new(o, NULL, NULL, AST_STATE_DOWN); + if (c == NULL) { + ast_log(LOG_WARNING, "Unable to create new usb channel\n"); + return NULL; + } + return c; +} + +static int console_key(int fd, int argc, char *argv[]) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + + if (argc != 2) + return RESULT_SHOWUSAGE; + o->txtestkey = 1; + return RESULT_SUCCESS; +} + +static int console_unkey(int fd, int argc, char *argv[]) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + + if (argc != 2) + return RESULT_SHOWUSAGE; + o->txtestkey = 0; + + return RESULT_SUCCESS; +} + +static int radio_tune(int fd, int argc, char *argv[]) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + int i=0; + + if ((argc < 2) || (argc > 4)) + return RESULT_SHOWUSAGE; + + if (argc == 2) /* just show stuff */ + { + ast_cli(fd,"Output A is currently set to "); + if(o->txmixa==TX_OUT_COMPOSITE)ast_cli(fd,"composite.\n"); + else if (o->txmixa==TX_OUT_VOICE)ast_cli(fd,"voice.\n"); + else if (o->txmixa==TX_OUT_LSD)ast_cli(fd,"tone.\n"); + else if (o->txmixa==TX_OUT_AUX)ast_cli(fd,"auxvoice.\n"); + else ast_cli(fd,"off.\n"); + + ast_cli(fd,"Output B is currently set to "); + if(o->txmixb==TX_OUT_COMPOSITE)ast_cli(fd,"composite.\n"); + else if (o->txmixb==TX_OUT_VOICE)ast_cli(fd,"voice.\n"); + else if (o->txmixb==TX_OUT_LSD)ast_cli(fd,"tone.\n"); + else if (o->txmixb==TX_OUT_AUX)ast_cli(fd,"auxvoice.\n"); + else ast_cli(fd,"off.\n"); + + ast_cli(fd,"Tx Voice Level currently set to %d\n",o->txmixaset); + ast_cli(fd,"Tx Tone Level currently set to %d\n",o->txctcssadj); + ast_cli(fd,"Rx Squelch currently set to %d\n",o->rxsquelchadj); + return RESULT_SHOWUSAGE; + } + + if (!strcasecmp(argv[2],"rxnoise")) tune_rxinput(o); + else if (!strcasecmp(argv[2],"rxvoice")) tune_rxvoice(o); + else if (!strcasecmp(argv[2],"rxtone")) tune_rxctcss(o); + else if (!strcasecmp(argv[2],"rxsquelch")) + { + if (argc == 3) + { + ast_cli(fd,"Current Signal Strength is %d\n",((32767-o->pmrChan->rxRssi)*1000/32767)); + ast_cli(fd,"Current Squelch setting is %d\n",o->rxsquelchadj); + //ast_cli(fd,"Current Raw RSSI is %d\n",o->pmrChan->rxRssi); + //ast_cli(fd,"Current (real) Squelch setting is %d\n",*(o->pmrChan->prxSquelchAdjust)); + } else { + i = atoi(argv[3]); + if ((i < 0) || (i > 999)) return RESULT_SHOWUSAGE; + ast_cli(fd,"Changed Squelch setting to %d\n",i); + o->rxsquelchadj = i; + *(o->pmrChan->prxSquelchAdjust)= ((999 - i) * 32767) / 1000; + } + } + else if (!strcasecmp(argv[2],"txvoice")) { + i = 0; + + if( (o->txmixa!=TX_OUT_VOICE) && (o->txmixb!=TX_OUT_VOICE) && + (o->txmixa!=TX_OUT_COMPOSITE) && (o->txmixb!=TX_OUT_COMPOSITE) + ) + { + ast_log(LOG_ERROR,"No txvoice output configured.\n"); + } + else if (argc == 3) + { + if((o->txmixa==TX_OUT_VOICE)||(o->txmixa==TX_OUT_COMPOSITE)) + ast_cli(fd,"Current txvoice setting on Channel A is %d\n",o->txmixaset); + else + ast_cli(fd,"Current txvoice setting on Channel B is %d\n",o->txmixbset); + } + else + { + i = atoi(argv[3]); + if ((i < 0) || (i > 999)) return RESULT_SHOWUSAGE; + + if((o->txmixa==TX_OUT_VOICE)||(o->txmixa==TX_OUT_COMPOSITE)) + { + o->txmixaset=i; + ast_cli(fd,"Changed txvoice setting on Channel A to %d\n",o->txmixaset); + } + else + { + o->txmixbset=i; + ast_cli(fd,"Changed txvoice setting on Channel B to %d\n",o->txmixbset); + } + mixer_write(o); + mult_set(o); + ast_cli(fd,"Changed Tx Voice Output setting to %d\n",i); + } + tune_txoutput(o,i); + } + else if (!strcasecmp(argv[2],"auxvoice")) { + i = 0; + if( (o->txmixa!=TX_OUT_AUX) && (o->txmixb!=TX_OUT_AUX)) + { + ast_log(LOG_WARNING,"No auxvoice output configured.\n"); + } + else if (argc == 3) + { + if(o->txmixa==TX_OUT_AUX) + ast_cli(fd,"Current auxvoice setting on Channel A is %d\n",o->txmixaset); + else + ast_cli(fd,"Current auxvoice setting on Channel B is %d\n",o->txmixbset); + } + else + { + i = atoi(argv[3]); + if ((i < 0) || (i > 999)) return RESULT_SHOWUSAGE; + if(o->txmixa==TX_OUT_AUX) + { + o->txmixbset=i; + ast_cli(fd,"Changed auxvoice setting on Channel A to %d\n",o->txmixaset); + } + else + { + o->txmixbset=i; + ast_cli(fd,"Changed auxvoice setting on Channel B to %d\n",o->txmixbset); + } + mixer_write(o); + mult_set(o); + } + //tune_auxoutput(o,i); + } + else if (!strcasecmp(argv[2],"txtone")) + { + if (argc == 3) + ast_cli(fd,"Current Tx CTCSS modulation setting = %d\n",o->txctcssadj); + else + { + i = atoi(argv[3]); + if ((i < 0) || (i > 999)) return RESULT_SHOWUSAGE; + o->txctcssadj = i; + set_txctcss_level(o); + ast_cli(fd,"Changed Tx CTCSS modulation setting to %i\n",i); + } + o->txtestkey=1; + usleep(5000000); + o->txtestkey=0; + } + else if (!strcasecmp(argv[2],"dump")) pmrdump(o); + else if (!strcasecmp(argv[2],"nocap")) + { + ast_cli(fd,"File capture (trace) was rx=%d tx=%d and now off.\n",o->b.rxcap2,o->b.txcap2); + ast_cli(fd,"File capture (raw) was rx=%d tx=%d and now off.\n",o->b.rxcapraw,o->b.txcapraw); + o->b.rxcapraw=o->b.txcapraw=o->b.rxcap2=o->b.txcap2=o->pmrChan->b.rxCapture=o->pmrChan->b.txCapture=0; + if (frxcapraw) { fclose(frxcapraw); frxcapraw = NULL; } + if (frxcaptrace) { fclose(frxcaptrace); frxcaptrace = NULL; } + if (frxoutraw) { fclose(frxoutraw); frxoutraw = NULL; } + if (ftxcapraw) { fclose(ftxcapraw); ftxcapraw = NULL; } + if (ftxcaptrace) { fclose(ftxcaptrace); ftxcaptrace = NULL; } + if (ftxoutraw) { fclose(ftxoutraw); ftxoutraw = NULL; } + } + else if (!strcasecmp(argv[2],"rxtracecap")) + { + if (!frxcaptrace) frxcaptrace= fopen(RX_CAP_TRACE_FILE,"w"); + ast_cli(fd,"Trace rx on.\n"); + o->b.rxcap2=o->pmrChan->b.rxCapture=1; + } + else if (!strcasecmp(argv[2],"txtracecap")) + { + if (!ftxcaptrace) ftxcaptrace= fopen(TX_CAP_TRACE_FILE,"w"); + ast_cli(fd,"Trace tx on.\n"); + o->b.txcap2=o->pmrChan->b.txCapture=1; + } + else if (!strcasecmp(argv[2],"rxcap")) + { + if (!frxcapraw) frxcapraw = fopen(RX_CAP_RAW_FILE,"w"); + ast_cli(fd,"cap rx raw on.\n"); + o->b.rxcapraw=1; + } + else if (!strcasecmp(argv[2],"txcap")) + { + if (!ftxcapraw) ftxcapraw = fopen(TX_CAP_RAW_FILE,"w"); + ast_cli(fd,"cap tx raw on.\n"); + o->b.txcapraw=1; + } + else if (!strcasecmp(argv[2],"save")) + { + tune_write(o); + ast_cli(fd,"Saved radio tuning settings to usbradio_tune.conf\n"); + } + else return RESULT_SHOWUSAGE; + return RESULT_SUCCESS; +} + +/* + set transmit ctcss modulation level + adjust mixer output or internal gain depending on output type + setting range is 0.0 to 0.9 +*/ +static int set_txctcss_level(struct chan_usbradio_pvt *o) +{ + if (o->txmixa == TX_OUT_LSD) + { + o->txmixaset=(151*o->txctcssadj) / 1000; + mixer_write(o); + mult_set(o); + } + else if (o->txmixb == TX_OUT_LSD) + { + o->txmixbset=(151*o->txctcssadj) / 1000; + mixer_write(o); + mult_set(o); + } + else + { + *o->pmrChan->ptxCtcssAdjust=(o->txctcssadj * M_Q8) / 1000; + } + return 0; +} +/* + CLI debugging on and off +*/ +static int radio_set_debug(int fd, int argc, char *argv[]) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + + o->debuglevel=1; + ast_cli(fd,"usbradio debug on.\n"); + + return RESULT_SUCCESS; +} + +static int radio_set_debug_off(int fd, int argc, char *argv[]) +{ + struct chan_usbradio_pvt *o = find_desc(usbradio_active); + + o->debuglevel=0; + ast_cli(fd,"usbradio debug off.\n"); + return RESULT_SUCCESS; +} + +static char key_usage[] = + "Usage: radio key\n" + " Simulates COR active.\n"; + +static char unkey_usage[] = + "Usage: radio unkey\n" + " Simulates COR un-active.\n"; + +/* +radio tune 6 3000 measured tx value +*/ +static char radio_tune_usage[] = + "Usage: radio tune <function>\n" + " rxnoise\n" + " rxvoice\n" + " rxtone\n" + " rxsquelch [newsetting]\n" + " txvoice [newsetting]\n" + " txtone [newsetting]\n" + " auxvoice [newsetting]\n" + " save (settings to tuning file)\n" + "\n All [newsetting]'s are values 0-999\n\n"; + +static struct ast_cli_entry cli_usbradio[] = { + { { "radio", "key", NULL }, + console_key, "Simulate Rx Signal Present", + key_usage, NULL, NULL}, + + { { "radio", "unkey", NULL }, + console_unkey, "Simulate Rx Signal Lusb", + unkey_usage, NULL, NULL }, + + { { "radio", "tune", NULL }, + radio_tune, "Radio Tune", + radio_tune_usage, NULL, NULL }, + + { { "radio", "set", "debug", NULL }, + radio_set_debug, "Radio Debug", + radio_tune_usage, NULL, NULL }, + + { { "radio", "set", "debug", "off", NULL }, + radio_set_debug_off, "Radio Debug", + radio_tune_usage, NULL, NULL }, +}; + +/* + * store the callerid components + */ +#if 0 +static void store_callerid(struct chan_usbradio_pvt *o, char *s) +{ + ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num)); +} +#endif + +static void store_rxdemod(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no")){ + o->rxdemod = RX_AUDIO_NONE; + } + else if (!strcasecmp(s,"speaker")){ + o->rxdemod = RX_AUDIO_SPEAKER; + } + else if (!strcasecmp(s,"flat")){ + o->rxdemod = RX_AUDIO_FLAT; + } + else { + ast_log(LOG_WARNING,"Unrecognized rxdemod parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set rxdemod = %s\n", s); +} + + +static void store_txmixa(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no")){ + o->txmixa = TX_OUT_OFF; + } + else if (!strcasecmp(s,"voice")){ + o->txmixa = TX_OUT_VOICE; + } + else if (!strcasecmp(s,"tone")){ + o->txmixa = TX_OUT_LSD; + } + else if (!strcasecmp(s,"composite")){ + o->txmixa = TX_OUT_COMPOSITE; + } + else if (!strcasecmp(s,"auxvoice")){ + o->txmixb = TX_OUT_AUX; + } + else { + ast_log(LOG_WARNING,"Unrecognized txmixa parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set txmixa = %s\n", s); +} + +static void store_txmixb(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no")){ + o->txmixb = TX_OUT_OFF; + } + else if (!strcasecmp(s,"voice")){ + o->txmixb = TX_OUT_VOICE; + } + else if (!strcasecmp(s,"tone")){ + o->txmixb = TX_OUT_LSD; + } + else if (!strcasecmp(s,"composite")){ + o->txmixb = TX_OUT_COMPOSITE; + } + else if (!strcasecmp(s,"auxvoice")){ + o->txmixb = TX_OUT_AUX; + } + else { + ast_log(LOG_WARNING,"Unrecognized txmixb parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set txmixb = %s\n", s); +} +/* +*/ +static void store_rxcdtype(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no")){ + o->rxcdtype = CD_IGNORE; + } + else if (!strcasecmp(s,"usb")){ + o->rxcdtype = CD_HID; + } + else if (!strcasecmp(s,"dsp")){ + o->rxcdtype = CD_XPMR_NOISE; + } + else if (!strcasecmp(s,"vox")){ + o->rxcdtype = CD_XPMR_VOX; + } + else if (!strcasecmp(s,"usbinvert")){ + o->rxcdtype = CD_HID_INVERT; + } + else { + ast_log(LOG_WARNING,"Unrecognized rxcdtype parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set rxcdtype = %s\n", s); +} +/* +*/ +static void store_rxsdtype(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no") || !strcasecmp(s,"SD_IGNORE")){ + o->rxsdtype = SD_IGNORE; + } + else if (!strcasecmp(s,"usb") || !strcasecmp(s,"SD_HID")){ + o->rxsdtype = SD_HID; + } + else if (!strcasecmp(s,"usbinvert") || !strcasecmp(s,"SD_HID_INVERT")){ + o->rxsdtype = SD_HID_INVERT; + } + else if (!strcasecmp(s,"software") || !strcasecmp(s,"SD_XPMR")){ + o->rxsdtype = SD_XPMR; + } + else { + ast_log(LOG_WARNING,"Unrecognized rxsdtype parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set rxsdtype = %s\n", s); +} +/* +*/ +static void store_rxgain(struct chan_usbradio_pvt *o, char *s) +{ + float f; + sscanf(s,"%f",&f); + o->rxgain = f; + //ast_log(LOG_WARNING, "set rxgain = %f\n", f); +} +/* +*/ +static void store_rxvoiceadj(struct chan_usbradio_pvt *o, char *s) +{ + float f; + sscanf(s,"%f",&f); + o->rxvoiceadj = f; + //ast_log(LOG_WARNING, "set rxvoiceadj = %f\n", f); +} +/* +*/ +static void store_rxctcssadj(struct chan_usbradio_pvt *o, char *s) +{ + float f; + sscanf(s,"%f",&f); + o->rxctcssadj = f; + //ast_log(LOG_WARNING, "set rxctcssadj = %f\n", f); +} +/* +*/ +static void store_txtoctype(struct chan_usbradio_pvt *o, char *s) +{ + if (!strcasecmp(s,"no") || !strcasecmp(s,"TOC_NONE")){ + o->txtoctype = TOC_NONE; + } + else if (!strcasecmp(s,"phase") || !strcasecmp(s,"TOC_PHASE")){ + o->txtoctype = TOC_PHASE; + } + else if (!strcasecmp(s,"notone") || !strcasecmp(s,"TOC_NOTONE")){ + o->txtoctype = TOC_NOTONE; + } + else { + ast_log(LOG_WARNING,"Unrecognized txtoctype parameter: %s\n",s); + } + + //ast_log(LOG_WARNING, "set txtoctype = %s\n", s); +} +/* +*/ +static void store_rxctcssfreq(struct chan_usbradio_pvt *o, char *s) +{ + float f; + sscanf(s,"%f",&f); + o->rxctcssfreq = f; + //ast_log(LOG_WARNING, "set rxctcss = %f\n", f); +} +/* +*/ +static void store_txctcssfreq(struct chan_usbradio_pvt *o, char *s) +{ + float f; + sscanf(s,"%f",&f); + o->txctcssfreq = f; + //ast_log(LOG_WARNING, "set txctcss = %f\n", f); +} +/* +*/ +static void tune_txoutput(struct chan_usbradio_pvt *o, int value) +{ + o->txtestkey=1; + o->pmrChan->txPttIn=1; + + // generate 1KHz tone at 7200 peak + //o->pmrChan->spsSigGen1->freq=10000; + //o->pmrChan->spsSigGen1->outputGain=(float)(0.22*M_Q8); + //o->pmrChan->b.startSpecialTone=1; + + TxTestTone(o->pmrChan, 1); + + usleep(5000000); + //o->pmrChan->b.stopSpecialTone=1; + usleep(100000); + + TxTestTone(o->pmrChan, 0); + + o->pmrChan->txPttIn=0; + o->txtestkey=0; +} +/* +*/ +static void tune_rxinput(struct chan_usbradio_pvt *o) +{ + const int target=23000; + const int tolerance=2000; + const int settingmin=1; + const int settingstart=2; + const int maxtries=12; + + float settingmax; + + int setting=0, tries=0, tmpdiscfactor, meas; + int tunetype=0; + + settingmax = o->micmax; + + if(o->pmrChan->rxDemod)tunetype=1; + + setting = settingstart; + + while(tries<maxtries) + { + setamixer(o->devicenum,MIXER_PARAM_MIC_CAPTURE_VOL,setting,0); + setamixer(o->devicenum,MIXER_PARAM_MIC_BOOST,o->rxboostset,0); + usleep(100000); + if(o->rxcdtype==CD_XPMR_VOX || o->rxdemod==RX_AUDIO_SPEAKER) + { + // printf("Measure Direct Input\n"); + o->pmrChan->spsMeasure->source = o->pmrChan->spsRx->source; + o->pmrChan->spsMeasure->discfactor=1000; + o->pmrChan->spsMeasure->enabled=1; + o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0; + usleep(400000); + meas=o->pmrChan->spsMeasure->apeak; + o->pmrChan->spsMeasure->enabled=0; + } + else + { + // printf("Measure HF Noise\n"); + tmpdiscfactor=o->pmrChan->spsRx->discfactor; + o->pmrChan->spsRx->discfactor=(i16)1000; + o->pmrChan->spsRx->discounteru=o->pmrChan->spsRx->discounterl=0; + o->pmrChan->spsRx->amax=o->pmrChan->spsRx->amin=0; + usleep(200000); + meas=o->pmrChan->rxRssi; + o->pmrChan->spsRx->discfactor=tmpdiscfactor; + o->pmrChan->spsRx->discounteru=o->pmrChan->spsRx->discounterl=0; + o->pmrChan->spsRx->amax=o->pmrChan->spsRx->amin=0; + } + if(!meas)meas++; + printf("tries=%d, setting=%d, meas=%i\n",tries,setting,meas); + + if( meas<(target-tolerance) || meas>(target+tolerance) || tries<3){ + setting=setting*target/meas; + } + else if(tries>4 && meas>(target-tolerance) && meas<(target+tolerance) ) + { + break; + } + + if(setting<settingmin)setting=settingmin; + else if(setting>settingmax)setting=settingmax; + + tries++; + } + printf("DONE tries=%d, setting=%d, meas=%i\n",tries, + (setting * 1000) / o->micmax,meas); + if( meas<(target-tolerance) || meas>(target+tolerance) ){ + printf("ERROR: RX INPUT ADJUST FAILED.\n"); + }else{ + printf("INFO: RX INPUT ADJUST SUCCESS.\n"); + o->rxmixerset=(setting * 1000) / o->micmax; + } +} +/* +*/ +static void tune_rxvoice(struct chan_usbradio_pvt *o) +{ + const int target=7200; // peak + const int tolerance=360; // peak to peak + const float settingmin=0.1; + const float settingmax=4; + const float settingstart=1; + const int maxtries=12; + + float setting; + + int tries=0, meas; + + printf("INFO: RX VOICE ADJUST START.\n"); + printf("target=%i tolerance=%i \n",target,tolerance); + + if(!o->pmrChan->spsMeasure) + printf("ERROR: NO MEASURE BLOCK.\n"); + + if(!o->pmrChan->spsMeasure->source || !o->pmrChan->prxVoiceAdjust ) + printf("ERROR: NO SOURCE OR MEASURE SETTING.\n"); + + o->pmrChan->spsMeasure->source=o->pmrChan->spsRxOut->sink; + o->pmrChan->spsMeasure->enabled=1; + o->pmrChan->spsMeasure->discfactor=1000; + + setting=settingstart; + + // printf("ERROR: NO MEASURE BLOCK.\n"); + + while(tries<maxtries) + { + *(o->pmrChan->prxVoiceAdjust)=setting*M_Q8; + usleep(10000); + o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0; + usleep(1000000); + meas = o->pmrChan->spsMeasure->apeak; + printf("tries=%d, setting=%f, meas=%i\n",tries,setting,meas); + + if( meas<(target-tolerance) || meas>(target+tolerance) || tries<3){ + setting=setting*target/meas; + } + else if(tries>4 && meas>(target-tolerance) && meas<(target+tolerance) ) + { + break; + } + if(setting<settingmin)setting=settingmin; + else if(setting>settingmax)setting=settingmax; + + tries++; + } + + o->pmrChan->spsMeasure->enabled=0; + + printf("DONE tries=%d, setting=%f, meas=%f\n",tries,setting,(float)meas); + if( meas<(target-tolerance) || meas>(target+tolerance) ){ + printf("ERROR: RX VOICE GAIN ADJUST FAILED.\n"); + }else{ + printf("INFO: RX VOICE GAIN ADJUST SUCCESS.\n"); + o->rxvoiceadj=setting; + } +} +/* +*/ +static void tune_rxctcss(struct chan_usbradio_pvt *o) +{ + const int target=4096; + const int tolerance=100; + const float settingmin=0.1; + const float settingmax=4; + const float settingstart=1; + const int maxtries=12; + + float setting; + int tries=0, meas; + + printf("INFO: RX CTCSS ADJUST START.\n"); + printf("target=%i tolerance=%i \n",target,tolerance); + + o->pmrChan->spsMeasure->source=o->pmrChan->prxCtcssMeasure; + o->pmrChan->spsMeasure->discfactor=400; + o->pmrChan->spsMeasure->enabled=1; + + setting=settingstart; + + while(tries<maxtries) + { + *(o->pmrChan->prxCtcssAdjust)=setting*M_Q8; + usleep(10000); + o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0; + usleep(500000); + meas = o->pmrChan->spsMeasure->apeak; + printf("tries=%d, setting=%f, meas=%i\n",tries,setting,meas); + + if( meas<(target-tolerance) || meas>(target+tolerance) || tries<3){ + setting=setting*target/meas; + } + else if(tries>4 && meas>(target-tolerance) && meas<(target+tolerance) ) + { + break; + } + if(setting<settingmin)setting=settingmin; + else if(setting>settingmax)setting=settingmax; + + tries++; + } + o->pmrChan->spsMeasure->enabled=0; + printf("DONE tries=%d, setting=%f, meas=%f\n",tries,setting,(float)meas); + if( meas<(target-tolerance) || meas>(target+tolerance) ){ + printf("ERROR: RX CTCSS GAIN ADJUST FAILED.\n"); + }else{ + printf("INFO: RX CTCSS GAIN ADJUST SUCCESS.\n"); + o->rxctcssadj=setting; + } +} +/* + this file then is included in chan_usbradio.conf + #include /etc/asterisk/usbradio_tune.conf +*/ +static void tune_write(struct chan_usbradio_pvt *o) +{ + FILE *fp; + + fp=fopen("/etc/asterisk/usbradio_tune.conf","w"); + + if (!strcmp(o->name,"dsp")) + fprintf(fp,"[general]\n"); + else + fprintf(fp,"[%s]\n",o->name); + + fprintf(fp,"; name=%s\n",o->name); + fprintf(fp,"; devicenum=%i\n",o->devicenum); + + fprintf(fp,"rxmixerset=%i\n",o->rxmixerset); + fprintf(fp,"rxboostset=%i\n",o->rxboostset); + fprintf(fp,"txmixaset=%i\n",o->txmixaset); + fprintf(fp,"txmixbset=%i\n",o->txmixbset); + + fprintf(fp,"rxvoiceadj=%f\n",o->rxvoiceadj); + fprintf(fp,"rxctcssadj=%f\n",o->rxctcssadj); + fprintf(fp,"txctcssadj=%i\n",o->txctcssadj); + + fprintf(fp,"rxsquelchadj=%i\n",o->rxsquelchadj); + fclose(fp); +} +// +static void mixer_write(struct chan_usbradio_pvt *o) +{ + setamixer(o->devicenum,MIXER_PARAM_MIC_PLAYBACK_SW,0,0); + setamixer(o->devicenum,MIXER_PARAM_MIC_PLAYBACK_VOL,0,0); + setamixer(o->devicenum,MIXER_PARAM_SPKR_PLAYBACK_SW,1,0); + setamixer(o->devicenum,MIXER_PARAM_SPKR_PLAYBACK_VOL, + o->txmixaset * o->spkrmax / 1000, + o->txmixbset * o->spkrmax / 1000); + setamixer(o->devicenum,MIXER_PARAM_MIC_CAPTURE_VOL, + o->rxmixerset * o->micmax / 1000,0); + setamixer(o->devicenum,MIXER_PARAM_MIC_BOOST,o->rxboostset,0); + setamixer(o->devicenum,MIXER_PARAM_MIC_CAPTURE_SW,1,0); +} +/* + adjust dsp multiplier to add resolution to tx level adjustment +*/ +static void mult_set(struct chan_usbradio_pvt *o) +{ + + if(o->pmrChan->spsTxOutA) { + o->pmrChan->spsTxOutA->outputGain = + mult_calc((o->txmixaset * 152) / 1000); + } + if(o->pmrChan->spsTxOutB){ + o->pmrChan->spsTxOutB->outputGain = + mult_calc((o->txmixbset * 152) / 1000); + } +} +// +// input 0 - 151 outputs are pot and multiplier +// +static int mult_calc(int value) +{ + const int multx=M_Q8; + int pot,mult; + + pot=((int)(value/4)*4)+2; + mult = multx-( ( multx * (3-(value%4)) ) / (pot+2) ); + return(mult); +} + +#define pd(x) {printf(#x" = %d\n",x);} +#define pp(x) {printf(#x" = %p\n",x);} +#define ps(x) {printf(#x" = %s\n",x);} +#define pf(x) {printf(#x" = %f\n",x);} +/* +*/ +static void pmrdump(struct chan_usbradio_pvt *o) +{ + t_pmr_chan *p; + + p=o->pmrChan; + + printf("\nodump()\n"); + + pd(o->devicenum); + + pd(o->rxdemod); + pd(o->rxcdtype); + pd(o->rxsdtype); + pd(o->txtoctype); + + pd(o->rxmixerset); + pf(o->rxvoiceadj); + pf(o->rxctcssadj); + pd(o->rxsquelchadj); + + pd(o->txprelim); + pd(o->txmixa); + pd(o->txmixb); + + pd(o->txmixaset); + pd(o->txmixbset); + + printf("\npmrdump()\n"); + + printf("prxSquelchAdjust=%i\n",*(o->pmrChan->prxSquelchAdjust)); + + pd(p->rxCarrierPoint); + pd(p->rxCarrierHyst); + + pd(p->rxCtcss->relax); + pf(p->rxCtcssFreq); + pd(p->rxCtcssIndex); + pf(p->txCtcssFreq); + + pd(p->txMixA); + pd(p->txMixB); + + pd(p->rxDeEmpEnable); + pd(p->rxCenterSlicerEnable); + pd(p->rxCtcssDecodeEnable); + pd(p->rxDcsDecodeEnable); + + pd(p->txHpfEnable); + pd(p->txLimiterEnable); + pd(p->txPreEmpEnable); + pd(p->txLpfEnable); + + if(p->spsTxOutA)pd(p->spsTxOutA->outputGain); + if(p->spsTxOutB)pd(p->spsTxOutB->outputGain); + + return; +} + + +/* + * grab fields from the config file, init the descriptor and open the device. + */ +static struct chan_usbradio_pvt *store_config(struct ast_config *cfg, char *ctg) +{ + struct ast_variable *v; + struct chan_usbradio_pvt *o; + struct ast_config *cfg1; + + if (ctg == NULL) { + traceusb1((" store_config() ctg == NULL\n")); + o = &usbradio_default; + ctg = "general"; + } else { + if (!(o = ast_calloc(1, sizeof(*o)))){ + return NULL; + } + *o = usbradio_default; + /* "general" is also the default thing */ + if (strcmp(ctg, "general") == 0) { + o->name = ast_strdup("dsp"); + usbradio_active = o->name; + } + else o->name = ast_strdup(ctg); + } + + strcpy(o->mohinterpret, "default"); + o->micmax = amixer_max(o->devicenum,MIXER_PARAM_MIC_CAPTURE_VOL); + o->spkrmax = amixer_max(o->devicenum,MIXER_PARAM_SPKR_PLAYBACK_VOL); + /* fill other fields from configuration */ + for (v = ast_variable_browse(cfg, ctg); v; v = v->next) { + M_START(v->name, v->value); + + /* handle jb conf */ + if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) + continue; + +#if 0 + M_BOOL("autoanswer", o->autoanswer) + M_BOOL("autohangup", o->autohangup) + M_BOOL("overridecontext", o->overridecontext) + M_STR("context", o->ctx) + M_STR("language", o->language) + M_STR("mohinterpret", o->mohinterpret) + M_STR("extension", o->ext) + M_F("callerid", store_callerid(o, v->value)) +#endif + M_UINT("frags", o->frags) + M_UINT("queuesize", o->queuesize) + M_UINT("devicenum", o->devicenum) + M_UINT("debug", usbradio_debug) + M_BOOL("rxcpusaver",o->rxcpusaver) + M_BOOL("txcpusaver",o->txcpusaver) + M_BOOL("invertptt",o->invertptt) + M_F("rxdemod",store_rxdemod(o,v->value)) + M_BOOL("txprelim",o->txprelim); + M_F("txmixa",store_txmixa(o,v->value)) + M_F("txmixb",store_txmixb(o,v->value)) + M_F("carrierfrom",store_rxcdtype(o,v->value)) + M_F("rxsdtype",store_rxsdtype(o,v->value)) + M_F("rxctcssfreq",store_rxctcssfreq(o,v->value)) + M_F("txctcssfreq",store_txctcssfreq(o,v->value)) + M_F("rxgain",store_rxgain(o,v->value)) + M_BOOL("rxboostset", o->rxboostset) + M_UINT("rxctcssrelax", o->rxctcssrelax) + M_F("txtoctype",store_txtoctype(o,v->value)) + M_UINT("hdwtype", o->hdwtype) + M_UINT("duplex", o->radioduplex) + M_END(; + ); + } + + cfg1 = ast_config_load(config1); + if (!cfg1) + { + o->rxmixerset = 500; + o->txmixaset = 500; + o->txmixbset = 500; + o->rxvoiceadj = 0.5; + o->rxctcssadj = 0.5; + o->txctcssadj = 200; + o->rxsquelchadj = 500; + ast_log(LOG_WARNING,"File %s not found, using default parameters.\n",config1); + } else { + for (v = ast_variable_browse(cfg1, ctg); v; v = v->next) { + + M_START(v->name, v->value); + M_UINT("rxmixerset", o->rxmixerset) + M_UINT("txmixaset", o->txmixaset) + M_UINT("txmixbset", o->txmixbset) + M_F("rxvoiceadj",store_rxvoiceadj(o,v->value)) + M_F("rxctcssadj",store_rxctcssadj(o,v->value)) + M_UINT("txctcssadj",o->txctcssadj); + M_UINT("rxsquelchadj", o->rxsquelchadj) + M_END(; + ); + } + ast_config_destroy(cfg1); + } + + o->debuglevel=0; + + if (o == &usbradio_default) /* we are done with the default */ + return NULL; + + o->lastopen = ast_tvnow(); /* don't leave it 0 or tvdiff may wrap */ + o->dsp = ast_dsp_new(); + if (o->dsp) + { + ast_dsp_set_features(o->dsp,DSP_FEATURE_DTMF_DETECT); + ast_dsp_digitmode(o->dsp,DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_RELAXDTMF); + } + + if(o->rxctcssfreq!=0 && o->rxdemod==RX_AUDIO_SPEAKER) + { + ast_log(LOG_ERROR, "Incompatable Options o->rxctcssfreq=%f and o->rxdemod=speaker\n", o->rxctcssfreq); + } + + if(o->pmrChan==NULL) + { + t_pmr_chan tChan; + + memset(&tChan,0,sizeof(t_pmr_chan)); + + tChan.rxDemod=o->rxdemod; + tChan.rxCdType=o->rxcdtype; + + tChan.txMod=o->txprelim; + + tChan.txMixA = o->txmixa; + tChan.txMixB = o->txmixb; + + tChan.rxCpuSaver=o->rxcpusaver; + tChan.txCpuSaver=o->txcpusaver; + + tChan.rxCtcssFreq=o->rxctcssfreq; + tChan.txCtcssFreq=o->txctcssfreq; + + o->pmrChan=createPmrChannel(&tChan,FRAME_SIZE); + + o->pmrChan->radioDuplex=o->radioduplex; + + o->pmrChan->rxCpuSaver=o->rxcpusaver; + o->pmrChan->txCpuSaver=o->txcpusaver; + + *(o->pmrChan->prxSquelchAdjust) = + ((999 - o->rxsquelchadj) * 32767) / 1000; + + o->pmrChan->spsRx->outputGain = o->rxvoiceadj*M_Q8; + + o->pmrChan->txTocType = o->txtoctype; + + if ((o->txmixa == TX_OUT_LSD) || + (o->txmixa == TX_OUT_COMPOSITE) || + (o->txmixb == TX_OUT_LSD) || + (o->txmixb == TX_OUT_COMPOSITE)) + { + *(o->pmrChan->prxCtcssAdjust)=o->rxctcssadj*M_Q8; + set_txctcss_level(o); + } + + o->pmrChan->rxCtcss->relax=o->rxctcssrelax; + + } + + if( (o->txmixa!=TX_OUT_VOICE) && (o->txmixb!=TX_OUT_VOICE) && + (o->txmixa!=TX_OUT_COMPOSITE) && (o->txmixb!=TX_OUT_COMPOSITE) + ) + { + ast_log(LOG_ERROR,"No txvoice output configured.\n"); + } + + if( o->txctcssfreq && + o->txmixa!=TX_OUT_LSD && o->txmixa!=TX_OUT_COMPOSITE && + o->txmixb!=TX_OUT_LSD && o->txmixb!=TX_OUT_COMPOSITE + ) + { + ast_log(LOG_ERROR,"No txtone output configured.\n"); + } + + if( o->rxctcssfreq && o->pmrChan->rxCtcssIndex<0 ) + { + ast_log(LOG_ERROR,"Invalid CTCSS Frequency.\n"); + } + + // RxTestIt(o); + + mixer_write(o); + mult_set(o); + hidhdwconfig(o); + + // pmrdump(o); + + if (pipe(o->sndcmd) != 0) { + ast_log(LOG_ERROR, "Unable to create pipe\n"); + goto error; + } + + printf("creating sound thread\n"); + ast_pthread_create_background(&o->sthread, NULL, sound_thread, o); + + /* link into list of devices */ + if (o != &usbradio_default) { + o->next = usbradio_default.next; + usbradio_default.next = o; + } + return o; + + error: + if (o != &usbradio_default) + free(o); + return NULL; +} + +#if DEBUG_FILETEST == 1 +/* + Test It on a File +*/ +int RxTestIt(struct chan_usbradio_pvt *o) +{ + const int numSamples = SAMPLES_PER_BLOCK; + const int numChannels = 16; + + i16 sample,i,ii; + + i32 txHangTime; + + i16 txEnable; + + t_pmr_chan tChan; + t_pmr_chan *pChan; + + FILE *hInput=NULL, *hOutput=NULL, *hOutputTx=NULL; + + i16 iBuff[numSamples*2*6], oBuff[numSamples]; + + printf("RxTestIt()\n"); + + pChan=o->pmrChan; + pChan->b.txCapture=1; + pChan->b.rxCapture=1; + + txEnable = 0; + + hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm","r"); + if(!hInput){ + printf(" RxTestIt() File Not Found.\n"); + return 0; + } + hOutput = fopen("/usr/src/xpmr/testdata/rx_debug.pcm","w"); + + printf(" RxTestIt() Working...\n"); + + while(!feof(hInput)) + { + fread((void *)iBuff,2,numSamples*2*6,hInput); + + if(txHangTime)txHangTime-=numSamples; + if(txHangTime<0)txHangTime=0; + + if(pChan->rxCtcss->decode)txHangTime=(8000/1000*2000); + + if(pChan->rxCtcss->decode && !txEnable) + { + txEnable=1; + //pChan->inputBlanking=(8000/1000*200); + } + else if(!pChan->rxCtcss->decode && txEnable) + { + txEnable=0; + } + + PmrRx(pChan,iBuff,oBuff); + + fwrite((void *)pChan->prxDebug,2,numSamples*numChannels,hOutput); + } + pChan->b.txCapture=0; + pChan->b.rxCapture=0; + + if(hInput)fclose(hInput); + if(hOutput)fclose(hOutput); + + printf(" RxTestIt() Complete.\n"); + + return 0; +} +#endif + +#include "./xpmr/xpmr.c" +/* +*/ +static int load_module(void) +{ + struct ast_config *cfg = NULL; + char *ctg = NULL; + + /* Copy the default jb config over global_jbconf */ + memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); + + /* load config file */ + if (!(cfg = ast_config_load(config))) { + ast_log(LOG_NOTICE, "Unable to load config %s\n", config); + return AST_MODULE_LOAD_DECLINE; + } + + do { + store_config(cfg, ctg); + } while ( (ctg = ast_category_browse(cfg, ctg)) != NULL); + + ast_config_destroy(cfg); + + if (find_desc(usbradio_active) == NULL) { + ast_log(LOG_NOTICE, "Device %s not found\n", usbradio_active); + /* XXX we could default to 'dsp' perhaps ? */ + /* XXX should cleanup allocated memory etc. */ + return AST_MODULE_LOAD_FAILURE; + } + + if (ast_channel_register(&usbradio_tech)) { + ast_log(LOG_ERROR, "Unable to register channel type 'usb'\n"); + return AST_MODULE_LOAD_FAILURE; + } + + ast_cli_register_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry)); + + return AST_MODULE_LOAD_SUCCESS; +} +/* +*/ +static int unload_module(void) +{ + struct chan_usbradio_pvt *o; + + ast_log(LOG_WARNING, "unload_module() called\n"); + + ast_channel_unregister(&usbradio_tech); + ast_cli_unregister_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry)); + + for (o = usbradio_default.next; o; o = o->next) { + + ast_log(LOG_WARNING, "destroyPmrChannel() called\n"); + if(o->pmrChan)destroyPmrChannel(o->pmrChan); + + #if DEBUG_CAPTURES == 1 + if (frxcapraw) { fclose(frxcapraw); frxcapraw = NULL; } + if (frxcaptrace) { fclose(frxcaptrace); frxcaptrace = NULL; } + if (frxoutraw) { fclose(frxoutraw); frxoutraw = NULL; } + if (ftxcapraw) { fclose(ftxcapraw); ftxcapraw = NULL; } + if (ftxcaptrace) { fclose(ftxcaptrace); ftxcaptrace = NULL; } + if (ftxoutraw) { fclose(ftxoutraw); ftxoutraw = NULL; } + #endif + + close(o->sounddev); + if (o->sndcmd[0] > 0) { + close(o->sndcmd[0]); + close(o->sndcmd[1]); + } + if (o->dsp) ast_dsp_free(o->dsp); + if (o->owner) + ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD); + if (o->owner) /* XXX how ??? */ + return -1; + /* XXX what about the thread ? */ + /* XXX what about the memory allocated ? */ + } + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "usb Console Channel Driver"); + +/* end of file */ + + diff --git a/channels/xpmr/LICENSE b/channels/xpmr/LICENSE new file mode 100755 index 0000000000000000000000000000000000000000..a52b16e4064dfee29faf4b9fdbb058237b418fce --- /dev/null +++ b/channels/xpmr/LICENSE @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/channels/xpmr/sinetabx.h b/channels/xpmr/sinetabx.h new file mode 100755 index 0000000000000000000000000000000000000000..1ceb659e72601b68c4811ae3707080bdc058dcd2 --- /dev/null +++ b/channels/xpmr/sinetabx.h @@ -0,0 +1,300 @@ +/* + * sinetabx.h - for Xelatec Private Mobile Radio Processes + * + * All Rights Reserved. Copyright (C)2007, Xelatec, LLC + * + * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This version may be optionally licenced under the GNU LGPL licence. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + */ + +/*! \file + * + * \brief Private Land Mobile Radio Channel Voice and Signaling Processor + * + * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC + */ + +#ifndef XPMR_SINETABX_H +#define XPMR_SINETABX_H 1 + +#define SAMPLES_PER_SINE 256 + +const i16 sinetablex[]={ +0, // 0 +804, // 1 +1608, // 2 +2410, // 3 +3212, // 4 +4011, // 5 +4808, // 6 +5602, // 7 +6393, // 8 +7179, // 9 +7962, // 10 +8739, // 11 +9512, // 12 +10278, // 13 +11039, // 14 +11793, // 15 +12539, // 16 +13279, // 17 +14010, // 18 +14732, // 19 +15446, // 20 +16151, // 21 +16846, // 22 +17530, // 23 +18204, // 24 +18868, // 25 +19519, // 26 +20159, // 27 +20787, // 28 +21403, // 29 +22005, // 30 +22594, // 31 +23170, // 32 +23731, // 33 +24279, // 34 +24811, // 35 +25329, // 36 +25832, // 37 +26319, // 38 +26790, // 39 +27245, // 40 +27683, // 41 +28105, // 42 +28510, // 43 +28898, // 44 +29268, // 45 +29621, // 46 +29956, // 47 +30273, // 48 +30571, // 49 +30852, // 50 +31113, // 51 +31356, // 52 +31580, // 53 +31785, // 54 +31971, // 55 +32137, // 56 +32285, // 57 +32412, // 58 +32521, // 59 +32609, // 60 +32678, // 61 +32728, // 62 +32757, // 63 +32767, // 64 +32757, // 65 +32728, // 66 +32678, // 67 +32609, // 68 +32521, // 69 +32412, // 70 +32285, // 71 +32137, // 72 +31971, // 73 +31785, // 74 +31580, // 75 +31356, // 76 +31113, // 77 +30852, // 78 +30571, // 79 +30273, // 80 +29956, // 81 +29621, // 82 +29268, // 83 +28898, // 84 +28510, // 85 +28105, // 86 +27683, // 87 +27245, // 88 +26790, // 89 +26319, // 90 +25832, // 91 +25329, // 92 +24811, // 93 +24279, // 94 +23731, // 95 +23170, // 96 +22594, // 97 +22005, // 98 +21403, // 99 +20787, // 100 +20159, // 101 +19519, // 102 +18868, // 103 +18204, // 104 +17530, // 105 +16846, // 106 +16151, // 107 +15446, // 108 +14732, // 109 +14010, // 110 +13279, // 111 +12539, // 112 +11793, // 113 +11039, // 114 +10278, // 115 +9512, // 116 +8739, // 117 +7962, // 118 +7179, // 119 +6393, // 120 +5602, // 121 +4808, // 122 +4011, // 123 +3212, // 124 +2410, // 125 +1608, // 126 +804, // 127 +0, // 128 +-804, // 129 +-1608, // 130 +-2410, // 131 +-3212, // 132 +-4011, // 133 +-4808, // 134 +-5602, // 135 +-6393, // 136 +-7179, // 137 +-7962, // 138 +-8739, // 139 +-9512, // 140 +-10278, // 141 +-11039, // 142 +-11793, // 143 +-12539, // 144 +-13279, // 145 +-14010, // 146 +-14732, // 147 +-15446, // 148 +-16151, // 149 +-16846, // 150 +-17530, // 151 +-18204, // 152 +-18868, // 153 +-19519, // 154 +-20159, // 155 +-20787, // 156 +-21403, // 157 +-22005, // 158 +-22594, // 159 +-23170, // 160 +-23731, // 161 +-24279, // 162 +-24811, // 163 +-25329, // 164 +-25832, // 165 +-26319, // 166 +-26790, // 167 +-27245, // 168 +-27683, // 169 +-28105, // 170 +-28510, // 171 +-28898, // 172 +-29268, // 173 +-29621, // 174 +-29956, // 175 +-30273, // 176 +-30571, // 177 +-30852, // 178 +-31113, // 179 +-31356, // 180 +-31580, // 181 +-31785, // 182 +-31971, // 183 +-32137, // 184 +-32285, // 185 +-32412, // 186 +-32521, // 187 +-32609, // 188 +-32678, // 189 +-32728, // 190 +-32757, // 191 +-32767, // 192 +-32757, // 193 +-32728, // 194 +-32678, // 195 +-32609, // 196 +-32521, // 197 +-32412, // 198 +-32285, // 199 +-32137, // 200 +-31971, // 201 +-31785, // 202 +-31580, // 203 +-31356, // 204 +-31113, // 205 +-30852, // 206 +-30571, // 207 +-30273, // 208 +-29956, // 209 +-29621, // 210 +-29268, // 211 +-28898, // 212 +-28510, // 213 +-28105, // 214 +-27683, // 215 +-27245, // 216 +-26790, // 217 +-26319, // 218 +-25832, // 219 +-25329, // 220 +-24811, // 221 +-24279, // 222 +-23731, // 223 +-23170, // 224 +-22594, // 225 +-22005, // 226 +-21403, // 227 +-20787, // 228 +-20159, // 229 +-19519, // 230 +-18868, // 231 +-18204, // 232 +-17530, // 233 +-16846, // 234 +-16151, // 235 +-15446, // 236 +-14732, // 237 +-14010, // 238 +-13279, // 239 +-12539, // 240 +-11793, // 241 +-11039, // 242 +-10278, // 243 +-9512, // 244 +-8739, // 245 +-7962, // 246 +-7179, // 247 +-6393, // 248 +-5602, // 249 +-4808, // 250 +-4011, // 251 +-3212, // 252 +-2410, // 253 +-1608, // 254 +-804, // 255 +}; + +#endif /* !XPMR_SINETABX_H */ diff --git a/channels/xpmr/xpmr.c b/channels/xpmr/xpmr.c new file mode 100755 index 0000000000000000000000000000000000000000..c67e4084119162817e3f085f7b620bccdb542653 --- /dev/null +++ b/channels/xpmr/xpmr.c @@ -0,0 +1,2266 @@ +/* + * xpmr.c - Xelatec Private Mobile Radio Processes + * + * All Rights Reserved. Copyright (C)2007, Xelatec, LLC + * + * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This version may be optionally licenced under the GNU LGPL licence. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + */ + +/*! \file + * + * \brief Private Land Mobile Radio Channel Voice and Signaling Processor + * + * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC + */ +/* + FYI = For Your Information + PMR = Private Mobile Radio + RX = Receive + TX = Transmit + CTCSS = Continuous Tone Coded Squelch System + TONE = Same as above. + LSD = Low Speed Data, subaudible signaling. May be tones or codes. + VOX = Voice Operated Transmit + DSP = Digital Signal Processing + LPF = Low Pass Filter + FIR = Finite Impulse Response (Filter) + IIR = Infinite Impulse Response (Filter) +*/ +#include <stdio.h> +#include <ctype.h> +#include <math.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <sys/time.h> +#include <stdlib.h> +#include <errno.h> + +#include "xpmr.h" +#include "xpmr_coef.h" +#include "sinetabx.h" + +static i16 pmrChanIndex=0; // count of created pmr instances + +/* + Convert a Frequency in Hz to a zero based CTCSS Table index +*/ +i16 CtcssFreqIndex(float freq) +{ + i16 i,hit=-1; + + for(i=0;i<CTCSS_NUM_CODES;i++){ + if(freq==freq_ctcss[i])hit=i; + } + return hit; +} +/* + pmr_rx_frontend + Takes a block of data and low pass filters it. + Determines the amplitude of high frequency noise for carrier detect. + Decimates input data to change the rate. +*/ +i16 pmr_rx_frontend(t_pmr_sps *mySps) +{ + #define DCgainBpfNoise 65536 + + i16 samples,iOutput, *input, *output, *noutput; + i16 *x, *coef, *coef2; + i32 i, naccum, outputGain, calcAdjust; + i64 y; + i16 nx, hyst, setpt, compOut; + i16 amax, amin, apeak, discounteru, discounterl, discfactor; + i16 decimator, decimate, doNoise; + + TRACEX(("pmr_rx_frontend()\n")); + + if(!mySps->enabled)return(1); + + decimator = mySps->decimator; + decimate = mySps->decimate; + + input = mySps->source; + output = mySps->sink; + noutput = mySps->parentChan->pRxNoise; + + nx = mySps->nx; + coef = mySps->coef; + coef2 = mySps->coef2; + + calcAdjust = mySps->calcAdjust; + outputGain = mySps->outputGain; + + amax=mySps->amax; + amin=mySps->amin; + apeak=mySps->apeak; + discounteru=mySps->discounteru; + discounterl=mySps->discounterl; + discfactor=mySps->discfactor; + setpt=mySps->setpt; + hyst=mySps->hyst; + compOut=mySps->compOut; + + samples=mySps->nSamples*decimate; + x=mySps->x; + iOutput=0; + + if(mySps->parentChan->rxCdType!=CD_XPMR_VOX)doNoise=1; + else doNoise=0; + + for(i=0;i<samples;i++) + { + i16 n; + + //shift the old samples + for(n=nx-1; n>0; n--) + x[n] = x[n-1]; + + x[0] = input[i*2]; + + --decimator; + + if(decimator<=0) + { + decimator=decimate; + + y=0; + for(n=0; n<nx; n++) + y += coef[n] * x[n]; + + y=((y/calcAdjust)*outputGain)/M_Q8; + + if(y>32767)y=32767; + else if(y<-32767)y=-32767; + + output[iOutput]=y; // Rx Baseband decimated + noutput[iOutput++] = apeak; // Rx Noise + } + + if(doNoise) + { + // calculate noise output + naccum=0; + for(n=0; n<nx; n++) + naccum += coef_fir_bpf_noise_1[n] * x[n]; + + naccum /= DCgainBpfNoise; + + if(naccum>amax) + { + amax=naccum; + discounteru=discfactor; + } + else if(--discounteru<=0) + { + discounteru=discfactor; + amax=(i32)((amax*32700)/32768); + } + + if(naccum<amin) + { + amin=naccum; + discounterl=discfactor; + } + else if(--discounterl<=0) + { + discounterl=discfactor; + amin=(i32)((amin*32700)/32768); + } + + apeak=(amax-amin)/2; + + } // if doNoise + } + + if(doNoise) + { + ((t_pmr_chan *)(mySps->parentChan))->rxRssi=apeak; + + if(apeak>setpt || (compOut&&(apeak>(setpt-hyst)))) compOut=1; + else compOut=0; + mySps->compOut=compOut; + mySps->amax=amax; + mySps->amin=amin; + mySps->apeak=apeak; + mySps->discounteru=discounteru; + mySps->discounterl=discounterl; + } + + return 0; +} +/* + pmr general purpose fir + works on a block of samples +*/ +i16 pmr_gp_fir(t_pmr_sps *mySps) +{ + i32 nsamples,inputGain,outputGain,calcAdjust; + i16 *input, *output; + i16 *x, *coef; + i32 i, ii; + i16 nx, hyst, setpt, compOut; + i16 amax, amin, apeak=0, discounteru=0, discounterl=0, discfactor; + i16 decimator, decimate, interpolate; + i16 numChanOut, selChanOut, mixOut, monoOut; + + TRACEX(("pmr_gp_fir() %i\n",mySps->enabled)); + + if(!mySps->enabled)return(1); + + inputGain = mySps->inputGain; + calcAdjust = mySps->calcAdjust; + outputGain = mySps->outputGain; + + input = mySps->source; + output = mySps->sink; + x = mySps->x; + nx = mySps->nx; + coef = mySps->coef; + + decimator = mySps->decimator; + decimate = mySps->decimate; + interpolate = mySps->interpolate; + + setpt = mySps->setpt; + compOut = mySps->compOut; + + inputGain = mySps->inputGain; + outputGain = mySps->outputGain; + numChanOut = mySps->numChanOut; + selChanOut = mySps->selChanOut; + mixOut = mySps->mixOut; + monoOut = mySps->monoOut; + + amax=mySps->amax; + amin=mySps->amin; + + discfactor=mySps->discfactor; + hyst=mySps->hyst; + setpt=mySps->setpt; + nsamples=mySps->nSamples; + + if(mySps->option==3) + { + mySps->option=0; + mySps->enabled=0; + for(i=0;i<nsamples;i++) + { + if(monoOut) + output[(i*2)]=output[(i*2)+1]=0; + else + output[(i*numChanOut)+selChanOut]=0; + } + return 0; + } + + ii=0; + for(i=0;i<nsamples;i++) + { + int ix; + + int64_t y=0; + + if(decimate<0) + { + decimator=decimate; + } + + for(ix=0;ix<interpolate;ix++) + { + i16 n; + y=0; + + for(n=nx-1; n>0; n--) + x[n] = x[n-1]; + x[0] = (input[i]*inputGain)/M_Q8; + + #if 0 + --decimator; + if(decimator<=0) + { + decimator=decimate; + for(n=0; n<nx; n++) + y += coef[n] * x[n]; + y /= (outputGain*3); + output[ii++]=y; + } + #else + for(n=0; n<nx; n++) + y += coef[n] * x[n]; + + y=((y/calcAdjust)*outputGain)/M_Q8; + + if(mixOut){ + if(monoOut){ + output[(ii*2)]=output[(ii*2)+1]+=y; + } + else{ + output[(ii*numChanOut)+selChanOut]+=y; + } + } + else{ + if(monoOut){ + output[(ii*2)]=output[(ii*2)+1]=y; + } + else{ + output[(ii*numChanOut)+selChanOut]=y; + } + } + ii++; + #endif + } + + // amplitude detector + if(setpt) + { + i16 accum=y; + + if(accum>amax) + { + amax=accum; + discounteru=discfactor; + } + else if(--discounteru<=0) + { + discounteru=discfactor; + amax=(i32)((amax*32700)/32768); + } + + if(accum<amin) + { + amin=accum; + discounterl=discfactor; + } + else if(--discounterl<=0) + { + discounterl=discfactor; + amin=(i32)((amin*32700)/32768); + } + + apeak = (i32)(amax-amin)/2; + + if(apeak>setpt)compOut=1; + else if(compOut&&(apeak<(setpt-hyst)))compOut=0; + } + } + + mySps->decimator = decimator; + + mySps->amax=amax; + mySps->amin=amin; + mySps->apeak=apeak; + mySps->discounteru=discounteru; + mySps->discounterl=discounterl; + + mySps->compOut=compOut; + + return 0; +} +/* + general purpose integrator lpf +*/ +i16 gp_inte_00(t_pmr_sps *mySps) +{ + i16 npoints; + i16 *input, *output; + + i32 inputGain, outputGain,calcAdjust; + i32 i; + i32 accum; + + i32 state00; + i16 coeff00, coeff01; + + TRACEX(("gp_inte_00() %i\n",mySps->enabled)); + if(!mySps->enabled)return(1); + + input = mySps->source; + output = mySps->sink; + + npoints=mySps->nSamples; + + inputGain=mySps->inputGain; + outputGain=mySps->outputGain; + calcAdjust=mySps->calcAdjust; + + coeff00=((i16*)mySps->coef)[0]; + coeff01=((i16*)mySps->coef)[1]; + state00=((i32*)mySps->x)[0]; + + // note fixed gain of 2 to compensate for attenuation + // in passband + + for(i=0;i<npoints;i++) + { + accum=input[i]; + state00 = accum + (state00*coeff01)/M_Q15; + accum = (state00*coeff00)/(M_Q15/4); + output[i]=(accum*outputGain)/M_Q8; + } + + ((i32*)(mySps->x))[0]=state00; + + return 0; +} +/* + general purpose differentiator hpf +*/ +i16 gp_diff(t_pmr_sps *mySps) +{ + i16 *input, *output; + i16 npoints; + i32 inputGain, outputGain, calcAdjust; + i32 i; + i32 temp0,temp1; + i16 x0; + i32 y0; + i16 a0,a1; + i16 b0; + i16 *coef; + i16 *x; + + input = mySps->source; + output = mySps->sink; + + npoints=mySps->nSamples; + + inputGain=mySps->inputGain; + outputGain=mySps->outputGain; + calcAdjust=mySps->calcAdjust; + + coef=(i16*)(mySps->coef); + x=(i16*)(mySps->x); + a0=coef[0]; + a1=coef[1]; + b0=coef[2]; + + x0=x[0]; + + TRACEX(("gp_diff()\n")); + + for (i=0;i<npoints;i++) + { + temp0 = x0 * a1; + x0 = input[i]; + temp1 = input[i] * a0; + y0 = (temp0 + temp1)/calcAdjust; + output[i]=(y0*outputGain)/M_Q8; + } + + x[0]=x0; + + return 0; +} +/* ---------------------------------------------------------------------- + CenterSlicer +*/ +i16 CenterSlicer(t_pmr_sps *mySps) +{ + i16 npoints,lhit,uhit; + i16 *input, *output, *buff; + + i32 inputGain, outputGain, inputGainB; + i32 i; + i32 accum; + + i32 amax; // buffer amplitude maximum + i32 amin; // buffer amplitude minimum + i32 apeak; // buffer amplitude peak + i32 center; + i32 setpt; // amplitude set point for peak tracking + + i32 discounteru; // amplitude detector integrator discharge counter upper + i32 discounterl; // amplitude detector integrator discharge counter lower + i32 discfactor; // amplitude detector integrator discharge factor + + TRACEX(("CenterSlicer() %i\n",mySps->enabled)); + + input = mySps->source; + output = mySps->sink; + buff = mySps->buff; + + npoints=mySps->nSamples; + + inputGain=mySps->inputGain; + outputGain=mySps->outputGain; + inputGainB=mySps->inputGainB; + + amax=mySps->amax; + amin=mySps->amin; + setpt=mySps->setpt; + apeak=mySps->apeak; + discounteru=mySps->discounteru; + discounterl=mySps->discounterl; + + discfactor=mySps->discfactor; + npoints=mySps->nSamples; + + for(i=0;i<npoints;i++) + { + accum=input[i]; + + lhit=uhit=0; + + if(accum>amax) + { + amax=accum; + uhit=1; + if(amin<(amax-setpt)) + { + amin=(amax-setpt); + lhit=1; + } + } + else if(accum<amin) + { + amin=accum; + lhit=1; + if(amax>(amin+setpt)) + { + amax=(amin+setpt); + uhit=1; + } + } + + if(--discounteru<=0 && amax>0) + { + amax--; + uhit=1; + } + + if(--discounterl<=0 && amin<0) + { + amin++; + lhit=1; + } + + if(uhit)discounteru=discfactor; + if(lhit)discounterl=discfactor; + + apeak = (amax-amin)/2; + center = (amax+amin)/2; + accum = accum - center; + output[i]=accum; + + // do limiter function + if(accum>inputGainB)accum=inputGainB; + else if(accum<-inputGainB)accum=-inputGainB; + buff[i]=accum; + + #if XPMR_DEBUG0 == 1 + #if 0 + mySps->debugBuff0[i]=center; + #endif + #if 0 + if(mySps->parentChan->frameCountRx&0x01) mySps->parentChan->prxDebug1[i]=amax; + else mySps->parentChan->prxDebug1[i]=amin; + #endif + #endif + } + + mySps->amax=amax; + mySps->amin=amin; + mySps->apeak=apeak; + mySps->discounteru=discounteru; + mySps->discounterl=discounterl; + + return 0; +} +/* ---------------------------------------------------------------------- + MeasureBlock + determine peak amplitude +*/ +i16 MeasureBlock(t_pmr_sps *mySps) +{ + i16 npoints; + i16 *input, *output; + + i32 inputGain, outputGain; + i32 i; + i32 accum; + + i16 amax; // buffer amplitude maximum + i16 amin; // buffer amplitude minimum + i16 apeak=0; // buffer amplitude peak (peak to peak)/2 + i16 setpt; // amplitude set point for amplitude comparator + + i32 discounteru; // amplitude detector integrator discharge counter upper + i32 discounterl; // amplitude detector integrator discharge counter lower + i32 discfactor; // amplitude detector integrator discharge factor + + TRACEX(("MeasureBlock() %i\n",mySps->enabled)); + + if(!mySps->enabled)return 1; + + if(mySps->option==3) + { + mySps->amax = mySps->amin = mySps->apeak = \ + mySps->discounteru = mySps->discounterl = \ + mySps->enabled = 0; + return 1; + } + + input = mySps->source; + output = mySps->sink; + + npoints=mySps->nSamples; + + inputGain=mySps->inputGain; + outputGain=mySps->outputGain; + + amax=mySps->amax; + amin=mySps->amin; + setpt=mySps->setpt; + discounteru=mySps->discounteru; + discounterl=mySps->discounterl; + + discfactor=mySps->discfactor; + npoints=mySps->nSamples; + + for(i=0;i<npoints;i++) + { + accum=input[i]; + + if(accum>amax) + { + amax=accum; + discounteru=discfactor; + } + else if(--discounteru<=0) + { + discounteru=discfactor; + amax=(i32)((amax*32700)/32768); + } + + if(accum<amin) + { + amin=accum; + discounterl=discfactor; + } + else if(--discounterl<=0) + { + discounterl=discfactor; + amin=(i32)((amin*32700)/32768); + } + + apeak = (i32)(amax-amin)/2; + if(output)output[i]=apeak; + } + + mySps->amax=amax; + mySps->amin=amin; + mySps->apeak=apeak; + mySps->discounteru=discounteru; + mySps->discounterl=discounterl; + if(apeak>=setpt) mySps->compOut=1; + else mySps->compOut=0; + + //TRACEX((" -MeasureBlock()=%i\n",mySps->apeak)); + return 0; +} +/* + SoftLimiter +*/ +i16 SoftLimiter(t_pmr_sps *mySps) +{ + i16 npoints; + //i16 samples, lhit,uhit; + i16 *input, *output; + + i32 inputGain, outputGain; + i32 i; + i32 accum; + i32 tmp; + + i32 amax; // buffer amplitude maximum + i32 amin; // buffer amplitude minimum + //i32 apeak; // buffer amplitude peak + i32 setpt; // amplitude set point for amplitude comparator + i16 compOut; // amplitude comparator output + + input = mySps->source; + output = mySps->sink; + + inputGain=mySps->inputGain; + outputGain=mySps->outputGain; + + npoints=mySps->nSamples; + + setpt=mySps->setpt; + amax=(setpt*124)/128; + amin=-amax; + + TRACEX(("SoftLimiter() %i %i %i) \n",amin, amax,setpt)); + + for(i=0;i<npoints;i++) + { + accum=input[i]; + //accum=input[i]*mySps->inputGain/256; + + if(accum>setpt) + { + tmp=((accum-setpt)*4)/128; + accum=setpt+tmp; + if(accum>amax)accum=amax; + compOut=1; + accum=setpt; + } + else if(accum<-setpt) + { + tmp=((accum+setpt)*4)/128; + accum=(-setpt)-tmp; + if(accum<amin)accum=amin; + compOut=1; + accum=-setpt; + } + + output[i]=(accum*outputGain)/M_Q8; + } + + return 0; +} +/* + SigGen() - sine, square function generator + sps overloaded values + discfactor = phase factor + discfactoru = phase index + if source is not NULL then mix it in! + + sign table and output gain are in Q15 format (32767=.999) +*/ +i16 SigGen(t_pmr_sps *mySps) +{ + #define PH_FRACT_FACT 128 + + i32 ph; + i16 i,outputgain,waveform,numChanOut,selChanOut; + i32 accum; + + TRACEX(("SigGen(%i) \n",mySps->option)); + + if(!mySps->freq ||!mySps->enabled)return 0; + + outputgain=mySps->outputGain; + waveform=0; + numChanOut=mySps->numChanOut; + selChanOut=mySps->selChanOut; + + if(mySps->option==1) + { + mySps->option=0; + mySps->state=1; + mySps->discfactor= + (SAMPLES_PER_SINE*mySps->freq*PH_FRACT_FACT)/mySps->sampleRate/10; + + TRACEX((" SigGen() discfactor = %i\n",mySps->discfactor)); + if(mySps->discounterl)mySps->state=2; + } + else if(mySps->option==2) + { + i16 shiftfactor=CTCSS_TURN_OFF_SHIFT; + // phase shift request + mySps->option=0; + mySps->state=2; + mySps->discounterl=CTCSS_TURN_OFF_TIME-(2*MS_PER_FRAME); // + + mySps->discounteru = \ + (mySps->discounteru + (((SAMPLES_PER_SINE*shiftfactor)/360)*PH_FRACT_FACT)) % (SAMPLES_PER_SINE*PH_FRACT_FACT); + //printf("shiftfactor = %i\n",shiftfactor); + //shiftfactor+=10; + } + else if(mySps->option==3) + { + // stop it and clear the output buffer + mySps->option=0; + mySps->state=0; + mySps->enabled=0; + for(i=0;i<mySps->nSamples;i++) + mySps->sink[(i*numChanOut)+selChanOut]=0; + return(0); + } + else if(mySps->state==2) + { + // doing turn off + mySps->discounterl-=MS_PER_FRAME; + if(mySps->discounterl<=0) + { + mySps->option=3; + mySps->state=2; + } + } + else if(mySps->state==0) + { + return(0); + } + + ph=mySps->discounteru; + + for(i=0;i<mySps->nSamples;i++) + { + if(!waveform) + { + // sine + //tmp=(sinetablex[ph/PH_FRACT_FACT]*amplitude)/M_Q16; + accum=sinetablex[ph/PH_FRACT_FACT]; + accum=(accum*outputgain)/M_Q8; + } + else + { + // square + if(ph>SAMPLES_PER_SINE/2) + accum=outputgain/M_Q8; + else + accum=-outputgain/M_Q8; + } + + if(mySps->source)accum+=mySps->source[i]; + + mySps->sink[(i*numChanOut)+selChanOut]=accum; + + ph=(ph+mySps->discfactor)%(SAMPLES_PER_SINE*PH_FRACT_FACT); + } + + mySps->discounteru=ph; + + return 0; +} +/* + adder/mixer + takes existing buffer and adds source buffer to destination buffer + sink buffer = (sink buffer * gain) + source buffer +*/ +i16 pmrMixer(t_pmr_sps *mySps) +{ + i32 accum; + i16 i, *input, *inputB, *output; + i16 inputGain, inputGainB; // apply to input data in Q7.8 format + i16 outputGain; // apply to output data in Q7.8 format + i16 discounteru,discounterl,amax,amin,setpt,discfactor; + i16 npoints,uhit,lhit,apeak,measPeak; + + TRACEX(("pmrMixer()\n")); + + input = mySps->source; + inputB = mySps->sourceB; + output = mySps->sink; + + inputGain=mySps->inputGain; + inputGainB=mySps->inputGainB; + outputGain=mySps->outputGain; + + amax=mySps->amax; + amin=mySps->amin; + setpt=mySps->setpt; + discounteru=mySps->discounteru; + discounterl=mySps->discounteru; + + discfactor=mySps->discfactor; + npoints=mySps->nSamples; + measPeak=mySps->measPeak; + + for(i=0;i<npoints;i++) + { + accum = ((input[i]*inputGain)/M_Q8) + + ((inputB[i]*inputGainB)/M_Q8); + + accum=(accum*outputGain)/M_Q8; + output[i]=accum; + + if(measPeak){ + lhit=uhit=0; + + if(accum>amax){ + amax=accum; + uhit=1; + if(amin<(amax-setpt)){ + amin=(amax-setpt); + lhit=1; + } + } + else if(accum<amin){ + amin=accum; + lhit=1; + if(amax>(amin+setpt)){ + amax=(amin+setpt); + uhit=1; + } + } + + if(--discounteru<=0 && amax>0){ + amax--; + uhit=1; + } + + if(--discounterl<=0 && amin<0){ + amin++; + lhit=1; + } + + if(uhit)discounteru=discfactor; + if(lhit)discounterl=discfactor; + } + } + + if(measPeak){ + apeak = (amax-amin)/2; + mySps->apeak=apeak; + mySps->amax=amax; + mySps->amin=amin; + mySps->discounteru=discounteru; + mySps->discounterl=discounterl; + } + + return 0; +} +/* + DelayLine +*/ +i16 DelayLine(t_pmr_sps *mySps) +{ + i16 *input, *output, *buff; + i16 i, npoints,buffsize,inindex,outindex; + + TRACEX((" DelayLine() %i\n",mySps->enabled)); + + input = mySps->source; + output = mySps->sink; + buff = (i16*)(mySps->buff); + buffsize = mySps->buffSize; + npoints = mySps->nSamples; + + outindex = mySps->buffOutIndex; + inindex = outindex + mySps->buffLead; + + for(i=0;i<npoints;i++) + { + inindex %= buffsize; + outindex %= buffsize; + + buff[inindex]=input[i]; + output[i]=buff[outindex]; + inindex++; + outindex++; + } + mySps->buffOutIndex=outindex; + + return 0; +} +/* + Continuous Tone Coded Squelch (CTCSS) Detector +*/ +i16 ctcss_detect(t_pmr_chan *pmrChan) +{ + i16 i,points2do, points=0, *pInput, hit, thit,relax; + i16 tnum, tmp, indexWas=0, indexNow, gain, peakwas=0, diffpeak; + i16 difftrig; + i16 lasttv0=0, lasttv1=0, lasttv2=0, tv0, tv1, tv2, indexDebug; + + TRACEX(("ctcss_detect(%p) %i %i %i %i\n",pmrChan, + pmrChan->rxCtcss->enabled, + pmrChan->rxCtcssIndex, + pmrChan->rxCtcss->testIndex, + pmrChan->rxCtcss->decode)); + + if(!pmrChan->rxCtcss->enabled)return(1); + + relax = pmrChan->rxCtcss->relax; + pInput = pmrChan->rxCtcss->input; + gain = pmrChan->rxCtcss->gain; + + if(relax) difftrig=(-0.1*M_Q15); + else difftrig=(-0.05*M_Q15); + + thit=hit=-1; + + //TRACEX((" ctcss_detect() %i %i %i %i\n", CTCSS_NUM_CODES,0,0,0)); + + for(tnum=0;tnum<CTCSS_NUM_CODES;tnum++) + { + i32 accum, peak; + t_tdet *ptdet; + i16 fudgeFactor; + i16 binFactor; + + //TRACEX((" ctcss_detect() tnum=%i %i\n",tnum,pmrChan->rxCtcssMap[tnum])); + + if( (pmrChan->rxCtcssMap[tnum] < 0) || + (pmrChan->rxCtcss->decode>=0 && (tnum!= pmrChan->rxCtcss->decode)) || + (!pmrChan->rxCtcss->multiFreq && (tnum!= pmrChan->rxCtcssIndex)) + ) + continue; + + //TRACEX((" ctcss_detect() tnum=%i\n",tnum)); + + ptdet=&(pmrChan->rxCtcss->tdet[tnum]); + indexDebug=0; + points=points2do=pmrChan->nSamplesRx; + fudgeFactor=ptdet->fudgeFactor; + binFactor=ptdet->binFactor; + + while(ptdet->counter < (points2do*CTCSS_SCOUNT_MUL)) + { + //TRACEX((" ctcss_detect() - inner loop\n")); + tmp=(ptdet->counter/CTCSS_SCOUNT_MUL)+1; + ptdet->counter-=(tmp*CTCSS_SCOUNT_MUL); + points2do-=tmp; + indexNow=points-points2do; + + ptdet->counter += ptdet->counterFactor; + + accum = pInput[indexNow-1]; // dude's major bug fix! + + peakwas=ptdet->peak; + + ptdet->z[ptdet->zIndex]+= + (((accum - ptdet->z[ptdet->zIndex])*binFactor)/M_Q15); + + peak = abs(ptdet->z[0]-ptdet->z[2]) + abs(ptdet->z[1]-ptdet->z[3]); + + if (ptdet->peak < peak) + ptdet->peak += ( ((peak-ptdet->peak)*binFactor)/M_Q15); + else + ptdet->peak=peak; + + { + static const i16 a0=13723; + static const i16 a1=-13723; + i32 temp0,temp1; + i16 x0; + + //differentiate + x0=ptdet->zd; + temp0 = x0 * a1; + ptdet->zd = ptdet->peak; + temp1 = ptdet->peak * a0; + diffpeak = (temp0 + temp1)/1024; + } + + if(diffpeak<(-0.03*M_Q15))ptdet->dvd-=4; + else if(ptdet->dvd<0)ptdet->dvd++; + + if((ptdet->dvd < -12) && diffpeak > (-0.02*M_Q15))ptdet->dvu+=2; + else if(ptdet->dvu)ptdet->dvu--; + + tmp=ptdet->setpt; + if(pmrChan->rxCtcss->decode==tnum) + { + if(relax)tmp=(tmp*55)/100; + else tmp=(tmp*80)/100; + } + + if(ptdet->peak > tmp) + { + if(ptdet->decode<(fudgeFactor*32))ptdet->decode++; + } + else if(pmrChan->rxCtcss->decode==tnum) + { + if(ptdet->peak > ptdet->hyst)ptdet->decode--; + else if(relax) ptdet->decode--; + else ptdet->decode-=4; + } + else + { + ptdet->decode=0; + } + + if((pmrChan->rxCtcss->decode==tnum) && !relax && (ptdet->dvu > (0.00075*M_Q15))) + { + ptdet->decode=0; + ptdet->z[0]=ptdet->z[1]=ptdet->z[2]=ptdet->z[3]=ptdet->dvu=0; + //printf("ctcss_detect() turnoff code!\n"); + } + + if(ptdet->decode<0 || !pmrChan->rxCarrierDetect)ptdet->decode=0; + + if(ptdet->decode>=fudgeFactor)thit=tnum; + + #if XPMR_DEBUG0 == 1 + //if(thit>=0 && thit==tnum) + // printf(" ctcss_detect() %i %i %i %i \n",tnum,ptdet->peak,ptdet->setpt,ptdet->hyst); + + // tv0=accum; + tv0=ptdet->peak; + tv1=diffpeak; + tv2=ptdet->dvu; + + //tv1=ptdet->zi*100; + while(indexDebug<indexNow) + { + if(indexDebug==0)lasttv0=ptdet->pDebug0[points-1]; + if(ptdet->pDebug0)ptdet->pDebug0[indexDebug]=lasttv0; + + if(indexDebug==0)lasttv1=ptdet->pDebug1[points-1]; + if(ptdet->pDebug1)ptdet->pDebug1[indexDebug]=lasttv1; + + if(indexDebug==0)lasttv2=ptdet->pDebug2[points-1]; + if(ptdet->pDebug2)ptdet->pDebug2[indexDebug]=lasttv2; + + indexDebug++; + } + lasttv0=tv0; + lasttv1=tv1; + lasttv2=tv2*100; + #endif + indexWas=indexNow; + ptdet->zIndex=(++ptdet->zIndex)%4; + } + ptdet->counter-=(points2do*CTCSS_SCOUNT_MUL); + + #if XPMR_DEBUG0 == 1 + for(i=indexWas;i<points;i++) + { + if(ptdet->pDebug0)ptdet->pDebug0[i]=lasttv0; + if(ptdet->pDebug1)ptdet->pDebug1[i]=lasttv1; + if(ptdet->pDebug2)ptdet->pDebug2[i]=lasttv2; + } + #endif + } + + //TRACEX((" ctcss_detect() thit %i\n",thit)); + + if(pmrChan->rxCtcss->BlankingTimer>0)pmrChan->rxCtcss->BlankingTimer-=points; + if(pmrChan->rxCtcss->BlankingTimer<0)pmrChan->rxCtcss->BlankingTimer=0; + + if(thit>=0 && pmrChan->rxCtcss->decode<0 && !pmrChan->rxCtcss->BlankingTimer) + { + pmrChan->rxCtcss->decode=thit; + } + else if(thit<0 && pmrChan->rxCtcss->decode>=0) + { + pmrChan->rxCtcss->BlankingTimer=SAMPLE_RATE_NETWORK/5; + pmrChan->rxCtcss->decode=-1; + + for(tnum=0;tnum<CTCSS_NUM_CODES;tnum++) + { + t_tdet *ptdet=NULL; + ptdet=&(pmrChan->rxCtcss->tdet[tnum]); + ptdet->decode=0; + ptdet->z[0]=ptdet->z[1]=ptdet->z[2]=ptdet->z[3]=0; + } + } + //TRACEX((" ctcss_detect() thit %i %i\n",thit,pmrChan->rxCtcss->decode)); + return(0); +} +/* + TxTestTone +*/ +static i16 TxTestTone(t_pmr_chan *pChan, i16 function) +{ + if(function==1) + { + pChan->spsSigGen1->enabled=1; + pChan->spsSigGen1->option=1; + pChan->spsTx->source=pChan->spsSigGen1->sink; + } + else + { + pChan->spsSigGen1->option=3; + } + return 0; +} +/* + assumes: + sampling rate is 48KS/s + samples are all 16 bits + samples are filtered and decimated by 1/6th +*/ +t_pmr_chan *createPmrChannel(t_pmr_chan *tChan, i16 numSamples) +{ + i16 i, *inputTmp; + + t_pmr_chan *pChan; + t_pmr_sps *pSps; + t_dec_ctcss *pDecCtcss; + t_tdet *ptdet; + + TRACEX(("createPmrChannel(%p,%i)\n",tChan,numSamples)); + + pChan = (t_pmr_chan *)calloc(sizeof(t_pmr_chan),1); + + if(pChan==NULL) + { + printf("createPmrChannel() failed\n"); + return(NULL); + } + + pChan->nSamplesRx=numSamples; + pChan->nSamplesTx=numSamples; + + pChan->index=pmrChanIndex++; + + for(i=0;i<CTCSS_NUM_CODES;i++) + { + pChan->rxCtcssMap[i]=-1; + } + + pChan->rxCtcssIndex=-1; + + if(tChan==NULL) + { + pChan->rxNoiseSquelchEnable=0; + pChan->rxHpfEnable=0; + pChan->rxDeEmpEnable=0; + pChan->rxCenterSlicerEnable=0; + pChan->rxCtcssDecodeEnable=0; + pChan->rxDcsDecodeEnable=0; + + pChan->rxCarrierPoint = 17000; + pChan->rxCarrierHyst = 2500; + + pChan->rxCtcssFreq=103.5; + + pChan->txHpfEnable=0; + pChan->txLimiterEnable=0; + pChan->txPreEmpEnable=0; + pChan->txLpfEnable=1; + pChan->txCtcssFreq=103.5; + pChan->txMixA=TX_OUT_VOICE; + pChan->txMixB=TX_OUT_LSD; + } + else + { + pChan->rxDemod=tChan->rxDemod; + pChan->rxCdType=tChan->rxCdType; + pChan->rxSquelchPoint = tChan->rxSquelchPoint; + pChan->rxCarrierHyst = 3000; + pChan->rxCtcssFreq=tChan->rxCtcssFreq; + + for(i=0;i<CTCSS_NUM_CODES;i++) + pChan->rxCtcssMap[i]=tChan->rxCtcssMap[i]; + + pChan->txMod=tChan->txMod; + pChan->txHpfEnable=1; + pChan->txLpfEnable=1; + pChan->txCtcssFreq=tChan->txCtcssFreq; + pChan->txMixA=tChan->txMixA; + pChan->txMixB=tChan->txMixB; + pChan->radioDuplex=tChan->radioDuplex; + } + + TRACEX(("misc settings \n")); + + if(pChan->rxCdType==CD_XPMR_NOISE){ + pChan->rxNoiseSquelchEnable=1; + } + + if(pChan->rxDemod==RX_AUDIO_FLAT){ + pChan->rxHpfEnable=1; + pChan->rxDeEmpEnable=1; + } + + pChan->rxCarrierPoint=(pChan->rxSquelchPoint*32767)/100; + pChan->rxCarrierHyst = 3000; //pChan->rxCarrierPoint/15; + + pChan->rxDcsDecodeEnable=0; + + if(pChan->rxCtcssFreq!=0){ + pChan->rxHpfEnable=1; + pChan->rxCenterSlicerEnable=1; + pChan->rxCtcssDecodeEnable=1; + pChan->rxCtcssIndex=CtcssFreqIndex(pChan->rxCtcssFreq); + } + + if(pChan->txMod){ + pChan->txPreEmpEnable=1; + pChan->txLimiterEnable=1; + } + + TRACEX(("calloc buffers \n")); + + pChan->pRxDemod = calloc(numSamples,2); + pChan->pRxNoise = calloc(numSamples,2); + pChan->pRxBase = calloc(numSamples,2); + pChan->pRxHpf = calloc(numSamples,2); + pChan->pRxLsd = calloc(numSamples,2); + pChan->pRxSpeaker = calloc(numSamples,2); + pChan->pRxCtcss = calloc(numSamples,2); + pChan->pRxDcTrack = calloc(numSamples,2); + pChan->pRxLsdLimit = calloc(numSamples,2); + + + pChan->pTxBase = calloc(numSamples,2); + pChan->pTxHpf = calloc(numSamples,2); + pChan->pTxPreEmp = calloc(numSamples,2); + pChan->pTxLimiter = calloc(numSamples,2); + pChan->pTxLsd = calloc(numSamples,2); + pChan->pTxLsdLpf = calloc(numSamples,2); + pChan->pTxComposite = calloc(numSamples,2); + pChan->pSigGen0 = calloc(numSamples,2); + pChan->pSigGen1 = calloc(numSamples,2); + + pChan->pTxCode = calloc(numSamples,2); + pChan->pTxOut = calloc(numSamples,2*2*6); // output buffer + + #if XPMR_DEBUG0 == 1 + pChan->pTxPttIn = calloc(numSamples,2); + pChan->pTxPttOut = calloc(numSamples,2); + pChan->prxDebug0 = calloc(numSamples,2); + pChan->prxDebug1 = calloc(numSamples,2); + pChan->prxDebug2 = calloc(numSamples,2); + pChan->prxDebug3 = calloc(numSamples,2); + pChan->ptxDebug0 = calloc(numSamples,2); + pChan->ptxDebug1 = calloc(numSamples,2); + pChan->ptxDebug2 = calloc(numSamples,2); + pChan->ptxDebug3 = calloc(numSamples,2); + pChan->pNull = calloc(numSamples,2); + for(i=0;i<numSamples;i++)pChan->pNull[i]=((i%(numSamples/2))*8000)-4000; + #endif + + TRACEX(("create ctcss\n")); + + pDecCtcss = (t_dec_ctcss *)calloc(sizeof(t_dec_ctcss),1); + + pChan->rxCtcss=pDecCtcss; + pDecCtcss->enabled=1; + pDecCtcss->gain=1*M_Q8; + pDecCtcss->limit=8192; + pDecCtcss->input=pChan->pRxLsdLimit; + pDecCtcss->testIndex=pChan->rxCtcssIndex; + if(!pDecCtcss->testIndex)pDecCtcss->testIndex=1; + pChan->rxCtcssMap[pChan->rxCtcssIndex]=pChan->rxCtcssIndex; + pDecCtcss->decode=-1; + + for(i=0;i<CTCSS_NUM_CODES;i++) + { + ptdet=&(pChan->rxCtcss->tdet[i]); + ptdet->state=1; + ptdet->setpt=(M_Q15*0.067); // 0.069 + ptdet->hyst =(M_Q15*0.020); + ptdet->counterFactor=coef_ctcss_div[i]; + ptdet->binFactor=(M_Q15*0.135); // was 0.140 + ptdet->fudgeFactor=8; + } + + // General Purpose Function Generator + pSps=pChan->spsSigGen1=createPmrSps(); + pSps->parentChan=pChan; + pSps->sink=pChan->pSigGen1; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->sigProc=SigGen; + pSps->nSamples=pChan->nSamplesTx; + pSps->sampleRate=SAMPLE_RATE_NETWORK; + pSps->freq=10000; // in increments of 0.1 Hz + pSps->outputGain=(.25*M_Q8); + pSps->option=0; + pSps->interpolate=1; + pSps->decimate=1; + pSps->enabled=0; + + + // CTCSS ENCODER + pSps = pChan->spsSigGen0 = createPmrSps(); + pSps->parentChan=pChan; + pSps->sink=pChan->pTxLsd; + pSps->sigProc=SigGen; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesTx; + pSps->sampleRate=SAMPLE_RATE_NETWORK; + pSps->freq=pChan->txCtcssFreq*10; // in increments of 0.1 Hz + pSps->outputGain=(0.5*M_Q8); + pSps->option=0; + pSps->interpolate=1; + pSps->decimate=1; + pSps->enabled=0; + + + // Tx LSD Low Pass Filter + pSps=pChan->spsTxLsdLpf=pSps->nextSps=createPmrSps(); + pSps->source=pChan->pTxLsd; + pSps->sink=pChan->pTxLsdLpf; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=0; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesTx; + pSps->decimator=pSps->decimate=1; + pSps->interpolate=1; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + + if(pChan->txCtcssFreq>203.0) + { + pSps->ncoef=taps_fir_lpf_250_9_66; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_250_9_66; + pSps->nx=taps_fir_lpf_250_9_66; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + pSps->calcAdjust=gain_fir_lpf_250_9_66; + } + else + { + pSps->ncoef=taps_fir_lpf_215_9_88; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_215_9_88; + pSps->nx=taps_fir_lpf_215_9_88; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + pSps->calcAdjust=gain_fir_lpf_215_9_88; + } + + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + + + // RX Process + TRACEX(("create rx\n")); + pSps = NULL; + + // allocate space for first sps and set pointers + pSps=pChan->spsRx=createPmrSps(); + pSps->parentChan=pChan; + pSps->source=NULL; //set when called + pSps->sink=pChan->pRxBase; + pSps->sigProc=pmr_rx_frontend; + pSps->enabled=1; + pSps->decimator=pSps->decimate=6; + pSps->interpolate=pSps->interpolate=1; + pSps->nSamples=pChan->nSamplesRx; + pSps->ncoef=taps_fir_bpf_noise_1; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_3K_1; + pSps->coef2=(void*)coef_fir_bpf_noise_1; + pSps->nx=taps_fir_bpf_noise_1; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_coef)); + pSps->calcAdjust=(gain_fir_lpf_3K_1*256)/0x0100; + pSps->outputGain=(1.0*M_Q8); + pSps->discfactor=2; + pSps->hyst=pChan->rxCarrierHyst; + pSps->setpt=pChan->rxCarrierPoint; + pChan->prxSquelchAdjust=&pSps->setpt; + #if XPMR_DEBUG0 == 1 + pSps->debugBuff0=pChan->pRxDemod; + pSps->debugBuff1=pChan->pRxNoise; + pSps->debugBuff2=pChan->prxDebug0; + #endif + + + // allocate space for next sps and set pointers + // Rx SubAudible Decoder Low Pass Filter + pSps=pSps->nextSps=createPmrSps(); + pSps->parentChan=pChan; + pSps->source=pChan->pRxBase; + pSps->sink=pChan->pRxLsd; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=1; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesRx; + pSps->decimator=pSps->decimate=1; + pSps->interpolate=1; + + if(pChan->rxCtcssFreq>203.5) + { + pSps->ncoef=taps_fir_lpf_250_9_66; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_250_9_66; + pSps->nx=taps_fir_lpf_250_9_66; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + pSps->calcAdjust=gain_fir_lpf_250_9_66; + } + else + { + pSps->ncoef=taps_fir_lpf_215_9_88; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_215_9_88; + pSps->nx=taps_fir_lpf_215_9_88; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + pSps->calcAdjust=gain_fir_lpf_215_9_88; + } + + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + pChan->prxCtcssMeasure=pSps->sink; + pChan->prxCtcssAdjust=&(pSps->outputGain); + + + // allocate space for next sps and set pointers + // CenterSlicer + if(pChan->rxCenterSlicerEnable) + { + pSps=pSps->nextSps=createPmrSps(); + pSps->parentChan=pChan; + pSps->source=pChan->pRxLsd; + pSps->sink=pChan->pRxDcTrack; + pSps->buff=pChan->pRxLsdLimit; + pSps->sigProc=CenterSlicer; + pSps->enabled=1; + pSps->nSamples=pChan->nSamplesRx; + pSps->discfactor=800; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + pSps->setpt=3000; + pSps->inputGainB=1000; // limiter set point + } + + // allocate space for next sps and set pointers + // Rx HPF + pSps=pSps->nextSps=createPmrSps(); + pSps->parentChan=pChan; + pChan->spsRxHpf=pSps; + pSps->source=pChan->pRxBase; + pSps->sink=pChan->pRxHpf; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=1; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesRx; + pSps->decimator=pSps->decimate=1; + pSps->interpolate=1; + pSps->ncoef=taps_fir_hpf_300_9_66; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_hpf_300_9_66; + pSps->nx=taps_fir_hpf_300_9_66; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->calcAdjust=gain_fir_hpf_300_9_66; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + pChan->spsRxOut=pSps; + + // allocate space for next sps and set pointers + // Rx DeEmp + if(pChan->rxDeEmpEnable){ + pSps=pSps->nextSps=createPmrSps(); + pSps->parentChan=pChan; + pChan->spsRxDeEmp=pSps; + pSps->source=pChan->pRxHpf; + pSps->sink=pChan->pRxSpeaker; + pChan->spsRxOut=pSps; // OUTPUT STRUCTURE! maw + pSps->sigProc=gp_inte_00; + pSps->enabled=1; + pSps->nSamples=pChan->nSamplesRx; + + pSps->ncoef=taps_int_lpf_300_1_2; + pSps->size_coef=2; + pSps->coef=(void*)coef_int_lpf_300_1_2; + + pSps->nx=taps_int_lpf_300_1_2; + pSps->size_x=4; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->calcAdjust=gain_int_lpf_300_1_2/2; + pSps->inputGain=(1.0*M_Q8); + pSps->outputGain=(1.0*M_Q8); + pChan->prxVoiceMeasure=pSps->sink; + pChan->prxVoiceAdjust=&(pSps->outputGain); + } + + if(pChan->rxDelayLineEnable) + { + TRACEX(("create delayline\n")); + pSps=pChan->spsDelayLine=pSps->nextSps=createPmrSps(); + pSps->sigProc=DelayLine; + pSps->source=pChan->pRxSpeaker; + pSps->sink=pChan->pRxSpeaker; + pSps->enabled=0; + pSps->inputGain=1*M_Q8; + pSps->outputGain=1*M_Q8; + pSps->nSamples=pChan->nSamplesRx; + pSps->buffSize=4096; + pSps->buff=calloc(4096,2); // one second maximum + pSps->buffLead = (SAMPLE_RATE_NETWORK*0.100); + pSps->buffOutIndex=0; + } + + if(pChan->rxCdType==CD_XPMR_VOX) + { + TRACEX(("create vox measureblock\n")); + pSps=pChan->spsRxVox=pSps->nextSps=createPmrSps(); + pSps->sigProc=MeasureBlock; + pSps->parentChan=pChan; + pSps->source=pChan->pRxBase; + pSps->sink=pChan->prxDebug1; + pSps->inputGain=1*M_Q8; + pSps->outputGain=1*M_Q8; + pSps->nSamples=pChan->nSamplesRx; + pSps->discfactor=3; + pSps->setpt=(0.01*M_Q15); + pSps->hyst=(pSps->setpt/10); + pSps->enabled=1; + } + + // tuning measure block + pSps=pChan->spsMeasure=pSps->nextSps=createPmrSps(); + pSps->parentChan=pChan; + pSps->source=pChan->spsRx->sink; + pSps->sink=pChan->prxDebug2; + pSps->sigProc=MeasureBlock; + pSps->enabled=0; + pSps->nSamples=pChan->nSamplesRx; + pSps->discfactor=10; + + pSps->nextSps=NULL; // last sps in chain RX + + + // CREATE TRANSMIT CHAIN + TRACEX((" create tx\n")); + inputTmp=NULL; + pSps = NULL; + + // allocate space for first sps and set pointers + + // Tx HPF SubAudible + if(pChan->txHpfEnable) + { + pSps=createPmrSps(); + pChan->spsTx=pSps; + pSps->source=pChan->pTxBase; + pSps->sink=pChan->pTxHpf; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=1; + pSps->numChanOut=1; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesTx; + pSps->decimator=pSps->decimate=1; + pSps->interpolate=1; + pSps->ncoef=taps_fir_hpf_300_9_66; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_hpf_300_9_66; + pSps->nx=taps_fir_hpf_300_9_66; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->calcAdjust=gain_fir_hpf_300_9_66; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + inputTmp=pChan->pTxHpf; + } + + // Tx PreEmphasis + if(pChan->txPreEmpEnable) + { + if(pSps==NULL) pSps=pChan->spsTx=createPmrSps(); + else pSps=pSps->nextSps=createPmrSps(); + + pSps->parentChan=pChan; + pSps->source=inputTmp; + pSps->sink=pChan->pTxPreEmp; + + pSps->sigProc=gp_diff; + pSps->enabled=1; + pSps->nSamples=pChan->nSamplesTx; + + pSps->ncoef=taps_int_hpf_4000_1_2; + pSps->size_coef=2; + pSps->coef=(void*)coef_int_hpf_4000_1_2; + + pSps->nx=taps_int_hpf_4000_1_2; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->outputGain=(1*M_Q8); + pSps->calcAdjust=gain_int_hpf_4000_1_2; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + inputTmp=pSps->sink; + } + + // Tx Limiter + if(pChan->txLimiterEnable) + { + if(pSps==NULL) pSps=pChan->spsTx=createPmrSps(); + else pSps=pSps->nextSps=createPmrSps(); + pSps->source=inputTmp; + pSps->sink=pChan->pTxLimiter; + pSps->sigProc=SoftLimiter; + pSps->enabled=1; + pSps->nSamples=pChan->nSamplesTx; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + pSps->setpt=12000; + inputTmp=pSps->sink; + } + + // Composite Mix of Voice and LSD + if((pChan->txMixA==TX_OUT_COMPOSITE)||(pChan->txMixB==TX_OUT_COMPOSITE)) + { + if(pSps==NULL) + pSps=pChan->spsTx=createPmrSps(); + else + pSps=pSps->nextSps=createPmrSps(); + pSps->source=inputTmp; + pSps->sourceB=pChan->pTxLsdLpf; //asdf ??? !!! maw pTxLsdLpf + pSps->sink=pChan->pTxComposite; + pSps->sigProc=pmrMixer; + pSps->enabled=1; + pSps->nSamples=pChan->nSamplesTx; + pSps->inputGain=2*M_Q8; + pSps->inputGainB=1*M_Q8/8; + pSps->outputGain=1*M_Q8; + pSps->setpt=0; + inputTmp=pSps->sink; + pChan->ptxCtcssAdjust=&pSps->inputGainB; + } + + // Chan A Upsampler and Filter + if(pSps==NULL) pSps=pChan->spsTx=createPmrSps(); + else pSps=pSps->nextSps=createPmrSps(); + + pChan->spsTxOutA=pSps; + if(!pChan->spsTx)pChan->spsTx=pSps; + pSps->parentChan=pChan; + + if(pChan->txMixA==TX_OUT_COMPOSITE) + { + pSps->source=pChan->pTxComposite; + } + else if(pChan->txMixA==TX_OUT_LSD) + { + pSps->source=pChan->pTxLsdLpf; + } + else if(pChan->txMixA==TX_OUT_VOICE) + { + pSps->source=pChan->pTxHpf; + } + else if (pChan->txMixA==TX_OUT_AUX) + { + pSps->source=inputTmp; + } + else + { + pSps->source=NULL; + } + + pSps->sink=pChan->pTxOut; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=1; + pSps->numChanOut=2; + pSps->selChanOut=0; + pSps->nSamples=pChan->nSamplesTx; + pSps->interpolate=6; + pSps->ncoef=taps_fir_lpf_3K_1; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_3K_1; + pSps->nx=taps_fir_lpf_3K_1; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->calcAdjust=gain_fir_lpf_3K_1; + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + if(pChan->txMixA==pChan->txMixB)pSps->monoOut=1; + else pSps->monoOut=0; + + + // Chan B Upsampler and Filter + if((pChan->txMixA!=pChan->txMixB)&&(pChan->txMixB!=TX_OUT_OFF)) + { + if(pSps==NULL) pSps=pChan->spsTx=createPmrSps(); + else pSps=pSps->nextSps=createPmrSps(); + + pChan->spsTxOutB=pSps; + pSps->parentChan=pChan; + if(pChan->txMixB==TX_OUT_COMPOSITE) + { + pSps->source=pChan->pTxComposite; + } + else if(pChan->txMixB==TX_OUT_LSD) + { + pSps->source=pChan->pTxLsdLpf; + // pChan->ptxCtcssAdjust=&pSps->inputGain; + } + else if(pChan->txMixB==TX_OUT_VOICE) + { + pSps->source=inputTmp; + } + else if(pChan->txMixB==TX_OUT_AUX) + { + pSps->source=pChan->pTxHpf; + } + else + { + pSps->source=NULL; + } + + pSps->sink=pChan->pTxOut; + pSps->sigProc=pmr_gp_fir; + pSps->enabled=1; + pSps->numChanOut=2; + pSps->selChanOut=1; + pSps->mixOut=0; + pSps->nSamples=pChan->nSamplesTx; + pSps->interpolate=6; + pSps->ncoef=taps_fir_lpf_3K_1; + pSps->size_coef=2; + pSps->coef=(void*)coef_fir_lpf_3K_1; + pSps->nx=taps_fir_lpf_3K_1; + pSps->size_x=2; + pSps->x=(void*)(calloc(pSps->nx,pSps->size_x)); + if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n"); + pSps->calcAdjust=(gain_fir_lpf_3K_1); + pSps->inputGain=(1*M_Q8); + pSps->outputGain=(1*M_Q8); + + } + + pSps->nextSps=NULL; + + #if XPMR_DEBUG0 == 1 + { + TRACEX((" configure tracing\n")); + t_tdet *ptdet; + + pChan->rxCtcss->pDebug0=calloc(numSamples,2); + pChan->rxCtcss->pDebug1=calloc(numSamples,2); + pChan->rxCtcss->pDebug2=calloc(numSamples,2); + + for(i=0;i<CTCSS_NUM_CODES;i++){ + ptdet=&(pChan->rxCtcss->tdet[i]); + ptdet->pDebug0=calloc(numSamples,2); + ptdet->pDebug1=calloc(numSamples,2); + ptdet->pDebug2=calloc(numSamples,2); + } + + // buffer, 2 bytes per sample, and 16 channels + pChan->prxDebug=calloc(numSamples*16,2); + pChan->ptxDebug=calloc(numSamples*16,2); + } + #endif + + TRACEX((" createPmrChannel() end\n")); + + return pChan; +} +/* +*/ +i16 destroyPmrChannel(t_pmr_chan *pChan) +{ + t_pmr_sps *pmr_sps, *tmp_sps; + + TRACEX(("destroyPmrChannel()\n")); + + free(pChan->pRxDemod); + free(pChan->pRxNoise); + free(pChan->pRxBase); + free(pChan->pRxHpf); + free(pChan->pRxLsd); + free(pChan->pRxSpeaker); + free(pChan->pRxDcTrack); + if(pChan->pRxLsdLimit)free(pChan->pRxLsdLimit); + free(pChan->pTxBase); + free(pChan->pTxHpf); + free(pChan->pTxPreEmp); + free(pChan->pTxLimiter); + free(pChan->pTxLsd); + free(pChan->pTxLsdLpf); + if(pChan->pTxComposite)free(pChan->pTxComposite); + free(pChan->pTxCode); + free(pChan->pTxOut); + + if(pChan->pSigGen0)free(pChan->pSigGen0); + if(pChan->pSigGen1)free(pChan->pSigGen1); + + #if XPMR_DEBUG0 == 1 + free(pChan->pTxPttIn); + free(pChan->pTxPttOut); + if(pChan->prxDebug)free(pChan->prxDebug); + if(pChan->ptxDebug)free(pChan->ptxDebug); + free(pChan->rxCtcss->pDebug0); + free(pChan->rxCtcss->pDebug1); + + free(pChan->prxDebug0); + free(pChan->prxDebug1); + free(pChan->prxDebug2); + free(pChan->prxDebug3); + + free(pChan->ptxDebug0); + free(pChan->ptxDebug1); + free(pChan->ptxDebug2); + free(pChan->ptxDebug3); + + i16 i; + for(i=0;i<CTCSS_NUM_CODES;i++) + { + free(pChan->rxCtcss->tdet[i].pDebug0); + free(pChan->rxCtcss->tdet[i].pDebug1); + free(pChan->rxCtcss->tdet[i].pDebug2); + } + #endif + + free(pChan->pRxCtcss); + + pmr_sps=pChan->spsRx; + + while(pmr_sps) + { + tmp_sps = pmr_sps; + pmr_sps = tmp_sps->nextSps; + destroyPmrSps(tmp_sps); + } + + free(pChan); + + return 0; +} +/* +*/ +t_pmr_sps *createPmrSps(void) +{ + t_pmr_sps *pSps; + + TRACEX(("createPmrSps()\n")); + + pSps = (t_pmr_sps *)calloc(sizeof(t_pmr_sps),1); + + if(!pSps)printf("Error: createPmrSps()\n"); + + // pSps->x=calloc(pSps->nx,pSps->size_x); + + return pSps; +} +/* +*/ +i16 destroyPmrSps(t_pmr_sps *pSps) +{ + TRACEX(("destroyPmrSps(%i)\n",pSps->index)); + + if(pSps->x!=NULL)free(pSps->x); + free(pSps); + return 0; +} +/* + PmrRx does the whole buffer +*/ +i16 PmrRx(t_pmr_chan *pChan, i16 *input, i16 *output) +{ + int i,ii; + t_pmr_sps *pmr_sps; + + TRACEX(("PmrRx() %i\n",pChan->frameCountRx)); + + if(pChan==NULL){ + printf("PmrRx() pChan == NULL\n"); + return 1; + } + + pChan->frameCountRx++; + + pmr_sps=pChan->spsRx; // first sps + pmr_sps->source=input; + + if(output!=NULL)pChan->spsRxOut->sink=output; //last sps + + #if 0 + if(pChan->inputBlanking>0) + { + pChan->inputBlanking-=pChan->nSamplesRx; + if(pChan->inputBlanking<0)pChan->inputBlanking=0; + for(i=0;i<pChan->nSamplesRx*6;i++) + input[i]=0; + } + #endif + + // || (pChan->radioDuplex && (pChan->pttIn || pChan->pttOut))) + if(pChan->rxCpuSaver && !pChan->rxCarrierDetect) + { + if(pChan->spsRxHpf)pChan->spsRxHpf->enabled=0; + if(pChan->spsRxDeEmp)pChan->spsRxDeEmp->enabled=0; + } + else + { + if(pChan->spsRxHpf)pChan->spsRxHpf->enabled=1; + if(pChan->spsRxDeEmp)pChan->spsRxDeEmp->enabled=1; + } + + i=0; + while(pmr_sps!=NULL && pmr_sps!=0) + { + TRACEX(("PmrRx() sps %i\n",i++)); + pmr_sps->sigProc(pmr_sps); + pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps); + //pmr_sps=NULL; // sph maw + } + + #define XPMR_VOX_HANGTIME 2000 + + if(pChan->rxCdType==CD_XPMR_VOX) + { + if(pChan->spsRxVox->compOut) + { + pChan->rxVoxTimer=XPMR_VOX_HANGTIME; //VOX HangTime in ms + } + if(pChan->rxVoxTimer>0) + { + pChan->rxVoxTimer-=MS_PER_FRAME; + pChan->rxCarrierDetect=1; + } + else + { + pChan->rxVoxTimer=0; + pChan->rxCarrierDetect=0; + } + } + else + { + pChan->rxCarrierDetect=!pChan->spsRx->compOut; + } + + if( !pChan->rxCpuSaver || pChan->rxCarrierDetect + || pChan->rxCtcss->decode!=-1) ctcss_detect(pChan); + + #if XPMR_DEBUG0 == 1 + // TRACEX(("Write file.\n")); + ii=0; + if(pChan->b.rxCapture) + { + for(i=0;i<pChan->nSamplesRx;i++) + { + pChan->prxDebug[ii++]=input[i*2*6]; // input data + pChan->prxDebug[ii++]=output[i]; // output data + pChan->prxDebug[ii++]=pChan->rxCarrierDetect*M_Q14; // carrier detect + if(pChan->rxCtcss) + pChan->prxDebug[ii++]=pChan->rxCtcss->decode*M_Q15/CTCSS_NUM_CODES; // decoded ctcss + else + pChan->prxDebug[ii++]=0; + + pChan->prxDebug[ii++]=pChan->pRxNoise[i]; // rssi + pChan->prxDebug[ii++]=pChan->pRxBase[i]; // decimated, low pass filtered + pChan->prxDebug[ii++]=pChan->pRxHpf[i]; // output to network + pChan->prxDebug[ii++]=pChan->pRxSpeaker[i]; + + pChan->prxDebug[ii++]=pChan->pRxLsd[i]; // CTCSS Filtered + pChan->prxDebug[ii++]=pChan->pRxDcTrack[i]; // DC Restoration + pChan->prxDebug[ii++]=pChan->pRxLsdLimit[i]; // Amplitude Limited + + //pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex+1].pDebug0[i]; // Upper Adjacent CTCSS Code + pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug0[i]; // Primary CTCSS Code + pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug1[i]; // dv/dt of decoder output + pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug2[i]; + + //pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex-1].pDebug0[i]; // Lower Adjacent CTCSS Code + + pChan->prxDebug[ii++]=pChan->prxDebug1[i]; // Measure Output for VOX + pChan->prxDebug[ii++]=pChan->prxDebug2[i]; // Measure Output for Tuning + } + } + #endif + + return 0; +} +/* + PmrTx does the whole buffer +*/ +i16 PmrTx(t_pmr_chan *pChan, i16 *input, i16 *output) +{ + int i, hit=0; + t_pmr_sps *pmr_sps; + + pChan->frameCountTx++; + + TRACEX(("PmrTx() %i\n",pChan->frameCountTx)); + + if(pChan==NULL){ + printf("PmrTx() pChan == NULL\n"); + return 1; + } + + if(pChan->b.startSpecialTone) + { + pChan->b.startSpecialTone=0; + pChan->spsSigGen1->option=1; + pChan->spsSigGen1->enabled=1; + pChan->b.doingSpecialTone=1; + } else if(pChan->b.stopSpecialTone) + { + pChan->b.stopSpecialTone=0; + pChan->spsSigGen1->option=0; + pChan->b.doingSpecialTone=0; + pChan->spsSigGen1->enabled=0; + } else if(pChan->b.doingSpecialTone) + { + pChan->spsSigGen1->sink=output; + pChan->spsSigGen1->sigProc(pChan->spsSigGen1); + for(i=0;i<(pChan->nSamplesTx*2*6);i+=2)output[i+1]=output[i]; + return 0; + } + + // handle transmitter ptt input + hit=0; + if( pChan->txPttIn && pChan->txState==0) + { + pChan->txState = 2; + pChan->txPttOut=1; + pChan->spsSigGen0->freq=pChan->txCtcssFreq*10; + pChan->spsSigGen0->option=1; + pChan->spsSigGen0->enabled=1; + if(pChan->spsTxOutA)pChan->spsTxOutA->enabled=1; + if(pChan->spsTxOutB)pChan->spsTxOutB->enabled=1; + if(pChan->spsTxLsdLpf)pChan->spsTxLsdLpf->enabled=1; + TRACEX((" TxOn\n")); + } + else if(!pChan->txPttIn && pChan->txState==2) + { + if( pChan->txTocType==TOC_NONE || !pChan->txCtcssFreq ) + { + hit=1; + TRACEX((" Tx Off Immediate.\n")); + } + else if(pChan->txCtcssFreq && pChan->txTocType==TOC_NOTONE) + { + pChan->txState=3; + pChan->txHangTime=TOC_NOTONE_TIME/MS_PER_FRAME; + pChan->spsSigGen0->option=3; + TRACEX((" Tx Turn Off No Tone Start.\n")); + } + else + { + pChan->txState=3; + pChan->txHangTime=0; + pChan->spsSigGen0->option=2; + TRACEX((" Tx Turn Off Phase Shift Start.\n")); + } + } + else if(pChan->txState==3) + { + if(pChan->txHangTime) + { + if(--pChan->txHangTime==0)hit=1; + + } + else if(pChan->txHangTime<=0 && pChan->spsSigGen0->state==0) + { + hit=1; + TRACEX((" Tx Off TOC.\n")); + } + if(pChan->txPttIn) + { + TRACEX((" Tx Key During HangTime\n")); + if((pChan->txTocType==TOC_PHASE)||(pChan->txTocType==TOC_NONE)) + { + pChan->txState = 2; + hit=0; + } + } + } + + if( pChan->txCpuSaver && !hit && !pChan->txPttIn && !pChan->txPttOut && pChan->txState==0 ) return (1); + + if(hit) + { + pChan->txPttOut=0; + pChan->txState=0; + if(pChan->spsTxLsdLpf)pChan->spsTxLsdLpf->option=3; + if(pChan->spsTxOutA)pChan->spsTxOutA->option=3; + if(pChan->spsTxOutB)pChan->spsTxOutB->option=3; + TRACEX((" Tx Off hit.\n")); + } + + if(pChan->spsSigGen0) + { + pChan->spsSigGen0->sigProc(pChan->spsSigGen0); + pmr_sps=pChan->spsSigGen0->nextSps; + i=0; + while(pmr_sps!=NULL && pmr_sps!=0) + { + TRACEX((" PmrTx() subaudible sps %i\n",i++)); + //printf(" CTCSS ENCODE %i %i\n",pChan->spsSigGen0->freq,pChan->spsSigGen0->outputGain); + pmr_sps->sigProc(pmr_sps); + pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps); + } + } + + if(pChan->spsSigGen1 && pChan->spsSigGen1->enabled) + { + pChan->spsSigGen1->sigProc(pChan->spsSigGen1); + } + + // Do Voice + pmr_sps=pChan->spsTx; + if(!pChan->spsSigGen1->enabled)pmr_sps->source=input; + else input=pmr_sps->source; + + if(output!=NULL) + { + if(pChan->spsTxOutA)pChan->spsTxOutA->sink=output; + if(pChan->spsTxOutB)pChan->spsTxOutB->sink=output; + } + + i=0; + while(pmr_sps!=NULL && pmr_sps!=0) + { + TRACEX((" PmrTx() sps %i\n",i++)); + pmr_sps->sigProc(pmr_sps); + pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps); + } + + + if(pChan->txMixA==TX_OUT_OFF || !pChan->txPttOut){ + for(i=0;i<pChan->nSamplesTx*2*6;i+=2)output[i]=0; + } + + if(pChan->txMixB==TX_OUT_OFF || !pChan->txPttOut ){ + for(i=0;i<pChan->nSamplesTx*2*6;i+=2)output[i+1]=0; + } + + #if XPMR_DEBUG0 == 1 + if(pChan->b.txCapture) + { + i16 ii=0; + for(i=0;i<pChan->nSamplesTx;i++) + { + pChan->ptxDebug[ii++]=input[i]; + pChan->ptxDebug[ii++]=output[i*2*6]; + pChan->ptxDebug[ii++]=output[(i*2*6)+1]; + pChan->ptxDebug[ii++]=pChan->txPttIn*8192; + + pChan->ptxDebug[ii++]=pChan->txPttOut*8192; + if(pChan->txHpfEnable)pChan->ptxDebug[ii++]=pChan->pTxHpf[i]; + else pChan->ptxDebug[ii++]=0; + if(pChan->txPreEmpEnable)pChan->ptxDebug[ii++]=pChan->pTxPreEmp[i]; + else pChan->ptxDebug[ii++]=0; + if(pChan->txLimiterEnable)pChan->ptxDebug[ii++]=pChan->pTxLimiter[i]; + else pChan->ptxDebug[ii++]=0; + + pChan->ptxDebug[ii++]=pChan->pTxLsd[i]; + pChan->ptxDebug[ii++]=pChan->pTxLsdLpf[i]; + pChan->ptxDebug[ii++]=pChan->pTxComposite[i]; + pChan->ptxDebug[ii++]=pChan->pSigGen1[i]; + + #if 1 + pChan->ptxDebug[ii++]=pChan->ptxDebug0[i]; + pChan->ptxDebug[ii++]=pChan->ptxDebug1[i]; + pChan->ptxDebug[ii++]=pChan->ptxDebug2[i]; + pChan->ptxDebug[ii++]=pChan->ptxDebug3[i]; + #else + pChan->ptxDebug[ii++]=0; + pChan->ptxDebug[ii++]=0; + pChan->ptxDebug[ii++]=0; + pChan->ptxDebug[ii++]=0; + #endif + } + } + #endif + + return 0; +} +/* end of file */ diff --git a/channels/xpmr/xpmr.h b/channels/xpmr/xpmr.h new file mode 100755 index 0000000000000000000000000000000000000000..d51aa6dca327250601ddf230f47bd8bd106aeb7b --- /dev/null +++ b/channels/xpmr/xpmr.h @@ -0,0 +1,553 @@ +/* + * xpmr.h - for Xelatec Private Mobile Radio Processes + * + * All Rights Reserved. Copyright (C)2007, Xelatec, LLC + * + * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This version may be optionally licenced under the GNU LGPL licence. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + */ + +/*! \file + * + * \brief Private Land Mobile Radio Channel Voice and Signaling Processor + * + * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC + */ + +#ifndef XPMR_H +#define XPMR_H 1 + +#ifdef CHAN_USBRADIO +#define XPMR_DEBUG0 1 +#define XPMR_TRACE 0 +#else +#define XPMR_DEBUG0 1 +#define XPMR_TRACE 1 +#endif + +#if(XPMR_TRACE == 1) +#define TRACEX(a) {printf a;} +#define TRACEXL(a) {printf("%s @ %u : ",__FILE__ ,__LINE__); printf a; } +#define TRACEXT(a) { struct timeval hack; gettimeofday(&hack,NULL); printf("%ld.",hack.tv_sec%100000); printf("%i : ",(int)hack.tv_usec); printf a; } +#else +#define TRACEX(a) +#define TRACEXL(a) +#define TRACEXT(a) +#endif + +#define i8 int8_t +#define u8 u_int8_t +#define i16 int16_t +#define u16 u_int16_t +#define i32 int32_t +#define u32 u_int32_t +#define i64 int64_t +#define u64 u_int64_t + +#define M_Q24 0x01000000 // +#define M_Q23 0x00800000 // +#define M_Q22 0x00400000 // +#define M_Q21 0x00200000 // +#define M_Q20 0x00100000 // 1048576 +#define M_Q19 0x00080000 // 524288 +#define M_Q18 0x00040000 // 262144 +#define M_Q17 0x00020000 // 131072 +#define M_Q16 0x00010000 // 65536 +#define M_Q15 0x00008000 // 32768 +#define M_Q14 0x00004000 // 16384 +#define M_Q13 0x00002000 // 8182 +#define M_Q12 0x00001000 // 4096 +#define M_Q11 0x00000800 // 2048 +#define M_Q10 0x00000400 // 1024 +#define M_Q9 0x00000200 // 512 +#define M_Q8 0x00000100 // 256 +#define M_Q7 0x00000080 // 128 +#define M_Q6 0x00000040 // 64 +#define M_Q5 0x00000020 // 32 +#define M_Q4 0x00000010 // 16 +#define M_Q3 0x00000008 // 16 +#define M_Q2 0x00000004 // 16 +#define M_Q1 0x00000002 // 16 +#define M_Q0 0x00000001 // 16 + +#define RADIANS_PER_CYCLE (2*M_PI) + +#define SAMPLE_RATE_INPUT 48000 +#define SAMPLE_RATE_NETWORK 8000 + +#define SAMPLES_PER_BLOCK 160 +#define MS_PER_FRAME 20 + +#define CTCSS_NUM_CODES 38 +#define CTCSS_SCOUNT_MUL 100 +#define CTCSS_INTEGRATE 3932 // 32767*.120 // 120/1000 // 0.120 +#define CTCSS_INPUT_LIMIT 1000 +#define CTCSS_DETECT_POINT 1989 +#define CTCSS_HYSTERSIS 200 + +#define CTCSS_TURN_OFF_TIME 160 // ms +#define CTCSS_TURN_OFF_SHIFT 240 // degrees +#define TOC_NOTONE_TIME 600 // ms + +#ifndef CHAN_USBRADIO +enum {RX_AUDIO_NONE,RX_AUDIO_SPEAKER,RX_AUDIO_FLAT}; +enum {TX_AUDIO_NONE,TX_AUDIO_FLAT,TX_AUDIO_FILTERED,TX_AUDIO_PROC}; +enum {CD_IGNORE,CD_XPMR_NOISE,CD_XPMR_VOX,CD_HID,CD_HID_INVERT}; +enum {SD_IGNORE,SD_HID,SD_HID_INVERT,SD_XPMR}; // no,external,externalinvert,software +enum {RX_KEY_CARRIER,RX_KEY_CARRIER_CODE}; +enum {TX_OUT_OFF,TX_OUT_VOICE,TX_OUT_LSD,TX_OUT_COMPOSITE,TX_OUT_AUX}; +enum {TOC_NONE,TOC_PHASE,TOC_NOTONE}; +#endif + +/* + one structure for each ctcss tone to decode +*/ +typedef struct +{ + i16 counter; // counter to next sample + i16 counterFactor; // full divisor used to increment counter + i16 binFactor; + i16 fudgeFactor; + i16 peak; // peak amplitude now maw sph now + i16 enabled; + i16 state; // dead, running, error + i16 zIndex; // z bucket index + i16 z[4]; // maw sph today + i16 zi; + i16 dvu; + i16 dvd; + i16 zd; + i16 setpt; + i16 hyst; + i16 decode; + i16 diffpeak; + i16 debug; // value held from last pass + i16 *pDebug0; // pointer to debug output + i16 *pDebug1; // pointer to debug output + i16 *pDebug2; // pointer to debug output + +} t_tdet; + +typedef struct +{ + i16 enabled; // if 0 none, 0xFFFF all tones, or single tone + i16 *input; + i16 clamplitude; + i16 center; + i16 decode; // current ctcss decode index + i32 BlankingTimer; + u32 TurnOffTimer; + t_tdet tdet[CTCSS_NUM_CODES]; + i16 gain; + i16 limit; + i16 *pDebug0; + i16 *pDebug1; + i16 *pDebug2; + i16 testIndex; + i16 multiFreq; + i8 relax; + +} t_dec_ctcss; + +typedef struct +{ + i16 enabled; // if 0 none, 0xFFFF all tones, or single tone + i16 clamplitude; + i16 center; + i16 decode; // current ctcss decode value + i32 BlankingTimer; + u32 TurnOffTimer; + i16 gain; + i16 limit; + i16 *pDebug0; + i16 *pDebug1; + i16 rxPolarity; +} t_dec_dcs; + +/* + Low Speed Data decoding both polarities +*/ +typedef struct +{ + i16 counter; // counter to next sample + i16 synced; + u32 syncCorr[2]; + u32 data[2]; + i16 state; // disabled, enabled, + i16 decode; + i16 debug; + + i16 polarity; + u32 frameNum; + + u16 area; + u16 chan; + u16 home; + u16 id; + u16 free; + + u16 crc; + i16 rssi; + +} t_decLsd; + + +/* general purpose pmr signal processing element */ + +struct t_pmr_chan; + +typedef struct t_pmr_sps +{ + i16 index; // unique to each instance + + i16 enabled; // enabled/disabled + + struct t_pmr_chan *parentChan; + + i16 *source; // source buffer + i16 *sourceB; // source buffer B + i16 *sink; // sink buffer + + i16 numChanOut; // allows output direct to interleaved buffer + i16 selChanOut; + + u32 ticks; + + void *buff; // this structure's internal buffer + + i16 *debugBuff0; // debug buffer + i16 *debugBuff1; // debug buffer + i16 *debugBuff2; // debug buffer + i16 *debugBuff3; // debug buffer + + i16 nSamples; // number of samples in the buffer + + u32 buffSize; // buffer maximum index + u32 buffInIndex; // index to current input point + u32 buffOutIndex; // index to current output point + u32 buffLead; // lead of input over output through cb + + i16 decimate; // decimation or interpolation factor (could be put in coef's) + i16 interpolate; + i16 decimator; // like the state this must be saved between calls (could be put in x's) + + u32 sampleRate; // in Hz for elements in this structure + u32 freq; // in 0.1 Hz + + i16 measPeak; // do measure Peak + i16 amax; // buffer amplitude maximum + i16 amin; // buffer amplitude minimum + i16 apeak; // buffer amplitude peak value (peak to peak)/2 + i16 setpt; // amplitude set point for amplitude comparator + i16 hyst; // hysterysis for amplitude comparator + i16 compOut; // amplitude comparator output + + i32 discounteru; // amplitude detector integrator discharge counter upper + i32 discounterl; // amplitude detector integrator discharge counter lower + i32 discfactor; // amplitude detector integrator discharge factor + + i16 err; // error condition + i16 option; // option / request zero + i16 state; // stopped, start, stopped assumes zero'd + + i16 cleared; // output buffer cleared + + i16 delay; + i16 decode; + + i32 inputGain; // apply to input data ? in Q7.8 format + i32 inputGainB; // apply to input data ? in Q7.8 format + i32 outputGain; // apply to output data ? in Q7.8 format + i16 mixOut; + i16 monoOut; + + i16 filterType; // iir, fir, 1, 2, 3, 4 ... + + i16 (*sigProc)(struct t_pmr_sps *sps); // function to call + + i32 calcAdjust; // final adjustment + i16 nx; // number of x history elements + i16 ncoef; // number of coefficients + i16 size_x; // size of each x history element + i16 size_coef; // size of each coefficient + void *x; // history registers + void *x2; // history registers, 2nd bank + void *coef; // coefficients + void *coef2; // coefficients 2 + + void *nextSps; // next Sps function + +} t_pmr_sps; + +/* + pmr channel +*/ +typedef struct t_pmr_chan +{ + i16 index; // which one + i16 enabled; // enabled/disabled + i16 status; // ok, error, busy, idle, initializing + + i16 nSamplesRx; // max frame size + i16 nSamplesTx; + + i32 inputSampleRate; // in S/s 48000 + i32 baseSampleRate; // in S/s 8000 + + i16 inputGain; + i16 inputOffset; + + u32 frameCountRx; // number processed + u32 frameCountTx; + + i32 txHangTime; + i32 txTurnOff; + + i16 rxDC; // average DC value of input + i16 rxSqSet; // carrier squelch threshold + i16 rxSqHyst; // carrier squelch hysterysis + i16 rxRssi; // current Rssi level + i16 rxQuality; // signal quality metric + i16 rxCarrierDetect; // carrier detect + i16 rxCdType; + i16 rxExtCarrierDetect; + i32 inputBlanking; // Tx pulse eliminator + + i16 rxDemod; // see enum + i16 txMod; // + + i16 rxNoiseSquelchEnable; + i16 rxHpfEnable; + i16 rxDeEmpEnable; + i16 rxCenterSlicerEnable; + i16 rxCtcssDecodeEnable; + i16 rxDcsDecodeEnable; + i16 rxDelayLineEnable; + + i16 txHpfEnable; + i16 txLimiterEnable; + i16 txPreEmpEnable; + i16 txLpfEnable; + + char radioDuplex; + + struct { + unsigned pmrNoiseSquelch:1; + unsigned rxHpf:1; + unsigned txHpf:1; + unsigned txLpf:1; + unsigned rxDeEmphasis:1; + unsigned txPreEmphasis:1; + unsigned startSpecialTone:1; + unsigned stopSpecialTone:1; + unsigned doingSpecialTone:1; + unsigned extCarrierDetect:1; + unsigned txCapture:1; + unsigned rxCapture:1; + }b; + + i16 dummy; + + i32 txScramFreq; + i32 rxScramFreq; + + i16 gainVoice; + i16 gainSubAudible; + + i16 txMixA; // Off, Ctcss, Voice, Composite + i16 txMixB; // Off, Ctcss, Voice, Composite + + i16 rxMuting; + + i16 rxCpuSaver; + i16 txCpuSaver; + + i8 rxSqMode; // 0 open, 1 carrier, 2 coded + + i8 cdMethod; + + i16 rxSquelchPoint; + + i16 rxCarrierPoint; + i16 rxCarrierHyst; + + i16 rxCtcssMap[CTCSS_NUM_CODES]; + + i16 txCtcssTocShift; + i16 txCtcssTocTime; + i8 txTocType; + + float txCtcssFreq; + float rxCtcssFreq; + float rxInputGain; + + i16 rxCtcssIndex; + + i16 txPttIn; // from external request + i16 txPttOut; // to radio hardware + + i16 bandwidth; // wide/narrow + i16 txCompand; // type + i16 rxCompand; // + + i16 txEqRight; // muted, flat, pre-emp limited filtered + i16 txEqLeft; + + i16 txPotRight; // + i16 txPotLeft; // + + i16 rxPotRight; // + i16 rxPotLeft; // + + i16 function; + + i16 txState; // off,settling,on,hangtime,turnoff + + t_pmr_sps *spsMeasure; // measurement block + + t_pmr_sps *spsRx; // 1st signal processing struct + t_pmr_sps *spsRxLsd; + t_pmr_sps *spsRxDeEmp; + t_pmr_sps *spsRxHpf; + t_pmr_sps *spsRxVox; + t_pmr_sps *spsDelayLine; // Last signal processing struct + t_pmr_sps *spsRxOut; // Last signal processing struct + + t_pmr_sps *spsTx; // 1st signal processing struct + + t_pmr_sps *spsTxLsdLpf; + t_pmr_sps *spsTxOutA; // Last signal processing struct + + t_pmr_sps *spsTxOutB; // Last signal processing struct + + t_pmr_sps *spsSigGen0; // ctcss + t_pmr_sps *spsSigGen1; // test and other tones + + // tune tweaks + + i32 rxVoxTimer; // Vox Hang Timer + + i16 *prxSquelchAdjust; + + // i16 *prxNoiseMeasure; // for autotune + // i32 *prxNoiseAdjust; + + i16 *prxVoiceMeasure; + i32 *prxVoiceAdjust; + + i16 *prxCtcssMeasure; + i32 *prxCtcssAdjust; + + i16 *ptxVoiceAdjust; // from calling application + i32 *ptxCtcssAdjust; // from calling application + + i32 *ptxLimiterAdjust; // from calling application + + i16 *pRxDemod; // buffers + i16 *pRxBase; // decimated lpf input + i16 *pRxNoise; + i16 *pRxLsd; // subaudible only + i16 *pRxHpf; // subaudible removed + i16 *pRxDeEmp; // EIA Audio + i16 *pRxSpeaker; // EIA Audio + i16 *pRxDcTrack; // DC Restored LSD + i16 *pRxLsdLimit; // LSD Limited + i16 *pRxCtcss; // + i16 *pRxSquelch; + + i16 *pTxBase; // input data + i16 *pTxHpf; + i16 *pTxPreEmp; + i16 *pTxLimiter; + i16 *pTxLsd; + i16 *pTxLsdLpf; + i16 *pTxComposite; + i16 *pTxMod; // upsampled, low pass filtered + + i16 *pTxOut; // + + i16 *pTxPttIn; + i16 *pTxPttOut; + i16 *pTxHang; + i16 *pTxCode; + + i16 *pSigGen0; + i16 *pSigGen1; + + i16 *pAlt0; + i16 *pAlt1; + + i16 *pNull; + + i16 *prxDebug; // consolidated debug buffer + i16 *ptxDebug; // consolidated debug buffer + + i16 *prxDebug0; + i16 *prxDebug1; + i16 *prxDebug2; + i16 *prxDebug3; + + i16 *ptxDebug0; + i16 *ptxDebug1; + i16 *ptxDebug2; + i16 *ptxDebug3; + + t_dec_ctcss *rxCtcss; + + i16 clamplitudeDcs; + i16 centerDcs; + u32 dcsBlankingTimer; + i16 dcsDecode; // current dcs decode value + + i16 clamplitudeLsd; + i16 centerLsd; + t_decLsd decLsd[2]; // for both polarities + +} t_pmr_chan; + +static i16 TxTestTone(t_pmr_chan *pChan, i16 function); + +t_pmr_chan *createPmrChannel(t_pmr_chan *tChan, i16 numSamples); +t_pmr_sps *createPmrSps(void); +i16 destroyPmrChannel(t_pmr_chan *pChan); +i16 destroyPmrSps(t_pmr_sps *pSps); +i16 pmr_rx_frontend(t_pmr_sps *mySps); +i16 pmr_gp_fir(t_pmr_sps *mySps); +i16 pmr_gp_iir(t_pmr_sps *mySps); +i16 gp_inte_00(t_pmr_sps *mySps); +i16 gp_diff(t_pmr_sps *mySps); +i16 CenterSlicer(t_pmr_sps *mySps); +i16 ctcss_detect(t_pmr_chan *pmrChan); +i16 SoftLimiter(t_pmr_sps *mySps); +i16 SigGen(t_pmr_sps *mySps); +i16 pmrMixer(t_pmr_sps *mySps); +i16 DelayLine(t_pmr_sps *mySps); +i16 PmrRx(t_pmr_chan *PmrChan, i16 *input, i16 *output); +i16 PmrTx(t_pmr_chan *PmrChan, i16 *input, i16 *output); +i16 CtcssFreqIndex(float freq); +i16 MeasureBlock(t_pmr_sps *mySps); +#endif /* ! XPMR_H */ + +/* end of file */ + + + diff --git a/channels/xpmr/xpmr_coef.h b/channels/xpmr/xpmr_coef.h new file mode 100755 index 0000000000000000000000000000000000000000..4b7274e5a950ae4580e4a8f92bda91e1599e2817 --- /dev/null +++ b/channels/xpmr/xpmr_coef.h @@ -0,0 +1,963 @@ +/* + * xpmr_coef.h - for Xelatec Private Mobile Radio Processes + * + * All Rights Reserved. Copyright (C)2007, Xelatec, LLC + * + * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This version may be optionally licenced under the GNU LGPL licence. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + * Some filter coeficients via 'WinFilter' http://www.winfilter.20m.com. + * + */ + +/*! \file + * + * \brief Private Land Mobile Radio Channel Voice and Signaling Processor + * + * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC + */ + +#ifndef XPMR_COEF_H +#define XMPR_COEF_H 1 + +// frequencies in 0.1 Hz +const u32 dtmf_row[] = +{ + 6970, 7700, 8520, 9410 +}; +const u32 dtmf_col[] = +{ + 12090, 13360, 14770, 16330 +}; + +const i16 coef_dcs_rx = 1488; // dcs rx data divisor for oversampling 8000/134.4 +const i16 coef_dcs_tx = 5952; // dcs tx data divisor + +const i16 coef_lsd_div = 672; // low speed data divisor +const u32 coef_lsd_sync = 0x158; // 000101011000 +const u32 coef_lsd_sync_pattern[] = {0x0000000F, 0x0F0FF000}; + +#define CTCSS_COEF_INT 120 +#define CTCSS_SAMPLE_RATE 8000 +#define TDIV(x) ((CTCSS_SAMPLE_RATE*1000/x)+5)/10 + +i32 coef_ctcss[4][5]= +{ + // freq, divisor, integrator, filter + {770,TDIV(770),CTCSS_COEF_INT,0,0}, + {1000,TDIV(1000),CTCSS_COEF_INT,0,0}, + {1035,TDIV(1035),CTCSS_COEF_INT,0,0}, + {0,0,0,0} +}; + + +i16 coef_ctcss_div[]= +{ +2985, // 00 067.0 +2782, // 01 071.9 +2688, // 02 074.4 +2597, // 03 077.0 +2509, // 04 079.7 +2424, // 05 082.5 +2342, // 06 085.4 +2260, // 07 088.5 +2186, // 08 091.5 +2110, // 09 094.8 +2053, // 10 097.4 +2000, // 11 100.0 +1932, // 12 103.5 +1866, // 13 107.2 +1803, // 14 110.9 +1742, // 15 114.8 +1684, // 16 118.8 +1626, // 17 123.0 +1571, // 18 127.3 +1517, // 19 131.8 +1465, // 20 136.5 +1415, // 21 141.3 +1368, // 22 146.2 +1321, // 23 151.4 +1276, // 24 156.7 +1233, // 25 162.2 +1191, // 26 167.9 +1151, // 27 173.8 +1112, // 28 179.9 +1074, // 29 186.2 +1037, // 30 192.8 +983, // 31 203.5 +949, // 32 210.7 +917, // 33 218.1 +886, // 34 225.7 +856, // 35 233.6 +827, // 36 241.8 +799 // 37 250.3 +}; + +float freq_ctcss[]= +{ +067.0, // 00 +071.9, // 01 +074.4, // 02 +077.0, // 03 +079.7, // 04 +082.5, // 05 +085.4, // 06 +088.5, // 07 +091.5, // 08 +094.8, // 09 +097.4, // 10 +100.0, // 11 +103.5, // 12 +107.2, // 13 +110.9, // 14 +114.8, // 15 +118.8, // 16 +123.0, // 17 +127.3, // 18 +131.8, // 19 +136.5, // 20 +141.3, // 21 +146.2, // 22 +151.4, // 23 +156.7, // 24 +162.2, // 25 +167.9, // 26 +173.8, // 27 +179.9, // 28 +186.2, // 29 +192.8, // 30 +203.5, // 31 +210.7 , // 32 +218.1 , // 33 +225.7 , // 34 +233.6 , // 35 +241.8 , // 36 +250.3 // 37 +}; + +/* + noise squelch carrier detect filter +*/ +static const int16_t taps_fir_bpf_noise_1 = 66; +static const int32_t gain_fir_bpf_noise_1 = 65536; +static const int16_t coef_fir_bpf_noise_1[] = { + 139, + -182, + -269, + -66, + 56, + 59, + 250, + 395, + -80, + -775, + -557, + 437, + 779, + 210, + -17, + 123, + -692, + -1664, + -256, + 2495, + 2237, + -1018, + -2133, + -478, + -1134, + -2711, + 2642, + 10453, + 4010, + -14385, + -16488, + 6954, + 23030, + 6954, + -16488, + -14385, + 4010, + 10453, + 2642, + -2711, + -1134, + -478, + -2133, + -1018, + 2237, + 2495, + -256, + -1664, + -692, + 123, + -17, + 210, + 779, + 437, + -557, + -775, + -80, + 395, + 250, + 59, + 56, + -66, + -269, + -182, + 139, + 257 +}; +/* + tbd +*/ +static const int16_t taps_fir_lpf_3K_1 = 66; +static const int32_t gain_fir_lpf_3K_1 = 131072; +static const int16_t coef_fir_lpf_3K_1[] = { + 259, + 58, + -185, + -437, + -654, + -793, + -815, + -696, + -434, + -48, + 414, + 886, + 1284, + 1523, + 1529, + 1254, + 691, + -117, + -1078, + -2049, + -2854, + -3303, + -3220, + -2472, + -995, + 1187, + 3952, + 7086, + 10300, + 13270, + 15672, + 17236, + 17778, + 17236, + 15672, + 13270, + 10300, + 7086, + 3952, + 1187, + -995, + -2472, + -3220, + -3303, + -2854, + -2049, + -1078, + -117, + 691, + 1254, + 1529, + 1523, + 1284, + 886, + 414, + -48, + -434, + -696, + -815, + -793, + -654, + -437, + -185, + 58, + 259, + 393 +}; + +/************************************************************** +Filter type: Low Pass +Filter model: Butterworth +Filter order: 9 +Sampling Frequency: 8 KHz +Cut Frequency: 0.250000 KHz +Coefficents Quantization: 16-bit +***************************************************************/ +static const int16_t taps_fir_lpf_250_11_64 = 64; +static const int32_t gain_fir_lpf_250_11_64 = 262144; +static const int16_t coef_fir_lpf_250_11_64[] = +{ + 366, + -3, + -418, + -865, + -1328, + -1788, + -2223, + -2609, + -2922, + -3138, + -3232, + -3181, + -2967, + -2573, + -1988, + -1206, + -228, + 937, + 2277, + 3767, + 5379, + 7077, + 8821, + 10564, + 12259, + 13855, + 15305, + 16563, + 17588, + 18346, + 18812, + 18968, + 18812, + 18346, + 17588, + 16563, + 15305, + 13855, + 12259, + 10564, + 8821, + 7077, + 5379, + 3767, + 2277, + 937, + -228, + -1206, + -1988, + -2573, + -2967, + -3181, + -3232, + -3138, + -2922, + -2609, + -2223, + -1788, + -1328, + -865, + -418, + -3, + 366, + 680 +}; + +// de-emphasis integrator 300 Hz with 8KS/s +// a0, b1 +static const int16_t taps_int_lpf_300_1_2 = 2; +static const int32_t gain_int_lpf_300_1_2 = 8182; +static const int16_t coef_int_lpf_300_1_2[]={ +6878, +25889 +}; + +// pre-emphasis differentiator 4000 Hz with 8KS/s +// a0,a1,b0, +static const int16_t taps_int_hpf_4000_1_2 = 2; +static const int32_t gain_int_hpf_4000_1_2 = 16384; +static const int16_t coef_int_hpf_4000_1_2[]={ +17610, +-17610, +2454 +}; + + +/* + ltr crc table + from http://www.radioreference.com/forums/showthread.php?t=24126 +*/ + +static const u8 ltr_table[]= +{ +0x38, // 00 Area 0111000 +0x1c, // 01 Channel 4 0011100 +0x0e, // 02 Channel 3 0001110 +0x46, // 03 Channel 2 1000110 +0x23, // 04 Channel 1 0100011 +0x51, // 05 Channel 0 1010001 +0x68, // 06 Home 4 1101000 +0x75, // 07 Home 3 1110101 +0x7a, // 08 Home 2 1111010 +0x3d, // 09 Home 1 0111101 +0x1f, // 10 Home 0 0011111 +0x4f, // 11 Group 7 1001111 +0x26, // 12 Group 6 0100110 +0x52, // 13 Group 5 1010010 +0x29, // 14 Group 4 0101001 +0x15, // 15 Group 3 0010101 +0x0d, // 16 Group 2 0001101 +0x45, // 17 Group 1 1000101 +0x62, // 18 Group 0 1100010 +0x31, // 19 Free 4 0110001 +0x19, // 20 Free 3 0011001 +0x0d, // 21 Free 2 0001101 +0x07, // 22 Free 1 0000111 +0x43 // 23 Free 0 1000011 +}; + +static const i16 bitWeight[]= +{ +0, // 0 +1, // 1 +1, // 2 +2, // 3 +1, // 4 +2, // 5 +2, // 6 +3, // 7 +1, // 8 +2, // 9 +2, // 10 +3, // 11 +2, // 12 +3, // 13 +3, // 14 +4, // 15 +1, // 16 +2, // 17 +2, // 18 +3, // 19 +2, // 20 +3, // 21 +3, // 22 +4, // 23 +2, // 24 +3, // 25 +3, // 26 +4, // 27 +3, // 28 +4, // 29 +4, // 30 +5, // 31 +1, // 32 +2, // 33 +2, // 34 +3, // 35 +2, // 36 +3, // 37 +3, // 38 +4, // 39 +2, // 40 +3, // 41 +3, // 42 +4, // 43 +3, // 44 +4, // 45 +4, // 46 +5, // 47 +2, // 48 +3, // 49 +3, // 50 +4, // 51 +3, // 52 +4, // 53 +4, // 54 +5, // 55 +3, // 56 +4, // 57 +4, // 58 +5, // 59 +4, // 60 +5, // 61 +5, // 62 +6, // 63 +1, // 64 +2, // 65 +2, // 66 +3, // 67 +2, // 68 +3, // 69 +3, // 70 +4, // 71 +2, // 72 +3, // 73 +3, // 74 +4, // 75 +3, // 76 +4, // 77 +4, // 78 +5, // 79 +2, // 80 +3, // 81 +3, // 82 +4, // 83 +3, // 84 +4, // 85 +4, // 86 +5, // 87 +3, // 88 +4, // 89 +4, // 90 +5, // 91 +4, // 92 +5, // 93 +5, // 94 +6, // 95 +2, // 96 +3, // 97 +3, // 98 +4, // 99 +3, // 100 +4, // 101 +4, // 102 +5, // 103 +3, // 104 +4, // 105 +4, // 106 +5, // 107 +4, // 108 +5, // 109 +5, // 110 +6, // 111 +3, // 112 +4, // 113 +4, // 114 +5, // 115 +4, // 116 +5, // 117 +5, // 118 +6, // 119 +4, // 120 +5, // 121 +5, // 122 +6, // 123 +5, // 124 +6, // 125 +6, // 126 +7, // 127 +1, // 128 +2, // 129 +2, // 130 +3, // 131 +2, // 132 +3, // 133 +3, // 134 +4, // 135 +2, // 136 +3, // 137 +3, // 138 +4, // 139 +3, // 140 +4, // 141 +4, // 142 +5, // 143 +2, // 144 +3, // 145 +3, // 146 +4, // 147 +3, // 148 +4, // 149 +4, // 150 +5, // 151 +3, // 152 +4, // 153 +4, // 154 +5, // 155 +4, // 156 +5, // 157 +5, // 158 +6, // 159 +2, // 160 +3, // 161 +3, // 162 +4, // 163 +3, // 164 +4, // 165 +4, // 166 +5, // 167 +3, // 168 +4, // 169 +4, // 170 +5, // 171 +4, // 172 +5, // 173 +5, // 174 +6, // 175 +3, // 176 +4, // 177 +4, // 178 +5, // 179 +4, // 180 +5, // 181 +5, // 182 +6, // 183 +4, // 184 +5, // 185 +5, // 186 +6, // 187 +5, // 188 +6, // 189 +6, // 190 +7, // 191 +2, // 192 +3, // 193 +3, // 194 +4, // 195 +3, // 196 +4, // 197 +4, // 198 +5, // 199 +3, // 200 +4, // 201 +4, // 202 +5, // 203 +4, // 204 +5, // 205 +5, // 206 +6, // 207 +3, // 208 +4, // 209 +4, // 210 +5, // 211 +4, // 212 +5, // 213 +5, // 214 +6, // 215 +4, // 216 +5, // 217 +5, // 218 +6, // 219 +5, // 220 +6, // 221 +6, // 222 +7, // 223 +3, // 224 +4, // 225 +4, // 226 +5, // 227 +4, // 228 +5, // 229 +5, // 230 +6, // 231 +4, // 232 +5, // 233 +5, // 234 +6, // 235 +5, // 236 +6, // 237 +6, // 238 +7, // 239 +4, // 240 +5, // 241 +5, // 242 +6, // 243 +5, // 244 +6, // 245 +6, // 246 +7, // 247 +5, // 248 +6, // 249 +6, // 250 +7, // 251 +6, // 252 +7, // 253 +7, // 254 +8 // 255 +}; + + +/* + ctcss decode filter +*/ +/************************************************************** +Filter type: Low Pass +Filter model: Butterworth +Filter order: 9 +Sampling Frequency: 8 KHz +Cut Frequency: 0.250000 KHz +Coefficents Quantization: 16-bit +***************************************************************/ +static const int16_t taps_fir_lpf_250_9_66 = 66; +static const int32_t gain_fir_lpf_250_9_66 = 262144; +static const int16_t coef_fir_lpf_250_9_66[] = +{ + 676, + 364, + -3, + -415, + -860, +-1320, +-1777, +-2209, +-2593, +-2904, +-3119, +-3212, +-3162, +-2949, +-2557, +-1975, +-1198, + -226, + 932, + 2263, + 3744, + 5346, + 7034, + 8767, +10499, +12184, +13770, +15211, +16462, +17480, +18234, +18696, +18852, +18696, +18234, +17480, +16462, +15211, +13770, +12184, +10499, + 8767, + 7034, + 5346, + 3744, + 2263, + 932, + -226, +-1198, +-1975, +-2557, +-2949, +-3162, +-3212, +-3119, +-2904, +-2593, +-2209, +-1777, +-1320, + -860, + -415, + -3, + 364, + 676, + 927 +}; +/* ************************************************************* +Filter type: Low Pass +Filter model: Butterworth +Filter order: 9 +Sampling Frequency: 8 KHz +Cut Frequency: 0.215 KHz +Coefficents Quantization: 16-bit +***************************************************************/ +static const int16_t taps_fir_lpf_215_9_88 = 88; +static const int32_t gain_fir_lpf_215_9_88 = 524288; +static const int16_t coef_fir_lpf_215_9_88[] = { + 2038, + 2049, + 1991, + 1859, + 1650, + 1363, + 999, + 562, + 58, + -502, +-1106, +-1739, +-2382, +-3014, +-3612, +-4153, +-4610, +-4959, +-5172, +-5226, +-5098, +-4769, +-4222, +-3444, +-2430, +-1176, + 310, + 2021, + 3937, + 6035, + 8284, +10648, +13086, +15550, +17993, +20363, +22608, +24677, +26522, +28099, +29369, +30299, +30867, +31058, +30867, +30299, +29369, +28099, +26522, +24677, +22608, +20363, +17993, +15550, +13086, +10648, + 8284, + 6035, + 3937, + 2021, + 310, +-1176, +-2430, +-3444, +-4222, +-4769, +-5098, +-5226, +-5172, +-4959, +-4610, +-4153, +-3612, +-3014, +-2382, +-1739, +-1106, + -502, + 58, + 562, + 999, + 1363, + 1650, + 1859, + 1991, + 2049, + 2038, + 1966 +}; +// end coef fir_lpf_215_9_88 +// +/************************************************************** +Filter type: High Pass +Filter model: Butterworth +Filter order: 9 +Sampling Frequency: 8 KHz +Cut Frequency: 0.300000 KHz +Coefficents Quantization: 16-bit +***************************************************************/ +static const int16_t taps_fir_hpf_300_9_66 = 66; +static const int32_t gain_fir_hpf_300_9_66 = 32768; +static const int16_t coef_fir_hpf_300_9_66[] = +{ + -141, + -114, + -77, + -30, + 23, + 83, + 147, + 210, + 271, + 324, + 367, + 396, + 407, + 396, + 362, + 302, + 216, + 102, + -36, + -199, + -383, + -585, + -798, +-1017, +-1237, +-1452, +-1653, +-1836, +-1995, +-2124, +-2219, +-2278, +30463, +-2278, +-2219, +-2124, +-1995, +-1836, +-1653, +-1452, +-1237, +-1017, + -798, + -585, + -383, + -199, + -36, + 102, + 216, + 302, + 362, + 396, + 407, + 396, + 367, + 324, + 271, + 210, + 147, + 83, + 23, + -30, + -77, + -114, + -141, + -158 + }; +#endif /* !XPMR_COEF_H */ +/* end of file */ + + + +